diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3b33f7190638..45ba2f078be7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -77,7 +77,7 @@ debugging to find the actual problem behind the issue.
 
 [`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
 lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
-an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+an AST expression).
 
 [`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
 [`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md
index 2e39f279eae4..e23b32039c90 100644
--- a/book/src/development/common_tools_writing_lints.md
+++ b/book/src/development/common_tools_writing_lints.md
@@ -86,7 +86,7 @@ arguments have to be checked separately.
 
 ```rust
 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
-use clippy_utils::{paths, match_def_path};
+use clippy_utils::paths;
 use rustc_span::symbol::sym;
 use rustc_hir::LangItem;
 
@@ -108,7 +108,7 @@ impl LateLintPass<'_> for MyStructLint {
 
         // 3. Using the type path
         // This method should be avoided if possible
-        if match_def_path(cx, def_id, &paths::RESULT) {
+        if paths::RESULT.matches_ty(cx, ty) {
             // The type is a `core::result::Result`
         }
     }
diff --git a/book/src/development/trait_checking.md b/book/src/development/trait_checking.md
index b7d229ccc193..cc4eb966f596 100644
--- a/book/src/development/trait_checking.md
+++ b/book/src/development/trait_checking.md
@@ -73,22 +73,24 @@ impl LateLintPass<'_> for CheckDropTraitLint {
 ## Using Type Path
 
 If neither diagnostic item nor a language item is available, we can use
-[`clippy_utils::paths`][paths] with the `match_trait_method` to determine trait
-implementation.
+[`clippy_utils::paths`][paths] to determine get a trait's `DefId`.
 
 > **Note**: This approach should be avoided if possible, the best thing to do would be to make a PR to [`rust-lang/rust`][rust] adding a diagnostic item.
 
-Below, we check if the given `expr` implements the `Iterator`'s trait method `cloned` :
+Below, we check if the given `expr` implements [`core::iter::Step`](https://doc.rust-lang.org/std/iter/trait.Step.html):
 
 ```rust
-use clippy_utils::{match_trait_method, paths};
+use clippy_utils::{implements_trait, paths};
 use rustc_hir::Expr;
 use rustc_lint::{LateContext, LateLintPass};
 
-impl LateLintPass<'_> for CheckTokioAsyncReadExtTrait {
+impl LateLintPass<'_> for CheckIterStep {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        if match_trait_method(cx, expr, &paths::CORE_ITER_CLONED) {
-            println!("`expr` implements `CORE_ITER_CLONED` trait!");
+        let ty = cx.typeck_results().expr_ty(expr);
+        if let Some(trait_def_id) = paths::ITER_STEP.first(cx)
+            && implements_trait(cx, ty, trait_def_id, &[])
+        {
+            println!("`expr` implements the `core::iter::Step` trait!");
         }
     }
 }
diff --git a/clippy.toml b/clippy.toml
index 0a7724bbe4e6..77573105d86a 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -7,14 +7,11 @@ lint-commented-code = true
 [[disallowed-methods]]
 path = "rustc_lint::context::LintContext::lint"
 reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
-allow-invalid = true
 
 [[disallowed-methods]]
 path = "rustc_lint::context::LintContext::span_lint"
 reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
-allow-invalid = true
 
 [[disallowed-methods]]
 path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
 reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"
-allow-invalid = true
diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs
index 70cf0915bb6f..2cb5493f1a98 100644
--- a/clippy_config/src/types.rs
+++ b/clippy_config/src/types.rs
@@ -1,7 +1,8 @@
+use clippy_utils::paths::{PathNS, find_crates, lookup_path};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Applicability, Diag};
 use rustc_hir::PrimTy;
-use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefIdMap;
 use rustc_middle::ty::TyCtxt;
 use rustc_span::{Span, Symbol};
@@ -133,6 +134,7 @@ impl DisallowedPathEnum {
 pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
     tcx: TyCtxt<'_>,
     disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
+    ns: PathNS,
     def_kind_predicate: impl Fn(DefKind) -> bool,
     predicate_description: &str,
     allow_prim_tys: bool,
@@ -145,62 +147,47 @@ pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
         FxHashMap::default();
     for disallowed_path in disallowed_paths {
         let path = disallowed_path.path();
-        let path_split = path.split("::").collect::<Vec<_>>();
-        let mut resolutions = clippy_utils::def_path_res(tcx, &path_split);
-
-        let mut found_def_id = None;
-        let mut found_prim_ty = false;
-        resolutions.retain(|res| match res {
-            Res::Def(def_kind, def_id) => {
-                found_def_id = Some(*def_id);
-                def_kind_predicate(*def_kind)
-            },
-            Res::PrimTy(_) => {
-                found_prim_ty = true;
-                allow_prim_tys
-            },
-            _ => false,
-        });
+        let sym_path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
+        let mut resolutions = lookup_path(tcx, ns, &sym_path);
+        resolutions.retain(|&def_id| def_kind_predicate(tcx.def_kind(def_id)));
+
+        let (prim_ty, found_prim_ty) = if let &[name] = sym_path.as_slice()
+            && let Some(prim) = PrimTy::from_name(name)
+        {
+            (allow_prim_tys.then_some(prim), true)
+        } else {
+            (None, false)
+        };
+
         if resolutions.is_empty()
+            && prim_ty.is_none()
+            && !disallowed_path.allow_invalid
             // Don't warn about unloaded crates:
             // https://github.com/rust-lang/rust-clippy/pull/14397#issuecomment-2848328221
-            && (path_split.len() < 2
-                || !clippy_utils::find_crates(tcx, Symbol::intern(path_split[0])).is_empty())
+            && (sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty())
         {
-            let span = disallowed_path.span();
-
-            if let Some(def_id) = found_def_id {
-                tcx.sess.dcx().span_warn(
-                    span,
-                    format!(
-                        "expected a {predicate_description}, found {} {}",
-                        tcx.def_descr_article(def_id),
-                        tcx.def_descr(def_id)
-                    ),
-                );
+            // Relookup the path in an arbitrary namespace to get a good `expected, found` message
+            let found_def_ids = lookup_path(tcx, PathNS::Arbitrary, &sym_path);
+            let message = if let Some(&def_id) = found_def_ids.first() {
+                let (article, description) = tcx.article_and_description(def_id);
+                format!("expected a {predicate_description}, found {article} {description}")
             } else if found_prim_ty {
-                tcx.sess.dcx().span_warn(
-                    span,
-                    format!("expected a {predicate_description}, found a primitive type",),
-                );
-            } else if !disallowed_path.allow_invalid {
-                tcx.sess.dcx().span_warn(
-                    span,
-                    format!("`{path}` does not refer to an existing {predicate_description}"),
-                );
-            }
+                format!("expected a {predicate_description}, found a primitive type")
+            } else {
+                format!("`{path}` does not refer to a reachable {predicate_description}")
+            };
+            tcx.sess
+                .dcx()
+                .struct_span_warn(disallowed_path.span(), message)
+                .with_help("add `allow-invalid = true` to the entry to suppress this warning")
+                .emit();
         }
 
-        for res in resolutions {
-            match res {
-                Res::Def(_, def_id) => {
-                    def_ids.insert(def_id, (path, disallowed_path));
-                },
-                Res::PrimTy(ty) => {
-                    prim_tys.insert(ty, (path, disallowed_path));
-                },
-                _ => unreachable!(),
-            }
+        for def_id in resolutions {
+            def_ids.insert(def_id, (path, disallowed_path));
+        }
+        if let Some(ty) = prim_ty {
+            prim_tys.insert(ty, (path, disallowed_path));
         }
     }
 
diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs
index 52d1d5b4c67a..31cc004f6855 100644
--- a/clippy_lints/src/await_holding_invalid.rs
+++ b/clippy_lints/src/await_holding_invalid.rs
@@ -1,7 +1,7 @@
 use clippy_config::Conf;
 use clippy_config::types::{DisallowedPathWithoutReplacement, create_disallowed_map};
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::{match_def_path, paths};
+use clippy_utils::paths::{self, PathNS};
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, DefIdMap};
 use rustc_lint::{LateContext, LateLintPass};
@@ -182,6 +182,7 @@ impl AwaitHolding {
         let (def_ids, _) = create_disallowed_map(
             tcx,
             &conf.await_holding_invalid_types,
+            PathNS::Type,
             crate::disallowed_types::def_kind_predicate,
             "type",
             false,
@@ -275,12 +276,10 @@ fn emit_invalid_type(
 }
 
 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
-    cx.tcx.is_diagnostic_item(sym::MutexGuard, def_id)
-        || cx.tcx.is_diagnostic_item(sym::RwLockReadGuard, def_id)
-        || cx.tcx.is_diagnostic_item(sym::RwLockWriteGuard, def_id)
-        || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
-        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
-        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
+    match cx.tcx.get_diagnostic_name(def_id) {
+        Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
+        None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
+    }
 }
 
 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs
index 6dbaa5cb5079..61dfc0fc0425 100644
--- a/clippy_lints/src/casts/manual_dangling_ptr.rs
+++ b/clippy_lints/src/casts/manual_dangling_ptr.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::SpanRangeExt;
-use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core};
+use clippy_utils::{expr_or_init, path_def_id, paths, std_or_core};
 use rustc_ast::LitKind;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
@@ -54,7 +54,7 @@ fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) ->
 fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool {
     if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind
         && let Some(fun_id) = path_def_id(cx, fun)
-        && match_def_path(cx, fun_id, &paths::ALIGN_OF)
+        && paths::ALIGN_OF.matches(cx, fun_id)
         && let Some(args) = path.segments.last().and_then(|seg| seg.args)
         && let [GenericArg::Type(generic_ty)] = args.args
     {
diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs
index 06528f875a29..3443b36eb4f3 100644
--- a/clippy_lints/src/derive.rs
+++ b/clippy_lints/src/derive.rs
@@ -2,7 +2,7 @@ use std::ops::ControlFlow;
 
 use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then};
 use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
-use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths};
+use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths};
 use rustc_errors::Applicability;
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
@@ -377,7 +377,7 @@ fn check_unsafe_derive_deserialize<'tcx>(
     }
 
     if let Some(trait_def_id) = trait_ref.trait_def_id()
-        && match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
+        && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
         && let ty::Adt(def, _) = ty.kind()
         && let Some(local_def_id) = def.did().as_local()
         && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)
diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs
index fa33fef23062..25b7099c855d 100644
--- a/clippy_lints/src/disallowed_macros.rs
+++ b/clippy_lints/src/disallowed_macros.rs
@@ -2,6 +2,7 @@ use clippy_config::Conf;
 use clippy_config::types::{DisallowedPath, create_disallowed_map};
 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
 use clippy_utils::macros::macro_backtrace;
+use clippy_utils::paths::PathNS;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::DefIdMap;
@@ -75,6 +76,7 @@ impl DisallowedMacros {
         let (disallowed, _) = create_disallowed_map(
             tcx,
             &conf.disallowed_macros,
+            PathNS::Macro,
             |def_kind| matches!(def_kind, DefKind::Macro(_)),
             "macro",
             false,
diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs
index 1382dafa931e..fb970e17f38f 100644
--- a/clippy_lints/src/disallowed_methods.rs
+++ b/clippy_lints/src/disallowed_methods.rs
@@ -1,6 +1,7 @@
 use clippy_config::Conf;
 use clippy_config::types::{DisallowedPath, create_disallowed_map};
 use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::paths::PathNS;
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::DefIdMap;
 use rustc_hir::{Expr, ExprKind};
@@ -66,6 +67,7 @@ impl DisallowedMethods {
         let (disallowed, _) = create_disallowed_map(
             tcx,
             &conf.disallowed_methods,
+            PathNS::Value,
             |def_kind| {
                 matches!(
                     def_kind,
diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs
index 2bae82648ac7..d0b2f0c8407f 100644
--- a/clippy_lints/src/disallowed_types.rs
+++ b/clippy_lints/src/disallowed_types.rs
@@ -1,6 +1,7 @@
 use clippy_config::Conf;
 use clippy_config::types::{DisallowedPath, create_disallowed_map};
 use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::paths::PathNS;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefIdMap;
@@ -60,7 +61,14 @@ pub struct DisallowedTypes {
 
 impl DisallowedTypes {
     pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
-        let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
+        let (def_ids, prim_tys) = create_disallowed_map(
+            tcx,
+            &conf.disallowed_types,
+            PathNS::Type,
+            def_kind_predicate,
+            "type",
+            true,
+        );
         Self { def_ids, prim_tys }
     }
 
diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs
index 5f3fc5100e75..d0d02a382d15 100644
--- a/clippy_lints/src/functions/mod.rs
+++ b/clippy_lints/src/functions/mod.rs
@@ -9,8 +9,8 @@ mod too_many_arguments;
 mod too_many_lines;
 
 use clippy_config::Conf;
-use clippy_utils::def_path_def_ids;
 use clippy_utils::msrvs::Msrv;
+use clippy_utils::paths::{PathNS, lookup_path_str};
 use rustc_hir as hir;
 use rustc_hir::intravisit;
 use rustc_lint::{LateContext, LateLintPass};
@@ -469,7 +469,7 @@ impl Functions {
             trait_ids: conf
                 .allow_renamed_params_for
                 .iter()
-                .flat_map(|p| def_path_def_ids(tcx, &p.split("::").collect::<Vec<_>>()))
+                .flat_map(|p| lookup_path_str(tcx, PathNS::Type, p))
                 .collect(),
             msrv: conf.msrv,
         }
diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs
index bdbf5b37c5f0..916191b2a7b0 100644
--- a/clippy_lints/src/let_underscore.rs
+++ b/clippy_lints/src/let_underscore.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
+use clippy_utils::ty::{implements_trait, is_must_use_ty};
 use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths};
 use rustc_hir::{LetStmt, LocalSource, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
@@ -129,12 +129,6 @@ declare_clippy_lint! {
 
 declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]);
 
-const SYNC_GUARD_PATHS: [&[&str]; 3] = [
-    &paths::PARKING_LOT_MUTEX_GUARD,
-    &paths::PARKING_LOT_RWLOCK_READ_GUARD,
-    &paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
-];
-
 impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) {
         if matches!(local.source, LocalSource::Normal)
@@ -144,7 +138,9 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
         {
             let init_ty = cx.typeck_results().expr_ty(init);
             let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
-                GenericArgKind::Type(inner_ty) => SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)),
+                GenericArgKind::Type(inner_ty) => inner_ty
+                    .ty_adt_def()
+                    .is_some_and(|adt| paths::PARKING_LOT_GUARDS.iter().any(|path| path.matches(cx, adt.did()))),
                 GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
             });
             if contains_sync_guard {
diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs
index b365dbf088f5..04e00f841035 100644
--- a/clippy_lints/src/manual_option_as_slice.rs
+++ b/clippy_lints/src/manual_option_as_slice.rs
@@ -1,7 +1,7 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
 use clippy_utils::msrvs::Msrv;
-use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym};
+use clippy_utils::{is_none_arm, msrvs, paths, peel_hir_expr_refs, sym};
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal};
@@ -220,5 +220,5 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
 }
 
 fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
-    clippy_utils::is_expr_path_def_path(cx, expr, &["core", "slice", "raw", "from_ref"])
+    paths::SLICE_FROM_REF.matches_path(cx, expr)
 }
diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints/src/methods/io_other_error.rs
index bdc834bd47a5..ec4b9c7ae2ee 100644
--- a/clippy_lints/src/methods/io_other_error.rs
+++ b/clippy_lints/src/methods/io_other_error.rs
@@ -1,5 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::{expr_or_init, paths};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, QPath};
 use rustc_lint::LateContext;
@@ -8,13 +9,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args
     if let [error_kind, error] = args
         && !expr.span.from_expansion()
         && !error_kind.span.from_expansion()
-        && clippy_utils::is_expr_path_def_path(cx, path, &clippy_utils::paths::IO_ERROR_NEW)
-        && clippy_utils::is_expr_path_def_path(
-            cx,
-            clippy_utils::expr_or_init(cx, error_kind),
-            &clippy_utils::paths::IO_ERRORKIND_OTHER,
-        )
         && let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind
+        && paths::IO_ERROR_NEW.matches_path(cx, path)
+        && paths::IO_ERRORKIND_OTHER_CTOR.matches_path(cx, expr_or_init(cx, error_kind))
         && msrv.meets(cx, msrvs::IO_ERROR_OTHER)
     {
         span_lint_and_then(
diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs
index 18978a1d2bc8..e2df8ce1513c 100644
--- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs
+++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs
@@ -1,9 +1,10 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
-use clippy_utils::{match_def_path, path_def_id};
+use clippy_utils::{path_res, sym};
 use rustc_ast::ast;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
+use rustc_hir::def::Res;
 use rustc_lint::LateContext;
 use rustc_middle::ty::layout::LayoutOf;
 
@@ -79,16 +80,15 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
     }
 
     let ty = cx.typeck_results().expr_ty(expr);
-    let ty_str = ty.to_string();
 
-    // `std::T::MAX` `std::T::MIN` constants
-    if let Some(id) = path_def_id(cx, expr) {
-        if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
-            return Some(MinMax::Max);
-        }
-
-        if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
-            return Some(MinMax::Min);
+    // `T::MAX` and `T::MIN` constants
+    if let hir::ExprKind::Path(hir::QPath::TypeRelative(base, seg)) = expr.kind
+        && let Res::PrimTy(_) = path_res(cx, base)
+    {
+        match seg.ident.name {
+            sym::MAX => return Some(MinMax::Max),
+            sym::MIN => return Some(MinMax::Min),
+            _ => {},
         }
     }
 
diff --git a/clippy_lints/src/methods/needless_character_iteration.rs b/clippy_lints/src/methods/needless_character_iteration.rs
index f528f7f065c6..71c1576cd57d 100644
--- a/clippy_lints/src/methods/needless_character_iteration.rs
+++ b/clippy_lints/src/methods/needless_character_iteration.rs
@@ -7,9 +7,8 @@ use rustc_span::Span;
 use super::NEEDLESS_CHARACTER_ITERATION;
 use super::utils::get_last_chain_binding_hir_id;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::paths::CHAR_IS_ASCII;
 use clippy_utils::source::SpanRangeExt;
-use clippy_utils::{match_def_path, path_to_local_id, peel_blocks, sym};
+use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym};
 
 fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
     while let ExprKind::AddrOf(_, _, e) = expr.kind {
@@ -76,9 +75,7 @@ fn handle_expr(
             // If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
             // `is_ascii`, then only `.all()` should warn.
             if revert != is_all
-                && let ExprKind::Path(path) = fn_path.kind
-                && let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
-                && match_def_path(cx, fn_def_id, &CHAR_IS_ASCII)
+                && is_path_diagnostic_item(cx, fn_path, sym::char_is_ascii)
                 && path_to_local_id(peels_expr_ref(arg), first_param)
                 && let Some(snippet) = before_chars.get_source_text(cx)
             {
diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs
index da084871402a..bce314e64f05 100644
--- a/clippy_lints/src/methods/open_options.rs
+++ b/clippy_lints/src/methods/open_options.rs
@@ -1,8 +1,8 @@
 use rustc_data_structures::fx::FxHashMap;
 
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
-use clippy_utils::ty::{is_type_diagnostic_item, match_type};
-use clippy_utils::{match_any_def_paths, paths};
+use clippy_utils::paths;
+use clippy_utils::ty::is_type_diagnostic_item;
 use rustc_ast::ast::LitKind;
 use rustc_hir::{Expr, ExprKind};
 use rustc_lint::LateContext;
@@ -13,7 +13,7 @@ use rustc_span::{Span, sym};
 use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS};
 
 fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
-    is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || match_type(cx, ty, &paths::TOKIO_IO_OPEN_OPTIONS)
+    is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty)
 }
 
 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
@@ -126,14 +126,14 @@ fn get_open_options(
         && let ExprKind::Path(path) = callee.kind
         && let Some(did) = cx.qpath_res(&path, callee.hir_id).opt_def_id()
     {
-        let std_file_options = [sym::file_options, sym::open_options_new];
-
-        let tokio_file_options: &[&[&str]] = &[&paths::TOKIO_IO_OPEN_OPTIONS_NEW, &paths::TOKIO_FILE_OPTIONS];
+        let is_std_options = matches!(
+            cx.tcx.get_diagnostic_name(did),
+            Some(sym::file_options | sym::open_options_new)
+        );
 
-        let is_std_options = std_file_options
-            .into_iter()
-            .any(|sym| cx.tcx.is_diagnostic_item(sym, did));
-        is_std_options || match_any_def_paths(cx, did, tokio_file_options).is_some()
+        is_std_options
+            || paths::TOKIO_IO_OPEN_OPTIONS_NEW.matches(cx, did)
+            || paths::TOKIO_FILE_OPTIONS.matches(cx, did)
     } else {
         false
     }
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index d183457da25a..c8efb600f576 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::source::snippet_with_context;
 use clippy_utils::usage::local_used_after_expr;
 use clippy_utils::visitors::{Descend, for_each_expr};
-use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
+use clippy_utils::{is_diag_item_method, path_to_local_id, paths};
 use core::ops::ControlFlow;
 use rustc_errors::Applicability;
 use rustc_hir::{
@@ -288,7 +288,7 @@ fn parse_iter_usage<'tcx>(
             match (name.ident.as_str(), args) {
                 ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
                 ("next_tuple", []) => {
-                    return if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE)
+                    return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did)
                         && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind()
                         && cx.tcx.is_diagnostic_item(sym::Option, adt_def.did())
                         && let ty::Tuple(subs) = subs.type_at(0).kind()
diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs
index 0cbf6004be3a..17e2620d9dd4 100644
--- a/clippy_lints/src/methods/useless_asref.rs
+++ b/clippy_lints/src/methods/useless_asref.rs
@@ -1,9 +1,7 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
 use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth};
-use clippy_utils::{
-    get_parent_expr, is_diag_trait_item, match_def_path, path_to_local_id, peel_blocks, strip_pat_refs,
-};
+use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs};
 use rustc_errors::Applicability;
 use rustc_hir::{self as hir, LangItem};
 use rustc_lint::LateContext;
@@ -81,8 +79,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
                 applicability,
             );
         }
-    } else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
-        || match_def_path(cx, def_id, &["core", "result", "Result", call_name])
+    } else if let Some(impl_id) = cx.tcx.opt_parent(def_id)
+        && let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def()
+        && (cx.tcx.lang_items().option_type() == Some(adt.did()) || cx.tcx.is_diagnostic_item(sym::Result, adt.did()))
     {
         let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
         let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs
index 66631a692063..a1e621cc9f6b 100644
--- a/clippy_lints/src/missing_enforced_import_rename.rs
+++ b/clippy_lints/src/missing_enforced_import_rename.rs
@@ -1,6 +1,6 @@
 use clippy_config::Conf;
-use clippy_utils::def_path_def_ids;
 use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::paths::{PathNS, lookup_path_str};
 use clippy_utils::source::SpanRangeExt;
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
@@ -56,8 +56,12 @@ impl ImportRename {
             renames: conf
                 .enforced_import_renames
                 .iter()
-                .map(|x| (x.path.split("::").collect::<Vec<_>>(), Symbol::intern(&x.rename)))
-                .flat_map(|(path, rename)| def_path_def_ids(tcx, &path).map(move |id| (id, rename)))
+                .map(|x| (&x.path, Symbol::intern(&x.rename)))
+                .flat_map(|(path, rename)| {
+                    lookup_path_str(tcx, PathNS::Arbitrary, path)
+                        .into_iter()
+                        .map(move |id| (id, rename))
+                })
                 .collect(),
         }
     }
diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs
index f6bc9428d65f..370ded99a4d0 100644
--- a/clippy_lints/src/non_std_lazy_statics.rs
+++ b/clippy_lints/src/non_std_lazy_statics.rs
@@ -1,8 +1,9 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
 use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::paths::{self, PathNS, find_crates, lookup_path_str};
 use clippy_utils::visitors::for_each_expr;
-use clippy_utils::{def_path_def_ids, fn_def_id, is_no_std_crate, path_def_id};
+use clippy_utils::{fn_def_id, is_no_std_crate, path_def_id, sym};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
@@ -62,10 +63,7 @@ static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[
 
 pub struct NonStdLazyStatic {
     msrv: Msrv,
-    lazy_static_lazy_static: Vec<DefId>,
-    once_cell_crate: Vec<CrateNum>,
-    once_cell_sync_lazy: Vec<DefId>,
-    once_cell_sync_lazy_new: Vec<DefId>,
+    once_cell_crates: Vec<CrateNum>,
     sugg_map: FxIndexMap<DefId, Option<String>>,
     lazy_type_defs: FxIndexMap<DefId, LazyInfo>,
     uses_other_once_cell_types: bool,
@@ -76,10 +74,7 @@ impl NonStdLazyStatic {
     pub fn new(conf: &'static Conf) -> Self {
         Self {
             msrv: conf.msrv,
-            lazy_static_lazy_static: Vec::new(),
-            once_cell_crate: Vec::new(),
-            once_cell_sync_lazy: Vec::new(),
-            once_cell_sync_lazy_new: Vec::new(),
+            once_cell_crates: Vec::new(),
             sugg_map: FxIndexMap::default(),
             lazy_type_defs: FxIndexMap::default(),
             uses_other_once_cell_types: false,
@@ -95,17 +90,15 @@ fn can_use_lazy_cell(cx: &LateContext<'_>, msrv: Msrv) -> bool {
 
 impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
     fn check_crate(&mut self, cx: &LateContext<'hir>) {
-        // Fetch def_ids for external paths
-        self.lazy_static_lazy_static = def_path_def_ids(cx.tcx, &["lazy_static", "lazy_static"]).collect();
-        self.once_cell_sync_lazy = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy"]).collect();
-        self.once_cell_sync_lazy_new = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy", "new"]).collect();
-        // And CrateNums for `once_cell` crate
-        self.once_cell_crate = self.once_cell_sync_lazy.iter().map(|d| d.krate).collect();
+        // Add CrateNums for `once_cell` crate
+        self.once_cell_crates = find_crates(cx.tcx, sym::once_cell)
+            .iter()
+            .map(|def_id| def_id.krate)
+            .collect();
 
         // Convert hardcoded fn replacement list into a map with def_id
         for (path, sugg) in FUNCTION_REPLACEMENTS {
-            let path_vec: Vec<&str> = path.split("::").collect();
-            for did in def_path_def_ids(cx.tcx, &path_vec) {
+            for did in lookup_path_str(cx.tcx, PathNS::Value, path) {
                 self.sugg_map.insert(did, sugg.map(ToOwned::to_owned));
             }
         }
@@ -114,7 +107,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
     fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) {
         if let ItemKind::Static(..) = item.kind
             && let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span)
-            && self.lazy_static_lazy_static.contains(&macro_call.def_id)
+            && paths::LAZY_STATIC.matches(cx, macro_call.def_id)
             && can_use_lazy_cell(cx, self.msrv)
         {
             span_lint(
@@ -130,7 +123,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
             return;
         }
 
-        if let Some(lazy_info) = LazyInfo::from_item(self, cx, item)
+        if let Some(lazy_info) = LazyInfo::from_item(cx, item)
             && can_use_lazy_cell(cx, self.msrv)
         {
             self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info);
@@ -155,9 +148,9 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
         if let rustc_hir::TyKind::Path(qpath) = ty.peel_refs().kind
             && let Some(ty_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id()
             // Is from `once_cell` crate
-            && self.once_cell_crate.contains(&ty_def_id.krate)
+            && self.once_cell_crates.contains(&ty_def_id.krate)
             // And is NOT `once_cell::sync::Lazy`
-            && !self.once_cell_sync_lazy.contains(&ty_def_id)
+            && !paths::ONCE_CELL_SYNC_LAZY.matches(cx, ty_def_id)
         {
             self.uses_other_once_cell_types = true;
         }
@@ -190,12 +183,12 @@ struct LazyInfo {
 }
 
 impl LazyInfo {
-    fn from_item(state: &NonStdLazyStatic, cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
+    fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
         // Check if item is a `once_cell:sync::Lazy` static.
         if let ItemKind::Static(_, ty, _, body_id) = item.kind
             && let Some(path_def_id) = path_def_id(cx, ty)
             && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
-            && state.once_cell_sync_lazy.contains(&path_def_id)
+            && paths::ONCE_CELL_SYNC_LAZY.matches(cx, path_def_id)
         {
             let ty_span_no_args = path_span_without_args(path);
             let body = cx.tcx.hir_body(body_id);
@@ -204,7 +197,7 @@ impl LazyInfo {
             let mut new_fn_calls = FxIndexMap::default();
             for_each_expr::<(), ()>(cx, body, |ex| {
                 if let Some((fn_did, call_span)) = fn_def_id_and_span_from_body(cx, ex, body_id)
-                    && state.once_cell_sync_lazy_new.contains(&fn_did)
+                    && paths::ONCE_CELL_SYNC_LAZY_NEW.matches(cx, fn_did)
                 {
                     new_fn_calls.insert(call_span, fn_did);
                 }
diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs
index 834ff2af0e88..89d945161f62 100644
--- a/clippy_lints/src/regex.rs
+++ b/clippy_lints/src/regex.rs
@@ -2,8 +2,9 @@ use std::fmt::Display;
 
 use clippy_utils::consts::{ConstEvalCtxt, Constant};
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::paths::PathLookup;
 use clippy_utils::source::SpanRangeExt;
-use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths, sym};
+use clippy_utils::{path_def_id, paths};
 use rustc_ast::ast::{LitKind, StrStyle};
 use rustc_hir::def_id::DefIdMap;
 use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId};
@@ -121,17 +122,9 @@ impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX, REGEX_CREATION_IN_LOOPS]
 
 impl<'tcx> LateLintPass<'tcx> for Regex {
     fn check_crate(&mut self, cx: &LateContext<'tcx>) {
-        // We don't use `match_def_path` here because that relies on matching the exact path, which changed
-        // between regex 1.8 and 1.9
-        //
-        // `def_path_res_with_base` will resolve through re-exports but is relatively heavy, so we only
-        // perform the operation once and store the results
-        let regex_crates = find_crates(cx.tcx, sym::regex);
-        let mut resolve = |path: &[&str], kind: RegexKind| {
-            for res in def_path_res_with_base(cx.tcx, regex_crates.clone(), &path[1..]) {
-                if let Some(id) = res.opt_def_id() {
-                    self.definitions.insert(id, kind);
-                }
+        let mut resolve = |path: &PathLookup, kind: RegexKind| {
+            for &id in path.get(cx) {
+                self.definitions.insert(id, kind);
             }
         };
 
diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs
index a8c6518b592b..a64b9b223786 100644
--- a/clippy_lints/src/serde_api.rs
+++ b/clippy_lints/src/serde_api.rs
@@ -1,5 +1,5 @@
 use clippy_utils::diagnostics::span_lint;
-use clippy_utils::{get_trait_def_id, paths};
+use clippy_utils::paths;
 use rustc_hir::{Impl, Item, ItemKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::declare_lint_pass;
@@ -32,9 +32,7 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi {
         }) = item.kind
         {
             let did = trait_ref.path.res.def_id();
-            if let Some(visit_did) = get_trait_def_id(cx.tcx, &paths::SERDE_DE_VISITOR)
-                && did == visit_did
-            {
+            if paths::SERDE_DE_VISITOR.matches(cx, did) {
                 let mut seen_str = None;
                 let mut seen_string = None;
                 for item in *items {
diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs
index 2d989b1cf0ba..54d09ff9ee40 100644
--- a/clippy_lints/src/single_range_in_vec_init.rs
+++ b/clippy_lints/src/single_range_in_vec_init.rs
@@ -3,7 +3,7 @@ use clippy_utils::higher::VecArgs;
 use clippy_utils::macros::root_macro_call_first_node;
 use clippy_utils::source::SpanRangeExt;
 use clippy_utils::ty::implements_trait;
-use clippy_utils::{get_trait_def_id, is_no_std_crate};
+use clippy_utils::{is_no_std_crate, paths};
 use rustc_ast::{LitIntType, LitKind, UintTy};
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr};
@@ -100,7 +100,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
             && let Some(start_snippet) = start.span.get_source_text(cx)
             && let Some(end_snippet) = end.span.get_source_text(cx)
         {
-            let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx.tcx, &["core", "iter", "Step"])
+            let should_emit_every_value = if let Some(step_def_id) = paths::ITER_STEP.only(cx)
                 && implements_trait(cx, ty, step_def_id, &[])
             {
                 true
diff --git a/clippy_lints/src/to_digit_is_some.rs b/clippy_lints/src/to_digit_is_some.rs
index bb969bc802fe..c8a6a41d6d8d 100644
--- a/clippy_lints/src/to_digit_is_some.rs
+++ b/clippy_lints/src/to_digit_is_some.rs
@@ -1,10 +1,9 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet_with_applicability;
-use clippy_utils::{match_def_path, sym};
+use clippy_utils::{paths, sym};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
 use rustc_session::declare_lint_pass;
 
 declare_clippy_lint! {
@@ -40,27 +39,18 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
         if let hir::ExprKind::MethodCall(is_some_path, to_digit_expr, [], _) = &expr.kind
             && is_some_path.ident.name == sym::is_some
         {
-            let match_result = match &to_digit_expr.kind {
+            let match_result = match to_digit_expr.kind {
                 hir::ExprKind::MethodCall(to_digits_path, char_arg, [radix_arg], _) => {
                     if to_digits_path.ident.name == sym::to_digit
-                        && let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg)
-                        && *char_arg_ty.kind() == ty::Char
+                        && cx.typeck_results().expr_ty_adjusted(char_arg).is_char()
                     {
-                        Some((true, *char_arg, radix_arg))
+                        Some((true, char_arg, radix_arg))
                     } else {
                         None
                     }
                 },
                 hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => {
-                    if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind
-                        && let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id)
-                        && let Some(to_digits_def_id) = to_digits_call_res.opt_def_id()
-                        && match_def_path(
-                            cx,
-                            to_digits_def_id,
-                            &["core", "char", "methods", "<impl char>", "to_digit"],
-                        )
-                    {
+                    if paths::CHAR_TO_DIGIT.matches_path(cx, to_digits_call) {
                         Some((false, char_arg, radix_arg))
                     } else {
                         None
diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs
index 2d88c490b1ab..e3f28908ff83 100644
--- a/clippy_lints/src/unused_io_amount.rs
+++ b/clippy_lints/src/unused_io_amount.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::macros::{is_panic, root_macro_call_first_node};
-use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks};
+use clippy_utils::{is_res_lang_ctor, paths, peel_blocks};
 use hir::{ExprKind, HirId, PatKind};
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
@@ -93,14 +93,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
                 return;
             }
 
-            let async_paths: [&[&str]; 4] = [
+            let async_paths = [
                 &paths::TOKIO_IO_ASYNCREADEXT,
                 &paths::TOKIO_IO_ASYNCWRITEEXT,
                 &paths::FUTURES_IO_ASYNCREADEXT,
                 &paths::FUTURES_IO_ASYNCWRITEEXT,
             ];
 
-            if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) {
+            if async_paths.into_iter().any(|path| path.matches(cx, trait_id)) {
                 return;
             }
         }
@@ -291,19 +291,28 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
         },
     };
 
-    match (
-        is_trait_method(cx, call, sym::IoRead),
-        is_trait_method(cx, call, sym::IoWrite),
-        match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
-            || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
-        match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
-            || match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
-    ) {
-        (true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
-        (_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
-        (_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
-        (_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
-        _ => None,
+    if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(call.hir_id)
+        && let Some(trait_def_id) = cx.tcx.trait_of_item(method_def_id)
+    {
+        if let Some(diag_name) = cx.tcx.get_diagnostic_name(trait_def_id) {
+            match diag_name {
+                sym::IoRead => Some(IoOp::SyncRead(vectorized)),
+                sym::IoWrite => Some(IoOp::SyncWrite(vectorized)),
+                _ => None,
+            }
+        } else if paths::FUTURES_IO_ASYNCREADEXT.matches(cx, trait_def_id)
+            || paths::TOKIO_IO_ASYNCREADEXT.matches(cx, trait_def_id)
+        {
+            Some(IoOp::AsyncRead(vectorized))
+        } else if paths::TOKIO_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
+            || paths::FUTURES_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
+        {
+            Some(IoOp::AsyncWrite(vectorized))
+        } else {
+            None
+        }
+    } else {
+        None
     }
 }
 
diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs
index b7dcd2ffb0ee..812c4df4ddde 100644
--- a/clippy_lints/src/utils/author.rs
+++ b/clippy_lints/src/utils/author.rs
@@ -1,16 +1,18 @@
-use clippy_utils::{get_attr, higher};
+use clippy_utils::{MaybePath, get_attr, higher, path_def_id};
+use itertools::Itertools;
 use rustc_ast::LitIntType;
 use rustc_ast::ast::{LitFloatType, LitKind};
 use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def_id::DefId;
 use rustc_hir::{
     self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind, ExprKind,
-    FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr, TyKind,
+    FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr,
 };
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_session::declare_lint_pass;
 use rustc_span::symbol::{Ident, Symbol};
 use std::cell::Cell;
-use std::fmt::{Display, Formatter, Write as _};
+use std::fmt::{Display, Formatter};
 
 declare_lint_pass!(
     /// ### What it does
@@ -148,6 +150,15 @@ fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_,
     }
 }
 
+fn paths_static_name(cx: &LateContext<'_>, id: DefId) -> String {
+    cx.get_def_path(id)
+        .iter()
+        .map(Symbol::as_str)
+        .filter(|s| !s.starts_with('<'))
+        .join("_")
+        .to_uppercase()
+}
+
 struct Binding<T> {
     name: String,
     value: T,
@@ -257,11 +268,44 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
         chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
     }
 
-    fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
+    fn qpath<'p>(&self, qpath: &Binding<&QPath<'_>>, has_hir_id: &Binding<&impl MaybePath<'p>>) {
         if let QPath::LangItem(lang_item, ..) = *qpath.value {
             chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
-        } else if let Ok(path) = path_to_string(qpath.value) {
-            chain!(self, "match_qpath({qpath}, &[{}])", path);
+        } else if let Some(def_id) = self.cx.qpath_res(qpath.value, has_hir_id.value.hir_id()).opt_def_id()
+            && !def_id.is_local()
+        {
+            bind!(self, def_id);
+            chain!(
+                self,
+                "let Some({def_id}) = cx.qpath_res({qpath}, {has_hir_id}.hir_id).opt_def_id()"
+            );
+            if let Some(name) = self.cx.tcx.get_diagnostic_name(def_id.value) {
+                chain!(self, "cx.tcx.is_diagnostic_item(sym::{name}, {def_id})");
+            } else {
+                chain!(
+                    self,
+                    "paths::{}.matches(cx, {def_id}) // Add the path to `clippy_utils::paths` if needed",
+                    paths_static_name(self.cx, def_id.value)
+                );
+            }
+        }
+    }
+
+    fn maybe_path<'p>(&self, path: &Binding<&impl MaybePath<'p>>) {
+        if let Some(id) = path_def_id(self.cx, path.value)
+            && !id.is_local()
+        {
+            if let Some(lang) = self.cx.tcx.lang_items().from_def_id(id) {
+                chain!(self, "is_path_lang_item(cx, {path}, LangItem::{}", lang.name());
+            } else if let Some(name) = self.cx.tcx.get_diagnostic_name(id) {
+                chain!(self, "is_path_diagnostic_item(cx, {path}, sym::{name})");
+            } else {
+                chain!(
+                    self,
+                    "paths::{}.matches_path(cx, {path}) // Add the path to `clippy_utils::paths` if needed",
+                    paths_static_name(self.cx, id)
+                );
+            }
         }
     }
 
@@ -270,7 +314,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             ConstArgKind::Path(ref qpath) => {
                 bind!(self, qpath);
                 chain!(self, "let ConstArgKind::Path(ref {qpath}) = {const_arg}.kind");
-                self.qpath(qpath);
             },
             ConstArgKind::Anon(anon_const) => {
                 bind!(self, anon_const);
@@ -394,12 +437,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
                 bind!(self, let_expr);
                 kind!("Let({let_expr})");
                 self.pat(field!(let_expr.pat));
-                // Does what ExprKind::Cast does, only adds a clause for the type
-                // if it's a path
-                if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
-                    bind!(self, qpath);
-                    chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
-                    self.qpath(qpath);
+                if let Some(ty) = let_expr.value.ty {
+                    bind!(self, ty);
+                    chain!(self, "let Some({ty}) = {let_expr}.ty");
+                    self.maybe_path(ty);
                 }
                 self.expr(field!(let_expr.init));
             },
@@ -451,11 +492,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             ExprKind::Cast(expr, cast_ty) => {
                 bind!(self, expr, cast_ty);
                 kind!("Cast({expr}, {cast_ty})");
-                if let TyKind::Path(ref qpath) = cast_ty.value.kind {
-                    bind!(self, qpath);
-                    chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
-                    self.qpath(qpath);
-                }
+                self.maybe_path(cast_ty);
                 self.expr(expr);
             },
             ExprKind::Type(expr, _ty) => {
@@ -561,10 +598,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
                 self.expr(object);
                 self.expr(index);
             },
-            ExprKind::Path(ref qpath) => {
-                bind!(self, qpath);
-                kind!("Path(ref {qpath})");
-                self.qpath(qpath);
+            ExprKind::Path(_) => {
+                self.maybe_path(expr);
             },
             ExprKind::AddrOf(kind, mutability, inner) => {
                 bind!(self, inner);
@@ -608,7 +643,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
                     StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
                 });
                 kind!("Struct({qpath}, {fields}, {base})");
-                self.qpath(qpath);
+                self.qpath(qpath, expr);
                 self.slice(fields, |field| {
                     self.ident(field!(field.ident));
                     self.expr(field!(field.expr));
@@ -648,7 +683,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
         self.expr(expr);
     }
 
-    fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>) {
+    fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>, pat: &Binding<&hir::Pat<'_>>) {
         let kind = |kind| chain!(self, "let PatExprKind::{kind} = {lit}.kind");
         macro_rules! kind {
             ($($t:tt)*) => (kind(format_args!($($t)*)));
@@ -657,15 +692,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             PatExprKind::Lit { lit, negated } => {
                 bind!(self, lit);
                 bind!(self, negated);
-                kind!("Lit{{ref {lit}, {negated} }}");
+                kind!("Lit {{ ref {lit}, {negated} }}");
                 self.lit(lit);
             },
             PatExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
-            PatExprKind::Path(ref qpath) => {
-                bind!(self, qpath);
-                kind!("Path(ref {qpath})");
-                self.qpath(qpath);
-            },
+            PatExprKind::Path(_) => self.maybe_path(pat),
         }
     }
 
@@ -697,7 +728,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             PatKind::Struct(ref qpath, fields, ignore) => {
                 bind!(self, qpath, fields);
                 kind!("Struct(ref {qpath}, {fields}, {ignore})");
-                self.qpath(qpath);
+                self.qpath(qpath, pat);
                 self.slice(fields, |field| {
                     self.ident(field!(field.ident));
                     self.pat(field!(field.pat));
@@ -711,7 +742,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
                 bind!(self, qpath, fields);
                 kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
-                self.qpath(qpath);
+                self.qpath(qpath, pat);
                 self.slice(fields, |pat| self.pat(pat));
             },
             PatKind::Tuple(fields, skip_pos) => {
@@ -743,13 +774,13 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
             PatKind::Expr(lit_expr) => {
                 bind!(self, lit_expr);
                 kind!("Expr({lit_expr})");
-                self.pat_expr(lit_expr);
+                self.pat_expr(lit_expr, pat);
             },
             PatKind::Range(start, end, end_kind) => {
                 opt_bind!(self, start, end);
                 kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
-                start.if_some(|e| self.pat_expr(e));
-                end.if_some(|e| self.pat_expr(e));
+                start.if_some(|e| self.pat_expr(e, pat));
+                end.if_some(|e| self.pat_expr(e, pat));
             },
             PatKind::Slice(start, middle, end) => {
                 bind!(self, start, end);
@@ -797,32 +828,3 @@ fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
     let attrs = cx.tcx.hir_attrs(hir_id);
     get_attr(cx.sess(), attrs, "author").count() > 0
 }
-
-fn path_to_string(path: &QPath<'_>) -> Result<String, ()> {
-    fn inner(s: &mut String, path: &QPath<'_>) -> Result<(), ()> {
-        match *path {
-            QPath::Resolved(_, path) => {
-                for (i, segment) in path.segments.iter().enumerate() {
-                    if i > 0 {
-                        *s += ", ";
-                    }
-                    write!(s, "{:?}", segment.ident.as_str()).unwrap();
-                }
-            },
-            QPath::TypeRelative(ty, segment) => match &ty.kind {
-                TyKind::Path(inner_path) => {
-                    inner(s, inner_path)?;
-                    *s += ", ";
-                    write!(s, "{:?}", segment.ident.as_str()).unwrap();
-                },
-                other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
-            },
-            QPath::LangItem(..) => return Err(()),
-        }
-
-        Ok(())
-    }
-    let mut s = String::new();
-    inner(&mut s, path)?;
-    Ok(s)
-}
diff --git a/clippy_lints_internal/src/collapsible_calls.rs b/clippy_lints_internal/src/collapsible_calls.rs
index d7967a0cc022..407deb45db0a 100644
--- a/clippy_lints_internal/src/collapsible_calls.rs
+++ b/clippy_lints_internal/src/collapsible_calls.rs
@@ -1,6 +1,6 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet;
-use clippy_utils::{SpanlessEq, is_expr_path_def_path, is_lint_allowed, peel_blocks_with_stmt};
+use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt};
 use rustc_errors::Applicability;
 use rustc_hir::{Closure, Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass};
@@ -10,6 +10,8 @@ use rustc_span::Span;
 
 use std::borrow::{Borrow, Cow};
 
+use crate::internal_paths;
+
 declare_tool_lint! {
     /// ### What it does
     /// Lints `span_lint_and_then` function calls, where the
@@ -80,7 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
         }
 
         if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind
-            && is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"])
+            && internal_paths::SPAN_LINT_AND_THEN.matches_path(cx, func)
             && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind
             && let body = cx.tcx.hir_body(body)
             && let only_expr = peel_blocks_with_stmt(body.value)
diff --git a/clippy_lints_internal/src/internal_paths.rs b/clippy_lints_internal/src/internal_paths.rs
new file mode 100644
index 000000000000..dc1e30ab2bdd
--- /dev/null
+++ b/clippy_lints_internal/src/internal_paths.rs
@@ -0,0 +1,17 @@
+use clippy_utils::paths::{PathLookup, PathNS};
+use clippy_utils::{sym, type_path, value_path};
+
+// Paths inside rustc
+pub static EARLY_LINT_PASS: PathLookup = type_path!(rustc_lint::passes::EarlyLintPass);
+pub static KW_MODULE: PathLookup = type_path!(rustc_span::symbol::kw);
+pub static LINT: PathLookup = type_path!(rustc_lint_defs::Lint);
+pub static SYMBOL: PathLookup = type_path!(rustc_span::symbol::Symbol);
+pub static SYMBOL_AS_STR: PathLookup = value_path!(rustc_span::symbol::Symbol::as_str);
+pub static SYM_MODULE: PathLookup = type_path!(rustc_span::symbol::sym);
+pub static SYNTAX_CONTEXT: PathLookup = type_path!(rustc_span::hygiene::SyntaxContext);
+
+// Paths in clippy itself
+pub static CLIPPY_SYM_MODULE: PathLookup = type_path!(clippy_utils::sym);
+pub static MSRV_STACK: PathLookup = type_path!(clippy_utils::msrvs::MsrvStack);
+pub static PATH_LOOKUP_NEW: PathLookup = value_path!(clippy_utils::paths::PathLookup::new);
+pub static SPAN_LINT_AND_THEN: PathLookup = value_path!(clippy_utils::diagnostics::span_lint_and_then);
diff --git a/clippy_lints_internal/src/invalid_paths.rs b/clippy_lints_internal/src/invalid_paths.rs
deleted file mode 100644
index bee87efa3fcd..000000000000
--- a/clippy_lints_internal/src/invalid_paths.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use clippy_utils::consts::{ConstEvalCtxt, Constant};
-use clippy_utils::def_path_res;
-use clippy_utils::diagnostics::span_lint;
-use rustc_hir as hir;
-use rustc_hir::Item;
-use rustc_hir::def::DefKind;
-use rustc_lint::{LateContext, LateLintPass};
-use rustc_lint_defs::declare_tool_lint;
-use rustc_middle::ty::fast_reject::SimplifiedType;
-use rustc_middle::ty::{self, FloatTy};
-use rustc_session::declare_lint_pass;
-use rustc_span::symbol::Symbol;
-
-declare_tool_lint! {
-    /// ### What it does
-    /// Checks the paths module for invalid paths.
-    ///
-    /// ### Why is this bad?
-    /// It indicates a bug in the code.
-    ///
-    /// ### Example
-    /// None.
-    pub clippy::INVALID_PATHS,
-    Warn,
-    "invalid path",
-    report_in_external_macro: true
-}
-
-declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
-
-impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
-    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
-        let local_def_id = &cx.tcx.parent_module(item.hir_id());
-        let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
-        if mod_name.as_str() == "paths"
-            && let hir::ItemKind::Const(.., body_id) = item.kind
-            && let Some(Constant::Vec(path)) = ConstEvalCtxt::with_env(
-                cx.tcx,
-                ty::TypingEnv::post_analysis(cx.tcx, item.owner_id),
-                cx.tcx.typeck(item.owner_id),
-            )
-            .eval_simple(cx.tcx.hir_body(body_id).value)
-            && let Some(path) = path
-                .iter()
-                .map(|x| {
-                    if let Constant::Str(s) = x {
-                        Some(s.as_str())
-                    } else {
-                        None
-                    }
-                })
-                .collect::<Option<Vec<&str>>>()
-            && !check_path(cx, &path[..])
-        {
-            span_lint(cx, INVALID_PATHS, item.span, "invalid path");
-        }
-    }
-}
-
-// This is not a complete resolver for paths. It works on all the paths currently used in the paths
-// module.  That's all it does and all it needs to do.
-pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
-    if !def_path_res(cx.tcx, path).is_empty() {
-        return true;
-    }
-
-    // Some implementations can't be found by `path_to_res`, particularly inherent
-    // implementations of native types. Check lang items.
-    let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
-    let lang_items = cx.tcx.lang_items();
-    // This list isn't complete, but good enough for our current list of paths.
-    let incoherent_impls = [
-        SimplifiedType::Float(FloatTy::F32),
-        SimplifiedType::Float(FloatTy::F64),
-        SimplifiedType::Slice,
-        SimplifiedType::Str,
-        SimplifiedType::Bool,
-        SimplifiedType::Char,
-    ]
-    .iter()
-    .flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter())
-    .copied();
-    for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) {
-        let lang_item_path = cx.get_def_path(item_def_id);
-        if path_syms.starts_with(&lang_item_path)
-            && let [item] = &path_syms[lang_item_path.len()..]
-        {
-            if matches!(
-                cx.tcx.def_kind(item_def_id),
-                DefKind::Mod | DefKind::Enum | DefKind::Trait
-            ) {
-                for child in cx.tcx.module_children(item_def_id) {
-                    if child.ident.name == *item {
-                        return true;
-                    }
-                }
-            } else {
-                for child in cx.tcx.associated_item_def_ids(item_def_id) {
-                    if cx.tcx.item_name(*child) == *item {
-                        return true;
-                    }
-                }
-            }
-        }
-    }
-
-    false
-}
diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs
index b02d378619ca..da37fd3d8271 100644
--- a/clippy_lints_internal/src/lib.rs
+++ b/clippy_lints_internal/src/lib.rs
@@ -32,7 +32,7 @@ extern crate rustc_span;
 
 mod almost_standard_lint_formulation;
 mod collapsible_calls;
-mod invalid_paths;
+mod internal_paths;
 mod lint_without_lint_pass;
 mod msrv_attr_impl;
 mod outer_expn_data_pass;
@@ -46,7 +46,6 @@ use rustc_lint::{Lint, LintStore};
 static LINTS: &[&Lint] = &[
     almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION,
     collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
-    invalid_paths::INVALID_PATHS,
     lint_without_lint_pass::DEFAULT_LINT,
     lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
     lint_without_lint_pass::LINT_WITHOUT_LINT_PASS,
@@ -66,10 +65,9 @@ pub fn register_lints(store: &mut LintStore) {
     store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths));
     store.register_early_pass(|| Box::new(produce_ice::ProduceIce));
     store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
-    store.register_late_pass(|_| Box::new(invalid_paths::InvalidPaths));
     store.register_late_pass(|_| Box::<symbols::Symbols>::default());
     store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());
-    store.register_late_pass(|_| Box::<unnecessary_def_path::UnnecessaryDefPath>::default());
+    store.register_late_pass(|_| Box::new(unnecessary_def_path::UnnecessaryDefPath));
     store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass));
     store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl));
     store.register_late_pass(|_| Box::new(almost_standard_lint_formulation::AlmostStandardFormulation::new()));
diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs
index 6a75defcce34..655d8fb8d1bf 100644
--- a/clippy_lints_internal/src/lint_without_lint_pass.rs
+++ b/clippy_lints_internal/src/lint_without_lint_pass.rs
@@ -1,6 +1,7 @@
+use crate::internal_paths;
 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::is_lint_allowed;
 use clippy_utils::macros::root_macro_call_first_node;
-use clippy_utils::{is_lint_allowed, match_def_path, paths};
 use rustc_ast::ast::LitKind;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_hir as hir;
@@ -209,10 +210,10 @@ pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
         && let TyKind::Path(ref path) = inner.kind
         && let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id)
     {
-        return match_def_path(cx, def_id, &paths::LINT);
+        internal_paths::LINT.matches(cx, def_id)
+    } else {
+        false
     }
-
-    false
 }
 
 fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs
index dda054546e26..d48d8dc57b24 100644
--- a/clippy_lints_internal/src/msrv_attr_impl.rs
+++ b/clippy_lints_internal/src/msrv_attr_impl.rs
@@ -1,7 +1,6 @@
+use crate::internal_paths;
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet;
-use clippy_utils::ty::match_type;
-use clippy_utils::{match_def_path, paths};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -31,7 +30,7 @@ impl LateLintPass<'_> for MsrvAttrImpl {
                 .tcx
                 .impl_trait_ref(item.owner_id)
                 .map(EarlyBinder::instantiate_identity)
-            && match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS)
+            && internal_paths::EARLY_LINT_PASS.matches(cx, trait_ref.def_id)
             && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind()
             && self_ty_def.is_struct()
             && self_ty_def.all_fields().any(|f| {
@@ -40,7 +39,7 @@ impl LateLintPass<'_> for MsrvAttrImpl {
                     .instantiate_identity()
                     .walk()
                     .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
-                    .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV_STACK))
+                    .any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty()))
             })
             && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes")
         {
diff --git a/clippy_lints_internal/src/outer_expn_data_pass.rs b/clippy_lints_internal/src/outer_expn_data_pass.rs
index e94419647978..40951443a48a 100644
--- a/clippy_lints_internal/src/outer_expn_data_pass.rs
+++ b/clippy_lints_internal/src/outer_expn_data_pass.rs
@@ -1,6 +1,6 @@
+use crate::internal_paths;
 use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::ty::match_type;
-use clippy_utils::{is_lint_allowed, method_calls, paths};
+use clippy_utils::{is_lint_allowed, method_calls};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
@@ -45,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
             && let (self_arg, args) = arg_lists[1]
             && args.is_empty()
             && let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
-            && match_type(cx, self_ty, &paths::SYNTAX_CONTEXT)
+            && internal_paths::SYNTAX_CONTEXT.matches_ty(cx, self_ty)
         {
             span_lint_and_sugg(
                 cx,
diff --git a/clippy_lints_internal/src/symbols.rs b/clippy_lints_internal/src/symbols.rs
index c64e5821916b..bf166988a0c7 100644
--- a/clippy_lints_internal/src/symbols.rs
+++ b/clippy_lints_internal/src/symbols.rs
@@ -1,6 +1,5 @@
+use crate::internal_paths;
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::ty::match_type;
-use clippy_utils::{def_path_def_ids, match_def_path, paths};
 use rustc_ast::LitKind;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
@@ -69,12 +68,12 @@ impl_lint_pass!(Symbols => [INTERNING_LITERALS, SYMBOL_AS_STR]);
 impl<'tcx> LateLintPass<'tcx> for Symbols {
     fn check_crate(&mut self, cx: &LateContext<'_>) {
         let modules = [
-            ("kw", &paths::KW_MODULE[..]),
-            ("sym", &paths::SYM_MODULE),
-            ("sym", &paths::CLIPPY_SYM_MODULE),
+            ("kw", &internal_paths::KW_MODULE),
+            ("sym", &internal_paths::SYM_MODULE),
+            ("sym", &internal_paths::CLIPPY_SYM_MODULE),
         ];
         for (prefix, module) in modules {
-            for def_id in def_path_def_ids(cx.tcx, module) {
+            for def_id in module.get(cx) {
                 // When linting `clippy_utils` itself we can't use `module_children` as it's a local def id. It will
                 // still lint but the suggestion will say to add it to `sym.rs` even if it's already there
                 if def_id.is_local() {
@@ -84,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for Symbols {
                 for item in cx.tcx.module_children(def_id) {
                     if let Res::Def(DefKind::Const, item_def_id) = item.res
                         && let ty = cx.tcx.type_of(item_def_id).instantiate_identity()
-                        && match_type(cx, ty, &paths::SYMBOL)
+                        && internal_paths::SYMBOL.matches_ty(cx, ty)
                         && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id)
                         && let Some(value) = value.to_u32().discard_err()
                     {
@@ -160,7 +159,7 @@ fn suggestion(symbols: &mut FxHashMap<u32, (&'static str, Symbol)>, name: Symbol
 fn as_str_span(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> {
     if let ExprKind::MethodCall(_, recv, [], _) = expr.kind
         && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
-        && match_def_path(cx, method_def_id, &paths::SYMBOL_AS_STR)
+        && internal_paths::SYMBOL_AS_STR.matches(cx, method_def_id)
     {
         Some(recv.span.shrink_to_hi().to(expr.span.shrink_to_hi()))
     } else {
diff --git a/clippy_lints_internal/src/unnecessary_def_path.rs b/clippy_lints_internal/src/unnecessary_def_path.rs
index 6bdfbed55b06..8877f1faf0ee 100644
--- a/clippy_lints_internal/src/unnecessary_def_path.rs
+++ b/clippy_lints_internal/src/unnecessary_def_path.rs
@@ -1,23 +1,14 @@
-use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
-use clippy_utils::source::snippet_with_applicability;
-use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
-use rustc_ast::ast::LitKind;
-use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
-use rustc_errors::Applicability;
-use rustc_hir::def::{DefKind, Res};
+use crate::internal_paths;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::paths::{PathNS, lookup_path};
+use clippy_utils::{path_def_id, peel_ref_operators};
 use rustc_hir::def_id::DefId;
-use rustc_hir::{Expr, ExprKind, LetStmt, Mutability, Node};
+use rustc_hir::{Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_lint_defs::declare_tool_lint;
+use rustc_lint_defs::{declare_lint_pass, declare_tool_lint};
 use rustc_middle::mir::ConstValue;
-use rustc_middle::mir::interpret::{Allocation, GlobalAlloc};
-use rustc_middle::ty::{self, Ty};
-use rustc_session::impl_lint_pass;
-use rustc_span::Span;
 use rustc_span::symbol::Symbol;
 
-use std::str;
-
 declare_tool_lint! {
     /// ### What it does
     /// Checks for usage of def paths when a diagnostic item or a `LangItem` could be used.
@@ -28,12 +19,14 @@ declare_tool_lint! {
     ///
     /// ### Example
     /// ```rust,ignore
-    /// utils::match_type(cx, ty, &paths::VEC)
+    /// pub static VEC: PathLookup = path!(alloc::vec::Vec);
+    ///
+    /// VEC.contains_ty(cx, ty)
     /// ```
     ///
     /// Use instead:
     /// ```rust,ignore
-    /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
+    /// is_type_diagnostic_item(cx, ty, sym::Vec)
     /// ```
     pub clippy::UNNECESSARY_DEF_PATH,
     Warn,
@@ -41,259 +34,67 @@ declare_tool_lint! {
     report_in_external_macro: true
 }
 
-impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
-
-#[derive(Default)]
-pub struct UnnecessaryDefPath {
-    array_def_ids: FxIndexSet<(DefId, Span)>,
-    linted_def_ids: FxHashSet<DefId>,
-}
+declare_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
 
 impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx 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(&'static str),
-            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 let [cx_arg, def_arg, args @ ..] = args
-            && let ExprKind::Path(path) = &func.kind
-            && let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id()
-            && 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
-            && let Some(segments) = path_to_matched_type(cx, item_arg)
-            && let segments = segments.iter().map(|sym| &**sym).collect::<Vec<_>>()
-            && let Some(def_id) = def_path_def_ids(cx.tcx, &segments[..]).next()
+        if let ExprKind::Call(ctor, [_, path]) = expr.kind
+            && internal_paths::PATH_LOOKUP_NEW.matches_path(cx, ctor)
+            && let ExprKind::Array(segments) = peel_ref_operators(cx, path).kind
+            && let Some(macro_id) = expr.span.ctxt().outer_expn_data().macro_def_id
         {
-            // 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.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.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
-                },
-                _ => false,
+            let ns = match cx.tcx.item_name(macro_id).as_str() {
+                "type_path" => PathNS::Type,
+                "value_path" => PathNS::Value,
+                "macro_path" => PathNS::Macro,
+                _ => unreachable!(),
             };
 
-            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().get(LangItem::{item}) == 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().get(LangItem::{item}) == 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",
+            let path: Vec<Symbol> = segments
+                .iter()
+                .map(|segment| {
+                    if let Some(const_def_id) = path_def_id(cx, segment)
+                        && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(const_def_id)
+                        && let Some(value) = value.to_u32().discard_err()
+                    {
+                        Symbol::new(value)
+                    } else {
+                        panic!("failed to resolve path {:?}", expr.span);
+                    }
+                })
+                .collect();
+
+            for def_id in lookup_path(cx.tcx, ns, &path) {
+                if let Some(name) = cx.tcx.get_diagnostic_name(def_id) {
+                    span_lint_and_then(
+                        cx,
+                        UNNECESSARY_DEF_PATH,
+                        expr.span.source_callsite(),
+                        format!("a diagnostic name exists for this path: sym::{name}"),
+                        |diag| {
+                            diag.help(
+                                "remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead",
+                            );
+                            diag.help("see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils");
+                        },
+                    );
+                } else if let Some(item_name) = get_lang_item_name(cx, def_id) {
+                    span_lint_and_then(
+                        cx,
+                        UNNECESSARY_DEF_PATH,
+                        expr.span.source_callsite(),
+                        format!("a language item exists for this path: LangItem::{item_name}"),
+                        |diag| {
+                            diag.help("remove the `PathLookup` and use utilities such as `cx.tcx.lang_items` instead");
+                            diag.help("see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=lang&filter-crate=clippy_utils");
+                        },
                     );
                 }
-            });
-
-            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 };
-
-        for def_id in def_path_def_ids(cx.tcx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
-            self.array_def_ids.insert((def_id, span));
-        }
-    }
-}
-
-fn path_to_matched_type(cx: &LateContext<'_>, expr: &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) => {
-                if let Node::LetStmt(LetStmt { init: Some(init), .. }) = cx.tcx.parent_hir_node(hir_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).instantiate_identity(),
-            ),
-            Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
-                ConstValue::Indirect { alloc_id, offset } if offset.bytes() == 0 => {
-                    let alloc = cx.tcx.global_alloc(alloc_id).unwrap_memory();
-                    read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity())
-                },
-                _ => 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().ptrs().values().next()?;
-        if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) {
-            (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()
-            .ptrs()
-            .values()
-            .map(|&alloc| {
-                if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) {
-                    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
-                && let LitKind::Str(sym, _) = lit.node
-            {
-                return Some((*sym.as_str()).to_owned());
-            }
-
-            None
-        })
-        .collect()
-}
-
 fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> {
     if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) {
         Some(lang_item.variant_name())
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 187dfa4dda84..bbd0c262c246 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -97,28 +97,28 @@ use rustc_data_structures::packed::Pu128;
 use rustc_data_structures::unhash::UnhashMap;
 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
 use rustc_hir::def::{DefKind, Res};
-use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId};
+use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
 use rustc_hir::definitions::{DefPath, DefPathData};
 use rustc_hir::hir_id::{HirIdMap, HirIdSet};
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
 use rustc_hir::{
-    self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
-    CoroutineDesugaring, CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg,
-    GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource,
-    Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath,
-    Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
+    self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring,
+    CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl,
+    ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
+    Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
+    TraitItemKind, TraitRef, TyKind, UnOp, def,
 };
 use rustc_lexer::{TokenKind, tokenize};
 use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::nested_filter;
 use rustc_middle::hir::place::PlaceBase;
 use rustc_middle::lint::LevelAndSource;
 use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
-use rustc_middle::ty::fast_reject::SimplifiedType;
 use rustc_middle::ty::layout::IntegerExt;
 use rustc_middle::ty::{
-    self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgKind, GenericArgsRef, IntTy, Ty,
-    TyCtxt, TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
+    self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
+    TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
 };
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::source_map::SourceMap;
@@ -131,7 +131,6 @@ use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
 use crate::higher::Range;
 use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
 use crate::visitors::for_each_expr_without_closures;
-use rustc_middle::hir::nested_filter;
 
 #[macro_export]
 macro_rules! extract_msrv_attr {
@@ -239,7 +238,7 @@ pub fn is_in_const_context(cx: &LateContext<'_>) -> bool {
 ///  * const blocks (or inline consts)
 ///  * associated constants
 pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
-    use ConstContext::{Const, ConstFn, Static};
+    use rustc_hir::ConstContext::{Const, ConstFn, Static};
     let Some(ctx) = tcx.hir_body_const_context(tcx.hir_enclosing_body_owner(hir_id)) else {
         return false;
     };
@@ -347,14 +346,6 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
     }
 }
 
-/// Checks if the method call given in `expr` belongs to the given trait.
-/// This is a deprecated function, consider using [`is_trait_method`].
-pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
-    let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
-    let trt_id = cx.tcx.trait_of_item(def_id);
-    trt_id.is_some_and(|trt_id| match_def_path(cx, trt_id, path))
-}
-
 /// Checks if the given method call expression calls an inherent method.
 pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
@@ -438,44 +429,6 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tc
         })
 }
 
-/// THIS METHOD IS DEPRECATED. Matches a `QPath` against a slice of segment string literals.
-///
-/// This method is deprecated and will eventually be removed since it does not match against the
-/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
-/// `QPath::Resolved.1.res.opt_def_id()`.
-///
-/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
-/// `rustc_hir::QPath`.
-///
-/// # Examples
-/// ```rust,ignore
-/// match_qpath(path, &["std", "rt", "begin_unwind"])
-/// ```
-pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
-    match *path {
-        QPath::Resolved(_, path) => match_path(path, segments),
-        QPath::TypeRelative(ty, segment) => match ty.kind {
-            TyKind::Path(ref inner_path) => {
-                if let [prefix @ .., end] = segments
-                    && match_qpath(inner_path, prefix)
-                {
-                    return segment.ident.name.as_str() == *end;
-                }
-                false
-            },
-            _ => false,
-        },
-        QPath::LangItem(..) => false,
-    }
-}
-
-/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
-///
-/// Please use `is_path_diagnostic_item` if the target is a diagnostic item.
-pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
-    path_def_id(cx, expr).is_some_and(|id| match_def_path(cx, id, segments))
-}
-
 /// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if
 /// it matches the given lang item.
 pub fn is_path_lang_item<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>, lang_item: LangItem) -> bool {
@@ -492,34 +445,6 @@ pub fn is_path_diagnostic_item<'tcx>(
     path_def_id(cx, maybe_path).is_some_and(|id| cx.tcx.is_diagnostic_item(diag_item, id))
 }
 
-/// THIS METHOD IS DEPRECATED. Matches a `Path` against a slice of segment string literals.
-///
-/// This method is deprecated and will eventually be removed since it does not match against the
-/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
-/// `QPath::Resolved.1.res.opt_def_id()`.
-///
-/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
-/// `rustc_hir::Path`.
-///
-/// # Examples
-///
-/// ```rust,ignore
-/// if match_path(&trait_ref.path, &paths::HASH) {
-///     // This is the `std::hash::Hash` trait.
-/// }
-///
-/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
-///     // This is a `rustc_middle::lint::Lint`.
-/// }
-/// ```
-pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
-    path.segments
-        .iter()
-        .rev()
-        .zip(segments.iter().rev())
-        .all(|(a, b)| a.ident.name.as_str() == *b)
-}
-
 /// If the expression is a path to a local, returns the canonical `HirId` of the local.
 pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
     if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
@@ -586,201 +511,6 @@ pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>
     path_res(cx, maybe_path).opt_def_id()
 }
 
-fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
-    let ty = match name {
-        "bool" => SimplifiedType::Bool,
-        "char" => SimplifiedType::Char,
-        "str" => SimplifiedType::Str,
-        "array" => SimplifiedType::Array,
-        "slice" => SimplifiedType::Slice,
-        // FIXME: rustdoc documents these two using just `pointer`.
-        //
-        // Maybe this is something we should do here too.
-        "const_ptr" => SimplifiedType::Ptr(Mutability::Not),
-        "mut_ptr" => SimplifiedType::Ptr(Mutability::Mut),
-        "isize" => SimplifiedType::Int(IntTy::Isize),
-        "i8" => SimplifiedType::Int(IntTy::I8),
-        "i16" => SimplifiedType::Int(IntTy::I16),
-        "i32" => SimplifiedType::Int(IntTy::I32),
-        "i64" => SimplifiedType::Int(IntTy::I64),
-        "i128" => SimplifiedType::Int(IntTy::I128),
-        "usize" => SimplifiedType::Uint(UintTy::Usize),
-        "u8" => SimplifiedType::Uint(UintTy::U8),
-        "u16" => SimplifiedType::Uint(UintTy::U16),
-        "u32" => SimplifiedType::Uint(UintTy::U32),
-        "u64" => SimplifiedType::Uint(UintTy::U64),
-        "u128" => SimplifiedType::Uint(UintTy::U128),
-        "f32" => SimplifiedType::Float(FloatTy::F32),
-        "f64" => SimplifiedType::Float(FloatTy::F64),
-        _ => {
-            return [].iter().copied();
-        },
-    };
-
-    tcx.incoherent_impls(ty).iter().copied()
-}
-
-fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
-    match tcx.def_kind(def_id) {
-        DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx
-            .module_children(def_id)
-            .iter()
-            .filter(|item| item.ident.name == name)
-            .map(|child| child.res.expect_non_local())
-            .collect(),
-        DefKind::Impl { .. } => tcx
-            .associated_item_def_ids(def_id)
-            .iter()
-            .copied()
-            .filter(|assoc_def_id| tcx.item_name(*assoc_def_id) == name)
-            .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id))
-            .collect(),
-        _ => Vec::new(),
-    }
-}
-
-fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symbol) -> Vec<Res> {
-    let root_mod;
-    let item_kind = match tcx.hir_node_by_def_id(local_id) {
-        Node::Crate(r#mod) => {
-            root_mod = ItemKind::Mod(Ident::dummy(), r#mod);
-            &root_mod
-        },
-        Node::Item(item) => &item.kind,
-        _ => return Vec::new(),
-    };
-
-    let res = |ident: Ident, owner_id: OwnerId| {
-        if ident.name == name {
-            let def_id = owner_id.to_def_id();
-            Some(Res::Def(tcx.def_kind(def_id), def_id))
-        } else {
-            None
-        }
-    };
-
-    match item_kind {
-        ItemKind::Mod(_, r#mod) => r#mod
-            .item_ids
-            .iter()
-            .filter_map(|&item_id| {
-                let ident = tcx.hir_item(item_id).kind.ident()?;
-                res(ident, item_id.owner_id)
-            })
-            .collect(),
-        ItemKind::Impl(r#impl) => r#impl
-            .items
-            .iter()
-            .filter_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id))
-            .collect(),
-        ItemKind::Trait(.., trait_item_refs) => trait_item_refs
-            .iter()
-            .filter_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id))
-            .collect(),
-        _ => Vec::new(),
-    }
-}
-
-fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
-    if let Some(local_id) = def_id.as_local() {
-        local_item_children_by_name(tcx, local_id, name)
-    } else {
-        non_local_item_children_by_name(tcx, def_id, name)
-    }
-}
-
-/// Finds the crates called `name`, may be multiple due to multiple major versions.
-pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> Vec<Res> {
-    tcx.crates(())
-        .iter()
-        .copied()
-        .filter(move |&num| tcx.crate_name(num) == name)
-        .map(CrateNum::as_def_id)
-        .map(|id| Res::Def(tcx.def_kind(id), id))
-        .collect()
-}
-
-/// Resolves a def path like `std::vec::Vec`.
-///
-/// Can return multiple resolutions when there are multiple versions of the same crate, e.g.
-/// `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0.
-///
-/// Also returns multiple results when there are multiple paths under the same name e.g. `std::vec`
-/// would have both a [`DefKind::Mod`] and [`DefKind::Macro`].
-///
-/// This function is expensive and should be used sparingly.
-pub fn def_path_res(tcx: TyCtxt<'_>, path: &[&str]) -> Vec<Res> {
-    let (base, path) = match path {
-        [primitive] => {
-            return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)];
-        },
-        [base, path @ ..] => (base, path),
-        _ => return Vec::new(),
-    };
-
-    let base_sym = Symbol::intern(base);
-
-    let local_crate = if tcx.crate_name(LOCAL_CRATE) == base_sym {
-        Some(LOCAL_CRATE.as_def_id())
-    } else {
-        None
-    };
-
-    let crates = find_primitive_impls(tcx, base)
-        .chain(local_crate)
-        .map(|id| Res::Def(tcx.def_kind(id), id))
-        .chain(find_crates(tcx, base_sym))
-        .collect();
-
-    def_path_res_with_base(tcx, crates, path)
-}
-
-/// Resolves a def path like `vec::Vec` with the base `std`.
-///
-/// This is lighter than [`def_path_res`], and should be called with [`find_crates`] looking up
-/// items from the same crate repeatedly, although should still be used sparingly.
-pub fn def_path_res_with_base(tcx: TyCtxt<'_>, mut base: Vec<Res>, mut path: &[&str]) -> Vec<Res> {
-    while let [segment, rest @ ..] = path {
-        path = rest;
-        let segment = Symbol::intern(segment);
-
-        base = base
-            .into_iter()
-            .filter_map(|res| res.opt_def_id())
-            .flat_map(|def_id| {
-                // When the current def_id is e.g. `struct S`, check the impl items in
-                // `impl S { ... }`
-                let inherent_impl_children = tcx
-                    .inherent_impls(def_id)
-                    .iter()
-                    .flat_map(|&impl_def_id| item_children_by_name(tcx, impl_def_id, segment));
-
-                let direct_children = item_children_by_name(tcx, def_id, segment);
-
-                inherent_impl_children.chain(direct_children)
-            })
-            .collect();
-    }
-
-    base
-}
-
-/// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`].
-pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator<Item = DefId> + use<> {
-    def_path_res(tcx, path).into_iter().filter_map(|res| res.opt_def_id())
-}
-
-/// Convenience function to get the `DefId` of a trait by path.
-/// It could be a trait or trait alias.
-///
-/// This function is expensive and should be used sparingly.
-pub fn get_trait_def_id(tcx: TyCtxt<'_>, path: &[&str]) -> Option<DefId> {
-    def_path_res(tcx, path).into_iter().find_map(|res| match res {
-        Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
-        _ => None,
-    })
-}
-
 /// Gets the `hir::TraitRef` of the trait the given method is implemented for.
 ///
 /// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
@@ -2065,24 +1795,6 @@ pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
         })
 }
 
-/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
-/// any.
-///
-/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items.
-pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
-    let search_path = cx.get_def_path(did);
-    paths
-        .iter()
-        .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
-}
-
-/// Checks if the given `DefId` matches the path.
-pub fn match_def_path(cx: &LateContext<'_>, did: DefId, syms: &[&str]) -> bool {
-    // We should probably move to Symbols in Clippy as well rather than interning every time.
-    let path = cx.get_def_path(did);
-    syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
-}
-
 /// Checks if the given `DefId` matches the `libc` item.
 pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
     let path = cx.get_def_path(did);
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 7f64ebd3b643..795fb502c9cc 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -4,62 +4,332 @@
 //! Whenever possible, please consider diagnostic items over hardcoded paths.
 //! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
 
-// Paths inside rustc
-pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
-pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
-    ["rustc_lint_defs", "Applicability", "Unspecified"],
-    ["rustc_lint_defs", "Applicability", "HasPlaceholders"],
-    ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
-    ["rustc_lint_defs", "Applicability", "MachineApplicable"],
-];
-pub const DIAG: [&str; 2] = ["rustc_errors", "Diag"];
-pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
-pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
-pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
-pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
-pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
-pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
-pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
-pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
-pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
-pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
-pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
-pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+use crate::{MaybePath, path_def_id, sym};
+use rustc_ast::Mutability;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::Namespace::{MacroNS, TypeNS, ValueNS};
+use rustc_hir::def::{DefKind, Namespace};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
+use rustc_hir::{ImplItemRef, ItemKind, Node, OwnerId, TraitItemRef};
+use rustc_lint::LateContext;
+use rustc_middle::ty::fast_reject::SimplifiedType;
+use rustc_middle::ty::{FloatTy, IntTy, Ty, TyCtxt, UintTy};
+use rustc_span::{Ident, STDLIB_STABLE_CRATES, Symbol};
+use std::sync::OnceLock;
 
-// Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items.
-pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>", "is_ascii"];
-pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
-pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];
-pub const ALIGN_OF: [&str; 3] = ["core", "mem", "align_of"];
+/// Specifies whether to resolve a path in the [`TypeNS`], [`ValueNS`], [`MacroNS`] or in an
+/// arbitrary namespace
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum PathNS {
+    Type,
+    Value,
+    Macro,
+
+    /// Resolves to the name in the first available namespace, e.g. for `std::vec` this would return
+    /// either the macro or the module but **not** both
+    ///
+    /// Must only be used when the specific resolution is unimportant such as in
+    /// `missing_enforced_import_renames`
+    Arbitrary,
+}
+
+impl PathNS {
+    fn matches(self, ns: Option<Namespace>) -> bool {
+        let required = match self {
+            PathNS::Type => TypeNS,
+            PathNS::Value => ValueNS,
+            PathNS::Macro => MacroNS,
+            PathNS::Arbitrary => return true,
+        };
+
+        ns == Some(required)
+    }
+}
+
+/// Lazily resolves a path into a list of [`DefId`]s using [`lookup_path`].
+///
+/// Typically it will contain one [`DefId`] or none, but in some situations there can be multiple:
+/// - `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0
+/// - `alloc::boxed::Box::downcast` would return a function for each of the different inherent impls
+///   ([1], [2], [3])
+///
+/// [1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast
+/// [2]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-1
+/// [3]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-2
+pub struct PathLookup {
+    ns: PathNS,
+    path: &'static [Symbol],
+    once: OnceLock<Vec<DefId>>,
+}
+
+impl PathLookup {
+    /// Only exported for tests and `clippy_lints_internal`
+    #[doc(hidden)]
+    pub const fn new(ns: PathNS, path: &'static [Symbol]) -> Self {
+        Self {
+            ns,
+            path,
+            once: OnceLock::new(),
+        }
+    }
+
+    /// Returns the list of [`DefId`]s that the path resolves to
+    pub fn get(&self, cx: &LateContext<'_>) -> &[DefId] {
+        self.once.get_or_init(|| lookup_path(cx.tcx, self.ns, self.path))
+    }
 
-// Paths in clippy itself
-pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"];
-pub const CLIPPY_SYM_MODULE: [&str; 2] = ["clippy_utils", "sym"];
+    /// Returns the single [`DefId`] that the path resolves to, this can only be used for paths into
+    /// stdlib crates to avoid the issue of multiple [`DefId`]s being returned
+    ///
+    /// May return [`None`] in `no_std`/`no_core` environments
+    pub fn only(&self, cx: &LateContext<'_>) -> Option<DefId> {
+        let ids = self.get(cx);
+        debug_assert!(STDLIB_STABLE_CRATES.contains(&self.path[0]));
+        debug_assert!(ids.len() <= 1, "{ids:?}");
+        ids.first().copied()
+    }
+
+    /// Checks if the path resolves to the given `def_id`
+    pub fn matches(&self, cx: &LateContext<'_>, def_id: DefId) -> bool {
+        self.get(cx).contains(&def_id)
+    }
+
+    /// Resolves `maybe_path` to a [`DefId`] and checks if the [`PathLookup`] matches it
+    pub fn matches_path<'tcx>(&self, cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> bool {
+        path_def_id(cx, maybe_path).is_some_and(|def_id| self.matches(cx, def_id))
+    }
+
+    /// Checks if the path resolves to `ty`'s definition, must be an `Adt`
+    pub fn matches_ty(&self, cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+        ty.ty_adt_def().is_some_and(|adt| self.matches(cx, adt.did()))
+    }
+}
+
+macro_rules! path_macros {
+    ($($name:ident: $ns:expr,)*) => {
+        $(
+            /// Only exported for tests and `clippy_lints_internal`
+            #[doc(hidden)]
+            #[macro_export]
+            macro_rules! $name {
+                ($$($$seg:ident $$(::)?)*) => {
+                    PathLookup::new($ns, &[$$(sym::$$seg,)*])
+                };
+            }
+        )*
+    };
+}
+
+path_macros! {
+    type_path: PathNS::Type,
+    value_path: PathNS::Value,
+    macro_path: PathNS::Macro,
+}
+
+// Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items.
+pub static ALIGN_OF: PathLookup = value_path!(core::mem::align_of);
+pub static CHAR_TO_DIGIT: PathLookup = value_path!(char::to_digit);
+pub static IO_ERROR_NEW: PathLookup = value_path!(std::io::Error::new);
+pub static IO_ERRORKIND_OTHER_CTOR: PathLookup = value_path!(std::io::ErrorKind::Other);
+pub static ITER_STEP: PathLookup = type_path!(core::iter::Step);
+pub static SLICE_FROM_REF: PathLookup = value_path!(core::slice::from_ref);
 
 // Paths in external crates
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
-pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
-pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"];
-pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
-pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
-pub const REGEX_BUILDER_NEW: [&str; 3] = ["regex", "RegexBuilder", "new"];
-pub const REGEX_BYTES_BUILDER_NEW: [&str; 4] = ["regex", "bytes", "RegexBuilder", "new"];
-pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "bytes", "Regex", "new"];
-pub const REGEX_BYTES_SET_NEW: [&str; 4] = ["regex", "bytes", "RegexSet", "new"];
-pub const REGEX_NEW: [&str; 3] = ["regex", "Regex", "new"];
-pub const REGEX_SET_NEW: [&str; 3] = ["regex", "RegexSet", "new"];
-pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
-pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const TOKIO_IO_OPEN_OPTIONS: [&str; 4] = ["tokio", "fs", "open_options", "OpenOptions"];
-#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
-pub const TOKIO_IO_OPEN_OPTIONS_NEW: [&str; 5] = ["tokio", "fs", "open_options", "OpenOptions", "new"];
+pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt);
+pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt);
+pub static ITERTOOLS_NEXT_TUPLE: PathLookup = value_path!(itertools::Itertools::next_tuple);
+pub static PARKING_LOT_GUARDS: [PathLookup; 3] = [
+    type_path!(lock_api::mutex::MutexGuard),
+    type_path!(lock_api::rwlock::RwLockReadGuard),
+    type_path!(lock_api::rwlock::RwLockWriteGuard),
+];
+pub static REGEX_BUILDER_NEW: PathLookup = value_path!(regex::RegexBuilder::new);
+pub static REGEX_BYTES_BUILDER_NEW: PathLookup = value_path!(regex::bytes::RegexBuilder::new);
+pub static REGEX_BYTES_NEW: PathLookup = value_path!(regex::bytes::Regex::new);
+pub static REGEX_BYTES_SET_NEW: PathLookup = value_path!(regex::bytes::RegexSet::new);
+pub static REGEX_NEW: PathLookup = value_path!(regex::Regex::new);
+pub static REGEX_SET_NEW: PathLookup = value_path!(regex::RegexSet::new);
+pub static SERDE_DESERIALIZE: PathLookup = type_path!(serde::de::Deserialize);
+pub static SERDE_DE_VISITOR: PathLookup = type_path!(serde::de::Visitor);
+pub static TOKIO_FILE_OPTIONS: PathLookup = value_path!(tokio::fs::File::options);
+pub static TOKIO_IO_ASYNCREADEXT: PathLookup = type_path!(tokio::io::AsyncReadExt);
+pub static TOKIO_IO_ASYNCWRITEEXT: PathLookup = type_path!(tokio::io::AsyncWriteExt);
+pub static TOKIO_IO_OPEN_OPTIONS: PathLookup = type_path!(tokio::fs::OpenOptions);
+pub static TOKIO_IO_OPEN_OPTIONS_NEW: PathLookup = value_path!(tokio::fs::OpenOptions::new);
+pub static LAZY_STATIC: PathLookup = macro_path!(lazy_static::lazy_static);
+pub static ONCE_CELL_SYNC_LAZY: PathLookup = type_path!(once_cell::sync::Lazy);
+pub static ONCE_CELL_SYNC_LAZY_NEW: PathLookup = value_path!(once_cell::sync::Lazy::new);
+
+// Paths for internal lints go in `clippy_lints_internal/src/internal_paths.rs`
+
+/// Equivalent to a [`lookup_path`] after splitting the input string on `::`
+///
+/// This function is expensive and should be used sparingly.
+pub fn lookup_path_str(tcx: TyCtxt<'_>, ns: PathNS, path: &str) -> Vec<DefId> {
+    let path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
+    lookup_path(tcx, ns, &path)
+}
+
+/// Resolves a def path like `std::vec::Vec`.
+///
+/// Typically it will return one [`DefId`] or none, but in some situations there can be multiple:
+/// - `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0
+/// - `alloc::boxed::Box::downcast` would return a function for each of the different inherent impls
+///   ([1], [2], [3])
+///
+/// This function is expensive and should be used sparingly.
+///
+/// [1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast
+/// [2]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-1
+/// [3]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-2
+pub fn lookup_path(tcx: TyCtxt<'_>, ns: PathNS, path: &[Symbol]) -> Vec<DefId> {
+    let (root, rest) = match *path {
+        [] | [_] => return Vec::new(),
+        [root, ref rest @ ..] => (root, rest),
+    };
+
+    let mut out = Vec::new();
+    for &base in find_crates(tcx, root).iter().chain(find_primitive_impls(tcx, root)) {
+        lookup_with_base(tcx, base, ns, rest, &mut out);
+    }
+    out
+}
+
+/// Finds the crates called `name`, may be multiple due to multiple major versions.
+pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> &'static [DefId] {
+    static BY_NAME: OnceLock<FxHashMap<Symbol, Vec<DefId>>> = OnceLock::new();
+    let map = BY_NAME.get_or_init(|| {
+        let mut map = FxHashMap::default();
+        map.insert(tcx.crate_name(LOCAL_CRATE), vec![LOCAL_CRATE.as_def_id()]);
+        for &num in tcx.crates(()) {
+            map.entry(tcx.crate_name(num)).or_default().push(num.as_def_id());
+        }
+        map
+    });
+    match map.get(&name) {
+        Some(def_ids) => def_ids,
+        None => &[],
+    }
+}
+
+fn find_primitive_impls(tcx: TyCtxt<'_>, name: Symbol) -> &[DefId] {
+    let ty = match name {
+        sym::bool => SimplifiedType::Bool,
+        sym::char => SimplifiedType::Char,
+        sym::str => SimplifiedType::Str,
+        sym::array => SimplifiedType::Array,
+        sym::slice => SimplifiedType::Slice,
+        // FIXME: rustdoc documents these two using just `pointer`.
+        //
+        // Maybe this is something we should do here too.
+        sym::const_ptr => SimplifiedType::Ptr(Mutability::Not),
+        sym::mut_ptr => SimplifiedType::Ptr(Mutability::Mut),
+        sym::isize => SimplifiedType::Int(IntTy::Isize),
+        sym::i8 => SimplifiedType::Int(IntTy::I8),
+        sym::i16 => SimplifiedType::Int(IntTy::I16),
+        sym::i32 => SimplifiedType::Int(IntTy::I32),
+        sym::i64 => SimplifiedType::Int(IntTy::I64),
+        sym::i128 => SimplifiedType::Int(IntTy::I128),
+        sym::usize => SimplifiedType::Uint(UintTy::Usize),
+        sym::u8 => SimplifiedType::Uint(UintTy::U8),
+        sym::u16 => SimplifiedType::Uint(UintTy::U16),
+        sym::u32 => SimplifiedType::Uint(UintTy::U32),
+        sym::u64 => SimplifiedType::Uint(UintTy::U64),
+        sym::u128 => SimplifiedType::Uint(UintTy::U128),
+        sym::f32 => SimplifiedType::Float(FloatTy::F32),
+        sym::f64 => SimplifiedType::Float(FloatTy::F64),
+        _ => return &[],
+    };
+
+    tcx.incoherent_impls(ty)
+}
+
+/// Resolves a def path like `vec::Vec` with the base `std`.
+fn lookup_with_base(tcx: TyCtxt<'_>, mut base: DefId, ns: PathNS, mut path: &[Symbol], out: &mut Vec<DefId>) {
+    loop {
+        match *path {
+            [segment] => {
+                out.extend(item_child_by_name(tcx, base, ns, segment));
+
+                // When the current def_id is e.g. `struct S`, check the impl items in
+                // `impl S { ... }`
+                let inherent_impl_children = tcx
+                    .inherent_impls(base)
+                    .iter()
+                    .filter_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, ns, segment));
+                out.extend(inherent_impl_children);
+
+                return;
+            },
+            [segment, ref rest @ ..] => {
+                path = rest;
+                let Some(child) = item_child_by_name(tcx, base, PathNS::Type, segment) else {
+                    return;
+                };
+                base = child;
+            },
+            [] => unreachable!(),
+        }
+    }
+}
+
+fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
+    if let Some(local_id) = def_id.as_local() {
+        local_item_child_by_name(tcx, local_id, ns, name)
+    } else {
+        non_local_item_child_by_name(tcx, def_id, ns, name)
+    }
+}
+
+fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, name: Symbol) -> Option<DefId> {
+    let root_mod;
+    let item_kind = match tcx.hir_node_by_def_id(local_id) {
+        Node::Crate(r#mod) => {
+            root_mod = ItemKind::Mod(Ident::dummy(), r#mod);
+            &root_mod
+        },
+        Node::Item(item) => &item.kind,
+        _ => return None,
+    };
+
+    let res = |ident: Ident, owner_id: OwnerId| {
+        if ident.name == name && ns.matches(tcx.def_kind(owner_id).ns()) {
+            Some(owner_id.to_def_id())
+        } else {
+            None
+        }
+    };
+
+    match item_kind {
+        ItemKind::Mod(_, r#mod) => r#mod.item_ids.iter().find_map(|&item_id| {
+            let ident = tcx.hir_item(item_id).kind.ident()?;
+            res(ident, item_id.owner_id)
+        }),
+        ItemKind::Impl(r#impl) => r#impl
+            .items
+            .iter()
+            .find_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id)),
+        ItemKind::Trait(.., trait_item_refs) => trait_item_refs
+            .iter()
+            .find_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id)),
+        _ => None,
+    }
+}
+
+fn non_local_item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
+    match tcx.def_kind(def_id) {
+        DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx.module_children(def_id).iter().find_map(|child| {
+            if child.ident.name == name && ns.matches(child.res.ns()) {
+                child.res.opt_def_id()
+            } else {
+                None
+            }
+        }),
+        DefKind::Impl { .. } => tcx
+            .associated_item_def_ids(def_id)
+            .iter()
+            .copied()
+            .find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
+        _ => None,
+    }
+}
diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs
index 38f077134c03..94b73f37269f 100644
--- a/clippy_utils/src/sym.rs
+++ b/clippy_utils/src/sym.rs
@@ -1,6 +1,6 @@
 #![allow(non_upper_case_globals)]
 
-use rustc_span::symbol::{PREDEFINED_SYMBOLS_COUNT, Symbol};
+use rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT;
 
 #[doc(no_inline)]
 pub use rustc_span::sym::*;
@@ -24,33 +24,45 @@ macro_rules! generate {
         ];
 
         $(
-            pub const $name: Symbol = Symbol::new(PREDEFINED_SYMBOLS_COUNT + ${index()});
+            pub const $name: rustc_span::Symbol = rustc_span::Symbol::new(PREDEFINED_SYMBOLS_COUNT + ${index()});
         )*
     };
 }
 
 generate! {
     abs,
+    align_of,
     as_bytes,
     as_deref_mut,
     as_deref,
     as_mut,
+    AsyncReadExt,
+    AsyncWriteExt,
     Binary,
     build_hasher,
+    bytes,
     cargo_clippy: "cargo-clippy",
     Cargo_toml: "Cargo.toml",
     cast,
     chars,
     CLIPPY_ARGS,
     CLIPPY_CONF_DIR,
+    clippy_utils,
     clone_into,
     cloned,
     collect,
+    const_ptr,
     contains,
     copied,
     CRLF: "\r\n",
     Current,
+    de,
+    Deserialize,
+    diagnostics,
+    EarlyLintPass,
     ends_with,
+    error,
+    ErrorKind,
     exp,
     extend,
     finish_non_exhaustive,
@@ -58,48 +70,87 @@ generate! {
     flat_map,
     for_each,
     from_raw,
+    from_ref,
     from_str_radix,
+    fs,
+    futures_util,
     get,
+    hygiene,
     insert,
     int_roundings,
     into_bytes,
     into_owned,
     IntoIter,
+    io,
     is_ascii,
     is_empty,
     is_err,
     is_none,
     is_ok,
     is_some,
+    itertools,
+    Itertools,
+    kw,
     last,
+    lazy_static,
+    Lazy,
     LF: "\n",
+    Lint,
+    lock_api,
     LowerExp,
     LowerHex,
     max,
+    MAX,
+    mem,
     min,
+    MIN,
     mode,
     msrv,
+    msrvs,
+    MsrvStack,
+    mut_ptr,
+    mutex,
+    next_tuple,
     Octal,
+    once_cell,
+    OpenOptions,
     or_default,
+    Other,
     parse,
+    PathLookup,
+    paths,
     push,
     regex,
+    Regex,
+    RegexBuilder,
+    RegexSet,
     reserve,
     resize,
     restriction,
+    rustc_lint_defs,
+    rustc_lint,
+    rustc_span,
     rustfmt_skip,
+    rwlock,
+    serde,
     set_len,
     set_mode,
     set_readonly,
     signum,
+    span_lint_and_then,
     split_whitespace,
     split,
     Start,
+    Step,
+    symbol,
+    Symbol,
+    SyntaxContext,
     take,
     TBD,
     then_some,
     to_digit,
     to_owned,
+    tokio,
     unused_extern_crates,
     unwrap_err,
     unwrap_or_default,
@@ -107,6 +158,7 @@ generate! {
     UpperHex,
     V4,
     V6,
+    Visitor,
     Weak,
     with_capacity,
     wrapping_offset,
diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs
index db7233126955..2683892448dd 100644
--- a/clippy_utils/src/ty/mod.rs
+++ b/clippy_utils/src/ty/mod.rs
@@ -32,7 +32,8 @@ use std::assert_matches::debug_assert_matches;
 use std::collections::hash_map::Entry;
 use std::iter;
 
-use crate::{def_path_def_ids, match_def_path, path_res};
+use crate::path_res;
+use crate::paths::{PathNS, lookup_path_str};
 
 mod type_certainty;
 pub use type_certainty::expr_type_is_certain;
@@ -229,9 +230,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
 /// Checks whether a type implements a trait.
 /// The function returns false in case the type contains an inference variable.
 ///
-/// See:
-/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
-/// * [Common tools for writing lints] for an example how to use this function and other options.
+/// See [Common tools for writing lints] for an example how to use this function and other options.
 ///
 /// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
 pub fn implements_trait<'tcx>(
@@ -424,17 +423,6 @@ pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
     matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
 }
 
-/// Checks if type is struct, enum or union type with the given def path.
-///
-/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
-/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
-pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
-    match ty.kind() {
-        ty::Adt(adt, _) => match_def_path(cx, adt.did(), path),
-        _ => false,
-    }
-}
-
 /// Checks if the drop order for a type matters.
 ///
 /// Some std types implement drop solely to deallocate memory. For these types, and composites
@@ -1131,10 +1119,7 @@ impl<'tcx> InteriorMut<'tcx> {
     pub fn new(tcx: TyCtxt<'tcx>, ignore_interior_mutability: &[String]) -> Self {
         let ignored_def_ids = ignore_interior_mutability
             .iter()
-            .flat_map(|ignored_ty| {
-                let path: Vec<&str> = ignored_ty.split("::").collect();
-                def_path_def_ids(tcx, path.as_slice())
-            })
+            .flat_map(|ignored_ty| lookup_path_str(tcx, PathNS::Type, ignored_ty))
             .collect();
 
         Self {
diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs
index 3398ff8af2f5..6e3586623277 100644
--- a/clippy_utils/src/ty/type_certainty/mod.rs
+++ b/clippy_utils/src/ty/type_certainty/mod.rs
@@ -11,14 +11,14 @@
 //! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should
 //! be considered a bug.
 
-use crate::def_path_res;
+use crate::paths::{PathNS, lookup_path};
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty};
 use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind};
 use rustc_lint::LateContext;
 use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
-use rustc_span::{Span, Symbol};
+use rustc_span::Span;
 
 mod certainty;
 use certainty::{Certainty, Meet, join, meet};
@@ -194,7 +194,7 @@ fn path_segment_certainty(
     path_segment: &PathSegment<'_>,
     resolves_to_type: bool,
 ) -> Certainty {
-    let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) {
+    let certainty = match update_res(cx, parent_certainty, path_segment, resolves_to_type).unwrap_or(path_segment.res) {
         // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or
         // an unparameterized type), or the generics are instantiated with arguments that are certain.
         //
@@ -267,17 +267,24 @@ fn path_segment_certainty(
 
 /// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`.
 /// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`.
-fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option<Res> {
+fn update_res(
+    cx: &LateContext<'_>,
+    parent_certainty: Certainty,
+    path_segment: &PathSegment<'_>,
+    resolves_to_type: bool,
+) -> Option<Res> {
     if path_segment.res == Res::Err
         && let Some(def_id) = parent_certainty.to_def_id()
     {
         let mut def_path = cx.get_def_path(def_id);
         def_path.push(path_segment.ident.name);
-        let reses = def_path_res(cx.tcx, &def_path.iter().map(Symbol::as_str).collect::<Vec<_>>());
-        if let [res] = reses.as_slice() { Some(*res) } else { None }
-    } else {
-        None
+        let ns = if resolves_to_type { PathNS::Type } else { PathNS::Value };
+        if let &[id] = lookup_path(cx.tcx, ns, &def_path).as_slice() {
+            return Some(Res::Def(cx.tcx.def_kind(id), id));
+        }
     }
+
+    None
 }
 
 #[allow(clippy::cast_possible_truncation)]
diff --git a/tests/ui-internal/auxiliary/paths.rs b/tests/ui-internal/auxiliary/paths.rs
deleted file mode 100644
index f730f564a09c..000000000000
--- a/tests/ui-internal/auxiliary/paths.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-#![allow(clippy::unnecessary_def_path)]
-
-pub static OPTION: [&str; 3] = ["core", "option", "Option"];
-pub const RESULT: &[&str] = &["core", "result", "Result"];
diff --git a/tests/ui-internal/invalid_paths.rs b/tests/ui-internal/invalid_paths.rs
deleted file mode 100644
index 7317abc2185a..000000000000
--- a/tests/ui-internal/invalid_paths.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-#![deny(clippy::invalid_paths)]
-#![allow(clippy::missing_clippy_version_attribute, clippy::unnecessary_def_path)]
-
-mod paths {
-    // Good path
-    pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
-
-    // Path to method on inherent impl of a primitive type
-    pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
-
-    // Path to method on inherent impl
-    pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
-
-    // Path with empty segment
-    pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
-    //~^ invalid_paths
-
-    // Path with bad crate
-    pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
-    //~^ invalid_paths
-
-    // Path with bad module
-    pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
-    //~^ invalid_paths
-
-    // Path to method on an enum inherent impl
-    pub const OPTION_IS_SOME: [&str; 4] = ["core", "option", "Option", "is_some"];
-}
-
-fn main() {}
diff --git a/tests/ui-internal/invalid_paths.stderr b/tests/ui-internal/invalid_paths.stderr
deleted file mode 100644
index 7b7b25ce8d8d..000000000000
--- a/tests/ui-internal/invalid_paths.stderr
+++ /dev/null
@@ -1,26 +0,0 @@
-error: invalid path
-  --> tests/ui-internal/invalid_paths.rs:15:5
-   |
-LL |     pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-note: the lint level is defined here
-  --> tests/ui-internal/invalid_paths.rs:1:9
-   |
-LL | #![deny(clippy::invalid_paths)]
-   |         ^^^^^^^^^^^^^^^^^^^^^
-
-error: invalid path
-  --> tests/ui-internal/invalid_paths.rs:19:5
-   |
-LL |     pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: invalid path
-  --> tests/ui-internal/invalid_paths.rs:23:5
-   |
-LL |     pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 3 previous errors
-
diff --git a/tests/ui-internal/unnecessary_def_path.fixed b/tests/ui-internal/unnecessary_def_path.fixed
deleted file mode 100644
index 89902ebe4e54..000000000000
--- a/tests/ui-internal/unnecessary_def_path.fixed
+++ /dev/null
@@ -1,77 +0,0 @@
-//@aux-build:paths.rs
-#![deny(clippy::unnecessary_def_path)]
-#![feature(rustc_private)]
-#![allow(clippy::unnecessary_map_or)]
-
-extern crate clippy_utils;
-extern crate paths;
-extern crate rustc_hir;
-extern crate rustc_lint;
-extern crate rustc_middle;
-extern crate rustc_span;
-
-#[allow(unused)]
-use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
-#[allow(unused)]
-use clippy_utils::{
-    is_enum_variant_ctor, is_expr_path_def_path, is_path_diagnostic_item, is_res_lang_ctor, is_trait_method,
-    match_def_path, match_trait_method, path_res,
-};
-
-#[allow(unused)]
-use rustc_hir::LangItem;
-#[allow(unused)]
-use rustc_span::sym;
-
-use rustc_hir::Expr;
-use rustc_hir::def_id::DefId;
-use rustc_lint::LateContext;
-use rustc_middle::ty::Ty;
-
-#[allow(unused, clippy::unnecessary_def_path)]
-static OPTION: [&str; 3] = ["core", "option", "Option"];
-#[allow(unused, clippy::unnecessary_def_path)]
-const RESULT: &[&str] = &["core", "result", "Result"];
-
-fn _f<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, did: DefId, expr: &Expr<'_>) {
-    let _ = is_type_diagnostic_item(cx, ty, sym::Option);
-    //~^ unnecessary_def_path
-    let _ = is_type_diagnostic_item(cx, ty, sym::Result);
-    //~^ unnecessary_def_path
-    let _ = is_type_diagnostic_item(cx, ty, sym::Result);
-    //~^ unnecessary_def_path
-
-    #[allow(unused, clippy::unnecessary_def_path)]
-    let rc_path = &["alloc", "rc", "Rc"];
-    let _ = is_type_diagnostic_item(cx, ty, sym::Rc);
-    //~^ unnecessary_def_path
-
-    let _ = is_type_diagnostic_item(cx, ty, sym::Option);
-    //~^ unnecessary_def_path
-    let _ = is_type_diagnostic_item(cx, ty, sym::Result);
-    //~^ unnecessary_def_path
-
-    let _ = is_type_lang_item(cx, ty, LangItem::OwnedBox);
-    //~^ unnecessary_def_path
-    let _ = is_type_diagnostic_item(cx, ty, sym::maybe_uninit_uninit);
-    //~^ unnecessary_def_path
-
-    let _ = cx.tcx.lang_items().get(LangItem::OwnedBox) == Some(did);
-    //~^ unnecessary_def_path
-    let _ = cx.tcx.is_diagnostic_item(sym::Option, did);
-    //~^ unnecessary_def_path
-    let _ = cx.tcx.lang_items().get(LangItem::OptionSome) == Some(did);
-    //~^ unnecessary_def_path
-
-    let _ = is_trait_method(cx, expr, sym::AsRef);
-    //~^ unnecessary_def_path
-
-    let _ = is_path_diagnostic_item(cx, expr, sym::Option);
-    //~^ unnecessary_def_path
-    let _ = path_res(cx, expr).opt_def_id().map_or(false, |id| cx.tcx.lang_items().get(LangItem::IteratorNext) == Some(id));
-    //~^ unnecessary_def_path
-    let _ = is_res_lang_ctor(cx, path_res(cx, expr), LangItem::OptionSome);
-    //~^ unnecessary_def_path
-}
-
-fn main() {}
diff --git a/tests/ui-internal/unnecessary_def_path.rs b/tests/ui-internal/unnecessary_def_path.rs
index cfca15267c19..5cd3254188d9 100644
--- a/tests/ui-internal/unnecessary_def_path.rs
+++ b/tests/ui-internal/unnecessary_def_path.rs
@@ -1,77 +1,20 @@
-//@aux-build:paths.rs
-#![deny(clippy::unnecessary_def_path)]
 #![feature(rustc_private)]
-#![allow(clippy::unnecessary_map_or)]
 
-extern crate clippy_utils;
-extern crate paths;
-extern crate rustc_hir;
-extern crate rustc_lint;
-extern crate rustc_middle;
-extern crate rustc_span;
+use clippy_utils::paths::{PathLookup, PathNS};
+use clippy_utils::{macro_path, sym, type_path, value_path};
 
-#[allow(unused)]
-use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
-#[allow(unused)]
-use clippy_utils::{
-    is_enum_variant_ctor, is_expr_path_def_path, is_path_diagnostic_item, is_res_lang_ctor, is_trait_method,
-    match_def_path, match_trait_method, path_res,
-};
+static OPTION: PathLookup = type_path!(core::option::Option);
+//~^ unnecessary_def_path
+static SOME: PathLookup = type_path!(core::option::Option::Some);
+//~^ unnecessary_def_path
 
-#[allow(unused)]
-use rustc_hir::LangItem;
-#[allow(unused)]
-use rustc_span::sym;
+static RESULT: PathLookup = type_path!(core::result::Result);
+//~^ unnecessary_def_path
+static RESULT_VIA_STD: PathLookup = type_path!(std::result::Result);
+//~^ unnecessary_def_path
 
-use rustc_hir::Expr;
-use rustc_hir::def_id::DefId;
-use rustc_lint::LateContext;
-use rustc_middle::ty::Ty;
+static VEC_NEW: PathLookup = value_path!(alloc::vec::Vec::new);
+//~^ unnecessary_def_path
 
-#[allow(unused, clippy::unnecessary_def_path)]
-static OPTION: [&str; 3] = ["core", "option", "Option"];
-#[allow(unused, clippy::unnecessary_def_path)]
-const RESULT: &[&str] = &["core", "result", "Result"];
-
-fn _f<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, did: DefId, expr: &Expr<'_>) {
-    let _ = match_type(cx, ty, &OPTION);
-    //~^ unnecessary_def_path
-    let _ = match_type(cx, ty, RESULT);
-    //~^ unnecessary_def_path
-    let _ = match_type(cx, ty, &["core", "result", "Result"]);
-    //~^ unnecessary_def_path
-
-    #[allow(unused, clippy::unnecessary_def_path)]
-    let rc_path = &["alloc", "rc", "Rc"];
-    let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
-    //~^ unnecessary_def_path
-
-    let _ = match_type(cx, ty, &paths::OPTION);
-    //~^ unnecessary_def_path
-    let _ = match_type(cx, ty, paths::RESULT);
-    //~^ unnecessary_def_path
-
-    let _ = match_type(cx, ty, &["alloc", "boxed", "Box"]);
-    //~^ unnecessary_def_path
-    let _ = match_type(cx, ty, &["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]);
-    //~^ unnecessary_def_path
-
-    let _ = match_def_path(cx, did, &["alloc", "boxed", "Box"]);
-    //~^ unnecessary_def_path
-    let _ = match_def_path(cx, did, &["core", "option", "Option"]);
-    //~^ unnecessary_def_path
-    let _ = match_def_path(cx, did, &["core", "option", "Option", "Some"]);
-    //~^ unnecessary_def_path
-
-    let _ = match_trait_method(cx, expr, &["core", "convert", "AsRef"]);
-    //~^ unnecessary_def_path
-
-    let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option"]);
-    //~^ unnecessary_def_path
-    let _ = is_expr_path_def_path(cx, expr, &["core", "iter", "traits", "Iterator", "next"]);
-    //~^ unnecessary_def_path
-    let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option", "Some"]);
-    //~^ unnecessary_def_path
-}
-
-fn main() {}
+static VEC_MACRO: PathLookup = macro_path!(std::vec);
+//~^ unnecessary_def_path
diff --git a/tests/ui-internal/unnecessary_def_path.stderr b/tests/ui-internal/unnecessary_def_path.stderr
index d7fb4ea551e1..4abb1be7406c 100644
--- a/tests/ui-internal/unnecessary_def_path.stderr
+++ b/tests/ui-internal/unnecessary_def_path.stderr
@@ -1,100 +1,58 @@
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:37:13
+error: a diagnostic name exists for this path: sym::Option
+  --> tests/ui-internal/unnecessary_def_path.rs:6:29
    |
-LL |     let _ = match_type(cx, ty, &OPTION);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Option)`
+LL | static OPTION: PathLookup = type_path!(core::option::Option);
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-note: the lint level is defined here
-  --> tests/ui-internal/unnecessary_def_path.rs:2:9
-   |
-LL | #![deny(clippy::unnecessary_def_path)]
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:39:13
-   |
-LL |     let _ = match_type(cx, ty, RESULT);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils
+   = note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::unnecessary_def_path)]`
 
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:41:13
+error: a language item exists for this path: LangItem::OptionSome
+  --> tests/ui-internal/unnecessary_def_path.rs:8:27
    |
-LL |     let _ = match_type(cx, ty, &["core", "result", "Result"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:46:13
+LL | static SOME: PathLookup = type_path!(core::option::Option::Some);
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Rc)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.lang_items` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=lang&filter-crate=clippy_utils
 
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:49:13
+error: a diagnostic name exists for this path: sym::Result
+  --> tests/ui-internal/unnecessary_def_path.rs:11:29
    |
-LL |     let _ = match_type(cx, ty, &paths::OPTION);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Option)`
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:51:13
+LL | static RESULT: PathLookup = type_path!(core::result::Result);
+   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let _ = match_type(cx, ty, paths::RESULT);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils
 
-error: use of a def path to a `LangItem`
-  --> tests/ui-internal/unnecessary_def_path.rs:54:13
+error: a diagnostic name exists for this path: sym::Result
+  --> tests/ui-internal/unnecessary_def_path.rs:13:37
    |
-LL |     let _ = match_type(cx, ty, &["alloc", "boxed", "Box"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_lang_item(cx, ty, LangItem::OwnedBox)`
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:56:13
+LL | static RESULT_VIA_STD: PathLookup = type_path!(std::result::Result);
+   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let _ = match_type(cx, ty, &["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::maybe_uninit_uninit)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils
 
-error: use of a def path to a `LangItem`
-  --> tests/ui-internal/unnecessary_def_path.rs:59:13
+error: a diagnostic name exists for this path: sym::vec_new
+  --> tests/ui-internal/unnecessary_def_path.rs:16:30
    |
-LL |     let _ = match_def_path(cx, did, &["alloc", "boxed", "Box"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.lang_items().get(LangItem::OwnedBox) == Some(did)`
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:61:13
+LL | static VEC_NEW: PathLookup = value_path!(alloc::vec::Vec::new);
+   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let _ = match_def_path(cx, did, &["core", "option", "Option"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.is_diagnostic_item(sym::Option, did)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils
 
-error: use of a def path to a `LangItem`
-  --> tests/ui-internal/unnecessary_def_path.rs:63:13
-   |
-LL |     let _ = match_def_path(cx, did, &["core", "option", "Option", "Some"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.lang_items().get(LangItem::OptionSome) == Some(did)`
+error: a diagnostic name exists for this path: sym::vec_macro
+  --> tests/ui-internal/unnecessary_def_path.rs:19:32
    |
-   = help: if this `DefId` came from a constructor expression or pattern then the parent `DefId` should be used instead
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:66:13
-   |
-LL |     let _ = match_trait_method(cx, expr, &["core", "convert", "AsRef"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_trait_method(cx, expr, sym::AsRef)`
-
-error: use of a def path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path.rs:69:13
-   |
-LL |     let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_path_diagnostic_item(cx, expr, sym::Option)`
-
-error: use of a def path to a `LangItem`
-  --> tests/ui-internal/unnecessary_def_path.rs:71:13
-   |
-LL |     let _ = is_expr_path_def_path(cx, expr, &["core", "iter", "traits", "Iterator", "next"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `path_res(cx, expr).opt_def_id().map_or(false, |id| cx.tcx.lang_items().get(LangItem::IteratorNext) == Some(id))`
-
-error: use of a def path to a `LangItem`
-  --> tests/ui-internal/unnecessary_def_path.rs:73:13
+LL | static VEC_MACRO: PathLookup = macro_path!(std::vec);
+   |                                ^^^^^^^^^^^^^^^^^^^^^
    |
-LL |     let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option", "Some"]);
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_res_lang_ctor(cx, path_res(cx, expr), LangItem::OptionSome)`
+   = help: remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead
+   = help: see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils
 
-error: aborting due to 15 previous errors
+error: aborting due to 6 previous errors
 
diff --git a/tests/ui-internal/unnecessary_def_path_hardcoded_path.rs b/tests/ui-internal/unnecessary_def_path_hardcoded_path.rs
deleted file mode 100644
index bd7a55114acb..000000000000
--- a/tests/ui-internal/unnecessary_def_path_hardcoded_path.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![feature(rustc_private)]
-#![allow(unused)]
-#![deny(clippy::unnecessary_def_path)]
-
-extern crate rustc_hir;
-
-use rustc_hir::LangItem;
-
-fn main() {
-    const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"];
-    //~^ unnecessary_def_path
-    const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
-    //~^ unnecessary_def_path
-    const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
-    //~^ unnecessary_def_path
-
-    // Don't lint, not a diagnostic or language item
-    const OPS_MOD: [&str; 2] = ["core", "ops"];
-}
diff --git a/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr b/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr
deleted file mode 100644
index c49abc516f56..000000000000
--- a/tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr
+++ /dev/null
@@ -1,31 +0,0 @@
-error: hardcoded path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:10:36
-   |
-LL |     const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"];
-   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: convert all references to use `sym::Deref`
-note: the lint level is defined here
-  --> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:3:9
-   |
-LL | #![deny(clippy::unnecessary_def_path)]
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: hardcoded path to a language item
-  --> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:12:40
-   |
-LL |     const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
-   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: convert all references to use `LangItem::DerefMut`
-
-error: hardcoded path to a diagnostic item
-  --> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:14:43
-   |
-LL |     const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
-   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: convert all references to use `sym::deref_method`
-
-error: aborting due to 3 previous errors
-
diff --git a/tests/ui-toml/toml_disallowed_types/clippy.toml b/tests/ui-toml/toml_disallowed_types/clippy.toml
index 6cb9e2ef9546..08e35017f782 100644
--- a/tests/ui-toml/toml_disallowed_types/clippy.toml
+++ b/tests/ui-toml/toml_disallowed_types/clippy.toml
@@ -6,7 +6,7 @@ disallowed-types = [
     "std::thread::Thread",
     "std::time::Instant",
     "std::io::Read",
-    "std::primitive::usize",
+    "usize",
     "bool",
     # can give path and reason with an inline table
     { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
diff --git a/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.stderr b/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.stderr
index 18bc36ca1e33..061cdc7649ad 100644
--- a/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.stderr
+++ b/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.stderr
@@ -37,7 +37,7 @@ error: use of a disallowed type `std::io::Read`
 LL | fn trait_obj(_: &dyn std::io::Read) {}
    |                      ^^^^^^^^^^^^^
 
-error: use of a disallowed type `std::primitive::usize`
+error: use of a disallowed type `usize`
   --> tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs:26:33
    |
 LL | fn full_and_single_path_prim(_: usize, _: bool) {}
@@ -49,13 +49,13 @@ error: use of a disallowed type `bool`
 LL | fn full_and_single_path_prim(_: usize, _: bool) {}
    |                                           ^^^^
 
-error: use of a disallowed type `std::primitive::usize`
+error: use of a disallowed type `usize`
   --> tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs:30:28
    |
 LL | fn const_generics<const C: usize>() {}
    |                            ^^^^^
 
-error: use of a disallowed type `std::primitive::usize`
+error: use of a disallowed type `usize`
   --> tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs:33:24
    |
 LL | struct GenArg<const U: usize>([u8; U]);
@@ -123,7 +123,7 @@ error: use of a disallowed type `proc_macro2::Ident`
 LL |     let _ = syn::Ident::new("", todo!());
    |             ^^^^^^^^^^
 
-error: use of a disallowed type `std::primitive::usize`
+error: use of a disallowed type `usize`
   --> tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs:61:12
    |
 LL |     let _: usize = 64_usize;
diff --git a/tests/ui-toml/toml_invalid_path/clippy.toml b/tests/ui-toml/toml_invalid_path/clippy.toml
index 6d0d732a9223..997ed47b71cb 100644
--- a/tests/ui-toml/toml_invalid_path/clippy.toml
+++ b/tests/ui-toml/toml_invalid_path/clippy.toml
@@ -1,12 +1,15 @@
-[[disallowed-types]]
-path = "std::result::Result::Err"
-
 [[disallowed-macros]]
 path = "bool"
 
 [[disallowed-methods]]
 path = "std::process::current_exe"
 
+[[disallowed-methods]]
+path = ""
+
+[[disallowed-types]]
+path = "std::result::Result::Err"
+
 # negative test
 
 [[disallowed-methods]]
diff --git a/tests/ui-toml/toml_invalid_path/conf_invalid_path.rs b/tests/ui-toml/toml_invalid_path/conf_invalid_path.rs
index c15203827034..ff4eada39007 100644
--- a/tests/ui-toml/toml_invalid_path/conf_invalid_path.rs
+++ b/tests/ui-toml/toml_invalid_path/conf_invalid_path.rs
@@ -1,5 +1,6 @@
 //@error-in-other-file: expected a macro, found a primitive type
-//@error-in-other-file: `std::process::current_exe` does not refer to an existing function
-//@error-in-other-file: expected a type, found a tuple variant
+//@error-in-other-file: `std::process::current_exe` does not refer to a reachable function
+//@error-in-other-file: `` does not refer to a reachable function
+//@error-in-other-file: expected a type, found a variant
 
 fn main() {}
diff --git a/tests/ui-toml/toml_invalid_path/conf_invalid_path.stderr b/tests/ui-toml/toml_invalid_path/conf_invalid_path.stderr
index 82550108eba5..59a427dc99ca 100644
--- a/tests/ui-toml/toml_invalid_path/conf_invalid_path.stderr
+++ b/tests/ui-toml/toml_invalid_path/conf_invalid_path.stderr
@@ -1,23 +1,38 @@
 warning: expected a macro, found a primitive type
-  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:4:1
+  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:1:1
    |
 LL | / [[disallowed-macros]]
 LL | | path = "bool"
    | |_____________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
 
-warning: `std::process::current_exe` does not refer to an existing function
-  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:7:1
+warning: `std::process::current_exe` does not refer to a reachable function
+  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:4:1
    |
 LL | / [[disallowed-methods]]
 LL | | path = "std::process::current_exe"
    | |__________________________________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
 
-warning: expected a type, found a tuple variant
-  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:1:1
+warning: `` does not refer to a reachable function
+  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:7:1
+   |
+LL | / [[disallowed-methods]]
+LL | | path = ""
+   | |_________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
+
+warning: expected a type, found a variant
+  --> $DIR/tests/ui-toml/toml_invalid_path/clippy.toml:10:1
    |
 LL | / [[disallowed-types]]
 LL | | path = "std::result::Result::Err"
    | |_________________________________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
 
-warning: 3 warnings emitted
+warning: 4 warnings emitted
 
diff --git a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs
index 2dbc5eca32ec..14f15e733111 100644
--- a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs
+++ b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs
@@ -1,5 +1,5 @@
-//@error-in-other-file: `regex::Regex::new_` does not refer to an existing function
-//@error-in-other-file: `regex::Regex_::new` does not refer to an existing function
+//@error-in-other-file: `regex::Regex::new_` does not refer to a reachable function
+//@error-in-other-file: `regex::Regex_::new` does not refer to a reachable function
 
 extern crate regex;
 
diff --git a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr
index 5d28e5fa970e..e5fd548b26df 100644
--- a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr
+++ b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr
@@ -1,16 +1,20 @@
-warning: `regex::Regex::new_` does not refer to an existing function
+warning: `regex::Regex::new_` does not refer to a reachable function
   --> $DIR/tests/ui-toml/toml_unloaded_crate/clippy.toml:3:1
    |
 LL | / [[disallowed-methods]]
 LL | | path = "regex::Regex::new_"
    | |___________________________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
 
-warning: `regex::Regex_::new` does not refer to an existing function
+warning: `regex::Regex_::new` does not refer to a reachable function
   --> $DIR/tests/ui-toml/toml_unloaded_crate/clippy.toml:6:1
    |
 LL | / [[disallowed-methods]]
 LL | | path = "regex::Regex_::new"
    | |___________________________^
+   |
+   = help: add `allow-invalid = true` to the entry to suppress this warning
 
 warning: 2 warnings emitted
 
diff --git a/tests/ui/author.stdout b/tests/ui/author.stdout
index eed704e82fe1..88a275302387 100644
--- a/tests/ui/author.stdout
+++ b/tests/ui/author.stdout
@@ -1,8 +1,6 @@
 if let StmtKind::Let(local) = stmt.kind
     && let Some(init) = local.init
     && let ExprKind::Cast(expr, cast_ty) = init.kind
-    && let TyKind::Path(ref qpath) = cast_ty.kind
-    && match_qpath(qpath, &["char"])
     && let ExprKind::Lit(ref lit) = expr.kind
     && let LitKind::Int(69, LitIntType::Unsuffixed) = lit.node
     && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind
diff --git a/tests/ui/author/blocks.stdout b/tests/ui/author/blocks.stdout
index 54325f9776c5..e453299edbcf 100644
--- a/tests/ui/author/blocks.stdout
+++ b/tests/ui/author/blocks.stdout
@@ -14,8 +14,6 @@ if let ExprKind::Block(block, None) = expr.kind
     && name1.as_str() == "_t"
     && let StmtKind::Semi(e) = block.stmts[2].kind
     && let ExprKind::Unary(UnOp::Neg, inner) = e.kind
-    && let ExprKind::Path(ref qpath) = inner.kind
-    && match_qpath(qpath, &["x"])
     && block.expr.is_none()
 {
     // report your lint here
@@ -25,18 +23,14 @@ if let ExprKind::Block(block, None) = expr.kind
     && let StmtKind::Let(local) = block.stmts[0].kind
     && let Some(init) = local.init
     && let ExprKind::Call(func, args) = init.kind
-    && let ExprKind::Path(ref qpath) = func.kind
-    && match_qpath(qpath, &["String", "new"])
+    && is_path_diagnostic_item(cx, func, sym::string_new)
     && args.is_empty()
     && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind
     && name.as_str() == "expr"
     && let Some(trailing_expr) = block.expr
     && let ExprKind::Call(func1, args1) = trailing_expr.kind
-    && let ExprKind::Path(ref qpath1) = func1.kind
-    && match_qpath(qpath1, &["drop"])
+    && is_path_diagnostic_item(cx, func1, sym::mem_drop)
     && args1.len() == 1
-    && let ExprKind::Path(ref qpath2) = args1[0].kind
-    && match_qpath(qpath2, &["expr"])
 {
     // report your lint here
 }
diff --git a/tests/ui/author/call.stdout b/tests/ui/author/call.stdout
index 59d4da490fe5..2b179d45112e 100644
--- a/tests/ui/author/call.stdout
+++ b/tests/ui/author/call.stdout
@@ -1,8 +1,7 @@
 if let StmtKind::Let(local) = stmt.kind
     && let Some(init) = local.init
     && let ExprKind::Call(func, args) = init.kind
-    && let ExprKind::Path(ref qpath) = func.kind
-    && match_qpath(qpath, &["{{root}}", "std", "cmp", "min"])
+    && is_path_diagnostic_item(cx, func, sym::cmp_min)
     && args.len() == 2
     && let ExprKind::Lit(ref lit) = args[0].kind
     && let LitKind::Int(3, LitIntType::Unsuffixed) = lit.node
diff --git a/tests/ui/author/if.stdout b/tests/ui/author/if.stdout
index 8ffdf8862027..da359866bffc 100644
--- a/tests/ui/author/if.stdout
+++ b/tests/ui/author/if.stdout
@@ -31,10 +31,8 @@ if let StmtKind::Let(local) = stmt.kind
 if let ExprKind::If(cond, then, Some(else_expr)) = expr.kind
     && let ExprKind::Let(let_expr) = cond.kind
     && let PatKind::Expr(lit_expr) = let_expr.pat.kind
-    && let PatExprKind::Lit{ref lit, negated } = lit_expr.kind
+    && let PatExprKind::Lit { ref lit, negated } = lit_expr.kind
     && let LitKind::Bool(true) = lit.node
-    && let ExprKind::Path(ref qpath) = let_expr.init.kind
-    && match_qpath(qpath, &["a"])
     && let ExprKind::Block(block, None) = then.kind
     && block.stmts.is_empty()
     && block.expr.is_none()
diff --git a/tests/ui/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout
index a5a8c0304ee4..f02ea5bf075f 100644
--- a/tests/ui/author/issue_3849.stdout
+++ b/tests/ui/author/issue_3849.stdout
@@ -1,11 +1,8 @@
 if let StmtKind::Let(local) = stmt.kind
     && let Some(init) = local.init
     && let ExprKind::Call(func, args) = init.kind
-    && let ExprKind::Path(ref qpath) = func.kind
-    && match_qpath(qpath, &["std", "mem", "transmute"])
+    && is_path_diagnostic_item(cx, func, sym::transmute)
     && args.len() == 1
-    && let ExprKind::Path(ref qpath1) = args[0].kind
-    && match_qpath(qpath1, &["ZPTR"])
     && let PatKind::Wild = local.pat.kind
 {
     // report your lint here
diff --git a/tests/ui/author/loop.stdout b/tests/ui/author/loop.stdout
index c94eb171f52b..79794cec9269 100644
--- a/tests/ui/author/loop.stdout
+++ b/tests/ui/author/loop.stdout
@@ -14,8 +14,6 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo
     && block.stmts.len() == 1
     && let StmtKind::Let(local) = block.stmts[0].kind
     && let Some(init) = local.init
-    && let ExprKind::Path(ref qpath1) = init.kind
-    && match_qpath(qpath1, &["y"])
     && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind
     && name1.as_str() == "z"
     && block.expr.is_none()
@@ -64,8 +62,6 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo
     // report your lint here
 }
 if let Some(higher::While { condition: condition, body: body }) = higher::While::hir(expr)
-    && let ExprKind::Path(ref qpath) = condition.kind
-    && match_qpath(qpath, &["a"])
     && let ExprKind::Block(block, None) = body.kind
     && block.stmts.len() == 1
     && let StmtKind::Semi(e) = block.stmts[0].kind
@@ -77,10 +73,8 @@ if let Some(higher::While { condition: condition, body: body }) = higher::While:
 }
 if let Some(higher::WhileLet { let_pat: let_pat, let_expr: let_expr, if_then: if_then }) = higher::WhileLet::hir(expr)
     && let PatKind::Expr(lit_expr) = let_pat.kind
-    && let PatExprKind::Lit{ref lit, negated } = lit_expr.kind
+    && let PatExprKind::Lit { ref lit, negated } = lit_expr.kind
     && let LitKind::Bool(true) = lit.node
-    && let ExprKind::Path(ref qpath) = let_expr.kind
-    && match_qpath(qpath, &["a"])
     && let ExprKind::Block(block, None) = if_then.kind
     && block.stmts.len() == 1
     && let StmtKind::Semi(e) = block.stmts[0].kind
diff --git a/tests/ui/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout
index 3186d0cbc276..5b347aef14fa 100644
--- a/tests/ui/author/macro_in_closure.stdout
+++ b/tests/ui/author/macro_in_closure.stdout
@@ -7,12 +7,10 @@ if let StmtKind::Let(local) = stmt.kind
     && block.stmts.len() == 1
     && let StmtKind::Semi(e) = block.stmts[0].kind
     && let ExprKind::Call(func, args) = e.kind
-    && let ExprKind::Path(ref qpath) = func.kind
-    && match_qpath(qpath, &["$crate", "io", "_print"])
+    && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed
     && args.len() == 1
     && let ExprKind::Call(func1, args1) = args[0].kind
-    && let ExprKind::Path(ref qpath1) = func1.kind
-    && match_qpath(qpath1, &["format_arguments", "new_v1"])
+    && paths::CORE_FMT_ARGUMENTS_NEW_V1.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed
     && args1.len() == 2
     && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind
     && let ExprKind::Array(elements) = inner.kind
@@ -27,12 +25,9 @@ if let StmtKind::Let(local) = stmt.kind
     && let ExprKind::Array(elements1) = inner1.kind
     && elements1.len() == 1
     && let ExprKind::Call(func2, args2) = elements1[0].kind
-    && let ExprKind::Path(ref qpath2) = func2.kind
-    && match_qpath(qpath2, &["format_argument", "new_display"])
+    && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed
     && args2.len() == 1
     && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind
-    && let ExprKind::Path(ref qpath3) = inner2.kind
-    && match_qpath(qpath3, &["x"])
     && block.expr.is_none()
     && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind
     && name.as_str() == "print_text"
diff --git a/tests/ui/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout
index 3f9be297c33c..75dabd57bfea 100644
--- a/tests/ui/author/macro_in_loop.stdout
+++ b/tests/ui/author/macro_in_loop.stdout
@@ -17,12 +17,10 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo
     && block1.stmts.len() == 1
     && let StmtKind::Semi(e1) = block1.stmts[0].kind
     && let ExprKind::Call(func, args) = e1.kind
-    && let ExprKind::Path(ref qpath1) = func.kind
-    && match_qpath(qpath1, &["$crate", "io", "_print"])
+    && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed
     && args.len() == 1
     && let ExprKind::Call(func1, args1) = args[0].kind
-    && let ExprKind::Path(ref qpath2) = func1.kind
-    && match_qpath(qpath2, &["format_arguments", "new_v1"])
+    && paths::CORE_FMT_ARGUMENTS_NEW_V1.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed
     && args1.len() == 2
     && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind
     && let ExprKind::Array(elements) = inner.kind
@@ -37,12 +35,9 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo
     && let ExprKind::Array(elements1) = inner1.kind
     && elements1.len() == 1
     && let ExprKind::Call(func2, args2) = elements1[0].kind
-    && let ExprKind::Path(ref qpath3) = func2.kind
-    && match_qpath(qpath3, &["format_argument", "new_display"])
+    && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed
     && args2.len() == 1
     && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind
-    && let ExprKind::Path(ref qpath4) = inner2.kind
-    && match_qpath(qpath4, &["i"])
     && block1.expr.is_none()
     && block.expr.is_none()
 {
diff --git a/tests/ui/author/matches.stdout b/tests/ui/author/matches.stdout
index acb3b140dfa1..9752d7a9f99d 100644
--- a/tests/ui/author/matches.stdout
+++ b/tests/ui/author/matches.stdout
@@ -5,13 +5,13 @@ if let StmtKind::Let(local) = stmt.kind
     && let LitKind::Int(42, LitIntType::Unsuffixed) = lit.node
     && arms.len() == 3
     && let PatKind::Expr(lit_expr) = arms[0].pat.kind
-    && let PatExprKind::Lit{ref lit1, negated } = lit_expr.kind
+    && let PatExprKind::Lit { ref lit1, negated } = lit_expr.kind
     && let LitKind::Int(16, LitIntType::Unsuffixed) = lit1.node
     && arms[0].guard.is_none()
     && let ExprKind::Lit(ref lit2) = arms[0].body.kind
     && let LitKind::Int(5, LitIntType::Unsuffixed) = lit2.node
     && let PatKind::Expr(lit_expr1) = arms[1].pat.kind
-    && let PatExprKind::Lit{ref lit3, negated1 } = lit_expr1.kind
+    && let PatExprKind::Lit { ref lit3, negated1 } = lit_expr1.kind
     && let LitKind::Int(17, LitIntType::Unsuffixed) = lit3.node
     && arms[1].guard.is_none()
     && let ExprKind::Block(block, None) = arms[1].body.kind
@@ -23,8 +23,6 @@ if let StmtKind::Let(local) = stmt.kind
     && let PatKind::Binding(BindingMode::NONE, _, name, None) = local1.pat.kind
     && name.as_str() == "x"
     && let Some(trailing_expr) = block.expr
-    && let ExprKind::Path(ref qpath) = trailing_expr.kind
-    && match_qpath(qpath, &["x"])
     && let PatKind::Wild = arms[2].pat.kind
     && arms[2].guard.is_none()
     && let ExprKind::Lit(ref lit5) = arms[2].body.kind
diff --git a/tests/ui/author/struct.stdout b/tests/ui/author/struct.stdout
index b66bbccb3cf1..1e8fbafd30c5 100644
--- a/tests/ui/author/struct.stdout
+++ b/tests/ui/author/struct.stdout
@@ -1,5 +1,4 @@
 if let ExprKind::Struct(qpath, fields, None) = expr.kind
-    && match_qpath(qpath, &["Test"])
     && fields.len() == 1
     && fields[0].ident.as_str() == "field"
     && let ExprKind::If(cond, then, Some(else_expr)) = fields[0].expr.kind
@@ -20,11 +19,10 @@ if let ExprKind::Struct(qpath, fields, None) = expr.kind
     // report your lint here
 }
 if let PatKind::Struct(ref qpath, fields, false) = arm.pat.kind
-    && match_qpath(qpath, &["Test"])
     && fields.len() == 1
     && fields[0].ident.as_str() == "field"
     && let PatKind::Expr(lit_expr) = fields[0].pat.kind
-    && let PatExprKind::Lit{ref lit, negated } = lit_expr.kind
+    && let PatExprKind::Lit { ref lit, negated } = lit_expr.kind
     && let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
     && arm.guard.is_none()
     && let ExprKind::Block(block, None) = arm.body.kind
@@ -34,10 +32,9 @@ if let PatKind::Struct(ref qpath, fields, false) = arm.pat.kind
     // report your lint here
 }
 if let PatKind::TupleStruct(ref qpath, fields, None) = arm.pat.kind
-    && match_qpath(qpath, &["TestTuple"])
     && fields.len() == 1
     && let PatKind::Expr(lit_expr) = fields[0].kind
-    && let PatExprKind::Lit{ref lit, negated } = lit_expr.kind
+    && let PatExprKind::Lit { ref lit, negated } = lit_expr.kind
     && let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
     && arm.guard.is_none()
     && let ExprKind::Block(block, None) = arm.body.kind
@@ -48,8 +45,6 @@ if let PatKind::TupleStruct(ref qpath, fields, None) = arm.pat.kind
 }
 if let ExprKind::MethodCall(method_name, receiver, args, _) = expr.kind
     && method_name.ident.as_str() == "test"
-    && let ExprKind::Path(ref qpath) = receiver.kind
-    && match_qpath(qpath, &["test_method_call"])
     && args.is_empty()
 {
     // report your lint here
diff --git a/tests/ui/manual_saturating_arithmetic.fixed b/tests/ui/manual_saturating_arithmetic.fixed
index 3f73d6e5a1a6..304be05f6c4c 100644
--- a/tests/ui/manual_saturating_arithmetic.fixed
+++ b/tests/ui/manual_saturating_arithmetic.fixed
@@ -1,7 +1,5 @@
 #![allow(clippy::legacy_numeric_constants, unused_imports)]
 
-use std::{i32, i128, u32, u128};
-
 fn main() {
     let _ = 1u32.saturating_add(1);
     //~^ manual_saturating_arithmetic
diff --git a/tests/ui/manual_saturating_arithmetic.rs b/tests/ui/manual_saturating_arithmetic.rs
index 98246a5cd96c..c2b570e974ac 100644
--- a/tests/ui/manual_saturating_arithmetic.rs
+++ b/tests/ui/manual_saturating_arithmetic.rs
@@ -1,7 +1,5 @@
 #![allow(clippy::legacy_numeric_constants, unused_imports)]
 
-use std::{i32, i128, u32, u128};
-
 fn main() {
     let _ = 1u32.checked_add(1).unwrap_or(u32::max_value());
     //~^ manual_saturating_arithmetic
diff --git a/tests/ui/manual_saturating_arithmetic.stderr b/tests/ui/manual_saturating_arithmetic.stderr
index 9d133d8a073b..2f006a3ae170 100644
--- a/tests/ui/manual_saturating_arithmetic.stderr
+++ b/tests/ui/manual_saturating_arithmetic.stderr
@@ -1,5 +1,5 @@
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:6:13
+  --> tests/ui/manual_saturating_arithmetic.rs:4:13
    |
 LL |     let _ = 1u32.checked_add(1).unwrap_or(u32::max_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u32.saturating_add(1)`
@@ -8,19 +8,19 @@ LL |     let _ = 1u32.checked_add(1).unwrap_or(u32::max_value());
    = help: to override `-D warnings` add `#[allow(clippy::manual_saturating_arithmetic)]`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:8:13
+  --> tests/ui/manual_saturating_arithmetic.rs:6:13
    |
 LL |     let _ = 1u32.checked_add(1).unwrap_or(u32::MAX);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u32.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:10:13
+  --> tests/ui/manual_saturating_arithmetic.rs:8:13
    |
 LL |     let _ = 1u8.checked_add(1).unwrap_or(255);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1u8.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:12:13
+  --> tests/ui/manual_saturating_arithmetic.rs:10:13
    |
 LL |       let _ = 1u128
    |  _____________^
@@ -30,49 +30,49 @@ LL | |         .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455);
    | |_______________________________________________________________________^ help: consider using `saturating_add`: `1u128.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:18:13
+  --> tests/ui/manual_saturating_arithmetic.rs:16:13
    |
 LL |     let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_mul`: `1u32.saturating_mul(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:21:13
+  --> tests/ui/manual_saturating_arithmetic.rs:19:13
    |
 LL |     let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u32.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:23:13
+  --> tests/ui/manual_saturating_arithmetic.rs:21:13
    |
 LL |     let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u32.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:25:13
+  --> tests/ui/manual_saturating_arithmetic.rs:23:13
    |
 LL |     let _ = 1u8.checked_sub(1).unwrap_or(0);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1u8.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:30:13
+  --> tests/ui/manual_saturating_arithmetic.rs:28:13
    |
 LL |     let _ = 1i32.checked_add(1).unwrap_or(i32::max_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:32:13
+  --> tests/ui/manual_saturating_arithmetic.rs:30:13
    |
 LL |     let _ = 1i32.checked_add(1).unwrap_or(i32::MAX);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:34:13
+  --> tests/ui/manual_saturating_arithmetic.rs:32:13
    |
 LL |     let _ = 1i8.checked_add(1).unwrap_or(127);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i8.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:36:13
+  --> tests/ui/manual_saturating_arithmetic.rs:34:13
    |
 LL |       let _ = 1i128
    |  _____________^
@@ -82,25 +82,25 @@ LL | |         .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
    | |_______________________________________________________________________^ help: consider using `saturating_add`: `1i128.saturating_add(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:40:13
+  --> tests/ui/manual_saturating_arithmetic.rs:38:13
    |
 LL |     let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:42:13
+  --> tests/ui/manual_saturating_arithmetic.rs:40:13
    |
 LL |     let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i32.saturating_add(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:44:13
+  --> tests/ui/manual_saturating_arithmetic.rs:42:13
    |
 LL |     let _ = 1i8.checked_add(-1).unwrap_or(-128);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_add`: `1i8.saturating_add(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:46:13
+  --> tests/ui/manual_saturating_arithmetic.rs:44:13
    |
 LL |       let _ = 1i128
    |  _____________^
@@ -110,25 +110,25 @@ LL | |         .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
    | |________________________________________________________________________^ help: consider using `saturating_add`: `1i128.saturating_add(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:54:13
+  --> tests/ui/manual_saturating_arithmetic.rs:52:13
    |
 LL |     let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:56:13
+  --> tests/ui/manual_saturating_arithmetic.rs:54:13
    |
 LL |     let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:58:13
+  --> tests/ui/manual_saturating_arithmetic.rs:56:13
    |
 LL |     let _ = 1i8.checked_sub(1).unwrap_or(-128);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i8.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:60:13
+  --> tests/ui/manual_saturating_arithmetic.rs:58:13
    |
 LL |       let _ = 1i128
    |  _____________^
@@ -138,25 +138,25 @@ LL | |         .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728);
    | |________________________________________________________________________^ help: consider using `saturating_sub`: `1i128.saturating_sub(1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:64:13
+  --> tests/ui/manual_saturating_arithmetic.rs:62:13
    |
 LL |     let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:66:13
+  --> tests/ui/manual_saturating_arithmetic.rs:64:13
    |
 LL |     let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i32.saturating_sub(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:68:13
+  --> tests/ui/manual_saturating_arithmetic.rs:66:13
    |
 LL |     let _ = 1i8.checked_sub(-1).unwrap_or(127);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `1i8.saturating_sub(-1)`
 
 error: manual saturating arithmetic
-  --> tests/ui/manual_saturating_arithmetic.rs:70:13
+  --> tests/ui/manual_saturating_arithmetic.rs:68:13
    |
 LL |       let _ = 1i128
    |  _____________^