summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
new file mode 100644
index 000000000..4cf76f536
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
@@ -0,0 +1,343 @@
+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::{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_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+use std::str;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
+ ///
+ /// ### Why is this bad?
+ /// The path for an item is subject to change and is less efficient to look up than a
+ /// diagnostic item or a `LangItem`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// utils::match_type(cx, ty, &paths::VEC)
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
+ /// ```
+ pub UNNECESSARY_DEF_PATH,
+ internal,
+ "using a def path when a diagnostic item or a `LangItem` is available"
+}
+
+impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+
+#[derive(Default)]
+pub struct UnnecessaryDefPath {
+ array_def_ids: FxHashSet<(DefId, Span)>,
+ linted_def_ids: FxHashSet<DefId>,
+}
+
+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),
+ }
+ static PATHS: &[&[&str]] = &[
+ &["clippy_utils", "match_def_path"],
+ &["clippy_utils", "match_trait_method"],
+ &["clippy_utils", "ty", "match_type"],
+ &["clippy_utils", "is_expr_path_def_path"],
+ ];
+
+ if_chain! {
+ 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);
+ let item_arg = if which_path == 4 { &args[1] } else { &args[0] };
+ // 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) = inherent_def_path_res(cx, &segments[..]);
+ then {
+ // Check if the target item is a diagnostic item or LangItem.
+ #[rustfmt::skip]
+ 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(item_name) = get_lang_item_name(cx, def_id) {
+ (
+ "use of a def path to a `LangItem`",
+ Item::LangItem(item_name),
+ )
+ } else {
+ return;
+ };
+
+ let has_ctor = match cx.tcx.def_kind(def_id) {
+ DefKind::Struct => {
+ let variant = cx.tcx.adt_def(def_id).non_enum_variant();
+ variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+ },
+ DefKind::Variant => {
+ let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id);
+ variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
+ },
+ _ => false,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app);
+ let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app);
+ let (sugg, with_note) = match (which_path, item) {
+ // match_def_path
+ (0, Item::DiagnosticItem(item)) => (
+ format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"),
+ has_ctor,
+ ),
+ (0, Item::LangItem(item)) => (
+ format!("{cx_snip}.tcx.lang_items().require(LangItem::{item}).ok() == Some({def_snip})"),
+ has_ctor,
+ ),
+ // match_trait_method
+ (1, Item::DiagnosticItem(item)) => {
+ (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false)
+ },
+ // match_type
+ (2, Item::DiagnosticItem(item)) => (
+ format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+ false,
+ ),
+ (2, Item::LangItem(item)) => (
+ format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"),
+ false,
+ ),
+ // is_expr_path_def_path
+ (3, Item::DiagnosticItem(item)) if has_ctor => (
+ format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",),
+ false,
+ ),
+ (3, Item::LangItem(item)) if has_ctor => (
+ format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",),
+ false,
+ ),
+ (3, Item::DiagnosticItem(item)) => (
+ format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
+ false,
+ ),
+ (3, Item::LangItem(item)) => (
+ format!(
+ "path_res({cx_snip}, {def_snip}).opt_def_id()\
+ .map_or(false, |id| {cx_snip}.tcx.lang_items().require(LangItem::{item}).ok() == Some(id))",
+ ),
+ false,
+ ),
+ _ => return,
+ };
+
+ span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| {
+ diag.span_suggestion(span, "try", sugg, app);
+ if with_note {
+ diag.help(
+ "if this `DefId` came from a constructor expression or pattern then the \
+ parent `DefId` should be used instead",
+ );
+ }
+ });
+
+ 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>> {
+ match peel_hir_expr_refs(expr).0.kind {
+ ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Local(hir_id) => {
+ let parent_id = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(Local { init: Some(init), .. })) = cx.tcx.hir().find(parent_id) {
+ path_to_matched_type(cx, init)
+ } else {
+ None
+ }
+ },
+ Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
+ cx,
+ cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
+ cx.tcx.type_of(def_id),
+ ),
+ Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
+ ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => {
+ read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id))
+ },
+ _ => None,
+ },
+ _ => None,
+ },
+ ExprKind::Array(exprs) => path_from_array(exprs),
+ _ => None,
+ }
+}
+
+fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> {
+ let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() {
+ let &alloc = alloc.provenance().values().next()?;
+ if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
+ (alloc.inner(), ty)
+ } else {
+ return None;
+ }
+ } else {
+ (alloc, ty)
+ };
+
+ if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
+ && let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
+ && ty.is_str()
+ {
+ alloc
+ .provenance()
+ .values()
+ .map(|&alloc| {
+ if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
+ let alloc = alloc.inner();
+ str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()))
+ .ok().map(ToOwned::to_owned)
+ } else {
+ None
+ }
+ })
+ .collect()
+ } else {
+ 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
+ }
+}