Skip to main content

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