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