]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
New upstream version 1.75.0+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};
781aab86
FG
13use rustc_middle::mir::interpret::{Allocation, GlobalAlloc};
14use rustc_middle::mir::ConstValue;
353b0b11 15use rustc_middle::ty::{self, Ty};
2b03887a 16use rustc_session::{declare_tool_lint, impl_lint_pass};
487cf647 17use rustc_span::symbol::Symbol;
2b03887a
FG
18use rustc_span::Span;
19
20use std::str;
21
22declare_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
44impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
45
46#[derive(Default)]
47pub struct UnnecessaryDefPath {
9ffffee4 48 array_def_ids: FxIndexSet<(DefId, Span)>,
2b03887a
FG
49 linted_def_ids: FxHashSet<DefId>,
50}
51
52impl<'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
91impl 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
219fn 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
249fn 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
285fn 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
300fn 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}