Repository: rust-lang/rust
Pull request #139762: Don't assemble non-env/bound candidates if projection is rigid
Author: login: compiler-errors
Labels: name: T-compiler
description: Relevant to the compiler team, which will review and decide on the PR/issue.
name: S-waiting-on-bors
description: Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
name: WG-trait-system-refactor
description: The Rustc Trait System Refactor Initiative (-Znext-solver)
Created: 2025-04-13T20:02:56Z | Merged: 2025-04-20T05:17:45Z

Putting this up for an initial review, it's still missing comments, clean-up, and possibly a tweak to deal with ambiguities in the `BestObligation` folder.

This PR fixes https://github.com/rust-lang/trait-system-refactor-initiative/issues/173. Specifically, we're creating an unnecessary query cycle in normalization by assembling an *impl candidate* even if we know later on during `merge_candidates` that we'll be filtering out that impl candidate.

This PR adjusts the `merge_candidates` to assemble *only* env/bound candidates if we have `TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound`.

I'll leave some thoughts/comments in the code.

r? lcnr


Diff:
diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
index 83b2465d05aa7..ecb57cc0ad71b 100644
--- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
@@ -288,6 +288,21 @@ where
     ) -> Vec<Candidate<I>>;
 }
 
+/// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit
+/// candidate assembly to param-env and alias-bound candidates.
+///
+/// On top of being a micro-optimization, as it avoids doing unnecessary work when
+/// a param-env trait bound candidate shadows impls for normalization, this is also
+/// required to prevent query cycles due to RPITIT inference. See the issue at:
+/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/173>.
+pub(super) enum AssembleCandidatesFrom {
+    All,
+    /// Only assemble candidates from the environment and alias bounds, ignoring
+    /// user-written and built-in impls. We only expect `ParamEnv` and `AliasBound`
+    /// candidates to be assembled.
+    EnvAndBounds,
+}
+
 impl<D, I> EvalCtxt<'_, D>
 where
     D: SolverDelegate<Interner = I>,
@@ -296,6 +311,7 @@ where
     pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
         &mut self,
         goal: Goal<I, G>,
+        assemble_from: AssembleCandidatesFrom,
     ) -> Vec<Candidate<I>> {
         let Ok(normalized_self_ty) =
             self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
@@ -322,16 +338,18 @@ where
             }
         }
 
-        self.assemble_impl_candidates(goal, &mut candidates);
-
-        self.assemble_builtin_impl_candidates(goal, &mut candidates);
-
         self.assemble_alias_bound_candidates(goal, &mut candidates);
-
-        self.assemble_object_bound_candidates(goal, &mut candidates);
-
         self.assemble_param_env_candidates(goal, &mut candidates);
 
+        match assemble_from {
+            AssembleCandidatesFrom::All => {
+                self.assemble_impl_candidates(goal, &mut candidates);
+                self.assemble_builtin_impl_candidates(goal, &mut candidates);
+                self.assemble_object_bound_candidates(goal, &mut candidates);
+            }
+            AssembleCandidatesFrom::EnvAndBounds => {}
+        }
+
         candidates
     }
 
@@ -754,6 +772,9 @@ where
         })
     }
 
+    /// Assemble and merge candidates for goals which are related to an underlying trait
+    /// goal. Right now, this is normalizes-to and host effect goals.
+    ///
     /// We sadly can't simply take all possible candidates for normalization goals
     /// and check whether they result in the same constraints. We want to make sure
     /// that trying to normalize an alias doesn't result in constraints which aren't
@@ -782,47 +803,44 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
         &mut self,
         proven_via: Option<TraitGoalProvenVia>,
-        candidates: Vec<Candidate<I>>,
+        goal: Goal<I, G>,
         inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
     ) -> QueryResult<I> {
         let Some(proven_via) = proven_via else {
             // We don't care about overflow. If proving the trait goal overflowed, then
             // it's enough to report an overflow error for that, we don't also have to
             // overflow during normalization.
-            return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
+            //
+            // We use `forced_ambiguity` here over `make_ambiguous_response_no_constraints`
+            // because the former will also record a built-in candidate in the inspector.
+            return self.forced_ambiguity(MaybeCause::Ambiguity).map(|cand| cand.result);
         };
 
         match proven_via {
             TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
-                let mut considered_candidates = Vec::new();
-                considered_candidates.extend(
-                    candidates
-                        .iter()
-                        .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
-                        .map(|c| c.result),
-                );
-
                 // Even when a trait bound has been proven using a where-bound, we
                 // still need to consider alias-bounds for normalization, see
-                // tests/ui/next-solver/alias-bound-shadowed-by-env.rs.
-                //
+                // `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
+                let candidates_from_env_and_bounds: Vec<_> = self
+                    .assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
+
                 // We still need to prefer where-bounds over alias-bounds however.
-                // See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs.
-                //
-                // FIXME(const_trait_impl): should this behavior also be used by
-                // constness checking. Doing so is *at least theoretically* breaking,
-                // see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
-                if considered_candidates.is_empty() {
-                    considered_candidates.extend(
-                        candidates
-                            .iter()
-                            .filter(|c| matches!(c.source, CandidateSource::AliasBound))
-                            .map(|c| c.result),
-                    );
-                }
+                // See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`.
+                let mut considered_candidates: Vec<_> = if candidates_from_env_and_bounds
+                    .iter()
+                    .any(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
+                {
+                    candidates_from_env_and_bounds
+                        .into_iter()
+                        .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
+                        .map(|c| c.result)
+                        .collect()
+                } else {
+                    candidates_from_env_and_bounds.into_iter().map(|c| c.result).collect()
+                };
 
                 // If the trait goal has been proven by using the environment, we want to treat
                 // aliases as rigid if there are no applicable projection bounds in the environment.
@@ -839,6 +857,9 @@ where
                 }
             }
             TraitGoalProvenVia::Misc => {
+                let candidates =
+                    self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
+
                 // Prefer "orphaned" param-env normalization predicates, which are used
                 // (for example, and ideally only) when proving item bounds for an impl.
                 let candidates_from_env: Vec<_> = candidates
diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
index 0b61c368d8e8d..7752a705cd145 100644
--- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
@@ -399,12 +399,11 @@ where
         &mut self,
         goal: Goal<I, ty::HostEffectPredicate<I>>,
     ) -> QueryResult<I> {
-        let candidates = self.assemble_and_evaluate_candidates(goal);
         let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
             let trait_goal: Goal<I, ty::TraitPredicate<I>> =
                 goal.with(ecx.cx(), goal.predicate.trait_ref);
             ecx.compute_trait_goal(trait_goal)
         })?;
-        self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution))
+        self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution))
     }
 }
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 fdeb276a58e69..9466901683e21 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
@@ -32,14 +32,13 @@ where
         let cx = self.cx();
         match goal.predicate.alias.kind(cx) {
             ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
-                let candidates = self.assemble_and_evaluate_candidates(goal);
                 let trait_ref = goal.predicate.alias.trait_ref(cx);
                 let (_, proven_via) =
                     self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
                         let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
                         ecx.compute_trait_goal(trait_goal)
                     })?;
-                self.merge_candidates(proven_via, candidates, |ecx| {
+                self.assemble_and_merge_candidates(proven_via, goal, |ecx| {
                     ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
                         this.structurally_instantiate_normalizes_to_term(
                             goal,
diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
index 409af8568d7a3..7bd1300f34ed7 100644
--- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
+++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
@@ -13,7 +13,7 @@ use tracing::{instrument, trace};
 
 use crate::delegate::SolverDelegate;
 use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
-use crate::solve::assembly::{self, Candidate};
+use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate};
 use crate::solve::inspect::ProbeKind;
 use crate::solve::{
     BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
@@ -1365,7 +1365,7 @@ where
         &mut self,
         goal: Goal<I, TraitPredicate<I>>,
     ) -> Result<(CanonicalResponse<I>, Option<TraitGoalProvenVia>), NoSolution> {
-        let candidates = self.assemble_and_evaluate_candidates(goal);
+        let candidates = self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
         self.merge_trait_candidates(goal, candidates)
     }
 }
diff --git a/tests/ui/impl-unused-tps.stderr b/tests/ui/impl-unused-tps.stderr
index 09c3fce641cfe..eff5ffff9b6ac 100644
--- a/tests/ui/impl-unused-tps.stderr
+++ b/tests/ui/impl-unused-tps.stderr
@@ -7,23 +7,6 @@ LL | impl<T> Foo<T> for [isize; 0] {
 LL | impl<T, U> Foo<T> for U {
    | ^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[isize; 0]`
 
-error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
-  --> $DIR/impl-unused-tps.rs:32:9
-   |
-LL | impl<T, U> Bar for T {
-   |         ^ unconstrained type parameter
-
-error[E0119]: conflicting implementations of trait `Bar`
-  --> $DIR/impl-unused-tps.rs:40:1
-   |
-LL |   impl<T, U> Bar for T {
-   |   -------------------- first implementation here
-...
-LL | / impl<T, U> Bar for T
-LL | | where
-LL | |     T: Bar<Out = U>,
-   | |____________________^ conflicting implementation
-
 error[E0119]: conflicting implementations of trait `Foo<[isize; 0]>` for type `[isize; 0]`
   --> $DIR/impl-unused-tps.rs:49:1
    |
@@ -52,6 +35,12 @@ error[E0207]: the type parameter `U` is not constrained by the impl trait, self
 LL | impl<T, U> Foo<T> for [isize; 1] {
    |         ^ unconstrained type parameter
 
+error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
+  --> $DIR/impl-unused-tps.rs:32:9
+   |
+LL | impl<T, U> Bar for T {
+   |         ^ unconstrained type parameter
+
 error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
   --> $DIR/impl-unused-tps.rs:40:9
    |
@@ -70,6 +59,17 @@ error[E0207]: the type parameter `V` is not constrained by the impl trait, self
 LL | impl<T, U, V> Foo<T> for T
    |            ^ unconstrained type parameter
 
+error[E0119]: conflicting implementations of trait `Bar`
+  --> $DIR/impl-unused-tps.rs:40:1
+   |
+LL |   impl<T, U> Bar for T {
+   |   -------------------- first implementation here
+...
+LL | / impl<T, U> Bar for T
+LL | | where
+LL | |     T: Bar<Out = U>,
+   | |____________________^ conflicting implementation
+
 error: aborting due to 9 previous errors
 
 Some errors have detailed explanations: E0119, E0207.
diff --git a/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs b/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs
new file mode 100644
index 0000000000000..ec3d710ef3719
--- /dev/null
+++ b/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs
@@ -0,0 +1,32 @@
+//@ compile-flags: -Znext-solver
+//@ check-pass
+//@ edition: 2024
+
+// Ensure we don't end up in a query cycle due to trying to assemble an impl candidate
+// for an RPITIT normalizes-to goal, even though that impl candidate would *necessarily*
+// be made rigid by a where clause. This query cycle is thus avoidable by not assembling
+// that impl candidate which we *know* we are going to throw away anyways.
+
+use std::future::Future;
+
+pub trait ReactiveFunction: Send {
+    type Output;
+
+    fn invoke(self) -> Self::Output;
+}
+
+trait AttributeValue {
+    fn resolve(self) -> impl Future<Output = ()> + Send;
+}
+
+impl<F, V> AttributeValue for F
+where
+    F: ReactiveFunction<Output = V>,
+    V: AttributeValue,
+{
+    async fn resolve(self) {
+        self.invoke().resolve().await
+    }
+}
+
+fn main() {}


Reviews:
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T20:06:52Z
user: login: lcnr
body: an alternative structure would be to change `assemble_and_evaluate_candidates` to also take
```rust
enum AssembledCandidates {
   All,
   /// Only assemble candidates from the environment,
   /// ignoring impls. We only look for `ParamEnv` and
   /// `AliasBound` candidates.
   FromEnv,
}
```
and then put `assemble_impl_candidates` behind an if/match :thinking: 

I feel like splitting assembly into multiple functions is making this a bit harder to reason about.

thoughts?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:53:19Z
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:13:01Z
user: login: lcnr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:13:22Z
user: login: lcnr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:14:55Z
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:19:41Z
user: login: lcnr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:34:35Z

Review comments:
diff_hunk: @@ -310,25 +311,40 @@ where
         // normalizing the self type as well, since type variables are not uniquified.
         let goal = self.resolve_vars_if_possible(goal);
 
-        let mut candidates = vec![];
-
         if let TypingMode::Coherence = self.typing_mode() {
             if let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal) {
                 return vec![candidate];
             }
         }
 
-        self.assemble_impl_candidates(goal, &mut candidates);
-
-        self.assemble_builtin_impl_candidates(goal, &mut candidates);
-
-        self.assemble_alias_bound_candidates(goal, &mut candidates);
-
-        self.assemble_object_bound_candidates(goal, &mut candidates);
+        assemble(self, goal)
+    }
 
-        self.assemble_param_env_candidates(goal, &mut candidates);
+    pub(super) fn assemble_and_evaluate_candidates_from_env_and_bounds<G: GoalKind<D>>(
+        &mut self,
+        goal: Goal<I, G>,
+    ) -> Vec<Candidate<I>> {
+        self.assemble_and_evaluate_candidates_with_normalized_goal(goal, |ecx, goal| {
+            let mut candidates = vec![];
+            ecx.assemble_alias_bound_candidates(goal, &mut candidates);
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: Bit unsatisfying that we have to keep this in sync w/ below. Could introduce another helper function, I guess.
created_at: 2025-04-13T20:03:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -310,25 +311,40 @@ where
         // normalizing the self type as well, since type variables are not uniquified.
         let goal = self.resolve_vars_if_possible(goal);
 
-        let mut candidates = vec![];
-
         if let TypingMode::Coherence = self.typing_mode() {
             if let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal) {
                 return vec![candidate];
             }
         }
 
-        self.assemble_impl_candidates(goal, &mut candidates);
-
-        self.assemble_builtin_impl_candidates(goal, &mut candidates);
-
-        self.assemble_alias_bound_candidates(goal, &mut candidates);
-
-        self.assemble_object_bound_candidates(goal, &mut candidates);
+        assemble(self, goal)
+    }
 
-        self.assemble_param_env_candidates(goal, &mut candidates);
+    pub(super) fn assemble_and_evaluate_candidates_from_env_and_bounds<G: GoalKind<D>>(
+        &mut self,
+        goal: Goal<I, G>,
+    ) -> Vec<Candidate<I>> {
+        self.assemble_and_evaluate_candidates_with_normalized_goal(goal, |ecx, goal| {
+            let mut candidates = vec![];
+            ecx.assemble_alias_bound_candidates(goal, &mut candidates);
+            ecx.assemble_param_env_candidates(goal, &mut candidates);
+            candidates
+        })
+    }
 
-        candidates
+    pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
+        &mut self,
+        goal: Goal<I, G>,
+    ) -> Vec<Candidate<I>> {
+        self.assemble_and_evaluate_candidates_with_normalized_goal(goal, |ecx, goal| {
+            let mut candidates = vec![];
+            ecx.assemble_impl_candidates(goal, &mut candidates);
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: It's a bit subtle b/c we could inadvertently assemble some *other* kind of env/bound candidate outside of the `alias_bound_candidates`/`param_env_candidates` functions. 

We could move those functions to the end, and right before we assert that all the candidates assembled *before* aren't env/bound candidates, to make sure we're not subtly incomplete here?
created_at: 2025-04-13T20:04:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -289,9 +289,10 @@ where
     D: SolverDelegate<Interner = I>,
     I: Interner,
 {
-    pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
+    fn assemble_and_evaluate_candidates_with_normalized_goal<G: GoalKind<D>>(
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: Need comment.
created_at: 2025-04-13T20:04:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -310,25 +311,40 @@ where
         // normalizing the self type as well, since type variables are not uniquified.
         let goal = self.resolve_vars_if_possible(goal);
 
-        let mut candidates = vec![];
-
         if let TypingMode::Coherence = self.typing_mode() {
             if let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal) {
                 return vec![candidate];
             }
         }
 
-        self.assemble_impl_candidates(goal, &mut candidates);
-
-        self.assemble_builtin_impl_candidates(goal, &mut candidates);
-
-        self.assemble_alias_bound_candidates(goal, &mut candidates);
-
-        self.assemble_object_bound_candidates(goal, &mut candidates);
+        assemble(self, goal)
+    }
 
-        self.assemble_param_env_candidates(goal, &mut candidates);
+    pub(super) fn assemble_and_evaluate_candidates_from_env_and_bounds<G: GoalKind<D>>(
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: Need comment that people who modify this function must ensure it remains complete.
created_at: 2025-04-13T20:05:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -778,10 +794,10 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: Need to adjust above comment to mention that this is also doing the assembly.
created_at: 2025-04-13T20:05:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -269,7 +269,7 @@ impl<'tcx> BestObligation<'tcx> {
         goal: &inspect::InspectGoal<'_, 'tcx>,
         self_ty: Ty<'tcx>,
     ) -> ControlFlow<PredicateObligation<'tcx>> {
-        assert!(!self.consider_ambiguities);
+        //assert!(!self.consider_ambiguities);
path: compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs
user: login: compiler-errors
body: We now assemble no candidates if the trait goal for an normalizes-to is ambiguous. 

When we encounter that `let Some(proven_via) = proven_via else {` case above, we could wrap w/ candidate kind (or inject it into the inspector) to avoid this assertion, or otherwise just remove the assertion altogether.
created_at: 2025-04-13T20:06:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -778,47 +792,41 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
         &mut self,
         proven_via: Option<TraitGoalProvenVia>,
-        candidates: Vec<Candidate<I>>,
+        goal: Goal<I, G>,
         inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
     ) -> QueryResult<I> {
         let Some(proven_via) = proven_via else {
             // We don't care about overflow. If proving the trait goal overflowed, then
             // it's enough to report an overflow error for that, we don't also have to
             // overflow during normalization.
-            return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
+            return Ok(self.forced_ambiguity(MaybeCause::Ambiguity)?.result);
         };
 
         match proven_via {
             TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
-                let mut considered_candidates = Vec::new();
-                considered_candidates.extend(
-                    candidates
-                        .iter()
-                        .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
-                        .map(|c| c.result),
-                );
-
                 // Even when a trait bound has been proven using a where-bound, we
                 // still need to consider alias-bounds for normalization, see
-                // tests/ui/next-solver/alias-bound-shadowed-by-env.rs.
-                //
+                // `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
+                let candidates_from_env_and_bounds: Vec<_> = self
+                    .assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
+
                 // We still need to prefer where-bounds over alias-bounds however.
-                // See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs.
-                //
-                // FIXME(const_trait_impl): should this behavior also be used by
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: AFAICT this fixme is no longer relevant? We use `assemble_and_merge` for host preds now.
created_at: 2025-04-17T19:13:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -284,6 +284,14 @@ where
     ) -> Vec<Candidate<I>>;
 }
 
+pub(super) enum AssembleCandidatesFrom {
+    All,
+    /// Only assemble candidates from the environment and alias bounds, ignoring
+    /// /user-written and built-in impls. We only expect `ParamEnv` and `AliasBound`
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: lcnr
body: /user-written

also, pls add a quick comment to the enum itself describing why its necessary
created_at: 2025-04-17T19:13:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -778,47 +792,41 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
         &mut self,
         proven_via: Option<TraitGoalProvenVia>,
-        candidates: Vec<Candidate<I>>,
+        goal: Goal<I, G>,
         inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
     ) -> QueryResult<I> {
         let Some(proven_via) = proven_via else {
             // We don't care about overflow. If proving the trait goal overflowed, then
             // it's enough to report an overflow error for that, we don't also have to
             // overflow during normalization.
-            return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
+            return Ok(self.forced_ambiguity(MaybeCause::Ambiguity)?.result);
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: lcnr
body: any case where `make_ambiguous_response_no_constraints` was causing issues? or just general consistency. Also isn't this just `return self.forced_ambiguity()`? :<
created_at: 2025-04-17T19:14:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -778,47 +792,41 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
         &mut self,
         proven_via: Option<TraitGoalProvenVia>,
-        candidates: Vec<Candidate<I>>,
+        goal: Goal<I, G>,
         inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
     ) -> QueryResult<I> {
         let Some(proven_via) = proven_via else {
             // We don't care about overflow. If proving the trait goal overflowed, then
             // it's enough to report an overflow error for that, we don't also have to
             // overflow during normalization.
-            return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
+            return Ok(self.forced_ambiguity(MaybeCause::Ambiguity)?.result);
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: compiler-errors
body: > any case where make_ambiguous_response_no_constraints was causing issues? 

`make_ambiguous_response_no_constraints` was causing us not to register a *candidate kind*, which is the reason why I had commented out that assertion in the `BestObligation` folder above.

https://github.com/rust-lang/rust/pull/139762#discussion_r2041200133

I prefer just actually assembling a built-in candidate for ambiguity here, but if you care a lot about this then we could just remove the assertion I linked above, but it does mean unnecessarily stepping into the "search for failures due to no candidates assembled" code in the best obligation folder.

> lso isn't this just return self.forced_ambiguity()? :<

Nope, since we need to unwrap the candidate and return its query response.

i.e. `return Ok(x?.field)` is not `return x`.
created_at: 2025-04-17T19:19:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -778,47 +792,41 @@ where
     ///
     /// See trait-system-refactor-initiative#124 for more details.
     #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
-    pub(super) fn merge_candidates(
+    pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
         &mut self,
         proven_via: Option<TraitGoalProvenVia>,
-        candidates: Vec<Candidate<I>>,
+        goal: Goal<I, G>,
         inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
     ) -> QueryResult<I> {
         let Some(proven_via) = proven_via else {
             // We don't care about overflow. If proving the trait goal overflowed, then
             // it's enough to report an overflow error for that, we don't also have to
             // overflow during normalization.
-            return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
+            return Ok(self.forced_ambiguity(MaybeCause::Ambiguity)?.result);
path: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
user: login: lcnr
body: `return x.map(|c| c.result)` then?
created_at: 2025-04-17T19:34:34Z
author_association: CONTRIBUTOR

PR comments:
user: login: rustbot
created_at: 2025-04-13T20:03:02Z
body: Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor
author_association: COLLABORATOR
user: login: lcnr
created_at: 2025-04-14T09:48:21Z
body: This PR has two separate effects
- we avoid fetching the type of impl associated items if they are shadowed anyways
  - avoid the query cycle in `leptos`, and generally avoids an unnecessary query dependency
- we avoid reassembling impl candidates if stuff is proven via where-bound, likely positively impacting perf

I feel :shrug: about the first case. I think we should fix https://github.com/rust-lang/rust/issues/139788 regardless as using RPITITs recursively shouldn't cycle even if they are actually needed. It does match the behavior of the old solver so it feels generally reasonable to do so.

The second one is a bit worrying. I would be opposed/far more hesitant if it were to do the same when assembling trait goals as that definitely has an exponential performance impact (e.g. it would fix https://github.com/rust-lang/trait-system-refactor-initiative/issues/109). I want to be very careful here as I expect us to consider impl candidates in more cases in the future and ideally doing so won't introduce new hangs :<

I expect that in theory we may have exponential performance differences depending on whether or not we check impl item bounds. I don't know if they are likely enough to actually cause issues for us in the future. I am still a bit on the fence about this PR, but generally in favor I think.  

 
author_association: CONTRIBUTOR
user: login: compiler-errors
created_at: 2025-04-14T10:14:08Z
body: > I would be opposed/far more hesitant if it were to do the same when assembling trait goals as that definitely has an exponential performance impact (e.g. it would fix https://github.com/rust-lang/trait-system-refactor-initiative/issues/109).

Well part of the point of this approach is we're already doing all the work of proving the trait goal in order to even compute the `TraitGoalProvenVia`, so my *vibe* is that all of the additional extra possibly exponential computational work here that we're winnowing out *today* would be due to GAT bounds.

Totally unrelated side-note that it would be nice if *query* cycles were recoverable, lol.
author_association: CONTRIBUTOR
user: login: bors
created_at: 2025-04-15T12:20:07Z
body: :umbrella: The latest upstream changes (presumably #139845) made this pull request unmergeable. Please [resolve the merge conflicts](https://rustc-dev-guide.rust-lang.org/git.html#rebasing-and-conflicts).
author_association: COLLABORATOR
user: login: lcnr
created_at: 2025-04-15T12:32:40Z
body: >  so my vibe is that all of the additional extra possibly exponential computational work here that we're winnowing out today would be due to GAT bounds.

yeah, exactly, the only hangs we'd be avoiding this way are due to GAT bounds. I guess this seems acceptable to me, so yeah. Let's go ahead with this change
author_association: CONTRIBUTOR
user: login: compiler-errors
created_at: 2025-04-17T18:58:46Z
body: Yeah, that would work too.
author_association: CONTRIBUTOR
user: login: lcnr
created_at: 2025-04-19T11:24:54Z
body: @bors r+ rollup
author_association: CONTRIBUTOR
user: login: bors
created_at: 2025-04-19T11:24:57Z
body: :pushpin: Commit e882ff4e7ebc4653cdc69e57bc656fe558a4af88 has been approved by `lcnr`

It is now in the [queue](https://bors.rust-lang.org/homu/queue/rust) for this repository.

<!-- @bors r=lcnr e882ff4e7ebc4653cdc69e57bc656fe558a4af88 -->
<!-- homu: {"type":"Approved","sha":"e882ff4e7ebc4653cdc69e57bc656fe558a4af88","approver":"lcnr","queue":"https://bors.rust-lang.org/homu/queue/rust"} -->
author_association: COLLABORATOR