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