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;
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;
21 declare_clippy_lint! {
23 /// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
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`.
31 /// utils::match_type(cx, ty, &paths::VEC)
36 /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
38 pub UNNECESSARY_DEF_PATH,
40 "using a def path when a diagnostic item or a `LangItem` is available"
43 impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
46 pub struct UnnecessaryDefPath {
47 array_def_ids: FxHashSet<(DefId, Span)>,
48 linted_def_ids: FxHashSet<DefId>,
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) {
58 ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span),
59 ExprKind::Array(elements) => self.check_array(cx, elements, expr.span),
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) {
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}"))
82 &format!("hardcoded path to a {msg}"),
84 &format!("convert all references to use `{sugg}`"),
90 impl UnnecessaryDefPath {
91 #[allow(clippy::too_many_lines)]
92 fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) {
94 LangItem(&'static str),
95 DiagnosticItem(Symbol),
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"],
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();
115 // Check if the target item is a diagnostic item or LangItem.
117 let (msg, item) = if let Some(item_name)
118 = cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id)
121 "use of a def path to a diagnostic item",
122 Item::DiagnosticItem(*item_name),
124 } else if let Some(item_name) = get_lang_item_name(cx, def_id) {
126 "use of a def path to a `LangItem`",
127 Item::LangItem(item_name),
133 let has_ctor = match cx.tcx.def_kind(def_id) {
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())
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())
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) {
150 (0, Item::DiagnosticItem(item)) => (
151 format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"),
154 (0, Item::LangItem(item)) => (
155 format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"),
158 // match_trait_method
159 (1, Item::DiagnosticItem(item)) => {
160 (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false)
163 (2, Item::DiagnosticItem(item)) => (
164 format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
167 (2, Item::LangItem(item)) => (
168 format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"),
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})",),
176 (3, Item::LangItem(item)) if has_ctor => (
177 format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",),
180 (3, Item::DiagnosticItem(item)) => (
181 format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
184 (3, Item::LangItem(item)) => (
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))",
194 span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| {
195 diag.span_suggestion(span, "try", sugg, app);
198 "if this `DefId` came from a constructor expression or pattern then the \
199 parent `DefId` should be used instead",
204 self.linted_def_ids.insert(def_id);
209 fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) {
210 let Some(path) = path_from_array(elements) else { return };
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));
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)
229 Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
231 cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
232 cx.tcx.type_of(def_id),
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))
242 ExprKind::Array(exprs) => path_from_array(exprs),
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) {
259 if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
260 && let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
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)
282 fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> {
286 if let ExprKind::Lit(lit) = &expr.kind {
287 if let LitKind::Str(sym, _) = lit.node {
288 return Some((*sym.as_str()).to_owned());
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())