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, Stability, StabilityLevel};
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: &'a 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    stability_feature: &'static str,
1037    stability_since: &'static str,
1038    const_stability_feature: Option<&'static str>,
1039}
1040
1041/// Limiting the creation of manually inlined traits to only those that are used by the lints.
1042/// There are other foreign traits, but it is not obvious how the manually inlined traits
1043/// should look like for them.
1044const MANUAL_TRAIT_ITEMS: [ManualTraitItem; 14] = [
1045    ManualTraitItem {
1046        name: "Debug",
1047        path: &["core", "fmt", "Debug"],
1048        is_auto: false,
1049        is_unsafe: false,
1050        stability_feature: "rust1",
1051        stability_since: "1.0.0",
1052        const_stability_feature: None,
1053    },
1054    ManualTraitItem {
1055        name: "Clone",
1056        path: &["core", "clone", "Clone"],
1057        is_auto: false,
1058        is_unsafe: false,
1059        stability_feature: "rust1",
1060        stability_since: "1.0.0",
1061        const_stability_feature: Some("const_clone"),
1062    },
1063    ManualTraitItem {
1064        name: "Copy",
1065        path: &["core", "marker", "Copy"],
1066        is_auto: false,
1067        is_unsafe: false,
1068        stability_feature: "rust1",
1069        stability_since: "1.0.0",
1070        const_stability_feature: None,
1071    },
1072    ManualTraitItem {
1073        name: "PartialOrd",
1074        path: &["core", "cmp", "PartialOrd"],
1075        is_auto: false,
1076        is_unsafe: false,
1077        stability_feature: "rust1",
1078        stability_since: "1.0.0",
1079        const_stability_feature: Some("const_cmp"),
1080    },
1081    ManualTraitItem {
1082        name: "Ord",
1083        path: &["core", "cmp", "Ord"],
1084        is_auto: false,
1085        is_unsafe: false,
1086        stability_feature: "rust1",
1087        stability_since: "1.0.0",
1088        const_stability_feature: Some("const_cmp"),
1089    },
1090    ManualTraitItem {
1091        name: "PartialEq",
1092        path: &["core", "cmp", "PartialEq"],
1093        is_auto: false,
1094        is_unsafe: false,
1095        stability_feature: "rust1",
1096        stability_since: "1.0.0",
1097        const_stability_feature: Some("const_cmp"),
1098    },
1099    ManualTraitItem {
1100        name: "Eq",
1101        path: &["core", "cmp", "Eq"],
1102        is_auto: false,
1103        is_unsafe: false,
1104        stability_feature: "rust1",
1105        stability_since: "1.0.0",
1106        const_stability_feature: Some("const_cmp"),
1107    },
1108    ManualTraitItem {
1109        name: "Hash",
1110        path: &["core", "hash", "Hash"],
1111        is_auto: false,
1112        is_unsafe: false,
1113        stability_feature: "rust1",
1114        stability_since: "1.0.0",
1115        const_stability_feature: None,
1116    },
1117    ManualTraitItem {
1118        name: "Send",
1119        path: &["core", "marker", "Send"],
1120        is_auto: true,
1121        is_unsafe: true,
1122        stability_feature: "rust1",
1123        stability_since: "1.0.0",
1124        const_stability_feature: None,
1125    },
1126    ManualTraitItem {
1127        name: "Sync",
1128        path: &["core", "marker", "Sync"],
1129        is_auto: true,
1130        is_unsafe: true,
1131        stability_feature: "rust1",
1132        stability_since: "1.0.0",
1133        const_stability_feature: None,
1134    },
1135    ManualTraitItem {
1136        name: "Unpin",
1137        path: &["core", "marker", "Unpin"],
1138        is_auto: true,
1139        is_unsafe: false,
1140        stability_feature: "pin",
1141        stability_since: "1.33.0",
1142        const_stability_feature: None,
1143    },
1144    ManualTraitItem {
1145        name: "RefUnwindSafe",
1146        path: &["core", "panic", "unwind_safe", "RefUnwindSafe"],
1147        is_auto: true,
1148        is_unsafe: false,
1149        stability_feature: "catch_unwind",
1150        stability_since: "1.9.0",
1151        const_stability_feature: None,
1152    },
1153    ManualTraitItem {
1154        name: "UnwindSafe",
1155        path: &["core", "panic", "unwind_safe", "UnwindSafe"],
1156        is_auto: true,
1157        is_unsafe: false,
1158        stability_feature: "catch_unwind",
1159        stability_since: "1.9.0",
1160        const_stability_feature: None,
1161    },
1162    ManualTraitItem {
1163        name: "Sized",
1164        path: &["core", "marker", "Sized"],
1165        is_auto: false,
1166        is_unsafe: false,
1167        stability_feature: "rust1",
1168        stability_since: "1.0.0",
1169        const_stability_feature: None,
1170    },
1171];
1172
1173fn new_trait(manual_trait_item: &ManualTraitItem, id: Id, crate_id: u32) -> Item {
1174    Item {
1175        id,
1176        crate_id,
1177        name: Some(manual_trait_item.name.to_string()),
1178        span: None,
1179        visibility: rustdoc_types::Visibility::Public,
1180        docs: None,
1181        links: HashMap::default(),
1182        attrs: Vec::new(),
1183        deprecation: None,
1184        stability: Some(Box::new(Stability {
1185            feature: manual_trait_item.stability_feature.to_string(),
1186            level: StabilityLevel::Stable {
1187                since: Some(manual_trait_item.stability_since.to_string()),
1188            },
1189        })),
1190        const_stability: manual_trait_item.const_stability_feature.map(|feature| {
1191            Box::new(Stability {
1192                feature: feature.to_string(),
1193                level: StabilityLevel::Unstable,
1194            })
1195        }),
1196        inner: rustdoc_types::ItemEnum::Trait(rustdoc_types::Trait {
1197            is_auto: manual_trait_item.is_auto,
1198            is_unsafe: manual_trait_item.is_unsafe,
1199            is_dyn_compatible: matches!(
1200                manual_trait_item.name,
1201                "Debug"
1202                    | "PartialEq"
1203                    | "PartialOrd"
1204                    | "Send"
1205                    | "Sync"
1206                    | "Unpin"
1207                    | "UnwindSafe"
1208                    | "RefUnwindSafe"
1209            ),
1210            // The `item`, `generics`, `bounds` and `implementations`
1211            // are not currently present in the schema,
1212            // so it is safe to fill them with empty containers,
1213            // even though some traits in reality have some values in them.
1214            items: Vec::new(),
1215            generics: rustdoc_types::Generics {
1216                params: Vec::new(),
1217                where_predicates: Vec::new(),
1218            },
1219            bounds: Vec::new(),
1220            implementations: Vec::new(),
1221        }),
1222    }
1223}
1224
1225fn create_manually_inlined_builtin_traits(crate_: &Crate) -> (HashMap<Id, Item>, Id) {
1226    let paths = &crate_.paths;
1227
1228    // `paths` may have thousands of items.
1229    #[cfg(feature = "rayon")]
1230    let iter = paths.par_iter();
1231    #[cfg(not(feature = "rayon"))]
1232    let iter = paths.iter();
1233
1234    let manually_inlined_builtin_traits: HashMap<Id, Item> = iter
1235        .filter_map(|(id, entry)| {
1236            if entry.kind != rustdoc_types::ItemKind::Trait {
1237                return None;
1238            }
1239
1240            // This is a linear scan, but across a tiny array.
1241            // It isn't worth doing anything fancier here.
1242            MANUAL_TRAIT_ITEMS
1243                .iter()
1244                .find(|t| t.path == entry.path)
1245                .map(|manual| (*id, new_trait(manual, *id, entry.crate_id)))
1246        })
1247        .collect();
1248
1249    assert_eq!(
1250        manually_inlined_builtin_traits.len(),
1251        MANUAL_TRAIT_ITEMS.len(),
1252        "failed to find some expected built-in traits: found only {manually_inlined_builtin_traits:?} and expected {MANUAL_TRAIT_ITEMS:?}",
1253    );
1254
1255    let sized_id = manually_inlined_builtin_traits
1256        .iter()
1257        .find(|(_, item)| item.name.as_deref() == Some("Sized"))
1258        .map(|(id, _)| *id)
1259        .expect("failed to find `Sized` trait");
1260
1261    (manually_inlined_builtin_traits, sized_id)
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266    use itertools::Itertools;
1267    use rustdoc_types::{Crate, Id};
1268
1269    use crate::{ImportablePath, IndexedCrate, test_util::load_pregenerated_rustdoc};
1270
1271    fn find_item_id<'a>(crate_: &'a Crate, name: &str) -> &'a Id {
1272        crate_
1273            .index
1274            .iter()
1275            .filter_map(|(id, item)| (item.name.as_deref() == Some(name)).then_some(id))
1276            .exactly_one()
1277            .expect("exactly one matching name")
1278    }
1279
1280    // These tests stay at the indexing layer when they assert rustdoc JSON
1281    // invariants or path/reachability facts. Schema-visible behavior belongs
1282    // in `src/adapter/tests.rs`.
1283    mod rust_std_stability {
1284        use rustdoc_types::{Item, ItemEnum, StabilityLevel};
1285
1286        use super::{ImportablePath, find_item_id};
1287        use crate::{PackageIndex, test_util::load_pregenerated_rustdoc};
1288
1289        const STABILITY_FIXTURE: &str = "rust_std_stability";
1290        const CRATE_ROOT: &str = "rust_std_stability";
1291
1292        fn public_path<'a>(
1293            paths: &'a [ImportablePath<'a>],
1294            components: &[&str],
1295        ) -> &'a ImportablePath<'a> {
1296            paths
1297                .iter()
1298                .find(|path| path.path.components == components)
1299                .expect("expected importable path not found")
1300        }
1301
1302        fn assert_const_unstable_feature(item: &Item, expected_feature: &str) {
1303            let const_stability = item
1304                .const_stability
1305                .as_deref()
1306                .expect("expected const stability");
1307            assert!(matches!(const_stability.level, StabilityLevel::Unstable));
1308            assert_eq!(expected_feature, const_stability.feature);
1309        }
1310
1311        fn assert_unstable_feature(item: &Item, expected_feature: &str) {
1312            let stability = item.stability.as_deref().expect("expected stability");
1313            assert!(matches!(stability.level, StabilityLevel::Unstable));
1314            assert_eq!(expected_feature, stability.feature);
1315        }
1316
1317        #[test]
1318        fn rustdoc_fixture_propagates_inherited_const_stability_to_child_items() {
1319            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1320
1321            let const_impl_method_id = find_item_id(&rustdoc, "const_impl_method");
1322            let const_impl_method = &rustdoc.index[const_impl_method_id];
1323            let ItemEnum::Function(function) = &const_impl_method.inner else {
1324                panic!("expected function item");
1325            };
1326            assert!(function.header.is_const);
1327            assert_const_unstable_feature(const_impl_method, "const_inherent_impl_unstable");
1328
1329            let provided_method_id = find_item_id(&rustdoc, "provided");
1330            let provided_method = &rustdoc.index[provided_method_id];
1331            let ItemEnum::Function(function) = &provided_method.inner else {
1332                panic!("expected function item");
1333            };
1334            assert!(!function.header.is_const);
1335            assert_const_unstable_feature(provided_method, "fixture_const_trait_unstable");
1336        }
1337
1338        #[test]
1339        fn rustdoc_fixture_propagates_inherited_item_stability_to_child_items() {
1340            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1341
1342            for (name, expected_feature) in [
1343                (
1344                    "unannotated_method_in_unstable_trait",
1345                    "unstable_trait_with_unannotated_method",
1346                ),
1347                (
1348                    "method_inside_unstable_inherent_impl",
1349                    "unstable_inherent_impl",
1350                ),
1351            ] {
1352                let item_id = find_item_id(&rustdoc, name);
1353                let item = &rustdoc.index[item_id];
1354                let ItemEnum::Function(_) = &item.inner else {
1355                    panic!("expected function item");
1356                };
1357                assert_unstable_feature(item, expected_feature);
1358            }
1359        }
1360
1361        #[test]
1362        fn default_policy_ignores_rust_std_structured_stability() {
1363            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1364            let item_id = find_item_id(&rustdoc, "unstable_function");
1365            let package_index = PackageIndex::from_crate(&rustdoc);
1366            let paths = package_index.own_crate.publicly_importable_names(item_id);
1367
1368            assert_eq!(paths.len(), 1);
1369            assert!(paths[0].public_api());
1370            assert!(
1371                package_index
1372                    .own_crate
1373                    .public_api_eligible(&rustdoc.index[item_id])
1374            );
1375        }
1376
1377        #[test]
1378        fn rust_std_policy_treats_unstable_item_as_non_public_api() {
1379            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1380            let item_id = find_item_id(&rustdoc, "unstable_function");
1381            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1382            let paths = package_index.own_crate.publicly_importable_names(item_id);
1383
1384            assert_eq!(paths.len(), 1);
1385            assert!(paths[0].modifiers.unstable);
1386            assert!(!paths[0].public_api());
1387            assert!(
1388                !package_index
1389                    .own_crate
1390                    .public_api_eligible(&rustdoc.index[item_id])
1391            );
1392
1393            let flags = package_index
1394                .own_crate
1395                .flags
1396                .as_ref()
1397                .expect("flags index should exist");
1398            assert!(flags[item_id].is_reachable());
1399            assert!(!flags[item_id].is_pub_reachable());
1400            assert!(flags[item_id].is_non_pub_api_reachable());
1401        }
1402
1403        #[test]
1404        fn rust_std_storage_constructor_applies_stability_to_own_crate() {
1405            let storage =
1406                crate::PackageStorage::from_rustdoc(load_pregenerated_rustdoc(STABILITY_FIXTURE));
1407            let item_id = find_item_id(&storage.own_crate, "unstable_function");
1408            let package_index = PackageIndex::from_rust_std_component_storage(&storage);
1409            let paths = package_index.own_crate.publicly_importable_names(item_id);
1410
1411            assert_eq!(paths.len(), 1);
1412            assert!(!paths[0].public_api());
1413            assert!(paths[0].modifiers.unstable);
1414        }
1415
1416        #[test]
1417        fn rust_std_policy_treats_stable_item_under_unstable_module_as_non_public_api() {
1418            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1419            let item_id = find_item_id(&rustdoc, "stable_inside_unstable_module");
1420            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1421            let paths = package_index.own_crate.publicly_importable_names(item_id);
1422
1423            let path = public_path(
1424                &paths,
1425                &[
1426                    CRATE_ROOT,
1427                    "unstable_module",
1428                    "stable_inside_unstable_module",
1429                ],
1430            );
1431            assert!(path.modifiers.unstable);
1432            assert!(!path.public_api());
1433            assert!(
1434                package_index
1435                    .own_crate
1436                    .public_api_eligible(&rustdoc.index[item_id])
1437            );
1438        }
1439
1440        #[test]
1441        fn rust_std_policy_allows_stable_reexport_from_unstable_module() {
1442            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1443            let item_id = find_item_id(&rustdoc, "stable_reexported_from_unstable_module");
1444            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1445            let paths = package_index.own_crate.publicly_importable_names(item_id);
1446
1447            let stable_reexport_path = public_path(
1448                &paths,
1449                &[CRATE_ROOT, "stable_reexport_from_unstable_module"],
1450            );
1451            let unstable_definition_path = public_path(
1452                &paths,
1453                &[
1454                    CRATE_ROOT,
1455                    "unstable_reexport_source",
1456                    "stable_reexported_from_unstable_module",
1457                ],
1458            );
1459            assert!(stable_reexport_path.public_api());
1460            assert!(!stable_reexport_path.modifiers.unstable);
1461            assert!(!unstable_definition_path.public_api());
1462            assert!(unstable_definition_path.modifiers.unstable);
1463            assert!(
1464                package_index
1465                    .own_crate
1466                    .public_api_eligible(&rustdoc.index[item_id])
1467            );
1468        }
1469
1470        #[test]
1471        fn rust_std_policy_keeps_public_and_non_public_paths_for_same_item() {
1472            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1473            let item_id = find_item_id(&rustdoc, "reexport_target");
1474            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1475            let paths = package_index.own_crate.publicly_importable_names(item_id);
1476
1477            let stable_path = public_path(&paths, &[CRATE_ROOT, "stable_reexport_target"]);
1478            let unstable_path = public_path(&paths, &[CRATE_ROOT, "unstable_reexport_target"]);
1479            assert!(stable_path.public_api());
1480            assert!(!stable_path.modifiers.unstable);
1481            assert!(!unstable_path.public_api());
1482            assert!(unstable_path.modifiers.unstable);
1483        }
1484
1485        #[test]
1486        fn rust_std_policy_applies_direct_glob_use_stability_to_importable_path() {
1487            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1488            let item_id = find_item_id(&rustdoc, "direct_glob_target");
1489            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1490            let paths = package_index.own_crate.publicly_importable_names(item_id);
1491
1492            let module_path = public_path(
1493                &paths,
1494                &[CRATE_ROOT, "direct_glob_source", "direct_glob_target"],
1495            );
1496            let glob_path = public_path(&paths, &[CRATE_ROOT, "direct_glob_target"]);
1497            assert!(module_path.public_api());
1498            assert!(!module_path.modifiers.unstable);
1499            assert!(!glob_path.public_api());
1500            assert!(glob_path.modifiers.unstable);
1501        }
1502
1503        #[test]
1504        fn rust_std_policy_applies_nested_glob_stability_to_synthesized_paths() {
1505            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1506            let item_id = find_item_id(&rustdoc, "nested_glob_target");
1507            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1508            let paths = package_index.own_crate.publicly_importable_names(item_id);
1509
1510            let root_glob_path = public_path(&paths, &[CRATE_ROOT, "nested_glob_target"]);
1511            let outer_path =
1512                public_path(&paths, &[CRATE_ROOT, "nested_outer", "nested_glob_target"]);
1513            let inner_path = public_path(
1514                &paths,
1515                &[
1516                    CRATE_ROOT,
1517                    "nested_outer",
1518                    "nested_inner",
1519                    "nested_glob_target",
1520                ],
1521            );
1522            assert!(!root_glob_path.public_api());
1523            assert!(root_glob_path.modifiers.unstable);
1524            assert!(!outer_path.public_api());
1525            assert!(outer_path.modifiers.unstable);
1526            assert!(inner_path.public_api());
1527            assert!(!inner_path.modifiers.unstable);
1528        }
1529
1530        #[test]
1531        fn rust_std_policy_preserves_non_glob_reexport_stability_through_glob() {
1532            let rustdoc = load_pregenerated_rustdoc(STABILITY_FIXTURE);
1533            let item_id = find_item_id(&rustdoc, "non_glob_target");
1534            let package_index = PackageIndex::from_rust_std_component_crate(&rustdoc);
1535            let paths = package_index.own_crate.publicly_importable_names(item_id);
1536
1537            let stable_glob_path = public_path(&paths, &[CRATE_ROOT, "non_glob_target"]);
1538            let unstable_glob_path = public_path(&paths, &[CRATE_ROOT, "non_glob_unstable_alias"]);
1539            assert!(stable_glob_path.public_api());
1540            assert!(!stable_glob_path.modifiers.unstable);
1541            assert!(!unstable_glob_path.public_api());
1542            assert!(unstable_glob_path.modifiers.unstable);
1543        }
1544    }
1545
1546    /// Ensure that methods, consts, and fields within structs are not importable.
1547    #[test]
1548    fn structs_are_not_modules() {
1549        let rustdoc = load_pregenerated_rustdoc("structs_are_not_modules");
1550        let indexed_crate = IndexedCrate::new(&rustdoc);
1551
1552        let top_level_function = find_item_id(&rustdoc, "top_level_function");
1553        let method = find_item_id(&rustdoc, "method");
1554        let associated_fn = find_item_id(&rustdoc, "associated_fn");
1555        let field = find_item_id(&rustdoc, "field");
1556        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
1557
1558        // All the items are public.
1559        assert!(
1560            indexed_crate
1561                .visibility_tracker
1562                .visible_parent_edges()
1563                .contains_key(&top_level_function.0)
1564        );
1565        assert!(
1566            indexed_crate
1567                .visibility_tracker
1568                .visible_parent_edges()
1569                .contains_key(&method.0)
1570        );
1571        assert!(
1572            indexed_crate
1573                .visibility_tracker
1574                .visible_parent_edges()
1575                .contains_key(&associated_fn.0)
1576        );
1577        assert!(
1578            indexed_crate
1579                .visibility_tracker
1580                .visible_parent_edges()
1581                .contains_key(&field.0)
1582        );
1583        assert!(
1584            indexed_crate
1585                .visibility_tracker
1586                .visible_parent_edges()
1587                .contains_key(&const_item.0)
1588        );
1589
1590        // But only `top_level_function` is importable.
1591        assert_eq!(
1592            vec![ImportablePath::new(
1593                vec!["structs_are_not_modules", "top_level_function"],
1594                false,
1595                false,
1596                false,
1597            )],
1598            indexed_crate.publicly_importable_names(top_level_function)
1599        );
1600        assert_eq!(
1601            Vec::<ImportablePath<'_>>::new(),
1602            indexed_crate.publicly_importable_names(method)
1603        );
1604        assert_eq!(
1605            Vec::<ImportablePath<'_>>::new(),
1606            indexed_crate.publicly_importable_names(associated_fn)
1607        );
1608        assert_eq!(
1609            Vec::<ImportablePath<'_>>::new(),
1610            indexed_crate.publicly_importable_names(field)
1611        );
1612        assert_eq!(
1613            Vec::<ImportablePath<'_>>::new(),
1614            indexed_crate.publicly_importable_names(const_item)
1615        );
1616    }
1617
1618    /// Ensure that methods and consts within enums are not importable.
1619    /// However, enum variants are the exception: they are importable!
1620    #[test]
1621    fn enums_are_not_modules() {
1622        let rustdoc = load_pregenerated_rustdoc("enums_are_not_modules");
1623        let indexed_crate = IndexedCrate::new(&rustdoc);
1624
1625        let top_level_function = find_item_id(&rustdoc, "top_level_function");
1626        let variant = find_item_id(&rustdoc, "Variant");
1627        let method = find_item_id(&rustdoc, "method");
1628        let associated_fn = find_item_id(&rustdoc, "associated_fn");
1629        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
1630
1631        // All the items are public.
1632        assert!(
1633            indexed_crate
1634                .visibility_tracker
1635                .visible_parent_edges()
1636                .contains_key(&top_level_function.0)
1637        );
1638        assert!(
1639            indexed_crate
1640                .visibility_tracker
1641                .visible_parent_edges()
1642                .contains_key(&variant.0)
1643        );
1644        assert!(
1645            indexed_crate
1646                .visibility_tracker
1647                .visible_parent_edges()
1648                .contains_key(&method.0)
1649        );
1650        assert!(
1651            indexed_crate
1652                .visibility_tracker
1653                .visible_parent_edges()
1654                .contains_key(&associated_fn.0)
1655        );
1656        assert!(
1657            indexed_crate
1658                .visibility_tracker
1659                .visible_parent_edges()
1660                .contains_key(&const_item.0)
1661        );
1662
1663        // But only `top_level_function` and `Foo::variant` is importable.
1664        assert_eq!(
1665            vec![ImportablePath::new(
1666                vec!["enums_are_not_modules", "top_level_function"],
1667                false,
1668                false,
1669                false,
1670            )],
1671            indexed_crate.publicly_importable_names(top_level_function)
1672        );
1673        assert_eq!(
1674            vec![ImportablePath::new(
1675                vec!["enums_are_not_modules", "Foo", "Variant"],
1676                false,
1677                false,
1678                false,
1679            )],
1680            indexed_crate.publicly_importable_names(variant)
1681        );
1682        assert_eq!(
1683            Vec::<ImportablePath<'_>>::new(),
1684            indexed_crate.publicly_importable_names(method)
1685        );
1686        assert_eq!(
1687            Vec::<ImportablePath<'_>>::new(),
1688            indexed_crate.publicly_importable_names(associated_fn)
1689        );
1690        assert_eq!(
1691            Vec::<ImportablePath<'_>>::new(),
1692            indexed_crate.publicly_importable_names(const_item)
1693        );
1694    }
1695
1696    /// Ensure that methods, consts, and fields within unions are not importable.
1697    #[test]
1698    fn unions_are_not_modules() {
1699        let rustdoc = load_pregenerated_rustdoc("unions_are_not_modules");
1700        let indexed_crate = IndexedCrate::new(&rustdoc);
1701
1702        let top_level_function = find_item_id(&rustdoc, "top_level_function");
1703        let method = find_item_id(&rustdoc, "method");
1704        let associated_fn = find_item_id(&rustdoc, "associated_fn");
1705        let left_field = find_item_id(&rustdoc, "left");
1706        let right_field = find_item_id(&rustdoc, "right");
1707        let const_item = find_item_id(&rustdoc, "THE_ANSWER");
1708
1709        // All the items are public.
1710        assert!(
1711            indexed_crate
1712                .visibility_tracker
1713                .visible_parent_edges()
1714                .contains_key(&top_level_function.0)
1715        );
1716        assert!(
1717            indexed_crate
1718                .visibility_tracker
1719                .visible_parent_edges()
1720                .contains_key(&method.0)
1721        );
1722        assert!(
1723            indexed_crate
1724                .visibility_tracker
1725                .visible_parent_edges()
1726                .contains_key(&associated_fn.0)
1727        );
1728        assert!(
1729            indexed_crate
1730                .visibility_tracker
1731                .visible_parent_edges()
1732                .contains_key(&left_field.0)
1733        );
1734        assert!(
1735            indexed_crate
1736                .visibility_tracker
1737                .visible_parent_edges()
1738                .contains_key(&right_field.0)
1739        );
1740        assert!(
1741            indexed_crate
1742                .visibility_tracker
1743                .visible_parent_edges()
1744                .contains_key(&const_item.0)
1745        );
1746
1747        // But only `top_level_function` is importable.
1748        assert_eq!(
1749            vec![ImportablePath::new(
1750                vec!["unions_are_not_modules", "top_level_function"],
1751                false,
1752                false,
1753                false,
1754            )],
1755            indexed_crate.publicly_importable_names(top_level_function)
1756        );
1757        assert_eq!(
1758            Vec::<ImportablePath<'_>>::new(),
1759            indexed_crate.publicly_importable_names(method)
1760        );
1761        assert_eq!(
1762            Vec::<ImportablePath<'_>>::new(),
1763            indexed_crate.publicly_importable_names(associated_fn)
1764        );
1765        assert_eq!(
1766            Vec::<ImportablePath<'_>>::new(),
1767            indexed_crate.publicly_importable_names(left_field)
1768        );
1769        assert_eq!(
1770            Vec::<ImportablePath<'_>>::new(),
1771            indexed_crate.publicly_importable_names(right_field)
1772        );
1773        assert_eq!(
1774            Vec::<ImportablePath<'_>>::new(),
1775            indexed_crate.publicly_importable_names(const_item)
1776        );
1777    }
1778
1779    mod reexports {
1780        use std::collections::{BTreeMap, BTreeSet};
1781
1782        use itertools::Itertools;
1783        use maplit::{btreemap, btreeset};
1784        use rustdoc_types::{ItemEnum, Visibility};
1785
1786        use crate::{ImportablePath, IndexedCrate, test_util::load_pregenerated_rustdoc};
1787
1788        fn assert_exported_items_match(
1789            test_crate: &str,
1790            expected_items: &BTreeMap<&str, BTreeSet<&str>>,
1791        ) {
1792            let rustdoc = load_pregenerated_rustdoc(test_crate);
1793            let indexed_crate = IndexedCrate::new(&rustdoc);
1794
1795            for (&expected_item_name, expected_importable_paths) in expected_items {
1796                assert!(
1797                    !expected_item_name.contains(':'),
1798                    "only direct item names can be checked at the moment: {expected_item_name}"
1799                );
1800
1801                let item_id_candidates = rustdoc
1802                    .index
1803                    .iter()
1804                    .filter_map(|(id, item)| {
1805                        (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1806                    })
1807                    .collect_vec();
1808                if item_id_candidates.len() != 1 {
1809                    panic!(
1810                        "Expected to find exactly one item with name {expected_item_name}, \
1811                        but found these matching IDs: {item_id_candidates:?}"
1812                    );
1813                }
1814                let item_id = item_id_candidates[0];
1815                let actual_items: Vec<_> = indexed_crate
1816                    .publicly_importable_names(item_id)
1817                    .into_iter()
1818                    .map(|importable| importable.path.components.into_iter().join("::"))
1819                    .collect();
1820                let deduplicated_actual_items: BTreeSet<_> =
1821                    actual_items.iter().map(|x| x.as_str()).collect();
1822                assert_eq!(
1823                    actual_items.len(),
1824                    deduplicated_actual_items.len(),
1825                    "duplicates found: {actual_items:?}"
1826                );
1827
1828                assert_eq!(
1829                    expected_importable_paths, &deduplicated_actual_items,
1830                    "mismatch for item name {expected_item_name}",
1831                );
1832            }
1833        }
1834
1835        /// Allows testing for items with overlapping names, such as a function and a type
1836        /// with the same name (which Rust considers in separate namespaces).
1837        fn assert_duplicated_exported_items_match(
1838            test_crate: &str,
1839            expected_items_and_counts: &BTreeMap<&str, (usize, BTreeSet<&str>)>,
1840        ) {
1841            let rustdoc = load_pregenerated_rustdoc(test_crate);
1842            let indexed_crate = IndexedCrate::new(&rustdoc);
1843
1844            for (&expected_item_name, (expected_count, expected_importable_paths)) in
1845                expected_items_and_counts
1846            {
1847                assert!(
1848                    !expected_item_name.contains(':'),
1849                    "only direct item names can be checked at the moment: {expected_item_name}"
1850                );
1851
1852                let item_id_candidates = rustdoc
1853                    .index
1854                    .iter()
1855                    .filter_map(|(id, item)| {
1856                        (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1857                    })
1858                    .collect_vec();
1859                if item_id_candidates.len() != *expected_count {
1860                    panic!(
1861                        "Expected to find exactly {expected_count} items with name \
1862                        {expected_item_name}, but found these matching IDs: {item_id_candidates:?}"
1863                    );
1864                }
1865                for item_id in item_id_candidates {
1866                    let actual_items: Vec<_> = indexed_crate
1867                        .publicly_importable_names(item_id)
1868                        .into_iter()
1869                        .map(|importable| importable.path.components.into_iter().join("::"))
1870                        .collect();
1871                    let deduplicated_actual_items: BTreeSet<_> =
1872                        actual_items.iter().map(|x| x.as_str()).collect();
1873                    assert_eq!(
1874                        actual_items.len(),
1875                        deduplicated_actual_items.len(),
1876                        "duplicates found: {actual_items:?}"
1877                    );
1878                    assert_eq!(expected_importable_paths, &deduplicated_actual_items);
1879                }
1880            }
1881        }
1882
1883        #[test]
1884        fn pub_inside_pub_crate_mod() {
1885            let test_crate = "pub_inside_pub_crate_mod";
1886            let expected_items = btreemap! {
1887                "Foo" => btreeset![],
1888                "Bar" => btreeset![
1889                    "pub_inside_pub_crate_mod::Bar",
1890                ],
1891            };
1892
1893            assert_exported_items_match(test_crate, &expected_items);
1894        }
1895
1896        #[test]
1897        fn reexport() {
1898            let test_crate = "reexport";
1899            let expected_items = btreemap! {
1900                "foo" => btreeset![
1901                    "reexport::foo",
1902                    "reexport::inner::foo",
1903                ],
1904            };
1905
1906            assert_exported_items_match(test_crate, &expected_items);
1907        }
1908
1909        #[test]
1910        fn reexport_from_private_module() {
1911            let test_crate = "reexport_from_private_module";
1912            let expected_items = btreemap! {
1913                "foo" => btreeset![
1914                    "reexport_from_private_module::foo",
1915                ],
1916                "Bar" => btreeset![
1917                    "reexport_from_private_module::Bar",
1918                ],
1919                "Baz" => btreeset![
1920                    "reexport_from_private_module::nested::Baz",
1921                ],
1922                "quux" => btreeset![
1923                    "reexport_from_private_module::quux",
1924                ],
1925            };
1926
1927            assert_exported_items_match(test_crate, &expected_items);
1928        }
1929
1930        #[test]
1931        fn renaming_reexport() {
1932            let test_crate = "renaming_reexport";
1933            let expected_items = btreemap! {
1934                "foo" => btreeset![
1935                    "renaming_reexport::bar",
1936                    "renaming_reexport::inner::foo",
1937                ],
1938            };
1939
1940            assert_exported_items_match(test_crate, &expected_items);
1941        }
1942
1943        #[test]
1944        fn renaming_reexport_of_reexport() {
1945            let test_crate = "renaming_reexport_of_reexport";
1946            let expected_items = btreemap! {
1947                "foo" => btreeset![
1948                    "renaming_reexport_of_reexport::bar",
1949                    "renaming_reexport_of_reexport::foo",
1950                    "renaming_reexport_of_reexport::inner::foo",
1951                ],
1952            };
1953
1954            assert_exported_items_match(test_crate, &expected_items);
1955        }
1956
1957        #[test]
1958        fn renaming_mod_reexport() {
1959            let test_crate = "renaming_mod_reexport";
1960            let expected_items = btreemap! {
1961                "foo" => btreeset![
1962                    "renaming_mod_reexport::inner::a::foo",
1963                    "renaming_mod_reexport::inner::b::foo",
1964                    "renaming_mod_reexport::direct::foo",
1965                ],
1966            };
1967
1968            assert_exported_items_match(test_crate, &expected_items);
1969        }
1970
1971        #[test]
1972        fn glob_reexport() {
1973            let test_crate = "glob_reexport";
1974            let expected_items = btreemap! {
1975                "foo" => btreeset![
1976                    "glob_reexport::foo",
1977                    "glob_reexport::inner::foo",
1978                ],
1979                "Bar" => btreeset![
1980                    "glob_reexport::Bar",
1981                    "glob_reexport::inner::Bar",
1982                ],
1983                "nested" => btreeset![
1984                    "glob_reexport::nested",
1985                ],
1986                "Baz" => btreeset![
1987                    "glob_reexport::Baz",
1988                ],
1989                "First" => btreeset![
1990                    "glob_reexport::First",
1991                    "glob_reexport::Baz::First",
1992                ],
1993                "Second" => btreeset![
1994                    "glob_reexport::Second",
1995                    "glob_reexport::Baz::Second",
1996                ],
1997            };
1998
1999            assert_exported_items_match(test_crate, &expected_items);
2000        }
2001
2002        #[test]
2003        fn glob_of_glob_reexport() {
2004            let test_crate = "glob_of_glob_reexport";
2005            let expected_items = btreemap! {
2006                "foo" => btreeset![
2007                    "glob_of_glob_reexport::foo",
2008                ],
2009                "Bar" => btreeset![
2010                    "glob_of_glob_reexport::Bar",
2011                ],
2012                "Baz" => btreeset![
2013                    "glob_of_glob_reexport::Baz",
2014                ],
2015                "Onion" => btreeset![
2016                    "glob_of_glob_reexport::Onion",
2017                ],
2018            };
2019
2020            assert_exported_items_match(test_crate, &expected_items);
2021        }
2022
2023        #[test]
2024        fn glob_of_renamed_reexport() {
2025            let test_crate = "glob_of_renamed_reexport";
2026            let expected_items = btreemap! {
2027                "foo" => btreeset![
2028                    "glob_of_renamed_reexport::renamed_foo",
2029                ],
2030                "Bar" => btreeset![
2031                    "glob_of_renamed_reexport::RenamedBar",
2032                ],
2033                "First" => btreeset![
2034                    "glob_of_renamed_reexport::RenamedFirst",
2035                ],
2036                "Onion" => btreeset![
2037                    "glob_of_renamed_reexport::RenamedOnion",
2038                ],
2039            };
2040
2041            assert_exported_items_match(test_crate, &expected_items);
2042        }
2043
2044        #[test]
2045        fn glob_reexport_enum_variants() {
2046            let test_crate = "glob_reexport_enum_variants";
2047            let expected_items = btreemap! {
2048                "First" => btreeset![
2049                    "glob_reexport_enum_variants::First",
2050                ],
2051                "Second" => btreeset![
2052                    "glob_reexport_enum_variants::Second",
2053                ],
2054            };
2055
2056            assert_exported_items_match(test_crate, &expected_items);
2057        }
2058
2059        #[test]
2060        fn glob_reexport_cycle() {
2061            let test_crate = "glob_reexport_cycle";
2062            let expected_items = btreemap! {
2063                "foo" => btreeset![
2064                    "glob_reexport_cycle::first::foo",
2065                    "glob_reexport_cycle::second::foo",
2066                ],
2067                "Bar" => btreeset![
2068                    "glob_reexport_cycle::first::Bar",
2069                    "glob_reexport_cycle::second::Bar",
2070                ],
2071            };
2072
2073            assert_exported_items_match(test_crate, &expected_items);
2074        }
2075
2076        #[test]
2077        fn infinite_recursive_reexport() {
2078            let test_crate = "infinite_recursive_reexport";
2079            let expected_items = btreemap! {
2080                "foo" => btreeset![
2081                    // We don't want to expand all infinitely-many names here.
2082                    // We only return cycle-free paths, which are the following:
2083                    "infinite_recursive_reexport::foo",
2084                    "infinite_recursive_reexport::inner::foo",
2085                ],
2086            };
2087
2088            assert_exported_items_match(test_crate, &expected_items);
2089        }
2090
2091        #[test]
2092        fn infinite_indirect_recursive_reexport() {
2093            let test_crate = "infinite_indirect_recursive_reexport";
2094            let expected_items = btreemap! {
2095                "foo" => btreeset![
2096                    // We don't want to expand all infinitely-many names here.
2097                    // We only return cycle-free paths, which are the following:
2098                    "infinite_indirect_recursive_reexport::foo",
2099                    "infinite_indirect_recursive_reexport::nested::foo",
2100                ],
2101            };
2102
2103            assert_exported_items_match(test_crate, &expected_items);
2104        }
2105
2106        #[test]
2107        fn infinite_corecursive_reexport() {
2108            let test_crate = "infinite_corecursive_reexport";
2109            let expected_items = btreemap! {
2110                "foo" => btreeset![
2111                    // We don't want to expand all infinitely-many names here.
2112                    // We only return cycle-free paths, which are the following:
2113                    "infinite_corecursive_reexport::a::foo",
2114                    "infinite_corecursive_reexport::b::a::foo",
2115                ],
2116            };
2117
2118            assert_exported_items_match(test_crate, &expected_items);
2119        }
2120
2121        #[test]
2122        fn pub_type_alias_reexport() {
2123            let test_crate = "pub_type_alias_reexport";
2124            let expected_items = btreemap! {
2125                "Foo" => btreeset![
2126                    "pub_type_alias_reexport::Exported",
2127                ],
2128            };
2129
2130            assert_exported_items_match(test_crate, &expected_items);
2131        }
2132
2133        #[test]
2134        fn pub_generic_type_alias_reexport() {
2135            let test_crate = "pub_generic_type_alias_reexport";
2136            let expected_items = btreemap! {
2137                "Foo" => btreeset![
2138                    // Only `Exported` and `ExportedRenamedParams` are re-exports.
2139                    //
2140                    //`ExportedRenamedParams` renames the generic parameters
2141                    // but does not change their meaning.
2142                    //
2143                    // `ExportedWithDefaults` is not a re-export because it adds
2144                    //
2145                    // The other type aliases are not equivalent since they constrain
2146                    // some of the underlying type's generic parameters.
2147                    "pub_generic_type_alias_reexport::Exported",
2148                    "pub_generic_type_alias_reexport::ExportedRenamedParams",
2149                ],
2150                "Exported" => btreeset![
2151                    // The type alias itself is also a visible item.
2152                    "pub_generic_type_alias_reexport::Exported",
2153                ],
2154                "ExportedWithDefaults" => btreeset![
2155                    // The type alias itself is also a visible item.
2156                    "pub_generic_type_alias_reexport::ExportedWithDefaults",
2157                ],
2158                "ExportedRenamedParams" => btreeset![
2159                    // The type alias itself is also a visible item.
2160                    "pub_generic_type_alias_reexport::ExportedRenamedParams",
2161                ],
2162                "ExportedSpecificLifetime" => btreeset![
2163                    "pub_generic_type_alias_reexport::ExportedSpecificLifetime",
2164                ],
2165                "ExportedSpecificType" => btreeset![
2166                    "pub_generic_type_alias_reexport::ExportedSpecificType",
2167                ],
2168                "ExportedSpecificConst" => btreeset![
2169                    "pub_generic_type_alias_reexport::ExportedSpecificConst",
2170                ],
2171                "ExportedFullySpecified" => btreeset![
2172                    "pub_generic_type_alias_reexport::ExportedFullySpecified",
2173                ],
2174            };
2175
2176            assert_exported_items_match(test_crate, &expected_items);
2177        }
2178
2179        #[test]
2180        fn pub_generic_type_alias_shuffled_order() {
2181            let test_crate = "pub_generic_type_alias_shuffled_order";
2182            let expected_items = btreemap! {
2183                // The type aliases reverse the generic parameters' orders,
2184                // so they are not re-exports of the underlying types.
2185                "GenericFoo" => btreeset![
2186                    "pub_generic_type_alias_shuffled_order::inner::GenericFoo",
2187                ],
2188                "LifetimeFoo" => btreeset![
2189                    "pub_generic_type_alias_shuffled_order::inner::LifetimeFoo",
2190                ],
2191                "ConstFoo" => btreeset![
2192                    "pub_generic_type_alias_shuffled_order::inner::ConstFoo",
2193                ],
2194                "ReversedGenericFoo" => btreeset![
2195                    "pub_generic_type_alias_shuffled_order::ReversedGenericFoo",
2196                ],
2197                "ReversedLifetimeFoo" => btreeset![
2198                    "pub_generic_type_alias_shuffled_order::ReversedLifetimeFoo",
2199                ],
2200                "ReversedConstFoo" => btreeset![
2201                    "pub_generic_type_alias_shuffled_order::ReversedConstFoo",
2202                ],
2203            };
2204
2205            assert_exported_items_match(test_crate, &expected_items);
2206        }
2207
2208        #[test]
2209        fn pub_generic_type_alias_added_defaults() {
2210            let test_crate = "pub_generic_type_alias_added_defaults";
2211            let expected_items = btreemap! {
2212                "Foo" => btreeset![
2213                    "pub_generic_type_alias_added_defaults::inner::Foo",
2214                ],
2215                "Bar" => btreeset![
2216                    "pub_generic_type_alias_added_defaults::inner::Bar",
2217                ],
2218                "DefaultFoo" => btreeset![
2219                    "pub_generic_type_alias_added_defaults::DefaultFoo",
2220                ],
2221                "DefaultBar" => btreeset![
2222                    "pub_generic_type_alias_added_defaults::DefaultBar",
2223                ],
2224            };
2225
2226            assert_exported_items_match(test_crate, &expected_items);
2227        }
2228
2229        #[test]
2230        fn pub_generic_type_alias_changed_defaults() {
2231            let test_crate = "pub_generic_type_alias_changed_defaults";
2232            let expected_items = btreemap! {
2233                // The type aliases change the default values of the generic parameters,
2234                // so they are not re-exports of the underlying types.
2235                "Foo" => btreeset![
2236                    "pub_generic_type_alias_changed_defaults::inner::Foo",
2237                ],
2238                "Bar" => btreeset![
2239                    "pub_generic_type_alias_changed_defaults::inner::Bar",
2240                ],
2241                "ExportedWithoutTypeDefault" => btreeset![
2242                    "pub_generic_type_alias_changed_defaults::ExportedWithoutTypeDefault",
2243                ],
2244                "ExportedWithoutConstDefault" => btreeset![
2245                    "pub_generic_type_alias_changed_defaults::ExportedWithoutConstDefault",
2246                ],
2247                "ExportedWithoutDefaults" => btreeset![
2248                    "pub_generic_type_alias_changed_defaults::ExportedWithoutDefaults",
2249                ],
2250                "ExportedWithDifferentTypeDefault" => btreeset![
2251                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentTypeDefault",
2252                ],
2253                "ExportedWithDifferentConstDefault" => btreeset![
2254                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentConstDefault",
2255                ],
2256                "ExportedWithDifferentDefaults" => btreeset![
2257                    "pub_generic_type_alias_changed_defaults::ExportedWithDifferentDefaults",
2258                ],
2259            };
2260
2261            assert_exported_items_match(test_crate, &expected_items);
2262        }
2263
2264        #[test]
2265        fn pub_generic_type_alias_same_signature_but_not_equivalent() {
2266            let test_crate = "pub_generic_type_alias_same_signature_but_not_equivalent";
2267            let expected_items = btreemap! {
2268                "GenericFoo" => btreeset![
2269                    "pub_generic_type_alias_same_signature_but_not_equivalent::inner::GenericFoo",
2270                ],
2271                "ChangedFoo" => btreeset![
2272                    "pub_generic_type_alias_same_signature_but_not_equivalent::ChangedFoo",
2273                ],
2274            };
2275
2276            assert_exported_items_match(test_crate, &expected_items);
2277        }
2278
2279        #[test]
2280        fn pub_type_alias_of_type_alias() {
2281            let test_crate = "pub_type_alias_of_type_alias";
2282            let expected_items = btreemap! {
2283                "Foo" => btreeset![
2284                    "pub_type_alias_of_type_alias::inner::Foo",
2285                    "pub_type_alias_of_type_alias::inner::AliasedFoo",
2286                    "pub_type_alias_of_type_alias::ExportedFoo",
2287                ],
2288                "Bar" => btreeset![
2289                    "pub_type_alias_of_type_alias::inner::Bar",
2290                    "pub_type_alias_of_type_alias::inner::AliasedBar",
2291                    "pub_type_alias_of_type_alias::ExportedBar",
2292                ],
2293                "AliasedFoo" => btreeset![
2294                    "pub_type_alias_of_type_alias::inner::AliasedFoo",
2295                    "pub_type_alias_of_type_alias::ExportedFoo",
2296                ],
2297                "AliasedBar" => btreeset![
2298                    "pub_type_alias_of_type_alias::inner::AliasedBar",
2299                    "pub_type_alias_of_type_alias::ExportedBar",
2300                ],
2301                "ExportedFoo" => btreeset![
2302                    "pub_type_alias_of_type_alias::ExportedFoo",
2303                ],
2304                "ExportedBar" => btreeset![
2305                    "pub_type_alias_of_type_alias::ExportedBar",
2306                ],
2307                "DifferentLifetimeBar" => btreeset![
2308                    "pub_type_alias_of_type_alias::DifferentLifetimeBar",
2309                ],
2310                "DifferentGenericBar" => btreeset![
2311                    "pub_type_alias_of_type_alias::DifferentGenericBar",
2312                ],
2313                "DifferentConstBar" => btreeset![
2314                    "pub_type_alias_of_type_alias::DifferentConstBar",
2315                ],
2316                "ReorderedBar" => btreeset![
2317                    "pub_type_alias_of_type_alias::ReorderedBar",
2318                ],
2319                "DefaultValueBar" => btreeset![
2320                    "pub_type_alias_of_type_alias::DefaultValueBar",
2321                ],
2322            };
2323
2324            assert_exported_items_match(test_crate, &expected_items);
2325        }
2326
2327        #[test]
2328        fn pub_type_alias_of_composite_type() {
2329            let test_crate = "pub_type_alias_of_composite_type";
2330            let expected_items = btreemap! {
2331                "Foo" => btreeset![
2332                    "pub_type_alias_of_composite_type::inner::Foo",
2333                ],
2334                "I64Tuple" => btreeset![
2335                    "pub_type_alias_of_composite_type::I64Tuple",
2336                ],
2337                "MixedTuple" => btreeset![
2338                    "pub_type_alias_of_composite_type::MixedTuple",
2339                ],
2340                "GenericTuple" => btreeset![
2341                    "pub_type_alias_of_composite_type::GenericTuple",
2342                ],
2343                "LifetimeTuple" => btreeset![
2344                    "pub_type_alias_of_composite_type::LifetimeTuple",
2345                ],
2346                "ConstTuple" => btreeset![
2347                    "pub_type_alias_of_composite_type::ConstTuple",
2348                ],
2349                "DefaultGenericTuple" => btreeset![
2350                    "pub_type_alias_of_composite_type::DefaultGenericTuple",
2351                ],
2352                "DefaultConstTuple" => btreeset![
2353                    "pub_type_alias_of_composite_type::DefaultConstTuple",
2354                ],
2355            };
2356
2357            assert_exported_items_match(test_crate, &expected_items);
2358        }
2359
2360        #[test]
2361        fn pub_generic_type_alias_omitted_default() {
2362            let test_crate = "pub_generic_type_alias_omitted_default";
2363            let expected_items = btreemap! {
2364                "DefaultConst" => btreeset![
2365                    "pub_generic_type_alias_omitted_default::inner::DefaultConst",
2366                ],
2367                "DefaultType" => btreeset![
2368                    "pub_generic_type_alias_omitted_default::inner::DefaultType",
2369                ],
2370                "ConstOnly" => btreeset![
2371                    "pub_generic_type_alias_omitted_default::inner::ConstOnly",
2372                ],
2373                "TypeOnly" => btreeset![
2374                    "pub_generic_type_alias_omitted_default::inner::TypeOnly",
2375                ],
2376                "OmittedConst" => btreeset![
2377                    "pub_generic_type_alias_omitted_default::OmittedConst",
2378                ],
2379                "OmittedType" => btreeset![
2380                    "pub_generic_type_alias_omitted_default::OmittedType",
2381                ],
2382                "NonGenericConst" => btreeset![
2383                    "pub_generic_type_alias_omitted_default::NonGenericConst",
2384                ],
2385                "NonGenericType" => btreeset![
2386                    "pub_generic_type_alias_omitted_default::NonGenericType",
2387                ],
2388            };
2389
2390            assert_exported_items_match(test_crate, &expected_items);
2391        }
2392
2393        #[test]
2394        fn swapping_names() {
2395            let test_crate = "swapping_names";
2396            let expected_items = btreemap! {
2397                "Foo" => btreeset![
2398                    "swapping_names::Foo",
2399                    "swapping_names::inner::Bar",
2400                    "swapping_names::inner::nested::Foo",
2401                ],
2402                "Bar" => btreeset![
2403                    "swapping_names::Bar",
2404                    "swapping_names::inner::Foo",
2405                    "swapping_names::inner::nested::Bar",
2406                ],
2407            };
2408
2409            assert_exported_items_match(test_crate, &expected_items);
2410        }
2411
2412        #[test]
2413        fn overlapping_glob_and_local_module() {
2414            let test_crate = "overlapping_glob_and_local_module";
2415            let expected_items = btreemap! {
2416                "Foo" => btreeset![
2417                    "overlapping_glob_and_local_module::sibling::duplicated::Foo",
2418                ],
2419                "Bar" => btreeset![
2420                    "overlapping_glob_and_local_module::inner::duplicated::Bar",
2421                ],
2422            };
2423
2424            assert_exported_items_match(test_crate, &expected_items);
2425        }
2426
2427        #[test]
2428        fn overlapping_glob_and_renamed_module() {
2429            let test_crate = "overlapping_glob_and_renamed_module";
2430            let expected_items = btreemap! {
2431                "Foo" => btreeset![
2432                    "overlapping_glob_and_renamed_module::sibling::duplicated::Foo",
2433                ],
2434                "Bar" => btreeset![
2435                    "overlapping_glob_and_renamed_module::inner::duplicated::Bar",
2436                ],
2437            };
2438
2439            assert_exported_items_match(test_crate, &expected_items);
2440        }
2441
2442        #[test]
2443        fn type_and_value_with_matching_names() {
2444            let test_crate = "type_and_value_with_matching_names";
2445            let expected_items = btreemap! {
2446                "Foo" => (2, btreeset![
2447                    "type_and_value_with_matching_names::Foo",
2448                    "type_and_value_with_matching_names::nested::Foo",
2449                ]),
2450                "Bar" => (2, btreeset![
2451                    "type_and_value_with_matching_names::Bar",
2452                    "type_and_value_with_matching_names::nested::Bar",
2453                ]),
2454            };
2455
2456            assert_duplicated_exported_items_match(test_crate, &expected_items);
2457        }
2458
2459        #[test]
2460        fn no_shadowing_across_namespaces() {
2461            let test_crate = "no_shadowing_across_namespaces";
2462            let expected_items = btreemap! {
2463                "Foo" => (2, btreeset![
2464                    "no_shadowing_across_namespaces::Foo",
2465                    "no_shadowing_across_namespaces::nested::Foo",
2466                ]),
2467            };
2468
2469            assert_duplicated_exported_items_match(test_crate, &expected_items);
2470        }
2471
2472        #[test]
2473        fn explicit_reexport_of_matching_names() {
2474            let test_crate = "explicit_reexport_of_matching_names";
2475            let expected_items = btreemap! {
2476                "Foo" => (2, btreeset![
2477                    "explicit_reexport_of_matching_names::Bar",
2478                    "explicit_reexport_of_matching_names::Foo",
2479                    "explicit_reexport_of_matching_names::nested::Foo",
2480                ]),
2481            };
2482
2483            assert_duplicated_exported_items_match(test_crate, &expected_items);
2484        }
2485
2486        #[test]
2487        fn overlapping_glob_and_local_item() {
2488            let test_crate = "overlapping_glob_and_local_item";
2489
2490            let rustdoc = load_pregenerated_rustdoc(test_crate);
2491            let indexed_crate = IndexedCrate::new(&rustdoc);
2492
2493            let foo_ids = rustdoc
2494                .index
2495                .iter()
2496                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2497                .collect_vec();
2498            if foo_ids.len() != 2 {
2499                panic!(
2500                    "Expected to find exactly 2 items with name \
2501                    Foo, but found these matching IDs: {foo_ids:?}"
2502                );
2503            }
2504
2505            let item_id_candidates = rustdoc
2506                .index
2507                .iter()
2508                .filter_map(|(id, item)| {
2509                    (matches!(item.name.as_deref(), Some("Foo" | "Bar"))).then_some(id)
2510                })
2511                .collect_vec();
2512            if item_id_candidates.len() != 3 {
2513                panic!(
2514                    "Expected to find exactly 3 items named Foo or Bar, \
2515                    but found these matching IDs: {item_id_candidates:?}"
2516                );
2517            }
2518
2519            let mut all_importable_paths = Vec::new();
2520            for item_id in item_id_candidates {
2521                let actual_items: Vec<_> = indexed_crate
2522                    .publicly_importable_names(item_id)
2523                    .into_iter()
2524                    .map(|importable| importable.path.components.into_iter().join("::"))
2525                    .collect();
2526                let deduplicated_actual_items: BTreeSet<_> =
2527                    actual_items.iter().map(|x| x.as_str()).collect();
2528                assert_eq!(
2529                    actual_items.len(),
2530                    deduplicated_actual_items.len(),
2531                    "duplicates found: {actual_items:?}"
2532                );
2533
2534                if deduplicated_actual_items
2535                    .first()
2536                    .expect("no names")
2537                    .ends_with("::Foo")
2538                {
2539                    assert_eq!(
2540                        deduplicated_actual_items.len(),
2541                        1,
2542                        "\
2543expected exactly one importable path for `Foo` items in this crate but got: {actual_items:?}"
2544                    );
2545                } else {
2546                    assert_eq!(
2547                        deduplicated_actual_items,
2548                        btreeset! {
2549                            "overlapping_glob_and_local_item::Bar",
2550                            "overlapping_glob_and_local_item::inner::Bar",
2551                        }
2552                    );
2553                }
2554
2555                all_importable_paths.extend(actual_items);
2556            }
2557
2558            all_importable_paths.sort_unstable();
2559            assert_eq!(
2560                vec![
2561                    "overlapping_glob_and_local_item::Bar",
2562                    "overlapping_glob_and_local_item::Foo",
2563                    "overlapping_glob_and_local_item::inner::Bar",
2564                    "overlapping_glob_and_local_item::inner::Foo",
2565                ],
2566                all_importable_paths,
2567            );
2568        }
2569
2570        #[test]
2571        fn nested_overlapping_glob_and_local_item() {
2572            let test_crate = "nested_overlapping_glob_and_local_item";
2573
2574            let rustdoc = load_pregenerated_rustdoc(test_crate);
2575            let indexed_crate = IndexedCrate::new(&rustdoc);
2576
2577            let item_id_candidates = rustdoc
2578                .index
2579                .iter()
2580                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2581                .collect_vec();
2582            if item_id_candidates.len() != 2 {
2583                panic!(
2584                    "Expected to find exactly 2 items with name \
2585                    Foo, but found these matching IDs: {item_id_candidates:?}"
2586                );
2587            }
2588
2589            let mut all_importable_paths = Vec::new();
2590            for item_id in item_id_candidates {
2591                let actual_items: Vec<_> = indexed_crate
2592                    .publicly_importable_names(item_id)
2593                    .into_iter()
2594                    .map(|importable| importable.path.components.into_iter().join("::"))
2595                    .collect();
2596                let deduplicated_actual_items: BTreeSet<_> =
2597                    actual_items.iter().map(|x| x.as_str()).collect();
2598
2599                assert_eq!(
2600                    actual_items.len(),
2601                    deduplicated_actual_items.len(),
2602                    "duplicates found: {actual_items:?}"
2603                );
2604
2605                match deduplicated_actual_items.len() {
2606                    1 => assert_eq!(
2607                        deduplicated_actual_items,
2608                        btreeset! { "nested_overlapping_glob_and_local_item::Foo" },
2609                    ),
2610                    2 => assert_eq!(
2611                        deduplicated_actual_items,
2612                        btreeset! {
2613                            "nested_overlapping_glob_and_local_item::inner::Foo",
2614                            "nested_overlapping_glob_and_local_item::inner::nested::Foo",
2615                        }
2616                    ),
2617                    _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
2618                };
2619
2620                all_importable_paths.extend(actual_items);
2621            }
2622
2623            all_importable_paths.sort_unstable();
2624            assert_eq!(
2625                vec![
2626                    "nested_overlapping_glob_and_local_item::Foo",
2627                    "nested_overlapping_glob_and_local_item::inner::Foo",
2628                    "nested_overlapping_glob_and_local_item::inner::nested::Foo",
2629                ],
2630                all_importable_paths,
2631            );
2632        }
2633
2634        #[test]
2635        fn cyclic_overlapping_glob_and_local_item() {
2636            let test_crate = "cyclic_overlapping_glob_and_local_item";
2637
2638            let rustdoc = load_pregenerated_rustdoc(test_crate);
2639            let indexed_crate = IndexedCrate::new(&rustdoc);
2640
2641            let item_id_candidates = rustdoc
2642                .index
2643                .iter()
2644                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2645                .collect_vec();
2646            if item_id_candidates.len() != 2 {
2647                panic!(
2648                    "Expected to find exactly 2 items with name \
2649                    Foo, but found these matching IDs: {item_id_candidates:?}"
2650                );
2651            }
2652
2653            let mut all_importable_paths = Vec::new();
2654            for item_id in item_id_candidates {
2655                let actual_items: Vec<_> = indexed_crate
2656                    .publicly_importable_names(item_id)
2657                    .into_iter()
2658                    .map(|importable| importable.path.components.into_iter().join("::"))
2659                    .collect();
2660                let deduplicated_actual_items: BTreeSet<_> =
2661                    actual_items.iter().map(|x| x.as_str()).collect();
2662
2663                assert_eq!(
2664                    actual_items.len(),
2665                    deduplicated_actual_items.len(),
2666                    "duplicates found: {actual_items:?}"
2667                );
2668
2669                match deduplicated_actual_items.len() {
2670                    1 => assert_eq!(
2671                        btreeset! { "cyclic_overlapping_glob_and_local_item::Foo" },
2672                        deduplicated_actual_items,
2673                    ),
2674                    4 => assert_eq!(
2675                        btreeset! {
2676                            "cyclic_overlapping_glob_and_local_item::inner::Foo",
2677                            "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
2678                            "cyclic_overlapping_glob_and_local_item::nested::Foo",
2679                            "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2680                        },
2681                        deduplicated_actual_items,
2682                    ),
2683                    _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
2684                };
2685
2686                all_importable_paths.extend(actual_items);
2687            }
2688
2689            all_importable_paths.sort_unstable();
2690            assert_eq!(
2691                vec![
2692                    "cyclic_overlapping_glob_and_local_item::Foo",
2693                    "cyclic_overlapping_glob_and_local_item::inner::Foo",
2694                    "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
2695                    "cyclic_overlapping_glob_and_local_item::nested::Foo",
2696                    "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2697                ],
2698                all_importable_paths,
2699            );
2700        }
2701
2702        #[test]
2703        fn overlapping_glob_of_enum_with_local_item() {
2704            let test_crate = "overlapping_glob_of_enum_with_local_item";
2705            let easy_expected_items = btreemap! {
2706                "Foo" => btreeset![
2707                    "overlapping_glob_of_enum_with_local_item::Foo",
2708                ],
2709                "Second" => btreeset![
2710                    "overlapping_glob_of_enum_with_local_item::Foo::Second",
2711                    "overlapping_glob_of_enum_with_local_item::inner::Second",
2712                ],
2713            };
2714
2715            // Check the "easy" cases: `Foo` and `Second`.
2716            // This is necessary but not sufficient to confirm our implementation works,
2717            // since it doesn't check anything about `First` which is the point of this test case.
2718            assert_exported_items_match(test_crate, &easy_expected_items);
2719
2720            let rustdoc = load_pregenerated_rustdoc(test_crate);
2721            let indexed_crate = IndexedCrate::new(&rustdoc);
2722
2723            let items_named_first: Vec<_> = indexed_crate
2724                .inner
2725                .index
2726                .values()
2727                .filter(|item| item.name.as_deref() == Some("First"))
2728                .collect();
2729            assert_eq!(2, items_named_first.len(), "{items_named_first:?}");
2730            let variant_item = items_named_first
2731                .iter()
2732                .copied()
2733                .find(|item| matches!(item.inner, ItemEnum::Variant(..)))
2734                .expect("no variant item found");
2735            let struct_item = items_named_first
2736                .iter()
2737                .copied()
2738                .find(|item| matches!(item.inner, ItemEnum::Struct(..)))
2739                .expect("no struct item found");
2740
2741            assert_eq!(
2742                vec![ImportablePath::new(
2743                    vec!["overlapping_glob_of_enum_with_local_item", "Foo", "First"],
2744                    false,
2745                    false,
2746                    false,
2747                )],
2748                indexed_crate.publicly_importable_names(&variant_item.id),
2749            );
2750            assert_eq!(
2751                // The struct definition overrides the glob-imported variant here.
2752                vec![ImportablePath::new(
2753                    vec!["overlapping_glob_of_enum_with_local_item", "inner", "First"],
2754                    false,
2755                    false,
2756                    false,
2757                )],
2758                indexed_crate.publicly_importable_names(&struct_item.id),
2759            );
2760        }
2761
2762        #[test]
2763        fn glob_of_enum_does_not_shadow_local_fn() {
2764            let test_crate = "glob_of_enum_does_not_shadow_local_fn";
2765
2766            let rustdoc = load_pregenerated_rustdoc(test_crate);
2767            let indexed_crate = IndexedCrate::new(&rustdoc);
2768
2769            let first_ids = rustdoc
2770                .index
2771                .iter()
2772                .filter_map(|(id, item)| (item.name.as_deref() == Some("First")).then_some(id))
2773                .collect_vec();
2774            if first_ids.len() != 2 {
2775                panic!(
2776                    "Expected to find exactly 2 items with name \
2777                    First, but found these matching IDs: {first_ids:?}"
2778                );
2779            }
2780
2781            for item_id in first_ids {
2782                let actual_items: Vec<_> = indexed_crate
2783                    .publicly_importable_names(item_id)
2784                    .into_iter()
2785                    .map(|importable| importable.path.components.into_iter().join("::"))
2786                    .collect();
2787                let deduplicated_actual_items: BTreeSet<_> =
2788                    actual_items.iter().map(|x| x.as_str()).collect();
2789                assert_eq!(
2790                    actual_items.len(),
2791                    deduplicated_actual_items.len(),
2792                    "duplicates found: {actual_items:?}"
2793                );
2794
2795                let expected_items = match &rustdoc.index[item_id].inner {
2796                    ItemEnum::Variant(..) => {
2797                        vec!["glob_of_enum_does_not_shadow_local_fn::Foo::First"]
2798                    }
2799                    ItemEnum::Function(..) => {
2800                        vec!["glob_of_enum_does_not_shadow_local_fn::inner::First"]
2801                    }
2802                    other => {
2803                        unreachable!("item {item_id:?} had unexpected inner content: {other:?}")
2804                    }
2805                };
2806
2807                assert_eq!(expected_items, actual_items);
2808            }
2809        }
2810
2811        /// There's currently no way to detect private imports that shadow glob items.
2812        /// Reported as: <https://github.com/rust-lang/rust/issues/111338>
2813        #[test]
2814        #[should_panic = "expected no importable item names but found \
2815                         [\"overlapping_glob_and_private_import::inner::Foo\"]"]
2816        fn overlapping_glob_and_private_import() {
2817            let test_crate = "overlapping_glob_and_private_import";
2818
2819            let rustdoc = load_pregenerated_rustdoc(test_crate);
2820            let indexed_crate = IndexedCrate::new(&rustdoc);
2821
2822            let item_id_candidates = rustdoc
2823                .index
2824                .iter()
2825                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2826                .collect_vec();
2827            if item_id_candidates.len() != 2 {
2828                panic!(
2829                    "Expected to find exactly 2 items with name \
2830                    Foo, but found these matching IDs: {item_id_candidates:?}"
2831                );
2832            }
2833
2834            for item_id in item_id_candidates {
2835                let actual_items: Vec<_> = indexed_crate
2836                    .publicly_importable_names(item_id)
2837                    .into_iter()
2838                    .map(|importable| importable.path.components.into_iter().join("::"))
2839                    .collect();
2840
2841                assert!(
2842                    actual_items.is_empty(),
2843                    "expected no importable item names but found {actual_items:?}"
2844                );
2845            }
2846        }
2847
2848        /// Our logic for determining whether a tuple struct's implicit constructor is exported
2849        /// is too simplistic: it assumes "yes" if all fields are pub, and "no" otherwise.
2850        /// This is why this test currently fails.
2851        /// TODO: fix this once rustdoc includes shadowing information
2852        ///       <https://github.com/rust-lang/rust/issues/111338>
2853        ///
2854        /// Its sibling test `visibility_modifier_avoids_shadowing` ensures that shadowing is
2855        /// not inappropriately applied when the tuple constructors do *not* shadow each other.
2856        #[test]
2857        #[should_panic = "expected no importable item names but found \
2858                         [\"visibility_modifier_causes_shadowing::Foo\"]"]
2859        fn visibility_modifier_causes_shadowing() {
2860            let test_crate = "visibility_modifier_causes_shadowing";
2861
2862            let rustdoc = load_pregenerated_rustdoc(test_crate);
2863            let indexed_crate = IndexedCrate::new(&rustdoc);
2864
2865            let item_id_candidates = rustdoc
2866                .index
2867                .iter()
2868                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2869                .collect_vec();
2870            if item_id_candidates.len() != 3 {
2871                panic!(
2872                    "Expected to find exactly 3 items with name \
2873                    Foo, but found these matching IDs: {item_id_candidates:?}"
2874                );
2875            }
2876
2877            for item_id in item_id_candidates {
2878                let actual_items: Vec<_> = indexed_crate
2879                    .publicly_importable_names(item_id)
2880                    .into_iter()
2881                    .map(|importable| importable.path.components.into_iter().join("::"))
2882                    .collect();
2883
2884                assert!(
2885                    actual_items.is_empty(),
2886                    "expected no importable item names but found {actual_items:?}"
2887                );
2888            }
2889        }
2890
2891        #[test]
2892        fn visibility_modifier_avoids_shadowing() {
2893            let test_crate = "visibility_modifier_avoids_shadowing";
2894
2895            let rustdoc = load_pregenerated_rustdoc(test_crate);
2896            let indexed_crate = IndexedCrate::new(&rustdoc);
2897
2898            let item_id_candidates = rustdoc
2899                .index
2900                .iter()
2901                .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2902                .collect_vec();
2903            if item_id_candidates.len() != 3 {
2904                panic!(
2905                    "Expected to find exactly 3 items with name \
2906                    Foo, but found these matching IDs: {item_id_candidates:?}"
2907                );
2908            }
2909
2910            for item_id in item_id_candidates {
2911                let actual_items: Vec<_> = indexed_crate
2912                    .publicly_importable_names(item_id)
2913                    .into_iter()
2914                    .map(|importable| importable.path.components.into_iter().join("::"))
2915                    .collect();
2916
2917                if rustdoc.index[item_id].visibility == Visibility::Public {
2918                    assert_eq!(
2919                        vec!["visibility_modifier_avoids_shadowing::Foo"],
2920                        actual_items,
2921                    );
2922                } else {
2923                    assert!(
2924                        actual_items.is_empty(),
2925                        "expected no importable item names but found {actual_items:?}"
2926                    );
2927                }
2928            }
2929        }
2930
2931        #[test]
2932        fn glob_vs_glob_shadowing() {
2933            let test_crate = "glob_vs_glob_shadowing";
2934
2935            let expected_items = btreemap! {
2936                "Foo" => (2, btreeset![]),
2937                "Bar" => (1, btreeset![
2938                    "glob_vs_glob_shadowing::Bar",
2939                ]),
2940                "Baz" => (1, btreeset![
2941                    "glob_vs_glob_shadowing::Baz",
2942                ]),
2943            };
2944
2945            assert_duplicated_exported_items_match(test_crate, &expected_items);
2946        }
2947
2948        #[test]
2949        fn glob_vs_glob_shadowing_downstream() {
2950            let test_crate = "glob_vs_glob_shadowing_downstream";
2951
2952            let expected_items = btreemap! {
2953                "Foo" => (3, btreeset![]),
2954                "Bar" => (1, btreeset![
2955                    "glob_vs_glob_shadowing_downstream::second::Bar",
2956                ]),
2957            };
2958
2959            assert_duplicated_exported_items_match(test_crate, &expected_items);
2960        }
2961
2962        #[test]
2963        fn glob_vs_glob_no_shadowing_for_same_item() {
2964            let test_crate = "glob_vs_glob_no_shadowing_for_same_item";
2965
2966            let expected_items = btreemap! {
2967                "Foo" => btreeset![
2968                    "glob_vs_glob_no_shadowing_for_same_item::Foo",
2969                ],
2970            };
2971
2972            assert_exported_items_match(test_crate, &expected_items);
2973        }
2974
2975        #[test]
2976        fn glob_vs_glob_no_shadowing_for_same_renamed_item() {
2977            let test_crate = "glob_vs_glob_no_shadowing_for_same_renamed_item";
2978
2979            let expected_items = btreemap! {
2980                "Bar" => btreeset![
2981                    "glob_vs_glob_no_shadowing_for_same_renamed_item::Foo",
2982                ],
2983            };
2984
2985            assert_exported_items_match(test_crate, &expected_items);
2986        }
2987
2988        #[test]
2989        fn glob_vs_glob_no_shadowing_for_same_multiply_renamed_item() {
2990            let test_crate = "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item";
2991
2992            let expected_items = btreemap! {
2993                "Bar" => btreeset![
2994                    "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item::Foo",
2995                ],
2996            };
2997
2998            assert_exported_items_match(test_crate, &expected_items);
2999        }
3000
3001        #[test]
3002        fn reexport_consts_and_statics() {
3003            let test_crate = "reexport_consts_and_statics";
3004            let expected_items = btreemap! {
3005                "FIRST" => btreeset![
3006                    "reexport_consts_and_statics::FIRST",
3007                    "reexport_consts_and_statics::inner::FIRST",
3008                ],
3009                "SECOND" => btreeset![
3010                    "reexport_consts_and_statics::SECOND",
3011                    "reexport_consts_and_statics::inner::SECOND",
3012                ],
3013            };
3014
3015            assert_exported_items_match(test_crate, &expected_items);
3016        }
3017
3018        #[test]
3019        fn reexport_as_underscore() {
3020            let test_crate = "reexport_as_underscore";
3021            let expected_items = btreemap! {
3022                "Struct" => btreeset![
3023                    "reexport_as_underscore::Struct",
3024                ],
3025                "Trait" => btreeset![],
3026                "hidden" => btreeset![],
3027                "UnderscoreImported" => btreeset![],
3028            };
3029
3030            assert_exported_items_match(test_crate, &expected_items);
3031        }
3032
3033        #[test]
3034        fn nested_reexport_as_underscore() {
3035            let test_crate = "nested_reexport_as_underscore";
3036            let expected_items = btreemap! {
3037                "Trait" => btreeset![],  // no importable paths!
3038            };
3039
3040            assert_exported_items_match(test_crate, &expected_items);
3041        }
3042
3043        #[test]
3044        fn overlapping_reexport_as_underscore() {
3045            let test_crate = "overlapping_reexport_as_underscore";
3046
3047            let rustdoc = load_pregenerated_rustdoc(test_crate);
3048            let indexed_crate = IndexedCrate::new(&rustdoc);
3049
3050            let item_id_candidates = rustdoc
3051                .index
3052                .iter()
3053                .filter_map(|(id, item)| (item.name.as_deref() == Some("Example")).then_some(id))
3054                .collect_vec();
3055            if item_id_candidates.len() != 2 {
3056                panic!(
3057                    "Expected to find exactly 2 items with name \
3058                    Example, but found these matching IDs: {item_id_candidates:?}"
3059                );
3060            }
3061
3062            for item_id in item_id_candidates {
3063                let importable_paths: Vec<_> = indexed_crate
3064                    .publicly_importable_names(item_id)
3065                    .into_iter()
3066                    .map(|importable| importable.path.components.into_iter().join("::"))
3067                    .collect();
3068
3069                match &rustdoc.index[item_id].inner {
3070                    ItemEnum::Struct(..) => {
3071                        assert_eq!(
3072                            vec!["overlapping_reexport_as_underscore::Example"],
3073                            importable_paths,
3074                        );
3075                    }
3076                    ItemEnum::Trait(..) => {
3077                        assert!(
3078                            importable_paths.is_empty(),
3079                            "expected no importable item names but found {importable_paths:?}"
3080                        );
3081                    }
3082                    _ => unreachable!(
3083                        "unexpected item for ID {item_id:?}: {:?}",
3084                        rustdoc.index[item_id]
3085                    ),
3086                }
3087            }
3088        }
3089
3090        #[test]
3091        fn reexport_declarative_macro() {
3092            let test_crate = "reexport_declarative_macro";
3093            let expected_items = btreemap! {
3094                "top_level_exported" => btreeset![
3095                    "reexport_declarative_macro::top_level_exported",
3096                ],
3097                "private_mod_exported" => btreeset![
3098                    "reexport_declarative_macro::private_mod_exported",
3099                ],
3100                "top_level_reexported" => btreeset![
3101                    "reexport_declarative_macro::top_level_reexported",
3102                    "reexport_declarative_macro::macros::top_level_reexported",
3103                    "reexport_declarative_macro::reexports::top_level_reexported",
3104                    "reexport_declarative_macro::glob_reexports::top_level_reexported",
3105                ],
3106                "private_mod_reexported" => btreeset![
3107                    "reexport_declarative_macro::private_mod_reexported",
3108                    "reexport_declarative_macro::macros::private_mod_reexported",
3109                    "reexport_declarative_macro::reexports::private_mod_reexported",
3110                    "reexport_declarative_macro::glob_reexports::private_mod_reexported",
3111                ],
3112                "top_level_not_exported" => btreeset![],
3113                "private_mod_not_exported" => btreeset![],
3114            };
3115
3116            assert_exported_items_match(test_crate, &expected_items);
3117        }
3118    }
3119
3120    mod index_tests {
3121        use itertools::Itertools;
3122
3123        use crate::{IndexedCrate, indexed_crate::ImplEntry, test_util::load_pregenerated_rustdoc};
3124
3125        #[test]
3126        fn defaulted_trait_items_overridden_in_impls_have_single_item_in_index() {
3127            let test_crate = "defaulted_trait_items_overridden_in_impls";
3128
3129            let rustdoc = load_pregenerated_rustdoc(test_crate);
3130            let indexed_crate = IndexedCrate::new(&rustdoc);
3131
3132            let impl_owner = indexed_crate
3133                .inner
3134                .index
3135                .values()
3136                .filter(|item| item.name.as_deref() == Some("Example"))
3137                .exactly_one()
3138                .expect("failed to find exactly one Example item");
3139            let trait_item = indexed_crate
3140                .inner
3141                .index
3142                .values()
3143                .filter(|item| item.name.as_deref() == Some("Trait"))
3144                .exactly_one()
3145                .expect("failed to find exactly one Trait item");
3146            let trait_provided_items: Vec<_> = match &trait_item.inner {
3147                rustdoc_types::ItemEnum::Trait(t) => t
3148                    .items
3149                    .iter()
3150                    .map(|id| &indexed_crate.inner.index[id])
3151                    .collect(),
3152                _ => unreachable!(),
3153            };
3154            let trait_provided_method = trait_provided_items
3155                .iter()
3156                .copied()
3157                .filter(|item| matches!(item.inner, rustdoc_types::ItemEnum::Function { .. }))
3158                .exactly_one()
3159                .expect("more than one provided method");
3160
3161            let impl_index = indexed_crate
3162                .impl_method_index
3163                .as_ref()
3164                .expect("no impl index was built");
3165            let method_entries = impl_index
3166                .get(&ImplEntry::new(&impl_owner.id, "method"))
3167                .expect("no method entries found");
3168
3169            // Associated const items aren't part of the `impl_index` at the moment, it's just methods.
3170            let const_entries = impl_index.get(&ImplEntry::new(&impl_owner.id, "N"));
3171            assert_eq!(const_entries, None, "{const_entries:#?}");
3172
3173            // There's exactly one provided method and it isn't the trait's default one.
3174            assert_eq!(method_entries.len(), 1, "{method_entries:#?}");
3175            assert_ne!(method_entries[0].1, trait_provided_method);
3176        }
3177
3178        #[test]
3179        fn provided_trait_method_index_ignores_same_named_associated_types() {
3180            let test_crate = "defaulted_trait_items_overridden_in_impls";
3181
3182            let rustdoc = load_pregenerated_rustdoc(test_crate);
3183            let indexed_crate = IndexedCrate::new(&rustdoc);
3184
3185            let impl_owner = indexed_crate
3186                .inner
3187                .index
3188                .values()
3189                .filter(|item| item.name.as_deref() == Some("SameNameExample"))
3190                .exactly_one()
3191                .expect("failed to find exactly one SameNameExample item");
3192
3193            let impl_index = indexed_crate
3194                .impl_method_index
3195                .as_ref()
3196                .expect("no impl index was built");
3197            let method_entries = impl_index
3198                .get(&ImplEntry::new(&impl_owner.id, "method"))
3199                .expect("no method entries found");
3200
3201            assert_eq!(method_entries.len(), 1, "{method_entries:#?}");
3202            assert!(
3203                matches!(
3204                    method_entries[0].1.inner,
3205                    rustdoc_types::ItemEnum::Function(..)
3206                ),
3207                "impl method index included a non-function item: {method_entries:#?}",
3208            );
3209        }
3210    }
3211}