Skip to content

Commit ab02459

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 ab02459

File tree

7 files changed

+234
-69
lines changed

7 files changed

+234
-69
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

+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
@@ -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

+78-4
Original file line numberDiff line numberDiff line change
@@ -586,14 +586,19 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<
586586
}
587587
}
588588

589-
fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
590-
tcx.crates(())
589+
fn find_crate(cx: &LateContext<'_>, name: &str) -> Option<DefId> {
590+
cx.tcx
591+
.crates(())
591592
.iter()
592593
.copied()
593-
.find(|&num| tcx.crate_name(num).as_str() == name)
594+
.find(|&num| cx.tcx.crate_name(num).as_str() == name)
594595
.map(CrateNum::as_def_id)
595596
}
596597

598+
if path.first() == Some(&"crate") || path.first().copied() == cx.sess().opts.crate_name.as_deref() {
599+
return def_path_res_local(cx, &path[1..]);
600+
}
601+
597602
let (base, path) = match *path {
598603
[primitive] => {
599604
return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
@@ -603,7 +608,7 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<
603608
};
604609
let tcx = cx.tcx;
605610
let starts = find_primitive(tcx, base)
606-
.chain(find_crate(tcx, base))
611+
.chain(find_crate(cx, base))
607612
.map(|id| Res::Def(tcx.def_kind(id), id));
608613

609614
for first in starts {
@@ -642,7 +647,60 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<
642647
return last;
643648
}
644649
}
650+
Res::Err
651+
}
645652

653+
fn def_path_res_local(cx: &LateContext<'_>, mut path: &[&str]) -> Res {
654+
let map = cx.tcx.hir();
655+
let mut ids = map.root_module().item_ids;
656+
while let Some(&segment) = path.first() {
657+
let mut next_ids = None;
658+
for i in ids {
659+
if let Some(Node::Item(hir::Item {
660+
ident,
661+
kind,
662+
def_id: item_def_id,
663+
..
664+
})) = map.find(i.hir_id())
665+
{
666+
if ident.name.as_str() == segment {
667+
path = &path[1..];
668+
let def_kind = match kind {
669+
ItemKind::Mod(m) => {
670+
next_ids = Some(m.item_ids);
671+
DefKind::Mod
672+
},
673+
ItemKind::Struct(..) => DefKind::Struct,
674+
ItemKind::Enum(..) => DefKind::Enum,
675+
ItemKind::ExternCrate(_) => DefKind::ExternCrate,
676+
ItemKind::Use(_, _) => DefKind::Use,
677+
ItemKind::Static(_, hir::Mutability::Mut, _) => DefKind::Static(ast::Mutability::Mut),
678+
ItemKind::Static(_, hir::Mutability::Not, _) => DefKind::Static(ast::Mutability::Not),
679+
ItemKind::Const(_, _) => DefKind::Const,
680+
ItemKind::Fn(_, _, _) => DefKind::Fn,
681+
ItemKind::Macro(_, mkind) => DefKind::Macro(*mkind),
682+
ItemKind::ForeignMod { .. } => DefKind::ForeignMod,
683+
ItemKind::GlobalAsm(_) => DefKind::GlobalAsm,
684+
ItemKind::TyAlias(_, _) => DefKind::TyAlias,
685+
ItemKind::OpaqueTy(_) => DefKind::OpaqueTy,
686+
ItemKind::Union(_, _) => DefKind::Union,
687+
ItemKind::Trait(_, _, _, _, _) => DefKind::Trait,
688+
ItemKind::TraitAlias(_, _) => DefKind::TraitAlias,
689+
ItemKind::Impl(_) => DefKind::Impl,
690+
};
691+
if path.is_empty() {
692+
return Res::Def(def_kind, item_def_id.to_def_id());
693+
}
694+
break;
695+
}
696+
}
697+
}
698+
if let Some(next_ids) = next_ids {
699+
ids = next_ids;
700+
} else {
701+
break;
702+
}
703+
}
646704
Res::Err
647705
}
648706

@@ -1811,6 +1869,22 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]])
18111869
.position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
18121870
}
18131871

1872+
/// The same as [`match_any_def_paths`], but for iterators over path-like things.
1873+
/// Useful e.g. with `str::split`
1874+
pub fn match_any_def_paths_iter<'a, P>(
1875+
cx: &LateContext<'_>,
1876+
did: DefId,
1877+
paths: impl IntoIterator<Item = P> + 'a,
1878+
) -> Option<usize>
1879+
where
1880+
P: IntoIterator<Item = &'a str>,
1881+
{
1882+
let search_path = cx.get_def_path(did);
1883+
paths
1884+
.into_iter()
1885+
.position(|p| p.into_iter().map(Symbol::intern).eq(search_path.iter().copied()))
1886+
}
1887+
18141888
/// Checks if the given `DefId` matches the path.
18151889
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
18161890
// 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+
ignore-interior-mutability = ["crate::Counted"]

0 commit comments

Comments
 (0)