Rustdoc hangs with this recursive generic type
Open
Nobody has claimed this yet.
A-synthetic-impls
C-bug
fixed-by-next-solver
I-hang
I-prioritize
T-rustdoc
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Code
pub struct Recursive<T> {
child: Box<Recursive<Box<T>>>,
data: T,
}
Reproduction Steps
Run cargo doc, hangs.
Expected Outcome
Not hang
Actual Output
With RUSTDOC_LOG="rustdoc=debug"
warning: some trace filter directives would enable traces that are disabled statically
| `rustdoc=debug` would enable the DEBUG level for the `rustdoc` target
= note: the static max level is `info`
= help: to enable DEBUG logging, remove the `max_level_info` feature from the `tracing` crate
DEBUG rustdoc::config merge_doctests: Auto
INFO rustdoc starting to run rustc
DEBUG rustdoc::clean trait_ref=Binder { value: <Self as std::marker::MetaSized>, bound_vars: [] }
DEBUG rustdoc::visit_ast Going through module Mod { spans: ModSpans { inner_span: src\lib.rs:1:1: 4:2 (#0), inject_use_span: no-location (#0) }, item_ids: [ItemId { owner_id: DefId(0:1 ~ rustdoc_hang_repro[5a4c]::std) }, ItemId { owner_id: DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}) }, ItemId { owner_id: DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive) }] }
DEBUG rustdoc::visit_ast visiting item Item { owner_id: DefId(0:1 ~ rustdoc_hang_repro[5a4c]::std), kind: ExternCrate(None, std#1), span: no-location (#1), vis_span: no-location (#1), has_delayed_lints: false, eii: false }
DEBUG rustdoc::visit_ast visiting item Item { owner_id: DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive), kind: Struct(Recursive#0, Generics { params: [GenericParam { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).1), def_id: DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T), name: Plain(T#0), span: src\lib.rs:1:22: 1:23 (#0), pure_wrt_drop: false, kind: Type { default: None, synthetic: false }, colon_span: None, source: Generics }], predicates: [], has_where_clause_predicates: false, where_clause_span: src\lib.rs:1:24: 1:24 (#0), span: src\lib.rs:1:21: 1:24 (#0) }, Struct { fields: [FieldDef { span: src\lib.rs:2:5: 2:34 (#0), vis_span: src\lib.rs:2:5: 2:5 (#0), ident: child#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).10), def_id: DefId(0:5 ~ rustdoc_hang_repro[5a4c]::Recursive::child), ty: Ty { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).2), span: src\lib.rs:2:12: 2:34 (#0), kind: Path(Resolved(None, Path { span: src\lib.rs:2:12: 2:34 (#0), res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { ident: Box#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).9), res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).3), span: src\lib.rs:2:16: 2:33 (#0), kind: Path(Resolved(None, Path { span: src\lib.rs:2:16: 2:33 (#0), res: Def(Struct, DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)), segments: [PathSegment { ident: Recursive#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).8), res: Def(Struct, DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).4), span: src\lib.rs:2:26: 2:32 (#0), kind: Path(Resolved(None, Path { span: src\lib.rs:2:26: 2:32 (#0), res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { ident: Box#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).7), res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).5), span: src\lib.rs:2:30: 2:31 (#0), kind: Path(Resolved(None, Path { span: src\lib.rs:2:30: 2:31 (#0), res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), segments: [PathSegment { ident: T#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).6), res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), args: None, infer_args: false }] })) })], constraints: [], parenthesized: No, span_ext: src\lib.rs:2:29: 2:32 (#0) }), infer_args: false }] })) })], constraints: [], parenthesized: No, span_ext: src\lib.rs:2:25: 2:33 (#0) }), infer_args: false }] })) })], constraints: [], parenthesized: No, span_ext: src\lib.rs:2:15: 2:34 (#0) }), infer_args: false }] })) }, safety: Safe, default: None }, FieldDef { span: src\lib.rs:3:5: 3:12 (#0), vis_span: src\lib.rs:3:5: 3:5 (#0), ident: data#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).13), def_id: DefId(0:6 ~ rustdoc_hang_repro[5a4c]::Recursive::data), ty: Ty { hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).11), span: src\lib.rs:3:11: 3:12 (#0), kind: Path(Resolved(None, Path { span: src\lib.rs:3:11: 3:12 (#0), res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), segments: [PathSegment { ident: T#0, hir_id: HirId(DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive).12), res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), args: None, infer_args: false }] })) }, safety: Safe, default: None }], recovered: No }), span: src\lib.rs:1:1: 4:2 (#0), vis_span: src\lib.rs:1:1: 1:4 (#0), has_delayed_lints: false, eii: false }
DEBUG rustdoc::visit_ast visiting item Item { owner_id: DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}), kind: Use(Path { span: no-location (#1), res: PerNS { value_ns: Some(Err), type_ns: Some(Err), macro_ns: Some(Err) }, segments: [PathSegment { ident: std#1, hir_id: HirId(DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}).1), res: Err, args: None, infer_args: false }, PathSegment { ident: prelude#1, hir_id: HirId(DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}).2), res: Err, args: None, infer_args: false }, PathSegment { ident: rust_2024#1, hir_id: HirId(DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}).3), res: Err, args: None, infer_args: false }] }, Glob), span: no-location (#1), vis_span: no-location (#1), has_delayed_lints: false, eii: false }
DEBUG rustdoc::visit_ast Leaving module Mod { spans: ModSpans { inner_span: src\lib.rs:1:1: 4:2 (#0), inject_use_span: no-location (#0) }, item_ids: [ItemId { owner_id: DefId(0:1 ~ rustdoc_hang_repro[5a4c]::std) }, ItemId { owner_id: DefId(0:2 ~ rustdoc_hang_repro[5a4c]::{use#0}) }, ItemId { owner_id: DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive) }] }
DEBUG rustdoc::clean::utils resolve_type(Path { res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), segments: [PathSegment { name: "T", args: AngleBracketed { args: [], constraints: [] } }] })
DEBUG rustdoc::clean::utils resolve_type(Path { res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { name: "Box", args: AngleBracketed { args: [Type(Generic("T"))], constraints: [] } }] })
DEBUG rustdoc::clean::utils register_res(Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)))
DEBUG rustdoc::clean::utils resolve_type(Path { res: Def(Struct, DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)), segments: [PathSegment { name: "Recursive", args: AngleBracketed { args: [Type(Path { path: Path { res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { name: "Box", args: AngleBracketed { args: [Type(Generic("T"))], constraints: [] } }] } })], constraints: [] } }] })
DEBUG rustdoc::clean::utils register_res(Def(Struct, DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)))
DEBUG rustdoc::clean::utils resolve_type(Path { res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { name: "Box", args: AngleBracketed { args: [Type(Path { path: Path { res: Def(Struct, DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)), segments: [PathSegment { name: "Recursive", args: AngleBracketed { args: [Type(Path { path: Path { res: Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)), segments: [PathSegment { name: "Box", args: AngleBracketed { args: [Type(Generic("T"))], constraints: [] } }] } })], constraints: [] } }] } })], constraints: [] } }] })
DEBUG rustdoc::clean::utils register_res(Def(Struct, DefId(3:669 ~ alloc[61df]::boxed::Box)))
DEBUG rustdoc::clean::utils resolve_type(Path { res: Def(TyParam, DefId(0:4 ~ rustdoc_hang_repro[5a4c]::Recursive::T)), segments: [PathSegment { name: "T", args: AngleBracketed { args: [], constraints: [] } }] })
INFO rustdoc::core Executing passes
DEBUG rustdoc::core running pass collect-trait-impls
rustdoc::clean::auto_trait::synthesize_auto_trait_impls item_def_id=DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)
rustdoc::clean::auto_trait::synthesize_auto_trait_impls item_def_id=DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive)
rustdoc::clean::auto_trait::synthesize_auto_trait_impl ty=Recursive<T/#0>, trait_def_id=DefId(2:40342 ~ core[3b42]::marker::Send), typing_env=TypingEnv { typing_mode: TypingModeEqWrapper(Analysis { defining_opaque_types_and_generators: [] }), param_env: ParamEnv { caller_bounds: [Binder { value: TraitPredicate(<T as std::marker::Sized>, polarity:Positive), bound_vars: [] }] } }, item_def_id=DefId(0:3 ~ rustdoc_hang_repro[5a4c]::Recursive), discard_positive_impls=No
Version
1.97.1
Additional Details
With RUSTFLAGS="-Znext-solver=globally", cargo +nightly doc also hangs
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the hang from the recursive type in src/lib.rs with cargo doc, then inspect the rustdoc::clean::auto_trait::synthesize_auto_trait_impls trace where processing stops. Compare normal and -Znext-solver=globally runs; done means rustdoc completes instead of hanging for the provided example.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100