Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
84 changes: 43 additions & 41 deletions compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)?;

Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Loading