]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
New upstream version 1.71.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / utils / internal_lints / unnecessary_def_path.rs
CommitLineData
2b03887a
FG
1use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
2use clippy_utils::source::snippet_with_applicability;
487cf647 3use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
2b03887a
FG
4use if_chain::if_chain;
5use rustc_ast::ast::LitKind;
9ffffee4 6use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
2b03887a
FG
7use rustc_errors::Applicability;
8use rustc_hir as hir;
487cf647 9use rustc_hir::def::{DefKind, Res};
2b03887a
FG
10use rustc_hir::def_id::DefId;
11use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
12use rustc_lint::{LateContext, LateLintPass};
13use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
353b0b11 14use rustc_middle::ty::{self, Ty};
2b03887a 15use rustc_session::{declare_tool_lint, impl_lint_pass};
487cf647 16use rustc_span::symbol::Symbol;
2b03887a
FG
17use rustc_span::Span;
18
19use std::str;
20
21declare_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
43impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
44
45#[derive(Default)]
46pub struct UnnecessaryDefPath {
9ffffee4 47 array_def_ids: FxIndexSet<(DefId, Span)>,
2b03887a
FG
48 linted_def_ids: FxHashSet<DefId>,
49}
50
51impl<'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
90impl 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
218fn 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
247fn 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
282fn 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
297fn 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}