Skip to content

Commit 39a8eca

Browse files
Don't assemble non-env/bound candidates if projection is rigid
1 parent 883f9f7 commit 39a8eca

File tree

6 files changed

+104
-56
lines changed

6 files changed

+104
-56
lines changed

Diff for: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

+51-33
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,21 @@ where
284284
) -> Vec<Candidate<I>>;
285285
}
286286

287+
/// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit
288+
/// candidate assembly to param-env and alias-bound candidates.
289+
///
290+
/// On top of being a micro-optimization, as it avoids doing unnecessary work when
291+
/// a param-env trait bound candidate shadows impls for normalization, this is also
292+
/// required to prevent query cycles due to RPITIT inference. See the issue at:
293+
/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/173>.
294+
pub(super) enum AssembleCandidatesFrom {
295+
All,
296+
/// Only assemble candidates from the environment and alias bounds, ignoring
297+
/// user-written and built-in impls. We only expect `ParamEnv` and `AliasBound`
298+
/// candidates to be assembled.
299+
EnvAndBounds,
300+
}
301+
287302
impl<D, I> EvalCtxt<'_, D>
288303
where
289304
D: SolverDelegate<Interner = I>,
@@ -292,6 +307,7 @@ where
292307
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
293308
&mut self,
294309
goal: Goal<I, G>,
310+
assemble_from: AssembleCandidatesFrom,
295311
) -> Vec<Candidate<I>> {
296312
let Ok(normalized_self_ty) =
297313
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
@@ -318,16 +334,18 @@ where
318334
}
319335
}
320336

321-
self.assemble_impl_candidates(goal, &mut candidates);
322-
323-
self.assemble_builtin_impl_candidates(goal, &mut candidates);
324-
325337
self.assemble_alias_bound_candidates(goal, &mut candidates);
326-
327-
self.assemble_object_bound_candidates(goal, &mut candidates);
328-
329338
self.assemble_param_env_candidates(goal, &mut candidates);
330339

340+
match assemble_from {
341+
AssembleCandidatesFrom::All => {
342+
self.assemble_impl_candidates(goal, &mut candidates);
343+
self.assemble_builtin_impl_candidates(goal, &mut candidates);
344+
self.assemble_object_bound_candidates(goal, &mut candidates);
345+
}
346+
AssembleCandidatesFrom::EnvAndBounds => {}
347+
}
348+
331349
candidates
332350
}
333351

@@ -750,6 +768,9 @@ where
750768
})
751769
}
752770

771+
/// Assemble and merge candidates for goals which are related to an underlying trait
772+
/// goal. Right now, this is normalizes-to and host effect goals.
773+
///
753774
/// We sadly can't simply take all possible candidates for normalization goals
754775
/// and check whether they result in the same constraints. We want to make sure
755776
/// that trying to normalize an alias doesn't result in constraints which aren't
@@ -778,47 +799,41 @@ where
778799
///
779800
/// See trait-system-refactor-initiative#124 for more details.
780801
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
781-
pub(super) fn merge_candidates(
802+
pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
782803
&mut self,
783804
proven_via: Option<TraitGoalProvenVia>,
784-
candidates: Vec<Candidate<I>>,
805+
goal: Goal<I, G>,
785806
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
786807
) -> QueryResult<I> {
787808
let Some(proven_via) = proven_via else {
788809
// We don't care about overflow. If proving the trait goal overflowed, then
789810
// it's enough to report an overflow error for that, we don't also have to
790811
// overflow during normalization.
791-
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
812+
return Ok(self.forced_ambiguity(MaybeCause::Ambiguity)?.result);
792813
};
793814

794815
match proven_via {
795816
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
796-
let mut considered_candidates = Vec::new();
797-
considered_candidates.extend(
798-
candidates
799-
.iter()
800-
.filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
801-
.map(|c| c.result),
802-
);
803-
804817
// Even when a trait bound has been proven using a where-bound, we
805818
// still need to consider alias-bounds for normalization, see
806-
// tests/ui/next-solver/alias-bound-shadowed-by-env.rs.
807-
//
819+
// `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
820+
let candidates_from_env_and_bounds: Vec<_> = self
821+
.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
822+
808823
// We still need to prefer where-bounds over alias-bounds however.
809-
// See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs.
810-
//
811-
// FIXME(const_trait_impl): should this behavior also be used by
812-
// constness checking. Doing so is *at least theoretically* breaking,
813-
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
814-
if considered_candidates.is_empty() {
815-
considered_candidates.extend(
816-
candidates
817-
.iter()
818-
.filter(|c| matches!(c.source, CandidateSource::AliasBound))
819-
.map(|c| c.result),
820-
);
821-
}
824+
// See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`.
825+
let mut considered_candidates: Vec<_> = if candidates_from_env_and_bounds
826+
.iter()
827+
.any(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
828+
{
829+
candidates_from_env_and_bounds
830+
.into_iter()
831+
.filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
832+
.map(|c| c.result)
833+
.collect()
834+
} else {
835+
candidates_from_env_and_bounds.into_iter().map(|c| c.result).collect()
836+
};
822837

823838
// If the trait goal has been proven by using the environment, we want to treat
824839
// aliases as rigid if there are no applicable projection bounds in the environment.
@@ -835,6 +850,9 @@ where
835850
}
836851
}
837852
TraitGoalProvenVia::Misc => {
853+
let candidates =
854+
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
855+
838856
// Prefer "orphaned" param-env normalization predicates, which are used
839857
// (for example, and ideally only) when proving item bounds for an impl.
840858
let candidates_from_env: Vec<_> = candidates

Diff for: compiler/rustc_next_trait_solver/src/solve/effect_goals.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -399,12 +399,11 @@ where
399399
&mut self,
400400
goal: Goal<I, ty::HostEffectPredicate<I>>,
401401
) -> QueryResult<I> {
402-
let candidates = self.assemble_and_evaluate_candidates(goal);
403402
let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
404403
let trait_goal: Goal<I, ty::TraitPredicate<I>> =
405404
goal.with(ecx.cx(), goal.predicate.trait_ref);
406405
ecx.compute_trait_goal(trait_goal)
407406
})?;
408-
self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution))
407+
self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution))
409408
}
410409
}

Diff for: compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ where
3232
let cx = self.cx();
3333
match goal.predicate.alias.kind(cx) {
3434
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
35-
let candidates = self.assemble_and_evaluate_candidates(goal);
3635
let trait_ref = goal.predicate.alias.trait_ref(cx);
3736
let (_, proven_via) =
3837
self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
3938
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
4039
ecx.compute_trait_goal(trait_goal)
4140
})?;
42-
self.merge_candidates(proven_via, candidates, |ecx| {
41+
self.assemble_and_merge_candidates(proven_via, goal, |ecx| {
4342
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
4443
this.structurally_instantiate_normalizes_to_term(
4544
goal,

Diff for: compiler/rustc_next_trait_solver/src/solve/trait_goals.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tracing::{instrument, trace};
1313

1414
use crate::delegate::SolverDelegate;
1515
use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
16-
use crate::solve::assembly::{self, Candidate};
16+
use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate};
1717
use crate::solve::inspect::ProbeKind;
1818
use crate::solve::{
1919
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
@@ -1365,7 +1365,7 @@ where
13651365
&mut self,
13661366
goal: Goal<I, TraitPredicate<I>>,
13671367
) -> Result<(CanonicalResponse<I>, Option<TraitGoalProvenVia>), NoSolution> {
1368-
let candidates = self.assemble_and_evaluate_candidates(goal);
1368+
let candidates = self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
13691369
self.merge_trait_candidates(goal, candidates)
13701370
}
13711371
}

Diff for: tests/ui/impl-unused-tps.stderr

+17-17
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,6 @@ LL | impl<T> Foo<T> for [isize; 0] {
77
LL | impl<T, U> Foo<T> for U {
88
| ^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[isize; 0]`
99

10-
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
11-
--> $DIR/impl-unused-tps.rs:32:9
12-
|
13-
LL | impl<T, U> Bar for T {
14-
| ^ unconstrained type parameter
15-
16-
error[E0119]: conflicting implementations of trait `Bar`
17-
--> $DIR/impl-unused-tps.rs:40:1
18-
|
19-
LL | impl<T, U> Bar for T {
20-
| -------------------- first implementation here
21-
...
22-
LL | / impl<T, U> Bar for T
23-
LL | | where
24-
LL | | T: Bar<Out = U>,
25-
| |____________________^ conflicting implementation
26-
2710
error[E0119]: conflicting implementations of trait `Foo<[isize; 0]>` for type `[isize; 0]`
2811
--> $DIR/impl-unused-tps.rs:49:1
2912
|
@@ -52,6 +35,12 @@ error[E0207]: the type parameter `U` is not constrained by the impl trait, self
5235
LL | impl<T, U> Foo<T> for [isize; 1] {
5336
| ^ unconstrained type parameter
5437

38+
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
39+
--> $DIR/impl-unused-tps.rs:32:9
40+
|
41+
LL | impl<T, U> Bar for T {
42+
| ^ unconstrained type parameter
43+
5544
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
5645
--> $DIR/impl-unused-tps.rs:40:9
5746
|
@@ -70,6 +59,17 @@ error[E0207]: the type parameter `V` is not constrained by the impl trait, self
7059
LL | impl<T, U, V> Foo<T> for T
7160
| ^ unconstrained type parameter
7261

62+
error[E0119]: conflicting implementations of trait `Bar`
63+
--> $DIR/impl-unused-tps.rs:40:1
64+
|
65+
LL | impl<T, U> Bar for T {
66+
| -------------------- first implementation here
67+
...
68+
LL | / impl<T, U> Bar for T
69+
LL | | where
70+
LL | | T: Bar<Out = U>,
71+
| |____________________^ conflicting implementation
72+
7373
error: aborting due to 9 previous errors
7474

7575
Some errors have detailed explanations: E0119, E0207.
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
//@ edition: 2024
4+
5+
// Ensure we don't end up in a query cycle due to trying to assemble an impl candidate
6+
// for an RPITIT normalizes-to goal, even though that impl candidate would *necessarily*
7+
// be made rigid by a where clause. This query cycle is thus avoidable by not assembling
8+
// that impl candidate which we *know* we are going to throw away anyways.
9+
10+
use std::future::Future;
11+
12+
pub trait ReactiveFunction: Send {
13+
type Output;
14+
15+
fn invoke(self) -> Self::Output;
16+
}
17+
18+
trait AttributeValue {
19+
fn resolve(self) -> impl Future<Output = ()> + Send;
20+
}
21+
22+
impl<F, V> AttributeValue for F
23+
where
24+
F: ReactiveFunction<Output = V>,
25+
V: AttributeValue,
26+
{
27+
async fn resolve(self) {
28+
self.invoke().resolve().await
29+
}
30+
}
31+
32+
fn main() {}

0 commit comments

Comments
 (0)