trustfall_rustdoc_adapter/
indexed_crate.rs

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