Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler hangs on some types threading lifetimes through a carrier trait #102966

Open
cormacrelf opened this issue Oct 12, 2022 · 5 comments
Open
Labels
A-implied-bounds Area: Implied bounds / inferred outlives-bounds C-bug Category: This is a bug. I-hang Issue: The compiler never terminates, due to infinite loops, deadlock, livelock, etc. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@cormacrelf
Copy link
Contributor

cormacrelf commented Oct 12, 2022

The following code hangs rustc stable 1.64, beta and nightly. It sits on 100% CPU doing rustc_middle things inside a rustc_typeck::outlives::implicit_infer::infer_predicates > insert_outlives_predicate operation. (Edit: by "rustc_middle things" I mean <rustc_middle::ty::TyS as core::cmp::Ord>::cmp.) I've let it run for at least half an hour to no avail.

struct Node<'a, G: NodeGenerics<'a>> {
    kind: Kind<'a, G>,
}

enum Kind<'a, G: NodeGenerics<'a>> {
    Empty,
    Var(Var<'a, G::R>),
}

trait NodeGenerics<'a> {
    type R: 'a;
}

struct RGen<T>(std::marker::PhantomData<T>);
impl<'a, T: 'a> NodeGenerics<'a> for RGen<T> {
    type R = T;
}

struct Var<'a, R: 'a> {
    node: Box<Node<'a, RGen<R>>>,
    _phantom: std::marker::PhantomData<R>,
}

You can change the code in a couple of small ways such that it won't hang.

  1. Remove the "carrier trait" NodeGenerics, replace it with T: 'a and use T instead of G::R. You would think that adding a carrier trait with the same bounds would not really change anything. But it does. (In my code, NodeGenerics has a bunch of different types attached to it, which are used in many different arms of Kind. It makes everything a lot neater.)
  2. Changing RGen<T> to just RGen and its implementation of NodeGenerics to have a constant type R = ().
  3. Don't use lifetimes at all, put 'static bounds in everywhere instead, i.e. make NodeGenerics take type R: 'static.

I don't know why it's hanging on this, but rustc should never hang, so there you go. Bug report filed.

@Rageking8
Copy link
Contributor

@rustbot label +C-bug +I-hang +T-compiler

@rustbot rustbot added C-bug Category: This is a bug. I-hang Issue: The compiler never terminates, due to infinite loops, deadlock, livelock, etc. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 12, 2022
@Rageking8
Copy link
Contributor

Using godbolt the first rustc version that starts hanging is 1.31. Not sure if this info is of any use.

@cormacrelf
Copy link
Contributor Author

cormacrelf commented Oct 12, 2022

I demangled a trace a bit, and, if you imagine the left margin steadily increasing here as they're all nested, it looks like this but goes on a lot longer:

Demangled trace
rustc_typeck[a73e30b7fe2aef56]::outlives::inferred_outlives_crate
rustc_typeck[a73e30b7fe2aef56]::outlives::implicit_infer::infer_predicates
rustc_typeck[a73e30b7fe2aef56]::outlives::utils::insert_outlives_predicate
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp
<rustc_middle[44a83f749f1857ae]::ty::sty::ProjectionTy as core[f7754f278a0185a3]::cmp::Ord>::cmp

So we are maybe looking at a huge TyS object constructed recursively. It may be at least 1,110 levels deep, the bottom level happens to be a <rustc_middle[44a83f749f1857ae]::ty::TyS as core[f7754f278a0185a3]::cmp::Ord>::cmp call that actually returns, or maybe the process sample (Activity Monitor on macOS) just didn't see anything deeper. A ProjectionTy here is an associated type on a trait. I think that's where the problem is because of this not happening when the carrier trait is not used to impose the lifetime bound.

@cormacrelf
Copy link
Contributor Author

OK. Basically yep, it's a huge type object. When inferring outlives-predicates, we are stacking many many associated types on top of each other, producing types like <RGen<<RGen<<RGen<<RGen<<G as NodeGenerics<'a>>::R> as NodeGenerics<'a>>::R> as NodeGenerics<'a>>::R> as NodeGenerics<'a>>::R> as NodeGenerics<'a>>::R.

Here's how this is formed, made from some extra debug-logging in the rustc_hir_analysis::outlives module, and renaming some of the type parameters in the code:

------------------------ROUND 1------------------------------
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:7 ~ issue_102966[6f77]::Kind) })
    (rebound/substituted = OutlivesPredicate(<KG as NodeGenerics<'kind>>::R, ReEarlyBound(0, 'kind)))
------------------------ROUND 2------------------------------
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:3 ~ issue_102966[6f77]::Node) })
    unsubstituted pred   OutlivesPredicate(<KG as NodeGenerics<'kind>>::R, ReEarlyBound(0, 'kind))
    (rebound/substituted OutlivesPredicate(<NG as NodeGenerics<'node>>::R, ReEarlyBound(0, 'node)))
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:26 ~ issue_102966[6f77]::Var) })
    unsubstituted pred   OutlivesPredicate(<NG as NodeGenerics<'node>>::R, ReEarlyBound(0, 'node))
    (rebound/substituted OutlivesPredicate(<RGen<R> as NodeGenerics<'var>>::R, ReEarlyBound(0, 'var)))
------------------------ROUND 3------------------------------
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:7 ~ issue_102966[6f77]::Kind) })
    unsubstituted pred   OutlivesPredicate(<RGen<R> as NodeGenerics<'var>>::R, ReEarlyBound(0, 'var))
    (rebound/substituted OutlivesPredicate(<RGen<<KG as NodeGenerics<'kind>>::R> as NodeGenerics<'kind>>::R, ReEarlyBound(0, 'kind)))
------------------------ROUND 4------------------------------
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:3 ~ issue_102966[6f77]::Node) })
    unsubstituted pred   OutlivesPredicate(<RGen<<KG as NodeGenerics<'kind>>::R> as NodeGenerics<'kind>>::R, ReEarlyBound(0, 'kind))
    (rebound/substituted OutlivesPredicate(<RGen<<NG as NodeGenerics<'node>>::R> as NodeGenerics<'node>>::R, ReEarlyBound(0, 'node)))
InferVisitor::visit_item(item=OwnerId { def_id: DefId(0:26 ~ issue_102966[6f77]::Var) })
    unsubstituted pred   OutlivesPredicate(<RGen<<NG as NodeGenerics<'node>>::R> as NodeGenerics<'node>>::R, ReEarlyBound(0, 'node))
    (rebound/substituted OutlivesPredicate(<RGen<<RGen<R> as NodeGenerics<'var>>::R> as NodeGenerics<'var>>::R, ReEarlyBound(0, 'var)))

As you can see, it alternates between rounds where Kind gets a new predicate, and rounds where Node does and then so does Var. The RGen<RGen< stacking happens when <NG as NodeGenerics<'node>::R is substituted while inferring bounds for Var (this is visible in the last two rounds). If you trace backwards you can see that Node picks up Kind's new bound from the previous round, in which Kind found the stacked RGens from the round before that.

Incidentally, we can make an even smaller repro by deleting Kind:

trait Trait<'a> { type Assoc: 'a; }
struct Node<'node, T: Trait<'node>>(Var<'node, T::Assoc>, Option<T::Assoc>);
struct RGen<R>(std::marker::PhantomData<R>);
impl<'a, R: 'a> Trait<'a> for RGen<R> { type Assoc = R; }
struct Var<'var, R: 'var>(Box<Node<'var, RGen<R>>>);

But essentially what happened here is we tricked the bound inference into recursively substituting a type into itself. It was only possible because we used an associated type, and rustc didn't simplify these (fully specified) associated type projections but rather just used the raw type constructions to check membership in the hashmap of previously added bounds.

There are two issues here:

  1. It shouldn't hang, obviously. So we need to detect cycles like this and break them, somehow.
  2. I think it should compile. During inference for Var, <RGen<R> as NodeGenerics<'var>>::R is R. Rustc hasn't noticed but the implementation of NodeGenerics for RGen just threads R through. It's always the same type. Theoretically, in the inference for Var, should not be blindly inserting that big projection, but rather noticing it already has an R: 'a bound and stopping there.

So: can we "evaluate" the associated type projection? In this case I feel like we can, because RGen exists, there's an implementation impl<'a, T> NodeGenerics<'a> for RGen<T> {...}. Will there be times we can't?

It seems to me a similar feat of inference was done for Sized cycles / infinite type detection. The process of calculating Sized bounds (and some edge cases where you add enough ?Sized to get it all the way to calculating struct layouts) does look through associated types.

trait Trait { type Assoc; }
struct Infinite<T>(<T as Trait>::Assoc);
impl<T> Trait for T { type Assoc = Infinite<T>; } // overflow evaluating the requirement `Infinite<T>: Sized`

@cormacrelf
Copy link
Contributor Author

One more thing, the explicit lifetime bounds on associated type bounds don't seem to be returned by the explicit_predicates_of query, and hence we get this:

DEBUG rustc_hir_analysis::outlives::implicit_infer Projection
DEBUG rustc_hir_analysis::outlives::implicit_infer check_explicit_predicates(def_id=DefId(0:3 ~ issue_102966[6f77]::Trait), substs=[T, ReEarlyBound(0, 'node)], explicit_map=ExplicitPredicatesMap { map: {DefId(0:3 ~ issue_102966[6f77]::Trait): EarlyBinder({}), DefId(5:8243 ~ alloc[3144]::alloc::Global): EarlyBinder({}), DefId(0:20 ~ issue_102966[6f77]::Var): EarlyBinder({OutlivesPredicate(R, ReEarlyBound(0, 'var)): src/test/ui/typeck/issue-102966.rs:10:21: 10:25 (#0)}), DefId(2:3397 ~ core[a4aa]::marker::PhantomData): EarlyBinder({}), DefId(5:447 ~ alloc[3144]::boxed::Box): EarlyBinder({}), DefId(0:12 ~ issue_102966[6f77]::RGen): EarlyBinder({}), DefId(0:6 ~ issue_102966[6f77]::Node): EarlyBinder({}), DefId(2:41190 ~ core[a4aa]::option::Option): EarlyBinder({})} }, required_predicates={OutlivesPredicate(<RGen<<T as Trait<'node>>::Assoc> as Trait<'node>>::Assoc, ReEarlyBound(0, 'node)): src/test/ui/typeck/issue-102966.rs:10:21: 10:25 (#0), OutlivesPredicate(<T as Trait<'node>>::Assoc, ReEarlyBound(0, 'node)): src/test/ui/typeck/issue-102966.rs:10:21: 10:25 (#0)}, ignored_self_ty=None)
DEBUG rustc_hir_analysis::outlives::explicit getting explicit_predicates_of def_id=DefId(0:3 ~ issue_102966[6f77]::Trait)
DEBUG rustc_hir_analysis::outlives::implicit_infer found explicit predicates: EarlyBinder({})

Not sure if this is wrong. Due to

pub(super) fn explicit_predicates_of<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> ty::GenericPredicates<'tcx> {
let def_kind = tcx.def_kind(def_id);
if let DefKind::Trait = def_kind {
// Remove bounds on associated types from the predicates, they will be
// returned by `explicit_item_bounds`.

cormacrelf added a commit to cormacrelf/rust that referenced this issue Oct 20, 2022
@Enselic Enselic added A-lifetimes Area: Lifetimes / regions A-inference Area: Type inference labels May 6, 2024
@fmease fmease added A-implied-bounds Area: Implied bounds / inferred outlives-bounds and removed A-lifetimes Area: Lifetimes / regions A-inference Area: Type inference labels Aug 15, 2024
@fmease fmease added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Mar 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-implied-bounds Area: Implied bounds / inferred outlives-bounds C-bug Category: This is a bug. I-hang Issue: The compiler never terminates, due to infinite loops, deadlock, livelock, etc. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants