mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-26 13:01:27 +03:00
510 lines
21 KiB
Rust
510 lines
21 KiB
Rust
//! Deeply normalize types using the old trait solver.
|
|
|
|
use rustc_data_structures::stack::ensure_sufficient_stack;
|
|
use rustc_errors::msg;
|
|
use rustc_hir::def::DefKind;
|
|
use rustc_infer::infer::at::At;
|
|
use rustc_infer::infer::{InferCtxt, InferOk};
|
|
use rustc_infer::traits::{
|
|
FromSolverError, Normalized, Obligation, PredicateObligations, TraitEngine,
|
|
};
|
|
use rustc_macros::extension;
|
|
use rustc_middle::span_bug;
|
|
use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
|
|
use rustc_middle::ty::{
|
|
self, AliasTerm, Term, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
|
|
TypeVisitableExt, TypingMode, Unnormalized,
|
|
};
|
|
use tracing::{debug, instrument};
|
|
|
|
use super::{BoundVarReplacer, PlaceholderReplacer, SelectionContext, project};
|
|
use crate::error_reporting::InferCtxtErrorExt;
|
|
use crate::error_reporting::traits::OverflowCause;
|
|
use crate::solve::NextSolverError;
|
|
|
|
#[extension(pub trait NormalizeExt<'tcx>)]
|
|
impl<'tcx> At<'_, 'tcx> {
|
|
/// Normalize a value using the `AssocTypeNormalizer`.
|
|
///
|
|
/// This normalization should be used when the type contains inference variables or the
|
|
/// projection may be fallible.
|
|
fn normalize<T: TypeFoldable<TyCtxt<'tcx>>>(
|
|
&self,
|
|
value: Unnormalized<'tcx, T>,
|
|
) -> InferOk<'tcx, T> {
|
|
let value = value.skip_normalization();
|
|
if self.infcx.next_trait_solver() {
|
|
InferOk { value, obligations: PredicateObligations::new() }
|
|
} else {
|
|
let mut selcx = SelectionContext::new(self.infcx);
|
|
let Normalized { value, obligations } =
|
|
normalize_with_depth(&mut selcx, self.param_env, self.cause.clone(), 0, value);
|
|
InferOk { value, obligations }
|
|
}
|
|
}
|
|
|
|
/// Deeply normalizes `value`, replacing all aliases which can by normalized in
|
|
/// the current environment. In the new solver this errors in case normalization
|
|
/// fails or is ambiguous.
|
|
///
|
|
/// In the old solver this simply uses `normalizes` and adds the nested obligations
|
|
/// to the `fulfill_cx`. This is necessary as we otherwise end up recomputing the
|
|
/// same goals in both a temporary and the shared context which negatively impacts
|
|
/// performance as these don't share caching.
|
|
///
|
|
/// FIXME(-Znext-solver=no): For performance reasons, we currently reuse an existing
|
|
/// fulfillment context in the old solver. Once we have removed the old solver, we
|
|
/// can remove the `fulfill_cx` parameter on this function.
|
|
fn deeply_normalize<T, E>(
|
|
self,
|
|
value: Unnormalized<'tcx, T>,
|
|
fulfill_cx: &mut dyn TraitEngine<'tcx, E>,
|
|
) -> Result<T, Vec<E>>
|
|
where
|
|
T: TypeFoldable<TyCtxt<'tcx>>,
|
|
E: FromSolverError<'tcx, NextSolverError<'tcx>>,
|
|
{
|
|
if self.infcx.next_trait_solver() {
|
|
crate::solve::deeply_normalize(self, value)
|
|
} else {
|
|
if fulfill_cx.has_pending_obligations() {
|
|
let pending_obligations = fulfill_cx.pending_obligations();
|
|
span_bug!(
|
|
pending_obligations[0].cause.span,
|
|
"deeply_normalize should not be called with pending obligations: \
|
|
{pending_obligations:#?}"
|
|
);
|
|
}
|
|
let value = self
|
|
.normalize(value)
|
|
.into_value_registering_obligations(self.infcx, &mut *fulfill_cx);
|
|
let errors = fulfill_cx.evaluate_obligations_error_on_ambiguity(self.infcx);
|
|
let value = self.infcx.resolve_vars_if_possible(value);
|
|
if errors.is_empty() {
|
|
Ok(value)
|
|
} else {
|
|
// Drop pending obligations, since deep normalization may happen
|
|
// in a loop and we don't want to trigger the assertion on the next
|
|
// iteration due to pending ambiguous obligations we've left over.
|
|
let _ = fulfill_cx.collect_remaining_errors(self.infcx);
|
|
Err(errors)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// As `normalize`, but with a custom depth.
|
|
pub(crate) fn normalize_with_depth<'a, 'b, 'tcx, T>(
|
|
selcx: &'a mut SelectionContext<'b, 'tcx>,
|
|
param_env: ty::ParamEnv<'tcx>,
|
|
cause: ObligationCause<'tcx>,
|
|
depth: usize,
|
|
value: T,
|
|
) -> Normalized<'tcx, T>
|
|
where
|
|
T: TypeFoldable<TyCtxt<'tcx>>,
|
|
{
|
|
let mut obligations = PredicateObligations::new();
|
|
let value = normalize_with_depth_to(selcx, param_env, cause, depth, value, &mut obligations);
|
|
Normalized { value, obligations }
|
|
}
|
|
|
|
#[instrument(level = "info", skip(selcx, param_env, cause, obligations))]
|
|
pub(crate) fn normalize_with_depth_to<'a, 'b, 'tcx, T>(
|
|
selcx: &'a mut SelectionContext<'b, 'tcx>,
|
|
param_env: ty::ParamEnv<'tcx>,
|
|
cause: ObligationCause<'tcx>,
|
|
depth: usize,
|
|
value: T,
|
|
obligations: &mut PredicateObligations<'tcx>,
|
|
) -> T
|
|
where
|
|
T: TypeFoldable<TyCtxt<'tcx>>,
|
|
{
|
|
debug!(obligations.len = obligations.len());
|
|
let mut normalizer = AssocTypeNormalizer::new(selcx, param_env, cause, depth, obligations);
|
|
let result = ensure_sufficient_stack(|| AssocTypeNormalizer::fold(&mut normalizer, value));
|
|
debug!(?result, obligations.len = normalizer.obligations.len());
|
|
debug!(?normalizer.obligations,);
|
|
result
|
|
}
|
|
|
|
pub(super) fn needs_normalization<'tcx, T: TypeVisitable<TyCtxt<'tcx>>>(
|
|
infcx: &InferCtxt<'tcx>,
|
|
value: &T,
|
|
) -> bool {
|
|
let mut flags = ty::TypeFlags::HAS_ALIAS;
|
|
|
|
// Opaques are treated as rigid outside of `TypingMode::PostAnalysis`,
|
|
// so we can ignore those.
|
|
match infcx.typing_mode() {
|
|
// FIXME(#132279): We likely want to reveal opaques during post borrowck analysis
|
|
TypingMode::Coherence
|
|
| TypingMode::Analysis { .. }
|
|
| TypingMode::Borrowck { .. }
|
|
| TypingMode::PostBorrowckAnalysis { .. } => flags.remove(ty::TypeFlags::HAS_TY_OPAQUE),
|
|
TypingMode::PostAnalysis => {}
|
|
}
|
|
|
|
value.has_type_flags(flags)
|
|
}
|
|
|
|
struct AssocTypeNormalizer<'a, 'b, 'tcx> {
|
|
selcx: &'a mut SelectionContext<'b, 'tcx>,
|
|
param_env: ty::ParamEnv<'tcx>,
|
|
cause: ObligationCause<'tcx>,
|
|
obligations: &'a mut PredicateObligations<'tcx>,
|
|
depth: usize,
|
|
universes: Vec<Option<ty::UniverseIndex>>,
|
|
}
|
|
|
|
impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> {
|
|
fn new(
|
|
selcx: &'a mut SelectionContext<'b, 'tcx>,
|
|
param_env: ty::ParamEnv<'tcx>,
|
|
cause: ObligationCause<'tcx>,
|
|
depth: usize,
|
|
obligations: &'a mut PredicateObligations<'tcx>,
|
|
) -> AssocTypeNormalizer<'a, 'b, 'tcx> {
|
|
debug_assert!(!selcx.infcx.next_trait_solver());
|
|
AssocTypeNormalizer { selcx, param_env, cause, obligations, depth, universes: vec![] }
|
|
}
|
|
|
|
fn fold<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, value: T) -> T {
|
|
let value = self.selcx.infcx.resolve_vars_if_possible(value);
|
|
debug!(?value);
|
|
|
|
assert!(
|
|
!value.has_escaping_bound_vars(),
|
|
"Normalizing {value:?} without wrapping in a `Binder`"
|
|
);
|
|
|
|
if !needs_normalization(self.selcx.infcx, &value) { value } else { value.fold_with(self) }
|
|
}
|
|
|
|
// FIXME(mgca): While this supports constants, it is only used for types by default right now
|
|
#[instrument(level = "debug", skip(self), ret)]
|
|
fn normalize_trait_projection(&mut self, proj: AliasTerm<'tcx>) -> Term<'tcx> {
|
|
if !proj.has_escaping_bound_vars() {
|
|
// When we don't have escaping bound vars we can normalize ambig aliases
|
|
// to inference variables (done in `normalize_projection_ty`). This would
|
|
// be wrong if there were escaping bound vars as even if we instantiated
|
|
// the bound vars with placeholders, we wouldn't be able to map them back
|
|
// after normalization succeeded.
|
|
//
|
|
// Also, as an optimization: when we don't have escaping bound vars, we don't
|
|
// need to replace them with placeholders (see branch below).
|
|
let proj = proj.fold_with(self);
|
|
project::normalize_projection_term(
|
|
self.selcx,
|
|
self.param_env,
|
|
proj,
|
|
self.cause.clone(),
|
|
self.depth,
|
|
self.obligations,
|
|
)
|
|
} else {
|
|
// If there are escaping bound vars, we temporarily replace the
|
|
// bound vars with placeholders. Note though, that in the case
|
|
// that we still can't project for whatever reason (e.g. self
|
|
// type isn't known enough), we *can't* register an obligation
|
|
// and return an inference variable (since then that obligation
|
|
// would have bound vars and that's a can of worms). Instead,
|
|
// we just give up and fall back to pretending like we never tried!
|
|
//
|
|
// Note: this isn't necessarily the final approach here; we may
|
|
// want to figure out how to register obligations with escaping vars
|
|
// or handle this some other way.
|
|
let infcx = self.selcx.infcx;
|
|
let (proj, mapped_regions, mapped_types, mapped_consts) =
|
|
BoundVarReplacer::replace_bound_vars(infcx, &mut self.universes, proj);
|
|
let proj = proj.fold_with(self);
|
|
let normalized_term = project::opt_normalize_projection_term(
|
|
self.selcx,
|
|
self.param_env,
|
|
proj,
|
|
self.cause.clone(),
|
|
self.depth,
|
|
self.obligations,
|
|
)
|
|
.ok()
|
|
.flatten()
|
|
.unwrap_or_else(|| proj.to_term(infcx.tcx));
|
|
|
|
PlaceholderReplacer::replace_placeholders(
|
|
infcx,
|
|
mapped_regions,
|
|
mapped_types,
|
|
mapped_consts,
|
|
&self.universes,
|
|
normalized_term,
|
|
)
|
|
}
|
|
}
|
|
|
|
// FIXME(mgca): While this supports constants, it is only used for types by default right now
|
|
#[instrument(level = "debug", skip(self), ret)]
|
|
fn normalize_inherent_projection(&mut self, inherent: AliasTerm<'tcx>) -> Term<'tcx> {
|
|
if !inherent.has_escaping_bound_vars() {
|
|
// When we don't have escaping bound vars we can normalize ambig aliases
|
|
// to inference variables (done in `normalize_projection_ty`). This would
|
|
// be wrong if there were escaping bound vars as even if we instantiated
|
|
// the bound vars with placeholders, we wouldn't be able to map them back
|
|
// after normalization succeeded.
|
|
//
|
|
// Also, as an optimization: when we don't have escaping bound vars, we don't
|
|
// need to replace them with placeholders (see branch below).
|
|
|
|
let inherent = inherent.fold_with(self);
|
|
project::normalize_inherent_projection(
|
|
self.selcx,
|
|
self.param_env,
|
|
inherent,
|
|
self.cause.clone(),
|
|
self.depth,
|
|
self.obligations,
|
|
)
|
|
} else {
|
|
let infcx = self.selcx.infcx;
|
|
let (inherent, mapped_regions, mapped_types, mapped_consts) =
|
|
BoundVarReplacer::replace_bound_vars(infcx, &mut self.universes, inherent);
|
|
let inherent = inherent.fold_with(self);
|
|
let inherent = project::normalize_inherent_projection(
|
|
self.selcx,
|
|
self.param_env,
|
|
inherent,
|
|
self.cause.clone(),
|
|
self.depth,
|
|
self.obligations,
|
|
);
|
|
|
|
PlaceholderReplacer::replace_placeholders(
|
|
infcx,
|
|
mapped_regions,
|
|
mapped_types,
|
|
mapped_consts,
|
|
&self.universes,
|
|
inherent,
|
|
)
|
|
}
|
|
}
|
|
|
|
// FIXME(mgca): While this supports constants, it is only used for types by default right now
|
|
#[instrument(level = "debug", skip(self), ret)]
|
|
fn normalize_free_alias(&mut self, free: AliasTerm<'tcx>) -> Term<'tcx> {
|
|
let recursion_limit = self.cx().recursion_limit();
|
|
if !recursion_limit.value_within_limit(self.depth) {
|
|
self.selcx.infcx.err_ctxt().report_overflow_error(
|
|
OverflowCause::DeeplyNormalize(free.into()),
|
|
self.cause.span,
|
|
false,
|
|
|diag| {
|
|
diag.note(msg!("in case this is a recursive type alias, consider using a struct, enum, or union instead"));
|
|
},
|
|
);
|
|
}
|
|
|
|
// We don't replace bound vars in the generic arguments of the free alias with
|
|
// placeholders. This doesn't cause any issues as instantiating parameters with
|
|
// bound variables is special-cased to rewrite the debruijn index to be higher
|
|
// whenever we fold through a binder.
|
|
//
|
|
// However, we do replace any escaping bound vars in the resulting goals with
|
|
// placeholders as the trait solver does not expect to encounter escaping bound
|
|
// vars in obligations.
|
|
//
|
|
// FIXME(lazy_type_alias): Check how much this actually matters for perf before
|
|
// stabilization. This is a bit weird and generally not how we handle binders in
|
|
// the compiler so ideally we'd do the same boundvar->placeholder->boundvar dance
|
|
// that other kinds of normalization do.
|
|
let infcx = self.selcx.infcx;
|
|
self.obligations.extend(
|
|
infcx
|
|
.tcx
|
|
.predicates_of(free.def_id())
|
|
.instantiate_own(infcx.tcx, free.args)
|
|
.map(|(pred, span)| (pred.skip_norm_wip(), span))
|
|
.map(|(mut predicate, span)| {
|
|
if free.has_escaping_bound_vars() {
|
|
(predicate, ..) = BoundVarReplacer::replace_bound_vars(
|
|
infcx,
|
|
&mut self.universes,
|
|
predicate,
|
|
);
|
|
}
|
|
let mut cause = self.cause.clone();
|
|
cause
|
|
.map_code(|code| ObligationCauseCode::TypeAlias(code, span, free.def_id()));
|
|
Obligation::new(infcx.tcx, cause, self.param_env, predicate)
|
|
}),
|
|
);
|
|
self.depth += 1;
|
|
let res = if free.kind(infcx.tcx).is_type() {
|
|
infcx
|
|
.tcx
|
|
.type_of(free.def_id())
|
|
.instantiate(infcx.tcx, free.args)
|
|
.skip_norm_wip()
|
|
.fold_with(self)
|
|
.into()
|
|
} else {
|
|
infcx
|
|
.tcx
|
|
.const_of_item(free.def_id())
|
|
.instantiate(infcx.tcx, free.args)
|
|
.skip_norm_wip()
|
|
.fold_with(self)
|
|
.into()
|
|
};
|
|
self.depth -= 1;
|
|
res
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b, 'tcx> TypeFolder<TyCtxt<'tcx>> for AssocTypeNormalizer<'a, 'b, 'tcx> {
|
|
fn cx(&self) -> TyCtxt<'tcx> {
|
|
self.selcx.tcx()
|
|
}
|
|
|
|
fn fold_binder<T: TypeFoldable<TyCtxt<'tcx>>>(
|
|
&mut self,
|
|
t: ty::Binder<'tcx, T>,
|
|
) -> ty::Binder<'tcx, T> {
|
|
self.universes.push(None);
|
|
let t = t.super_fold_with(self);
|
|
self.universes.pop();
|
|
t
|
|
}
|
|
|
|
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
|
|
if !needs_normalization(self.selcx.infcx, &ty) {
|
|
return ty;
|
|
}
|
|
|
|
let ty::Alias(data) = *ty.kind() else { return ty.super_fold_with(self) };
|
|
|
|
// We try to be a little clever here as a performance optimization in
|
|
// cases where there are nested projections under binders.
|
|
// For example:
|
|
// ```
|
|
// for<'a> fn(<T as Foo>::One<'a, Box<dyn Bar<'a, Item=<T as Foo>::Two<'a>>>>)
|
|
// ```
|
|
// We normalize the args on the projection before the projecting, but
|
|
// if we're naive, we'll
|
|
// replace bound vars on inner, project inner, replace placeholders on inner,
|
|
// replace bound vars on outer, project outer, replace placeholders on outer
|
|
//
|
|
// However, if we're a bit more clever, we can replace the bound vars
|
|
// on the entire type before normalizing nested projections, meaning we
|
|
// replace bound vars on outer, project inner,
|
|
// project outer, replace placeholders on outer
|
|
//
|
|
// This is possible because the inner `'a` will already be a placeholder
|
|
// when we need to normalize the inner projection
|
|
//
|
|
// On the other hand, this does add a bit of complexity, since we only
|
|
// replace bound vars if the current type is a `Projection` and we need
|
|
// to make sure we don't forget to fold the args regardless.
|
|
|
|
match data.kind {
|
|
ty::Opaque { def_id } => {
|
|
// Only normalize `impl Trait` outside of type inference, usually in codegen.
|
|
match self.selcx.infcx.typing_mode() {
|
|
// FIXME(#132279): We likely want to reveal opaques during post borrowck analysis
|
|
TypingMode::Coherence
|
|
| TypingMode::Analysis { .. }
|
|
| TypingMode::Borrowck { .. }
|
|
| TypingMode::PostBorrowckAnalysis { .. } => ty.super_fold_with(self),
|
|
TypingMode::PostAnalysis => {
|
|
let recursion_limit = self.cx().recursion_limit();
|
|
if !recursion_limit.value_within_limit(self.depth) {
|
|
self.selcx.infcx.err_ctxt().report_overflow_error(
|
|
OverflowCause::DeeplyNormalize(data.into()),
|
|
self.cause.span,
|
|
true,
|
|
|_| {},
|
|
);
|
|
}
|
|
|
|
let args = data.args.fold_with(self);
|
|
let generic_ty = self.cx().type_of(def_id);
|
|
let concrete_ty = generic_ty.instantiate(self.cx(), args).skip_norm_wip();
|
|
self.depth += 1;
|
|
let folded_ty = self.fold_ty(concrete_ty);
|
|
self.depth -= 1;
|
|
folded_ty
|
|
}
|
|
}
|
|
}
|
|
|
|
ty::Projection { .. } => self.normalize_trait_projection(data.into()).expect_type(),
|
|
ty::Inherent { .. } => self.normalize_inherent_projection(data.into()).expect_type(),
|
|
ty::Free { .. } => self.normalize_free_alias(data.into()).expect_type(),
|
|
}
|
|
}
|
|
|
|
#[instrument(skip(self), level = "debug")]
|
|
fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
|
|
let tcx = self.selcx.tcx();
|
|
|
|
if tcx.features().generic_const_exprs()
|
|
// Normalize type_const items even with feature `generic_const_exprs`.
|
|
&& !matches!(ct.kind(), ty::ConstKind::Unevaluated(uv) if tcx.is_type_const(uv.def))
|
|
|| !needs_normalization(self.selcx.infcx, &ct)
|
|
{
|
|
return ct;
|
|
}
|
|
|
|
let uv = match ct.kind() {
|
|
ty::ConstKind::Unevaluated(uv) => uv,
|
|
_ => return ct.super_fold_with(self),
|
|
};
|
|
|
|
// Note that the AssocConst and Const cases are unreachable on stable,
|
|
// unless a `min_generic_const_args` feature gate error has already
|
|
// been emitted earlier in compilation.
|
|
//
|
|
// That's because we can only end up with an Unevaluated ty::Const for a const item
|
|
// if it was marked with `type const`. Using this attribute without the mgca
|
|
// feature gate causes a parse error.
|
|
let ct = match tcx.def_kind(uv.def) {
|
|
DefKind::AssocConst { .. } => match tcx.def_kind(tcx.parent(uv.def)) {
|
|
DefKind::Trait => self.normalize_trait_projection(uv.into()).expect_const(),
|
|
DefKind::Impl { of_trait: false } => {
|
|
self.normalize_inherent_projection(uv.into()).expect_const()
|
|
}
|
|
kind => unreachable!(
|
|
"unexpected `DefKind` for const alias' resolution's parent def: {:?}",
|
|
kind
|
|
),
|
|
},
|
|
DefKind::Const { .. } => self.normalize_free_alias(uv.into()).expect_const(),
|
|
DefKind::AnonConst => {
|
|
let ct = ct.super_fold_with(self);
|
|
super::with_replaced_escaping_bound_vars(
|
|
self.selcx.infcx,
|
|
&mut self.universes,
|
|
ct,
|
|
|ct| super::evaluate_const(self.selcx.infcx, ct, self.param_env),
|
|
)
|
|
}
|
|
kind => {
|
|
unreachable!("unexpected `DefKind` for const alias to resolve to: {:?}", kind)
|
|
}
|
|
};
|
|
|
|
// We re-fold the normalized const as the `ty` field on `ConstKind::Value` may be
|
|
// unnormalized after const evaluation returns.
|
|
ct.super_fold_with(self)
|
|
}
|
|
|
|
#[inline]
|
|
fn fold_predicate(&mut self, p: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> {
|
|
if p.allow_normalization() && needs_normalization(self.selcx.infcx, &p) {
|
|
p.super_fold_with(self)
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
}
|