Skip to content

Commit d06539f

Browse files
committed
make arc-likes 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"]`
1 parent 191c983 commit d06539f

File tree

8 files changed

+210
-65
lines changed

8 files changed

+210
-65
lines changed

clippy_lints/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
735735
let max_trait_bounds = conf.max_trait_bounds;
736736
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
737737
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
738-
store.register_late_pass(|_| Box::new(mut_key::MutableKeyType));
738+
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
739+
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
739740
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
740741
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
741742
store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new()));

clippy_lints/src/mut_key.rs

+84-63
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use clippy_utils::diagnostics::span_lint;
2-
use clippy_utils::trait_ref_of_method;
2+
use clippy_utils::{match_any_def_paths_iter, trait_ref_of_method};
33
use rustc_hir as hir;
44
use rustc_lint::{LateContext, LateLintPass};
55
use rustc_middle::ty::TypeVisitable;
66
use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
7-
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
use rustc_session::{declare_tool_lint, impl_lint_pass};
88
use rustc_span::source_map::Span;
99
use rustc_span::symbol::sym;
1010
use std::iter;
@@ -78,98 +78,119 @@ declare_clippy_lint! {
7878
"Check for mutable `Map`/`Set` key type"
7979
}
8080

81-
declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
81+
#[derive(Clone)]
82+
pub struct MutableKeyType {
83+
ignore_interior_mutability: Vec<String>,
84+
}
85+
86+
impl_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
8287

8388
impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
8489
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
8590
if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
86-
check_sig(cx, item.hir_id(), sig.decl);
91+
self.check_sig(cx, item.hir_id(), sig.decl);
8792
}
8893
}
8994

9095
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
9196
if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
9297
if trait_ref_of_method(cx, item.def_id.def_id).is_none() {
93-
check_sig(cx, item.hir_id(), sig.decl);
98+
self.check_sig(cx, item.hir_id(), sig.decl);
9499
}
95100
}
96101
}
97102

98103
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
99104
if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
100-
check_sig(cx, item.hir_id(), sig.decl);
105+
self.check_sig(cx, item.hir_id(), sig.decl);
101106
}
102107
}
103108

104109
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
105110
if let hir::PatKind::Wild = local.pat.kind {
106111
return;
107112
}
108-
check_ty(cx, local.span, cx.typeck_results().pat_ty(local.pat));
113+
self.check_ty_(cx, local.span, cx.typeck_results().pat_ty(local.pat));
109114
}
110115
}
111116

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);
117+
impl MutableKeyType {
118+
pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
119+
Self {
120+
ignore_interior_mutability,
121+
}
117122
}
118-
check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
119-
}
120123

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");
124+
fn check_sig<'tcx>(&self, cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
125+
let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
126+
let fn_sig = cx.tcx.fn_sig(fn_def_id);
127+
for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
128+
self.check_ty_(cx, hir_ty.span, *ty);
131129
}
130+
self.check_ty_(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
132131
}
133-
}
134132

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

clippy_lints/src/utils/conf.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,15 @@ define_Conf! {
383383
///
384384
/// Whether `dbg!` should be allowed in test functions
385385
(allow_dbg_in_tests: bool = false),
386-
/// Lint: RESULT_LARGE_ERR
386+
/// Lint: RESULT_LARGE_ERR.
387387
///
388388
/// The maximum size of the `Err`-variant in a `Result` returned from a function
389389
(large_error_threshold: u64 = 128),
390+
/// Lint: MUTABLE_KEY.
391+
///
392+
/// A list of paths to types that should be treated like `Arc`, i.e. ignored but
393+
/// for the generic parameters for determining interior mutability
394+
(ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()])),
390395
}
391396

392397
/// Search for the configuration file.

clippy_utils/src/lib.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,22 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]])
18111811
.position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
18121812
}
18131813

1814+
/// The same as [`match_any_def_paths`], but for iterators over path-like things.
1815+
/// Useful e.g. with `str::split`
1816+
pub fn match_any_def_paths_iter<'a, P>(
1817+
cx: &LateContext<'_>,
1818+
did: DefId,
1819+
paths: impl IntoIterator<Item = P> + 'a,
1820+
) -> Option<usize>
1821+
where
1822+
P: IntoIterator<Item = &'a str>,
1823+
{
1824+
let search_path = cx.get_def_path(did);
1825+
paths
1826+
.into_iter()
1827+
.position(|p| p.into_iter().map(Symbol::intern).eq(search_path.iter().copied()))
1828+
}
1829+
18141830
/// Checks if the given `DefId` matches the path.
18151831
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
18161832
// We should probably move to Symbols in Clippy as well rather than interning every time.

tests/ui-toml/mut_key/clippy.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
arc-like-types = ["mut_key::Counted"]

tests/ui-toml/mut_key/mut_key.rs

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

tests/ui-toml/mut_key/mut_key.stderr

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `arc-like-types`, expected one of
2+
allow-dbg-in-tests
3+
allow-expect-in-tests
4+
allow-unwrap-in-tests
5+
allowed-scripts
6+
arithmetic-side-effects-allowed
7+
array-size-threshold
8+
avoid-breaking-exported-api
9+
await-holding-invalid-types
10+
blacklisted-names
11+
cargo-ignore-publish
12+
cognitive-complexity-threshold
13+
cyclomatic-complexity-threshold
14+
disallowed-macros
15+
disallowed-methods
16+
disallowed-names
17+
disallowed-types
18+
doc-valid-idents
19+
enable-raw-pointer-heuristic-for-send
20+
enforced-import-renames
21+
enum-variant-name-threshold
22+
enum-variant-size-threshold
23+
ignore-interior-mutability
24+
large-error-threshold
25+
literal-representation-threshold
26+
max-fn-params-bools
27+
max-include-file-size
28+
max-struct-bools
29+
max-suggested-slice-pattern-length
30+
max-trait-bounds
31+
msrv
32+
pass-by-value-size-limit
33+
single-char-binding-names-threshold
34+
standard-macro-braces
35+
third-party
36+
too-large-for-stack
37+
too-many-arguments-threshold
38+
too-many-lines-threshold
39+
trivial-copy-size-limit
40+
type-complexity-threshold
41+
unreadable-literal-lint-fractions
42+
upper-case-acronyms-aggressive
43+
vec-box-size-threshold
44+
verbose-bit-mask-threshold
45+
warn-on-all-wildcard-imports
46+
at line 1 column 1
47+
48+
error: aborting due to previous error
49+

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
max-fn-params-bools

0 commit comments

Comments
 (0)