Skip to content

infinite recursion with TAIT and replace_opaque_types_with_inference_vars in project #109268

Open
@lcnr

Description

@lcnr
#![feature(type_alias_impl_trait)]

type Foo<'a> = impl Fn() -> Foo<'a>;

fn crash<'a>(_: &'a (), x: Foo<'a>) -> Foo<'a> {
    x
}

fn main() {}

this causes rustc to freeze. This is similar to

#![feature(type_alias_impl_trait)]
type Foo = impl Fn() -> Foo;
fn foo() -> Foo {
//~^ ERROR: overflow evaluating the requirement
foo
}
fn main() {}

The only reason that this test does not hang is the following hack in fulfillment

if obligation.predicate.is_global() {
// no type variables present, can use evaluation for better caching.
// FIXME: consider caching errors too.
if self.selcx.infcx.predicate_must_hold_considering_regions(obligation) {
if let Some(key) = ProjectionCacheKey::from_poly_projection_predicate(
&mut self.selcx,
project_obligation.predicate,
) {
// If `predicate_must_hold_considering_regions` succeeds, then we've
// evaluated all sub-obligations. We can therefore mark the 'root'
// obligation as complete, and skip evaluating sub-obligations.
self.selcx
.infcx
.inner
.borrow_mut()
.projection_cache()
.complete(key, EvaluationResult::EvaluatedToOk);
}
return ProcessResult::Changed(vec![]);
} else {
debug!("Does NOT hold: {:?}", obligation);
}
}

because of this hack we only try to prove <Foo as FnOnce<()>>::Output == Foo using evaluate_obligation which uses DefiningAnchor::Bubble instead of the DefiningAnchor::Bind used by typeck and fulfill directly.

The reason this breaks when using DefiningAnchor::Bind is the following:

we call project_and_unify_type for <Foo as FnOnce<()>>::Output == Foo which normalizes <Foo as FnOnce<()>>::Output to Foo.

The issue is that we then replace Foo with a new inference variable (if we use DefiningAnchor::Bind) ?n and add the item bounds of Foo as obligations on that new inference variable:

let InferOk { value: actual, obligations: new } =
selcx.infcx.replace_opaque_types_with_inference_vars(
actual,
obligation.cause.body_id,
obligation.cause.span,
obligation.param_env,
);

This adds a new obligation <?n as FnOnce<()>>::Output == Foo to the fulfillment context, even though ?n was already constrained to Foo again. The easiest fix is to resolve inference variables in obligations before adding them to the fulfillment context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.E-needs-bisectionCall for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustcF-type_alias_impl_trait`#[feature(type_alias_impl_trait)]`S-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Status

    Can do after stabilization

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions