1use std::{borrow::Borrow, collections::hash_map::Entry, sync::Arc};
2
3#[cfg(not(feature = "rustc-hash"))]
4use std::collections::{HashMap, HashSet};
5
6#[cfg(feature = "rustc-hash")]
7use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
8
9#[cfg(feature = "rayon")]
10use rayon::prelude::*;
11use rustdoc_types::{Crate, Id, Item};
12
13use crate::{
14 adapter::supported_item_kind,
15 item_flags::{build_flags_index, ItemFlag},
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_index: Option<HashMap<ImplEntry<'a>, Vec<(&'a Item, &'a Item)>>>,
201
202 pub(crate) fn_owner_index: Option<HashMap<Id, &'a Item>>,
205
206 pub(crate) export_name_index: Option<HashMap<&'a str, &'a Item>>,
209
210 pub(crate) manually_inlined_builtin_traits: HashMap<Id, Item>,
224}
225
226struct MapList<K, V>(HashMap<K, Vec<V>>);
231
232#[cfg(feature = "rayon")]
233impl<K: std::cmp::Eq + std::hash::Hash + Send, V: Send> FromParallelIterator<(K, V)>
234 for MapList<K, V>
235{
236 #[inline]
237 fn from_par_iter<I>(par_iter: I) -> Self
238 where
239 I: IntoParallelIterator<Item = (K, V)>,
240 {
241 par_iter
242 .into_par_iter()
243 .fold(Self::new, |mut map, (key, value)| {
244 map.insert(key, value);
245 map
246 })
247 .reduce(Self::new, |mut l, r| {
249 l.merge(r);
250 l
251 })
252 }
253}
254
255impl<K: std::cmp::Eq + std::hash::Hash, V> FromIterator<(K, V)> for MapList<K, V> {
256 #[inline]
257 fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
258 let mut map = Self::new();
261 for (key, value) in iter {
262 map.insert(key, value);
263 }
264 map
265 }
266}
267
268impl<K: std::cmp::Eq + std::hash::Hash, V> Extend<(K, V)> for MapList<K, V> {
269 #[inline]
270 fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
271 for (key, value) in iter.into_iter() {
274 self.insert(key, value);
275 }
276 }
277}
278
279impl<K: std::cmp::Eq + std::hash::Hash, V> MapList<K, V> {
280 #[inline]
281 pub fn new() -> Self {
282 Self(HashMap::default())
283 }
284
285 #[inline]
286 pub fn into_inner(self) -> HashMap<K, Vec<V>> {
287 self.0
288 }
289
290 #[inline]
291 pub fn insert(&mut self, key: K, value: V) {
292 match self.0.entry(key) {
293 Entry::Occupied(mut entry) => entry.get_mut().push(value),
294 Entry::Vacant(entry) => {
295 entry.insert(vec![value]);
296 }
297 }
298 }
299
300 #[inline]
301 #[cfg(feature = "rayon")]
302 pub fn insert_many(&mut self, key: K, mut value: Vec<V>) {
303 match self.0.entry(key) {
304 Entry::Occupied(mut entry) => entry.get_mut().append(&mut value),
305 Entry::Vacant(entry) => {
306 entry.insert(value);
307 }
308 }
309 }
310
311 #[inline]
312 #[cfg(feature = "rayon")]
313 pub fn merge(&mut self, other: Self) {
314 self.0.reserve(other.0.len());
315 for (key, value) in other.0 {
316 self.insert_many(key, value);
317 }
318 }
319}
320
321fn build_impl_index(index: &HashMap<Id, Item>) -> MapList<ImplEntry<'_>, (&Item, &Item)> {
326 #[cfg(feature = "rayon")]
327 let iter = index.par_iter();
328 #[cfg(not(feature = "rayon"))]
329 let iter = index.iter();
330 iter.filter_map(|(id, item)| {
331 let impls = match &item.inner {
332 rustdoc_types::ItemEnum::Struct(s) => s.impls.as_slice(),
333 rustdoc_types::ItemEnum::Enum(e) => e.impls.as_slice(),
334 rustdoc_types::ItemEnum::Union(u) => u.impls.as_slice(),
335 _ => return None,
336 };
337
338 #[cfg(feature = "rayon")]
339 let iter = impls.par_iter();
340 #[cfg(not(feature = "rayon"))]
341 let iter = impls.iter();
342
343 Some((id, iter.filter_map(|impl_id| index.get(impl_id))))
344 })
345 .flat_map(|(id, impl_items)| {
346 impl_items.flat_map(move |impl_item| {
347 let impl_inner = match &impl_item.inner {
348 rustdoc_types::ItemEnum::Impl(impl_inner) => impl_inner,
349 _ => unreachable!("expected impl but got another item type: {impl_item:?}"),
350 };
351 let trait_provided_methods: HashSet<_> = impl_inner
352 .provided_trait_methods
353 .iter()
354 .map(|x| x.as_str())
355 .collect();
356
357 let trait_items = impl_inner
358 .trait_
359 .as_ref()
360 .and_then(|trait_path| index.get(&trait_path.id))
361 .map(move |trait_item| {
362 if let rustdoc_types::ItemEnum::Trait(trait_item) = &trait_item.inner {
363 trait_item.items.as_slice()
364 } else {
365 &[]
366 }
367 })
368 .unwrap_or(&[]);
369
370 #[cfg(feature = "rayon")]
371 let trait_items = trait_items.par_iter();
372 #[cfg(not(feature = "rayon"))]
373 let trait_items = trait_items.iter();
374
375 let trait_provided_items = trait_items
376 .filter_map(|id| index.get(id))
377 .filter(move |item| {
378 item.name
379 .as_deref()
380 .map(|name| trait_provided_methods.contains(name))
381 .unwrap_or_default()
382 })
383 .map(move |provided_item| {
384 (
385 ImplEntry::new(
386 id,
387 provided_item
388 .name
389 .as_deref()
390 .expect("item should have had a name"),
391 ),
392 (impl_item, provided_item),
393 )
394 });
395
396 #[cfg(feature = "rayon")]
397 let impl_items = impl_inner.items.par_iter();
398 #[cfg(not(feature = "rayon"))]
399 let impl_items = impl_inner.items.iter();
400
401 impl_items
402 .filter_map(move |item_id| {
403 let item = index.get(item_id)?;
404 let item_name = item.name.as_deref()?;
405 Some((ImplEntry::new(id, item_name), (impl_item, item)))
406 })
407 .chain(trait_provided_items)
408 })
409 })
410 .collect()
411}
412
413impl<'a> IndexedCrate<'a> {
414 pub fn new(crate_: &'a Crate) -> Self {
415 let mut value = Self {
416 inner: crate_,
417 visibility_tracker: VisibilityTracker::from_crate(crate_),
418 manually_inlined_builtin_traits: create_manually_inlined_builtin_traits(crate_),
419 flags: None,
420 imports_index: None,
421 impl_index: None,
422 fn_owner_index: None,
423 export_name_index: None,
424 };
425
426 debug_assert!(
427 !value.manually_inlined_builtin_traits.is_empty(),
428 "failed to find any traits to manually inline",
429 );
430
431 #[cfg(feature = "rayon")]
436 let iter = crate_.index.par_iter();
437 #[cfg(not(feature = "rayon"))]
438 let iter = crate_.index.iter();
439
440 let imports_index = iter
441 .filter_map(|(_id, item)| {
442 if !supported_item_kind(item) {
443 return None;
444 }
445 let importable_paths = value.publicly_importable_names(&item.id);
446
447 #[cfg(feature = "rayon")]
448 let iter = importable_paths.into_par_iter();
449 #[cfg(not(feature = "rayon"))]
450 let iter = importable_paths.into_iter();
451
452 Some(iter.map(move |importable_path| {
453 (importable_path.path, (item, importable_path.modifiers))
454 }))
455 })
456 .flatten()
457 .collect::<MapList<_, _>>()
458 .into_inner();
459 value.flags = Some(build_flags_index(&crate_.index, &imports_index));
460 value.imports_index = Some(imports_index);
461
462 value.impl_index = Some(build_impl_index(&crate_.index).into_inner());
463 value.fn_owner_index = Some(build_fn_owner_index(&crate_.index));
464 value.export_name_index = Some(build_export_name_index(&crate_.index));
465
466 value
467 }
468
469 pub fn publicly_importable_names(&self, id: &'a Id) -> Vec<ImportablePath<'a>> {
471 if self.inner.index.contains_key(id) {
472 self.visibility_tracker
473 .collect_publicly_importable_names(id.0)
474 } else {
475 Default::default()
476 }
477 }
478
479 pub fn is_trait_sealed(&self, id: &'a Id) -> bool {
501 self.flags
502 .as_ref()
503 .expect("flags index was never constructed")[id]
504 .is_unconditionally_sealed()
505 }
506
507 pub fn is_trait_public_api_sealed(&self, id: &'a Id) -> bool {
528 !self
529 .flags
530 .as_ref()
531 .expect("flags index was never constructed")[id]
532 .is_pub_api_implementable()
533 }
534}
535
536fn build_fn_owner_index(index: &HashMap<Id, Item>) -> HashMap<Id, &Item> {
537 #[cfg(feature = "rayon")]
538 let iter = index.par_iter().map(|(_, value)| value);
539 #[cfg(not(feature = "rayon"))]
540 let iter = index.values();
541
542 iter.flat_map(|owner_item| {
543 if let rustdoc_types::ItemEnum::Trait(value) = &owner_item.inner {
544 #[cfg(feature = "rayon")]
545 let trait_items = value.items.par_iter();
546 #[cfg(not(feature = "rayon"))]
547 let trait_items = value.items.iter();
548
549 let output = trait_items
550 .filter_map(|id| index.get(id))
554 .filter_map(move |inner_item| match &inner_item.inner {
556 rustdoc_types::ItemEnum::Function(..) => Some((inner_item.id, owner_item)),
557 _ => None,
558 });
559
560 #[cfg(feature = "rayon")]
561 let return_value = rayon::iter::Either::Left(output);
562 #[cfg(not(feature = "rayon"))]
563 let return_value: Box<dyn Iterator<Item = (Id, &Item)>> = Box::new(output);
564
565 return_value
566 } else {
567 let impls = match &owner_item.inner {
568 rustdoc_types::ItemEnum::Union(value) => value.impls.as_slice(),
569 rustdoc_types::ItemEnum::Struct(value) => value.impls.as_slice(),
570 rustdoc_types::ItemEnum::Enum(value) => value.impls.as_slice(),
571 _ => &[],
572 };
573
574 #[cfg(feature = "rayon")]
575 let impl_iter = impls.par_iter();
576 #[cfg(not(feature = "rayon"))]
577 let impl_iter = impls.iter();
578
579 let output = impl_iter
583 .filter_map(|id| index.get(id))
584 .flat_map(|impl_item| match &impl_item.inner {
586 rustdoc_types::ItemEnum::Impl(contents) => contents.items.as_slice(),
587 _ => &[],
588 })
589 .filter_map(|id| index.get(id))
593 .filter_map(move |item| match &item.inner {
595 rustdoc_types::ItemEnum::Function(..) => Some((item.id, owner_item)),
596 _ => None,
597 });
598
599 #[cfg(feature = "rayon")]
600 let return_value = rayon::iter::Either::Right(output);
601 #[cfg(not(feature = "rayon"))]
602 let return_value: Box<dyn Iterator<Item = (Id, &Item)>> = Box::new(output);
603
604 return_value
605 }
606 })
607 .collect()
608}
609
610fn build_export_name_index(index: &HashMap<Id, Item>) -> HashMap<&str, &Item> {
611 #[cfg(feature = "rayon")]
612 let iter = index.par_iter().map(|(_, value)| value);
613 #[cfg(not(feature = "rayon"))]
614 let iter = index.values();
615
616 iter.filter_map(|item| {
617 if !matches!(
618 item.inner,
619 rustdoc_types::ItemEnum::Function(..) | rustdoc_types::ItemEnum::Static(..)
620 ) {
621 return None;
622 }
623
624 crate::exported_name::item_export_name(item).map(move |name| (name, item))
625 })
626 .collect()
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
630#[non_exhaustive]
631pub struct Path<'a> {
632 pub(crate) components: Vec<&'a str>,
633}
634
635impl<'a> Path<'a> {
636 fn new(components: Vec<&'a str>) -> Self {
637 Self { components }
638 }
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
642#[non_exhaustive]
643pub struct Modifiers {
644 pub(crate) doc_hidden: bool,
645 pub(crate) deprecated: bool,
646}
647
648#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
649#[non_exhaustive]
650pub struct ImportablePath<'a> {
651 pub(crate) path: Path<'a>,
652 pub(crate) modifiers: Modifiers,
653}
654
655impl<'a> ImportablePath<'a> {
656 pub(crate) fn new(components: Vec<&'a str>, doc_hidden: bool, deprecated: bool) -> Self {
657 Self {
658 path: Path::new(components),
659 modifiers: Modifiers {
660 doc_hidden,
661 deprecated,
662 },
663 }
664 }
665
666 pub(crate) fn public_api(&self) -> bool {
667 self.modifiers.deprecated || !self.modifiers.doc_hidden
668 }
669}
670
671impl<'a: 'b, 'b> Borrow<[&'b str]> for Path<'a> {
672 fn borrow(&self) -> &[&'b str] {
673 &self.components
674 }
675}
676
677#[derive(Debug, Clone, PartialEq, Eq, Hash)]
678pub(crate) struct ImplEntry<'a> {
679 pub(crate) data: (&'a Id, &'a str),
685}
686
687impl<'a> ImplEntry<'a> {
688 #[inline]
689 fn new(owner_id: &'a Id, item_name: &'a str) -> Self {
690 Self {
691 data: (owner_id, item_name),
692 }
693 }
694
695 #[allow(dead_code)]
696 #[inline]
697 pub(crate) fn owner_id(&self) -> &'a Id {
698 self.data.0
699 }
700
701 #[allow(dead_code)]
702 #[inline]
703 pub(crate) fn item_name(&self) -> &'a str {
704 self.data.1
705 }
706}
707
708impl<'a: 'b, 'b> Borrow<(&'b Id, &'b str)> for ImplEntry<'a> {
709 fn borrow(&self) -> &(&'b Id, &'b str) {
710 &(self.data)
711 }
712}
713
714#[derive(Debug)]
715struct ManualTraitItem {
716 name: &'static str,
717 path: &'static [&'static str],
718 is_auto: bool,
719 is_unsafe: bool,
720}
721
722const MANUAL_TRAIT_ITEMS: [ManualTraitItem; 14] = [
726 ManualTraitItem {
727 name: "Debug",
728 path: &["core", "fmt", "Debug"],
729 is_auto: false,
730 is_unsafe: false,
731 },
732 ManualTraitItem {
733 name: "Clone",
734 path: &["core", "clone", "Clone"],
735 is_auto: false,
736 is_unsafe: false,
737 },
738 ManualTraitItem {
739 name: "Copy",
740 path: &["core", "marker", "Copy"],
741 is_auto: false,
742 is_unsafe: false,
743 },
744 ManualTraitItem {
745 name: "PartialOrd",
746 path: &["core", "cmp", "PartialOrd"],
747 is_auto: false,
748 is_unsafe: false,
749 },
750 ManualTraitItem {
751 name: "Ord",
752 path: &["core", "cmp", "Ord"],
753 is_auto: false,
754 is_unsafe: false,
755 },
756 ManualTraitItem {
757 name: "PartialEq",
758 path: &["core", "cmp", "PartialEq"],
759 is_auto: false,
760 is_unsafe: false,
761 },
762 ManualTraitItem {
763 name: "Eq",
764 path: &["core", "cmp", "Eq"],
765 is_auto: false,
766 is_unsafe: false,
767 },
768 ManualTraitItem {
769 name: "Hash",
770 path: &["core", "hash", "Hash"],
771 is_auto: false,
772 is_unsafe: false,
773 },
774 ManualTraitItem {
775 name: "Send",
776 path: &["core", "marker", "Send"],
777 is_auto: true,
778 is_unsafe: true,
779 },
780 ManualTraitItem {
781 name: "Sync",
782 path: &["core", "marker", "Sync"],
783 is_auto: true,
784 is_unsafe: true,
785 },
786 ManualTraitItem {
787 name: "Unpin",
788 path: &["core", "marker", "Unpin"],
789 is_auto: true,
790 is_unsafe: false,
791 },
792 ManualTraitItem {
793 name: "RefUnwindSafe",
794 path: &["core", "panic", "unwind_safe", "RefUnwindSafe"],
795 is_auto: true,
796 is_unsafe: false,
797 },
798 ManualTraitItem {
799 name: "UnwindSafe",
800 path: &["core", "panic", "unwind_safe", "UnwindSafe"],
801 is_auto: true,
802 is_unsafe: false,
803 },
804 ManualTraitItem {
805 name: "Sized",
806 path: &["core", "marker", "Sized"],
807 is_auto: false,
808 is_unsafe: false,
809 },
810];
811
812fn new_trait(manual_trait_item: &ManualTraitItem, id: Id, crate_id: u32) -> Item {
813 Item {
814 id,
815 crate_id,
816 name: Some(manual_trait_item.name.to_string()),
817 span: None,
818 visibility: rustdoc_types::Visibility::Public,
819 docs: None,
820 links: HashMap::default(),
821 attrs: Vec::new(),
822 deprecation: None,
823 inner: rustdoc_types::ItemEnum::Trait(rustdoc_types::Trait {
824 is_auto: manual_trait_item.is_auto,
825 is_unsafe: manual_trait_item.is_unsafe,
826 is_dyn_compatible: matches!(
827 manual_trait_item.name,
828 "Debug"
829 | "PartialEq"
830 | "PartialOrd"
831 | "Send"
832 | "Sync"
833 | "Unpin"
834 | "UnwindSafe"
835 | "RefUnwindSafe"
836 ),
837 items: Vec::new(),
842 generics: rustdoc_types::Generics {
843 params: Vec::new(),
844 where_predicates: Vec::new(),
845 },
846 bounds: Vec::new(),
847 implementations: Vec::new(),
848 }),
849 }
850}
851
852fn create_manually_inlined_builtin_traits(crate_: &Crate) -> HashMap<Id, Item> {
853 let paths = &crate_.paths;
854
855 #[cfg(feature = "rayon")]
857 let iter = paths.par_iter();
858 #[cfg(not(feature = "rayon"))]
859 let iter = paths.iter();
860
861 iter.filter_map(|(id, entry)| {
862 if entry.kind != rustdoc_types::ItemKind::Trait {
863 return None;
864 }
865
866 MANUAL_TRAIT_ITEMS
869 .iter()
870 .find(|t| t.path == entry.path)
871 .map(|manual| (*id, new_trait(manual, *id, entry.crate_id)))
872 })
873 .collect()
874}
875
876#[cfg(test)]
877mod tests {
878 use itertools::Itertools;
879 use rustdoc_types::{Crate, Id};
880
881 use crate::{test_util::load_pregenerated_rustdoc, ImportablePath, IndexedCrate};
882
883 fn find_item_id<'a>(crate_: &'a Crate, name: &str) -> &'a Id {
884 crate_
885 .index
886 .iter()
887 .filter_map(|(id, item)| (item.name.as_deref() == Some(name)).then_some(id))
888 .exactly_one()
889 .expect("exactly one matching name")
890 }
891
892 #[test]
894 fn structs_are_not_modules() {
895 let rustdoc = load_pregenerated_rustdoc("structs_are_not_modules");
896 let indexed_crate = IndexedCrate::new(&rustdoc);
897
898 let top_level_function = find_item_id(&rustdoc, "top_level_function");
899 let method = find_item_id(&rustdoc, "method");
900 let associated_fn = find_item_id(&rustdoc, "associated_fn");
901 let field = find_item_id(&rustdoc, "field");
902 let const_item = find_item_id(&rustdoc, "THE_ANSWER");
903
904 assert!(indexed_crate
906 .visibility_tracker
907 .visible_parent_ids()
908 .contains_key(&top_level_function.0));
909 assert!(indexed_crate
910 .visibility_tracker
911 .visible_parent_ids()
912 .contains_key(&method.0));
913 assert!(indexed_crate
914 .visibility_tracker
915 .visible_parent_ids()
916 .contains_key(&associated_fn.0));
917 assert!(indexed_crate
918 .visibility_tracker
919 .visible_parent_ids()
920 .contains_key(&field.0));
921 assert!(indexed_crate
922 .visibility_tracker
923 .visible_parent_ids()
924 .contains_key(&const_item.0));
925
926 assert_eq!(
928 vec![ImportablePath::new(
929 vec!["structs_are_not_modules", "top_level_function"],
930 false,
931 false,
932 )],
933 indexed_crate.publicly_importable_names(top_level_function)
934 );
935 assert_eq!(
936 Vec::<ImportablePath<'_>>::new(),
937 indexed_crate.publicly_importable_names(method)
938 );
939 assert_eq!(
940 Vec::<ImportablePath<'_>>::new(),
941 indexed_crate.publicly_importable_names(associated_fn)
942 );
943 assert_eq!(
944 Vec::<ImportablePath<'_>>::new(),
945 indexed_crate.publicly_importable_names(field)
946 );
947 assert_eq!(
948 Vec::<ImportablePath<'_>>::new(),
949 indexed_crate.publicly_importable_names(const_item)
950 );
951 }
952
953 #[test]
956 fn enums_are_not_modules() {
957 let rustdoc = load_pregenerated_rustdoc("enums_are_not_modules");
958 let indexed_crate = IndexedCrate::new(&rustdoc);
959
960 let top_level_function = find_item_id(&rustdoc, "top_level_function");
961 let variant = find_item_id(&rustdoc, "Variant");
962 let method = find_item_id(&rustdoc, "method");
963 let associated_fn = find_item_id(&rustdoc, "associated_fn");
964 let const_item = find_item_id(&rustdoc, "THE_ANSWER");
965
966 assert!(indexed_crate
968 .visibility_tracker
969 .visible_parent_ids()
970 .contains_key(&top_level_function.0));
971 assert!(indexed_crate
972 .visibility_tracker
973 .visible_parent_ids()
974 .contains_key(&variant.0));
975 assert!(indexed_crate
976 .visibility_tracker
977 .visible_parent_ids()
978 .contains_key(&method.0));
979 assert!(indexed_crate
980 .visibility_tracker
981 .visible_parent_ids()
982 .contains_key(&associated_fn.0));
983 assert!(indexed_crate
984 .visibility_tracker
985 .visible_parent_ids()
986 .contains_key(&const_item.0));
987
988 assert_eq!(
990 vec![ImportablePath::new(
991 vec!["enums_are_not_modules", "top_level_function"],
992 false,
993 false,
994 )],
995 indexed_crate.publicly_importable_names(top_level_function)
996 );
997 assert_eq!(
998 vec![ImportablePath::new(
999 vec!["enums_are_not_modules", "Foo", "Variant"],
1000 false,
1001 false,
1002 )],
1003 indexed_crate.publicly_importable_names(variant)
1004 );
1005 assert_eq!(
1006 Vec::<ImportablePath<'_>>::new(),
1007 indexed_crate.publicly_importable_names(method)
1008 );
1009 assert_eq!(
1010 Vec::<ImportablePath<'_>>::new(),
1011 indexed_crate.publicly_importable_names(associated_fn)
1012 );
1013 assert_eq!(
1014 Vec::<ImportablePath<'_>>::new(),
1015 indexed_crate.publicly_importable_names(const_item)
1016 );
1017 }
1018
1019 #[test]
1021 fn unions_are_not_modules() {
1022 let rustdoc = load_pregenerated_rustdoc("unions_are_not_modules");
1023 let indexed_crate = IndexedCrate::new(&rustdoc);
1024
1025 let top_level_function = find_item_id(&rustdoc, "top_level_function");
1026 let method = find_item_id(&rustdoc, "method");
1027 let associated_fn = find_item_id(&rustdoc, "associated_fn");
1028 let left_field = find_item_id(&rustdoc, "left");
1029 let right_field = find_item_id(&rustdoc, "right");
1030 let const_item = find_item_id(&rustdoc, "THE_ANSWER");
1031
1032 assert!(indexed_crate
1034 .visibility_tracker
1035 .visible_parent_ids()
1036 .contains_key(&top_level_function.0));
1037 assert!(indexed_crate
1038 .visibility_tracker
1039 .visible_parent_ids()
1040 .contains_key(&method.0));
1041 assert!(indexed_crate
1042 .visibility_tracker
1043 .visible_parent_ids()
1044 .contains_key(&associated_fn.0));
1045 assert!(indexed_crate
1046 .visibility_tracker
1047 .visible_parent_ids()
1048 .contains_key(&left_field.0));
1049 assert!(indexed_crate
1050 .visibility_tracker
1051 .visible_parent_ids()
1052 .contains_key(&right_field.0));
1053 assert!(indexed_crate
1054 .visibility_tracker
1055 .visible_parent_ids()
1056 .contains_key(&const_item.0));
1057
1058 assert_eq!(
1060 vec![ImportablePath::new(
1061 vec!["unions_are_not_modules", "top_level_function"],
1062 false,
1063 false,
1064 )],
1065 indexed_crate.publicly_importable_names(top_level_function)
1066 );
1067 assert_eq!(
1068 Vec::<ImportablePath<'_>>::new(),
1069 indexed_crate.publicly_importable_names(method)
1070 );
1071 assert_eq!(
1072 Vec::<ImportablePath<'_>>::new(),
1073 indexed_crate.publicly_importable_names(associated_fn)
1074 );
1075 assert_eq!(
1076 Vec::<ImportablePath<'_>>::new(),
1077 indexed_crate.publicly_importable_names(left_field)
1078 );
1079 assert_eq!(
1080 Vec::<ImportablePath<'_>>::new(),
1081 indexed_crate.publicly_importable_names(right_field)
1082 );
1083 assert_eq!(
1084 Vec::<ImportablePath<'_>>::new(),
1085 indexed_crate.publicly_importable_names(const_item)
1086 );
1087 }
1088
1089 mod reexports {
1090 use std::collections::{BTreeMap, BTreeSet};
1091
1092 use itertools::Itertools;
1093 use maplit::{btreemap, btreeset};
1094 use rustdoc_types::{ItemEnum, Visibility};
1095
1096 use crate::{test_util::load_pregenerated_rustdoc, ImportablePath, IndexedCrate};
1097
1098 fn assert_exported_items_match(
1099 test_crate: &str,
1100 expected_items: &BTreeMap<&str, BTreeSet<&str>>,
1101 ) {
1102 let rustdoc = load_pregenerated_rustdoc(test_crate);
1103 let indexed_crate = IndexedCrate::new(&rustdoc);
1104
1105 for (&expected_item_name, expected_importable_paths) in expected_items {
1106 assert!(
1107 !expected_item_name.contains(':'),
1108 "only direct item names can be checked at the moment: {expected_item_name}"
1109 );
1110
1111 let item_id_candidates = rustdoc
1112 .index
1113 .iter()
1114 .filter_map(|(id, item)| {
1115 (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1116 })
1117 .collect_vec();
1118 if item_id_candidates.len() != 1 {
1119 panic!(
1120 "Expected to find exactly one item with name {expected_item_name}, \
1121 but found these matching IDs: {item_id_candidates:?}"
1122 );
1123 }
1124 let item_id = item_id_candidates[0];
1125 let actual_items: Vec<_> = indexed_crate
1126 .publicly_importable_names(item_id)
1127 .into_iter()
1128 .map(|importable| importable.path.components.into_iter().join("::"))
1129 .collect();
1130 let deduplicated_actual_items: BTreeSet<_> =
1131 actual_items.iter().map(|x| x.as_str()).collect();
1132 assert_eq!(
1133 actual_items.len(),
1134 deduplicated_actual_items.len(),
1135 "duplicates found: {actual_items:?}"
1136 );
1137
1138 assert_eq!(
1139 expected_importable_paths, &deduplicated_actual_items,
1140 "mismatch for item name {expected_item_name}",
1141 );
1142 }
1143 }
1144
1145 fn assert_duplicated_exported_items_match(
1148 test_crate: &str,
1149 expected_items_and_counts: &BTreeMap<&str, (usize, BTreeSet<&str>)>,
1150 ) {
1151 let rustdoc = load_pregenerated_rustdoc(test_crate);
1152 let indexed_crate = IndexedCrate::new(&rustdoc);
1153
1154 for (&expected_item_name, (expected_count, expected_importable_paths)) in
1155 expected_items_and_counts
1156 {
1157 assert!(
1158 !expected_item_name.contains(':'),
1159 "only direct item names can be checked at the moment: {expected_item_name}"
1160 );
1161
1162 let item_id_candidates = rustdoc
1163 .index
1164 .iter()
1165 .filter_map(|(id, item)| {
1166 (item.name.as_deref() == Some(expected_item_name)).then_some(id)
1167 })
1168 .collect_vec();
1169 if item_id_candidates.len() != *expected_count {
1170 panic!(
1171 "Expected to find exactly {expected_count} items with name \
1172 {expected_item_name}, but found these matching IDs: {item_id_candidates:?}"
1173 );
1174 }
1175 for item_id in item_id_candidates {
1176 let actual_items: Vec<_> = indexed_crate
1177 .publicly_importable_names(item_id)
1178 .into_iter()
1179 .map(|importable| importable.path.components.into_iter().join("::"))
1180 .collect();
1181 let deduplicated_actual_items: BTreeSet<_> =
1182 actual_items.iter().map(|x| x.as_str()).collect();
1183 assert_eq!(
1184 actual_items.len(),
1185 deduplicated_actual_items.len(),
1186 "duplicates found: {actual_items:?}"
1187 );
1188 assert_eq!(expected_importable_paths, &deduplicated_actual_items);
1189 }
1190 }
1191 }
1192
1193 #[test]
1194 fn pub_inside_pub_crate_mod() {
1195 let test_crate = "pub_inside_pub_crate_mod";
1196 let expected_items = btreemap! {
1197 "Foo" => btreeset![],
1198 "Bar" => btreeset![
1199 "pub_inside_pub_crate_mod::Bar",
1200 ],
1201 };
1202
1203 assert_exported_items_match(test_crate, &expected_items);
1204 }
1205
1206 #[test]
1207 fn reexport() {
1208 let test_crate = "reexport";
1209 let expected_items = btreemap! {
1210 "foo" => btreeset![
1211 "reexport::foo",
1212 "reexport::inner::foo",
1213 ],
1214 };
1215
1216 assert_exported_items_match(test_crate, &expected_items);
1217 }
1218
1219 #[test]
1220 fn reexport_from_private_module() {
1221 let test_crate = "reexport_from_private_module";
1222 let expected_items = btreemap! {
1223 "foo" => btreeset![
1224 "reexport_from_private_module::foo",
1225 ],
1226 "Bar" => btreeset![
1227 "reexport_from_private_module::Bar",
1228 ],
1229 "Baz" => btreeset![
1230 "reexport_from_private_module::nested::Baz",
1231 ],
1232 "quux" => btreeset![
1233 "reexport_from_private_module::quux",
1234 ],
1235 };
1236
1237 assert_exported_items_match(test_crate, &expected_items);
1238 }
1239
1240 #[test]
1241 fn renaming_reexport() {
1242 let test_crate = "renaming_reexport";
1243 let expected_items = btreemap! {
1244 "foo" => btreeset![
1245 "renaming_reexport::bar",
1246 "renaming_reexport::inner::foo",
1247 ],
1248 };
1249
1250 assert_exported_items_match(test_crate, &expected_items);
1251 }
1252
1253 #[test]
1254 fn renaming_reexport_of_reexport() {
1255 let test_crate = "renaming_reexport_of_reexport";
1256 let expected_items = btreemap! {
1257 "foo" => btreeset![
1258 "renaming_reexport_of_reexport::bar",
1259 "renaming_reexport_of_reexport::foo",
1260 "renaming_reexport_of_reexport::inner::foo",
1261 ],
1262 };
1263
1264 assert_exported_items_match(test_crate, &expected_items);
1265 }
1266
1267 #[test]
1268 fn renaming_mod_reexport() {
1269 let test_crate = "renaming_mod_reexport";
1270 let expected_items = btreemap! {
1271 "foo" => btreeset![
1272 "renaming_mod_reexport::inner::a::foo",
1273 "renaming_mod_reexport::inner::b::foo",
1274 "renaming_mod_reexport::direct::foo",
1275 ],
1276 };
1277
1278 assert_exported_items_match(test_crate, &expected_items);
1279 }
1280
1281 #[test]
1282 fn glob_reexport() {
1283 let test_crate = "glob_reexport";
1284 let expected_items = btreemap! {
1285 "foo" => btreeset![
1286 "glob_reexport::foo",
1287 "glob_reexport::inner::foo",
1288 ],
1289 "Bar" => btreeset![
1290 "glob_reexport::Bar",
1291 "glob_reexport::inner::Bar",
1292 ],
1293 "nested" => btreeset![
1294 "glob_reexport::nested",
1295 ],
1296 "Baz" => btreeset![
1297 "glob_reexport::Baz",
1298 ],
1299 "First" => btreeset![
1300 "glob_reexport::First",
1301 "glob_reexport::Baz::First",
1302 ],
1303 "Second" => btreeset![
1304 "glob_reexport::Second",
1305 "glob_reexport::Baz::Second",
1306 ],
1307 };
1308
1309 assert_exported_items_match(test_crate, &expected_items);
1310 }
1311
1312 #[test]
1313 fn glob_of_glob_reexport() {
1314 let test_crate = "glob_of_glob_reexport";
1315 let expected_items = btreemap! {
1316 "foo" => btreeset![
1317 "glob_of_glob_reexport::foo",
1318 ],
1319 "Bar" => btreeset![
1320 "glob_of_glob_reexport::Bar",
1321 ],
1322 "Baz" => btreeset![
1323 "glob_of_glob_reexport::Baz",
1324 ],
1325 "Onion" => btreeset![
1326 "glob_of_glob_reexport::Onion",
1327 ],
1328 };
1329
1330 assert_exported_items_match(test_crate, &expected_items);
1331 }
1332
1333 #[test]
1334 fn glob_of_renamed_reexport() {
1335 let test_crate = "glob_of_renamed_reexport";
1336 let expected_items = btreemap! {
1337 "foo" => btreeset![
1338 "glob_of_renamed_reexport::renamed_foo",
1339 ],
1340 "Bar" => btreeset![
1341 "glob_of_renamed_reexport::RenamedBar",
1342 ],
1343 "First" => btreeset![
1344 "glob_of_renamed_reexport::RenamedFirst",
1345 ],
1346 "Onion" => btreeset![
1347 "glob_of_renamed_reexport::RenamedOnion",
1348 ],
1349 };
1350
1351 assert_exported_items_match(test_crate, &expected_items);
1352 }
1353
1354 #[test]
1355 fn glob_reexport_enum_variants() {
1356 let test_crate = "glob_reexport_enum_variants";
1357 let expected_items = btreemap! {
1358 "First" => btreeset![
1359 "glob_reexport_enum_variants::First",
1360 ],
1361 "Second" => btreeset![
1362 "glob_reexport_enum_variants::Second",
1363 ],
1364 };
1365
1366 assert_exported_items_match(test_crate, &expected_items);
1367 }
1368
1369 #[test]
1370 fn glob_reexport_cycle() {
1371 let test_crate = "glob_reexport_cycle";
1372 let expected_items = btreemap! {
1373 "foo" => btreeset![
1374 "glob_reexport_cycle::first::foo",
1375 "glob_reexport_cycle::second::foo",
1376 ],
1377 "Bar" => btreeset![
1378 "glob_reexport_cycle::first::Bar",
1379 "glob_reexport_cycle::second::Bar",
1380 ],
1381 };
1382
1383 assert_exported_items_match(test_crate, &expected_items);
1384 }
1385
1386 #[test]
1387 fn infinite_recursive_reexport() {
1388 let test_crate = "infinite_recursive_reexport";
1389 let expected_items = btreemap! {
1390 "foo" => btreeset![
1391 "infinite_recursive_reexport::foo",
1394 "infinite_recursive_reexport::inner::foo",
1395 ],
1396 };
1397
1398 assert_exported_items_match(test_crate, &expected_items);
1399 }
1400
1401 #[test]
1402 fn infinite_indirect_recursive_reexport() {
1403 let test_crate = "infinite_indirect_recursive_reexport";
1404 let expected_items = btreemap! {
1405 "foo" => btreeset![
1406 "infinite_indirect_recursive_reexport::foo",
1409 "infinite_indirect_recursive_reexport::nested::foo",
1410 ],
1411 };
1412
1413 assert_exported_items_match(test_crate, &expected_items);
1414 }
1415
1416 #[test]
1417 fn infinite_corecursive_reexport() {
1418 let test_crate = "infinite_corecursive_reexport";
1419 let expected_items = btreemap! {
1420 "foo" => btreeset![
1421 "infinite_corecursive_reexport::a::foo",
1424 "infinite_corecursive_reexport::b::a::foo",
1425 ],
1426 };
1427
1428 assert_exported_items_match(test_crate, &expected_items);
1429 }
1430
1431 #[test]
1432 fn pub_type_alias_reexport() {
1433 let test_crate = "pub_type_alias_reexport";
1434 let expected_items = btreemap! {
1435 "Foo" => btreeset![
1436 "pub_type_alias_reexport::Exported",
1437 ],
1438 };
1439
1440 assert_exported_items_match(test_crate, &expected_items);
1441 }
1442
1443 #[test]
1444 fn pub_generic_type_alias_reexport() {
1445 let test_crate = "pub_generic_type_alias_reexport";
1446 let expected_items = btreemap! {
1447 "Foo" => btreeset![
1448 "pub_generic_type_alias_reexport::Exported",
1458 "pub_generic_type_alias_reexport::ExportedRenamedParams",
1459 ],
1460 "Exported" => btreeset![
1461 "pub_generic_type_alias_reexport::Exported",
1463 ],
1464 "ExportedWithDefaults" => btreeset![
1465 "pub_generic_type_alias_reexport::ExportedWithDefaults",
1467 ],
1468 "ExportedRenamedParams" => btreeset![
1469 "pub_generic_type_alias_reexport::ExportedRenamedParams",
1471 ],
1472 "ExportedSpecificLifetime" => btreeset![
1473 "pub_generic_type_alias_reexport::ExportedSpecificLifetime",
1474 ],
1475 "ExportedSpecificType" => btreeset![
1476 "pub_generic_type_alias_reexport::ExportedSpecificType",
1477 ],
1478 "ExportedSpecificConst" => btreeset![
1479 "pub_generic_type_alias_reexport::ExportedSpecificConst",
1480 ],
1481 "ExportedFullySpecified" => btreeset![
1482 "pub_generic_type_alias_reexport::ExportedFullySpecified",
1483 ],
1484 };
1485
1486 assert_exported_items_match(test_crate, &expected_items);
1487 }
1488
1489 #[test]
1490 fn pub_generic_type_alias_shuffled_order() {
1491 let test_crate = "pub_generic_type_alias_shuffled_order";
1492 let expected_items = btreemap! {
1493 "GenericFoo" => btreeset![
1496 "pub_generic_type_alias_shuffled_order::inner::GenericFoo",
1497 ],
1498 "LifetimeFoo" => btreeset![
1499 "pub_generic_type_alias_shuffled_order::inner::LifetimeFoo",
1500 ],
1501 "ConstFoo" => btreeset![
1502 "pub_generic_type_alias_shuffled_order::inner::ConstFoo",
1503 ],
1504 "ReversedGenericFoo" => btreeset![
1505 "pub_generic_type_alias_shuffled_order::ReversedGenericFoo",
1506 ],
1507 "ReversedLifetimeFoo" => btreeset![
1508 "pub_generic_type_alias_shuffled_order::ReversedLifetimeFoo",
1509 ],
1510 "ReversedConstFoo" => btreeset![
1511 "pub_generic_type_alias_shuffled_order::ReversedConstFoo",
1512 ],
1513 };
1514
1515 assert_exported_items_match(test_crate, &expected_items);
1516 }
1517
1518 #[test]
1519 fn pub_generic_type_alias_added_defaults() {
1520 let test_crate = "pub_generic_type_alias_added_defaults";
1521 let expected_items = btreemap! {
1522 "Foo" => btreeset![
1523 "pub_generic_type_alias_added_defaults::inner::Foo",
1524 ],
1525 "Bar" => btreeset![
1526 "pub_generic_type_alias_added_defaults::inner::Bar",
1527 ],
1528 "DefaultFoo" => btreeset![
1529 "pub_generic_type_alias_added_defaults::DefaultFoo",
1530 ],
1531 "DefaultBar" => btreeset![
1532 "pub_generic_type_alias_added_defaults::DefaultBar",
1533 ],
1534 };
1535
1536 assert_exported_items_match(test_crate, &expected_items);
1537 }
1538
1539 #[test]
1540 fn pub_generic_type_alias_changed_defaults() {
1541 let test_crate = "pub_generic_type_alias_changed_defaults";
1542 let expected_items = btreemap! {
1543 "Foo" => btreeset![
1546 "pub_generic_type_alias_changed_defaults::inner::Foo",
1547 ],
1548 "Bar" => btreeset![
1549 "pub_generic_type_alias_changed_defaults::inner::Bar",
1550 ],
1551 "ExportedWithoutTypeDefault" => btreeset![
1552 "pub_generic_type_alias_changed_defaults::ExportedWithoutTypeDefault",
1553 ],
1554 "ExportedWithoutConstDefault" => btreeset![
1555 "pub_generic_type_alias_changed_defaults::ExportedWithoutConstDefault",
1556 ],
1557 "ExportedWithoutDefaults" => btreeset![
1558 "pub_generic_type_alias_changed_defaults::ExportedWithoutDefaults",
1559 ],
1560 "ExportedWithDifferentTypeDefault" => btreeset![
1561 "pub_generic_type_alias_changed_defaults::ExportedWithDifferentTypeDefault",
1562 ],
1563 "ExportedWithDifferentConstDefault" => btreeset![
1564 "pub_generic_type_alias_changed_defaults::ExportedWithDifferentConstDefault",
1565 ],
1566 "ExportedWithDifferentDefaults" => btreeset![
1567 "pub_generic_type_alias_changed_defaults::ExportedWithDifferentDefaults",
1568 ],
1569 };
1570
1571 assert_exported_items_match(test_crate, &expected_items);
1572 }
1573
1574 #[test]
1575 fn pub_generic_type_alias_same_signature_but_not_equivalent() {
1576 let test_crate = "pub_generic_type_alias_same_signature_but_not_equivalent";
1577 let expected_items = btreemap! {
1578 "GenericFoo" => btreeset![
1579 "pub_generic_type_alias_same_signature_but_not_equivalent::inner::GenericFoo",
1580 ],
1581 "ChangedFoo" => btreeset![
1582 "pub_generic_type_alias_same_signature_but_not_equivalent::ChangedFoo",
1583 ],
1584 };
1585
1586 assert_exported_items_match(test_crate, &expected_items);
1587 }
1588
1589 #[test]
1590 fn pub_type_alias_of_type_alias() {
1591 let test_crate = "pub_type_alias_of_type_alias";
1592 let expected_items = btreemap! {
1593 "Foo" => btreeset![
1594 "pub_type_alias_of_type_alias::inner::Foo",
1595 "pub_type_alias_of_type_alias::inner::AliasedFoo",
1596 "pub_type_alias_of_type_alias::ExportedFoo",
1597 ],
1598 "Bar" => btreeset![
1599 "pub_type_alias_of_type_alias::inner::Bar",
1600 "pub_type_alias_of_type_alias::inner::AliasedBar",
1601 "pub_type_alias_of_type_alias::ExportedBar",
1602 ],
1603 "AliasedFoo" => btreeset![
1604 "pub_type_alias_of_type_alias::inner::AliasedFoo",
1605 "pub_type_alias_of_type_alias::ExportedFoo",
1606 ],
1607 "AliasedBar" => btreeset![
1608 "pub_type_alias_of_type_alias::inner::AliasedBar",
1609 "pub_type_alias_of_type_alias::ExportedBar",
1610 ],
1611 "ExportedFoo" => btreeset![
1612 "pub_type_alias_of_type_alias::ExportedFoo",
1613 ],
1614 "ExportedBar" => btreeset![
1615 "pub_type_alias_of_type_alias::ExportedBar",
1616 ],
1617 "DifferentLifetimeBar" => btreeset![
1618 "pub_type_alias_of_type_alias::DifferentLifetimeBar",
1619 ],
1620 "DifferentGenericBar" => btreeset![
1621 "pub_type_alias_of_type_alias::DifferentGenericBar",
1622 ],
1623 "DifferentConstBar" => btreeset![
1624 "pub_type_alias_of_type_alias::DifferentConstBar",
1625 ],
1626 "ReorderedBar" => btreeset![
1627 "pub_type_alias_of_type_alias::ReorderedBar",
1628 ],
1629 "DefaultValueBar" => btreeset![
1630 "pub_type_alias_of_type_alias::DefaultValueBar",
1631 ],
1632 };
1633
1634 assert_exported_items_match(test_crate, &expected_items);
1635 }
1636
1637 #[test]
1638 fn pub_type_alias_of_composite_type() {
1639 let test_crate = "pub_type_alias_of_composite_type";
1640 let expected_items = btreemap! {
1641 "Foo" => btreeset![
1642 "pub_type_alias_of_composite_type::inner::Foo",
1643 ],
1644 "I64Tuple" => btreeset![
1645 "pub_type_alias_of_composite_type::I64Tuple",
1646 ],
1647 "MixedTuple" => btreeset![
1648 "pub_type_alias_of_composite_type::MixedTuple",
1649 ],
1650 "GenericTuple" => btreeset![
1651 "pub_type_alias_of_composite_type::GenericTuple",
1652 ],
1653 "LifetimeTuple" => btreeset![
1654 "pub_type_alias_of_composite_type::LifetimeTuple",
1655 ],
1656 "ConstTuple" => btreeset![
1657 "pub_type_alias_of_composite_type::ConstTuple",
1658 ],
1659 "DefaultGenericTuple" => btreeset![
1660 "pub_type_alias_of_composite_type::DefaultGenericTuple",
1661 ],
1662 "DefaultConstTuple" => btreeset![
1663 "pub_type_alias_of_composite_type::DefaultConstTuple",
1664 ],
1665 };
1666
1667 assert_exported_items_match(test_crate, &expected_items);
1668 }
1669
1670 #[test]
1671 fn pub_generic_type_alias_omitted_default() {
1672 let test_crate = "pub_generic_type_alias_omitted_default";
1673 let expected_items = btreemap! {
1674 "DefaultConst" => btreeset![
1675 "pub_generic_type_alias_omitted_default::inner::DefaultConst",
1676 ],
1677 "DefaultType" => btreeset![
1678 "pub_generic_type_alias_omitted_default::inner::DefaultType",
1679 ],
1680 "ConstOnly" => btreeset![
1681 "pub_generic_type_alias_omitted_default::inner::ConstOnly",
1682 ],
1683 "TypeOnly" => btreeset![
1684 "pub_generic_type_alias_omitted_default::inner::TypeOnly",
1685 ],
1686 "OmittedConst" => btreeset![
1687 "pub_generic_type_alias_omitted_default::OmittedConst",
1688 ],
1689 "OmittedType" => btreeset![
1690 "pub_generic_type_alias_omitted_default::OmittedType",
1691 ],
1692 "NonGenericConst" => btreeset![
1693 "pub_generic_type_alias_omitted_default::NonGenericConst",
1694 ],
1695 "NonGenericType" => btreeset![
1696 "pub_generic_type_alias_omitted_default::NonGenericType",
1697 ],
1698 };
1699
1700 assert_exported_items_match(test_crate, &expected_items);
1701 }
1702
1703 #[test]
1704 fn swapping_names() {
1705 let test_crate = "swapping_names";
1706 let expected_items = btreemap! {
1707 "Foo" => btreeset![
1708 "swapping_names::Foo",
1709 "swapping_names::inner::Bar",
1710 "swapping_names::inner::nested::Foo",
1711 ],
1712 "Bar" => btreeset![
1713 "swapping_names::Bar",
1714 "swapping_names::inner::Foo",
1715 "swapping_names::inner::nested::Bar",
1716 ],
1717 };
1718
1719 assert_exported_items_match(test_crate, &expected_items);
1720 }
1721
1722 #[test]
1723 fn overlapping_glob_and_local_module() {
1724 let test_crate = "overlapping_glob_and_local_module";
1725 let expected_items = btreemap! {
1726 "Foo" => btreeset![
1727 "overlapping_glob_and_local_module::sibling::duplicated::Foo",
1728 ],
1729 "Bar" => btreeset![
1730 "overlapping_glob_and_local_module::inner::duplicated::Bar",
1731 ],
1732 };
1733
1734 assert_exported_items_match(test_crate, &expected_items);
1735 }
1736
1737 #[test]
1738 fn overlapping_glob_and_renamed_module() {
1739 let test_crate = "overlapping_glob_and_renamed_module";
1740 let expected_items = btreemap! {
1741 "Foo" => btreeset![
1742 "overlapping_glob_and_renamed_module::sibling::duplicated::Foo",
1743 ],
1744 "Bar" => btreeset![
1745 "overlapping_glob_and_renamed_module::inner::duplicated::Bar",
1746 ],
1747 };
1748
1749 assert_exported_items_match(test_crate, &expected_items);
1750 }
1751
1752 #[test]
1753 fn type_and_value_with_matching_names() {
1754 let test_crate = "type_and_value_with_matching_names";
1755 let expected_items = btreemap! {
1756 "Foo" => (2, btreeset![
1757 "type_and_value_with_matching_names::Foo",
1758 "type_and_value_with_matching_names::nested::Foo",
1759 ]),
1760 "Bar" => (2, btreeset![
1761 "type_and_value_with_matching_names::Bar",
1762 "type_and_value_with_matching_names::nested::Bar",
1763 ]),
1764 };
1765
1766 assert_duplicated_exported_items_match(test_crate, &expected_items);
1767 }
1768
1769 #[test]
1770 fn no_shadowing_across_namespaces() {
1771 let test_crate = "no_shadowing_across_namespaces";
1772 let expected_items = btreemap! {
1773 "Foo" => (2, btreeset![
1774 "no_shadowing_across_namespaces::Foo",
1775 "no_shadowing_across_namespaces::nested::Foo",
1776 ]),
1777 };
1778
1779 assert_duplicated_exported_items_match(test_crate, &expected_items);
1780 }
1781
1782 #[test]
1783 fn explicit_reexport_of_matching_names() {
1784 if version_check::is_min_version("1.69.0").unwrap_or(true) {
1785 let test_crate = "explicit_reexport_of_matching_names";
1786 let expected_items = btreemap! {
1787 "Foo" => (2, btreeset![
1788 "explicit_reexport_of_matching_names::Bar",
1789 "explicit_reexport_of_matching_names::Foo",
1790 "explicit_reexport_of_matching_names::nested::Foo",
1791 ]),
1792 };
1793
1794 assert_duplicated_exported_items_match(test_crate, &expected_items);
1795 } else {
1796 use std::io::Write;
1797 writeln!(
1798 std::io::stderr(),
1799 "skipping 'explicit_reexport_of_matching_names' test due to Rust {:?}",
1800 version_check::Version::read(),
1801 )
1802 .expect("write failed");
1803 }
1804 }
1805
1806 #[test]
1807 fn overlapping_glob_and_local_item() {
1808 let test_crate = "overlapping_glob_and_local_item";
1809
1810 let rustdoc = load_pregenerated_rustdoc(test_crate);
1811 let indexed_crate = IndexedCrate::new(&rustdoc);
1812
1813 let foo_ids = rustdoc
1814 .index
1815 .iter()
1816 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1817 .collect_vec();
1818 if foo_ids.len() != 2 {
1819 panic!(
1820 "Expected to find exactly 2 items with name \
1821 Foo, but found these matching IDs: {foo_ids:?}"
1822 );
1823 }
1824
1825 let item_id_candidates = rustdoc
1826 .index
1827 .iter()
1828 .filter_map(|(id, item)| {
1829 (matches!(item.name.as_deref(), Some("Foo" | "Bar"))).then_some(id)
1830 })
1831 .collect_vec();
1832 if item_id_candidates.len() != 3 {
1833 panic!(
1834 "Expected to find exactly 3 items named Foo or Bar, \
1835 but found these matching IDs: {item_id_candidates:?}"
1836 );
1837 }
1838
1839 let mut all_importable_paths = Vec::new();
1840 for item_id in item_id_candidates {
1841 let actual_items: Vec<_> = indexed_crate
1842 .publicly_importable_names(item_id)
1843 .into_iter()
1844 .map(|importable| importable.path.components.into_iter().join("::"))
1845 .collect();
1846 let deduplicated_actual_items: BTreeSet<_> =
1847 actual_items.iter().map(|x| x.as_str()).collect();
1848 assert_eq!(
1849 actual_items.len(),
1850 deduplicated_actual_items.len(),
1851 "duplicates found: {actual_items:?}"
1852 );
1853
1854 if deduplicated_actual_items
1855 .first()
1856 .expect("no names")
1857 .ends_with("::Foo")
1858 {
1859 assert_eq!(
1860 deduplicated_actual_items.len(),
1861 1,
1862 "\
1863expected exactly one importable path for `Foo` items in this crate but got: {actual_items:?}"
1864 );
1865 } else {
1866 assert_eq!(
1867 deduplicated_actual_items,
1868 btreeset! {
1869 "overlapping_glob_and_local_item::Bar",
1870 "overlapping_glob_and_local_item::inner::Bar",
1871 }
1872 );
1873 }
1874
1875 all_importable_paths.extend(actual_items);
1876 }
1877
1878 all_importable_paths.sort_unstable();
1879 assert_eq!(
1880 vec![
1881 "overlapping_glob_and_local_item::Bar",
1882 "overlapping_glob_and_local_item::Foo",
1883 "overlapping_glob_and_local_item::inner::Bar",
1884 "overlapping_glob_and_local_item::inner::Foo",
1885 ],
1886 all_importable_paths,
1887 );
1888 }
1889
1890 #[test]
1891 fn nested_overlapping_glob_and_local_item() {
1892 let test_crate = "nested_overlapping_glob_and_local_item";
1893
1894 let rustdoc = load_pregenerated_rustdoc(test_crate);
1895 let indexed_crate = IndexedCrate::new(&rustdoc);
1896
1897 let item_id_candidates = rustdoc
1898 .index
1899 .iter()
1900 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1901 .collect_vec();
1902 if item_id_candidates.len() != 2 {
1903 panic!(
1904 "Expected to find exactly 2 items with name \
1905 Foo, but found these matching IDs: {item_id_candidates:?}"
1906 );
1907 }
1908
1909 let mut all_importable_paths = Vec::new();
1910 for item_id in item_id_candidates {
1911 let actual_items: Vec<_> = indexed_crate
1912 .publicly_importable_names(item_id)
1913 .into_iter()
1914 .map(|importable| importable.path.components.into_iter().join("::"))
1915 .collect();
1916 let deduplicated_actual_items: BTreeSet<_> =
1917 actual_items.iter().map(|x| x.as_str()).collect();
1918
1919 assert_eq!(
1920 actual_items.len(),
1921 deduplicated_actual_items.len(),
1922 "duplicates found: {actual_items:?}"
1923 );
1924
1925 match deduplicated_actual_items.len() {
1926 1 => assert_eq!(
1927 deduplicated_actual_items,
1928 btreeset! { "nested_overlapping_glob_and_local_item::Foo" },
1929 ),
1930 2 => assert_eq!(
1931 deduplicated_actual_items,
1932 btreeset! {
1933 "nested_overlapping_glob_and_local_item::inner::Foo",
1934 "nested_overlapping_glob_and_local_item::inner::nested::Foo",
1935 }
1936 ),
1937 _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
1938 };
1939
1940 all_importable_paths.extend(actual_items);
1941 }
1942
1943 all_importable_paths.sort_unstable();
1944 assert_eq!(
1945 vec![
1946 "nested_overlapping_glob_and_local_item::Foo",
1947 "nested_overlapping_glob_and_local_item::inner::Foo",
1948 "nested_overlapping_glob_and_local_item::inner::nested::Foo",
1949 ],
1950 all_importable_paths,
1951 );
1952 }
1953
1954 #[test]
1955 fn cyclic_overlapping_glob_and_local_item() {
1956 let test_crate = "cyclic_overlapping_glob_and_local_item";
1957
1958 let rustdoc = load_pregenerated_rustdoc(test_crate);
1959 let indexed_crate = IndexedCrate::new(&rustdoc);
1960
1961 let item_id_candidates = rustdoc
1962 .index
1963 .iter()
1964 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
1965 .collect_vec();
1966 if item_id_candidates.len() != 2 {
1967 panic!(
1968 "Expected to find exactly 2 items with name \
1969 Foo, but found these matching IDs: {item_id_candidates:?}"
1970 );
1971 }
1972
1973 let mut all_importable_paths = Vec::new();
1974 for item_id in item_id_candidates {
1975 let actual_items: Vec<_> = indexed_crate
1976 .publicly_importable_names(item_id)
1977 .into_iter()
1978 .map(|importable| importable.path.components.into_iter().join("::"))
1979 .collect();
1980 let deduplicated_actual_items: BTreeSet<_> =
1981 actual_items.iter().map(|x| x.as_str()).collect();
1982
1983 assert_eq!(
1984 actual_items.len(),
1985 deduplicated_actual_items.len(),
1986 "duplicates found: {actual_items:?}"
1987 );
1988
1989 match deduplicated_actual_items.len() {
1990 1 => assert_eq!(
1991 btreeset! { "cyclic_overlapping_glob_and_local_item::Foo" },
1992 deduplicated_actual_items,
1993 ),
1994 4 => assert_eq!(
1995 btreeset! {
1996 "cyclic_overlapping_glob_and_local_item::inner::Foo",
1997 "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
1998 "cyclic_overlapping_glob_and_local_item::nested::Foo",
1999 "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2000 },
2001 deduplicated_actual_items,
2002 ),
2003 _ => unreachable!("unexpected value for {deduplicated_actual_items:?}"),
2004 };
2005
2006 all_importable_paths.extend(actual_items);
2007 }
2008
2009 all_importable_paths.sort_unstable();
2010 assert_eq!(
2011 vec![
2012 "cyclic_overlapping_glob_and_local_item::Foo",
2013 "cyclic_overlapping_glob_and_local_item::inner::Foo",
2014 "cyclic_overlapping_glob_and_local_item::inner::nested::Foo",
2015 "cyclic_overlapping_glob_and_local_item::nested::Foo",
2016 "cyclic_overlapping_glob_and_local_item::nested::inner::Foo",
2017 ],
2018 all_importable_paths,
2019 );
2020 }
2021
2022 #[test]
2023 fn overlapping_glob_of_enum_with_local_item() {
2024 let test_crate = "overlapping_glob_of_enum_with_local_item";
2025 let easy_expected_items = btreemap! {
2026 "Foo" => btreeset![
2027 "overlapping_glob_of_enum_with_local_item::Foo",
2028 ],
2029 "Second" => btreeset![
2030 "overlapping_glob_of_enum_with_local_item::Foo::Second",
2031 "overlapping_glob_of_enum_with_local_item::inner::Second",
2032 ],
2033 };
2034
2035 assert_exported_items_match(test_crate, &easy_expected_items);
2039
2040 let rustdoc = load_pregenerated_rustdoc(test_crate);
2041 let indexed_crate = IndexedCrate::new(&rustdoc);
2042
2043 let items_named_first: Vec<_> = indexed_crate
2044 .inner
2045 .index
2046 .values()
2047 .filter(|item| item.name.as_deref() == Some("First"))
2048 .collect();
2049 assert_eq!(2, items_named_first.len(), "{items_named_first:?}");
2050 let variant_item = items_named_first
2051 .iter()
2052 .copied()
2053 .find(|item| matches!(item.inner, ItemEnum::Variant(..)))
2054 .expect("no variant item found");
2055 let struct_item = items_named_first
2056 .iter()
2057 .copied()
2058 .find(|item| matches!(item.inner, ItemEnum::Struct(..)))
2059 .expect("no struct item found");
2060
2061 assert_eq!(
2062 vec![ImportablePath::new(
2063 vec!["overlapping_glob_of_enum_with_local_item", "Foo", "First"],
2064 false,
2065 false,
2066 )],
2067 indexed_crate.publicly_importable_names(&variant_item.id),
2068 );
2069 assert_eq!(
2070 vec![ImportablePath::new(
2072 vec!["overlapping_glob_of_enum_with_local_item", "inner", "First"],
2073 false,
2074 false,
2075 )],
2076 indexed_crate.publicly_importable_names(&struct_item.id),
2077 );
2078 }
2079
2080 #[test]
2081 fn glob_of_enum_does_not_shadow_local_fn() {
2082 let test_crate = "glob_of_enum_does_not_shadow_local_fn";
2083
2084 let rustdoc = load_pregenerated_rustdoc(test_crate);
2085 let indexed_crate = IndexedCrate::new(&rustdoc);
2086
2087 let first_ids = rustdoc
2088 .index
2089 .iter()
2090 .filter_map(|(id, item)| (item.name.as_deref() == Some("First")).then_some(id))
2091 .collect_vec();
2092 if first_ids.len() != 2 {
2093 panic!(
2094 "Expected to find exactly 2 items with name \
2095 First, but found these matching IDs: {first_ids:?}"
2096 );
2097 }
2098
2099 for item_id in first_ids {
2100 let actual_items: Vec<_> = indexed_crate
2101 .publicly_importable_names(item_id)
2102 .into_iter()
2103 .map(|importable| importable.path.components.into_iter().join("::"))
2104 .collect();
2105 let deduplicated_actual_items: BTreeSet<_> =
2106 actual_items.iter().map(|x| x.as_str()).collect();
2107 assert_eq!(
2108 actual_items.len(),
2109 deduplicated_actual_items.len(),
2110 "duplicates found: {actual_items:?}"
2111 );
2112
2113 let expected_items = match &rustdoc.index[item_id].inner {
2114 ItemEnum::Variant(..) => {
2115 vec!["glob_of_enum_does_not_shadow_local_fn::Foo::First"]
2116 }
2117 ItemEnum::Function(..) => {
2118 vec!["glob_of_enum_does_not_shadow_local_fn::inner::First"]
2119 }
2120 other => {
2121 unreachable!("item {item_id:?} had unexpected inner content: {other:?}")
2122 }
2123 };
2124
2125 assert_eq!(expected_items, actual_items);
2126 }
2127 }
2128
2129 #[test]
2132 #[should_panic = "expected no importable item names but found \
2133 [\"overlapping_glob_and_private_import::inner::Foo\"]"]
2134 fn overlapping_glob_and_private_import() {
2135 let test_crate = "overlapping_glob_and_private_import";
2136
2137 let rustdoc = load_pregenerated_rustdoc(test_crate);
2138 let indexed_crate = IndexedCrate::new(&rustdoc);
2139
2140 let item_id_candidates = rustdoc
2141 .index
2142 .iter()
2143 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2144 .collect_vec();
2145 if item_id_candidates.len() != 2 {
2146 panic!(
2147 "Expected to find exactly 2 items with name \
2148 Foo, but found these matching IDs: {item_id_candidates:?}"
2149 );
2150 }
2151
2152 for item_id in item_id_candidates {
2153 let actual_items: Vec<_> = indexed_crate
2154 .publicly_importable_names(item_id)
2155 .into_iter()
2156 .map(|importable| importable.path.components.into_iter().join("::"))
2157 .collect();
2158
2159 assert!(
2160 actual_items.is_empty(),
2161 "expected no importable item names but found {actual_items:?}"
2162 );
2163 }
2164 }
2165
2166 #[test]
2175 #[should_panic = "expected no importable item names but found \
2176 [\"visibility_modifier_causes_shadowing::Foo\"]"]
2177 fn visibility_modifier_causes_shadowing() {
2178 let test_crate = "visibility_modifier_causes_shadowing";
2179
2180 let rustdoc = load_pregenerated_rustdoc(test_crate);
2181 let indexed_crate = IndexedCrate::new(&rustdoc);
2182
2183 let item_id_candidates = rustdoc
2184 .index
2185 .iter()
2186 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2187 .collect_vec();
2188 if item_id_candidates.len() != 3 {
2189 panic!(
2190 "Expected to find exactly 3 items with name \
2191 Foo, but found these matching IDs: {item_id_candidates:?}"
2192 );
2193 }
2194
2195 for item_id in item_id_candidates {
2196 let actual_items: Vec<_> = indexed_crate
2197 .publicly_importable_names(item_id)
2198 .into_iter()
2199 .map(|importable| importable.path.components.into_iter().join("::"))
2200 .collect();
2201
2202 assert!(
2203 actual_items.is_empty(),
2204 "expected no importable item names but found {actual_items:?}"
2205 );
2206 }
2207 }
2208
2209 #[test]
2210 fn visibility_modifier_avoids_shadowing() {
2211 let test_crate = "visibility_modifier_avoids_shadowing";
2212
2213 let rustdoc = load_pregenerated_rustdoc(test_crate);
2214 let indexed_crate = IndexedCrate::new(&rustdoc);
2215
2216 let item_id_candidates = rustdoc
2217 .index
2218 .iter()
2219 .filter_map(|(id, item)| (item.name.as_deref() == Some("Foo")).then_some(id))
2220 .collect_vec();
2221 if item_id_candidates.len() != 3 {
2222 panic!(
2223 "Expected to find exactly 3 items with name \
2224 Foo, but found these matching IDs: {item_id_candidates:?}"
2225 );
2226 }
2227
2228 for item_id in item_id_candidates {
2229 let actual_items: Vec<_> = indexed_crate
2230 .publicly_importable_names(item_id)
2231 .into_iter()
2232 .map(|importable| importable.path.components.into_iter().join("::"))
2233 .collect();
2234
2235 if rustdoc.index[item_id].visibility == Visibility::Public {
2236 assert_eq!(
2237 vec!["visibility_modifier_avoids_shadowing::Foo"],
2238 actual_items,
2239 );
2240 } else {
2241 assert!(
2242 actual_items.is_empty(),
2243 "expected no importable item names but found {actual_items:?}"
2244 );
2245 }
2246 }
2247 }
2248
2249 #[test]
2250 fn glob_vs_glob_shadowing() {
2251 let test_crate = "glob_vs_glob_shadowing";
2252
2253 let expected_items = btreemap! {
2254 "Foo" => (2, btreeset![]),
2255 "Bar" => (1, btreeset![
2256 "glob_vs_glob_shadowing::Bar",
2257 ]),
2258 "Baz" => (1, btreeset![
2259 "glob_vs_glob_shadowing::Baz",
2260 ]),
2261 };
2262
2263 assert_duplicated_exported_items_match(test_crate, &expected_items);
2264 }
2265
2266 #[test]
2267 fn glob_vs_glob_shadowing_downstream() {
2268 let test_crate = "glob_vs_glob_shadowing_downstream";
2269
2270 let expected_items = btreemap! {
2271 "Foo" => (3, btreeset![]),
2272 "Bar" => (1, btreeset![
2273 "glob_vs_glob_shadowing_downstream::second::Bar",
2274 ]),
2275 };
2276
2277 assert_duplicated_exported_items_match(test_crate, &expected_items);
2278 }
2279
2280 #[test]
2281 fn glob_vs_glob_no_shadowing_for_same_item() {
2282 let test_crate = "glob_vs_glob_no_shadowing_for_same_item";
2283
2284 let expected_items = btreemap! {
2285 "Foo" => btreeset![
2286 "glob_vs_glob_no_shadowing_for_same_item::Foo",
2287 ],
2288 };
2289
2290 assert_exported_items_match(test_crate, &expected_items);
2291 }
2292
2293 #[test]
2294 fn glob_vs_glob_no_shadowing_for_same_renamed_item() {
2295 let test_crate = "glob_vs_glob_no_shadowing_for_same_renamed_item";
2296
2297 let expected_items = btreemap! {
2298 "Bar" => btreeset![
2299 "glob_vs_glob_no_shadowing_for_same_renamed_item::Foo",
2300 ],
2301 };
2302
2303 assert_exported_items_match(test_crate, &expected_items);
2304 }
2305
2306 #[test]
2307 fn glob_vs_glob_no_shadowing_for_same_multiply_renamed_item() {
2308 let test_crate = "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item";
2309
2310 let expected_items = btreemap! {
2311 "Bar" => btreeset![
2312 "glob_vs_glob_no_shadowing_for_same_multiply_renamed_item::Foo",
2313 ],
2314 };
2315
2316 assert_exported_items_match(test_crate, &expected_items);
2317 }
2318
2319 #[test]
2320 fn reexport_consts_and_statics() {
2321 let test_crate = "reexport_consts_and_statics";
2322 let expected_items = btreemap! {
2323 "FIRST" => btreeset![
2324 "reexport_consts_and_statics::FIRST",
2325 "reexport_consts_and_statics::inner::FIRST",
2326 ],
2327 "SECOND" => btreeset![
2328 "reexport_consts_and_statics::SECOND",
2329 "reexport_consts_and_statics::inner::SECOND",
2330 ],
2331 };
2332
2333 assert_exported_items_match(test_crate, &expected_items);
2334 }
2335
2336 #[test]
2337 fn reexport_as_underscore() {
2338 let test_crate = "reexport_as_underscore";
2339 let expected_items = btreemap! {
2340 "Struct" => btreeset![
2341 "reexport_as_underscore::Struct",
2342 ],
2343 "Trait" => btreeset![],
2344 "hidden" => btreeset![],
2345 "UnderscoreImported" => btreeset![],
2346 };
2347
2348 assert_exported_items_match(test_crate, &expected_items);
2349 }
2350
2351 #[test]
2352 fn nested_reexport_as_underscore() {
2353 let test_crate = "nested_reexport_as_underscore";
2354 let expected_items = btreemap! {
2355 "Trait" => btreeset![], };
2357
2358 assert_exported_items_match(test_crate, &expected_items);
2359 }
2360
2361 #[test]
2362 fn overlapping_reexport_as_underscore() {
2363 let test_crate = "overlapping_reexport_as_underscore";
2364
2365 let rustdoc = load_pregenerated_rustdoc(test_crate);
2366 let indexed_crate = IndexedCrate::new(&rustdoc);
2367
2368 let item_id_candidates = rustdoc
2369 .index
2370 .iter()
2371 .filter_map(|(id, item)| (item.name.as_deref() == Some("Example")).then_some(id))
2372 .collect_vec();
2373 if item_id_candidates.len() != 2 {
2374 panic!(
2375 "Expected to find exactly 2 items with name \
2376 Example, but found these matching IDs: {item_id_candidates:?}"
2377 );
2378 }
2379
2380 for item_id in item_id_candidates {
2381 let importable_paths: Vec<_> = indexed_crate
2382 .publicly_importable_names(item_id)
2383 .into_iter()
2384 .map(|importable| importable.path.components.into_iter().join("::"))
2385 .collect();
2386
2387 match &rustdoc.index[item_id].inner {
2388 ItemEnum::Struct(..) => {
2389 assert_eq!(
2390 vec!["overlapping_reexport_as_underscore::Example"],
2391 importable_paths,
2392 );
2393 }
2394 ItemEnum::Trait(..) => {
2395 assert!(
2396 importable_paths.is_empty(),
2397 "expected no importable item names but found {importable_paths:?}"
2398 );
2399 }
2400 _ => unreachable!(
2401 "unexpected item for ID {item_id:?}: {:?}",
2402 rustdoc.index[item_id]
2403 ),
2404 }
2405 }
2406 }
2407
2408 #[test]
2409 fn reexport_declarative_macro() {
2410 let test_crate = "reexport_declarative_macro";
2411 let expected_items = btreemap! {
2412 "top_level_exported" => btreeset![
2413 "reexport_declarative_macro::top_level_exported",
2414 ],
2415 "private_mod_exported" => btreeset![
2416 "reexport_declarative_macro::private_mod_exported",
2417 ],
2418 "top_level_reexported" => btreeset![
2419 "reexport_declarative_macro::top_level_reexported",
2420 "reexport_declarative_macro::macros::top_level_reexported",
2421 "reexport_declarative_macro::reexports::top_level_reexported",
2422 "reexport_declarative_macro::glob_reexports::top_level_reexported",
2423 ],
2424 "private_mod_reexported" => btreeset![
2425 "reexport_declarative_macro::private_mod_reexported",
2426 "reexport_declarative_macro::macros::private_mod_reexported",
2427 "reexport_declarative_macro::reexports::private_mod_reexported",
2428 "reexport_declarative_macro::glob_reexports::private_mod_reexported",
2429 ],
2430 "top_level_not_exported" => btreeset![],
2431 "private_mod_not_exported" => btreeset![],
2432 };
2433
2434 assert_exported_items_match(test_crate, &expected_items);
2435 }
2436 }
2437}