trustfall_rustdoc_adapter/
indexed_crate.rs

1use std::{borrow::Borrow, collections::hash_map::Entry, sync::Arc};
2
3#[cfg(not(feature = "rustc-hash"))]
4use std::collections::{HashMap, HashSet};
5
6#[cfg(feature = "rustc-hash")]
7use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
8
9#[cfg(feature = "rayon")]
10use rayon::prelude::*;
11use rustdoc_types::{Crate, Id, Item};
12
13use crate::{
14    adapter::supported_item_kind,
15    item_flags::{build_flags_index, ItemFlag},
16    visibility_tracker::VisibilityTracker,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub(crate) struct DependencyKey(Arc<str>);
21
22impl Borrow<str> for DependencyKey {
23    fn borrow(&self) -> &str {
24        &self.0
25    }
26}
27
28impl Borrow<Arc<str>> for DependencyKey {
29    fn borrow(&self) -> &Arc<str> {
30        &self.0
31    }
32}
33
34#[derive(Debug, Clone)]
35pub(crate) struct PackageData {
36    pub(crate) package: cargo_metadata::Package,
37
38    features: HashMap<String, Vec<String>>,
39
40    // (dependency, target selector), parallel to `package.dependencies`
41    dependency_info: Vec<(cargo_toml::Dependency, Option<String>)>,
42}
43
44impl From<cargo_metadata::Package> for PackageData {
45    fn from(value: cargo_metadata::Package) -> Self {
46        let features = value
47            .features
48            .iter()
49            .map(|(k, v)| (k.clone(), v.clone()))
50            .collect();
51        let dependency_info: Vec<_> = value
52            .dependencies
53            .iter()
54            .map(|dep| {
55                let dependency = if dep.features.is_empty() {
56                    cargo_toml::Dependency::Simple(dep.req.to_string())
57                } else {
58                    cargo_toml::Dependency::Detailed(Box::new(cargo_toml::DependencyDetail {
59                        package: dep.rename.is_none().then(|| dep.name.clone()),
60                        version: (dep.req != cargo_metadata::semver::VersionReq::STAR)
61                            .then(|| dep.req.to_string()),
62                        features: dep.features.clone(),
63                        default_features: dep.uses_default_features,
64                        optional: dep.optional,
65                        path: dep.path.as_ref().map(|p| p.to_string()),
66                        ..Default::default()
67                    }))
68                };
69
70                (dependency, dep.target.as_ref().map(|p| p.to_string()))
71            })
72            .collect();
73
74        Self {
75            package: value,
76            features,
77            dependency_info,
78        }
79    }
80}
81
82#[derive(Debug, Clone)]
83pub struct PackageStorage {
84    pub(crate) own_crate: Crate,
85    pub(crate) package_data: Option<PackageData>,
86    pub(crate) dependencies: HashMap<DependencyKey, Crate>,
87}
88
89impl PackageStorage {
90    pub fn crate_version(&self) -> Option<&str> {
91        self.own_crate.crate_version.as_deref()
92    }
93
94    pub fn from_rustdoc(own_crate: Crate) -> Self {
95        Self {
96            own_crate,
97            package_data: None,
98            dependencies: Default::default(),
99        }
100    }
101
102    pub fn from_rustdoc_and_package(own_crate: Crate, package: cargo_metadata::Package) -> Self {
103        Self {
104            own_crate,
105            package_data: Some(package.into()),
106            dependencies: Default::default(),
107        }
108    }
109}
110
111#[non_exhaustive]
112#[derive(Debug)]
113pub struct PackageIndex<'a> {
114    pub(crate) own_crate: IndexedCrate<'a>,
115    pub(crate) features: Option<cargo_toml::features::Features<'a, 'a>>,
116    #[allow(dead_code)]
117    pub(crate) dependencies: HashMap<DependencyKey, IndexedCrate<'a>>,
118}
119
120impl<'a> PackageIndex<'a> {
121    /// Create a new [`PackageIndex`] for a given crate, in order to query it with Trustfall.
122    ///
123    /// Prefer the [`PackageIndex::from_storage`] function when possible, since it makes features
124    /// information available as well. Values constructed with the [`PackageIndex::from_crate`]
125    /// function will appear to have no information on features or other manifest data.
126    pub fn from_crate(crate_: &'a Crate) -> Self {
127        Self {
128            own_crate: IndexedCrate::new(crate_),
129            features: None,
130            dependencies: Default::default(),
131        }
132    }
133
134    /// Create a new [`PackageIndex`] for a given crate, in order to query it with Trustfall.
135    pub fn from_storage(storage: &'a PackageStorage) -> Self {
136        #[cfg(not(feature = "rayon"))]
137        let dependencies_iter = storage.dependencies.iter();
138        #[cfg(feature = "rayon")]
139        let dependencies_iter = storage.dependencies.par_iter();
140
141        Self {
142            own_crate: IndexedCrate::new(&storage.own_crate),
143            features: storage.package_data.as_ref().map(|data| {
144                let resolver = cargo_toml::features::Resolver::new();
145
146                let dependencies = data
147                    .package
148                    .dependencies
149                    .iter()
150                    .zip(data.dependency_info.iter())
151                    .filter_map(|(dep, (dep_data, platform))| {
152                        Some(cargo_toml::features::ParseDependency {
153                            key: dep.rename.as_deref().unwrap_or(dep.name.as_ref()),
154                            kind: match dep.kind {
155                                cargo_metadata::DependencyKind::Normal => {
156                                    cargo_toml::features::Kind::Normal
157                                }
158                                cargo_metadata::DependencyKind::Development => {
159                                    cargo_toml::features::Kind::Dev
160                                }
161                                cargo_metadata::DependencyKind::Build => {
162                                    cargo_toml::features::Kind::Build
163                                }
164                                _ => return None,
165                            },
166                            target: platform.as_deref(),
167                            dep: dep_data,
168                        })
169                    });
170
171                resolver.parse_custom(&data.features, dependencies)
172            }),
173
174            dependencies: dependencies_iter
175                .map(|(k, v)| (k.clone(), IndexedCrate::new(v)))
176                .collect(),
177        }
178    }
179}
180
181/// The rustdoc for a crate, together with associated indexed data to speed up common operations.
182///
183/// Besides the parsed rustdoc, it also contains some manually-inlined `rustdoc_types::Trait`s
184/// of the most common built-in traits.
185/// This is a temporary step, until we're able to combine rustdocs of multiple crates.
186#[derive(Debug, Clone)]
187pub struct IndexedCrate<'a> {
188    pub(crate) inner: &'a Crate,
189
190    /// Track which items are publicly visible and under which names.
191    pub(crate) visibility_tracker: VisibilityTracker<'a>,
192
193    /// index: importable name (in any namespace) -> list of items under that name
194    pub(crate) imports_index: Option<HashMap<Path<'a>, Vec<(&'a Item, Modifiers)>>>,
195
196    /// index: item ID -> bit flags recording yes-no indicators for various item states
197    pub(crate) flags: Option<HashMap<Id, ItemFlag>>,
198
199    /// index: impl owner + impl'd item name -> list of (impl itself, the named item))
200    pub(crate) impl_index: Option<HashMap<ImplEntry<'a>, Vec<(&'a Item, &'a Item)>>>,
201
202    /// index: method ("owned function") `Id` -> the struct/enum/union/trait that defines it;
203    /// functions at top level will not have an index entry here
204    pub(crate) fn_owner_index: Option<HashMap<Id, &'a Item>>,
205
206    /// index: function export name (`#[no_mangle]` or `#[export_name = "..."]` attribute)
207    /// -> function item with that export name; exported symbol names must be unique.
208    pub(crate) export_name_index: Option<HashMap<&'a str, &'a Item>>,
209
210    /// Trait items defined in external crates are not present in the `inner: &Crate` field,
211    /// even if they are implemented by a type in that crate. This also includes
212    /// Rust's built-in traits like `Debug, Send, Eq` etc.
213    ///
214    /// This change is approximately as of rustdoc v23,
215    /// in <https://github.com/rust-lang/rust/pull/105182>
216    ///
217    /// As a temporary workaround, we manually create the trait items
218    /// for the most common Rust built-in traits and link to those items
219    /// as if they were still part of the rustdoc JSON file.
220    ///
221    /// A more complete future solution may generate multiple crates' rustdoc JSON
222    /// and link to the external crate's trait items as necessary.
223    pub(crate) manually_inlined_builtin_traits: HashMap<Id, Item>,
224}
225
226/// Map a Key to a List (Vec) of values
227///
228/// It also has some nice operations for pushing a value to the list, or extending the list with
229/// many values.
230struct MapList<K, V>(HashMap<K, Vec<V>>);
231
232#[cfg(feature = "rayon")]
233impl<K: std::cmp::Eq + std::hash::Hash + Send, V: Send> FromParallelIterator<(K, V)>
234    for MapList<K, V>
235{
236    #[inline]
237    fn from_par_iter<I>(par_iter: I) -> Self
238    where
239        I: IntoParallelIterator<Item = (K, V)>,
240    {
241        par_iter
242            .into_par_iter()
243            .fold(Self::new, |mut map, (key, value)| {
244                map.insert(key, value);
245                map
246            })
247            // Reduce left is faster than reduce right (about 19% less time in our benchmarks)
248            .reduce(Self::new, |mut l, r| {
249                l.merge(r);
250                l
251            })
252    }
253}
254
255impl<K: std::cmp::Eq + std::hash::Hash, V> FromIterator<(K, V)> for MapList<K, V> {
256    #[inline]
257    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
258        // We could use Iterator::size_hint here to preallocate some space, but I couldn't measure
259        // a perf inprovement from that.
260        let mut map = Self::new();
261        for (key, value) in iter {
262            map.insert(key, value);
263        }
264        map
265    }
266}
267
268impl<K: std::cmp::Eq + std::hash::Hash, V> Extend<(K, V)> for MapList<K, V> {
269    #[inline]
270    fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
271        // We could use Iterator::size_hint here to reserve some space, but I measured a 2%-3%
272        // regression when doing that.
273        for (key, value) in iter.into_iter() {
274            self.insert(key, value);
275        }
276    }
277}
278
279impl<K: std::cmp::Eq + std::hash::Hash, V> MapList<K, V> {
280    #[inline]
281    pub fn new() -> Self {
282        Self(HashMap::default())
283    }
284
285    #[inline]
286    pub fn into_inner(self) -> HashMap<K, Vec<V>> {
287        self.0
288    }
289
290    #[inline]
291    pub fn insert(&mut self, key: K, value: V) {
292        match self.0.entry(key) {
293            Entry::Occupied(mut entry) => entry.get_mut().push(value),
294            Entry::Vacant(entry) => {
295                entry.insert(vec![value]);
296            }
297        }
298    }
299
300    #[inline]
301    #[cfg(feature = "rayon")]
302    pub fn insert_many(&mut self, key: K, mut value: Vec<V>) {
303        match self.0.entry(key) {
304            Entry::Occupied(mut entry) => entry.get_mut().append(&mut value),
305            Entry::Vacant(entry) => {
306                entry.insert(value);
307            }
308        }
309    }
310
311    #[inline]
312    #[cfg(feature = "rayon")]
313    pub fn merge(&mut self, other: Self) {
314        self.0.reserve(other.0.len());
315        for (key, value) in other.0 {
316            self.insert_many(key, value);
317        }
318    }
319}
320
321/// Build the impl index
322///
323/// When compiled using the `rayon` feature, build it in parallel. Specifically, this paralelizes
324/// the work of gathering all of the impls for the items in the index.
325fn build_impl_index(index: &HashMap<Id, Item>) -> MapList<ImplEntry<'_>, (&Item, &Item)> {
326    #[cfg(feature = "rayon")]
327    let iter = index.par_iter();
328    #[cfg(not(feature = "rayon"))]
329    let iter = index.iter();
330    iter.filter_map(|(id, item)| {
331        let impls = match &item.inner {
332            rustdoc_types::ItemEnum::Struct(s) => s.impls.as_slice(),
333            rustdoc_types::ItemEnum::Enum(e) => e.impls.as_slice(),
334            rustdoc_types::ItemEnum::Union(u) => u.impls.as_slice(),
335            _ => return None,
336        };
337
338        #[cfg(feature = "rayon")]
339        let iter = impls.par_iter();
340        #[cfg(not(feature = "rayon"))]
341        let iter = impls.iter();
342
343        Some((id, iter.filter_map(|impl_id| index.get(impl_id))))
344    })
345    .flat_map(|(id, impl_items)| {
346        impl_items.flat_map(move |impl_item| {
347            let impl_inner = match &impl_item.inner {
348                rustdoc_types::ItemEnum::Impl(impl_inner) => impl_inner,
349                _ => unreachable!("expected impl but got another item type: {impl_item:?}"),
350            };
351            let trait_provided_methods: HashSet<_> = impl_inner
352                .provided_trait_methods
353                .iter()
354                .map(|x| x.as_str())
355                .collect();
356
357            let trait_items = impl_inner
358                .trait_
359                .as_ref()
360                .and_then(|trait_path| index.get(&trait_path.id))
361                .map(move |trait_item| {
362                    if let rustdoc_types::ItemEnum::Trait(trait_item) = &trait_item.inner {
363                        trait_item.items.as_slice()
364                    } else {
365                        &[]
366                    }
367                })
368                .unwrap_or(&[]);
369
370            #[cfg(feature = "rayon")]
371            let trait_items = trait_items.par_iter();
372            #[cfg(not(feature = "rayon"))]
373            let trait_items = trait_items.iter();
374
375            let trait_provided_items = trait_items
376                .filter_map(|id| index.get(id))
377                .filter(move |item| {
378                    item.name
379                        .as_deref()
380                        .map(|name| trait_provided_methods.contains(name))
381                        .unwrap_or_default()
382                })
383                .map(move |provided_item| {
384                    (
385                        ImplEntry::new(
386                            id,
387                            provided_item
388                                .name
389                                .as_deref()
390                                .expect("item should have had a name"),
391                        ),
392                        (impl_item, provided_item),
393                    )
394                });
395
396            #[cfg(feature = "rayon")]
397            let impl_items = impl_inner.items.par_iter();
398            #[cfg(not(feature = "rayon"))]
399            let impl_items = impl_inner.items.iter();
400
401            impl_items
402                .filter_map(move |item_id| {
403                    let item = index.get(item_id)?;
404                    let item_name = item.name.as_deref()?;
405                    Some((ImplEntry::new(id, item_name), (impl_item, item)))
406                })
407                .chain(trait_provided_items)
408        })
409    })
410    .collect()
411}
412
413impl<'a> IndexedCrate<'a> {
414    pub fn new(crate_: &'a Crate) -> Self {
415        let mut value = Self {
416            inner: crate_,
417            visibility_tracker: VisibilityTracker::from_crate(crate_),
418            manually_inlined_builtin_traits: create_manually_inlined_builtin_traits(crate_),
419            flags: None,
420            imports_index: None,
421            impl_index: None,
422            fn_owner_index: None,
423            export_name_index: None,
424        };
425
426        debug_assert!(
427            !value.manually_inlined_builtin_traits.is_empty(),
428            "failed to find any traits to manually inline",
429        );
430
431        // Build the imports index
432        //
433        // This is inlined because we need access to `value`, but `value` is not a valid
434        // `IndexedCrate` yet. Do not extract into a separate function.
435        #[cfg(feature = "rayon")]
436        let iter = crate_.index.par_iter();
437        #[cfg(not(feature = "rayon"))]
438        let iter = crate_.index.iter();
439
440        let imports_index = iter
441            .filter_map(|(_id, item)| {
442                if !supported_item_kind(item) {
443                    return None;
444                }
445                let importable_paths = value.publicly_importable_names(&item.id);
446
447                #[cfg(feature = "rayon")]
448                let iter = importable_paths.into_par_iter();
449                #[cfg(not(feature = "rayon"))]
450                let iter = importable_paths.into_iter();
451
452                Some(iter.map(move |importable_path| {
453                    (importable_path.path, (item, importable_path.modifiers))
454                }))
455            })
456            .flatten()
457            .collect::<MapList<_, _>>()
458            .into_inner();
459        value.flags = Some(build_flags_index(&crate_.index, &imports_index));
460        value.imports_index = Some(imports_index);
461
462        value.impl_index = Some(build_impl_index(&crate_.index).into_inner());
463        value.fn_owner_index = Some(build_fn_owner_index(&crate_.index));
464        value.export_name_index = Some(build_export_name_index(&crate_.index));
465
466        value
467    }
468
469    /// Return all the paths with which the given item can be imported from this crate.
470    pub fn publicly_importable_names(&self, id: &'a Id) -> Vec<ImportablePath<'a>> {
471        if self.inner.index.contains_key(id) {
472            self.visibility_tracker
473                .collect_publicly_importable_names(id.0)
474        } else {
475            Default::default()
476        }
477    }
478
479    /// Return `true` if our analysis indicates the trait is sealed, and `false` otherwise.
480    ///
481    /// Our analysis is conservative: it has false-negatives but no false-positives.
482    /// If this method returns `true`, the trait is *definitely* sealed or else you've found a bug.
483    /// It may be possible to construct traits that *technically* are sealed for which our analysis
484    /// returns `false`.
485    ///
486    /// The goal of this method is to reflect author intent, not technicalities.
487    /// When Rustaceans seal traits on purpose, they do so with a limited number of techniques
488    /// that are well-defined and immediately recognizable to readers in the community:
489    /// <https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/>
490    ///
491    /// The analysis here looks for such techniques, which are always applied at the type signature
492    /// level. It does not inspect function bodies or do interprocedural analysis.
493    ///
494    /// ## Panics
495    ///
496    /// This method will panic if the provided `Id` is not an item in this crate.
497    ///
498    /// If the provided `Id` is not a trait, the result is sound but not specified.
499    /// It could be any return value or a panic, but not undefined behavior.
500    pub fn is_trait_sealed(&self, id: &'a Id) -> bool {
501        self.flags
502            .as_ref()
503            .expect("flags index was never constructed")[id]
504            .is_unconditionally_sealed()
505    }
506
507    /// Report whether our analysis indicates the trait can be implemented within public API.
508    ///
509    /// A trait can be implemented within public API if the trait is not sealed, and implementing it
510    /// does not require using any non-public-API items in the `impl`. A non-public-API item is
511    /// one that is `#[doc(hidden)]` but not `#[deprecated]`.
512    ///
513    /// Our analysis is conservative: it has false-negatives but no false-positives.
514    /// If this method returns `true`, the trait is *definitely* not implementable within public API
515    /// or else you've found a bug. It may be possible to construct traits that *technically*
516    /// are not implementable within public API for which our analysis returns `false`.
517    ///
518    /// Our analysis does not inspect function bodies or do interprocedural analysis.
519    /// The same caveats apply as for the [`Self::is_trait_sealed`] function above.
520    ///
521    /// ## Panics
522    ///
523    /// This method will panic if the provided `Id` is not an item in this crate.
524    ///
525    /// If the provided `Id` is not a trait, the result is sound but not specified.
526    /// It could be any return value or a panic, but not undefined behavior.
527    pub fn is_trait_public_api_sealed(&self, id: &'a Id) -> bool {
528        !self
529            .flags
530            .as_ref()
531            .expect("flags index was never constructed")[id]
532            .is_pub_api_implementable()
533    }
534}
535
536fn build_fn_owner_index(index: &HashMap<Id, Item>) -> HashMap<Id, &Item> {
537    #[cfg(feature = "rayon")]
538    let iter = index.par_iter().map(|(_, value)| value);
539    #[cfg(not(feature = "rayon"))]
540    let iter = index.values();
541
542    iter.flat_map(|owner_item| {
543        if let rustdoc_types::ItemEnum::Trait(value) = &owner_item.inner {
544            #[cfg(feature = "rayon")]
545            let trait_items = value.items.par_iter();
546            #[cfg(not(feature = "rayon"))]
547            let trait_items = value.items.iter();
548
549            let output = trait_items
550                // Try to resolve each trait item ID to an item in the index.
551                // In principle, this *should* always find a match, but we don't want to crash
552                // if rustdoc happens to omit an item due to a bug.
553                .filter_map(|id| index.get(id))
554                // Only keep the functions inside.
555                .filter_map(move |inner_item| match &inner_item.inner {
556                    rustdoc_types::ItemEnum::Function(..) => Some((inner_item.id, owner_item)),
557                    _ => None,
558                });
559
560            #[cfg(feature = "rayon")]
561            let return_value = rayon::iter::Either::Left(output);
562            #[cfg(not(feature = "rayon"))]
563            let return_value: Box<dyn Iterator<Item = (Id, &Item)>> = Box::new(output);
564
565            return_value
566        } else {
567            let impls = match &owner_item.inner {
568                rustdoc_types::ItemEnum::Union(value) => value.impls.as_slice(),
569                rustdoc_types::ItemEnum::Struct(value) => value.impls.as_slice(),
570                rustdoc_types::ItemEnum::Enum(value) => value.impls.as_slice(),
571                _ => &[],
572            };
573
574            #[cfg(feature = "rayon")]
575            let impl_iter = impls.par_iter();
576            #[cfg(not(feature = "rayon"))]
577            let impl_iter = impls.iter();
578
579            // Resolve the impl block, if we can find it in the index.
580            // In principle, this *should* always find a match, but we don't want to crash
581            // if rustdoc happens to omit an item due to a bug.
582            let output = impl_iter
583                .filter_map(|id| index.get(id))
584                // Get the IDs of the items inside it.
585                .flat_map(|impl_item| match &impl_item.inner {
586                    rustdoc_types::ItemEnum::Impl(contents) => contents.items.as_slice(),
587                    _ => &[],
588                })
589                // Resolve each item, if we can find it in the index.
590                // In principle, this *should* always find a match, but we don't want to crash
591                // if rustdoc happens to omit an item due to a bug.
592                .filter_map(|id| index.get(id))
593                // Only keep the functions inside.
594                .filter_map(move |item| match &item.inner {
595                    rustdoc_types::ItemEnum::Function(..) => Some((item.id, owner_item)),
596                    _ => None,
597                });
598
599            #[cfg(feature = "rayon")]
600            let return_value = rayon::iter::Either::Right(output);
601            #[cfg(not(feature = "rayon"))]
602            let return_value: Box<dyn Iterator<Item = (Id, &Item)>> = Box::new(output);
603
604            return_value
605        }
606    })
607    .collect()
608}
609
610fn build_export_name_index(index: &HashMap<Id, Item>) -> HashMap<&str, &Item> {
611    #[cfg(feature = "rayon")]
612    let iter = index.par_iter().map(|(_, value)| value);
613    #[cfg(not(feature = "rayon"))]
614    let iter = index.values();
615
616    iter.filter_map(|item| {
617        if !matches!(
618            item.inner,
619            rustdoc_types::ItemEnum::Function(..) | rustdoc_types::ItemEnum::Static(..)
620        ) {
621            return None;
622        }
623
624        crate::exported_name::item_export_name(item).map(move |name| (name, item))
625    })
626    .collect()
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
630#[non_exhaustive]
631pub struct Path<'a> {
632    pub(crate) components: Vec<&'a str>,
633}
634
635impl<'a> Path<'a> {
636    fn new(components: Vec<&'a str>) -> Self {
637        Self { components }
638    }
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
642#[non_exhaustive]
643pub struct Modifiers {
644    pub(crate) doc_hidden: bool,
645    pub(crate) deprecated: bool,
646}
647
648#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
649#[non_exhaustive]
650pub struct ImportablePath<'a> {
651    pub(crate) path: Path<'a>,
652    pub(crate) modifiers: Modifiers,
653}
654
655impl<'a> ImportablePath<'a> {
656    pub(crate) fn new(components: Vec<&'a str>, doc_hidden: bool, deprecated: bool) -> Self {
657        Self {
658            path: Path::new(components),
659            modifiers: Modifiers {
660                doc_hidden,
661                deprecated,
662            },
663        }
664    }
665
666    pub(crate) fn public_api(&self) -> bool {
667        self.modifiers.deprecated || !self.modifiers.doc_hidden
668    }
669}
670
671impl<'a: 'b, 'b> Borrow<[&'b str]> for Path<'a> {
672    fn borrow(&self) -> &[&'b str] {
673        &self.components
674    }
675}
676
677#[derive(Debug, Clone, PartialEq, Eq, Hash)]
678pub(crate) struct ImplEntry<'a> {
679    /// Tuple of:
680    /// - the Id of the struct/enum/union that owns the item,
681    /// - the name of the item in the owner's `impl` block.
682    ///
683    /// Stored as a tuple to make the `Borrow` impl work.
684    pub(crate) data: (&'a Id, &'a str),
685}
686
687impl<'a> ImplEntry<'a> {
688    #[inline]
689    fn new(owner_id: &'a Id, item_name: &'a str) -> Self {
690        Self {
691            data: (owner_id, item_name),
692        }
693    }
694
695    #[allow(dead_code)]
696    #[inline]
697    pub(crate) fn owner_id(&self) -> &'a Id {
698        self.data.0
699    }
700
701    #[allow(dead_code)]
702    #[inline]
703    pub(crate) fn item_name(&self) -> &'a str {
704        self.data.1
705    }
706}
707
708impl<'a: 'b, 'b> Borrow<(&'b Id, &'b str)> for ImplEntry<'a> {
709    fn borrow(&self) -> &(&'b Id, &'b str) {
710        &(self.data)
711    }
712}
713
714#[derive(Debug)]
715struct ManualTraitItem {
716    name: &'static str,
717    path: &'static [&'static str],
718    is_auto: bool,
719    is_unsafe: bool,
720}
721
722/// Limiting the creation of manually inlined traits to only those that are used by the lints.
723/// There are other foreign traits, but it is not obvious how the manually inlined traits
724/// should look like for them.
725const MANUAL_TRAIT_ITEMS: [ManualTraitItem; 14] = [
726    ManualTraitItem {
727        name: "Debug",
728        path: &["core", "fmt", "Debug"],
729        is_auto: false,
730        is_unsafe: false,
731    },
732    ManualTraitItem {
733        name: "Clone",
734        path: &["core", "clone", "Clone"],
735        is_auto: false,
736        is_unsafe: false,
737    },
738    ManualTraitItem {
739        name: "Copy",
740        path: &["core", "marker", "Copy"],
741        is_auto: false,
742        is_unsafe: false,
743    },
744    ManualTraitItem {
745        name: "PartialOrd",
746        path: &["core", "cmp", "PartialOrd"],
747        is_auto: false,
748        is_unsafe: false,
749    },
750    ManualTraitItem {
751        name: "Ord",
752        path: &["core", "cmp", "Ord"],
753        is_auto: false,
754        is_unsafe: false,
755    },
756    ManualTraitItem {
757        name: "PartialEq",
758        path: &["core", "cmp", "PartialEq"],
759        is_auto: false,
760        is_unsafe: false,
761    },
762    ManualTraitItem {
763        name: "Eq",
764        path: &["core", "cmp", "Eq"],
765        is_auto: false,
766        is_unsafe: false,
767    },
768    ManualTraitItem {
769        name: "Hash",
770        path: &["core", "hash", "Hash"],
771        is_auto: false,
772        is_unsafe: false,
773    },
774    ManualTraitItem {
775        name: "Send",
776        path: &["core", "marker", "Send"],
777        is_auto: true,
778        is_unsafe: true,
779    },
780    ManualTraitItem {
781        name: "Sync",
782        path: &["core", "marker", "Sync"],
783        is_auto: true,
784        is_unsafe: true,
785    },
786    ManualTraitItem {
787        name: "Unpin",
788        path: &["core", "marker", "Unpin"],
789        is_auto: true,
790        is_unsafe: false,
791    },
792    ManualTraitItem {
793        name: "RefUnwindSafe",
794        path: &["core", "panic", "unwind_safe", "RefUnwindSafe"],
795        is_auto: true,
796        is_unsafe: false,
797    },
798    ManualTraitItem {
799        name: "UnwindSafe",
800        path: &["core", "panic", "unwind_safe", "UnwindSafe"],
801        is_auto: true,
802        is_unsafe: false,
803    },
804    ManualTraitItem {
805        name: "Sized",
806        path: &["core", "marker", "Sized"],
807        is_auto: false,
808        is_unsafe: false,
809    },
810];
811
812fn new_trait(manual_trait_item: &ManualTraitItem, id: Id, crate_id: u32) -> Item {
813    Item {
814        id,
815        crate_id,
816        name: Some(manual_trait_item.name.to_string()),
817        span: None,
818        visibility: rustdoc_types::Visibility::Public,
819        docs: None,
820        links: HashMap::default(),
821        attrs: Vec::new(),
822        deprecation: None,
823        inner: rustdoc_types::ItemEnum::Trait(rustdoc_types::Trait {
824            is_auto: manual_trait_item.is_auto,
825            is_unsafe: manual_trait_item.is_unsafe,
826            is_dyn_compatible: matches!(
827                manual_trait_item.name,
828                "Debug"
829                    | "PartialEq"
830                    | "PartialOrd"
831                    | "Send"
832                    | "Sync"
833                    | "Unpin"
834                    | "UnwindSafe"
835                    | "RefUnwindSafe"
836            ),
837            // The `item`, `generics`, `bounds` and `implementations`
838            // are not currently present in the schema,
839            // so it is safe to fill them with empty containers,
840            // even though some traits in reality have some values in them.
841            items: Vec::new(),
842            generics: rustdoc_types::Generics {
843                params: Vec::new(),
844                where_predicates: Vec::new(),
845            },
846            bounds: Vec::new(),
847            implementations: Vec::new(),
848        }),
849    }
850}
851
852fn create_manually_inlined_builtin_traits(crate_: &Crate) -> HashMap<Id, Item> {
853    let paths = &crate_.paths;
854
855    // `paths` may have thousands of items.
856    #[cfg(feature = "rayon")]
857    let iter = paths.par_iter();
858    #[cfg(not(feature = "rayon"))]
859    let iter = paths.iter();
860
861    iter.filter_map(|(id, entry)| {
862        if entry.kind != rustdoc_types::ItemKind::Trait {
863            return None;
864        }
865
866        // This is a linear scan, but across a tiny array.
867        // It isn't worth doing anything fancier here.
868        MANUAL_TRAIT_ITEMS
869            .iter()
870            .find(|t| t.path == entry.path)
871            .map(|manual| (*id, new_trait(manual, *id, entry.crate_id)))
872    })
873    .collect()
874}
875
876#[cfg(test)]
877mod tests {
878    use itertools::Itertools;
879    use rustdoc_types::{Crate, Id};
880
881    use crate::{test_util::load_pregenerated_rustdoc, ImportablePath, IndexedCrate};
882
883    fn find_item_id<'a>(crate_: &'a Crate, name: &str) -> &'a Id {
884        crate_
885            .index
886            .iter()
887            .filter_map(|(id, item)| (item.name.as_deref() == Some(name)).then_some(id))
888            .exactly_one()
889            .expect("exactly one matching name")
890    }
891
892    /// Ensure that methods, consts, and fields within structs are not importable.
893    #[test]
894    fn structs_are_not_modules() {
895        let rustdoc = load_pregenerated_rustdoc("structs_are_not_modules");
896        let indexed_crate = IndexedCrate::new(&rustdoc);
897
898        let top_level_function = find_item_id(&rustdoc, "top_level_function");
899        let method = find_item_id(&rustdoc, "method");
900        let associated_fn = find_item_id(&rustdoc, "associated_fn");
901        let field = find_item_id(&rustdoc, "field");
902        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
903
904        // All the items are public.
905        assert!(indexed_crate
906            .visibility_tracker
907            .visible_parent_ids()
908            .contains_key(&top_level_function.0));
909        assert!(indexed_crate
910            .visibility_tracker
911            .visible_parent_ids()
912            .contains_key(&method.0));
913        assert!(indexed_crate
914            .visibility_tracker
915            .visible_parent_ids()
916            .contains_key(&associated_fn.0));
917        assert!(indexed_crate
918            .visibility_tracker
919            .visible_parent_ids()
920            .contains_key(&field.0));
921        assert!(indexed_crate
922            .visibility_tracker
923            .visible_parent_ids()
924            .contains_key(&const_item.0));
925
926        // But only `top_level_function` is importable.
927        assert_eq!(
928            vec![ImportablePath::new(
929                vec!["structs_are_not_modules", "top_level_function"],
930                false,
931                false,
932            )],
933            indexed_crate.publicly_importable_names(top_level_function)
934        );
935        assert_eq!(
936            Vec::<ImportablePath<'_>>::new(),
937            indexed_crate.publicly_importable_names(method)
938        );
939        assert_eq!(
940            Vec::<ImportablePath<'_>>::new(),
941            indexed_crate.publicly_importable_names(associated_fn)
942        );
943        assert_eq!(
944            Vec::<ImportablePath<'_>>::new(),
945            indexed_crate.publicly_importable_names(field)
946        );
947        assert_eq!(
948            Vec::<ImportablePath<'_>>::new(),
949            indexed_crate.publicly_importable_names(const_item)
950        );
951    }
952
953    /// Ensure that methods and consts within enums are not importable.
954    /// However, enum variants are the exception: they are importable!
955    #[test]
956    fn enums_are_not_modules() {
957        let rustdoc = load_pregenerated_rustdoc("enums_are_not_modules");
958        let indexed_crate = IndexedCrate::new(&rustdoc);
959
960        let top_level_function = find_item_id(&rustdoc, "top_level_function");
961        let variant = find_item_id(&rustdoc, "Variant");
962        let method = find_item_id(&rustdoc, "method");
963        let associated_fn = find_item_id(&rustdoc, "associated_fn");
964        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
965
966        // All the items are public.
967        assert!(indexed_crate
968            .visibility_tracker
969            .visible_parent_ids()
970            .contains_key(&top_level_function.0));
971        assert!(indexed_crate
972            .visibility_tracker
973            .visible_parent_ids()
974            .contains_key(&variant.0));
975        assert!(indexed_crate
976            .visibility_tracker
977            .visible_parent_ids()
978            .contains_key(&method.0));
979        assert!(indexed_crate
980            .visibility_tracker
981            .visible_parent_ids()
982            .contains_key(&associated_fn.0));
983        assert!(indexed_crate
984            .visibility_tracker
985            .visible_parent_ids()
986            .contains_key(&const_item.0));
987
988        // But only `top_level_function` and `Foo::variant` is importable.
989        assert_eq!(
990            vec![ImportablePath::new(
991                vec!["enums_are_not_modules", "top_level_function"],
992                false,
993                false,
994            )],
995            indexed_crate.publicly_importable_names(top_level_function)
996        );
997        assert_eq!(
998            vec![ImportablePath::new(
999                vec!["enums_are_not_modules", "Foo", "Variant"],
1000                false,
1001                false,
1002            )],
1003            indexed_crate.publicly_importable_names(variant)
1004        );
1005        assert_eq!(
1006            Vec::<ImportablePath<'_>>::new(),
1007            indexed_crate.publicly_importable_names(method)
1008        );
1009        assert_eq!(
1010            Vec::<ImportablePath<'_>>::new(),
1011            indexed_crate.publicly_importable_names(associated_fn)
1012        );
1013        assert_eq!(
1014            Vec::<ImportablePath<'_>>::new(),
1015            indexed_crate.publicly_importable_names(const_item)
1016        );
1017    }
1018
1019    /// Ensure that methods, consts, and fields within unions are not importable.
1020    #[test]
1021    fn unions_are_not_modules() {
1022        let rustdoc = load_pregenerated_rustdoc("unions_are_not_modules");
1023        let indexed_crate = IndexedCrate::new(&rustdoc);
1024
1025        let top_level_function = find_item_id(&rustdoc, "top_level_function");
1026        let method = find_item_id(&rustdoc, "method");
1027        let associated_fn = find_item_id(&rustdoc, "associated_fn");
1028        let left_field = find_item_id(&rustdoc, "left");
1029        let right_field = find_item_id(&rustdoc, "right");
1030        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
1031
1032        // All the items are public.
1033        assert!(indexed_crate
1034            .visibility_tracker
1035            .visible_parent_ids()
1036            .contains_key(&top_level_function.0));
1037        assert!(indexed_crate
1038            .visibility_tracker
1039            .visible_parent_ids()
1040            .contains_key(&method.0));
1041        assert!(indexed_crate
1042            .visibility_tracker
1043            .visible_parent_ids()
1044            .contains_key(&associated_fn.0));
1045        assert!(indexed_crate
1046            .visibility_tracker
1047            .visible_parent_ids()
1048            .contains_key(&left_field.0));
1049        assert!(indexed_crate
1050            .visibility_tracker
1051            .visible_parent_ids()
1052            .contains_key(&right_field.0));
1053        assert!(indexed_crate
1054            .visibility_tracker
1055            .visible_parent_ids()
1056            .contains_key(&const_item.0));
1057
1058        // But only `top_level_function` is importable.
1059        assert_eq!(
1060            vec![ImportablePath::new(
1061                vec!["unions_are_not_modules", "top_level_function"],
1062                false,
1063                false,
1064            )],
1065            indexed_crate.publicly_importable_names(top_level_function)
1066        );
1067        assert_eq!(
1068            Vec::<ImportablePath<'_>>::new(),
1069            indexed_crate.publicly_importable_names(method)
1070        );
1071        assert_eq!(
1072            Vec::<ImportablePath<'_>>::new(),
1073            indexed_crate.publicly_importable_names(associated_fn)
1074        );
1075        assert_eq!(
1076            Vec::<ImportablePath<'_>>::new(),
1077            indexed_crate.publicly_importable_names(left_field)
1078        );
1079        assert_eq!(
1080            Vec::<ImportablePath<'_>>::new(),
1081            indexed_crate.publicly_importable_names(right_field)
1082        );
1083        assert_eq!(
1084            Vec::<ImportablePath<'_>>::new(),
1085            indexed_crate.publicly_importable_names(const_item)
1086        );
1087    }
1088
1089    mod reexports {
1090        use std::collections::{BTreeMap, BTreeSet};
1091
1092        use itertools::Itertools;
1093        use maplit::{btreemap, btreeset};
1094        use rustdoc_types::{ItemEnum, Visibility};
1095
1096        use crate::{test_util::load_pregenerated_rustdoc, ImportablePath, IndexedCrate};
1097
1098        fn assert_exported_items_match(
1099            test_crate: &str,
1100            expected_items: &BTreeMap<&str, BTreeSet<&str>>,
1101        ) {
1102            let rustdoc = load_pregenerated_rustdoc(test_crate);
1103            let indexed_crate = IndexedCrate::new(&rustdoc);
1104
1105            for (&expected_item_name, expected_importable_paths) in expected_items {
1106                assert!(
1107                    !expected_item_name.contains(':'),
1108                    "only direct item names can be checked at the moment: {expected_item_name}"
1109                );
1110
1111                let item_id_candidates = rustdoc
1112                    .index
1113                    .iter()
1114                    .filter_map(|(id, item)| {
1115                        (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1116                    })
1117                    .collect_vec();
1118                if item_id_candidates.len() != 1 {
1119                    panic!(
1120                        "Expected to find exactly one item with name {expected_item_name}, \
1121                        but found these matching IDs: {item_id_candidates:?}"
1122                    );
1123                }
1124                let item_id = item_id_candidates[0];
1125                let actual_items: Vec<_> = indexed_crate
1126                    .publicly_importable_names(item_id)
1127                    .into_iter()
1128                    .map(|importable| importable.path.components.into_iter().join("::"))
1129                    .collect();
1130                let deduplicated_actual_items: BTreeSet<_> =
1131                    actual_items.iter().map(|x| x.as_str()).collect();
1132                assert_eq!(
1133                    actual_items.len(),
1134                    deduplicated_actual_items.len(),
1135                    "duplicates found: {actual_items:?}"
1136                );
1137
1138                assert_eq!(
1139                    expected_importable_paths, &deduplicated_actual_items,
1140                    "mismatch for item name {expected_item_name}",
1141                );
1142            }
1143        }
1144
1145        /// Allows testing for items with overlapping names, such as a function and a type
1146        /// with the same name (which Rust considers in separate namespaces).
1147        fn assert_duplicated_exported_items_match(
1148            test_crate: &str,
1149            expected_items_and_counts: &BTreeMap<&str, (usize, BTreeSet<&str>)>,
1150        ) {
1151            let rustdoc = load_pregenerated_rustdoc(test_crate);
1152            let indexed_crate = IndexedCrate::new(&rustdoc);
1153
1154            for (&expected_item_name, (expected_count, expected_importable_paths)) in
1155                expected_items_and_counts
1156            {
1157                assert!(
1158                    !expected_item_name.contains(':'),
1159                    "only direct item names can be checked at the moment: {expected_item_name}"
1160                );
1161
1162                let item_id_candidates = rustdoc
1163                    .index
1164                    .iter()
1165                    .filter_map(|(id, item)| {
1166                        (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1167                    })
1168                    .collect_vec();
1169                if item_id_candidates.len() != *expected_count {
1170                    panic!(
1171                        "Expected to find exactly {expected_count} items with name \
1172                        {expected_item_name}, but found these matching IDs: {item_id_candidates:?}"
1173                    );
1174                }
1175                for item_id in item_id_candidates {
1176                    let actual_items: Vec<_> = indexed_crate
1177                        .publicly_importable_names(item_id)
1178                        .into_iter()
1179                        .map(|importable| importable.path.components.into_iter().join("::"))
1180                        .collect();
1181                    let deduplicated_actual_items: BTreeSet<_> =
1182                        actual_items.iter().map(|x| x.as_str()).collect();
1183                    assert_eq!(
1184                        actual_items.len(),
1185                        deduplicated_actual_items.len(),
1186                        "duplicates found: {actual_items:?}"
1187                    );
1188                    assert_eq!(expected_importable_paths, &deduplicated_actual_items);
1189                }
1190            }
1191        }
1192
1193        #[test]
1194        fn pub_inside_pub_crate_mod() {
1195            let test_crate = "pub_inside_pub_crate_mod";
1196            let expected_items = btreemap! {
1197                "Foo" => btreeset![],
1198                "Bar" => btreeset![
1199                    "pub_inside_pub_crate_mod::Bar",
1200                ],
1201            };
1202
1203            assert_exported_items_match(test_crate, &expected_items);
1204        }
1205
1206        #[test]
1207        fn reexport() {
1208            let test_crate = "reexport";
1209            let expected_items = btreemap! {
1210                "foo" => btreeset![
1211                    "reexport::foo",
1212                    "reexport::inner::foo",
1213                ],
1214            };
1215
1216            assert_exported_items_match(test_crate, &expected_items);
1217        }
1218
1219        #[test]
1220        fn reexport_from_private_module() {
1221            let test_crate = "reexport_from_private_module";
1222            let expected_items = btreemap! {
1223                "foo" => btreeset![
1224                    "reexport_from_private_module::foo",
1225                ],
1226                "Bar" => btreeset![
1227                    "reexport_from_private_module::Bar",
1228                ],
1229                "Baz" => btreeset![
1230                    "reexport_from_private_module::nested::Baz",
1231                ],
1232                "quux" => btreeset![
1233                    "reexport_from_private_module::quux",
1234                ],
1235            };
1236
1237            assert_exported_items_match(test_crate, &expected_items);
1238        }
1239
1240        #[test]
1241        fn renaming_reexport() {
1242            let test_crate = "renaming_reexport";
1243            let expected_items = btreemap! {
1244                "foo" => btreeset![
1245                    "renaming_reexport::bar",
1246                    "renaming_reexport::inner::foo",
1247                ],
1248            };
1249
1250            assert_exported_items_match(test_crate, &expected_items);
1251        }
1252
1253        #[test]
1254        fn renaming_reexport_of_reexport() {
1255            let test_crate = "renaming_reexport_of_reexport";
1256            let expected_items = btreemap! {
1257                "foo" => btreeset![
1258                    "renaming_reexport_of_reexport::bar",
1259                    "renaming_reexport_of_reexport::foo",
1260                    "renaming_reexport_of_reexport::inner::foo",
1261                ],
1262            };
1263
1264            assert_exported_items_match(test_crate, &expected_items);
1265        }
1266
1267        #[test]
1268        fn renaming_mod_reexport() {
1269            let test_crate = "renaming_mod_reexport";
1270            let expected_items = btreemap! {
1271                "foo" => btreeset![
1272                    "renaming_mod_reexport::inner::a::foo",
1273                    "renaming_mod_reexport::inner::b::foo",
1274                    "renaming_mod_reexport::direct::foo",
1275                ],
1276            };
1277
1278            assert_exported_items_match(test_crate, &expected_items);
1279        }
1280
1281        #[test]
1282        fn glob_reexport() {
1283            let test_crate = "glob_reexport";
1284            let expected_items = btreemap! {
1285                "foo" => btreeset![
1286                    "glob_reexport::foo",
1287                    "glob_reexport::inner::foo",
1288                ],
1289                "Bar" => btreeset![
1290                    "glob_reexport::Bar",
1291                    "glob_reexport::inner::Bar",
1292                ],
1293                "nested" => btreeset![
1294                    "glob_reexport::nested",
1295                ],
1296                "Baz" => btreeset![
1297                    "glob_reexport::Baz",
1298                ],
1299                "First" => btreeset![
1300                    "glob_reexport::First",
1301                    "glob_reexport::Baz::First",
1302                ],
1303                "Second" => btreeset![
1304                    "glob_reexport::Second",
1305                    "glob_reexport::Baz::Second",
1306                ],
1307            };
1308
1309            assert_exported_items_match(test_crate, &expected_items);
1310        }
1311
1312        #[test]
1313        fn glob_of_glob_reexport() {
1314            let test_crate = "glob_of_glob_reexport";
1315            let expected_items = btreemap! {
1316                "foo" => btreeset![
1317                    "glob_of_glob_reexport::foo",
1318                ],
1319                "Bar" => btreeset![
1320                    "glob_of_glob_reexport::Bar",
1321                ],
1322                "Baz" => btreeset![
1323                    "glob_of_glob_reexport::Baz",
1324                ],
1325                "Onion" => btreeset![
1326                    "glob_of_glob_reexport::Onion",
1327                ],
1328            };
1329
1330            assert_exported_items_match(test_crate, &expected_items);
1331        }
1332
1333        #[test]
1334        fn glob_of_renamed_reexport() {
1335            let test_crate = "glob_of_renamed_reexport";
1336            let expected_items = btreemap! {
1337                "foo" => btreeset![
1338                    "glob_of_renamed_reexport::renamed_foo",
1339                ],
1340                "Bar" => btreeset![
1341                    "glob_of_renamed_reexport::RenamedBar",
1342                ],
1343                "First" => btreeset![
1344                    "glob_of_renamed_reexport::RenamedFirst",
1345                ],
1346                "Onion" => btreeset![
1347                    "glob_of_renamed_reexport::RenamedOnion",
1348                ],
1349            };
1350
1351            assert_exported_items_match(test_crate, &expected_items);
1352        }
1353
1354        #[test]
1355        fn glob_reexport_enum_variants() {
1356            let test_crate = "glob_reexport_enum_variants";
1357            let expected_items = btreemap! {
1358                "First" => btreeset![
1359                    "glob_reexport_enum_variants::First",
1360                ],
1361                "Second" => btreeset![
1362                    "glob_reexport_enum_variants::Second",
1363                ],
1364            };
1365
1366            assert_exported_items_match(test_crate, &expected_items);
1367        }
1368
1369        #[test]
1370        fn glob_reexport_cycle() {
1371            let test_crate = "glob_reexport_cycle";
1372            let expected_items = btreemap! {
1373                "foo" => btreeset![
1374                    "glob_reexport_cycle::first::foo",
1375                    "glob_reexport_cycle::second::foo",
1376                ],
1377                "Bar" => btreeset![
1378                    "glob_reexport_cycle::first::Bar",
1379                    "glob_reexport_cycle::second::Bar",
1380                ],
1381            };
1382
1383            assert_exported_items_match(test_crate, &expected_items);
1384        }
1385
1386        #[test]
1387        fn infinite_recursive_reexport() {
1388            let test_crate = "infinite_recursive_reexport";
1389            let expected_items = btreemap! {
1390                "foo" => btreeset![
1391                    // We don't want to expand all infinitely-many names here.
1392                    // We only return cycle-free paths, which are the following:
1393                    "infinite_recursive_reexport::foo",
1394                    "infinite_recursive_reexport::inner::foo",
1395                ],
1396            };
1397
1398            assert_exported_items_match(test_crate, &expected_items);
1399        }
1400
1401        #[test]
1402        fn infinite_indirect_recursive_reexport() {
1403            let test_crate = "infinite_indirect_recursive_reexport";
1404            let expected_items = btreemap! {
1405                "foo" => btreeset![
1406                    // We don't want to expand all infinitely-many names here.
1407                    // We only return cycle-free paths, which are the following:
1408                    "infinite_indirect_recursive_reexport::foo",
1409                    "infinite_indirect_recursive_reexport::nested::foo",
1410                ],
1411            };
1412
1413            assert_exported_items_match(test_crate, &expected_items);
1414        }
1415
1416        #[test]
1417        fn infinite_corecursive_reexport() {
1418            let test_crate = "infinite_corecursive_reexport";
1419            let expected_items = btreemap! {
1420                "foo" => btreeset![
1421                    // We don't want to expand all infinitely-many names here.
1422                    // We only return cycle-free paths, which are the following:
1423                    "infinite_corecursive_reexport::a::foo",
1424                    "infinite_corecursive_reexport::b::a::foo",
1425                ],
1426            };
1427
1428            assert_exported_items_match(test_crate, &expected_items);
1429        }
1430
1431        #[test]
1432        fn pub_type_alias_reexport() {
1433            let test_crate = "pub_type_alias_reexport";
1434            let expected_items = btreemap! {
1435                "Foo" => btreeset![
1436                    "pub_type_alias_reexport::Exported",
1437                ],
1438            };
1439
1440            assert_exported_items_match(test_crate, &expected_items);
1441        }
1442
1443        #[test]
1444        fn pub_generic_type_alias_reexport() {
1445            let test_crate = "pub_generic_type_alias_reexport";
1446            let expected_items = btreemap! {
1447                "Foo" => btreeset![
1448                    // Only `Exported` and `ExportedRenamedParams` are re-exports.
1449                    //
1450                    //`ExportedRenamedParams` renames the generic parameters
1451                    // but does not change their meaning.
1452                    //
1453                    // `ExportedWithDefaults` is not a re-export because it adds
1454                    //
1455                    // The other type aliases are not equivalent since they constrain
1456                    // some of the underlying type's generic parameters.
1457                    "pub_generic_type_alias_reexport::Exported",
1458                    "pub_generic_type_alias_reexport::ExportedRenamedParams",
1459                ],
1460                "Exported" => btreeset![
1461                    // The type alias itself is also a visible item.
1462                    "pub_generic_type_alias_reexport::Exported",
1463                ],
1464                "ExportedWithDefaults" => btreeset![
1465                    // The type alias itself is also a visible item.
1466                    "pub_generic_type_alias_reexport::ExportedWithDefaults",
1467                ],
1468                "ExportedRenamedParams" => btreeset![
1469                    // The type alias itself is also a visible item.
1470                    "pub_generic_type_alias_reexport::ExportedRenamedParams",
1471                ],
1472                "ExportedSpecificLifetime" => btreeset![
1473                    "pub_generic_type_alias_reexport::ExportedSpecificLifetime",
1474                ],
1475                "ExportedSpecificType" => btreeset![
1476                    "pub_generic_type_alias_reexport::ExportedSpecificType",
1477                ],
1478                "ExportedSpecificConst" => btreeset![
1479                    "pub_generic_type_alias_reexport::ExportedSpecificConst",
1480                ],
1481                "ExportedFullySpecified" => btreeset![
1482                    "pub_generic_type_alias_reexport::ExportedFullySpecified",
1483                ],
1484            };
1485
1486            assert_exported_items_match(test_crate, &expected_items);
1487        }
1488
1489        #[test]
1490        fn pub_generic_type_alias_shuffled_order() {
1491            let test_crate = "pub_generic_type_alias_shuffled_order";
1492            let expected_items = btreemap! {
1493                // The type aliases reverse the generic parameters' orders,
1494                // so they are not re-exports of the underlying types.
1495                "GenericFoo" => btreeset![
1496                    "pub_generic_type_alias_shuffled_order::inner::GenericFoo",
1497                ],
1498                "LifetimeFoo" => btreeset![
1499                    "pub_generic_type_alias_shuffled_order::inner::LifetimeFoo",
1500                ],
1501                "ConstFoo" => btreeset![
1502                    "pub_generic_type_alias_shuffled_order::inner::ConstFoo",
1503                ],
1504                "ReversedGenericFoo" => btreeset![
1505                    "pub_generic_type_alias_shuffled_order::ReversedGenericFoo",
1506                ],
1507                "ReversedLifetimeFoo" => btreeset![
1508                    "pub_generic_type_alias_shuffled_order::ReversedLifetimeFoo",
1509                ],
1510                "ReversedConstFoo" => btreeset![
1511                    "pub_generic_type_alias_shuffled_order::ReversedConstFoo",
1512                ],
1513            };
1514
1515            assert_exported_items_match(test_crate, &expected_items);
1516        }
1517
1518        #[test]
1519        fn pub_generic_type_alias_added_defaults() {
1520            let test_crate = "pub_generic_type_alias_added_defaults";
1521            let expected_items = btreemap! {
1522                "Foo" => btreeset![
1523                    "pub_generic_type_alias_added_defaults::inner::Foo",
1524                ],
1525                "Bar" => btreeset![
1526                    "pub_generic_type_alias_added_defaults::inner::Bar",
1527                ],
1528                "DefaultFoo" => btreeset![
1529                    "pub_generic_type_alias_added_defaults::DefaultFoo",
1530                ],
1531                "DefaultBar" => btreeset![
1532                    "pub_generic_type_alias_added_defaults::DefaultBar",
1533                ],
1534            };
1535
1536            assert_exported_items_match(test_crate, &expected_items);
1537        }
1538
1539        #[test]
1540        fn pub_generic_type_alias_changed_defaults() {
1541            let test_crate = "pub_generic_type_alias_changed_defaults";
1542            let expected_items = btreemap! {
1543                // The type aliases change the default values of the generic parameters,
1544                // so they are not re-exports of the underlying types.
1545                "Foo" => btreeset![
1546                    "pub_generic_type_alias_changed_defaults::inner::Foo",
1547                ],
1548                "Bar" => btreeset![
1549                    "pub_generic_type_alias_changed_defaults::inner::Bar",
1550                ],
1551                "ExportedWithoutTypeDefault" => btreeset![
1552                    "pub_generic_type_alias_changed_defaults::ExportedWithoutTypeDefault",
1553                ],
1554                "ExportedWithoutConstDefault" => btreeset![
1555                    "pub_generic_type_alias_changed_defaults::ExportedWithoutConstDefault",
1556                ],
1557                "ExportedWithoutDefaults" => btreeset![
1558                    "pub_generic_type_alias_changed_defaults::ExportedWithoutDefaults",
1559                ],
1560                "ExportedWithDifferentTypeDefault" => btreeset![
1561                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentTypeDefault",
1562                ],
1563                "ExportedWithDifferentConstDefault" => btreeset![
1564                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentConstDefault",
1565                ],
1566                "ExportedWithDifferentDefaults" => btreeset![
1567                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentDefaults",
1568                ],
1569            };
1570
1571            assert_exported_items_match(test_crate, &expected_items);
1572        }
1573
1574        #[test]
1575        fn pub_generic_type_alias_same_signature_but_not_equivalent() {
1576            let test_crate = "pub_generic_type_alias_same_signature_but_not_equivalent";
1577            let expected_items = btreemap! {
1578                "GenericFoo" => btreeset![
1579                    "pub_generic_type_alias_same_signature_but_not_equivalent::inner::GenericFoo",
1580                ],
1581                "ChangedFoo" => btreeset![
1582                    "pub_generic_type_alias_same_signature_but_not_equivalent::ChangedFoo",
1583                ],
1584            };
1585
1586            assert_exported_items_match(test_crate, &expected_items);
1587        }
1588
1589        #[test]
1590        fn pub_type_alias_of_type_alias() {
1591            let test_crate = "pub_type_alias_of_type_alias";
1592            let expected_items = btreemap! {
1593                "Foo" => btreeset![
1594                    "pub_type_alias_of_type_alias::inner::Foo",
1595                    "pub_type_alias_of_type_alias::inner::AliasedFoo",
1596                    "pub_type_alias_of_type_alias::ExportedFoo",
1597                ],
1598                "Bar" => btreeset![
1599                    "pub_type_alias_of_type_alias::inner::Bar",
1600                    "pub_type_alias_of_type_alias::inner::AliasedBar",
1601                    "pub_type_alias_of_type_alias::ExportedBar",
1602                ],
1603                "AliasedFoo" => btreeset![
1604                    "pub_type_alias_of_type_alias::inner::AliasedFoo",
1605                    "pub_type_alias_of_type_alias::ExportedFoo",
1606                ],
1607                "AliasedBar" => btreeset![
1608                    "pub_type_alias_of_type_alias::inner::AliasedBar",
1609                    "pub_type_alias_of_type_alias::ExportedBar",
1610                ],
1611                "ExportedFoo" => btreeset![
1612                    "pub_type_alias_of_type_alias::ExportedFoo",
1613                ],
1614                "ExportedBar" => btreeset![
1615                    "pub_type_alias_of_type_alias::ExportedBar",
1616                ],
1617                "DifferentLifetimeBar" => btreeset![
1618                    "pub_type_alias_of_type_alias::DifferentLifetimeBar",
1619                ],
1620                "DifferentGenericBar" => btreeset![
1621                    "pub_type_alias_of_type_alias::DifferentGenericBar",
1622                ],
1623                "DifferentConstBar" => btreeset![
1624                    "pub_type_alias_of_type_alias::DifferentConstBar",
1625                ],
1626                "ReorderedBar" => btreeset![
1627                    "pub_type_alias_of_type_alias::ReorderedBar",
1628                ],
1629                "DefaultValueBar" => btreeset![
1630                    "pub_type_alias_of_type_alias::DefaultValueBar",
1631                ],
1632            };
1633
1634            assert_exported_items_match(test_crate, &expected_items);
1635        }
1636
1637        #[test]
1638        fn pub_type_alias_of_composite_type() {
1639            let test_crate = "pub_type_alias_of_composite_type";
1640            let expected_items = btreemap! {
1641                "Foo" => btreeset![
1642                    "pub_type_alias_of_composite_type::inner::Foo",
1643                ],
1644                "I64Tuple" => btreeset![
1645                    "pub_type_alias_of_composite_type::I64Tuple",
1646                ],
1647                "MixedTuple" => btreeset![
1648                    "pub_type_alias_of_composite_type::MixedTuple",
1649                ],
1650                "GenericTuple" => btreeset![
1651                    "pub_type_alias_of_composite_type::GenericTuple",
1652                ],
1653                "LifetimeTuple" => btreeset![
1654                    "pub_type_alias_of_composite_type::LifetimeTuple",
1655                ],
1656                "ConstTuple" => btreeset![
1657                    "pub_type_alias_of_composite_type::ConstTuple",
1658                ],
1659                "DefaultGenericTuple" => btreeset![
1660                    "pub_type_alias_of_composite_type::DefaultGenericTuple",
1661                ],
1662                "DefaultConstTuple" => btreeset![
1663                    "pub_type_alias_of_composite_type::DefaultConstTuple",
1664                ],
1665            };
1666
1667            assert_exported_items_match(test_crate, &expected_items);
1668        }
1669
1670        #[test]
1671        fn pub_generic_type_alias_omitted_default() {
1672            let test_crate = "pub_generic_type_alias_omitted_default";
1673            let expected_items = btreemap! {
1674                "DefaultConst" => btreeset![
1675                    "pub_generic_type_alias_omitted_default::inner::DefaultConst",
1676                ],
1677                "DefaultType" => btreeset![
1678                    "pub_generic_type_alias_omitted_default::inner::DefaultType",
1679                ],
1680                "ConstOnly" => btreeset![
1681                    "pub_generic_type_alias_omitted_default::inner::ConstOnly",
1682                ],
1683                "TypeOnly" => btreeset![
1684                    "pub_generic_type_alias_omitted_default::inner::TypeOnly",
1685                ],
1686                "OmittedConst" => btreeset![
1687                    "pub_generic_type_alias_omitted_default::OmittedConst",
1688                ],
1689                "OmittedType" => btreeset![
1690                    "pub_generic_type_alias_omitted_default::OmittedType",
1691                ],
1692                "NonGenericConst" => btreeset![
1693                    "pub_generic_type_alias_omitted_default::NonGenericConst",
1694                ],
1695                "NonGenericType" => btreeset![
1696                    "pub_generic_type_alias_omitted_default::NonGenericType",
1697                ],
1698            };
1699
1700            assert_exported_items_match(test_crate, &expected_items);
1701        }
1702
1703        #[test]
1704        fn swapping_names() {
1705            let test_crate = "swapping_names";
1706            let expected_items = btreemap! {
1707                "Foo" => btreeset![
1708                    "swapping_names::Foo",
1709                    "swapping_names::inner::Bar",
1710                    "swapping_names::inner::nested::Foo",
1711                ],
1712                "Bar" => btreeset![
1713                    "swapping_names::Bar",
1714                    "swapping_names::inner::Foo",
1715                    "swapping_names::inner::nested::Bar",
1716                ],
1717            };
1718
1719            assert_exported_items_match(test_crate, &expected_items);
1720        }
1721
1722        #[test]
1723        fn overlapping_glob_and_local_module() {
1724            let test_crate = "overlapping_glob_and_local_module";
1725            let expected_items = btreemap! {
1726                "Foo" => btreeset![
1727                    "overlapping_glob_and_local_module::sibling::duplicated::Foo",
1728                ],
1729                "Bar" => btreeset![
1730                    "overlapping_glob_and_local_module::inner::duplicated::Bar",
1731                ],
1732            };
1733
1734            assert_exported_items_match(test_crate, &expected_items);
1735        }
1736
1737        #[test]
1738        fn overlapping_glob_and_renamed_module() {
1739            let test_crate = "overlapping_glob_and_renamed_module";
1740            let expected_items = btreemap! {
1741                "Foo" => btreeset![
1742                    "overlapping_glob_and_renamed_module::sibling::duplicated::Foo",
1743                ],
1744                "Bar" => btreeset![
1745                    "overlapping_glob_and_renamed_module::inner::duplicated::Bar",
1746                ],
1747            };
1748
1749            assert_exported_items_match(test_crate, &expected_items);
1750        }
1751
1752        #[test]
1753        fn type_and_value_with_matching_names() {
1754            let test_crate = "type_and_value_with_matching_names";
1755            let expected_items = btreemap! {
1756                "Foo" => (2, btreeset![
1757                    "type_and_value_with_matching_names::Foo",
1758                    "type_and_value_with_matching_names::nested::Foo",
1759                ]),
1760                "Bar" => (2, btreeset![
1761                    "type_and_value_with_matching_names::Bar",
1762                    "type_and_value_with_matching_names::nested::Bar",
1763                ]),
1764            };
1765
1766            assert_duplicated_exported_items_match(test_crate, &expected_items);
1767        }
1768
1769        #[test]
1770        fn no_shadowing_across_namespaces() {
1771            let test_crate = "no_shadowing_across_namespaces";
1772            let expected_items = btreemap! {
1773                "Foo" => (2, btreeset![
1774                    "no_shadowing_across_namespaces::Foo",
1775                    "no_shadowing_across_namespaces::nested::Foo",
1776                ]),
1777            };
1778
1779            assert_duplicated_exported_items_match(test_crate, &expected_items);
1780        }
1781
1782        #[test]
1783        fn explicit_reexport_of_matching_names() {
1784            if version_check::is_min_version("1.69.0").unwrap_or(true) {
1785                let test_crate = "explicit_reexport_of_matching_names";
1786                let expected_items = btreemap! {
1787                    "Foo" => (2, btreeset![
1788                        "explicit_reexport_of_matching_names::Bar",
1789                        "explicit_reexport_of_matching_names::Foo",
1790                        "explicit_reexport_of_matching_names::nested::Foo",
1791                    ]),
1792                };
1793
1794                assert_duplicated_exported_items_match(test_crate, &expected_items);
1795            } else {
1796                use std::io::Write;
1797                writeln!(
1798                    std::io::stderr(),
1799                    "skipping 'explicit_reexport_of_matching_names' test due to Rust {:?}",
1800                    version_check::Version::read(),
1801                )
1802                .expect("write failed");
1803            }
1804        }
1805
1806        #[test]
1807        fn overlapping_glob_and_local_item() {
1808            let test_crate = "overlapping_glob_and_local_item";
1809
1810            let rustdoc = load_pregenerated_rustdoc(test_crate);
1811            let indexed_crate = IndexedCrate::new(&rustdoc);
1812
1813            let foo_ids = rustdoc
1814                .index
1815                .iter()
1816                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1817                .collect_vec();
1818            if foo_ids.len() != 2 {
1819                panic!(
1820                    "Expected to find exactly 2 items with name \
1821                    Foo, but found these matching IDs: {foo_ids:?}"
1822                );
1823            }
1824
1825            let item_id_candidates = rustdoc
1826                .index
1827                .iter()
1828                .filter_map(|(id, item)| {
1829                    (matches!(item.name.as_deref(), Some("Foo" | "Bar"))).then_some(id)
1830                })
1831                .collect_vec();
1832            if item_id_candidates.len() != 3 {
1833                panic!(
1834                    "Expected to find exactly 3 items named Foo or Bar, \
1835                    but found these matching IDs: {item_id_candidates:?}"
1836                );
1837            }
1838
1839            let mut all_importable_paths = Vec::new();
1840            for item_id in item_id_candidates {
1841                let actual_items: Vec<_> = indexed_crate
1842                    .publicly_importable_names(item_id)
1843                    .into_iter()
1844                    .map(|importable| importable.path.components.into_iter().join("::"))
1845                    .collect();
1846                let deduplicated_actual_items: BTreeSet<_> =
1847                    actual_items.iter().map(|x| x.as_str()).collect();
1848                assert_eq!(
1849                    actual_items.len(),
1850                    deduplicated_actual_items.len(),
1851                    "duplicates found: {actual_items:?}"
1852                );
1853
1854                if deduplicated_actual_items
1855                    .first()
1856                    .expect("no names")
1857                    .ends_with("::Foo")
1858                {
1859                    assert_eq!(
1860                        deduplicated_actual_items.len(),
1861                        1,
1862                        "\
1863expected exactly one importable path for `Foo` items in this crate but got: {actual_items:?}"
1864                    );
1865                } else {
1866                    assert_eq!(
1867                        deduplicated_actual_items,
1868                        btreeset! {
1869                            "overlapping_glob_and_local_item::Bar",
1870                            "overlapping_glob_and_local_item::inner::Bar",
1871                        }
1872                    );
1873                }
1874
1875                all_importable_paths.extend(actual_items);
1876            }
1877
1878            all_importable_paths.sort_unstable();
1879            assert_eq!(
1880                vec![
1881                    "overlapping_glob_and_local_item::Bar",
1882                    "overlapping_glob_and_local_item::Foo",
1883                    "overlapping_glob_and_local_item::inner::Bar",
1884                    "overlapping_glob_and_local_item::inner::Foo",
1885                ],
1886                all_importable_paths,
1887            );
1888        }
1889
1890        #[test]
1891        fn nested_overlapping_glob_and_local_item() {
1892            let test_crate = "nested_overlapping_glob_and_local_item";
1893
1894            let rustdoc = load_pregenerated_rustdoc(test_crate);
1895            let indexed_crate = IndexedCrate::new(&rustdoc);
1896
1897            let item_id_candidates = rustdoc
1898                .index
1899                .iter()
1900                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1901                .collect_vec();
1902            if item_id_candidates.len() != 2 {
1903                panic!(
1904                    "Expected to find exactly 2 items with name \
1905                    Foo, but found these matching IDs: {item_id_candidates:?}"
1906                );
1907            }
1908
1909            let mut all_importable_paths = Vec::new();
1910            for item_id in item_id_candidates {
1911                let actual_items: Vec<_> = indexed_crate
1912                    .publicly_importable_names(item_id)
1913                    .into_iter()
1914                    .map(|importable| importable.path.components.into_iter().join("::"))
1915                    .collect();
1916                let deduplicated_actual_items: BTreeSet<_> =
1917                    actual_items.iter().map(|x| x.as_str()).collect();
1918
1919                assert_eq!(
1920                    actual_items.len(),
1921                    deduplicated_actual_items.len(),
1922                    "duplicates found: {actual_items:?}"
1923                );
1924
1925                match deduplicated_actual_items.len() {
1926                    1 => assert_eq!(
1927                        deduplicated_actual_items,
1928                        btreeset! { "nested_overlapping_glob_and_local_item::Foo" },
1929                    ),
1930                    2 => assert_eq!(
1931                        deduplicated_actual_items,
1932                        btreeset! {
1933                            "nested_overlapping_glob_and_local_item::inner::Foo",
1934                            "nested_overlapping_glob_and_local_item::inner::nested::Foo",
1935                        }
1936                    ),
1937                    _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
1938                };
1939
1940                all_importable_paths.extend(actual_items);
1941            }
1942
1943            all_importable_paths.sort_unstable();
1944            assert_eq!(
1945                vec![
1946                    "nested_overlapping_glob_and_local_item::Foo",
1947                    "nested_overlapping_glob_and_local_item::inner::Foo",
1948                    "nested_overlapping_glob_and_local_item::inner::nested::Foo",
1949                ],
1950                all_importable_paths,
1951            );
1952        }
1953
1954        #[test]
1955        fn cyclic_overlapping_glob_and_local_item() {
1956            let test_crate = "cyclic_overlapping_glob_and_local_item";
1957
1958            let rustdoc = load_pregenerated_rustdoc(test_crate);
1959            let indexed_crate = IndexedCrate::new(&rustdoc);
1960
1961            let item_id_candidates = rustdoc
1962                .index
1963                .iter()
1964                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1965                .collect_vec();
1966            if item_id_candidates.len() != 2 {
1967                panic!(
1968                    "Expected to find exactly 2 items with name \
1969                    Foo, but found these matching IDs: {item_id_candidates:?}"
1970                );
1971            }
1972
1973            let mut all_importable_paths = Vec::new();
1974            for item_id in item_id_candidates {
1975                let actual_items: Vec<_> = indexed_crate
1976                    .publicly_importable_names(item_id)
1977                    .into_iter()
1978                    .map(|importable| importable.path.components.into_iter().join("::"))
1979                    .collect();
1980                let deduplicated_actual_items: BTreeSet<_> =
1981                    actual_items.iter().map(|x| x.as_str()).collect();
1982
1983                assert_eq!(
1984                    actual_items.len(),
1985                    deduplicated_actual_items.len(),
1986                    "duplicates found: {actual_items:?}"
1987                );
1988
1989                match deduplicated_actual_items.len() {
1990                    1 => assert_eq!(
1991                        btreeset! { "cyclic_overlapping_glob_and_local_item::Foo" },
1992                        deduplicated_actual_items,
1993                    ),
1994                    4 => assert_eq!(
1995                        btreeset! {
1996                            "cyclic_overlapping_glob_and_local_item::inner::Foo",
1997                            "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
1998                            "cyclic_overlapping_glob_and_local_item::nested::Foo",
1999                            "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2000                        },
2001                        deduplicated_actual_items,
2002                    ),
2003                    _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
2004                };
2005
2006                all_importable_paths.extend(actual_items);
2007            }
2008
2009            all_importable_paths.sort_unstable();
2010            assert_eq!(
2011                vec![
2012                    "cyclic_overlapping_glob_and_local_item::Foo",
2013                    "cyclic_overlapping_glob_and_local_item::inner::Foo",
2014                    "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
2015                    "cyclic_overlapping_glob_and_local_item::nested::Foo",
2016                    "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2017                ],
2018                all_importable_paths,
2019            );
2020        }
2021
2022        #[test]
2023        fn overlapping_glob_of_enum_with_local_item() {
2024            let test_crate = "overlapping_glob_of_enum_with_local_item";
2025            let easy_expected_items = btreemap! {
2026                "Foo" => btreeset![
2027                    "overlapping_glob_of_enum_with_local_item::Foo",
2028                ],
2029                "Second" => btreeset![
2030                    "overlapping_glob_of_enum_with_local_item::Foo::Second",
2031                    "overlapping_glob_of_enum_with_local_item::inner::Second",
2032                ],
2033            };
2034
2035            // Check the "easy" cases: `Foo` and `Second`.
2036            // This is necessary but not sufficient to confirm our implementation works,
2037            // since it doesn't check anything about `First` which is the point of this test case.
2038            assert_exported_items_match(test_crate, &easy_expected_items);
2039
2040            let rustdoc = load_pregenerated_rustdoc(test_crate);
2041            let indexed_crate = IndexedCrate::new(&rustdoc);
2042
2043            let items_named_first: Vec<_> = indexed_crate
2044                .inner
2045                .index
2046                .values()
2047                .filter(|item| item.name.as_deref() == Some("First"))
2048                .collect();
2049            assert_eq!(2, items_named_first.len(), "{items_named_first:?}");
2050            let variant_item = items_named_first
2051                .iter()
2052                .copied()
2053                .find(|item| matches!(item.inner, ItemEnum::Variant(..)))
2054                .expect("no variant item found");
2055            let struct_item = items_named_first
2056                .iter()
2057                .copied()
2058                .find(|item| matches!(item.inner, ItemEnum::Struct(..)))
2059                .expect("no struct item found");
2060
2061            assert_eq!(
2062                vec![ImportablePath::new(
2063                    vec!["overlapping_glob_of_enum_with_local_item", "Foo", "First"],
2064                    false,
2065                    false,
2066                )],
2067                indexed_crate.publicly_importable_names(&variant_item.id),
2068            );
2069            assert_eq!(
2070                // The struct definition overrides the glob-imported variant here.
2071                vec![ImportablePath::new(
2072                    vec!["overlapping_glob_of_enum_with_local_item", "inner", "First"],
2073                    false,
2074                    false,
2075                )],
2076                indexed_crate.publicly_importable_names(&struct_item.id),
2077            );
2078        }
2079
2080        #[test]
2081        fn glob_of_enum_does_not_shadow_local_fn() {
2082            let test_crate = "glob_of_enum_does_not_shadow_local_fn";
2083
2084            let rustdoc = load_pregenerated_rustdoc(test_crate);
2085            let indexed_crate = IndexedCrate::new(&rustdoc);
2086
2087            let first_ids = rustdoc
2088                .index
2089                .iter()
2090                .filter_map(|(id, item)| (item.name.as_deref() == Some("First")).then_some(id))
2091                .collect_vec();
2092            if first_ids.len() != 2 {
2093                panic!(
2094                    "Expected to find exactly 2 items with name \
2095                    First, but found these matching IDs: {first_ids:?}"
2096                );
2097            }
2098
2099            for item_id in first_ids {
2100                let actual_items: Vec<_> = indexed_crate
2101                    .publicly_importable_names(item_id)
2102                    .into_iter()
2103                    .map(|importable| importable.path.components.into_iter().join("::"))
2104                    .collect();
2105                let deduplicated_actual_items: BTreeSet<_> =
2106                    actual_items.iter().map(|x| x.as_str()).collect();
2107                assert_eq!(
2108                    actual_items.len(),
2109                    deduplicated_actual_items.len(),
2110                    "duplicates found: {actual_items:?}"
2111                );
2112
2113                let expected_items = match &rustdoc.index[item_id].inner {
2114                    ItemEnum::Variant(..) => {
2115                        vec!["glob_of_enum_does_not_shadow_local_fn::Foo::First"]
2116                    }
2117                    ItemEnum::Function(..) => {
2118                        vec!["glob_of_enum_does_not_shadow_local_fn::inner::First"]
2119                    }
2120                    other => {
2121                        unreachable!("item {item_id:?} had unexpected inner content: {other:?}")
2122                    }
2123                };
2124
2125                assert_eq!(expected_items, actual_items);
2126            }
2127        }
2128
2129        /// There's currently no way to detect private imports that shadow glob items.
2130        /// Reported as: <https://github.com/rust-lang/rust/issues/111338>
2131        #[test]
2132        #[should_panic = "expected no importable item names but found \
2133                         [\"overlapping_glob_and_private_import::inner::Foo\"]"]
2134        fn overlapping_glob_and_private_import() {
2135            let test_crate = "overlapping_glob_and_private_import";
2136
2137            let rustdoc = load_pregenerated_rustdoc(test_crate);
2138            let indexed_crate = IndexedCrate::new(&rustdoc);
2139
2140            let item_id_candidates = rustdoc
2141                .index
2142                .iter()
2143                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2144                .collect_vec();
2145            if item_id_candidates.len() != 2 {
2146                panic!(
2147                    "Expected to find exactly 2 items with name \
2148                    Foo, but found these matching IDs: {item_id_candidates:?}"
2149                );
2150            }
2151
2152            for item_id in item_id_candidates {
2153                let actual_items: Vec<_> = indexed_crate
2154                    .publicly_importable_names(item_id)
2155                    .into_iter()
2156                    .map(|importable| importable.path.components.into_iter().join("::"))
2157                    .collect();
2158
2159                assert!(
2160                    actual_items.is_empty(),
2161                    "expected no importable item names but found {actual_items:?}"
2162                );
2163            }
2164        }
2165
2166        /// Our logic for determining whether a tuple struct's implicit constructor is exported
2167        /// is too simplistic: it assumes "yes" if all fields are pub, and "no" otherwise.
2168        /// This is why this test currently fails.
2169        /// TODO: fix this once rustdoc includes shadowing information
2170        ///       <https://github.com/rust-lang/rust/issues/111338>
2171        ///
2172        /// Its sibling test `visibility_modifier_avoids_shadowing` ensures that shadowing is
2173        /// not inappropriately applied when the tuple constructors do *not* shadow each other.
2174        #[test]
2175        #[should_panic = "expected no importable item names but found \
2176                         [\"visibility_modifier_causes_shadowing::Foo\"]"]
2177        fn visibility_modifier_causes_shadowing() {
2178            let test_crate = "visibility_modifier_causes_shadowing";
2179
2180            let rustdoc = load_pregenerated_rustdoc(test_crate);
2181            let indexed_crate = IndexedCrate::new(&rustdoc);
2182
2183            let item_id_candidates = rustdoc
2184                .index
2185                .iter()
2186                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2187                .collect_vec();
2188            if item_id_candidates.len() != 3 {
2189                panic!(
2190                    "Expected to find exactly 3 items with name \
2191                    Foo, but found these matching IDs: {item_id_candidates:?}"
2192                );
2193            }
2194
2195            for item_id in item_id_candidates {
2196                let actual_items: Vec<_> = indexed_crate
2197                    .publicly_importable_names(item_id)
2198                    .into_iter()
2199                    .map(|importable| importable.path.components.into_iter().join("::"))
2200                    .collect();
2201
2202                assert!(
2203                    actual_items.is_empty(),
2204                    "expected no importable item names but found {actual_items:?}"
2205                );
2206            }
2207        }
2208
2209        #[test]
2210        fn visibility_modifier_avoids_shadowing() {
2211            let test_crate = "visibility_modifier_avoids_shadowing";
2212
2213            let rustdoc = load_pregenerated_rustdoc(test_crate);
2214            let indexed_crate = IndexedCrate::new(&rustdoc);
2215
2216            let item_id_candidates = rustdoc
2217                .index
2218                .iter()
2219                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2220                .collect_vec();
2221            if item_id_candidates.len() != 3 {
2222                panic!(
2223                    "Expected to find exactly 3 items with name \
2224                    Foo, but found these matching IDs: {item_id_candidates:?}"
2225                );
2226            }
2227
2228            for item_id in item_id_candidates {
2229                let actual_items: Vec<_> = indexed_crate
2230                    .publicly_importable_names(item_id)
2231                    .into_iter()
2232                    .map(|importable| importable.path.components.into_iter().join("::"))
2233                    .collect();
2234
2235                if rustdoc.index[item_id].visibility == Visibility::Public {
2236                    assert_eq!(
2237                        vec!["visibility_modifier_avoids_shadowing::Foo"],
2238                        actual_items,
2239                    );
2240                } else {
2241                    assert!(
2242                        actual_items.is_empty(),
2243                        "expected no importable item names but found {actual_items:?}"
2244                    );
2245                }
2246            }
2247        }
2248
2249        #[test]
2250        fn glob_vs_glob_shadowing() {
2251            let test_crate = "glob_vs_glob_shadowing";
2252
2253            let expected_items = btreemap! {
2254                "Foo" => (2, btreeset![]),
2255                "Bar" => (1, btreeset![
2256                    "glob_vs_glob_shadowing::Bar",
2257                ]),
2258                "Baz" => (1, btreeset![
2259                    "glob_vs_glob_shadowing::Baz",
2260                ]),
2261            };
2262
2263            assert_duplicated_exported_items_match(test_crate, &expected_items);
2264        }
2265
2266        #[test]
2267        fn glob_vs_glob_shadowing_downstream() {
2268            let test_crate = "glob_vs_glob_shadowing_downstream";
2269
2270            let expected_items = btreemap! {
2271                "Foo" => (3, btreeset![]),
2272                "Bar" => (1, btreeset![
2273                    "glob_vs_glob_shadowing_downstream::second::Bar",
2274                ]),
2275            };
2276
2277            assert_duplicated_exported_items_match(test_crate, &expected_items);
2278        }
2279
2280        #[test]
2281        fn glob_vs_glob_no_shadowing_for_same_item() {
2282            let test_crate = "glob_vs_glob_no_shadowing_for_same_item";
2283
2284            let expected_items = btreemap! {
2285                "Foo" => btreeset![
2286                    "glob_vs_glob_no_shadowing_for_same_item::Foo",
2287                ],
2288            };
2289
2290            assert_exported_items_match(test_crate, &expected_items);
2291        }
2292
2293        #[test]
2294        fn glob_vs_glob_no_shadowing_for_same_renamed_item() {
2295            let test_crate = "glob_vs_glob_no_shadowing_for_same_renamed_item";
2296
2297            let expected_items = btreemap! {
2298                "Bar" => btreeset![
2299                    "glob_vs_glob_no_shadowing_for_same_renamed_item::Foo",
2300                ],
2301            };
2302
2303            assert_exported_items_match(test_crate, &expected_items);
2304        }
2305
2306        #[test]
2307        fn glob_vs_glob_no_shadowing_for_same_multiply_renamed_item() {
2308            let test_crate = "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item";
2309
2310            let expected_items = btreemap! {
2311                "Bar" => btreeset![
2312                    "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item::Foo",
2313                ],
2314            };
2315
2316            assert_exported_items_match(test_crate, &expected_items);
2317        }
2318
2319        #[test]
2320        fn reexport_consts_and_statics() {
2321            let test_crate = "reexport_consts_and_statics";
2322            let expected_items = btreemap! {
2323                "FIRST" => btreeset![
2324                    "reexport_consts_and_statics::FIRST",
2325                    "reexport_consts_and_statics::inner::FIRST",
2326                ],
2327                "SECOND" => btreeset![
2328                    "reexport_consts_and_statics::SECOND",
2329                    "reexport_consts_and_statics::inner::SECOND",
2330                ],
2331            };
2332
2333            assert_exported_items_match(test_crate, &expected_items);
2334        }
2335
2336        #[test]
2337        fn reexport_as_underscore() {
2338            let test_crate = "reexport_as_underscore";
2339            let expected_items = btreemap! {
2340                "Struct" => btreeset![
2341                    "reexport_as_underscore::Struct",
2342                ],
2343                "Trait" => btreeset![],
2344                "hidden" => btreeset![],
2345                "UnderscoreImported" => btreeset![],
2346            };
2347
2348            assert_exported_items_match(test_crate, &expected_items);
2349        }
2350
2351        #[test]
2352        fn nested_reexport_as_underscore() {
2353            let test_crate = "nested_reexport_as_underscore";
2354            let expected_items = btreemap! {
2355                "Trait" => btreeset![],  // no importable paths!
2356            };
2357
2358            assert_exported_items_match(test_crate, &expected_items);
2359        }
2360
2361        #[test]
2362        fn overlapping_reexport_as_underscore() {
2363            let test_crate = "overlapping_reexport_as_underscore";
2364
2365            let rustdoc = load_pregenerated_rustdoc(test_crate);
2366            let indexed_crate = IndexedCrate::new(&rustdoc);
2367
2368            let item_id_candidates = rustdoc
2369                .index
2370                .iter()
2371                .filter_map(|(id, item)| (item.name.as_deref() == Some("Example")).then_some(id))
2372                .collect_vec();
2373            if item_id_candidates.len() != 2 {
2374                panic!(
2375                    "Expected to find exactly 2 items with name \
2376                    Example, but found these matching IDs: {item_id_candidates:?}"
2377                );
2378            }
2379
2380            for item_id in item_id_candidates {
2381                let importable_paths: Vec<_> = indexed_crate
2382                    .publicly_importable_names(item_id)
2383                    .into_iter()
2384                    .map(|importable| importable.path.components.into_iter().join("::"))
2385                    .collect();
2386
2387                match &rustdoc.index[item_id].inner {
2388                    ItemEnum::Struct(..) => {
2389                        assert_eq!(
2390                            vec!["overlapping_reexport_as_underscore::Example"],
2391                            importable_paths,
2392                        );
2393                    }
2394                    ItemEnum::Trait(..) => {
2395                        assert!(
2396                            importable_paths.is_empty(),
2397                            "expected no importable item names but found {importable_paths:?}"
2398                        );
2399                    }
2400                    _ => unreachable!(
2401                        "unexpected item for ID {item_id:?}: {:?}",
2402                        rustdoc.index[item_id]
2403                    ),
2404                }
2405            }
2406        }
2407
2408        #[test]
2409        fn reexport_declarative_macro() {
2410            let test_crate = "reexport_declarative_macro";
2411            let expected_items = btreemap! {
2412                "top_level_exported" => btreeset![
2413                    "reexport_declarative_macro::top_level_exported",
2414                ],
2415                "private_mod_exported" => btreeset![
2416                    "reexport_declarative_macro::private_mod_exported",
2417                ],
2418                "top_level_reexported" => btreeset![
2419                    "reexport_declarative_macro::top_level_reexported",
2420                    "reexport_declarative_macro::macros::top_level_reexported",
2421                    "reexport_declarative_macro::reexports::top_level_reexported",
2422                    "reexport_declarative_macro::glob_reexports::top_level_reexported",
2423                ],
2424                "private_mod_reexported" => btreeset![
2425                    "reexport_declarative_macro::private_mod_reexported",
2426                    "reexport_declarative_macro::macros::private_mod_reexported",
2427                    "reexport_declarative_macro::reexports::private_mod_reexported",
2428                    "reexport_declarative_macro::glob_reexports::private_mod_reexported",
2429                ],
2430                "top_level_not_exported" => btreeset![],
2431                "private_mod_not_exported" => btreeset![],
2432            };
2433
2434            assert_exported_items_match(test_crate, &expected_items);
2435        }
2436    }
2437}