Skip to content
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
280 changes: 37 additions & 243 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

Large diffs are not rendered by default.

56 changes: 55 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::marker::PhantomData;

use rustc_type_ir::inherent::*;
use rustc_type_ir::search_graph::CandidateHeadUsages;
use rustc_type_ir::{InferCtxtLike, Interner};
use rustc_type_ir::solve::{Certainty, Goal};
use rustc_type_ir::{self as ty, InferCtxtLike, Interner};
use tracing::instrument;

use crate::canonical::instantiate_and_apply_query_response;
use crate::delegate::SolverDelegate;
use crate::solve::assembly::Candidate;
use crate::solve::eval_ctxt::CurrentGoalKind;
use crate::solve::{
BuiltinImplSource, CandidateSource, EvalCtxt, NoSolution, QueryResult, inspect,
};
Expand Down Expand Up @@ -135,4 +139,54 @@ where
source,
}
}

/// When probing candidates for the `NormalizesTo` goal, the projection term should be
/// fully unconstrained. This helps it by replacing the projection term to an unconstrained
/// inference var, probe with `CurrentGoalKind::NormalizesTo` to handle ambiguous nested
/// goals, instantiate and apply the response and then add those nested goals to the root
/// context.
pub(in crate::solve) fn probe_with_unconstrained_projection_term(
&mut self,
goal: Goal<I, ty::NormalizesTo<I>>,
f: impl FnOnce(&mut EvalCtxt<'_, D, I>, Goal<I, ty::NormalizesTo<I>>) -> QueryResult<I>,
) -> Result<(I::Term, Certainty), NoSolution> {
let cx = self.cx();
let unconstrained_term = self.next_term_infer_of_kind(goal.predicate.term);
let unconstrained_goal = goal
.with(cx, ty::NormalizesTo { alias: goal.predicate.alias, term: unconstrained_term });
let extended_var_values = cx.mk_args_from_iter(
self.var_values.var_values.iter().chain(std::iter::once(unconstrained_term.into())),
);
let mut extended_var_kinds = self.var_kinds.to_vec();
let extra_var_kind = match unconstrained_term.kind() {
ty::TermKind::Ty(_) => ty::CanonicalVarKind::Ty {
ui: self.max_input_universe,
sub_root: ty::BoundVar::from_usize(extended_var_kinds.len()),
},
ty::TermKind::Const(_) => ty::CanonicalVarKind::Const(self.max_input_universe),
};
extended_var_kinds.push(extra_var_kind);
let extended_var_kinds = cx.mk_canonical_var_kinds(&extended_var_kinds);

let resp = self.probe(|_| inspect::ProbeKind::ShadowedEnvProbing).enter(|ecx| {
ecx.current_goal_kind = CurrentGoalKind::NormalizesTo;
ecx.var_values.var_values = extended_var_values;
ecx.var_kinds = extended_var_kinds;
f(ecx, unconstrained_goal)
})?;

let (nested_goals, certainty) = instantiate_and_apply_query_response(
self.delegate,
goal.param_env,
extended_var_values.as_slice(),
resp,
self.origin_span,
);

for (source, nested_goal) in nested_goals.0 {
self.add_goal(source, nested_goal);
}

Ok((unconstrained_term, certainty))
}
}
12 changes: 4 additions & 8 deletions compiler/rustc_next_trait_solver/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,20 +344,16 @@ where
param_env: I::ParamEnv,
term: I::Term,
) -> Result<I::Term, NoSolution> {
if let Some(_) = term.to_alias_term() {
if let Some(alias) = term.to_alias_term() {
let normalized_term = self.next_term_infer_of_kind(term);
let alias_relate_goal = Goal::new(
let normalizes_to_goal = Goal::new(
self.cx(),
param_env,
ty::PredicateKind::AliasRelate(
term,
normalized_term,
ty::AliasRelationDirection::Equate,
),
ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term: normalized_term }),
);
// We normalize the self type to be able to relate it with
// types from candidates.
self.add_goal(GoalSource::TypeRelating, alias_relate_goal);
self.add_goal(GoalSource::TypeRelating, normalizes_to_goal);
self.try_evaluate_added_goals()?;
Ok(self.resolve_vars_if_possible(normalized_term))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ where
goal.predicate.alias.args,
),
) {
self.instantiate_normalizes_to_term(goal, normalized_const.into());
self.eq(goal.param_env, goal.predicate.term, normalized_const.into())?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} else {
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ where
cx.const_of_item(free_alias.def_id).instantiate(cx, free_alias.args).into()
};

self.instantiate_normalizes_to_term(goal, actual);
self.eq(goal.param_env, goal.predicate.term, actual)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ where
} else {
cx.const_of_item(inherent.def_id).instantiate(cx, inherent_args).into()
};
self.instantiate_normalizes_to_term(goal, normalized);
self.eq(goal.param_env, goal.predicate.term, normalized)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
}
97 changes: 54 additions & 43 deletions compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ where
&mut self,
goal: Goal<I, NormalizesTo<I>>,
) -> QueryResult<I> {
debug_assert!(self.term_is_fully_unconstrained(goal));
let cx = self.cx();
match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
Expand All @@ -39,49 +38,65 @@ 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| {
// 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(

let (probed_term, certainty) =
self.probe_with_unconstrained_projection_term(goal, |ecx, goal| {
ecx.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)),
}
}
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)
})
},
)
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,
)
},
)
},
)
})?;

self.eq_structurally_relating_aliases(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 that assumes that the expected term is normalized, currently not a problem as we don't yet use this for Projection goals.

If we use normal equate there this would go back into alias-relate, which would then recursively call this. We could explicitly normalize the term, but that also seems like a mess

I guess keep it as is, add FIXME(trait-system-refactor-initiative#223) and then we finish the migration after https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/Eager.20normalization.2C.20ahoy.21/with/583000386

goal.param_env,
goal.predicate.term,
probed_term,
)?;

self.evaluate_added_goals_and_make_canonical_response(certainty)
}
ty::AliasTermKind::InherentTy | ty::AliasTermKind::InherentConst => {
self.normalize_inherent_associated_term(goal)
Expand All @@ -99,18 +114,14 @@ where
/// We know `term` to always be a fully unconstrained inference variable, so
/// `eq` should never fail here. However, in case `term` contains aliases, we
/// emit nested `AliasRelate` goals to structurally normalize the alias.
pub fn instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, NormalizesTo<I>>,
term: I::Term,
) {
fn instantiate_normalizes_to_term(&mut self, goal: Goal<I, NormalizesTo<I>>, term: I::Term) {
self.eq(goal.param_env, goal.predicate.term, term)
.expect("expected goal term to be fully unconstrained");
}

/// Unlike `instantiate_normalizes_to_term` this instantiates the expected term
/// with a rigid alias. Using this is pretty much always wrong.
pub fn structurally_instantiate_normalizes_to_term(
fn structurally_instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, NormalizesTo<I>>,
term: ty::AliasTerm<I>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ where
.filter(|&def_id| defining_opaque_types.contains(&def_id))
else {
// If we're not in the defining scope, treat the alias as rigid.
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
self.relate_rigid_alias_non_alias(
goal.param_env,
goal.predicate.alias,
ty::Invariant,
goal.predicate.term,
)?;
Copy link
Copy Markdown
Member Author

@ShoyuVanilla ShoyuVanilla Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those replacements of asserted funtions have some reason

pub fn instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, NormalizesTo<I>>,
term: I::Term,
) {
self.eq(goal.param_env, goal.predicate.term, term)
.expect("expected goal term to be fully unconstrained");
}
/// Unlike `instantiate_normalizes_to_term` this instantiates the expected term
/// with a rigid alias. Using this is pretty much always wrong.
pub fn structurally_instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, NormalizesTo<I>>,
term: ty::AliasTerm<I>,
) {
self.relate_rigid_alias_non_alias(goal.param_env, term, ty::Invariant, goal.predicate.term)
.expect("expected goal term to be fully unconstrained");

As we are now using NormalizesTo to normalize things, it can be registered into ObligationCtxt from the outside of the solver. Usually, they don't break the assertion as normalization inserts a fresh infer var as RHS.

But check_potentially_region_dependent_goals can make ICEs here, as when it is called RHS of a NormalizesTo is not an unconstrained infer var anymore due to HIR typeck and the eq relation may fail because of region errors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, structurally_instantiate_normalizes_to_term only makes sense in probe_with_unconstrained_projection_term 🤔

Also needs a FIXME(trait-system-refactor-initiative#223) for now. The rhs may be unnormalized if we were to use this code path for Projection goals. Please add this for all the places where we relate the normalized to term with the rhs, they would all be wrong if the rhs hasn't been normalized yet

return self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
};

Expand Down Expand Up @@ -111,7 +116,12 @@ where
.as_local()
.filter(|&def_id| defined_opaque_types.contains(&def_id))
else {
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
self.relate_rigid_alias_non_alias(
goal.param_env,
goal.predicate.alias,
ty::Invariant,
goal.predicate.term,
)?;
return self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,13 @@ impl<'tcx> BestObligation<'tcx> {
) -> ControlFlow<PredicateObligation<'tcx>> {
assert!(!self.consider_ambiguities);
let tcx = goal.infcx().tcx;
if let ty::Alias(..) = self_ty.kind() {
let term: ty::Term<'tcx> = self_ty.into();
if let Some(alias) = term.to_alias_term() {
let infer_term = goal.infcx().next_ty_var(self.obligation.cause.span);
let pred = ty::PredicateKind::AliasRelate(
self_ty.into(),
infer_term.into(),
ty::AliasRelationDirection::Equate,
);
let pred = ty::PredicateKind::NormalizesTo(ty::NormalizesTo {
alias,
term: infer_term.into(),
});
let obligation =
Obligation::new(tcx, self.obligation.cause.clone(), goal.goal().param_env, pred);
self.with_derived_obligation(obligation, |this| {
Expand Down
11 changes: 3 additions & 8 deletions compiler/rustc_trait_selection/src/solve/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,10 @@ where
let infcx = self.at.infcx;
let tcx = infcx.tcx;
let recursion_limit = tcx.recursion_limit();
let alias = alias_term.to_alias_term().unwrap();
if !recursion_limit.value_within_limit(self.depth) {
let term = alias_term.to_alias_term().unwrap();

self.at.infcx.err_ctxt().report_overflow_error(
OverflowCause::DeeplyNormalize(term),
OverflowCause::DeeplyNormalize(alias),
self.at.cause.span,
true,
|_| {},
Expand All @@ -120,11 +119,7 @@ where
tcx,
self.at.cause.clone(),
self.at.param_env,
ty::PredicateKind::AliasRelate(
alias_term.into(),
infer_term.into(),
ty::AliasRelationDirection::Equate,
),
ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term: infer_term }),
);

self.fulfill_cx.register_predicate_obligation(infcx, obligation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ impl<'tcx> At<'_, 'tcx> {
assert!(!term.is_infer(), "should have resolved vars before calling");

if self.infcx.next_trait_solver() {
if let None = term.to_alias_term() {
let Some(alias) = term.to_alias_term() else {
return Ok(term);
}
};

let new_infer = self.infcx.next_term_var_of_kind(term, self.cause.span);

Expand All @@ -48,7 +48,7 @@ impl<'tcx> At<'_, 'tcx> {
self.infcx.tcx,
self.cause.clone(),
self.param_env,
ty::PredicateKind::AliasRelate(term, new_infer, ty::AliasRelationDirection::Equate),
ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term: new_infer }),
);

fulfill_cx.register_predicate_obligation(self.infcx, obligation);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error[E0284]: type annotations needed: cannot normalize `<dyn Object<U, Output = T> as Object<U>>::Output`
error[E0284]: type annotations needed: cannot satisfy `<dyn Object<U, Output = T> as Object<U>>::Output normalizes-to T`
--> $DIR/indirect-impl-for-trait-obj-coherence.rs:25:41
|
LL | foo::<dyn Object<U, Output = T>, U>(x)
| ^ cannot normalize `<dyn Object<U, Output = T> as Object<U>>::Output`
| ^ cannot satisfy `<dyn Object<U, Output = T> as Object<U>>::Output normalizes-to T`

error: aborting due to 1 previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn foo<T: ?Sized, U>(x: <T as Object<U>>::Output) -> U {
#[allow(dead_code)]
fn transmute<T, U>(x: T) -> U {
foo::<dyn Object<U, Output = T>, U>(x)
//[next]~^ ERROR type annotations needed: cannot normalize `<dyn Object<U, Output = T> as Object<U>>::Output`
//[next]~^ ERROR type annotations needed: cannot satisfy `<dyn Object<U, Output = T> as Object<U>>::Output normalizes-to T`
}

fn main() {}
4 changes: 2 additions & 2 deletions tests/ui/coherence/occurs-check/associated-type.next.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ LL | | for<'a> *const T: ToUnit<'a>,
|
= note: this behavior recently changed as a result of a bug fix; see rust-lang/rust#56105 for details

error[E0284]: type annotations needed: cannot normalize `<for<'a> fn(&'a (), ()) as Overlap<for<'a> fn(&'a (), ())>>::Assoc`
error[E0284]: type annotations needed: cannot satisfy `<for<'a> fn(&'a (), ()) as Overlap<for<'a> fn(&'a (), ())>>::Assoc normalizes-to usize`
--> $DIR/associated-type.rs:45:59
|
LL | foo::<for<'a> fn(&'a (), ()), for<'a> fn(&'a (), ())>(3usize);
| ^^^^^^ cannot normalize `<for<'a> fn(&'a (), ()) as Overlap<for<'a> fn(&'a (), ())>>::Assoc`
| ^^^^^^ cannot satisfy `<for<'a> fn(&'a (), ()) as Overlap<for<'a> fn(&'a (), ())>>::Assoc normalizes-to usize`

error: aborting due to 2 previous errors

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/coherence/occurs-check/associated-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@ fn foo<T: Overlap<U>, U>(x: T::Assoc) -> T::Assoc {

fn main() {
foo::<for<'a> fn(&'a (), ()), for<'a> fn(&'a (), ())>(3usize);
//[next]~^ ERROR: cannot normalize
//[next]~^ ERROR: type annotations needed: cannot satisfy `<for<'a> fn(&'a (), ()) as Overlap<for<'a> fn(&'a (), ())>>::Assoc normalizes-to usize`
}
Loading
Loading