]>
Commit | Line | Data |
---|---|---|
2b03887a FG |
1 | use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; |
2 | use clippy_utils::source::snippet_with_applicability; | |
487cf647 | 3 | use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs}; |
2b03887a FG |
4 | use if_chain::if_chain; |
5 | use rustc_ast::ast::LitKind; | |
9ffffee4 | 6 | use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; |
2b03887a FG |
7 | use rustc_errors::Applicability; |
8 | use rustc_hir as hir; | |
487cf647 | 9 | use rustc_hir::def::{DefKind, Res}; |
2b03887a FG |
10 | use rustc_hir::def_id::DefId; |
11 | use rustc_hir::{Expr, ExprKind, Local, Mutability, Node}; | |
12 | use rustc_lint::{LateContext, LateLintPass}; | |
13 | use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc}; | |
353b0b11 | 14 | use rustc_middle::ty::{self, Ty}; |
2b03887a | 15 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
487cf647 | 16 | use rustc_span::symbol::Symbol; |
2b03887a FG |
17 | use rustc_span::Span; |
18 | ||
19 | use std::str; | |
20 | ||
21 | declare_clippy_lint! { | |
22 | /// ### What it does | |
49aad941 | 23 | /// Checks for usage of def paths when a diagnostic item or a `LangItem` could be used. |
2b03887a FG |
24 | /// |
25 | /// ### Why is this bad? | |
26 | /// The path for an item is subject to change and is less efficient to look up than a | |
27 | /// diagnostic item or a `LangItem`. | |
28 | /// | |
29 | /// ### Example | |
30 | /// ```rust,ignore | |
31 | /// utils::match_type(cx, ty, &paths::VEC) | |
32 | /// ``` | |
33 | /// | |
34 | /// Use instead: | |
35 | /// ```rust,ignore | |
36 | /// utils::is_type_diagnostic_item(cx, ty, sym::Vec) | |
37 | /// ``` | |
38 | pub UNNECESSARY_DEF_PATH, | |
39 | internal, | |
40 | "using a def path when a diagnostic item or a `LangItem` is available" | |
41 | } | |
42 | ||
43 | impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]); | |
44 | ||
45 | #[derive(Default)] | |
46 | pub struct UnnecessaryDefPath { | |
9ffffee4 | 47 | array_def_ids: FxIndexSet<(DefId, Span)>, |
2b03887a FG |
48 | linted_def_ids: FxHashSet<DefId>, |
49 | } | |
50 | ||
51 | impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath { | |
52 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { | |
53 | if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) { | |
54 | return; | |
55 | } | |
56 | ||
57 | match expr.kind { | |
58 | ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span), | |
59 | ExprKind::Array(elements) => self.check_array(cx, elements, expr.span), | |
60 | _ => {}, | |
61 | } | |
62 | } | |
63 | ||
64 | fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { | |
65 | for &(def_id, span) in &self.array_def_ids { | |
66 | if self.linted_def_ids.contains(&def_id) { | |
67 | continue; | |
68 | } | |
69 | ||
70 | let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) { | |
71 | ("diagnostic item", format!("sym::{sym}")) | |
72 | } else if let Some(sym) = get_lang_item_name(cx, def_id) { | |
73 | ("language item", format!("LangItem::{sym}")) | |
74 | } else { | |
75 | continue; | |
76 | }; | |
77 | ||
78 | span_lint_and_help( | |
79 | cx, | |
80 | UNNECESSARY_DEF_PATH, | |
81 | span, | |
82 | &format!("hardcoded path to a {msg}"), | |
83 | None, | |
84 | &format!("convert all references to use `{sugg}`"), | |
85 | ); | |
86 | } | |
87 | } | |
88 | } | |
89 | ||
90 | impl UnnecessaryDefPath { | |
91 | #[allow(clippy::too_many_lines)] | |
92 | fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) { | |
93 | enum Item { | |
487cf647 | 94 | LangItem(&'static str), |
2b03887a FG |
95 | DiagnosticItem(Symbol), |
96 | } | |
97 | static PATHS: &[&[&str]] = &[ | |
98 | &["clippy_utils", "match_def_path"], | |
99 | &["clippy_utils", "match_trait_method"], | |
100 | &["clippy_utils", "ty", "match_type"], | |
101 | &["clippy_utils", "is_expr_path_def_path"], | |
102 | ]; | |
103 | ||
104 | if_chain! { | |
105 | if let [cx_arg, def_arg, args @ ..] = args; | |
106 | if let ExprKind::Path(path) = &func.kind; | |
107 | if let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id(); | |
108 | if let Some(which_path) = match_any_def_paths(cx, id, PATHS); | |
109 | let item_arg = if which_path == 4 { &args[1] } else { &args[0] }; | |
110 | // Extract the path to the matched type | |
111 | if let Some(segments) = path_to_matched_type(cx, item_arg); | |
112 | let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect(); | |
487cf647 | 113 | if let Some(def_id) = def_path_def_ids(cx, &segments[..]).next(); |
2b03887a FG |
114 | then { |
115 | // Check if the target item is a diagnostic item or LangItem. | |
116 | #[rustfmt::skip] | |
117 | let (msg, item) = if let Some(item_name) | |
118 | = cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id) | |
119 | { | |
120 | ( | |
121 | "use of a def path to a diagnostic item", | |
122 | Item::DiagnosticItem(*item_name), | |
123 | ) | |
124 | } else if let Some(item_name) = get_lang_item_name(cx, def_id) { | |
125 | ( | |
126 | "use of a def path to a `LangItem`", | |
127 | Item::LangItem(item_name), | |
128 | ) | |
129 | } else { | |
130 | return; | |
131 | }; | |
132 | ||
133 | let has_ctor = match cx.tcx.def_kind(def_id) { | |
134 | DefKind::Struct => { | |
135 | let variant = cx.tcx.adt_def(def_id).non_enum_variant(); | |
487cf647 | 136 | variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public()) |
2b03887a FG |
137 | }, |
138 | DefKind::Variant => { | |
139 | let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id); | |
487cf647 | 140 | variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public()) |
2b03887a FG |
141 | }, |
142 | _ => false, | |
143 | }; | |
144 | ||
145 | let mut app = Applicability::MachineApplicable; | |
146 | let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app); | |
147 | let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app); | |
148 | let (sugg, with_note) = match (which_path, item) { | |
149 | // match_def_path | |
150 | (0, Item::DiagnosticItem(item)) => ( | |
151 | format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"), | |
152 | has_ctor, | |
153 | ), | |
154 | (0, Item::LangItem(item)) => ( | |
487cf647 | 155 | format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"), |
2b03887a FG |
156 | has_ctor, |
157 | ), | |
158 | // match_trait_method | |
159 | (1, Item::DiagnosticItem(item)) => { | |
160 | (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false) | |
161 | }, | |
162 | // match_type | |
163 | (2, Item::DiagnosticItem(item)) => ( | |
164 | format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"), | |
165 | false, | |
166 | ), | |
167 | (2, Item::LangItem(item)) => ( | |
168 | format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"), | |
169 | false, | |
170 | ), | |
171 | // is_expr_path_def_path | |
172 | (3, Item::DiagnosticItem(item)) if has_ctor => ( | |
173 | format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",), | |
174 | false, | |
175 | ), | |
176 | (3, Item::LangItem(item)) if has_ctor => ( | |
177 | format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",), | |
178 | false, | |
179 | ), | |
180 | (3, Item::DiagnosticItem(item)) => ( | |
181 | format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"), | |
182 | false, | |
183 | ), | |
184 | (3, Item::LangItem(item)) => ( | |
185 | format!( | |
186 | "path_res({cx_snip}, {def_snip}).opt_def_id()\ | |
487cf647 | 187 | .map_or(false, |id| {cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some(id))", |
2b03887a FG |
188 | ), |
189 | false, | |
190 | ), | |
191 | _ => return, | |
192 | }; | |
193 | ||
194 | span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| { | |
195 | diag.span_suggestion(span, "try", sugg, app); | |
196 | if with_note { | |
197 | diag.help( | |
198 | "if this `DefId` came from a constructor expression or pattern then the \ | |
199 | parent `DefId` should be used instead", | |
200 | ); | |
201 | } | |
202 | }); | |
203 | ||
204 | self.linted_def_ids.insert(def_id); | |
205 | } | |
206 | } | |
207 | } | |
208 | ||
209 | fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) { | |
210 | let Some(path) = path_from_array(elements) else { return }; | |
211 | ||
487cf647 | 212 | for def_id in def_path_def_ids(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) { |
2b03887a FG |
213 | self.array_def_ids.insert((def_id, span)); |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<String>> { | |
219 | match peel_hir_expr_refs(expr).0.kind { | |
220 | ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) { | |
221 | Res::Local(hir_id) => { | |
9c376795 | 222 | let parent_id = cx.tcx.hir().parent_id(hir_id); |
2b03887a FG |
223 | if let Some(Node::Local(Local { init: Some(init), .. })) = cx.tcx.hir().find(parent_id) { |
224 | path_to_matched_type(cx, init) | |
225 | } else { | |
226 | None | |
227 | } | |
228 | }, | |
229 | Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path( | |
230 | cx, | |
231 | cx.tcx.eval_static_initializer(def_id).ok()?.inner(), | |
9ffffee4 | 232 | cx.tcx.type_of(def_id).subst_identity(), |
2b03887a FG |
233 | ), |
234 | Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? { | |
235 | ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => { | |
9ffffee4 | 236 | read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).subst_identity()) |
2b03887a FG |
237 | }, |
238 | _ => None, | |
239 | }, | |
240 | _ => None, | |
241 | }, | |
242 | ExprKind::Array(exprs) => path_from_array(exprs), | |
243 | _ => None, | |
244 | } | |
245 | } | |
246 | ||
247 | fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> { | |
248 | let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() { | |
487cf647 | 249 | let &alloc = alloc.provenance().ptrs().values().next()?; |
2b03887a FG |
250 | if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) { |
251 | (alloc.inner(), ty) | |
252 | } else { | |
253 | return None; | |
254 | } | |
255 | } else { | |
256 | (alloc, ty) | |
257 | }; | |
258 | ||
259 | if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind() | |
260 | && let ty::Ref(_, ty, Mutability::Not) = *ty.kind() | |
261 | && ty.is_str() | |
262 | { | |
263 | alloc | |
264 | .provenance() | |
487cf647 | 265 | .ptrs() |
2b03887a FG |
266 | .values() |
267 | .map(|&alloc| { | |
268 | if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) { | |
269 | let alloc = alloc.inner(); | |
270 | str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len())) | |
271 | .ok().map(ToOwned::to_owned) | |
272 | } else { | |
273 | None | |
274 | } | |
275 | }) | |
276 | .collect() | |
277 | } else { | |
278 | None | |
279 | } | |
280 | } | |
281 | ||
282 | fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> { | |
283 | exprs | |
284 | .iter() | |
285 | .map(|expr| { | |
286 | if let ExprKind::Lit(lit) = &expr.kind { | |
287 | if let LitKind::Str(sym, _) = lit.node { | |
288 | return Some((*sym.as_str()).to_owned()); | |
289 | } | |
290 | } | |
291 | ||
292 | None | |
293 | }) | |
294 | .collect() | |
295 | } | |
296 | ||
487cf647 FG |
297 | fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> { |
298 | if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) { | |
299 | Some(lang_item.variant_name()) | |
2b03887a FG |
300 | } else { |
301 | None | |
302 | } | |
303 | } |