move GAT inference prevention hack

This commit is contained in:
lcnr 2025-11-12 14:49:53 +01:00
parent 95aeb46965
commit 15b02a94b1
4 changed files with 104 additions and 47 deletions

View file

@ -1125,11 +1125,12 @@ where
/// treat the alias as rigid.
///
/// See trait-system-refactor-initiative#124 for more details.
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
#[instrument(level = "debug", skip_all, fields(proven_via, goal), ret)]
pub(super) fn assemble_and_merge_candidates<G: GoalKind<D>>(
&mut self,
proven_via: Option<TraitGoalProvenVia>,
goal: Goal<I, G>,
inject_forced_ambiguity_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> Option<QueryResult<I>>,
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let Some(proven_via) = proven_via else {
@ -1149,15 +1150,24 @@ where
// `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
let (mut candidates, _) = self
.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
debug!(?candidates);
// 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.
if candidates.is_empty() {
return inject_normalize_to_rigid_candidate(self);
}
// If we're normalizing an GAT, we bail if using a where-bound would constrain
// its generic arguments.
if let Some(result) = inject_forced_ambiguity_candidate(self) {
return result;
}
// We still need to prefer where-bounds over alias-bounds however.
// See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`.
if candidates.iter().any(|c| matches!(c.source, CandidateSource::ParamEnv(_))) {
candidates.retain(|c| matches!(c.source, CandidateSource::ParamEnv(_)));
} else if candidates.is_empty() {
// 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.
return inject_normalize_to_rigid_candidate(self);
}
if let Some((response, _)) = self.try_merge_candidates(&candidates) {

View file

@ -446,6 +446,6 @@ where
goal.with(ecx.cx(), goal.predicate.trait_ref);
ecx.compute_trait_goal(trait_goal)
})?;
self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution))
self.assemble_and_merge_candidates(proven_via, goal, |_ecx| None, |_ecx| Err(NoSolution))
}
}

View file

@ -39,15 +39,49 @@ where
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
ecx.compute_trait_goal(trait_goal)
})?;
self.assemble_and_merge_candidates(proven_via, goal, |ecx| {
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
this.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
})
self.assemble_and_merge_candidates(
proven_via,
goal,
|ecx| {
// FIXME(generic_associated_types): Addresses aggressive inference in #92917.
//
// If this type is a GAT with currently unconstrained arguments, we do not
// want to normalize it via a candidate which only applies for a specific
// instantiation. We could otherwise keep the GAT as rigid and succeed this way.
// See tests/ui/generic-associated-types/no-incomplete-gat-arg-inference.rs.
//
// This only avoids normalization if a GAT argument is fully unconstrained.
// This is quite arbitrary but fixing it causes some ambiguity, see #125196.
for arg in goal.predicate.alias.own_args(cx).iter() {
let Some(term) = arg.as_term() else {
continue;
};
match ecx.structurally_normalize_term(goal.param_env, term) {
Ok(term) => {
if term.is_infer() {
return Some(
ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::AMBIGUOUS,
),
);
}
}
Err(NoSolution) => return Some(Err(NoSolution)),
}
}
None
},
|ecx| {
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
this.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
},
)
}
ty::AliasTermKind::InherentTy | ty::AliasTermKind::InherentConst => {
self.normalize_inherent_associated_term(goal)
@ -132,39 +166,7 @@ where
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let cx = ecx.cx();
// FIXME(generic_associated_types): Addresses aggressive inference in #92917.
//
// If this type is a GAT with currently unconstrained arguments, we do not
// want to normalize it via a candidate which only applies for a specific
// instantiation. We could otherwise keep the GAT as rigid and succeed this way.
// See tests/ui/generic-associated-types/no-incomplete-gat-arg-inference.rs.
//
// This only avoids normalization if the GAT arguments are fully unconstrained.
// This is quite arbitrary but fixing it causes some ambiguity, see #125196.
match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
for arg in goal.predicate.alias.own_args(cx).iter() {
let Some(term) = arg.as_term() else {
continue;
};
let term = ecx.structurally_normalize_term(goal.param_env, term)?;
if term.is_infer() {
return ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::AMBIGUOUS,
);
}
}
}
ty::AliasTermKind::OpaqueTy
| ty::AliasTermKind::InherentTy
| ty::AliasTermKind::InherentConst
| ty::AliasTermKind::FreeTy
| ty::AliasTermKind::FreeConst
| ty::AliasTermKind::UnevaluatedConst => {}
}
let projection_pred = assumption.as_projection_clause().unwrap();
let assumption_projection_pred = ecx.instantiate_binder_with_infer(projection_pred);
ecx.eq(goal.param_env, goal.predicate.alias, assumption_projection_pred.projection_term)?;

View file

@ -0,0 +1,45 @@
//@ check-pass
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Regression test for trait-system-refactor-initiative#256. The ambiguous
// GAT arg check previously happened before checking whether the projection
// candidate actually applied in the new trait solver.
//
// This meant we didn't consider `T::Assoc<_>` to be a rigid alias, resulting
// in an inference failure.
pub trait Proj {
type Assoc<T>;
}
trait Id {
type This;
}
impl<T> Id for T {
type This = T;
}
// This previously compiled as the "assumption would incompletely constrain GAT args"
// check happened in each individual assumption after the `DeepRejectCtxt` fast path.
fn with_fast_reject<T, U>(x: T::Assoc<u32>)
where
T: Proj,
U: Proj<Assoc<i32> = u32>,
{
let _: T::Assoc<_> = x;
}
// This previously failed with ambiguity as that check did happen before we actually
// equated the goal with the assumption. Due to the alias in the where-clause
// we didn't fast-reject this candidate.
fn no_fast_reject<T, U>(x: T::Assoc<u32>)
where
T: Proj,
<U as Id>::This: Proj<Assoc<i32> = u32>,
{
let _: T::Assoc<_> = x;
}
fn main() {}