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