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