Skip to content

normalization: avoid incompletely constraining GAT args #140712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
38 changes: 19 additions & 19 deletions compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub(super) mod structural_traits;

use std::cell::Cell;
use std::ops::ControlFlow;

use derive_where::derive_where;
Expand Down Expand Up @@ -117,24 +118,24 @@ where
) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?;

ecx.probe(|candidate: &Result<Candidate<I>, NoSolution>| match candidate {
Ok(candidate) => inspect::ProbeKind::TraitCandidate {
source: candidate.source,
result: Ok(candidate.result),
},
Err(NoSolution) => inspect::ProbeKind::TraitCandidate {
source: CandidateSource::ParamEnv(ParamEnvSource::Global),
result: Err(NoSolution),
},
// Dealing with `ParamEnv` candidates is a bit of a mess as we need to lazily
// check whether the candidate is global while considering normalization.
//
// We need to write into `source` inside of `match_assumption`, but need to access it
// in `probe` even if the candidate does not apply before we get there. We handle this
// by using a `Cell` here. We only ever write into it inside of `match_assumption`.
let source = Cell::new(CandidateSource::ParamEnv(ParamEnvSource::Global));
ecx.probe(|result: &QueryResult<I>| inspect::ProbeKind::TraitCandidate {
source: source.get(),
result: *result,
})
.enter(|ecx| {
Self::match_assumption(ecx, goal, assumption)?;
let source = ecx.characterize_param_env_assumption(goal.param_env, assumption)?;
Ok(Candidate {
source,
result: ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)?,
Self::match_assumption(ecx, goal, assumption, |ecx| {
source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
})
.map(|result| Candidate { source: source.get(), result })
}

/// Try equating an assumption predicate against a goal's predicate. If it
Expand All @@ -150,10 +151,8 @@ where
) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?;

ecx.probe_trait_candidate(source).enter(|ecx| {
Self::match_assumption(ecx, goal, assumption)?;
then(ecx)
})
ecx.probe_trait_candidate(source)
.enter(|ecx| Self::match_assumption(ecx, goal, assumption, then))
}

/// Try to reject the assumption based off of simple heuristics, such as [`ty::ClauseKind`]
Expand All @@ -169,7 +168,8 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<(), NoSolution>;
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I>;

fn consider_impl_candidate(
ecx: &mut EvalCtxt<'_, D>,
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
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 assumption_trait_pred = ecx.instantiate_binder_with_infer(host_clause);
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.
Expand Down
38 changes: 35 additions & 3 deletions compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,40 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
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 assumption_projection_pred = ecx.instantiate_binder_with_infer(projection_pred);
Expand All @@ -139,15 +172,14 @@ where

// Add GAT where clauses from the trait's definition
// FIXME: We don't need these, since these are the type's own WF obligations.
let cx = ecx.cx();
ecx.add_goals(
GoalSource::AliasWellFormed,
cx.own_predicates_of(goal.predicate.def_id())
.iter_instantiated(cx, goal.predicate.alias.args)
.map(|pred| goal.with(cx, pred)),
);

Ok(())
then(ecx)
}

fn consider_additional_alias_assumptions(
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::solve::assembly::{self, AllowInferenceConstraints, AssembleCandidates
use crate::solve::inspect::ProbeKind;
use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
NoSolution, ParamEnvSource,
NoSolution, ParamEnvSource, QueryResult,
};

impl<D, I> assembly::GoalKind<D> for TraitPredicate<I>
Expand Down Expand Up @@ -150,13 +150,14 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<(), NoSolution> {
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let trait_clause = assumption.as_trait_clause().unwrap();

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)?;

Ok(())
then(ecx)
}

fn consider_auto_trait_candidate(
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_trait_selection/src/traits/select/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1760,12 +1760,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {

if is_match {
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,
// that means that we must have newly inferred something about the GAT.
// 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>.
if !generics.is_own_empty()
&& obligation.predicate.args[generics.parent_count..].iter().any(|&p| {
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_type_ir/src/inherent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,14 @@ pub trait GenericArg<I: Interner<GenericArg = Self>>:
+ From<I::Region>
+ 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> {
if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None }
}
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_type_ir/src/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,13 @@ impl<I: Interner> AliasTerm<I> {
pub fn trait_ref(self, interner: I) -> TraitRef<I> {
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// Fix for <https://github.com/rust-lang/rust/issues/125196>.
//@ 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 {
type Gat<T>;
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Loading