diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index 840a8a8682538..28fca4e02a724 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -92,10 +92,14 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { }; visitor.def_effective_visibilities.update_root(); - visitor.set_bindings_effective_visibilities(CRATE_DEF_ID); while visitor.changed { visitor.changed = false; + // The crate root walks in every iteration like any other module + // (`visit_item` re-walks nested modules): `update_decl_chain` visits + // each declaration at most once per walk, so anything it skips in + // one iteration must be re-attempted until nothing changes. + visitor.set_bindings_effective_visibilities(CRATE_DEF_ID); visit::walk_crate(&mut visitor, krate); } visitor.r.effective_visibilities = visitor.def_effective_visibilities; @@ -137,12 +141,29 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { /// Set the given effective visibility level to `Level::Direct` and /// sets the rest of the `use` chain to `Level::Reexported` until /// we hit the actual exported item. - fn update_decl_chain(&mut self, mut decl: Decl<'ra>, mut parent_id: ParentId<'ra>) { + fn update_decl_chain(&mut self, decl: Decl<'ra>, parent_id: ParentId<'ra>) { + self.update_decl_chain_inner(decl, parent_id, &mut Default::default()); + } + + fn update_decl_chain_inner( + &mut self, + mut decl: Decl<'ra>, + mut parent_id: ParentId<'ra>, + visited: &mut FxHashSet>, + ) { let priv_vis = |this: &Self, parent_id, decl| match parent_id { ParentId::Def(_) => this.current_private_vis, ParentId::Import(_) => this.r.private_vis_decl(decl), }; while let DeclKind::Import { source_decl, .. } = decl.kind { + // Ambiguous glob imports can form cyclic reexport chains: following + // `ambiguity_vis_max` below may lead back to a declaration this walk + // has already updated. Visit each declaration at most once + // per walk — any update a skip leaves behind is picked up by the + // next `changed` iteration in `compute_effective_visibilities`. + if !visited.insert(decl) { + return; + } self.update_import(decl, parent_id, priv_vis(self, parent_id, decl)); if let Some(max_vis_decl) = decl.ambiguity_vis_max.get() { // The name is exported with the visibility of the most visible declaration @@ -154,7 +175,7 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { // `ambiguous-import-visibility-globglob-mir.rs`). // This also avoids the most visible import in an ambiguous glob set // being reported as unused. - self.update_decl_chain(max_vis_decl, parent_id); + self.update_decl_chain_inner(max_vis_decl, parent_id, visited); } parent_id = ParentId::Import(decl); decl = source_decl; diff --git a/tests/ui/imports/ambiguous-import-visibility-globglob-cycle.rs b/tests/ui/imports/ambiguous-import-visibility-globglob-cycle.rs new file mode 100644 index 0000000000000..00aa544b13ea0 --- /dev/null +++ b/tests/ui/imports/ambiguous-import-visibility-globglob-cycle.rs @@ -0,0 +1,32 @@ +// Regression test for #160685: ambiguous glob imports whose reexport chains +// form a cycle (`axiomatic` and `own` glob-import each other, and the same +// item also arrives through `orphan`) sent `update_decl_chain` into infinite +// recursion through `ambiguity_vis_max`, overflowing the stack. Each +// declaration is now visited at most once per chain walk; the effective +// visibility fixpoint loop picks up anything a skipped revisit would have +// contributed. Minimized from the `reflect_tools` crate by @theemathas. + +//@ check-pass +//@ edition: 2024 + +pub mod axiomatic { + use super::*; // not pub + pub use own::*; + + pub mod own { + pub use super::*; + pub use orphan::*; + } + + pub mod orphan { + pub use super::private::CollectionDescriptor; + } + + pub mod private { + pub struct CollectionDescriptor; + } +} + +pub use axiomatic::orphan::*; + +fn main() {}