-use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{def_path_res, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
+use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Namespace, Res};
use rustc_hir::def_id::DefId;
-use rustc_hir::{ExprKind, Local, Mutability, Node};
+use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
use rustc_middle::ty::{self, AssocKind, DefIdTree, Ty};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
use std::str;
"using a def path when a diagnostic item or a `LangItem` is available"
}
-declare_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+
+#[derive(Default)]
+pub struct UnnecessaryDefPath {
+ array_def_ids: FxHashSet<(DefId, Span)>,
+ linted_def_ids: FxHashSet<DefId>,
+}
-#[allow(clippy::too_many_lines)]
impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
+ return;
+ }
+
+ match expr.kind {
+ ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span),
+ ExprKind::Array(elements) => self.check_array(cx, elements, expr.span),
+ _ => {},
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ for &(def_id, span) in &self.array_def_ids {
+ if self.linted_def_ids.contains(&def_id) {
+ continue;
+ }
+
+ let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) {
+ ("diagnostic item", format!("sym::{sym}"))
+ } else if let Some(sym) = get_lang_item_name(cx, def_id) {
+ ("language item", format!("LangItem::{sym}"))
+ } else {
+ continue;
+ };
+
+ span_lint_and_help(
+ cx,
+ UNNECESSARY_DEF_PATH,
+ span,
+ &format!("hardcoded path to a {msg}"),
+ None,
+ &format!("convert all references to use `{sugg}`"),
+ );
+ }
+ }
+}
+
+impl UnnecessaryDefPath {
+ #[allow(clippy::too_many_lines)]
+ fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) {
enum Item {
LangItem(Symbol),
DiagnosticItem(Symbol),
&["clippy_utils", "is_expr_path_def_path"],
];
- if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
- return;
- }
-
if_chain! {
- if let ExprKind::Call(func, [cx_arg, def_arg, args@..]) = expr.kind;
+ if let [cx_arg, def_arg, args@..] = args;
if let ExprKind::Path(path) = &func.kind;
if let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if let Some(which_path) = match_any_def_paths(cx, id, PATHS);
// Extract the path to the matched type
if let Some(segments) = path_to_matched_type(cx, item_arg);
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
- if let Some(def_id) = def_path_res(cx, &segments[..], None).opt_def_id();
+ if let Some(def_id) = inherent_def_path_res(cx, &segments[..]);
then {
- // def_path_res will match field names before anything else, but for this we want to match
- // inherent functions first.
- let def_id = if cx.tcx.def_kind(def_id) == DefKind::Field {
- let method_name = *segments.last().unwrap();
- cx.tcx.def_key(def_id).parent
- .and_then(|parent_idx|
- cx.tcx.inherent_impls(DefId { index: parent_idx, krate: def_id.krate }).iter()
- .find_map(|impl_id| cx.tcx.associated_items(*impl_id)
- .find_by_name_and_kind(
- cx.tcx,
- Ident::from_str(method_name),
- AssocKind::Fn,
- *impl_id,
- )
- )
- )
- .map_or(def_id, |item| item.def_id)
- } else {
- def_id
- };
-
// Check if the target item is a diagnostic item or LangItem.
let (msg, item) = if let Some(item_name)
= cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id)
"use of a def path to a diagnostic item",
Item::DiagnosticItem(*item_name),
)
- } else if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) {
- let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id();
- let item_name = cx.tcx.adt_def(lang_items).variants().iter().nth(lang_item).unwrap().name;
+ } else if let Some(item_name) = get_lang_item_name(cx, def_id) {
(
"use of a def path to a `LangItem`",
Item::LangItem(item_name),
span_lint_and_then(
cx,
UNNECESSARY_DEF_PATH,
- expr.span,
+ span,
msg,
|diag| {
- diag.span_suggestion(expr.span, "try", sugg, app);
+ diag.span_suggestion(span, "try", sugg, app);
if with_note {
diag.help(
"if this `DefId` came from a constructor expression or pattern then the \
}
},
);
+
+ self.linted_def_ids.insert(def_id);
}
}
}
+
+ fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) {
+ let Some(path) = path_from_array(elements) else { return };
+
+ if let Some(def_id) = inherent_def_path_res(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
+ self.array_def_ids.insert((def_id, span));
+ }
+ }
}
fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<String>> {
},
_ => None,
},
- ExprKind::Array(exprs) => exprs
- .iter()
- .map(|expr| {
- if let ExprKind::Lit(lit) = &expr.kind {
- if let LitKind::Str(sym, _) = lit.node {
- return Some((*sym.as_str()).to_owned());
- }
- }
-
- None
- })
- .collect(),
+ ExprKind::Array(exprs) => path_from_array(exprs),
_ => None,
}
}
None
}
}
+
+fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> {
+ exprs
+ .iter()
+ .map(|expr| {
+ if let ExprKind::Lit(lit) = &expr.kind {
+ if let LitKind::Str(sym, _) = lit.node {
+ return Some((*sym.as_str()).to_owned());
+ }
+ }
+
+ None
+ })
+ .collect()
+}
+
+// def_path_res will match field names before anything else, but for this we want to match
+// inherent functions first.
+fn inherent_def_path_res(cx: &LateContext<'_>, segments: &[&str]) -> Option<DefId> {
+ def_path_res(cx, segments, None).opt_def_id().map(|def_id| {
+ if cx.tcx.def_kind(def_id) == DefKind::Field {
+ let method_name = *segments.last().unwrap();
+ cx.tcx
+ .def_key(def_id)
+ .parent
+ .and_then(|parent_idx| {
+ cx.tcx
+ .inherent_impls(DefId {
+ index: parent_idx,
+ krate: def_id.krate,
+ })
+ .iter()
+ .find_map(|impl_id| {
+ cx.tcx.associated_items(*impl_id).find_by_name_and_kind(
+ cx.tcx,
+ Ident::from_str(method_name),
+ AssocKind::Fn,
+ *impl_id,
+ )
+ })
+ })
+ .map_or(def_id, |item| item.def_id)
+ } else {
+ def_id
+ }
+ })
+}
+
+fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<Symbol> {
+ if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) {
+ let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id();
+ let item_name = cx
+ .tcx
+ .adt_def(lang_items)
+ .variants()
+ .iter()
+ .nth(lang_item)
+ .unwrap()
+ .name;
+ Some(item_name)
+ } else {
+ None
+ }
+}