1use 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#[derive(Clone, Copy, PartialEq, Debug)]
25pub enum PathNS {
26 Type,
28 Value,
30 Macro,
32 Field,
34
35 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
57pub struct PathLookup {
67 ns: PathNS,
68 path: &'static [Symbol],
69 once: OnceLock<Vec<DefId>>,
70}
71
72impl PathLookup {
73 #[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 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 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 pub fn matches<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, def_id: DefId) -> bool {
101 self.get(&tcx.tcx()).contains(&def_id)
102 }
103
104 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 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 #[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
139pub 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
167pub 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
177pub 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
201pub 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 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
250fn 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 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}