about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/clippy_changelog.yml17
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--book/src/development/common_tools_writing_lints.md4
-rw-r--r--book/src/development/trait_checking.md16
-rw-r--r--clippy.toml3
-rw-r--r--clippy_config/src/lib.rs2
-rw-r--r--clippy_config/src/types.rs92
-rw-r--r--clippy_dev/src/lib.rs1
-rw-r--r--clippy_lints/src/await_holding_invalid.rs13
-rw-r--r--clippy_lints/src/casts/manual_dangling_ptr.rs4
-rw-r--r--clippy_lints/src/collapsible_if.rs32
-rw-r--r--clippy_lints/src/derive.rs4
-rw-r--r--clippy_lints/src/disallowed_macros.rs2
-rw-r--r--clippy_lints/src/disallowed_methods.rs2
-rw-r--r--clippy_lints/src/disallowed_types.rs10
-rw-r--r--clippy_lints/src/functions/mod.rs4
-rw-r--r--clippy_lints/src/let_underscore.rs12
-rw-r--r--clippy_lints/src/lib.rs4
-rw-r--r--clippy_lints/src/manual_option_as_slice.rs4
-rw-r--r--clippy_lints/src/matches/manual_unwrap_or.rs3
-rw-r--r--clippy_lints/src/methods/io_other_error.rs9
-rw-r--r--clippy_lints/src/methods/manual_saturating_arithmetic.rs20
-rw-r--r--clippy_lints/src/methods/needless_character_iteration.rs7
-rw-r--r--clippy_lints/src/methods/open_options.rs20
-rw-r--r--clippy_lints/src/methods/str_splitn.rs4
-rw-r--r--clippy_lints/src/methods/useless_asref.rs9
-rw-r--r--clippy_lints/src/missing_enforced_import_rename.rs10
-rw-r--r--clippy_lints/src/non_std_lazy_statics.rs41
-rw-r--r--clippy_lints/src/regex.rs17
-rw-r--r--clippy_lints/src/serde_api.rs6
-rw-r--r--clippy_lints/src/single_range_in_vec_init.rs4
-rw-r--r--clippy_lints/src/to_digit_is_some.rs20
-rw-r--r--clippy_lints/src/unused_async.rs18
-rw-r--r--clippy_lints/src/unused_io_amount.rs41
-rw-r--r--clippy_lints/src/utils/author.rs130
-rw-r--r--clippy_lints_internal/Cargo.toml2
-rw-r--r--clippy_lints_internal/src/collapsible_calls.rs6
-rw-r--r--clippy_lints_internal/src/internal_paths.rs17
-rw-r--r--clippy_lints_internal/src/invalid_paths.rs108
-rw-r--r--clippy_lints_internal/src/lib.rs8
-rw-r--r--clippy_lints_internal/src/lint_without_lint_pass.rs9
-rw-r--r--clippy_lints_internal/src/msrv_attr_impl.rs7
-rw-r--r--clippy_lints_internal/src/outer_expn_data_pass.rs6
-rw-r--r--clippy_lints_internal/src/symbols.rs15
-rw-r--r--clippy_lints_internal/src/unnecessary_def_path.rs317
-rw-r--r--clippy_utils/src/lib.rs311
-rw-r--r--clippy_utils/src/msrvs.rs1
-rw-r--r--clippy_utils/src/paths.rs380
-rw-r--r--clippy_utils/src/sym.rs56
-rw-r--r--clippy_utils/src/ty/mod.rs23
-rw-r--r--clippy_utils/src/ty/type_certainty/mod.rs23
-rw-r--r--lintcheck/src/main.rs1
-rw-r--r--src/driver.rs1
-rw-r--r--tests/compile-test.rs2
-rw-r--r--tests/ui-internal/auxiliary/paths.rs4
-rw-r--r--tests/ui-internal/invalid_paths.rs30
-rw-r--r--tests/ui-internal/invalid_paths.stderr26
-rw-r--r--tests/ui-internal/unnecessary_def_path.fixed77
-rw-r--r--tests/ui-internal/unnecessary_def_path.rs85
-rw-r--r--tests/ui-internal/unnecessary_def_path.stderr120
-rw-r--r--tests/ui-internal/unnecessary_def_path_hardcoded_path.rs19
-rw-r--r--tests/ui-internal/unnecessary_def_path_hardcoded_path.stderr31
-rw-r--r--tests/ui-toml/collapsible_if/collapsible_if_let_chains.fixed1
-rw-r--r--tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs1
-rw-r--r--tests/ui-toml/collapsible_if/collapsible_if_let_chains.stderr6
-rw-r--r--tests/ui-toml/toml_disallowed_types/clippy.toml2
-rw-r--r--tests/ui-toml/toml_disallowed_types/conf_disallowed_types.stderr8
-rw-r--r--tests/ui-toml/toml_invalid_path/clippy.toml9
-rw-r--r--tests/ui-toml/toml_invalid_path/conf_invalid_path.rs5
-rw-r--r--tests/ui-toml/toml_invalid_path/conf_invalid_path.stderr27
-rw-r--r--tests/ui-toml/toml_unloaded_crate/clippy.toml10
-rw-r--r--tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs6
-rw-r--r--tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr20
-rw-r--r--tests/ui/author.stdout2
-rw-r--r--tests/ui/author/blocks.stdout10
-rw-r--r--tests/ui/author/call.stdout3
-rw-r--r--tests/ui/author/if.stdout4
-rw-r--r--tests/ui/author/issue_3849.stdout5
-rw-r--r--tests/ui/author/loop.stdout8
-rw-r--r--tests/ui/author/macro_in_closure.stdout11
-rw-r--r--tests/ui/author/macro_in_loop.stdout11
-rw-r--r--tests/ui/author/matches.stdout6
-rw-r--r--tests/ui/author/struct.stdout9
-rw-r--r--tests/ui/auxiliary/proc_macro_attr.rs79
-rw-r--r--tests/ui/auxiliary/proc_macros.rs1
-rw-r--r--tests/ui/bool_to_int_with_if.fixed1
-rw-r--r--tests/ui/bool_to_int_with_if.rs1
-rw-r--r--tests/ui/bool_to_int_with_if.stderr22
-rw-r--r--tests/ui/collapsible_if.fixed30
-rw-r--r--tests/ui/collapsible_if.rs30
-rw-r--r--tests/ui/collapsible_if.stderr8
-rw-r--r--tests/ui/collapsible_if_let_chains.edition2024.fixed68
-rw-r--r--tests/ui/collapsible_if_let_chains.edition2024.stderr132
-rw-r--r--tests/ui/collapsible_if_let_chains.fixed29
-rw-r--r--tests/ui/collapsible_if_let_chains.rs52
-rw-r--r--tests/ui/collapsible_if_let_chains.stderr58
-rw-r--r--tests/ui/collapsible_match.rs1
-rw-r--r--tests/ui/collapsible_match.stderr52
-rw-r--r--tests/ui/comparison_to_empty.fixed1
-rw-r--r--tests/ui/comparison_to_empty.rs1
-rw-r--r--tests/ui/comparison_to_empty.stderr26
-rw-r--r--tests/ui/index_refutable_slice/if_let_slice_binding.fixed2
-rw-r--r--tests/ui/index_refutable_slice/if_let_slice_binding.rs2
-rw-r--r--tests/ui/manual_saturating_arithmetic.fixed2
-rw-r--r--tests/ui/manual_saturating_arithmetic.rs2
-rw-r--r--tests/ui/manual_saturating_arithmetic.stderr48
-rw-r--r--tests/ui/manual_unwrap_or_default.fixed13
-rw-r--r--tests/ui/manual_unwrap_or_default.rs13
-rw-r--r--tests/ui/needless_if.fixed1
-rw-r--r--tests/ui/needless_if.rs1
-rw-r--r--tests/ui/needless_if.stderr14
-rw-r--r--tests/ui/needless_late_init.fixed2
-rw-r--r--tests/ui/needless_late_init.rs2
-rw-r--r--tests/ui/needless_late_init.stderr34
-rw-r--r--tests/ui/redundant_pattern_matching_option.fixed2
-rw-r--r--tests/ui/redundant_pattern_matching_option.rs2
-rw-r--r--tests/ui/unused_async.rs8
-rw-r--r--triagebot.toml1
118 files changed, 1390 insertions, 1787 deletions
diff --git a/.github/workflows/clippy_changelog.yml b/.github/workflows/clippy_changelog.yml
index 1e97154bf8a..4d84d6b6dae 100644
--- a/.github/workflows/clippy_changelog.yml
+++ b/.github/workflows/clippy_changelog.yml
@@ -15,27 +15,18 @@ jobs:
   changelog:
     runs-on: ubuntu-latest
 
-    defaults:
-      run:
-        shell: bash
-
     steps:
     # Run
     - name: Check Changelog
       if: ${{ github.event_name == 'pull_request' }}
       run: |
-        body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | \
-          python -c "import sys, json; print(json.load(sys.stdin)['body'])")
-        output=$(awk '/^changelog:\s*\S/ && !/changelog: \[.*\]: your change/' <<< "$body" | sed "s/changelog:\s*//g")
-        if [ -z "$output" ]; then
-          echo "ERROR: pull request message must contain 'changelog: ...' with your changelog. Please add it."
+        if [[ -z $(grep -oP 'changelog: *\K\S+' <<< "$PR_BODY") ]]; then
+          echo "::error::Pull request message must contain 'changelog: ...' with your changelog. Please add it."
           exit 1
-        else
-          echo "changelog: $output"
         fi
       env:
-        PYTHONIOENCODING: 'utf-8'
-        PR_NUMBER: '${{ github.event.number }}'
+        PR_BODY: ${{ github.event.pull_request.body }})
+
 
   # We need to have the "conclusion" job also on PR CI, to make it possible
   # to add PRs to a merge queue.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3b33f719063..45ba2f078be 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 2e39f279eae..e23b32039c9 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 b7d229ccc19..cc4eb966f59 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 0a7724bbe4e..77573105d86 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/lib.rs b/clippy_config/src/lib.rs
index c227b8900b7..33608591fc7 100644
--- a/clippy_config/src/lib.rs
+++ b/clippy_config/src/lib.rs
@@ -1,4 +1,4 @@
-#![feature(rustc_private, array_windows, let_chains)]
+#![feature(rustc_private)]
 #![warn(
     trivial_casts,
     trivial_numeric_casts,
diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs
index 5949eaca7bc..2cb5493f1a9 100644
--- a/clippy_config/src/types.rs
+++ b/clippy_config/src/types.rs
@@ -1,10 +1,11 @@
+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;
+use rustc_span::{Span, Symbol};
 use serde::de::{self, Deserializer, Visitor};
 use serde::{Deserialize, Serialize, ser};
 use std::collections::HashMap;
@@ -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,57 +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 mut resolutions = clippy_utils::def_path_res(tcx, &path.split("::").collect::<Vec<_>>());
-
-        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,
-        });
-
-        if resolutions.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)
-                    ),
-                );
+        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
+            && (sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty())
+        {
+            // 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_dev/src/lib.rs b/clippy_dev/src/lib.rs
index c1ffaf269c6..db4b4d07c15 100644
--- a/clippy_dev/src/lib.rs
+++ b/clippy_dev/src/lib.rs
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![feature(rustc_private)]
 #![warn(
     trivial_casts,
diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs
index 52d1d5b4c67..31cc004f685 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 6dbaa5cb507..61dfc0fc042 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/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs
index 20fae8a6775..7f6ecea99fb 100644
--- a/clippy_lints/src/collapsible_if.rs
+++ b/clippy_lints/src/collapsible_if.rs
@@ -1,11 +1,11 @@
 use clippy_config::Conf;
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::msrvs::{self, Msrv};
 use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
 use rustc_ast::BinOpKind;
 use rustc_errors::Applicability;
-use rustc_hir::{Block, Expr, ExprKind, StmtKind};
+use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::TyCtxt;
 use rustc_session::impl_lint_pass;
 use rustc_span::Span;
 
@@ -78,14 +78,14 @@ declare_clippy_lint! {
 }
 
 pub struct CollapsibleIf {
-    let_chains_enabled: bool,
+    msrv: Msrv,
     lint_commented_code: bool,
 }
 
 impl CollapsibleIf {
-    pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
+    pub fn new(conf: &'static Conf) -> Self {
         Self {
-            let_chains_enabled: tcx.features().let_chains(),
+            msrv: conf.msrv,
             lint_commented_code: conf.lint_commented_code,
         }
     }
@@ -127,7 +127,7 @@ impl CollapsibleIf {
         if let Some(inner) = expr_block(then)
             && cx.tcx.hir_attrs(inner.hir_id).is_empty()
             && let ExprKind::If(check_inner, _, None) = &inner.kind
-            && self.eligible_condition(check_inner)
+            && self.eligible_condition(cx, check_inner)
             && let ctxt = expr.span.ctxt()
             && inner.span.ctxt() == ctxt
             && (self.lint_commented_code || !block_starts_with_comment(cx, then))
@@ -163,8 +163,9 @@ impl CollapsibleIf {
         }
     }
 
-    pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
-        self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
+    fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool {
+        !matches!(cond.kind, ExprKind::Let(..))
+            || (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS))
     }
 }
 
@@ -180,7 +181,7 @@ impl LateLintPass<'_> for CollapsibleIf {
             {
                 Self::check_collapsible_else_if(cx, then.span, else_);
             } else if else_.is_none()
-                && self.eligible_condition(cond)
+                && self.eligible_condition(cx, cond)
                 && let ExprKind::Block(then, None) = then.kind
             {
                 self.check_collapsible_if_if(cx, expr, cond, then);
@@ -202,13 +203,12 @@ fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
 fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
     match block.stmts {
         [] => block.expr,
-        [stmt] => {
-            if let StmtKind::Semi(expr) = stmt.kind {
-                Some(expr)
-            } else {
-                None
-            }
-        },
+        [
+            Stmt {
+                kind: StmtKind::Semi(expr),
+                ..
+            },
+        ] if block.expr.is_none() => Some(expr),
         _ => None,
     }
 }
diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs
index 06528f875a2..3443b36eb4f 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 fa33fef2306..25b7099c855 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 1382dafa931..fb970e17f38 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 2bae82648ac..d0b2f0c8407 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 5f3fc5100e7..d0d02a382d1 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 bdbf5b37c5f..916191b2a7b 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/lib.rs b/clippy_lints/src/lib.rs
index bc7fc60827a..ad8b223b3aa 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -1,5 +1,4 @@
 #![feature(array_windows)]
-#![feature(binary_heap_into_iter_sorted)]
 #![feature(box_patterns)]
 #![feature(macro_metavar_expr_concat)]
 #![feature(f128)]
@@ -7,7 +6,6 @@
 #![feature(if_let_guard)]
 #![feature(iter_intersperse)]
 #![feature(iter_partition_in_place)]
-#![feature(let_chains)]
 #![feature(never_type)]
 #![feature(round_char_boundary)]
 #![feature(rustc_private)]
@@ -731,7 +729,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
     store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit));
     store.register_late_pass(|_| Box::new(returns::Return));
-    store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
+    store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf)));
     store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
     store.register_early_pass(|| Box::new(precedence::Precedence));
     store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs
index b365dbf088f..04e00f84103 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/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs
index b64ae0b24d8..3ac2c9fc2b3 100644
--- a/clippy_lints/src/matches/manual_unwrap_or.rs
+++ b/clippy_lints/src/matches/manual_unwrap_or.rs
@@ -1,5 +1,6 @@
 use clippy_utils::consts::ConstEvalCtxt;
 use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
+use rustc_ast::{BindingMode, ByRef};
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
 use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, QPath};
@@ -16,7 +17,7 @@ use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
 
 fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<HirId> {
     if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
-        && let PatKind::Binding(_, pat_id, _, _) = pat.kind
+        && let PatKind::Binding(BindingMode(ByRef::No, _), pat_id, _, _) = pat.kind
         && let Some(def_id) = path.res.opt_def_id()
         // Since it comes from a pattern binding, we need to get the parent to actually match
         // against it.
diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints/src/methods/io_other_error.rs
index bdc834bd47a..ec4b9c7ae2e 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 18978a1d2bc..e2df8ce1513 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 f528f7f065c..71c1576cd57 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 da084871402..bce314e64f0 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 d183457da25..c8efb600f57 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 0cbf6004be3..17e2620d9dd 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 66631a69206..a1e621cc9f6 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 f6bc9428d65..370ded99a4d 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 834ff2af0e8..89d945161f6 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 a8c6518b592..a64b9b22378 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 2d989b1cf0b..54d09ff9ee4 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 bb969bc802f..c8a6a41d6d8 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_async.rs b/clippy_lints/src/unused_async.rs
index 1c1c841e964..8ceaa3dc58e 100644
--- a/clippy_lints/src/unused_async.rs
+++ b/clippy_lints/src/unused_async.rs
@@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
 use clippy_utils::is_def_id_trait_method;
 use rustc_hir::def::DefKind;
 use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
-use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Node, YieldSource};
+use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::hir::nested_filter;
 use rustc_session::impl_lint_pass;
@@ -116,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
         span: Span,
         def_id: LocalDefId,
     ) {
-        if !span.from_expansion() && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) {
+        if !span.from_expansion()
+            && fn_kind.asyncness().is_async()
+            && !is_def_id_trait_method(cx, def_id)
+            && !is_default_trait_impl(cx, def_id)
+        {
             let mut visitor = AsyncFnVisitor {
                 cx,
                 found_await: false,
@@ -189,3 +193,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
         }
     }
 }
+
+fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
+    matches!(
+        cx.tcx.hir_node_by_def_id(def_id),
+        Node::TraitItem(TraitItem {
+            defaultness: Defaultness::Default { .. },
+            ..
+        })
+    )
+}
diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs
index 2d88c490b1a..e3f28908ff8 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 b7dcd2ffb0e..812c4df4ddd 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/Cargo.toml b/clippy_lints_internal/Cargo.toml
index 2a0ceac27a3..a8293a1ad39 100644
--- a/clippy_lints_internal/Cargo.toml
+++ b/clippy_lints_internal/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "clippy_lints_internal"
 version = "0.0.1"
-edition = "2021"
+edition = "2024"
 
 [dependencies]
 clippy_config = { path = "../clippy_config" }
diff --git a/clippy_lints_internal/src/collapsible_calls.rs b/clippy_lints_internal/src/collapsible_calls.rs
index d7967a0cc02..407deb45db0 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 00000000000..dc1e30ab2bd
--- /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 bee87efa3fc..00000000000
--- 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 b02d378619c..308d161b9d6 100644
--- a/clippy_lints_internal/src/lib.rs
+++ b/clippy_lints_internal/src/lib.rs
@@ -1,4 +1,4 @@
-#![feature(let_chains, rustc_private)]
+#![feature(rustc_private)]
 #![allow(
     clippy::missing_docs_in_private_items,
     clippy::must_use_candidate,
@@ -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 6a75defcce3..655d8fb8d1b 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 dda054546e2..d48d8dc57b2 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 e9441964797..40951443a48 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 c64e5821916..bf166988a0c 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 6bdfbed55b0..8877f1faf0e 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 187dfa4dda8..0a9c39c41bd 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -1,9 +1,6 @@
-#![feature(array_chunks)]
 #![feature(box_patterns)]
 #![feature(if_let_guard)]
-#![feature(macro_metavar_expr_concat)]
 #![feature(macro_metavar_expr)]
-#![feature(let_chains)]
 #![feature(never_type)]
 #![feature(rustc_private)]
 #![feature(assert_matches)]
@@ -97,28 +94,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 +128,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 +235,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 +343,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 +426,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 +442,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 +508,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 +1792,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/msrvs.rs b/clippy_utils/src/msrvs.rs
index 19061b574ff..71985cb4b09 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -22,6 +22,7 @@ macro_rules! msrv_aliases {
 
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
+    1,88,0 { LET_CHAINS }
     1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT }
     1,85,0 { UINT_FLOAT_MIDPOINT }
     1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 7f64ebd3b64..795fb502c9c 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 38f077134c0..94b73f37269 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 db723312695..2683892448d 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 3398ff8af2f..6e358662327 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/lintcheck/src/main.rs b/lintcheck/src/main.rs
index fe488ef89da..d4bf6cd48a1 100644
--- a/lintcheck/src/main.rs
+++ b/lintcheck/src/main.rs
@@ -6,7 +6,6 @@
 // positives.
 
 #![feature(iter_collect_into)]
-#![feature(let_chains)]
 #![warn(
     trivial_casts,
     trivial_numeric_casts,
diff --git a/src/driver.rs b/src/driver.rs
index 87ca9c5bedd..f8acf88cf81 100644
--- a/src/driver.rs
+++ b/src/driver.rs
@@ -1,7 +1,6 @@
 #![allow(rustc::diagnostic_outside_of_impl)]
 #![allow(rustc::untranslatable_diagnostic)]
 #![feature(rustc_private)]
-#![feature(let_chains)]
 // warn on lints, that are included in `rust-lang/rust`s bootstrap
 #![warn(rust_2018_idioms, unused_lifetimes)]
 // warn on rustc internal lints
diff --git a/tests/compile-test.rs b/tests/compile-test.rs
index 6d391bd622a..78b27e2f613 100644
--- a/tests/compile-test.rs
+++ b/tests/compile-test.rs
@@ -1,4 +1,4 @@
-#![feature(rustc_private, let_chains)]
+#![feature(rustc_private)]
 #![warn(rust_2018_idioms, unused_lifetimes)]
 #![allow(unused_extern_crates)]
 
diff --git a/tests/ui-internal/auxiliary/paths.rs b/tests/ui-internal/auxiliary/paths.rs
deleted file mode 100644
index f730f564a09..00000000000
--- 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 7317abc2185..00000000000
--- 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 7b7b25ce8d8..00000000000
--- 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 89902ebe4e5..00000000000
--- 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 cfca15267c1..5cd3254188d 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 d7fb4ea551e..4abb1be7406 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 bd7a55114ac..00000000000
--- 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 c49abc516f5..00000000000
--- 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/collapsible_if/collapsible_if_let_chains.fixed b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.fixed
index f12273954c6..5e189471b00 100644
--- a/tests/ui-toml/collapsible_if/collapsible_if_let_chains.fixed
+++ b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.fixed
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![warn(clippy::collapsible_if)]
 
 fn main() {
diff --git a/tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs
index 5a984d7a3cb..525eebf632a 100644
--- a/tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs
+++ b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![warn(clippy::collapsible_if)]
 
 fn main() {
diff --git a/tests/ui-toml/collapsible_if/collapsible_if_let_chains.stderr b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.stderr
index c22a65a4473..c9de166a969 100644
--- a/tests/ui-toml/collapsible_if/collapsible_if_let_chains.stderr
+++ b/tests/ui-toml/collapsible_if/collapsible_if_let_chains.stderr
@@ -1,5 +1,5 @@
 error: this `if` statement can be collapsed
-  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:5:5
+  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:4:5
    |
 LL | /     if let Some(a) = Some(3) {
 LL | |         // with comment
@@ -21,7 +21,7 @@ LL ~         }
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:13:5
+  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:12:5
    |
 LL | /     if let Some(a) = Some(3) {
 LL | |         // with comment
@@ -41,7 +41,7 @@ LL ~         }
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:21:5
+  --> tests/ui-toml/collapsible_if/collapsible_if_let_chains.rs:20:5
    |
 LL | /     if Some(3) == Some(4).map(|x| x - 1) {
 LL | |         // with comment
diff --git a/tests/ui-toml/toml_disallowed_types/clippy.toml b/tests/ui-toml/toml_disallowed_types/clippy.toml
index 6cb9e2ef954..08e35017f78 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 18bc36ca1e3..061cdc7649a 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 6d0d732a922..997ed47b71c 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 c1520382703..ff4eada3900 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 82550108eba..59a427dc99c 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/clippy.toml b/tests/ui-toml/toml_unloaded_crate/clippy.toml
new file mode 100644
index 00000000000..e664256d2a2
--- /dev/null
+++ b/tests/ui-toml/toml_unloaded_crate/clippy.toml
@@ -0,0 +1,10 @@
+# The first two `disallowed-methods` paths should generate warnings, but the third should not.
+
+[[disallowed-methods]]
+path = "regex::Regex::new_"
+
+[[disallowed-methods]]
+path = "regex::Regex_::new"
+
+[[disallowed-methods]]
+path = "regex_::Regex::new"
diff --git a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs
new file mode 100644
index 00000000000..14f15e73311
--- /dev/null
+++ b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.rs
@@ -0,0 +1,6 @@
+//@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;
+
+fn main() {}
diff --git a/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr
new file mode 100644
index 00000000000..e5fd548b26d
--- /dev/null
+++ b/tests/ui-toml/toml_unloaded_crate/conf_unloaded_crate.stderr
@@ -0,0 +1,20 @@
+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 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 eed704e82fe..88a27530238 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 54325f9776c..e453299edbc 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 59d4da490fe..2b179d45112 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 8ffdf886202..da359866bff 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 a5a8c0304ee..f02ea5bf075 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 c94eb171f52..79794cec926 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 3186d0cbc27..5b347aef14f 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 3f9be297c33..75dabd57bfe 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 acb3b140dfa..9752d7a9f99 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 b66bbccb3cf..1e8fbafd30c 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/auxiliary/proc_macro_attr.rs b/tests/ui/auxiliary/proc_macro_attr.rs
index e72d6b6cead..4c61c5accd3 100644
--- a/tests/ui/auxiliary/proc_macro_attr.rs
+++ b/tests/ui/auxiliary/proc_macro_attr.rs
@@ -51,14 +51,14 @@ pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStrea
 
     fn mut_receiver_of(sig: &mut Signature) -> Option<&mut FnArg> {
         let arg = sig.inputs.first_mut()?;
-        if let FnArg::Typed(PatType { pat, .. }) = arg {
-            if let Pat::Ident(PatIdent { ident, .. }) = &**pat {
-                if ident == "self" {
-                    return Some(arg);
-                }
-            }
+        if let FnArg::Typed(PatType { pat, .. }) = arg
+            && let Pat::Ident(PatIdent { ident, .. }) = &**pat
+            && ident == "self"
+        {
+            Some(arg)
+        } else {
+            None
         }
-        None
     }
 
     let mut elided = 0;
@@ -66,30 +66,29 @@ pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStrea
 
     // Look for methods having arbitrary self type taken by &mut ref
     for inner in &mut item.items {
-        if let ImplItem::Fn(method) = inner {
-            if let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig) {
-                if let box Type::Reference(reference) = &mut pat_type.ty {
-                    // Target only unnamed lifetimes
-                    let name = match &reference.lifetime {
-                        Some(lt) if lt.ident == "_" => make_name(elided),
-                        None => make_name(elided),
-                        _ => continue,
-                    };
-                    elided += 1;
-
-                    // HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it.
-                    // In order to avoid adding the dependency, get a default span from a nonexistent token.
-                    // A default span is needed to mark the code as coming from expansion.
-                    let span = Star::default().span();
-
-                    // Replace old lifetime with the named one
-                    let lifetime = Lifetime::new(&name, span);
-                    reference.lifetime = Some(parse_quote!(#lifetime));
-
-                    // Add lifetime to the generics of the method
-                    method.sig.generics.params.push(parse_quote!(#lifetime));
-                }
-            }
+        if let ImplItem::Fn(method) = inner
+            && let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig)
+            && let box Type::Reference(reference) = &mut pat_type.ty
+        {
+            // Target only unnamed lifetimes
+            let name = match &reference.lifetime {
+                Some(lt) if lt.ident == "_" => make_name(elided),
+                None => make_name(elided),
+                _ => continue,
+            };
+            elided += 1;
+
+            // HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it.
+            // In order to avoid adding the dependency, get a default span from a nonexistent token.
+            // A default span is needed to mark the code as coming from expansion.
+            let span = Star::default().span();
+
+            // Replace old lifetime with the named one
+            let lifetime = Lifetime::new(&name, span);
+            reference.lifetime = Some(parse_quote!(#lifetime));
+
+            // Add lifetime to the generics of the method
+            method.sig.generics.params.push(parse_quote!(#lifetime));
         }
     }
 
@@ -129,15 +128,15 @@ pub fn fake_desugar_await(_args: TokenStream, input: TokenStream) -> TokenStream
     let mut async_fn = parse_macro_input!(input as syn::ItemFn);
 
     for stmt in &mut async_fn.block.stmts {
-        if let syn::Stmt::Expr(syn::Expr::Match(syn::ExprMatch { expr: scrutinee, .. }), _) = stmt {
-            if let syn::Expr::Await(syn::ExprAwait { base, await_token, .. }) = scrutinee.as_mut() {
-                let blc = quote_spanned!( await_token.span => {
-                    #[allow(clippy::let_and_return)]
-                    let __pinned = #base;
-                    __pinned
-                });
-                *scrutinee = parse_quote!(#blc);
-            }
+        if let syn::Stmt::Expr(syn::Expr::Match(syn::ExprMatch { expr: scrutinee, .. }), _) = stmt
+            && let syn::Expr::Await(syn::ExprAwait { base, await_token, .. }) = scrutinee.as_mut()
+        {
+            let blc = quote_spanned!( await_token.span => {
+                #[allow(clippy::let_and_return)]
+                let __pinned = #base;
+                __pinned
+            });
+            *scrutinee = parse_quote!(#blc);
         }
     }
 
diff --git a/tests/ui/auxiliary/proc_macros.rs b/tests/ui/auxiliary/proc_macros.rs
index 7a4cc4fa9ee..bb55539617f 100644
--- a/tests/ui/auxiliary/proc_macros.rs
+++ b/tests/ui/auxiliary/proc_macros.rs
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![feature(proc_macro_span)]
 #![allow(clippy::needless_if, dead_code)]
 
diff --git a/tests/ui/bool_to_int_with_if.fixed b/tests/ui/bool_to_int_with_if.fixed
index ed6141244b4..7fa7c016f93 100644
--- a/tests/ui/bool_to_int_with_if.fixed
+++ b/tests/ui/bool_to_int_with_if.fixed
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![warn(clippy::bool_to_int_with_if)]
 #![allow(unused, dead_code, clippy::unnecessary_operation, clippy::no_effect)]
 
diff --git a/tests/ui/bool_to_int_with_if.rs b/tests/ui/bool_to_int_with_if.rs
index 3f1f1c766e4..2295d6f1362 100644
--- a/tests/ui/bool_to_int_with_if.rs
+++ b/tests/ui/bool_to_int_with_if.rs
@@ -1,4 +1,3 @@
-#![feature(let_chains)]
 #![warn(clippy::bool_to_int_with_if)]
 #![allow(unused, dead_code, clippy::unnecessary_operation, clippy::no_effect)]
 
diff --git a/tests/ui/bool_to_int_with_if.stderr b/tests/ui/bool_to_int_with_if.stderr
index 94089bc6dc8..e4ae5730414 100644
--- a/tests/ui/bool_to_int_with_if.stderr
+++ b/tests/ui/bool_to_int_with_if.stderr
@@ -1,5 +1,5 @@
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:14:5
+  --> tests/ui/bool_to_int_with_if.rs:13:5
    |
 LL | /     if a {
 LL | |
@@ -14,7 +14,7 @@ LL | |     };
    = help: to override `-D warnings` add `#[allow(clippy::bool_to_int_with_if)]`
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:20:5
+  --> tests/ui/bool_to_int_with_if.rs:19:5
    |
 LL | /     if a {
 LL | |
@@ -27,7 +27,7 @@ LL | |     };
    = note: `!a as i32` or `(!a).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:26:5
+  --> tests/ui/bool_to_int_with_if.rs:25:5
    |
 LL | /     if !a {
 LL | |
@@ -40,7 +40,7 @@ LL | |     };
    = note: `!a as i32` or `(!a).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:32:5
+  --> tests/ui/bool_to_int_with_if.rs:31:5
    |
 LL | /     if a || b {
 LL | |
@@ -53,7 +53,7 @@ LL | |     };
    = note: `(a || b) as i32` or `(a || b).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:38:5
+  --> tests/ui/bool_to_int_with_if.rs:37:5
    |
 LL | /     if cond(a, b) {
 LL | |
@@ -66,7 +66,7 @@ LL | |     };
    = note: `cond(a, b) as i32` or `cond(a, b).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:44:5
+  --> tests/ui/bool_to_int_with_if.rs:43:5
    |
 LL | /     if x + y < 4 {
 LL | |
@@ -79,7 +79,7 @@ LL | |     };
    = note: `(x + y < 4) as i32` or `(x + y < 4).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:54:12
+  --> tests/ui/bool_to_int_with_if.rs:53:12
    |
 LL |       } else if b {
    |  ____________^
@@ -93,7 +93,7 @@ LL | |     };
    = note: `b as i32` or `b.into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:64:12
+  --> tests/ui/bool_to_int_with_if.rs:63:12
    |
 LL |       } else if b {
    |  ____________^
@@ -107,7 +107,7 @@ LL | |     };
    = note: `!b as i32` or `(!b).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:130:5
+  --> tests/ui/bool_to_int_with_if.rs:129:5
    |
 LL |     if a { 1 } else { 0 }
    |     ^^^^^^^^^^^^^^^^^^^^^ help: replace with from: `u8::from(a)`
@@ -115,7 +115,7 @@ LL |     if a { 1 } else { 0 }
    = note: `a as u8` or `a.into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:174:13
+  --> tests/ui/bool_to_int_with_if.rs:173:13
    |
 LL |     let _ = if dbg!(4 > 0) { 1 } else { 0 };
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with from: `i32::from(dbg!(4 > 0))`
@@ -123,7 +123,7 @@ LL |     let _ = if dbg!(4 > 0) { 1 } else { 0 };
    = note: `dbg!(4 > 0) as i32` or `dbg!(4 > 0).into()` can also be valid options
 
 error: boolean to int conversion using if
-  --> tests/ui/bool_to_int_with_if.rs:177:18
+  --> tests/ui/bool_to_int_with_if.rs:176:18
    |
 LL |     let _ = dbg!(if 4 > 0 { 1 } else { 0 });
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with from: `i32::from(4 > 0)`
diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed
index e1ceb04f9cb..b553182a445 100644
--- a/tests/ui/collapsible_if.fixed
+++ b/tests/ui/collapsible_if.fixed
@@ -101,27 +101,8 @@ fn main() {
         }
     }
 
-    // Test behavior wrt. `let_chains`.
-    // None of the cases below should be collapsed.
     fn truth() -> bool { true }
 
-    // Prefix:
-    if let 0 = 1 {
-        if truth() {}
-    }
-
-    // Suffix:
-    if truth() {
-        if let 0 = 1 {}
-    }
-
-    // Midfix:
-    if truth() {
-        if let 0 = 1 {
-            if truth() {}
-        }
-    }
-
     // Fix #5962
     if matches!(true, true)
         && matches!(true, true) {}
@@ -162,3 +143,14 @@ fn layout_check() -> u32 {
     ; 3
     //~^^^^^ collapsible_if
 }
+
+fn issue14722() {
+    let x = if true {
+        Some(1)
+    } else {
+        if true {
+            println!("Some debug information");
+        };
+        None
+    };
+}
diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs
index 0b996dca22e..f5998457ca6 100644
--- a/tests/ui/collapsible_if.rs
+++ b/tests/ui/collapsible_if.rs
@@ -108,27 +108,8 @@ fn main() {
         }
     }
 
-    // Test behavior wrt. `let_chains`.
-    // None of the cases below should be collapsed.
     fn truth() -> bool { true }
 
-    // Prefix:
-    if let 0 = 1 {
-        if truth() {}
-    }
-
-    // Suffix:
-    if truth() {
-        if let 0 = 1 {}
-    }
-
-    // Midfix:
-    if truth() {
-        if let 0 = 1 {
-            if truth() {}
-        }
-    }
-
     // Fix #5962
     if matches!(true, true) {
         if matches!(true, true) {}
@@ -172,3 +153,14 @@ fn layout_check() -> u32 {
     }; 3
     //~^^^^^ collapsible_if
 }
+
+fn issue14722() {
+    let x = if true {
+        Some(1)
+    } else {
+        if true {
+            println!("Some debug information");
+        };
+        None
+    };
+}
diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr
index 53281146239..32c6b019403 100644
--- a/tests/ui/collapsible_if.stderr
+++ b/tests/ui/collapsible_if.stderr
@@ -127,7 +127,7 @@ LL ~         }
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if.rs:133:5
+  --> tests/ui/collapsible_if.rs:114:5
    |
 LL | /     if matches!(true, true) {
 LL | |         if matches!(true, true) {}
@@ -141,7 +141,7 @@ LL ~         && matches!(true, true) {}
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if.rs:139:5
+  --> tests/ui/collapsible_if.rs:120:5
    |
 LL | /     if matches!(true, true) && truth() {
 LL | |         if matches!(true, true) {}
@@ -155,7 +155,7 @@ LL ~         && matches!(true, true) {}
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if.rs:151:5
+  --> tests/ui/collapsible_if.rs:132:5
    |
 LL | /     if true {
 LL | |         if true {
@@ -173,7 +173,7 @@ LL ~         }
    |
 
 error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if.rs:168:5
+  --> tests/ui/collapsible_if.rs:149:5
    |
 LL | /     if true {
 LL | |         if true {
diff --git a/tests/ui/collapsible_if_let_chains.edition2024.fixed b/tests/ui/collapsible_if_let_chains.edition2024.fixed
new file mode 100644
index 00000000000..ad08c21ba9e
--- /dev/null
+++ b/tests/ui/collapsible_if_let_chains.edition2024.fixed
@@ -0,0 +1,68 @@
+//@revisions: edition2021 edition2024
+//@[edition2021] edition:2021
+//@[edition2024] edition:2024
+//@[edition2021] check-pass
+
+#![warn(clippy::collapsible_if)]
+
+fn main() {
+    if let Some(a) = Some(3) {
+        // with comment, so do not lint
+        if let Some(b) = Some(4) {
+            let _ = a + b;
+        }
+    }
+
+    //~[edition2024]v collapsible_if
+    if let Some(a) = Some(3)
+        && let Some(b) = Some(4) {
+            let _ = a + b;
+        }
+
+    //~[edition2024]v collapsible_if
+    if let Some(a) = Some(3)
+        && a + 1 == 4 {
+            let _ = a;
+        }
+
+    //~[edition2024]v collapsible_if
+    if Some(3) == Some(4).map(|x| x - 1)
+        && let Some(b) = Some(4) {
+            let _ = b;
+        }
+
+    fn truth() -> bool {
+        true
+    }
+
+    // Prefix:
+    //~[edition2024]v collapsible_if
+    if let 0 = 1
+        && truth() {}
+
+    // Suffix:
+    //~[edition2024]v collapsible_if
+    if truth()
+        && let 0 = 1 {}
+
+    // Midfix:
+    //~[edition2024]vvv collapsible_if
+    //~[edition2024]v collapsible_if
+    if truth()
+        && let 0 = 1
+            && truth() {}
+}
+
+#[clippy::msrv = "1.87.0"]
+fn msrv_1_87() {
+    if let 0 = 1 {
+        if true {}
+    }
+}
+
+#[clippy::msrv = "1.88.0"]
+fn msrv_1_88() {
+    //~[edition2024]v collapsible_if
+    if let 0 = 1
+        && true {}
+}
diff --git a/tests/ui/collapsible_if_let_chains.edition2024.stderr b/tests/ui/collapsible_if_let_chains.edition2024.stderr
new file mode 100644
index 00000000000..b0aa3cecadb
--- /dev/null
+++ b/tests/ui/collapsible_if_let_chains.edition2024.stderr
@@ -0,0 +1,132 @@
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:17:5
+   |
+LL | /     if let Some(a) = Some(3) {
+LL | |         if let Some(b) = Some(4) {
+LL | |             let _ = a + b;
+LL | |         }
+LL | |     }
+   | |_____^
+   |
+   = note: `-D clippy::collapsible-if` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::collapsible_if)]`
+help: collapse nested if block
+   |
+LL ~     if let Some(a) = Some(3)
+LL ~         && let Some(b) = Some(4) {
+LL |             let _ = a + b;
+LL ~         }
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:24:5
+   |
+LL | /     if let Some(a) = Some(3) {
+LL | |         if a + 1 == 4 {
+LL | |             let _ = a;
+LL | |         }
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if let Some(a) = Some(3)
+LL ~         && a + 1 == 4 {
+LL |             let _ = a;
+LL ~         }
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:31:5
+   |
+LL | /     if Some(3) == Some(4).map(|x| x - 1) {
+LL | |         if let Some(b) = Some(4) {
+LL | |             let _ = b;
+LL | |         }
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if Some(3) == Some(4).map(|x| x - 1)
+LL ~         && let Some(b) = Some(4) {
+LL |             let _ = b;
+LL ~         }
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:43:5
+   |
+LL | /     if let 0 = 1 {
+LL | |         if truth() {}
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if let 0 = 1
+LL ~         && truth() {}
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:49:5
+   |
+LL | /     if truth() {
+LL | |         if let 0 = 1 {}
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if truth()
+LL ~         && let 0 = 1 {}
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:56:5
+   |
+LL | /     if truth() {
+LL | |         if let 0 = 1 {
+LL | |             if truth() {}
+LL | |         }
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if truth()
+LL ~         && let 0 = 1 {
+LL |             if truth() {}
+LL ~         }
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:57:9
+   |
+LL | /         if let 0 = 1 {
+LL | |             if truth() {}
+LL | |         }
+   | |_________^
+   |
+help: collapse nested if block
+   |
+LL ~         if let 0 = 1
+LL ~             && truth() {}
+   |
+
+error: this `if` statement can be collapsed
+  --> tests/ui/collapsible_if_let_chains.rs:73:5
+   |
+LL | /     if let 0 = 1 {
+LL | |         if true {}
+LL | |     }
+   | |_____^
+   |
+help: collapse nested if block
+   |
+LL ~     if let 0 = 1
+LL ~         && true {}
+   |
+
+error: aborting due to 8 previous errors
+
diff --git a/tests/ui/collapsible_if_let_chains.fixed b/tests/ui/collapsible_if_let_chains.fixed
deleted file mode 100644
index 3dd9498a4c9..00000000000
--- a/tests/ui/collapsible_if_let_chains.fixed
+++ /dev/null
@@ -1,29 +0,0 @@
-#![feature(let_chains)]
-#![warn(clippy::collapsible_if)]
-
-fn main() {
-    if let Some(a) = Some(3) {
-        // with comment, so do not lint
-        if let Some(b) = Some(4) {
-            let _ = a + b;
-        }
-    }
-
-    if let Some(a) = Some(3)
-        && let Some(b) = Some(4) {
-            let _ = a + b;
-        }
-    //~^^^^^ collapsible_if
-
-    if let Some(a) = Some(3)
-        && a + 1 == 4 {
-            let _ = a;
-        }
-    //~^^^^^ collapsible_if
-
-    if Some(3) == Some(4).map(|x| x - 1)
-        && let Some(b) = Some(4) {
-            let _ = b;
-        }
-    //~^^^^^ collapsible_if
-}
diff --git a/tests/ui/collapsible_if_let_chains.rs b/tests/ui/collapsible_if_let_chains.rs
index 064b9a0be48..b2e88b1a556 100644
--- a/tests/ui/collapsible_if_let_chains.rs
+++ b/tests/ui/collapsible_if_let_chains.rs
@@ -1,4 +1,8 @@
-#![feature(let_chains)]
+//@revisions: edition2021 edition2024
+//@[edition2021] edition:2021
+//@[edition2024] edition:2024
+//@[edition2021] check-pass
+
 #![warn(clippy::collapsible_if)]
 
 fn main() {
@@ -9,24 +13,64 @@ fn main() {
         }
     }
 
+    //~[edition2024]v collapsible_if
     if let Some(a) = Some(3) {
         if let Some(b) = Some(4) {
             let _ = a + b;
         }
     }
-    //~^^^^^ collapsible_if
 
+    //~[edition2024]v collapsible_if
     if let Some(a) = Some(3) {
         if a + 1 == 4 {
             let _ = a;
         }
     }
-    //~^^^^^ collapsible_if
 
+    //~[edition2024]v collapsible_if
     if Some(3) == Some(4).map(|x| x - 1) {
         if let Some(b) = Some(4) {
             let _ = b;
         }
     }
-    //~^^^^^ collapsible_if
+
+    fn truth() -> bool {
+        true
+    }
+
+    // Prefix:
+    //~[edition2024]v collapsible_if
+    if let 0 = 1 {
+        if truth() {}
+    }
+
+    // Suffix:
+    //~[edition2024]v collapsible_if
+    if truth() {
+        if let 0 = 1 {}
+    }
+
+    // Midfix:
+    //~[edition2024]vvv collapsible_if
+    //~[edition2024]v collapsible_if
+    if truth() {
+        if let 0 = 1 {
+            if truth() {}
+        }
+    }
+}
+
+#[clippy::msrv = "1.87.0"]
+fn msrv_1_87() {
+    if let 0 = 1 {
+        if true {}
+    }
+}
+
+#[clippy::msrv = "1.88.0"]
+fn msrv_1_88() {
+    //~[edition2024]v collapsible_if
+    if let 0 = 1 {
+        if true {}
+    }
 }
diff --git a/tests/ui/collapsible_if_let_chains.stderr b/tests/ui/collapsible_if_let_chains.stderr
deleted file mode 100644
index 64a88114c47..00000000000
--- a/tests/ui/collapsible_if_let_chains.stderr
+++ /dev/null
@@ -1,58 +0,0 @@
-error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if_let_chains.rs:12:5
-   |
-LL | /     if let Some(a) = Some(3) {
-LL | |         if let Some(b) = Some(4) {
-LL | |             let _ = a + b;
-LL | |         }
-LL | |     }
-   | |_____^
-   |
-   = note: `-D clippy::collapsible-if` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::collapsible_if)]`
-help: collapse nested if block
-   |
-LL ~     if let Some(a) = Some(3)
-LL ~         && let Some(b) = Some(4) {
-LL |             let _ = a + b;
-LL ~         }
-   |
-
-error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if_let_chains.rs:19:5
-   |
-LL | /     if let Some(a) = Some(3) {
-LL | |         if a + 1 == 4 {
-LL | |             let _ = a;
-LL | |         }
-LL | |     }
-   | |_____^
-   |
-help: collapse nested if block
-   |
-LL ~     if let Some(a) = Some(3)
-LL ~         && a + 1 == 4 {
-LL |             let _ = a;
-LL ~         }
-   |
-
-error: this `if` statement can be collapsed
-  --> tests/ui/collapsible_if_let_chains.rs:26:5
-   |
-LL | /     if Some(3) == Some(4).map(|x| x - 1) {
-LL | |         if let Some(b) = Some(4) {
-LL | |             let _ = b;
-LL | |         }
-LL | |     }
-   | |_____^
-   |
-help: collapse nested if block
-   |
-LL ~     if Some(3) == Some(4).map(|x| x - 1)
-LL ~         && let Some(b) = Some(4) {
-LL |             let _ = b;
-LL ~         }
-   |
-
-error: aborting due to 3 previous errors
-
diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs
index 55ef5584495..71b82040ff6 100644
--- a/tests/ui/collapsible_match.rs
+++ b/tests/ui/collapsible_match.rs
@@ -1,5 +1,6 @@
 #![warn(clippy::collapsible_match)]
 #![allow(
+    clippy::collapsible_if,
     clippy::equatable_if_let,
     clippy::needless_return,
     clippy::no_effect,
diff --git a/tests/ui/collapsible_match.stderr b/tests/ui/collapsible_match.stderr
index 5294a9d6975..c290d84ec29 100644
--- a/tests/ui/collapsible_match.stderr
+++ b/tests/ui/collapsible_match.stderr
@@ -1,5 +1,5 @@
 error: this `match` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:14:20
+  --> tests/ui/collapsible_match.rs:15:20
    |
 LL |           Ok(val) => match val {
    |  ____________________^
@@ -10,7 +10,7 @@ LL | |         },
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:14:12
+  --> tests/ui/collapsible_match.rs:15:12
    |
 LL |         Ok(val) => match val {
    |            ^^^ replace this binding
@@ -21,7 +21,7 @@ LL |             Some(n) => foo(n),
    = help: to override `-D warnings` add `#[allow(clippy::collapsible_match)]`
 
 error: this `match` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:24:20
+  --> tests/ui/collapsible_match.rs:25:20
    |
 LL |           Ok(val) => match val {
    |  ____________________^
@@ -32,7 +32,7 @@ LL | |         },
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:24:12
+  --> tests/ui/collapsible_match.rs:25:12
    |
 LL |         Ok(val) => match val {
    |            ^^^ replace this binding
@@ -41,7 +41,7 @@ LL |             Some(n) => foo(n),
    |             ^^^^^^^ with this pattern
 
 error: this `if let` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:34:9
+  --> tests/ui/collapsible_match.rs:35:9
    |
 LL | /         if let Some(n) = val {
 LL | |
@@ -51,7 +51,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:33:15
+  --> tests/ui/collapsible_match.rs:34:15
    |
 LL |     if let Ok(val) = res_opt {
    |               ^^^ replace this binding
@@ -59,7 +59,7 @@ LL |         if let Some(n) = val {
    |                ^^^^^^^ with this pattern
 
 error: this `if let` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:43:9
+  --> tests/ui/collapsible_match.rs:44:9
    |
 LL | /         if let Some(n) = val {
 LL | |
@@ -71,7 +71,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:42:15
+  --> tests/ui/collapsible_match.rs:43:15
    |
 LL |     if let Ok(val) = res_opt {
    |               ^^^ replace this binding
@@ -79,7 +79,7 @@ LL |         if let Some(n) = val {
    |                ^^^^^^^ with this pattern
 
 error: this `match` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:56:9
+  --> tests/ui/collapsible_match.rs:57:9
    |
 LL | /         match val {
 LL | |
@@ -89,7 +89,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:55:15
+  --> tests/ui/collapsible_match.rs:56:15
    |
 LL |     if let Ok(val) = res_opt {
    |               ^^^ replace this binding
@@ -98,7 +98,7 @@ LL |             Some(n) => foo(n),
    |             ^^^^^^^ with this pattern
 
 error: this `if let` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:66:13
+  --> tests/ui/collapsible_match.rs:67:13
    |
 LL | /             if let Some(n) = val {
 LL | |
@@ -108,7 +108,7 @@ LL | |             }
    | |_____________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:65:12
+  --> tests/ui/collapsible_match.rs:66:12
    |
 LL |         Ok(val) => {
    |            ^^^ replace this binding
@@ -116,7 +116,7 @@ LL |             if let Some(n) = val {
    |                    ^^^^^^^ with this pattern
 
 error: this `match` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:77:9
+  --> tests/ui/collapsible_match.rs:78:9
    |
 LL | /         match val {
 LL | |
@@ -126,7 +126,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:76:15
+  --> tests/ui/collapsible_match.rs:77:15
    |
 LL |     if let Ok(val) = res_opt {
    |               ^^^ replace this binding
@@ -135,7 +135,7 @@ LL |             Some(n) => foo(n),
    |             ^^^^^^^ with this pattern
 
 error: this `if let` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:89:13
+  --> tests/ui/collapsible_match.rs:90:13
    |
 LL | /             if let Some(n) = val {
 LL | |
@@ -147,7 +147,7 @@ LL | |             }
    | |_____________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:88:12
+  --> tests/ui/collapsible_match.rs:89:12
    |
 LL |         Ok(val) => {
    |            ^^^ replace this binding
@@ -155,7 +155,7 @@ LL |             if let Some(n) = val {
    |                    ^^^^^^^ with this pattern
 
 error: this `match` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:102:20
+  --> tests/ui/collapsible_match.rs:103:20
    |
 LL |           Ok(val) => match val {
    |  ____________________^
@@ -166,7 +166,7 @@ LL | |         },
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:102:12
+  --> tests/ui/collapsible_match.rs:103:12
    |
 LL |         Ok(val) => match val {
    |            ^^^ replace this binding
@@ -175,7 +175,7 @@ LL |             Some(n) => foo(n),
    |             ^^^^^^^ with this pattern
 
 error: this `match` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:112:22
+  --> tests/ui/collapsible_match.rs:113:22
    |
 LL |           Some(val) => match val {
    |  ______________________^
@@ -186,7 +186,7 @@ LL | |         },
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:112:14
+  --> tests/ui/collapsible_match.rs:113:14
    |
 LL |         Some(val) => match val {
    |              ^^^ replace this binding
@@ -195,7 +195,7 @@ LL |             Some(n) => foo(n),
    |             ^^^^^^^ with this pattern
 
 error: this `match` can be collapsed into the outer `match`
-  --> tests/ui/collapsible_match.rs:256:22
+  --> tests/ui/collapsible_match.rs:257:22
    |
 LL |           Some(val) => match val {
    |  ______________________^
@@ -206,7 +206,7 @@ LL | |         },
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:256:14
+  --> tests/ui/collapsible_match.rs:257:14
    |
 LL |         Some(val) => match val {
    |              ^^^ replace this binding
@@ -215,7 +215,7 @@ LL |             E::A(val) | E::B(val) => foo(val),
    |             ^^^^^^^^^^^^^^^^^^^^^ with this pattern
 
 error: this `if let` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:288:9
+  --> tests/ui/collapsible_match.rs:289:9
    |
 LL | /         if let Some(u) = a {
 LL | |
@@ -225,7 +225,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:287:27
+  --> tests/ui/collapsible_match.rs:288:27
    |
 LL |     if let Issue9647::A { a, .. } = x {
    |                           ^ replace this binding
@@ -233,7 +233,7 @@ LL |         if let Some(u) = a {
    |                ^^^^^^^ with this pattern, prefixed by `a`:
 
 error: this `if let` can be collapsed into the outer `if let`
-  --> tests/ui/collapsible_match.rs:298:9
+  --> tests/ui/collapsible_match.rs:299:9
    |
 LL | /         if let Some(u) = a {
 LL | |
@@ -243,7 +243,7 @@ LL | |         }
    | |_________^
    |
 help: the outer pattern can be modified to include the inner pattern
-  --> tests/ui/collapsible_match.rs:297:35
+  --> tests/ui/collapsible_match.rs:298:35
    |
 LL |     if let Issue9647::A { a: Some(a), .. } = x {
    |                                   ^ replace this binding
diff --git a/tests/ui/comparison_to_empty.fixed b/tests/ui/comparison_to_empty.fixed
index dfbb6168384..7a71829dd62 100644
--- a/tests/ui/comparison_to_empty.fixed
+++ b/tests/ui/comparison_to_empty.fixed
@@ -1,6 +1,5 @@
 #![warn(clippy::comparison_to_empty)]
 #![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
-#![feature(let_chains)]
 
 fn main() {
     // Disallow comparisons to empty
diff --git a/tests/ui/comparison_to_empty.rs b/tests/ui/comparison_to_empty.rs
index 61cdb2bbe9f..5d213a09e81 100644
--- a/tests/ui/comparison_to_empty.rs
+++ b/tests/ui/comparison_to_empty.rs
@@ -1,6 +1,5 @@
 #![warn(clippy::comparison_to_empty)]
 #![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
-#![feature(let_chains)]
 
 fn main() {
     // Disallow comparisons to empty
diff --git a/tests/ui/comparison_to_empty.stderr b/tests/ui/comparison_to_empty.stderr
index 00a50430a3e..deb3e938878 100644
--- a/tests/ui/comparison_to_empty.stderr
+++ b/tests/ui/comparison_to_empty.stderr
@@ -1,5 +1,5 @@
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:8:13
+  --> tests/ui/comparison_to_empty.rs:7:13
    |
 LL |     let _ = s == "";
    |             ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
@@ -8,73 +8,73 @@ LL |     let _ = s == "";
    = help: to override `-D warnings` add `#[allow(clippy::comparison_to_empty)]`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:10:13
+  --> tests/ui/comparison_to_empty.rs:9:13
    |
 LL |     let _ = s != "";
    |             ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:14:13
+  --> tests/ui/comparison_to_empty.rs:13:13
    |
 LL |     let _ = v == [];
    |             ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:16:13
+  --> tests/ui/comparison_to_empty.rs:15:13
    |
 LL |     let _ = v != [];
    |             ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()`
 
 error: comparison to empty slice using `if let`
-  --> tests/ui/comparison_to_empty.rs:18:8
+  --> tests/ui/comparison_to_empty.rs:17:8
    |
 LL |     if let [] = &*v {}
    |        ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(*v).is_empty()`
 
 error: comparison to empty slice using `if let`
-  --> tests/ui/comparison_to_empty.rs:21:8
+  --> tests/ui/comparison_to_empty.rs:20:8
    |
 LL |     if let [] = s {}
    |        ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
 
 error: comparison to empty slice using `if let`
-  --> tests/ui/comparison_to_empty.rs:23:8
+  --> tests/ui/comparison_to_empty.rs:22:8
    |
 LL |     if let [] = &*s {}
    |        ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
 
 error: comparison to empty slice using `if let`
-  --> tests/ui/comparison_to_empty.rs:25:8
+  --> tests/ui/comparison_to_empty.rs:24:8
    |
 LL |     if let [] = &*s
    |        ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:27:12
+  --> tests/ui/comparison_to_empty.rs:26:12
    |
 LL |         && s == []
    |            ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:48:13
+  --> tests/ui/comparison_to_empty.rs:47:13
    |
 LL |     let _ = s.eq("");
    |             ^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:50:13
+  --> tests/ui/comparison_to_empty.rs:49:13
    |
 LL |     let _ = s.ne("");
    |             ^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:53:13
+  --> tests/ui/comparison_to_empty.rs:52:13
    |
 LL |     let _ = v.eq(&[]);
    |             ^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()`
 
 error: comparison to empty slice
-  --> tests/ui/comparison_to_empty.rs:55:13
+  --> tests/ui/comparison_to_empty.rs:54:13
    |
 LL |     let _ = v.ne(&[]);
    |             ^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()`
diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed
index e19aa4acb4c..050cdfcba96 100644
--- a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed
+++ b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed
@@ -1,5 +1,5 @@
 #![deny(clippy::index_refutable_slice)]
-#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes)]
+#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)]
 
 enum SomeEnum<T> {
     One(T),
diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.rs b/tests/ui/index_refutable_slice/if_let_slice_binding.rs
index 29039356855..91429bfea27 100644
--- a/tests/ui/index_refutable_slice/if_let_slice_binding.rs
+++ b/tests/ui/index_refutable_slice/if_let_slice_binding.rs
@@ -1,5 +1,5 @@
 #![deny(clippy::index_refutable_slice)]
-#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes)]
+#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)]
 
 enum SomeEnum<T> {
     One(T),
diff --git a/tests/ui/manual_saturating_arithmetic.fixed b/tests/ui/manual_saturating_arithmetic.fixed
index 3f73d6e5a1a..304be05f6c4 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 98246a5cd96..c2b570e974a 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 9d133d8a073..2f006a3ae17 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
    |  _____________^
diff --git a/tests/ui/manual_unwrap_or_default.fixed b/tests/ui/manual_unwrap_or_default.fixed
index 9dae9fcae07..41ca44ceef4 100644
--- a/tests/ui/manual_unwrap_or_default.fixed
+++ b/tests/ui/manual_unwrap_or_default.fixed
@@ -106,3 +106,16 @@ fn issue_12928() {
 fn allowed_manual_unwrap_or_zero() -> u32 {
     Some(42).unwrap_or_default()
 }
+
+mod issue14716 {
+    struct Foo {
+        name: Option<String>,
+    }
+
+    fn bar(project: &Foo) {
+        let _name = match project.name {
+            Some(ref x) => x,
+            None => "",
+        };
+    }
+}
diff --git a/tests/ui/manual_unwrap_or_default.rs b/tests/ui/manual_unwrap_or_default.rs
index 539d7a8bbae..343fbc4879c 100644
--- a/tests/ui/manual_unwrap_or_default.rs
+++ b/tests/ui/manual_unwrap_or_default.rs
@@ -147,3 +147,16 @@ fn allowed_manual_unwrap_or_zero() -> u32 {
         0
     }
 }
+
+mod issue14716 {
+    struct Foo {
+        name: Option<String>,
+    }
+
+    fn bar(project: &Foo) {
+        let _name = match project.name {
+            Some(ref x) => x,
+            None => "",
+        };
+    }
+}
diff --git a/tests/ui/needless_if.fixed b/tests/ui/needless_if.fixed
index 347dbff7c59..c839156bed9 100644
--- a/tests/ui/needless_if.fixed
+++ b/tests/ui/needless_if.fixed
@@ -1,5 +1,4 @@
 //@aux-build:proc_macros.rs
-#![feature(let_chains)]
 #![allow(
     clippy::blocks_in_conditions,
     clippy::if_same_then_else,
diff --git a/tests/ui/needless_if.rs b/tests/ui/needless_if.rs
index 5e0f2a14408..11103af5c55 100644
--- a/tests/ui/needless_if.rs
+++ b/tests/ui/needless_if.rs
@@ -1,5 +1,4 @@
 //@aux-build:proc_macros.rs
-#![feature(let_chains)]
 #![allow(
     clippy::blocks_in_conditions,
     clippy::if_same_then_else,
diff --git a/tests/ui/needless_if.stderr b/tests/ui/needless_if.stderr
index 62cdf245944..4b56843bd52 100644
--- a/tests/ui/needless_if.stderr
+++ b/tests/ui/needless_if.stderr
@@ -1,5 +1,5 @@
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:27:5
+  --> tests/ui/needless_if.rs:26:5
    |
 LL |     if (true) {}
    |     ^^^^^^^^^^^^ help: you can remove it
@@ -8,13 +8,13 @@ LL |     if (true) {}
    = help: to override `-D warnings` add `#[allow(clippy::needless_if)]`
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:30:5
+  --> tests/ui/needless_if.rs:29:5
    |
 LL |     if maybe_side_effect() {}
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();`
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:36:5
+  --> tests/ui/needless_if.rs:35:5
    |
 LL | /     if {
 LL | |
@@ -31,7 +31,7 @@ LL +     });
    |
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:51:5
+  --> tests/ui/needless_if.rs:50:5
    |
 LL | /     if {
 LL | |
@@ -57,19 +57,19 @@ LL +     } && true);
    |
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:96:5
+  --> tests/ui/needless_if.rs:95:5
    |
 LL |     if { maybe_side_effect() } {}
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });`
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:99:5
+  --> tests/ui/needless_if.rs:98:5
    |
 LL |     if { maybe_side_effect() } && true {}
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);`
 
 error: this `if` branch is empty
-  --> tests/ui/needless_if.rs:104:5
+  --> tests/ui/needless_if.rs:103:5
    |
 LL |     if true {}
    |     ^^^^^^^^^^ help: you can remove it: `true;`
diff --git a/tests/ui/needless_late_init.fixed b/tests/ui/needless_late_init.fixed
index f832752ccd7..b686a8e9f1a 100644
--- a/tests/ui/needless_late_init.fixed
+++ b/tests/ui/needless_late_init.fixed
@@ -1,6 +1,4 @@
 //@aux-build:proc_macros.rs
-#![feature(let_chains)]
-#![allow(unused)]
 #![allow(
     clippy::assign_op_pattern,
     clippy::blocks_in_conditions,
diff --git a/tests/ui/needless_late_init.rs b/tests/ui/needless_late_init.rs
index a52fbf52923..23772ff7029 100644
--- a/tests/ui/needless_late_init.rs
+++ b/tests/ui/needless_late_init.rs
@@ -1,6 +1,4 @@
 //@aux-build:proc_macros.rs
-#![feature(let_chains)]
-#![allow(unused)]
 #![allow(
     clippy::assign_op_pattern,
     clippy::blocks_in_conditions,
diff --git a/tests/ui/needless_late_init.stderr b/tests/ui/needless_late_init.stderr
index b24c1275881..e3e25cdc8d7 100644
--- a/tests/ui/needless_late_init.stderr
+++ b/tests/ui/needless_late_init.stderr
@@ -1,5 +1,5 @@
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:27:5
+  --> tests/ui/needless_late_init.rs:25:5
    |
 LL |     let a;
    |     ^^^^^^ created here
@@ -17,7 +17,7 @@ LL ~     let a = "zero";
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:31:5
+  --> tests/ui/needless_late_init.rs:29:5
    |
 LL |     let b;
    |     ^^^^^^ created here
@@ -35,7 +35,7 @@ LL ~     let b = 1;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:33:5
+  --> tests/ui/needless_late_init.rs:31:5
    |
 LL |     let c;
    |     ^^^^^^ created here
@@ -52,7 +52,7 @@ LL ~     let c = 2;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:38:5
+  --> tests/ui/needless_late_init.rs:36:5
    |
 LL |     let d: usize;
    |     ^^^^^^^^^^^^^ created here
@@ -68,7 +68,7 @@ LL ~     let d: usize = 1;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:42:5
+  --> tests/ui/needless_late_init.rs:40:5
    |
 LL |     let e;
    |     ^^^^^^ created here
@@ -84,7 +84,7 @@ LL ~     let e = format!("{}", d);
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:48:5
+  --> tests/ui/needless_late_init.rs:46:5
    |
 LL |     let a;
    |     ^^^^^^
@@ -103,7 +103,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:58:5
+  --> tests/ui/needless_late_init.rs:56:5
    |
 LL |     let b;
    |     ^^^^^^
@@ -120,7 +120,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:66:5
+  --> tests/ui/needless_late_init.rs:64:5
    |
 LL |     let d;
    |     ^^^^^^
@@ -138,7 +138,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:75:5
+  --> tests/ui/needless_late_init.rs:73:5
    |
 LL |     let e;
    |     ^^^^^^
@@ -155,7 +155,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:83:5
+  --> tests/ui/needless_late_init.rs:81:5
    |
 LL |     let f;
    |     ^^^^^^
@@ -169,7 +169,7 @@ LL ~         1 => "three",
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:90:5
+  --> tests/ui/needless_late_init.rs:88:5
    |
 LL |     let g: usize;
    |     ^^^^^^^^^^^^^
@@ -186,7 +186,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:99:5
+  --> tests/ui/needless_late_init.rs:97:5
    |
 LL |     let x;
    |     ^^^^^^ created here
@@ -203,7 +203,7 @@ LL ~     let x = 1;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:104:5
+  --> tests/ui/needless_late_init.rs:102:5
    |
 LL |     let x;
    |     ^^^^^^ created here
@@ -220,7 +220,7 @@ LL ~     let x = SignificantDrop;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:109:5
+  --> tests/ui/needless_late_init.rs:107:5
    |
 LL |     let x;
    |     ^^^^^^ created here
@@ -238,7 +238,7 @@ LL ~     let x = SignificantDrop;
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:129:5
+  --> tests/ui/needless_late_init.rs:127:5
    |
 LL |     let a;
    |     ^^^^^^
@@ -257,7 +257,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:147:5
+  --> tests/ui/needless_late_init.rs:145:5
    |
 LL |     let a;
    |     ^^^^^^
@@ -276,7 +276,7 @@ LL ~     };
    |
 
 error: unneeded late initialization
-  --> tests/ui/needless_late_init.rs:300:5
+  --> tests/ui/needless_late_init.rs:298:5
    |
 LL |     let r;
    |     ^^^^^^ created here
diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed
index 33a5308bd35..dc9d6491691 100644
--- a/tests/ui/redundant_pattern_matching_option.fixed
+++ b/tests/ui/redundant_pattern_matching_option.fixed
@@ -1,4 +1,4 @@
-#![feature(let_chains, if_let_guard)]
+#![feature(if_let_guard)]
 #![warn(clippy::redundant_pattern_matching)]
 #![allow(
     clippy::needless_bool,
diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs
index 60bce2994ea..2e9714ad8e7 100644
--- a/tests/ui/redundant_pattern_matching_option.rs
+++ b/tests/ui/redundant_pattern_matching_option.rs
@@ -1,4 +1,4 @@
-#![feature(let_chains, if_let_guard)]
+#![feature(if_let_guard)]
 #![warn(clippy::redundant_pattern_matching)]
 #![allow(
     clippy::needless_bool,
diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs
index 5aaf7b9f5b5..433459253dd 100644
--- a/tests/ui/unused_async.rs
+++ b/tests/ui/unused_async.rs
@@ -119,3 +119,11 @@ fn main() {
     foo();
     bar();
 }
+
+mod issue14704 {
+    use std::sync::Arc;
+
+    trait Action {
+        async fn cancel(self: Arc<Self>) {}
+    }
+}
diff --git a/triagebot.toml b/triagebot.toml
index f27b109e995..b2a2a85f61a 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -42,7 +42,6 @@ new_pr = true
 contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md"
 users_on_vacation = [
     "matthiaskrgr",
-    "samueltardieu",
 ]
 
 [assign.owners]