Replace str path utils with new PathLookup type

This commit is contained in:
Alex Macleod 2025-04-28 17:12:16 +00:00
parent ea13461967
commit b768fbe4bc
70 changed files with 799 additions and 1400 deletions

View file

@ -96,8 +96,9 @@ use rustc_data_structures::fx::FxHashMap;
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::Namespace::{MacroNS, TypeNS, ValueNS};
use rustc_hir::def::{DefKind, Namespace, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId, LocalModDefId};
use rustc_hir::definitions::{DefPath, DefPathData};
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
@ -105,8 +106,8 @@ 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,
Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt,
StmtKind, TraitFn, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
};
use rustc_lexer::{TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
@ -347,14 +348,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 +431,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 +447,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,60 +513,57 @@ 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 {
fn find_primitive_impls(tcx: TyCtxt<'_>, name: Symbol) -> &[DefId] {
let ty = match name {
"bool" => SimplifiedType::Bool,
"char" => SimplifiedType::Char,
"str" => SimplifiedType::Str,
"array" => SimplifiedType::Array,
"slice" => SimplifiedType::Slice,
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.
"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();
},
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).iter().copied()
tcx.incoherent_impls(ty)
}
fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
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()
.filter(|item| item.ident.name == name)
.map(|child| child.res.expect_non_local())
.collect(),
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()
.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(),
.find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
_ => None,
}
}
fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symbol) -> Vec<Res> {
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) => {
@ -647,138 +571,147 @@ fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symb
&root_mod
},
Node::Item(item) => &item.kind,
_ => return Vec::new(),
_ => return None,
};
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))
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()
.filter_map(|&item_id| {
let ident = tcx.hir_item(item_id).kind.ident()?;
res(ident, item_id.owner_id)
})
.collect(),
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()
.filter_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id))
.collect(),
.find_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id)),
ItemKind::Trait(.., trait_item_refs) => trait_item_refs
.iter()
.filter_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id))
.collect(),
_ => Vec::new(),
.find_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id)),
_ => None,
}
}
fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> {
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_children_by_name(tcx, local_id, name)
local_item_child_by_name(tcx, local_id, ns, name)
} else {
non_local_item_children_by_name(tcx, def_id, name)
non_local_item_child_by_name(tcx, def_id, ns, 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()
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 => &[],
}
}
/// 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)
}
}
/// 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`].
/// 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.
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(),
///
/// [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 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)
let mut out = Vec::new();
for &base in find_crates(tcx, root).iter().chain(find_primitive_impls(tcx, root)) {
lookup_path_with_base(tcx, base, ns, rest, &mut out);
}
out
}
/// 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);
fn lookup_path_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));
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)
.inherent_impls(base)
.iter()
.flat_map(|&impl_def_id| item_children_by_name(tcx, impl_def_id, segment));
.filter_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, ns, segment));
out.extend(inherent_impl_children);
let direct_children = item_children_by_name(tcx, def_id, segment);
inherent_impl_children.chain(direct_children)
})
.collect();
return;
},
[segment, ref rest @ ..] => {
path = rest;
let Some(child) = item_child_by_name(tcx, base, PathNS::Type, segment) else {
return;
};
base = child;
},
[] => unreachable!(),
}
}
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.
/// Equivalent to a [`lookup_path`] after splitting the input string on `::`
///
/// 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,
})
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)
}
/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
@ -2065,24 +1998,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);

View file

@ -4,62 +4,125 @@
//! 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, PathNS, lookup_path, path_def_id, sym};
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::{STDLIB_STABLE_CRATES, Symbol};
use std::sync::OnceLock;
/// 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))
}
/// 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 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"];
// Paths in clippy itself
pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"];
pub const CLIPPY_SYM_MODULE: [&str; 2] = ["clippy_utils", "sym"];
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`

View file

@ -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,

View file

@ -32,7 +32,7 @@ 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::{PathNS, lookup_path_str, path_res};
mod type_certainty;
pub use type_certainty::expr_type_is_certain;
@ -229,9 +229,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 +422,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 +1118,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 {

View file

@ -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::{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)]