]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
Merge commit 'f4850f7292efa33759b4f7f9b7621268979e9914' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / utils / internal_lints / unnecessary_def_path.rs
1 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
2 use clippy_utils::source::snippet_with_applicability;
3 use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
4 use if_chain::if_chain;
5 use rustc_ast::ast::LitKind;
6 use rustc_data_structures::fx::FxHashSet;
7 use rustc_errors::Applicability;
8 use rustc_hir as hir;
9 use rustc_hir::def::{DefKind, Res};
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};
14 use rustc_middle::ty::{self, DefIdTree, Ty};
15 use rustc_session::{declare_tool_lint, impl_lint_pass};
16 use rustc_span::symbol::Symbol;
17 use rustc_span::Span;
18
19 use std::str;
20
21 declare_clippy_lint! {
22     /// ### What it does
23     /// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
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 {
47     array_def_ids: FxHashSet<(DefId, Span)>,
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 {
94             LangItem(&'static str),
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();
113             if let Some(def_id) = def_path_def_ids(cx, &segments[..]).next();
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();
136                         variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
137                     },
138                     DefKind::Variant => {
139                         let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id);
140                         variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
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)) => (
155                         format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"),
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()\
187                                 .map_or(false, |id| {cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some(id))",
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
212         for def_id in def_path_def_ids(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
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) => {
222                 let parent_id = cx.tcx.hir().get_parent_node(hir_id);
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(),
232                 cx.tcx.type_of(def_id),
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 => {
236                     read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id))
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() {
249         let &alloc = alloc.provenance().ptrs().values().next()?;
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()
265             .ptrs()
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
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())
300     } else {
301         None
302     }
303 }