trustfall_rustdoc_adapter/
indexed_crate.rs

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