Skip to content

Commit 5dec8df

Browse files
authored
Rollup merge of #110631 - notriddle:notriddle/impl-trait-cycle, r=GuillaumeGomez
rustdoc: catch and don't blow up on impl Trait cycles Fixes #110629 An odd feature of Rust is that `Foo` is invalid, but `Bar` is okay: type Foo<'a, 'b> = Box<dyn PartialEq<Foo<'a, 'b>>>; type Bar<'a, 'b> = impl PartialEq<Bar<'a, 'b>>; To get it right, track every time rustdoc descends into a type alias, so if it shows up twice, it can be write the path instead of infinitely expanding it.
2 parents b64d4c2 + b1d0827 commit 5dec8df

6 files changed

+127
-19
lines changed

src/librustdoc/clean/mod.rs

+41-18
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,9 @@ fn maybe_expand_private_type_alias<'tcx>(
15291529
let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None };
15301530
// Substitute private type aliases
15311531
let def_id = def_id.as_local()?;
1532-
let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id()) {
1532+
let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id())
1533+
&& !cx.current_type_aliases.contains_key(&def_id.to_def_id())
1534+
{
15331535
&cx.tcx.hir().expect_item(def_id).kind
15341536
} else {
15351537
return None;
@@ -1609,7 +1611,7 @@ fn maybe_expand_private_type_alias<'tcx>(
16091611
}
16101612
}
16111613

1612-
Some(cx.enter_alias(substs, |cx| clean_ty(ty, cx)))
1614+
Some(cx.enter_alias(substs, def_id.to_def_id(), |cx| clean_ty(ty, cx)))
16131615
}
16141616

16151617
pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type {
@@ -1700,7 +1702,7 @@ fn normalize<'tcx>(
17001702
pub(crate) fn clean_middle_ty<'tcx>(
17011703
bound_ty: ty::Binder<'tcx, Ty<'tcx>>,
17021704
cx: &mut DocContext<'tcx>,
1703-
def_id: Option<DefId>,
1705+
parent_def_id: Option<DefId>,
17041706
) -> Type {
17051707
let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty);
17061708
match *bound_ty.skip_binder().kind() {
@@ -1830,7 +1832,9 @@ pub(crate) fn clean_middle_ty<'tcx>(
18301832
Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None)).collect())
18311833
}
18321834

1833-
ty::Alias(ty::Projection, ref data) => clean_projection(bound_ty.rebind(*data), cx, def_id),
1835+
ty::Alias(ty::Projection, ref data) => {
1836+
clean_projection(bound_ty.rebind(*data), cx, parent_def_id)
1837+
}
18341838

18351839
ty::Param(ref p) => {
18361840
if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) {
@@ -1841,15 +1845,30 @@ pub(crate) fn clean_middle_ty<'tcx>(
18411845
}
18421846

18431847
ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => {
1844-
// Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
1845-
// by looking up the bounds associated with the def_id.
1846-
let bounds = cx
1847-
.tcx
1848-
.explicit_item_bounds(def_id)
1849-
.subst_iter_copied(cx.tcx, substs)
1850-
.map(|(bound, _)| bound)
1851-
.collect::<Vec<_>>();
1852-
clean_middle_opaque_bounds(cx, bounds)
1848+
// If it's already in the same alias, don't get an infinite loop.
1849+
if cx.current_type_aliases.contains_key(&def_id) {
1850+
let path =
1851+
external_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(substs));
1852+
Type::Path { path }
1853+
} else {
1854+
*cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
1855+
// Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
1856+
// by looking up the bounds associated with the def_id.
1857+
let bounds = cx
1858+
.tcx
1859+
.explicit_item_bounds(def_id)
1860+
.subst_iter_copied(cx.tcx, substs)
1861+
.map(|(bound, _)| bound)
1862+
.collect::<Vec<_>>();
1863+
let ty = clean_middle_opaque_bounds(cx, bounds);
1864+
if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
1865+
*count -= 1;
1866+
if *count == 0 {
1867+
cx.current_type_aliases.remove(&def_id);
1868+
}
1869+
}
1870+
ty
1871+
}
18531872
}
18541873

18551874
ty::Closure(..) => panic!("Closure"),
@@ -2229,13 +2248,17 @@ fn clean_maybe_renamed_item<'tcx>(
22292248
generics: clean_generics(ty.generics, cx),
22302249
}),
22312250
ItemKind::TyAlias(hir_ty, generics) => {
2251+
*cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
22322252
let rustdoc_ty = clean_ty(hir_ty, cx);
22332253
let ty = clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
2234-
TypedefItem(Box::new(Typedef {
2235-
type_: rustdoc_ty,
2236-
generics: clean_generics(generics, cx),
2237-
item_type: Some(ty),
2238-
}))
2254+
let generics = clean_generics(generics, cx);
2255+
if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
2256+
*count -= 1;
2257+
if *count == 0 {
2258+
cx.current_type_aliases.remove(&def_id);
2259+
}
2260+
}
2261+
TypedefItem(Box::new(Typedef { type_: rustdoc_ty, generics, item_type: Some(ty) }))
22392262
}
22402263
ItemKind::Enum(ref def, generics) => EnumItem(Enum {
22412264
variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(),

src/librustdoc/core.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub(crate) struct DocContext<'tcx> {
4646
// for expanding type aliases at the HIR level:
4747
/// Table `DefId` of type, lifetime, or const parameter -> substituted type, lifetime, or const
4848
pub(crate) substs: DefIdMap<clean::SubstParam>,
49+
pub(crate) current_type_aliases: DefIdMap<usize>,
4950
/// Table synthetic type parameter for `impl Trait` in argument position -> bounds
5051
pub(crate) impl_trait_bounds: FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>,
5152
/// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`.
@@ -82,13 +83,25 @@ impl<'tcx> DocContext<'tcx> {
8283

8384
/// Call the closure with the given parameters set as
8485
/// the substitutions for a type alias' RHS.
85-
pub(crate) fn enter_alias<F, R>(&mut self, substs: DefIdMap<clean::SubstParam>, f: F) -> R
86+
pub(crate) fn enter_alias<F, R>(
87+
&mut self,
88+
substs: DefIdMap<clean::SubstParam>,
89+
def_id: DefId,
90+
f: F,
91+
) -> R
8692
where
8793
F: FnOnce(&mut Self) -> R,
8894
{
8995
let old_substs = mem::replace(&mut self.substs, substs);
96+
*self.current_type_aliases.entry(def_id).or_insert(0) += 1;
9097
let r = f(self);
9198
self.substs = old_substs;
99+
if let Some(count) = self.current_type_aliases.get_mut(&def_id) {
100+
*count -= 1;
101+
if *count == 0 {
102+
self.current_type_aliases.remove(&def_id);
103+
}
104+
}
92105
r
93106
}
94107

@@ -327,6 +340,7 @@ pub(crate) fn run_global_ctxt(
327340
external_traits: Default::default(),
328341
active_extern_traits: Default::default(),
329342
substs: Default::default(),
343+
current_type_aliases: Default::default(),
330344
impl_trait_bounds: Default::default(),
331345
generated_synthetics: Default::default(),
332346
auto_traits,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
type Bar<'a, 'b> = Box<dyn PartialEq<Bar<'a, 'b>>>;
2+
//~^ ERROR cycle detected when expanding type alias
3+
4+
fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
5+
Box::new(i)
6+
}
7+
8+
fn main() {
9+
let meh = 42;
10+
let muh = 42;
11+
assert!(bar(&meh) == bar(&muh));
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
error[E0391]: cycle detected when expanding type alias `Bar`
2+
--> $DIR/issue-110629-private-type-cycle-dyn.rs:1:38
3+
|
4+
LL | type Bar<'a, 'b> = Box<dyn PartialEq<Bar<'a, 'b>>>;
5+
| ^^^^^^^^^^^
6+
|
7+
= note: ...which immediately requires expanding type alias `Bar` again
8+
= note: type aliases cannot be recursive
9+
= help: consider using a struct, enum, or union instead to break the cycle
10+
= help: see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information
11+
note: cycle used when collecting item types in top-level module
12+
--> $DIR/issue-110629-private-type-cycle-dyn.rs:1:1
13+
|
14+
LL | / type Bar<'a, 'b> = Box<dyn PartialEq<Bar<'a, 'b>>>;
15+
LL | |
16+
LL | |
17+
LL | | fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
18+
... |
19+
LL | | assert!(bar(&meh) == bar(&muh));
20+
LL | | }
21+
| |_^
22+
23+
error: aborting due to previous error
24+
25+
For more information about this error, try `rustc --explain E0391`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check-pass
2+
3+
#![feature(type_alias_impl_trait)]
4+
5+
type Bar<'a, 'b> = impl PartialEq<Bar<'a, 'b>> + std::fmt::Debug;
6+
7+
fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
8+
i
9+
}
10+
11+
fn main() {
12+
let meh = 42;
13+
let muh = 42;
14+
assert_eq!(bar(&meh), bar(&muh));
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// compile-flags: --document-private-items
2+
3+
#![feature(type_alias_impl_trait)]
4+
5+
type Bar<'a, 'b> = impl PartialEq<Bar<'a, 'b>> + std::fmt::Debug;
6+
7+
// @has issue_110629_private_type_cycle/type.Bar.html
8+
// @has - '//pre[@class="rust item-decl"]' \
9+
// "pub(crate) type Bar<'a, 'b> = impl PartialEq<Bar<'a, 'b>> + Debug;"
10+
11+
fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
12+
i
13+
}
14+
15+
fn main() {
16+
let meh = 42;
17+
let muh = 42;
18+
assert_eq!(bar(&meh), bar(&muh));
19+
}

0 commit comments

Comments
 (0)