Skip to main content

clippy_utils/
paths.rs

1//! This module contains paths to types and functions Clippy needs to know
2//! about.
3//!
4//! Whenever possible, please consider diagnostic items over hardcoded paths.
5//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
6
7use crate::res::MaybeQPath;
8use crate::sym;
9use rustc_ast::Mutability;
10use rustc_data_structures::fx::FxHashMap;
11use rustc_hir::def::Namespace::{MacroNS, TypeNS, ValueNS};
12use rustc_hir::def::{DefKind, Namespace, Res};
13use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
14use rustc_hir::{ItemKind, Node, UseKind, UseTree};
15use rustc_lint::LateContext;
16use rustc_middle::ty::fast_reject::SimplifiedType;
17use rustc_middle::ty::layout::HasTyCtxt;
18use rustc_middle::ty::{FloatTy, IntTy, Ty, TyCtxt, UintTy};
19use rustc_span::{Ident, STDLIB_STABLE_CRATES, Symbol};
20use std::sync::OnceLock;
21
22/// Specifies whether to resolve a path in the [`TypeNS`], [`ValueNS`], [`MacroNS`] or in an
23/// arbitrary namespace
24#[derive(Clone, Copy, PartialEq, Debug)]
25pub enum PathNS {
26    /// The [type namespace](TypeNS)
27    Type,
28    /// The [value namespace](ValueNS)
29    Value,
30    /// The [macro namespace](MacroNS)
31    Macro,
32    /// A struct or enum-variant field. Not a real name resolution namespace
33    Field,
34
35    /// Resolves to the name in the first available namespace, e.g. for `std::vec` this would return
36    /// either the macro or the module but **not** both
37    ///
38    /// Must only be used when the specific resolution is unimportant such as in
39    /// `missing_enforced_import_renames`
40    Arbitrary,
41}
42
43impl PathNS {
44    fn matches(self, ns: Option<Namespace>) -> bool {
45        let required = match self {
46            PathNS::Type => TypeNS,
47            PathNS::Value => ValueNS,
48            PathNS::Macro => MacroNS,
49            PathNS::Field => return false,
50            PathNS::Arbitrary => return true,
51        };
52
53        ns == Some(required)
54    }
55}
56
57/// Lazily resolves a path into a list of [`DefId`]s using [`lookup_path`].
58///
59/// Typically it will contain one [`DefId`] or none, but in some situations there can be multiple:
60/// - `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0
61/// - `alloc::boxed::Box::downcast` would return a function for each of the different inherent impls ([1], [2], [3])
62///
63/// [1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast
64/// [2]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-1
65/// [3]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-2
66pub struct PathLookup {
67    ns: PathNS,
68    path: &'static [Symbol],
69    once: OnceLock<Vec<DefId>>,
70}
71
72impl PathLookup {
73    /// Only exported for tests and `clippy_lints_internal`
74    #[doc(hidden)]
75    pub const fn new(ns: PathNS, path: &'static [Symbol]) -> Self {
76        Self {
77            ns,
78            path,
79            once: OnceLock::new(),
80        }
81    }
82
83    /// Returns the list of [`DefId`]s that the path resolves to
84    pub fn get<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>) -> &[DefId] {
85        self.once.get_or_init(|| lookup_path(tcx.tcx(), self.ns, self.path))
86    }
87
88    /// Returns the single [`DefId`] that the path resolves to, this can only be used for paths into
89    /// stdlib crates to avoid the issue of multiple [`DefId`]s being returned
90    ///
91    /// May return [`None`] in `no_std`/`no_core` environments
92    pub fn only(&self, cx: &LateContext<'_>) -> Option<DefId> {
93        let ids = self.get(cx);
94        debug_assert!(STDLIB_STABLE_CRATES.contains(&self.path[0]));
95        debug_assert!(ids.len() <= 1, "{ids:?}");
96        ids.first().copied()
97    }
98
99    /// Checks if the path resolves to the given `def_id`
100    pub fn matches<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, def_id: DefId) -> bool {
101        self.get(&tcx.tcx()).contains(&def_id)
102    }
103
104    /// Resolves `maybe_path` to a [`DefId`] and checks if the [`PathLookup`] matches it
105    pub fn matches_path<'tcx>(&self, cx: &LateContext<'_>, maybe_path: impl MaybeQPath<'tcx>) -> bool {
106        maybe_path
107            .res(cx)
108            .opt_def_id()
109            .is_some_and(|def_id| self.matches(cx, def_id))
110    }
111
112    /// Checks if the path resolves to `ty`'s definition, must be an `Adt`
113    pub fn matches_ty<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, ty: Ty<'_>) -> bool {
114        ty.ty_adt_def().is_some_and(|adt| self.matches(&tcx.tcx(), adt.did()))
115    }
116}
117
118macro_rules! path_macros {
119    ($($name:ident: $ns:expr,)*) => {
120        $(
121            /// Only exported for tests and `clippy_lints_internal`
122            #[doc(hidden)]
123            #[macro_export]
124            macro_rules! $name {
125                ($$($$seg:ident $$(::)?)*) => {
126                    PathLookup::new($ns, &[$$(sym::$$seg,)*])
127                };
128            }
129        )*
130    };
131}
132
133path_macros! {
134    type_path: PathNS::Type,
135    value_path: PathNS::Value,
136    macro_path: PathNS::Macro,
137}
138
139// Paths in the standard library missing a diagnostic item
140
141// Paths in external crates
142pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt);
143pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt);
144pub static ITERTOOLS_NEXT_TUPLE: PathLookup = value_path!(itertools::Itertools::next_tuple);
145pub static PARKING_LOT_GUARDS: [PathLookup; 3] = [
146    type_path!(lock_api::mutex::MutexGuard),
147    type_path!(lock_api::rwlock::RwLockReadGuard),
148    type_path!(lock_api::rwlock::RwLockWriteGuard),
149];
150pub static REGEX_BUILDER_NEW: PathLookup = value_path!(regex::RegexBuilder::new);
151pub static REGEX_BYTES_BUILDER_NEW: PathLookup = value_path!(regex::bytes::RegexBuilder::new);
152pub static REGEX_BYTES_NEW: PathLookup = value_path!(regex::bytes::Regex::new);
153pub static REGEX_BYTES_SET_NEW: PathLookup = value_path!(regex::bytes::RegexSet::new);
154pub static REGEX_NEW: PathLookup = value_path!(regex::Regex::new);
155pub static REGEX_SET_NEW: PathLookup = value_path!(regex::RegexSet::new);
156pub static SERDE_DESERIALIZE: PathLookup = type_path!(serde::de::Deserialize);
157pub static SERDE_DE_VISITOR: PathLookup = type_path!(serde::de::Visitor);
158pub static TOKIO_FILE_OPTIONS: PathLookup = value_path!(tokio::fs::File::options);
159pub static TOKIO_IO_ASYNCREADEXT: PathLookup = type_path!(tokio::io::AsyncReadExt);
160pub static TOKIO_IO_ASYNCWRITEEXT: PathLookup = type_path!(tokio::io::AsyncWriteExt);
161pub static TOKIO_IO_OPEN_OPTIONS: PathLookup = type_path!(tokio::fs::OpenOptions);
162pub static TOKIO_IO_OPEN_OPTIONS_NEW: PathLookup = value_path!(tokio::fs::OpenOptions::new);
163pub static LAZY_STATIC: PathLookup = macro_path!(lazy_static::lazy_static);
164pub static ONCE_CELL_SYNC_LAZY: PathLookup = type_path!(once_cell::sync::Lazy);
165pub static ONCE_CELL_SYNC_LAZY_NEW: PathLookup = value_path!(once_cell::sync::Lazy::new);
166
167// Paths for internal lints go in `clippy_lints_internal/src/internal_paths.rs`
168
169/// Equivalent to a [`lookup_path`] after splitting the input string on `::`
170///
171/// This function is expensive and should be used sparingly.
172pub fn lookup_path_str(tcx: TyCtxt<'_>, ns: PathNS, path: &str) -> Vec<DefId> {
173    let path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
174    lookup_path(tcx, ns, &path)
175}
176
177/// Resolves a def path like `std::vec::Vec`.
178///
179/// Typically it will return one [`DefId`] or none, but in some situations there can be multiple:
180/// - `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0
181/// - `alloc::boxed::Box::downcast` would return a function for each of the different inherent impls ([1], [2], [3])
182///
183/// This function is expensive and should be used sparingly.
184///
185/// [1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast
186/// [2]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-1
187/// [3]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast-2
188pub fn lookup_path(tcx: TyCtxt<'_>, ns: PathNS, path: &[Symbol]) -> Vec<DefId> {
189    let (root, rest) = match *path {
190        [] | [_] => return Vec::new(),
191        [root, ref rest @ ..] => (root, rest),
192    };
193
194    let mut out = Vec::new();
195    for &base in find_crates(tcx, root).iter().chain(find_primitive_impls(tcx, root)) {
196        lookup_with_base(tcx, base, ns, rest, &mut out);
197    }
198    out
199}
200
201/// Finds the crates called `name`, may be multiple due to multiple major versions.
202pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> &'static [DefId] {
203    static BY_NAME: OnceLock<FxHashMap<Symbol, Vec<DefId>>> = OnceLock::new();
204    let map = BY_NAME.get_or_init(|| {
205        let mut map = FxHashMap::default();
206        map.insert(tcx.crate_name(LOCAL_CRATE), vec![LOCAL_CRATE.as_def_id()]);
207        for &num in tcx.crates(()) {
208            map.entry(tcx.crate_name(num)).or_default().push(num.as_def_id());
209        }
210        map
211    });
212    match map.get(&name) {
213        Some(def_ids) => def_ids,
214        None => &[],
215    }
216}
217
218fn find_primitive_impls(tcx: TyCtxt<'_>, name: Symbol) -> &[DefId] {
219    let ty = match name {
220        sym::bool => SimplifiedType::Bool,
221        sym::char => SimplifiedType::Char,
222        sym::str => SimplifiedType::Str,
223        sym::array => SimplifiedType::Array,
224        sym::slice => SimplifiedType::Slice,
225        // FIXME: rustdoc documents these two using just `pointer`.
226        //
227        // Maybe this is something we should do here too.
228        sym::const_ptr => SimplifiedType::Ptr(Mutability::Not),
229        sym::mut_ptr => SimplifiedType::Ptr(Mutability::Mut),
230        sym::isize => SimplifiedType::Int(IntTy::Isize),
231        sym::i8 => SimplifiedType::Int(IntTy::I8),
232        sym::i16 => SimplifiedType::Int(IntTy::I16),
233        sym::i32 => SimplifiedType::Int(IntTy::I32),
234        sym::i64 => SimplifiedType::Int(IntTy::I64),
235        sym::i128 => SimplifiedType::Int(IntTy::I128),
236        sym::usize => SimplifiedType::Uint(UintTy::Usize),
237        sym::u8 => SimplifiedType::Uint(UintTy::U8),
238        sym::u16 => SimplifiedType::Uint(UintTy::U16),
239        sym::u32 => SimplifiedType::Uint(UintTy::U32),
240        sym::u64 => SimplifiedType::Uint(UintTy::U64),
241        sym::u128 => SimplifiedType::Uint(UintTy::U128),
242        sym::f32 => SimplifiedType::Float(FloatTy::F32),
243        sym::f64 => SimplifiedType::Float(FloatTy::F64),
244        _ => return &[],
245    };
246
247    tcx.incoherent_impls(ty)
248}
249
250/// Resolves a def path like `vec::Vec` with the base `std`.
251fn lookup_with_base(tcx: TyCtxt<'_>, mut base: DefId, ns: PathNS, mut path: &[Symbol], out: &mut Vec<DefId>) {
252    loop {
253        match *path {
254            [segment] => {
255                out.extend(item_child_by_name(tcx, base, ns, segment));
256
257                // When the current def_id is e.g. `struct S`, check the impl items in
258                // `impl S { ... }`
259                let inherent_impl_children = tcx
260                    .inherent_impls(base)
261                    .iter()
262                    .filter_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, ns, segment));
263                out.extend(inherent_impl_children);
264
265                return;
266            },
267            [segment, ref rest @ ..] => {
268                path = rest;
269                let Some(child) = item_child_by_name(tcx, base, PathNS::Type, segment) else {
270                    return;
271                };
272                base = child;
273            },
274            [] => unreachable!(),
275        }
276    }
277}
278
279fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
280    if let Some(local_id) = def_id.as_local() {
281        local_item_child_by_name(tcx, local_id, ns, name)
282    } else {
283        non_local_item_child_by_name(tcx, def_id, ns, name)
284    }
285}
286
287fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, name: Symbol) -> Option<DefId> {
288    let root_mod;
289    let item_kind = match tcx.hir_node_by_def_id(local_id) {
290        Node::Crate(r#mod) => {
291            root_mod = ItemKind::Mod(Ident::dummy(), r#mod);
292            &root_mod
293        },
294        Node::Item(item) => &item.kind,
295        Node::Variant(variant) if ns == PathNS::Field => {
296            return if let rustc_hir::VariantData::Struct { fields, .. } = variant.data
297                && let Some(field_def_id) = fields.iter().find_map(|field| {
298                    if field.ident.name == name {
299                        Some(field.def_id.to_def_id())
300                    } else {
301                        None
302                    }
303                }) {
304                Some(field_def_id)
305            } else {
306                None
307            };
308        },
309        _ => return None,
310    };
311
312    match item_kind {
313        ItemKind::Mod(_, r#mod) => r#mod.item_ids.iter().find_map(|&item_id| {
314            let item = tcx.hir_item(item_id);
315            if let ItemKind::Use(UseTree {
316                prefix,
317                kind: UseKind::Single(ident),
318            }) = item.kind
319            {
320                if ident.name == name {
321                    let opt_def_id = |ns: Option<Res>| ns.and_then(|res| res.opt_def_id());
322                    match ns {
323                        PathNS::Type => opt_def_id(prefix.res.type_ns),
324                        PathNS::Value => opt_def_id(prefix.res.value_ns),
325                        PathNS::Macro => opt_def_id(prefix.res.macro_ns),
326                        PathNS::Field => None,
327                        PathNS::Arbitrary => unreachable!(),
328                    }
329                } else {
330                    None
331                }
332            } else if let Some(ident) = item.kind.ident()
333                && ident.name == name
334                && ns.matches(tcx.def_kind(item.owner_id).ns())
335            {
336                Some(item.owner_id.to_def_id())
337            } else {
338                None
339            }
340        }),
341        ItemKind::Impl(..) | ItemKind::Trait { .. } => tcx
342            .associated_items(local_id)
343            .filter_by_name_unhygienic(name)
344            .find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
345            .map(|assoc_item| assoc_item.def_id),
346        ItemKind::Struct(_, _, rustc_hir::VariantData::Struct { fields, .. }) if ns == PathNS::Field => {
347            fields.iter().find_map(|field| {
348                if field.ident.name == name {
349                    Some(field.def_id.to_def_id())
350                } else {
351                    None
352                }
353            })
354        },
355        ItemKind::Enum(_, _, rustc_hir::EnumDef { variants }) if ns == PathNS::Type => {
356            variants.iter().find_map(|variant| {
357                if variant.ident.name == name {
358                    Some(variant.def_id.to_def_id())
359                } else {
360                    None
361                }
362            })
363        },
364        _ => None,
365    }
366}
367
368fn non_local_item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
369    match tcx.def_kind(def_id) {
370        DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx.module_children(def_id).iter().find_map(|child| {
371            if child.ident.name == name && ns.matches(child.res.ns()) {
372                child.res.opt_def_id()
373            } else {
374                None
375            }
376        }),
377        DefKind::Impl { .. } => tcx
378            .associated_item_def_ids(def_id)
379            .iter()
380            .copied()
381            .find(|&assoc_def_id| tcx.item_name(assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
382        DefKind::Struct => tcx
383            .associated_item_def_ids(def_id)
384            .iter()
385            .copied()
386            .find(|&assoc_def_id| tcx.item_name(assoc_def_id) == name),
387        _ => None,
388    }
389}