Skip to content

Commit f471582

Browse files
authored
Unrolled build for rust-lang#139762
Rollup merge of rust-lang#139762 - compiler-errors:non-env, r=lcnr Don't assemble non-env/bound candidates if projection is rigid 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 rust-lang/trait-system-refactor-initiative#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
2 parents 90fd16e + e882ff4 commit f471582

File tree

6 files changed

+107
-56
lines changed

6 files changed

+107
-56
lines changed

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

+54-33
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,21 @@ where
288288
) -> Vec<Candidate<I>>;
289289
}
290290

291+
/// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit
292+
/// candidate assembly to param-env and alias-bound candidates.
293+
///
294+
/// On top of being a micro-optimization, as it avoids doing unnecessary work when
295+
/// a param-env trait bound candidate shadows impls for normalization, this is also
296+
/// required to prevent query cycles due to RPITIT inference. See the issue at:
297+
/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/173>.
298+
pub(super) enum AssembleCandidatesFrom {
299+
All,
300+
/// Only assemble candidates from the environment and alias bounds, ignoring
301+
/// user-written and built-in impls. We only expect `ParamEnv` and `AliasBound`
302+
/// candidates to be assembled.
303+
EnvAndBounds,
304+
}
305+
291306
impl<D, I> EvalCtxt<'_, D>
292307
where
293308
D: SolverDelegate<Interner = I>,
@@ -296,6 +311,7 @@ where
296311
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
297312
&mut self,
298313
goal: Goal<I, G>,
314+
assemble_from: AssembleCandidatesFrom,
299315
) -> Vec<Candidate<I>> {
300316
let Ok(normalized_self_ty) =
301317
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
@@ -322,16 +338,18 @@ where
322338
}
323339
}
324340

325-
self.assemble_impl_candidates(goal, &mut candidates);
326-
327-
self.assemble_builtin_impl_candidates(goal, &mut candidates);
328-
329341
self.assemble_alias_bound_candidates(goal, &mut candidates);
330-
331-
self.assemble_object_bound_candidates(goal, &mut candidates);
332-
333342
self.assemble_param_env_candidates(goal, &mut candidates);
334343

344+
match assemble_from {
345+
AssembleCandidatesFrom::All => {
346+
self.assemble_impl_candidates(goal, &mut candidates);
347+
self.assemble_builtin_impl_candidates(goal, &mut candidates);
348+
self.assemble_object_bound_candidates(goal, &mut candidates);
349+
}
350+
AssembleCandidatesFrom::EnvAndBounds => {}
351+
}
352+
335353
candidates
336354
}
337355

@@ -754,6 +772,9 @@ where
754772
})
755773
}
756774

775+
/// Assemble and merge candidates for goals which are related to an underlying trait
776+
/// goal. Right now, this is normalizes-to and host effect goals.
777+
///
757778
/// We sadly can't simply take all possible candidates for normalization goals
758779
/// and check whether they result in the same constraints. We want to make sure
759780
/// that trying to normalize an alias doesn't result in constraints which aren't
@@ -782,47 +803,44 @@ where
782803
///
783804
/// See trait-system-refactor-initiative#124 for more details.
784805
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
785-
pub(super) fn merge_candidates(
806+
pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
786807
&mut self,
787808
proven_via: Option<TraitGoalProvenVia>,
788-
candidates: Vec<Candidate<I>>,
809+
goal: Goal<I, G>,
789810
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
790811
) -> QueryResult<I> {
791812
let Some(proven_via) = proven_via else {
792813
// We don't care about overflow. If proving the trait goal overflowed, then
793814
// it's enough to report an overflow error for that, we don't also have to
794815
// overflow during normalization.
795-
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity));
816+
//
817+
// We use `forced_ambiguity` here over `make_ambiguous_response_no_constraints`
818+
// because the former will also record a built-in candidate in the inspector.
819+
return self.forced_ambiguity(MaybeCause::Ambiguity).map(|cand| cand.result);
796820
};
797821

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

827845
// If the trait goal has been proven by using the environment, we want to treat
828846
// aliases as rigid if there are no applicable projection bounds in the environment.
@@ -839,6 +857,9 @@ where
839857
}
840858
}
841859
TraitGoalProvenVia::Misc => {
860+
let candidates =
861+
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
862+
842863
// Prefer "orphaned" param-env normalization predicates, which are used
843864
// (for example, and ideally only) when proving item bounds for an impl.
844865
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)