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