normalization: avoid incompletely constraining GAT args

This commit is contained in:
lcnr
2025-05-06 18:39:07 +00:00
parent 1973872013
commit 31ebe11f61
9 changed files with 111 additions and 23 deletions

View File

@@ -117,6 +117,8 @@ where
) -> Result<Candidate<I>, NoSolution> { ) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?; Self::fast_reject_assumption(ecx, goal, assumption)?;
// Dealing with `ParamEnv` candidates is a bit of a mess as we need to lazily
// check whether the candidate is global.
ecx.probe(|candidate: &Result<Candidate<I>, NoSolution>| match candidate { ecx.probe(|candidate: &Result<Candidate<I>, NoSolution>| match candidate {
Ok(candidate) => inspect::ProbeKind::TraitCandidate { Ok(candidate) => inspect::ProbeKind::TraitCandidate {
source: candidate.source, source: candidate.source,
@@ -128,12 +130,12 @@ where
}, },
}) })
.enter(|ecx| { .enter(|ecx| {
Self::match_assumption(ecx, goal, assumption)?; let mut source = CandidateSource::ParamEnv(ParamEnvSource::Global);
let source = ecx.characterize_param_env_assumption(goal.param_env, assumption)?; let result = Self::match_assumption(ecx, goal, assumption, |ecx| {
Ok(Candidate { source = ecx.characterize_param_env_assumption(goal.param_env, assumption)?;
source, ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
result: ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)?, })?;
}) Ok(Candidate { source, result })
}) })
} }
@@ -150,10 +152,8 @@ where
) -> Result<Candidate<I>, NoSolution> { ) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?; Self::fast_reject_assumption(ecx, goal, assumption)?;
ecx.probe_trait_candidate(source).enter(|ecx| { ecx.probe_trait_candidate(source)
Self::match_assumption(ecx, goal, assumption)?; .enter(|ecx| Self::match_assumption(ecx, goal, assumption, then))
then(ecx)
})
} }
/// Try to reject the assumption based off of simple heuristics, such as [`ty::ClauseKind`] /// Try to reject the assumption based off of simple heuristics, such as [`ty::ClauseKind`]
@@ -169,7 +169,8 @@ where
ecx: &mut EvalCtxt<'_, D>, ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>, goal: Goal<I, Self>,
assumption: I::Clause, assumption: I::Clause,
) -> Result<(), NoSolution>; then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I>;
fn consider_impl_candidate( fn consider_impl_candidate(
ecx: &mut EvalCtxt<'_, D>, ecx: &mut EvalCtxt<'_, D>,

View File

@@ -61,13 +61,14 @@ where
ecx: &mut EvalCtxt<'_, D>, ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>, goal: Goal<I, Self>,
assumption: I::Clause, assumption: I::Clause,
) -> Result<(), NoSolution> { then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let host_clause = assumption.as_host_effect_clause().unwrap(); let host_clause = assumption.as_host_effect_clause().unwrap();
let assumption_trait_pred = ecx.instantiate_binder_with_infer(host_clause); let assumption_trait_pred = ecx.instantiate_binder_with_infer(host_clause);
ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?; ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?;
Ok(()) then(ecx)
} }
/// Register additional assumptions for aliases corresponding to `~const` item bounds. /// Register additional assumptions for aliases corresponding to `~const` item bounds.

View File

@@ -129,7 +129,40 @@ where
ecx: &mut EvalCtxt<'_, D>, ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>, goal: Goal<I, Self>,
assumption: I::Clause, assumption: I::Clause,
) -> Result<(), NoSolution> { 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 projection_pred = assumption.as_projection_clause().unwrap();
let assumption_projection_pred = ecx.instantiate_binder_with_infer(projection_pred); let assumption_projection_pred = ecx.instantiate_binder_with_infer(projection_pred);
@@ -139,7 +172,6 @@ where
// Add GAT where clauses from the trait's definition // Add GAT where clauses from the trait's definition
// FIXME: We don't need these, since these are the type's own WF obligations. // FIXME: We don't need these, since these are the type's own WF obligations.
let cx = ecx.cx();
ecx.add_goals( ecx.add_goals(
GoalSource::AliasWellFormed, GoalSource::AliasWellFormed,
cx.own_predicates_of(goal.predicate.def_id()) cx.own_predicates_of(goal.predicate.def_id())
@@ -147,7 +179,7 @@ where
.map(|pred| goal.with(cx, pred)), .map(|pred| goal.with(cx, pred)),
); );
Ok(()) then(ecx)
} }
fn consider_additional_alias_assumptions( fn consider_additional_alias_assumptions(

View File

@@ -17,7 +17,7 @@ use crate::solve::assembly::{self, AllowInferenceConstraints, AssembleCandidates
use crate::solve::inspect::ProbeKind; use crate::solve::inspect::ProbeKind;
use crate::solve::{ use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause, BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
NoSolution, ParamEnvSource, NoSolution, ParamEnvSource, QueryResult,
}; };
impl<D, I> assembly::GoalKind<D> for TraitPredicate<I> impl<D, I> assembly::GoalKind<D> for TraitPredicate<I>
@@ -150,13 +150,14 @@ where
ecx: &mut EvalCtxt<'_, D>, ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>, goal: Goal<I, Self>,
assumption: I::Clause, assumption: I::Clause,
) -> Result<(), NoSolution> { then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let trait_clause = assumption.as_trait_clause().unwrap(); let trait_clause = assumption.as_trait_clause().unwrap();
let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause); let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause);
ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?; ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?;
Ok(()) then(ecx)
} }
fn consider_auto_trait_candidate( fn consider_auto_trait_candidate(

View File

@@ -1760,12 +1760,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
if is_match { if is_match {
let generics = self.tcx().generics_of(obligation.predicate.def_id); let generics = self.tcx().generics_of(obligation.predicate.def_id);
// FIXME(generic-associated-types): Addresses aggressive inference in #92917. // FIXME(generic_associated_types): Addresses aggressive inference in #92917.
// If this type is a GAT, and of the GAT args resolve to something new, // If this type is a GAT, and of the GAT args resolve to something new,
// that means that we must have newly inferred something about the GAT. // that means that we must have newly inferred something about the GAT.
// We should give up in that case. // We should give up in that case.
// FIXME(generic-associated-types): This only detects one layer of inference, //
// which is probably not what we actually want, but fixing it causes some ambiguity: // This only detects one layer of inference, which is probably not what we actually
// want, but fixing it causes some ambiguity:
// <https://github.com/rust-lang/rust/issues/125196>. // <https://github.com/rust-lang/rust/issues/125196>.
if !generics.is_own_empty() if !generics.is_own_empty()
&& obligation.predicate.args[generics.parent_count..].iter().any(|&p| { && obligation.predicate.args[generics.parent_count..].iter().any(|&p| {

View File

@@ -298,6 +298,14 @@ pub trait GenericArg<I: Interner<GenericArg = Self>>:
+ From<I::Region> + From<I::Region>
+ From<I::Const> + From<I::Const>
{ {
fn as_term(&self) -> Option<I::Term> {
match self.kind() {
ty::GenericArgKind::Lifetime(_) => None,
ty::GenericArgKind::Type(ty) => Some(ty.into()),
ty::GenericArgKind::Const(ct) => Some(ct.into()),
}
}
fn as_type(&self) -> Option<I::Ty> { fn as_type(&self) -> Option<I::Ty> {
if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None } if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None }
} }

View File

@@ -682,6 +682,13 @@ impl<I: Interner> AliasTerm<I> {
pub fn trait_ref(self, interner: I) -> TraitRef<I> { pub fn trait_ref(self, interner: I) -> TraitRef<I> {
self.trait_ref_and_own_args(interner).0 self.trait_ref_and_own_args(interner).0
} }
/// Extract the own args from this projection.
/// For example, if this is a projection of `<T as StreamingIterator>::Item<'a>`,
/// then this function would return the slice `['a]` as the own args.
pub fn own_args(self, interner: I) -> I::GenericArgsSlice {
self.trait_ref_and_own_args(interner).1
}
} }
/// The following methods work only with inherent associated term projections. /// The following methods work only with inherent associated term projections.

View File

@@ -1,5 +1,9 @@
// Fix for <https://github.com/rust-lang/rust/issues/125196>.
//@ check-pass //@ check-pass
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Fix for <https://github.com/rust-lang/rust/issues/125196>.
trait Tr { trait Tr {
type Gat<T>; type Gat<T>;

View File

@@ -0,0 +1,33 @@
//@ check-pass
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Regression test for trait-system-refactor-initiative#202. We have
// to make sure we don't constrain ambiguous GAT args when normalizing
// via where bounds or item bounds.
trait Trait {
type Assoc<U>;
}
fn ret<T: Trait, U>(x: U) -> <T as Trait>::Assoc<U> {
loop {}
}
fn where_bound<T: Trait<Assoc<u32> = u32>>() {
let inf = Default::default();
let x = ret::<T, _>(inf);
let _: i32 = inf;
}
trait ItemBound {
type Bound: Trait<Assoc<u32> = u32>;
}
fn item_bound<T: ItemBound>() {
let inf = Default::default();
let x = ret::<T::Bound, _>(inf);
let _: i32 = inf;
}
fn main() {}