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};
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,
27 Value,
28 Macro,
29 Field,
30
31 Arbitrary,
37}
38
39impl PathNS {
40 fn matches(self, ns: Option<Namespace>) -> bool {
41 let required = match self {
42 PathNS::Type => TypeNS,
43 PathNS::Value => ValueNS,
44 PathNS::Macro => MacroNS,
45 PathNS::Field => return false,
46 PathNS::Arbitrary => return true,
47 };
48
49 ns == Some(required)
50 }
51}
52
53pub struct PathLookup {
64 ns: PathNS,
65 path: &'static [Symbol],
66 once: OnceLock<Vec<DefId>>,
67}
68
69impl PathLookup {
70 #[doc(hidden)]
72 pub const fn new(ns: PathNS, path: &'static [Symbol]) -> Self {
73 Self {
74 ns,
75 path,
76 once: OnceLock::new(),
77 }
78 }
79
80 pub fn get<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>) -> &[DefId] {
82 self.once.get_or_init(|| lookup_path(tcx.tcx(), self.ns, self.path))
83 }
84
85 pub fn only(&self, cx: &LateContext<'_>) -> Option<DefId> {
90 let ids = self.get(cx);
91 debug_assert!(STDLIB_STABLE_CRATES.contains(&self.path[0]));
92 debug_assert!(ids.len() <= 1, "{ids:?}");
93 ids.first().copied()
94 }
95
96 pub fn matches<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, def_id: DefId) -> bool {
98 self.get(&tcx.tcx()).contains(&def_id)
99 }
100
101 pub fn matches_path<'tcx>(&self, cx: &LateContext<'_>, maybe_path: impl MaybeQPath<'tcx>) -> bool {
103 maybe_path
104 .res(cx)
105 .opt_def_id()
106 .is_some_and(|def_id| self.matches(cx, def_id))
107 }
108
109 pub fn matches_ty<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, ty: Ty<'_>) -> bool {
111 ty.ty_adt_def().is_some_and(|adt| self.matches(&tcx.tcx(), adt.did()))
112 }
113}
114
115macro_rules! path_macros {
116 ($($name:ident: $ns:expr,)*) => {
117 $(
118 #[doc(hidden)]
120 #[macro_export]
121 macro_rules! $name {
122 ($$($$seg:ident $$(::)?)*) => {
123 PathLookup::new($ns, &[$$(sym::$$seg,)*])
124 };
125 }
126 )*
127 };
128}
129
130path_macros! {
131 type_path: PathNS::Type,
132 value_path: PathNS::Value,
133 macro_path: PathNS::Macro,
134}
135
136pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt);
138pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt);
139pub static ITERTOOLS_NEXT_TUPLE: PathLookup = value_path!(itertools::Itertools::next_tuple);
140pub static PARKING_LOT_GUARDS: [PathLookup; 3] = [
141 type_path!(lock_api::mutex::MutexGuard),
142 type_path!(lock_api::rwlock::RwLockReadGuard),
143 type_path!(lock_api::rwlock::RwLockWriteGuard),
144];
145pub static REGEX_BUILDER_NEW: PathLookup = value_path!(regex::RegexBuilder::new);
146pub static REGEX_BYTES_BUILDER_NEW: PathLookup = value_path!(regex::bytes::RegexBuilder::new);
147pub static REGEX_BYTES_NEW: PathLookup = value_path!(regex::bytes::Regex::new);
148pub static REGEX_BYTES_SET_NEW: PathLookup = value_path!(regex::bytes::RegexSet::new);
149pub static REGEX_NEW: PathLookup = value_path!(regex::Regex::new);
150pub static REGEX_SET_NEW: PathLookup = value_path!(regex::RegexSet::new);
151pub static SERDE_DESERIALIZE: PathLookup = type_path!(serde::de::Deserialize);
152pub static SERDE_DE_VISITOR: PathLookup = type_path!(serde::de::Visitor);
153pub static TOKIO_FILE_OPTIONS: PathLookup = value_path!(tokio::fs::File::options);
154pub static TOKIO_IO_ASYNCREADEXT: PathLookup = type_path!(tokio::io::AsyncReadExt);
155pub static TOKIO_IO_ASYNCWRITEEXT: PathLookup = type_path!(tokio::io::AsyncWriteExt);
156pub static TOKIO_IO_OPEN_OPTIONS: PathLookup = type_path!(tokio::fs::OpenOptions);
157pub static TOKIO_IO_OPEN_OPTIONS_NEW: PathLookup = value_path!(tokio::fs::OpenOptions::new);
158pub static LAZY_STATIC: PathLookup = macro_path!(lazy_static::lazy_static);
159pub static ONCE_CELL_SYNC_LAZY: PathLookup = type_path!(once_cell::sync::Lazy);
160pub static ONCE_CELL_SYNC_LAZY_NEW: PathLookup = value_path!(once_cell::sync::Lazy::new);
161
162pub fn lookup_path_str(tcx: TyCtxt<'_>, ns: PathNS, path: &str) -> Vec<DefId> {
168 let path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
169 lookup_path(tcx, ns, &path)
170}
171
172pub fn lookup_path(tcx: TyCtxt<'_>, ns: PathNS, path: &[Symbol]) -> Vec<DefId> {
185 let (root, rest) = match *path {
186 [] | [_] => return Vec::new(),
187 [root, ref rest @ ..] => (root, rest),
188 };
189
190 let mut out = Vec::new();
191 for &base in find_crates(tcx, root).iter().chain(find_primitive_impls(tcx, root)) {
192 lookup_with_base(tcx, base, ns, rest, &mut out);
193 }
194 out
195}
196
197pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> &'static [DefId] {
199 static BY_NAME: OnceLock<FxHashMap<Symbol, Vec<DefId>>> = OnceLock::new();
200 let map = BY_NAME.get_or_init(|| {
201 let mut map = FxHashMap::default();
202 map.insert(tcx.crate_name(LOCAL_CRATE), vec![LOCAL_CRATE.as_def_id()]);
203 for &num in tcx.crates(()) {
204 map.entry(tcx.crate_name(num)).or_default().push(num.as_def_id());
205 }
206 map
207 });
208 match map.get(&name) {
209 Some(def_ids) => def_ids,
210 None => &[],
211 }
212}
213
214fn find_primitive_impls(tcx: TyCtxt<'_>, name: Symbol) -> &[DefId] {
215 let ty = match name {
216 sym::bool => SimplifiedType::Bool,
217 sym::char => SimplifiedType::Char,
218 sym::str => SimplifiedType::Str,
219 sym::array => SimplifiedType::Array,
220 sym::slice => SimplifiedType::Slice,
221 sym::const_ptr => SimplifiedType::Ptr(Mutability::Not),
225 sym::mut_ptr => SimplifiedType::Ptr(Mutability::Mut),
226 sym::isize => SimplifiedType::Int(IntTy::Isize),
227 sym::i8 => SimplifiedType::Int(IntTy::I8),
228 sym::i16 => SimplifiedType::Int(IntTy::I16),
229 sym::i32 => SimplifiedType::Int(IntTy::I32),
230 sym::i64 => SimplifiedType::Int(IntTy::I64),
231 sym::i128 => SimplifiedType::Int(IntTy::I128),
232 sym::usize => SimplifiedType::Uint(UintTy::Usize),
233 sym::u8 => SimplifiedType::Uint(UintTy::U8),
234 sym::u16 => SimplifiedType::Uint(UintTy::U16),
235 sym::u32 => SimplifiedType::Uint(UintTy::U32),
236 sym::u64 => SimplifiedType::Uint(UintTy::U64),
237 sym::u128 => SimplifiedType::Uint(UintTy::U128),
238 sym::f32 => SimplifiedType::Float(FloatTy::F32),
239 sym::f64 => SimplifiedType::Float(FloatTy::F64),
240 _ => return &[],
241 };
242
243 tcx.incoherent_impls(ty)
244}
245
246fn lookup_with_base(tcx: TyCtxt<'_>, mut base: DefId, ns: PathNS, mut path: &[Symbol], out: &mut Vec<DefId>) {
248 loop {
249 match *path {
250 [segment] => {
251 out.extend(item_child_by_name(tcx, base, ns, segment));
252
253 let inherent_impl_children = tcx
256 .inherent_impls(base)
257 .iter()
258 .filter_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, ns, segment));
259 out.extend(inherent_impl_children);
260
261 return;
262 },
263 [segment, ref rest @ ..] => {
264 path = rest;
265 let Some(child) = item_child_by_name(tcx, base, PathNS::Type, segment) else {
266 return;
267 };
268 base = child;
269 },
270 [] => unreachable!(),
271 }
272 }
273}
274
275fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
276 if let Some(local_id) = def_id.as_local() {
277 local_item_child_by_name(tcx, local_id, ns, name)
278 } else {
279 non_local_item_child_by_name(tcx, def_id, ns, name)
280 }
281}
282
283fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, name: Symbol) -> Option<DefId> {
284 let root_mod;
285 let item_kind = match tcx.hir_node_by_def_id(local_id) {
286 Node::Crate(r#mod) => {
287 root_mod = ItemKind::Mod(Ident::dummy(), r#mod);
288 &root_mod
289 },
290 Node::Item(item) => &item.kind,
291 Node::Variant(variant) if ns == PathNS::Field => {
292 return if let rustc_hir::VariantData::Struct { fields, .. } = variant.data
293 && let Some(field_def_id) = fields.iter().find_map(|field| {
294 if field.ident.name == name {
295 Some(field.def_id.to_def_id())
296 } else {
297 None
298 }
299 }) {
300 Some(field_def_id)
301 } else {
302 None
303 };
304 },
305 _ => return None,
306 };
307
308 match item_kind {
309 ItemKind::Mod(_, r#mod) => r#mod.item_ids.iter().find_map(|&item_id| {
310 let item = tcx.hir_item(item_id);
311 if let ItemKind::Use(path, UseKind::Single(ident)) = item.kind {
312 if ident.name == name {
313 let opt_def_id = |ns: Option<Res>| ns.and_then(|res| res.opt_def_id());
314 match ns {
315 PathNS::Type => opt_def_id(path.res.type_ns),
316 PathNS::Value => opt_def_id(path.res.value_ns),
317 PathNS::Macro => opt_def_id(path.res.macro_ns),
318 PathNS::Field => None,
319 PathNS::Arbitrary => unreachable!(),
320 }
321 } else {
322 None
323 }
324 } else if let Some(ident) = item.kind.ident()
325 && ident.name == name
326 && ns.matches(tcx.def_kind(item.owner_id).ns())
327 {
328 Some(item.owner_id.to_def_id())
329 } else {
330 None
331 }
332 }),
333 ItemKind::Impl(..) | ItemKind::Trait(..) => tcx
334 .associated_items(local_id)
335 .filter_by_name_unhygienic(name)
336 .find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
337 .map(|assoc_item| assoc_item.def_id),
338 ItemKind::Struct(_, _, rustc_hir::VariantData::Struct { fields, .. }) if ns == PathNS::Field => {
339 fields.iter().find_map(|field| {
340 if field.ident.name == name {
341 Some(field.def_id.to_def_id())
342 } else {
343 None
344 }
345 })
346 },
347 ItemKind::Enum(_, _, rustc_hir::EnumDef { variants }) if ns == PathNS::Type => {
348 variants.iter().find_map(|variant| {
349 if variant.ident.name == name {
350 Some(variant.def_id.to_def_id())
351 } else {
352 None
353 }
354 })
355 },
356 _ => None,
357 }
358}
359
360fn non_local_item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name: Symbol) -> Option<DefId> {
361 match tcx.def_kind(def_id) {
362 DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx.module_children(def_id).iter().find_map(|child| {
363 if child.ident.name == name && ns.matches(child.res.ns()) {
364 child.res.opt_def_id()
365 } else {
366 None
367 }
368 }),
369 DefKind::Impl { .. } => tcx
370 .associated_item_def_ids(def_id)
371 .iter()
372 .copied()
373 .find(|&assoc_def_id| tcx.item_name(assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
374 DefKind::Struct => tcx
375 .associated_item_def_ids(def_id)
376 .iter()
377 .copied()
378 .find(|&assoc_def_id| tcx.item_name(assoc_def_id) == name),
379 _ => None,
380 }
381}