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
9 changes: 8 additions & 1 deletion compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3041,7 +3041,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
span: Span,
) -> Result<(), ErrorGuaranteed> {
let tcx = self.tcx();
if tcx.is_type_const(def_id) {
// FIXME(gca): Intentionally disallowing paths to inherent associated non-type constants
// until a refactoring for how generic args for IACs are represented has been landed.
let is_inherent_assoc_const = tcx.def_kind(def_id)
== DefKind::AssocConst { is_type_const: false }
&& tcx.def_kind(tcx.parent(def_id)) == DefKind::Impl { of_trait: false };
if tcx.is_type_const(def_id)
|| tcx.features().generic_const_args() && !is_inherent_assoc_const
{
Ok(())
} else {
let mut err = self.dcx().struct_span_err(
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/context/impl_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
fn type_of_opaque_hir_typeck(self, def_id: LocalDefId) -> ty::EarlyBinder<'tcx, Ty<'tcx>> {
self.type_of_opaque_hir_typeck(def_id)
}
fn is_type_const(self, def_id: DefId) -> bool {
self.is_type_const(def_id)
}
fn const_of_item(self, def_id: DefId) -> ty::EarlyBinder<'tcx, Const<'tcx>> {
self.const_of_item(def_id)
}
Expand Down
27 changes: 27 additions & 0 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,33 @@ where
self.delegate.evaluate_const(param_env, uv)
}

pub(super) fn evaluate_const_and_instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, ty::NormalizesTo<I>>,
uv: ty::UnevaluatedConst<I>,
) -> QueryResult<I> {
match self.delegate.evaluate_const(goal.param_env, uv) {
Some(evaluated) => {
self.instantiate_normalizes_to_term(goal, evaluated.into());
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
None => {
if uv.has_non_region_infer() {
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
} else {
// We do not instantiate to the `uv` passed in, but rather
// `goal.predicate.alias`. The `uv` passed in might correspond to the `impl`
// form of a constant (with generic arguments corresponding to the impl block),
// however, we want to structurally instantiate to the original, non-rebased,
// trait `Self` form of the constant (with generic arguments being the trait
// `Self` type).
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
Copy link
Copy Markdown
Contributor Author

@khyperia khyperia Apr 24, 2026

Choose a reason for hiding this comment

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

I just realized I should add a comment here pointing out it's intentionally instantiating to goal.predicate.alias rather than the passed in uv (very important distinction, for the same reasons as the old solver reason we're using try_evaluate_const instead of evaluate_const)

View changes since the review

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

pub(super) fn is_transmutable(
&mut self,
src: I::Ty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ where
&mut self,
goal: Goal<I, ty::NormalizesTo<I>>,
) -> QueryResult<I> {
if let Some(normalized_const) = self.evaluate_const(
goal.param_env,
ty::UnevaluatedConst::new(
goal.predicate.alias.def_id().try_into().unwrap(),
goal.predicate.alias.args,
),
) {
self.instantiate_normalizes_to_term(goal, normalized_const.into());
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
let cx = self.cx();
let uv = goal.predicate.alias.expect_ct(self.cx());
// keep legacy behavior for array repeat expressions:
// when a constant is too generic to be evaluated, the legacy behavior is to return
// Ambiguous, whereas evaluate_const_and_instantiate_normalizes_to_term structurally
// instantiates to itself and returns Yes (if there are no inference variables)
let is_repeat_expr =
cx.anon_const_kind(uv.def.into()) == ty::AnonConstKind::RepeatExprCount;
if is_repeat_expr {
if let Some(normalized_const) = self.evaluate_const(goal.param_env, uv) {
self.instantiate_normalizes_to_term(goal, normalized_const.into());
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} else {
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
}
} else {
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
self.evaluate_const_and_instantiate_normalizes_to_term(goal, uv)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ where
.map(|pred| goal.with(cx, pred)),
);

let actual = if free_alias.kind(cx).is_type() {
cx.type_of(free_alias.def_id()).instantiate(cx, free_alias.args).skip_norm_wip().into()
} else {
cx.const_of_item(free_alias.def_id())
.instantiate(cx, free_alias.args)
.skip_norm_wip()
.into()
let actual = match free_alias.kind(cx) {
ty::AliasTermKind::FreeTy { def_id } => {
cx.type_of(def_id).instantiate(cx, free_alias.args).skip_norm_wip().into()
}
ty::AliasTermKind::FreeConst { def_id } if cx.is_type_const(def_id) => {
cx.const_of_item(def_id).instantiate(cx, free_alias.args).skip_norm_wip().into()
}
ty::AliasTermKind::FreeConst { .. } => {
return self.evaluate_const_and_instantiate_normalizes_to_term(
goal,
free_alias.expect_ct(cx),
);
}
kind => panic!("expected free alias, found {kind:?}"),
};

self.instantiate_normalizes_to_term(goal, actual);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,23 @@ where
.map(|pred| goal.with(cx, pred)),
);

let normalized = if inherent.kind(cx).is_type() {
cx.type_of(inherent.def_id()).instantiate(cx, inherent_args).skip_norm_wip().into()
} else {
cx.const_of_item(inherent.def_id())
.instantiate(cx, inherent_args)
.skip_norm_wip()
.into()
let normalized = match inherent.kind(cx) {
ty::AliasTermKind::InherentTy { def_id } => {
cx.type_of(def_id).instantiate(cx, inherent_args).skip_norm_wip().into()
}
ty::AliasTermKind::InherentConst { def_id } if cx.is_type_const(def_id) => {
cx.const_of_item(def_id).instantiate(cx, inherent_args).skip_norm_wip().into()
}
ty::AliasTermKind::InherentConst { .. } => {
// FIXME(gca): This is dead code at the moment. It should eventually call
// self.evaluate_const like projected consts do in consider_impl_candidate in
// normalizes_to/mod.rs. However, how generic args are represented for IACs is up in
// the air right now.
// Will self.evaluate_const eventually take the inherent_args or the impl_args form
// of args? It might be either.
panic!("References to inherent associated consts should have been blocked");
}
kind => panic!("expected inherent alias, found {kind:?}"),
};
self.instantiate_normalizes_to_term(goal, normalized);
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
Expand Down
41 changes: 34 additions & 7 deletions compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,19 +384,46 @@ where

// Finally we construct the actual value of the associated type.
let term = match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy { .. } => {
cx.type_of(target_item_def_id).map_bound(|ty| ty.into())
ty::AliasTermKind::ProjectionTy { .. } => cx
.type_of(target_item_def_id)
.instantiate(cx, target_args)
.skip_norm_wip()
.into(),
ty::AliasTermKind::ProjectionConst { .. }
if cx.is_type_const(target_item_def_id) =>
{
cx.const_of_item(target_item_def_id)
.instantiate(cx, target_args)
.skip_norm_wip()
.into()
}
ty::AliasTermKind::ProjectionConst { .. } => {
cx.const_of_item(target_item_def_id).map_bound(|ct| ct.into())
// target_args contains vars:
// - the Self type of the impl block is instantiated with fresh vars
// - the resulting type is eq'd against the actual Self type
// - the fresh vars are then used as target_args
// we need the actual args to run const eval, so we need to actually do the `eq`
// and figure out the args, so, call try_evaluate_added_goals
ecx.try_evaluate_added_goals()?;
// HACK(khyperia): this shouldn't be necessary, `try_evaluate_const` calls
// `resolve_vars_if_possible`. However, on failure,
// `evaluate_const_and_instantiate` then checks `has_non_region_infer` on the
// *pre*-`resolve_vars_if_possible` args, which, we want to return false if we
// successfully identified the actual type.
// Perhaps we could split EvaluateConstErr::HasGenericsOrInfers into HasGenerics
// and HasInfers or something, and make evaluate_const_and_instantiate change
// its behavior based on that, rather than it checking `has_non_region_infer`.
let target_args = ecx.resolve_vars_if_possible(target_args);
Copy link
Copy Markdown
Contributor Author

@khyperia khyperia Apr 22, 2026

Choose a reason for hiding this comment

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

@BoxyUwU this is annoying and gross (see HACK comment), resolve_vars_if_possible is called twice on target_args. I could maybe clean this up in a follow-up PR? Or I could gut/refactor in this PR. I would slightly prefer doing it in a follow-up, but, let me know!

View changes since the review

let uv = ty::UnevaluatedConst::new(
target_item_def_id.try_into().unwrap(),
target_args,
);
return ecx.evaluate_const_and_instantiate_normalizes_to_term(goal, uv);
}
kind => panic!("expected projection, found {kind:?}"),
};

ecx.instantiate_normalizes_to_term(
goal,
term.instantiate(cx, target_args).skip_norm_wip(),
);
ecx.instantiate_normalizes_to_term(goal, term);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,10 @@ pub fn try_evaluate_const<'tcx>(
// logic does not go through type system normalization. If it did this would
// be a backwards compatibility problem as we do not enforce "syntactic" non-
// usage of generic parameters like we do here.
if uv.args.has_non_region_param() || uv.args.has_non_region_infer() {
if uv.args.has_non_region_param()
|| uv.args.has_non_region_infer()
|| uv.args.has_non_region_placeholders()
{
return Err(EvaluateConstErr::HasGenericsOrInfers);
}

Expand Down
21 changes: 13 additions & 8 deletions compiler/rustc_trait_selection/src/traits/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,22 +339,26 @@ impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> {
}),
);
self.depth += 1;
let res = if free.kind(infcx.tcx).is_type() {
infcx
let res = match free.kind(infcx.tcx) {
ty::AliasTermKind::FreeTy { def_id } => infcx
.tcx
.type_of(free.def_id())
.type_of(def_id)
.instantiate(infcx.tcx, free.args)
.skip_norm_wip()
.fold_with(self)
.into()
} else {
infcx
.into(),
ty::AliasTermKind::FreeConst { def_id } if infcx.tcx.is_type_const(def_id) => infcx
.tcx
.const_of_item(free.def_id())
.const_of_item(def_id)
.instantiate(infcx.tcx, free.args)
.skip_norm_wip()
.fold_with(self)
.into()
.into(),
ty::AliasTermKind::FreeConst { .. } => {
super::evaluate_const(infcx, free.to_term(infcx.tcx).expect_const(), self.param_env)
.into()
}
kind => panic!("expected free alias, found {kind:?}"),
};
self.depth -= 1;
res
Expand All @@ -376,6 +380,7 @@ impl<'a, 'b, 'tcx> TypeFolder<TyCtxt<'tcx>> for AssocTypeNormalizer<'a, 'b, 'tcx
t
}

#[instrument(skip(self), level = "debug")]
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
if !needs_normalization(self.selcx.infcx, &ty) {
return ty;
Expand Down
67 changes: 55 additions & 12 deletions compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,22 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>(
));
}

let term: Term<'tcx> = if alias_term.kind(tcx).is_type() {
tcx.type_of(alias_term.def_id()).instantiate(tcx, args).skip_norm_wip().into()
} else {
tcx.const_of_item(alias_term.def_id()).instantiate(tcx, args).skip_norm_wip().into()
let term: Term<'tcx> = match alias_term.kind(tcx) {
ty::AliasTermKind::InherentTy { def_id } => {
tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip().into()
}
ty::AliasTermKind::InherentConst { def_id } if tcx.is_type_const(def_id) => {
tcx.const_of_item(def_id).instantiate(tcx, args).skip_norm_wip().into()
}
ty::AliasTermKind::InherentConst { .. } => {
// FIXME(gca): This is dead code at the moment. It should eventually call
// super::evaluate_const like projected consts do in confirm_impl_candidate in this
// file. However, how generic args are represented for IACs is up in the air right now.
// Will super::evaluate_const eventually take the inherent_args or the impl_args form of
// args? It might be either.
panic!("References to inherent associated consts should have been blocked");
}
kind => panic!("expected inherent alias, found {kind:?}"),
};

let mut term = selcx.infcx.resolve_vars_if_possible(term);
Expand Down Expand Up @@ -613,11 +625,13 @@ pub fn compute_inherent_assoc_term_args<'a, 'b, 'tcx>(
alias_term.rebase_inherent_args_onto_impl(impl_args, tcx)
}

#[derive(Debug)]
enum Projected<'tcx> {
Progress(Progress<'tcx>),
NoProgress(ty::Term<'tcx>),
}

#[derive(Debug)]
struct Progress<'tcx> {
term: ty::Term<'tcx>,
obligations: PredicateObligations<'tcx>,
Expand Down Expand Up @@ -648,7 +662,7 @@ impl<'tcx> Progress<'tcx> {
/// IMPORTANT:
/// - `obligation` must be fully normalized
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "info", skip(selcx))]
#[instrument(level = "info", ret, skip(selcx))]
fn project<'cx, 'tcx>(
selcx: &mut SelectionContext<'cx, 'tcx>,
obligation: &ProjectionTermObligation<'tcx>,
Expand Down Expand Up @@ -2029,12 +2043,6 @@ fn confirm_impl_candidate<'cx, 'tcx>(
let args = obligation.predicate.args.rebase_onto(tcx, trait_def_id, args);
let args = translate_args(selcx.infcx, param_env, impl_def_id, args, assoc_term.defining_node);

let term = if obligation.predicate.kind(tcx).is_type() {
tcx.type_of(assoc_term.item.def_id).map_bound(|ty| ty.into())
} else {
tcx.const_of_item(assoc_term.item.def_id).map_bound(|ct| ct.into())
};

let progress = if !tcx.check_args_compatible(assoc_term.item.def_id, args) {
let msg = "impl item and trait item have different parameters";
let span = obligation.cause.span;
Expand All @@ -2046,7 +2054,42 @@ fn confirm_impl_candidate<'cx, 'tcx>(
Progress { term: err, obligations: nested }
} else {
assoc_term_own_obligations(selcx, obligation, &mut nested);
Progress { term: term.instantiate(tcx, args).skip_norm_wip(), obligations: nested }

let term = match obligation.predicate.kind(tcx) {
ty::AliasTermKind::ProjectionTy { .. } => {
tcx.type_of(assoc_term.item.def_id).instantiate(tcx, args).skip_norm_wip().into()
}
ty::AliasTermKind::ProjectionConst { .. }
if tcx.is_type_const(assoc_term.item.def_id) =>
{
tcx.const_of_item(assoc_term.item.def_id)
.instantiate(tcx, args)
.skip_norm_wip()
.into()
}
ty::AliasTermKind::ProjectionConst { .. } => {
let uv = ty::UnevaluatedConst::new(assoc_term.item.def_id, args);
let ct = ty::Const::new_unevaluated(tcx, uv);
// We don't want to use super::evaluate_const, because that returns its parameter
// unchanged if it is too generic to evaluate. We are passing the `impl` form of the
// constant to evaluate_const (with generic arguments corresponding to the impl
// block), but we want to return the original, non-rebased, trait `Self` form of the
// constant (with generic arguments being the trait `Self` type) in Projected::NoProgress.
match super::try_evaluate_const(selcx.infcx, ct, param_env) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you add a comment about why we're not just using evaluate_const.

Ok(evaluated) => evaluated.into(),
Err(
super::EvaluateConstErr::EvaluationFailure(e)
| super::EvaluateConstErr::InvalidConstParamTy(e),
) => ty::Const::new_error(tcx, e).into(),
Err(super::EvaluateConstErr::HasGenericsOrInfers) => {
return Ok(Projected::NoProgress(obligation.predicate.to_term(tcx)));
}
}
}
kind => panic!("expected projection alias, found {kind:?}"),
};

Progress { term, obligations: nested }
};
Ok(Projected::Progress(progress))
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/wf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
ty::ConstKind::Unevaluated(uv) => {
if !c.has_escaping_bound_vars() {
// Skip type consts as mGCA doesn't support evaluatable clauses
if !tcx.is_type_const(uv.def) {
if !tcx.is_type_const(uv.def) && !tcx.features().generic_const_args() {
let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::ConstEvaluatable(c),
));
Expand Down
Loading
Loading