about summary refs log tree commit diff
path: root/clippy_utils
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2025-05-15 19:19:08 +0200
committerPhilipp Krones <hello@philkrones.com>2025-05-15 19:19:08 +0200
commit0bb1b5bd3b7eecd248ae709fb43826bd6f89a4ab (patch)
tree8830102e10a6d1b6831fd6996320d28d3543778c /clippy_utils
parent303c4ecfdd0c571d80c5bc151243aee1900cebfd (diff)
parent40bead02a528980645dda94a63b16526f9d2ffc3 (diff)
downloadrust-0bb1b5bd3b7eecd248ae709fb43826bd6f89a4ab.tar.gz
rust-0bb1b5bd3b7eecd248ae709fb43826bd6f89a4ab.zip
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_utils')
-rw-r--r--clippy_utils/src/lib.rs311
-rw-r--r--clippy_utils/src/msrvs.rs7
-rw-r--r--clippy_utils/src/paths.rs391
-rw-r--r--clippy_utils/src/sym.rs91
-rw-r--r--clippy_utils/src/ty/mod.rs85
-rw-r--r--clippy_utils/src/ty/type_certainty/mod.rs23
6 files changed, 466 insertions, 442 deletions
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..223a8649eb3 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -22,7 +22,8 @@ macro_rules! msrv_aliases {
 
 // names may refer to stabilized feature flags or library items
 msrv_aliases! {
-    1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT }
+    1,88,0 { LET_CHAINS }
+    1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT }
     1,85,0 { UINT_FLOAT_MIDPOINT }
     1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
     1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
@@ -38,7 +39,7 @@ msrv_aliases! {
     1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
     1,68,0 { PATH_MAIN_SEPARATOR_STR }
     1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
-    1,63,0 { CLONE_INTO }
+    1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
     1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
     1,60,0 { ABS_DIFF }
     1,59,0 { THREAD_LOCAL_CONST_INIT }
@@ -68,7 +69,7 @@ msrv_aliases! {
     1,31,0 { OPTION_REPLACE }
     1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
     1,29,0 { ITER_FLATTEN }
-    1,28,0 { FROM_BOOL, REPEAT_WITH }
+    1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
     1,27,0 { ITERATOR_TRY_FOLD }
     1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
     1,24,0 { IS_ASCII_DIGIT }
diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs
index 7f64ebd3b64..e5179e479cc 100644
--- a/clippy_utils/src/paths.rs
+++ b/clippy_utils/src/paths.rs
@@ -4,62 +4,343 @@
 //! 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, Res};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
+use rustc_hir::{ImplItemRef, ItemKind, Node, OwnerId, TraitItemRef, UseKind};
+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 item = tcx.hir_item(item_id);
+            if let ItemKind::Use(path, UseKind::Single(ident)) = item.kind {
+                if ident.name == name {
+                    path.res
+                        .iter()
+                        .find(|res| ns.matches(res.ns()))
+                        .and_then(Res::opt_def_id)
+                } else {
+                    None
+                }
+            } else {
+                res(item.kind.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..9428262b99a 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,90 +24,177 @@ 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,
+    ambiguous_glob_reexports,
     as_bytes,
     as_deref_mut,
     as_deref,
     as_mut,
+    AsyncReadExt,
+    AsyncWriteExt,
+    BACKSLASH_SINGLE_QUOTE: r"\'",
     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,
+    disallowed_types,
+    DOUBLE_QUOTE: "\"",
+    EarlyLintPass,
     ends_with,
+    enum_glob_use,
+    error,
+    ErrorKind,
     exp,
     extend,
     finish_non_exhaustive,
     finish,
     flat_map,
     for_each,
+    from_bytes_with_nul_unchecked,
+    from_bytes_with_nul,
+    from_ptr,
     from_raw,
+    from_ref,
     from_str_radix,
+    fs,
+    futures_util,
     get,
+    hidden_glob_reexports,
+    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,
+    ln,
+    lock_api,
+    log,
     LowerExp,
     LowerHex,
+    macro_use_imports,
+    map_or_else,
+    map_or,
     max,
+    MAX,
+    mem,
     min,
+    MIN,
     mode,
+    module_name_repetitions,
     msrv,
+    msrvs,
+    MsrvStack,
+    mut_ptr,
+    mutex,
+    needless_return,
+    next_tuple,
     Octal,
+    once_cell,
+    OpenOptions,
     or_default,
+    Other,
     parse,
+    PathLookup,
+    paths,
+    powf,
+    powi,
     push,
+    redundant_pub_crate,
     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,
+    single_component_path_imports,
+    span_lint_and_then,
     split_whitespace,
     split,
+    sqrt,
     Start,
+    Step,
+    style,
+    symbol,
+    Symbol,
+    SyntaxContext,
     take,
     TBD,
     then_some,
+    to_ascii_lowercase,
+    to_ascii_uppercase,
     to_digit,
+    to_lowercase,
     to_owned,
+    to_uppercase,
+    tokio,
+    unreachable_pub,
+    unsafe_removed_from_name,
+    unused_braces,
     unused_extern_crates,
+    unused_import_braces,
+    unused_trait_names,
+    unused,
     unwrap_err,
     unwrap_or_default,
+    unwrap_or_else,
     UpperExp,
     UpperHex,
     V4,
     V6,
+    Visitor,
+    warnings,
     Weak,
+    wildcard_imports,
     with_capacity,
     wrapping_offset,
 }
diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs
index da09edd7f7c..26d41cfb497 100644
--- a/clippy_utils/src/ty/mod.rs
+++ b/clippy_utils/src/ty/mod.rs
@@ -20,7 +20,7 @@ use rustc_middle::traits::EvaluationResult;
 use rustc_middle::ty::layout::ValidityRequirement;
 use rustc_middle::ty::{
     self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
-    GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
+    GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
     TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
 };
 use rustc_span::symbol::Ident;
@@ -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>(
@@ -359,56 +358,6 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
     }
 }
 
-// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize
-// this function can be removed once the `normalize` method does not panic when normalization does
-// not succeed
-/// Checks if `Ty` is normalizable. This function is useful
-/// to avoid crashes on `layout_of`.
-pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
-    is_normalizable_helper(cx, param_env, ty, 0, &mut FxHashMap::default())
-}
-
-fn is_normalizable_helper<'tcx>(
-    cx: &LateContext<'tcx>,
-    param_env: ParamEnv<'tcx>,
-    ty: Ty<'tcx>,
-    depth: usize,
-    cache: &mut FxHashMap<Ty<'tcx>, bool>,
-) -> bool {
-    if let Some(&cached_result) = cache.get(&ty) {
-        return cached_result;
-    }
-    if !cx.tcx.recursion_limit().value_within_limit(depth) {
-        return false;
-    }
-    // Prevent recursive loops by answering `true` to recursive requests with the same
-    // type. This will be adjusted when the outermost call analyzes all the type
-    // components.
-    cache.insert(ty, true);
-    let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
-    let cause = ObligationCause::dummy();
-    let result = if infcx.at(&cause, param_env).query_normalize(ty).is_ok() {
-        match ty.kind() {
-            ty::Adt(def, args) => def.variants().iter().all(|variant| {
-                variant
-                    .fields
-                    .iter()
-                    .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, args), depth + 1, cache))
-            }),
-            _ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
-                GenericArgKind::Type(inner_ty) if inner_ty != ty => {
-                    is_normalizable_helper(cx, param_env, inner_ty, depth + 1, cache)
-                },
-                _ => true, // if inner_ty == ty, we've already checked it
-            }),
-        }
-    } else {
-        false
-    };
-    cache.insert(ty, result);
-    result
-}
-
 /// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any
 /// integer or floating-point number type).
 ///
@@ -474,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
@@ -993,9 +931,6 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<
 /// account the layout of type parameters.
 pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
     use rustc_middle::ty::layout::LayoutOf;
-    if !is_normalizable(cx, cx.param_env, ty) {
-        return 0;
-    }
     match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) {
         (Ok(size), _) => size,
         (Err(_), ty::Tuple(list)) => list.iter().map(|t| approx_ty_size(cx, t)).sum(),
@@ -1184,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 {
@@ -1422,3 +1354,10 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'
     let mut phantoms = FxHashSet::default();
     has_non_owning_mutable_access_inner(cx, &mut phantoms, iter_ty)
 }
+
+/// Check if `ty` is slice-like, i.e., `&[T]`, `[T; N]`, or `Vec<T>`.
+pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+    ty.is_slice()
+        || ty.is_array()
+        || matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did()))
+}
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)]