|
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