diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 61cd8e46d8816..a5ffca59c6df1 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -465,6 +465,15 @@ where // `PathKind::Inductive`. Keeping them as unknown until we're confident // about this and have an example where it is necessary. GoalSource::AliasBoundConstCondition | GoalSource::AliasWellFormed => PathKind::Unknown, + // While normalizing an opaque, its provisional hidden type is registered + // before we check the goals required by its item bounds. Proving one of + // these bounds may normalize the same opaque again, forming a cycle back + // to that provisional normalization. + // + // Treat crossing this edge as coinductive so that recursive normalization + // can reuse the provisional hidden type. This does not make cycles wholly + // contained in the bound proof coinductive. + GoalSource::OpaqueTypeBound => PathKind::Coinductive, } } @@ -1399,7 +1408,7 @@ where hidden_ty, &mut goals, ); - self.add_goals(GoalSource::AliasWellFormed, goals)?; + self.add_goals(GoalSource::OpaqueTypeBound, goals)?; Ok(()) } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 7f3a993793b49..cc893488cfea7 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -241,6 +241,7 @@ impl<'tcx> BestObligation<'tcx> { GoalSource::ImplWhereBound | GoalSource::AliasBoundConstCondition | GoalSource::AliasWellFormed + | GoalSource::OpaqueTypeBound ) && nested_goal.result().is_err() }, ) @@ -538,7 +539,12 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> { impl_where_bound_count += 1; } (ChildMode::PassThrough, _) - | (_, GoalSource::AliasWellFormed | GoalSource::AliasBoundConstCondition) => { + | ( + _, + GoalSource::AliasWellFormed + | GoalSource::AliasBoundConstCondition + | GoalSource::OpaqueTypeBound, + ) => { obligation = make_obligation(self.obligation.cause.clone()); } } diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 88d2184ed95b2..3475d954ed583 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -438,6 +438,8 @@ pub enum GoalSource { /// 2. for rigid projections's trait goal, /// 3. for GAT where clauses. AliasWellFormed, + /// Predicate required while proving the item bounds of an opaque's hidden type. + OpaqueTypeBound, /// In case normalizing aliases in nested goals cycles, eagerly normalizing these /// aliases in the context of the parent may incorrectly change the cycle kind. /// Normalizing aliases in goals therefore tracks the original path kind for this diff --git a/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.rs b/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.rs new file mode 100644 index 0000000000000..314ec4cf6a0be --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.rs @@ -0,0 +1,37 @@ +//@ compile-flags: -Znext-solver +//@ check-fail + +trait Bound {} + +trait Needs {} + +impl Needs for () {} + +trait Trait { + type Assoc + where + T: Bound; +} + +impl Trait for () { + type Assoc + = () + where + T: Bound; +} + +// Normalizing `Assoc<()>` also requires its own `(): Bound` clause. +// Treating `AliasWellFormed` as coinductive would incorrectly make +// this cycle productive. +impl Bound for () +//~^ ERROR overflow evaluating the requirement `<() as Trait>::Assoc<()> == _` +where + <() as Trait>::Assoc<()>: Needs, +{} + +fn require_bound() {} + +fn main() { + require_bound::<()>(); + //~^ ERROR overflow evaluating the requirement `(): Bound` +} diff --git a/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.stderr b/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.stderr new file mode 100644 index 0000000000000..7e83bf229dd67 --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/alias-well-formed-not-coinductive.stderr @@ -0,0 +1,24 @@ +error[E0275]: overflow evaluating the requirement `<() as Trait>::Assoc<()> == _` + --> $DIR/alias-well-formed-not-coinductive.rs:26:1 + | +LL | / impl Bound for () +LL | | +LL | | where +LL | | <() as Trait>::Assoc<()>: Needs, + | |____________________________________^ + +error[E0275]: overflow evaluating the requirement `(): Bound` + --> $DIR/alias-well-formed-not-coinductive.rs:35:21 + | +LL | require_bound::<()>(); + | ^^ + | +note: required by a bound in `require_bound` + --> $DIR/alias-well-formed-not-coinductive.rs:32:21 + | +LL | fn require_bound() {} + | ^^^^^ required by this bound in `require_bound` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0275`. diff --git a/tests/ui/traits/next-solver/opaques/recursive-tait-bound-generic.rs b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-generic.rs new file mode 100644 index 0000000000000..a86058f6cd9c7 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-generic.rs @@ -0,0 +1,23 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +#![feature(type_alias_impl_trait)] + +pub type Foo = impl PartialEq<(Foo, T)>; + +struct Bar(T); + +impl PartialEq<(Foo, T)> for Bar { + fn eq(&self, _: &(Foo, T)) -> bool { + true + } +} + +#[define_opaque(Foo)] +fn foo(value: T) -> Foo { + Bar(value) +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/opaques/recursive-tait-bound-reveal.rs b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-reveal.rs new file mode 100644 index 0000000000000..1e6818a5b7ed3 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-reveal.rs @@ -0,0 +1,26 @@ +//@ compile-flags: -Znext-solver +//@ check-pass + +#![feature(type_alias_impl_trait)] + +// Checking the opaque's recursive item bound requires normalizing `Foo` +// to its provisional hidden type while selecting the `PartialEq` impl. +// This ensures we do not handle recursive TAIT bounds by suppressing +// normalization of the recursive opaque. + +pub type Foo = impl PartialEq<(Foo, i32)>; + +#[define_opaque(Foo)] +fn foo() -> Foo { + Bar +} + +struct Bar; + +impl PartialEq<(Bar, i32)> for Bar { + fn eq(&self, _: &(Bar, i32)) -> bool { + true + } +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.rs b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.rs new file mode 100644 index 0000000000000..e2354cce46053 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.rs @@ -0,0 +1,25 @@ +//@ compile-flags: -Znext-solver +//@ check-fail + +#![feature(type_alias_impl_trait)] + +trait Trait {} + +type Foo = impl Trait; + +struct Bar(T); + +// This cycle is entirely inside the proof of the opaque's item bound. +// Crossing `OpaqueTypeBound` must not make this recursive impl productive. +impl Trait for Bar +where + Bar: Trait, +{} + +#[define_opaque(Foo)] +fn foo() -> Foo { + //~^ ERROR overflow evaluating the requirement `Foo == Bar<()>` + Bar(()) +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.stderr b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.stderr new file mode 100644 index 0000000000000..ec9154102441e --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-tait-bound-unproductive.stderr @@ -0,0 +1,9 @@ +error[E0275]: overflow evaluating the requirement `Foo == Bar<()>` + --> $DIR/recursive-tait-bound-unproductive.rs:20:13 + | +LL | fn foo() -> Foo { + | ^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0275`. diff --git a/tests/ui/traits/next-solver/opaques/recursive-tait-bound.rs b/tests/ui/traits/next-solver/opaques/recursive-tait-bound.rs new file mode 100644 index 0000000000000..0932f7e5a2183 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-tait-bound.rs @@ -0,0 +1,23 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +#![feature(type_alias_impl_trait)] + +pub type Foo = impl PartialEq<(Foo, i32)>; + +#[define_opaque(Foo)] +fn foo() -> Foo { + Bar +} + +struct Bar; + +impl PartialEq<(Foo, i32)> for Bar { + fn eq(&self, _: &(Foo, i32)) -> bool { + true + } +} + +fn main() {}