Skip to content

Commit 9a42501

Browse files
committed
Auto merge of rust-lang#9692 - llogiq:mutable-key-more-arcs, r=Alexendoo
make ignored internally mutable types for `mutable-key` configurable We had some false positives where people would create their own types that had interior mutability unrelated to hash/eq. This addition lets you configure this as e.g. `arc-like-types=["bytes::Bytes"]` This fixes rust-lang#5325 by allowing users to specify the types whose innards like `Arc` should be ignored (the generic types are still checked) for the sake of detecting inner mutability. r? `@Alexendoo` --- changelog: Allow configuring types to ignore internal mutability in `mutable-key`
2 parents df67ebb + eba36e6 commit 9a42501

File tree

7 files changed

+199
-66
lines changed

7 files changed

+199
-66
lines changed

clippy_lints/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
727727
let max_trait_bounds = conf.max_trait_bounds;
728728
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
729729
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
730-
store.register_late_pass(|_| Box::new(mut_key::MutableKeyType));
730+
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
731+
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
731732
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
732733
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
733734
store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new()));

clippy_lints/src/mut_key.rs

+95-63
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use clippy_utils::diagnostics::span_lint;
2-
use clippy_utils::trait_ref_of_method;
2+
use clippy_utils::{def_path_res, trait_ref_of_method};
3+
use rustc_data_structures::fx::FxHashSet;
34
use rustc_hir as hir;
5+
use rustc_hir::def::Namespace;
46
use rustc_lint::{LateContext, LateLintPass};
57
use rustc_middle::ty::TypeVisitable;
68
use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
7-
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use rustc_session::{declare_tool_lint, impl_lint_pass};
810
use rustc_span::source_map::Span;
911
use rustc_span::symbol::sym;
1012
use std::iter;
@@ -78,98 +80,128 @@ declare_clippy_lint! {
7880
"Check for mutable `Map`/`Set` key type"
7981
}
8082

81-
declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
83+
#[derive(Clone)]
84+
pub struct MutableKeyType {
85+
ignore_interior_mutability: Vec<String>,
86+
ignore_mut_def_ids: FxHashSet<hir::def_id::DefId>,
87+
}
88+
89+
impl_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
8290

8391
impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
92+
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
93+
self.ignore_mut_def_ids.clear();
94+
let mut path = Vec::new();
95+
for ty in &self.ignore_interior_mutability {
96+
path.extend(ty.split("::"));
97+
if let Some(id) = def_path_res(cx, &path[..], Some(Namespace::TypeNS)).opt_def_id() {
98+
self.ignore_mut_def_ids.insert(id);
99+
}
100+
path.clear();
101+
}
102+
}
103+
84104
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
85105
if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
86-
check_sig(cx, item.hir_id(), sig.decl);
106+
self.check_sig(cx, item.hir_id(), sig.decl);
87107
}
88108
}
89109

90110
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
91111
if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
92112
if trait_ref_of_method(cx, item.def_id.def_id).is_none() {
93-
check_sig(cx, item.hir_id(), sig.decl);
113+
self.check_sig(cx, item.hir_id(), sig.decl);
94114
}
95115
}
96116
}
97117

98118
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
99119
if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
100-
check_sig(cx, item.hir_id(), sig.decl);
120+
self.check_sig(cx, item.hir_id(), sig.decl);
101121
}
102122
}
103123

104124
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
105125
if let hir::PatKind::Wild = local.pat.kind {
106126
return;
107127
}
108-
check_ty(cx, local.span, cx.typeck_results().pat_ty(local.pat));
128+
self.check_ty_(cx, local.span, cx.typeck_results().pat_ty(local.pat));
109129
}
110130
}
111131

112-
fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
113-
let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
114-
let fn_sig = cx.tcx.fn_sig(fn_def_id);
115-
for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
116-
check_ty(cx, hir_ty.span, *ty);
132+
impl MutableKeyType {
133+
pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
134+
Self {
135+
ignore_interior_mutability,
136+
ignore_mut_def_ids: FxHashSet::default(),
137+
}
117138
}
118-
check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
119-
}
120139

121-
// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
122-
// generics (because the compiler cannot ensure immutability for unknown types).
123-
fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
124-
let ty = ty.peel_refs();
125-
if let Adt(def, substs) = ty.kind() {
126-
let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
127-
.iter()
128-
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
129-
if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
130-
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
140+
fn check_sig<'tcx>(&self, cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
141+
let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
142+
let fn_sig = cx.tcx.fn_sig(fn_def_id);
143+
for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
144+
self.check_ty_(cx, hir_ty.span, *ty);
131145
}
146+
self.check_ty_(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
132147
}
133-
}
134148

135-
/// Determines if a type contains interior mutability which would affect its implementation of
136-
/// [`Hash`] or [`Ord`].
137-
fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
138-
match *ty.kind() {
139-
Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || is_interior_mutable_type(cx, inner_ty, span),
140-
Slice(inner_ty) => is_interior_mutable_type(cx, inner_ty, span),
141-
Array(inner_ty, size) => {
142-
size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
143-
&& is_interior_mutable_type(cx, inner_ty, span)
144-
},
145-
Tuple(fields) => fields.iter().any(|ty| is_interior_mutable_type(cx, ty, span)),
146-
Adt(def, substs) => {
147-
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
148-
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
149-
// because they have no impl for `Hash` or `Ord`.
150-
let is_std_collection = [
151-
sym::Option,
152-
sym::Result,
153-
sym::LinkedList,
154-
sym::Vec,
155-
sym::VecDeque,
156-
sym::BTreeMap,
157-
sym::BTreeSet,
158-
sym::Rc,
159-
sym::Arc,
160-
]
161-
.iter()
162-
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
163-
let is_box = Some(def.did()) == cx.tcx.lang_items().owned_box();
164-
if is_std_collection || is_box {
165-
// The type is mutable if any of its type parameters are
166-
substs.types().any(|ty| is_interior_mutable_type(cx, ty, span))
167-
} else {
168-
!ty.has_escaping_bound_vars()
169-
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
170-
&& !ty.is_freeze(cx.tcx.at(span), cx.param_env)
149+
// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
150+
// generics (because the compiler cannot ensure immutability for unknown types).
151+
fn check_ty_<'tcx>(&self, cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
152+
let ty = ty.peel_refs();
153+
if let Adt(def, substs) = ty.kind() {
154+
let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
155+
.iter()
156+
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
157+
if is_keyed_type && self.is_interior_mutable_type(cx, substs.type_at(0), span) {
158+
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
171159
}
172-
},
173-
_ => false,
160+
}
161+
}
162+
163+
/// Determines if a type contains interior mutability which would affect its implementation of
164+
/// [`Hash`] or [`Ord`].
165+
fn is_interior_mutable_type<'tcx>(&self, cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
166+
match *ty.kind() {
167+
Ref(_, inner_ty, mutbl) => {
168+
mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty, span)
169+
},
170+
Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty, span),
171+
Array(inner_ty, size) => {
172+
size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
173+
&& self.is_interior_mutable_type(cx, inner_ty, span)
174+
},
175+
Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty, span)),
176+
Adt(def, substs) => {
177+
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
178+
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
179+
// because they have no impl for `Hash` or `Ord`.
180+
let def_id = def.did();
181+
let is_std_collection = [
182+
sym::Option,
183+
sym::Result,
184+
sym::LinkedList,
185+
sym::Vec,
186+
sym::VecDeque,
187+
sym::BTreeMap,
188+
sym::BTreeSet,
189+
sym::Rc,
190+
sym::Arc,
191+
]
192+
.iter()
193+
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
194+
let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
195+
if is_std_collection || is_box || self.ignore_mut_def_ids.contains(&def_id) {
196+
// The type is mutable if any of its type parameters are
197+
substs.types().any(|ty| self.is_interior_mutable_type(cx, ty, span))
198+
} else {
199+
!ty.has_escaping_bound_vars()
200+
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
201+
&& !ty.is_freeze(cx.tcx.at(span), cx.param_env)
202+
}
203+
},
204+
_ => false,
205+
}
174206
}
175207
}

clippy_lints/src/utils/conf.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,15 @@ define_Conf! {
389389
///
390390
/// Whether `dbg!` should be allowed in test functions
391391
(allow_dbg_in_tests: bool = false),
392-
/// Lint: RESULT_LARGE_ERR
392+
/// Lint: RESULT_LARGE_ERR.
393393
///
394394
/// The maximum size of the `Err`-variant in a `Result` returned from a function
395395
(large_error_threshold: u64 = 128),
396+
/// Lint: MUTABLE_KEY.
397+
///
398+
/// A list of paths to types that should be treated like `Arc`, i.e. ignored but
399+
/// for the generic parameters for determining interior mutability
400+
(ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()])),
396401
}
397402

398403
/// Search for the configuration file.

clippy_utils/src/lib.rs

+41-1
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,13 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<
598598
[primitive] => {
599599
return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
600600
},
601-
[base, ref path @ ..] => (base, path),
601+
[base, ref path @ ..] => {
602+
let crate_name = cx.sess().opts.crate_name.as_deref();
603+
if Some(base) == crate_name {
604+
return def_path_res_local(cx, path);
605+
}
606+
(base, path)
607+
},
602608
_ => return Res::Err,
603609
};
604610
let tcx = cx.tcx;
@@ -642,7 +648,41 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<
642648
return last;
643649
}
644650
}
651+
Res::Err
652+
}
645653

654+
fn def_path_res_local(cx: &LateContext<'_>, mut path: &[&str]) -> Res {
655+
let map = cx.tcx.hir();
656+
let mut ids = map.root_module().item_ids;
657+
while let Some(&segment) = path.first() {
658+
let mut next_ids = None;
659+
for i in ids {
660+
if let Some(Node::Item(hir::Item {
661+
ident,
662+
kind,
663+
def_id: item_def_id,
664+
..
665+
})) = map.find(i.hir_id())
666+
{
667+
if ident.name.as_str() == segment {
668+
path = &path[1..];
669+
if path.is_empty() {
670+
let def_id = item_def_id.to_def_id();
671+
return Res::Def(cx.tcx.def_kind(def_id), def_id);
672+
}
673+
if let ItemKind::Mod(m) = kind {
674+
next_ids = Some(m.item_ids);
675+
};
676+
break;
677+
}
678+
}
679+
}
680+
if let Some(next_ids) = next_ids {
681+
ids = next_ids;
682+
} else {
683+
break;
684+
}
685+
}
646686
Res::Err
647687
}
648688

tests/ui-toml/mut_key/clippy.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignore-interior-mutability = ["mut_key::Counted"]

tests/ui-toml/mut_key/mut_key.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// compile-flags: --crate-name mut_key
2+
3+
#![warn(clippy::mutable_key_type)]
4+
5+
use std::cmp::{Eq, PartialEq};
6+
use std::collections::{HashMap, HashSet};
7+
use std::hash::{Hash, Hasher};
8+
use std::ops::Deref;
9+
use std::sync::atomic::{AtomicUsize, Ordering};
10+
11+
struct Counted<T> {
12+
count: AtomicUsize,
13+
val: T,
14+
}
15+
16+
impl<T: Clone> Clone for Counted<T> {
17+
fn clone(&self) -> Self {
18+
Self {
19+
count: AtomicUsize::new(0),
20+
val: self.val.clone(),
21+
}
22+
}
23+
}
24+
25+
impl<T: PartialEq> PartialEq for Counted<T> {
26+
fn eq(&self, other: &Self) -> bool {
27+
self.val == other.val
28+
}
29+
}
30+
impl<T: PartialEq + Eq> Eq for Counted<T> {}
31+
32+
impl<T: Hash> Hash for Counted<T> {
33+
fn hash<H: Hasher>(&self, state: &mut H) {
34+
self.val.hash(state);
35+
}
36+
}
37+
38+
impl<T> Deref for Counted<T> {
39+
type Target = T;
40+
41+
fn deref(&self) -> &T {
42+
self.count.fetch_add(1, Ordering::AcqRel);
43+
&self.val
44+
}
45+
}
46+
47+
// This is not linted because `"mut_key::Counted"` is in
48+
// `arc_like_types` in `clippy.toml`
49+
fn should_not_take_this_arg(_v: HashSet<Counted<String>>) {}
50+
51+
fn main() {
52+
should_not_take_this_arg(HashSet::new());
53+
}

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
2020
enforced-import-renames
2121
enum-variant-name-threshold
2222
enum-variant-size-threshold
23+
ignore-interior-mutability
2324
large-error-threshold
2425
literal-representation-threshold
2526
matches-for-let-else

0 commit comments

Comments
 (0)