about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChris Denton <chris@chrisdenton.dev>2025-04-28 23:29:16 +0000
committerGitHub <noreply@github.com>2025-04-28 23:29:16 +0000
commit0bd531aaf2622553a1488ede23db32aa51040f6c (patch)
tree0006430ddddf18bbe6c680e79a8e22a925a0630e
parent8dd26cb195df77d991ee7826b1aa587d9f638e78 (diff)
parent105d1dcefd9baa520e63830f557d9c472f50d4fd (diff)
downloadrust-0bd531aaf2622553a1488ede23db32aa51040f6c.tar.gz
rust-0bd531aaf2622553a1488ede23db32aa51040f6c.zip
Rollup merge of #140276 - compiler-errors:typeof-less-eagerly, r=lcnr
Do not compute type_of for impl item if impl where clauses are unsatisfied

Consider the following code:

```rust
trait Foo {
    fn call(self) -> impl Send;
}

trait Nested {}
impl<T> Foo for T
where
    T: Nested,
{
    fn call(self) -> impl Sized {
        NotSatisfied.call()
    }
}

struct NotSatisfied;
impl Foo for NotSatisfied {
    fn call(self) -> impl Sized {
        todo!()
    }
}
```

In `impl Foo for NotSatisfied`, we need to prove that the RPITIT is well formed. This requires proving the item bound `<NotSatisfied as Foo>::RPITIT: Send`. Normalizing `<NotSatisfied as Foo>::RPITIT: Send` assembles two impl candidates, via the `NotSatisfied` impl and the blanket `T` impl. We end up computing the `type_of` for the blanket impl even if `NotSatisfied: Nested` where clause does not hold.

This type_of query ends up needing to prove that its own `impl Sized` RPIT satisfies `Send`, which ends up needing to compute the hidden type of the RPIT, which is equal to the return type  of `NotSatisfied.call()`. That ends up in a query cycle, since we subsequently try normalizing that return type via the blanket impl again!

In the old solver, we don't end up computing the `type_of` an impl candidate if its where clauses don't hold, since this select call would fail before confirming the projection candidate:

https://github.com/rust-lang/rust/blob/d7ea436a02d5de4033fcf7fd4eb8ed965d0f574c/compiler/rustc_trait_selection/src/traits/project.rs#L882

This PR makes the new solver more consistent with the old solver by adding a call to `try_evaluate_added_goals` after regstering the impl predicates, which causes us to bail before computing the `type_of` for impls if the impl definitely doesn't apply.

r? lcnr

Fixes https://github.com/rust-lang/trait-system-refactor-initiative/issues/185
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs6
-rw-r--r--tests/ui/async-await/in-trait/cycle-if-impl-doesnt-apply.rs39
-rw-r--r--tests/ui/impl-trait/in-trait/cycle-if-impl-doesnt-apply.rs32
3 files changed, 77 insertions, 0 deletions
diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
index f5cad623903..87faf99ced6 100644
--- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
@@ -194,6 +194,12 @@ where
                 .map(|pred| goal.with(cx, pred));
             ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds);
 
+            // Bail if the nested goals don't hold here. This is to avoid unnecessarily
+            // computing the `type_of` query for associated types that never apply, as
+            // this may result in query cycles in the case of RPITITs.
+            // See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/185>.
+            ecx.try_evaluate_added_goals()?;
+
             // Add GAT where clauses from the trait's definition.
             // FIXME: We don't need these, since these are the type's own WF obligations.
             ecx.add_goals(
diff --git a/tests/ui/async-await/in-trait/cycle-if-impl-doesnt-apply.rs b/tests/ui/async-await/in-trait/cycle-if-impl-doesnt-apply.rs
new file mode 100644
index 00000000000..54992c98655
--- /dev/null
+++ b/tests/ui/async-await/in-trait/cycle-if-impl-doesnt-apply.rs
@@ -0,0 +1,39 @@
+//@ check-pass
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+//@ edition: 2024
+
+// Regression test for <https://github.com/rust-lang/trait-system-refactor-initiative/issues/185>.
+// Avoid unnecessarily computing the RPITIT type of the first impl when checking the WF of the
+// second impl, since the first impl relies on the hidden type of the second impl.
+
+use std::future::Future;
+
+trait Handler {}
+
+struct W<T>(T);
+
+trait SendTarget {
+    fn call(self) -> impl Future<Output = ()> + Send;
+}
+
+impl<T> SendTarget for W<T>
+where
+    T: Handler + Send,
+{
+    async fn call(self) {
+        todo!()
+    }
+}
+
+impl<T> SendTarget for T
+where
+    T: Handler + Send,
+{
+    async fn call(self) {
+        W(self).call().await
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/impl-trait/in-trait/cycle-if-impl-doesnt-apply.rs b/tests/ui/impl-trait/in-trait/cycle-if-impl-doesnt-apply.rs
new file mode 100644
index 00000000000..72e08ed3504
--- /dev/null
+++ b/tests/ui/impl-trait/in-trait/cycle-if-impl-doesnt-apply.rs
@@ -0,0 +1,32 @@
+//@ check-pass
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+//@ edition: 2024
+
+// Regression test for <https://github.com/rust-lang/trait-system-refactor-initiative/issues/185>.
+// Avoid unnecessarily computing the RPITIT type of the first impl when checking the WF of the
+// second impl, since the first impl relies on the hidden type of the second impl.
+
+trait Foo {
+    fn call(self) -> impl Send;
+}
+
+trait Nested {}
+impl<T> Foo for T
+where
+    T: Nested,
+{
+    fn call(self) -> impl Sized {
+        NotSatisfied.call()
+    }
+}
+
+struct NotSatisfied;
+impl Foo for NotSatisfied {
+    fn call(self) -> impl Sized {
+        todo!()
+    }
+}
+
+fn main() {}