|
1 | 1 | 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; |
3 | 4 | use rustc_hir as hir; |
| 5 | +use rustc_hir::def::Namespace; |
4 | 6 | use rustc_lint::{LateContext, LateLintPass}; |
5 | 7 | use rustc_middle::ty::TypeVisitable; |
6 | 8 | 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}; |
8 | 10 | use rustc_span::source_map::Span; |
9 | 11 | use rustc_span::symbol::sym; |
10 | 12 | use std::iter; |
@@ -78,98 +80,128 @@ declare_clippy_lint! { |
78 | 80 | "Check for mutable `Map`/`Set` key type" |
79 | 81 | } |
80 | 82 |
|
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 ]); |
82 | 90 |
|
83 | 91 | 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 | + |
84 | 104 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { |
85 | 105 | 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); |
87 | 107 | } |
88 | 108 | } |
89 | 109 |
|
90 | 110 | fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { |
91 | 111 | if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind { |
92 | 112 | 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); |
94 | 114 | } |
95 | 115 | } |
96 | 116 | } |
97 | 117 |
|
98 | 118 | fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { |
99 | 119 | 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); |
101 | 121 | } |
102 | 122 | } |
103 | 123 |
|
104 | 124 | fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { |
105 | 125 | if let hir::PatKind::Wild = local.pat.kind { |
106 | 126 | return; |
107 | 127 | } |
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)); |
109 | 129 | } |
110 | 130 | } |
111 | 131 |
|
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 | + } |
117 | 138 | } |
118 | | - check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output())); |
119 | | -} |
120 | 139 |
|
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); |
131 | 145 | } |
| 146 | + self.check_ty_(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output())); |
132 | 147 | } |
133 | | -} |
134 | 148 |
|
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"); |
171 | 159 | } |
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 | + } |
174 | 206 | } |
175 | 207 | } |
0 commit comments