Skip to content

Commit 1bd11c1

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 967f172 commit 1bd11c1

File tree

7 files changed

+157
-64
lines changed

7 files changed

+157
-64
lines changed

clippy_lints/src/lib.rs

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

clippy_lints/src/mut_key.rs

+81-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,116 @@ 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+
arc_likes: 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(arc_likes: Vec<String>) -> Self {
119+
Self { arc_likes }
117120
}
118-
check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
119-
}
120121

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

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

clippy_lints/src/utils/conf.rs

+5
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,11 @@ define_Conf! {
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+
(arc_like_types: Vec<String> = Vec::new()),
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/toml_unknown_key/conf_unknown_key.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
33
allow-expect-in-tests
44
allow-unwrap-in-tests
55
allowed-scripts
6+
arc-like-types
67
arithmetic-side-effects-allowed
78
array-size-threshold
89
avoid-breaking-exported-api

0 commit comments

Comments
 (0)