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