1
Fork 0

Auto merge of #136074 - compiler-errors:deeply-normalize-next-solver, r=lcnr

Properly deeply normalize in the next solver

Turn deep normalization into a `TypeOp`. In the old solver, just dispatch to the `Normalize` type op, but in the new solver call `deeply_normalize`. I chose to separate it into a different type op b/c some normalization is a no-op in the new solver, so this distinguishes just the normalization we need for correctness.

Then use `DeeplyNormalize` in the callsites we used to be using a `CustomTypeOp` (for normalizing known type outlives obligations), and also use it to normalize function args and impl headers in the new solver.

Finally, use it to normalize signatures for WF checks in the new solver as well. This addresses https://github.com/rust-lang/trait-system-refactor-initiative/issues/146.
This commit is contained in:
bors 2025-02-12 04:04:32 +00:00
commit 672e3aaf28
12 changed files with 250 additions and 47 deletions

View file

@ -9,8 +9,8 @@ use rustc_infer::infer::{
}; };
use rustc_infer::traits::ObligationCause; use rustc_infer::traits::ObligationCause;
use rustc_infer::traits::query::{ use rustc_infer::traits::query::{
CanonicalTypeOpAscribeUserTypeGoal, CanonicalTypeOpNormalizeGoal, CanonicalTypeOpAscribeUserTypeGoal, CanonicalTypeOpDeeplyNormalizeGoal,
CanonicalTypeOpProvePredicateGoal, CanonicalTypeOpNormalizeGoal, CanonicalTypeOpProvePredicateGoal,
}; };
use rustc_middle::ty::error::TypeError; use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{ use rustc_middle::ty::{
@ -109,6 +109,14 @@ impl<'tcx, T: Copy + fmt::Display + TypeFoldable<TyCtxt<'tcx>> + 'tcx> ToUnivers
} }
} }
impl<'tcx, T: Copy + fmt::Display + TypeFoldable<TyCtxt<'tcx>> + 'tcx> ToUniverseInfo<'tcx>
for CanonicalTypeOpDeeplyNormalizeGoal<'tcx, T>
{
fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
UniverseInfo::TypeOp(Rc::new(DeeplyNormalizeQuery { canonical_query: self, base_universe }))
}
}
impl<'tcx> ToUniverseInfo<'tcx> for CanonicalTypeOpAscribeUserTypeGoal<'tcx> { impl<'tcx> ToUniverseInfo<'tcx> for CanonicalTypeOpAscribeUserTypeGoal<'tcx> {
fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
UniverseInfo::TypeOp(Rc::new(AscribeUserTypeQuery { canonical_query: self, base_universe })) UniverseInfo::TypeOp(Rc::new(AscribeUserTypeQuery { canonical_query: self, base_universe }))
@ -285,6 +293,53 @@ where
} }
} }
struct DeeplyNormalizeQuery<'tcx, T> {
canonical_query: CanonicalTypeOpDeeplyNormalizeGoal<'tcx, T>,
base_universe: ty::UniverseIndex,
}
impl<'tcx, T> TypeOpInfo<'tcx> for DeeplyNormalizeQuery<'tcx, T>
where
T: Copy + fmt::Display + TypeFoldable<TyCtxt<'tcx>> + 'tcx,
{
fn fallback_error(&self, tcx: TyCtxt<'tcx>, span: Span) -> Diag<'tcx> {
tcx.dcx().create_err(HigherRankedLifetimeError {
cause: Some(HigherRankedErrorCause::CouldNotNormalize {
value: self.canonical_query.canonical.value.value.value.to_string(),
}),
span,
})
}
fn base_universe(&self) -> ty::UniverseIndex {
self.base_universe
}
fn nice_error<'infcx>(
&self,
mbcx: &mut MirBorrowckCtxt<'_, 'infcx, 'tcx>,
cause: ObligationCause<'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<Diag<'infcx>> {
let (infcx, key, _) =
mbcx.infcx.tcx.infer_ctxt().build_with_canonical(cause.span, &self.canonical_query);
let ocx = ObligationCtxt::new(&infcx);
let (param_env, value) = key.into_parts();
let _ = ocx.deeply_normalize(&cause, param_env, value.value);
let diag = try_extract_error_from_fulfill_cx(
&ocx,
mbcx.mir_def_id(),
placeholder_region,
error_region,
)?
.with_dcx(mbcx.dcx());
Some(diag)
}
}
struct AscribeUserTypeQuery<'tcx> { struct AscribeUserTypeQuery<'tcx> {
canonical_query: CanonicalTypeOpAscribeUserTypeGoal<'tcx>, canonical_query: CanonicalTypeOpAscribeUserTypeGoal<'tcx>,
base_universe: ty::UniverseIndex, base_universe: ty::UniverseIndex,

View file

@ -149,6 +149,18 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
self.normalize_with_category(value, location, ConstraintCategory::Boring) self.normalize_with_category(value, location, ConstraintCategory::Boring)
} }
pub(super) fn deeply_normalize<T>(&mut self, value: T, location: impl NormalizeLocation) -> T
where
T: type_op::normalize::Normalizable<'tcx> + fmt::Display + Copy + 'tcx,
{
let result: Result<_, ErrorGuaranteed> = self.fully_perform_op(
location.to_locations(),
ConstraintCategory::Boring,
self.infcx.param_env.and(type_op::normalize::DeeplyNormalize { value }),
);
result.unwrap_or(value)
}
#[instrument(skip(self), level = "debug")] #[instrument(skip(self), level = "debug")]
pub(super) fn normalize_with_category<T>( pub(super) fn normalize_with_category<T>(
&mut self, &mut self,

View file

@ -4,15 +4,12 @@ use rustc_infer::infer::outlives::env::RegionBoundPairs;
use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate}; use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate};
use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound};
use rustc_infer::infer::{self, InferCtxt, SubregionOrigin}; use rustc_infer::infer::{self, InferCtxt, SubregionOrigin};
use rustc_infer::traits::query::type_op::DeeplyNormalize;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, ConstraintCategory}; use rustc_middle::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, ConstraintCategory};
use rustc_middle::traits::ObligationCause;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::fold::fold_regions; use rustc_middle::ty::fold::fold_regions;
use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt};
use rustc_span::Span; use rustc_span::Span;
use rustc_trait_selection::traits::ScrubbedTraitError;
use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
use tracing::{debug, instrument}; use tracing::{debug, instrument};
@ -270,20 +267,8 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
ConstraintCategory<'tcx>, ConstraintCategory<'tcx>,
)>, )>,
) -> Ty<'tcx> { ) -> Ty<'tcx> {
let result = CustomTypeOp::new( match self.param_env.and(DeeplyNormalize { value: ty }).fully_perform(self.infcx, self.span)
|ocx| { {
ocx.deeply_normalize(
&ObligationCause::dummy_with_span(self.span),
self.param_env,
ty,
)
.map_err(|_: Vec<ScrubbedTraitError<'tcx>>| NoSolution)
},
"normalize type outlives obligation",
)
.fully_perform(self.infcx, self.span);
match result {
Ok(TypeOpOutput { output: ty, constraints, .. }) => { Ok(TypeOpOutput { output: ty, constraints, .. }) => {
if let Some(QueryRegionConstraints { outlives }) = constraints { if let Some(QueryRegionConstraints { outlives }) = constraints {
next_outlives_predicates.extend(outlives.iter().copied()); next_outlives_predicates.extend(outlives.iter().copied());

View file

@ -5,14 +5,11 @@ use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_infer::infer::outlives::env::RegionBoundPairs;
use rustc_infer::infer::region_constraints::GenericKind; use rustc_infer::infer::region_constraints::GenericKind;
use rustc_infer::infer::{InferCtxt, outlives}; use rustc_infer::infer::{InferCtxt, outlives};
use rustc_infer::traits::ScrubbedTraitError; use rustc_infer::traits::query::type_op::DeeplyNormalize;
use rustc_middle::mir::ConstraintCategory; use rustc_middle::mir::ConstraintCategory;
use rustc_middle::traits::ObligationCause;
use rustc_middle::traits::query::OutlivesBound; use rustc_middle::traits::query::OutlivesBound;
use rustc_middle::ty::{self, RegionVid, Ty, TypeVisitableExt}; use rustc_middle::ty::{self, RegionVid, Ty, TypeVisitableExt};
use rustc_span::{ErrorGuaranteed, Span}; use rustc_span::{ErrorGuaranteed, Span};
use rustc_trait_selection::solve::NoSolution;
use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp;
use rustc_trait_selection::traits::query::type_op::{self, TypeOp}; use rustc_trait_selection::traits::query::type_op::{self, TypeOp};
use tracing::{debug, instrument}; use tracing::{debug, instrument};
use type_op::TypeOpOutput; use type_op::TypeOpOutput;
@ -267,7 +264,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
} }
let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } =
param_env param_env
.and(type_op::normalize::Normalize { value: ty }) .and(DeeplyNormalize { value: ty })
.fully_perform(self.infcx, span) .fully_perform(self.infcx, span)
.unwrap_or_else(|guar| TypeOpOutput { .unwrap_or_else(|guar| TypeOpOutput {
output: Ty::new_error(self.infcx.tcx, guar), output: Ty::new_error(self.infcx.tcx, guar),
@ -303,9 +300,8 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
// Add implied bounds from impl header. // Add implied bounds from impl header.
if matches!(tcx.def_kind(defining_ty_def_id), DefKind::AssocFn | DefKind::AssocConst) { if matches!(tcx.def_kind(defining_ty_def_id), DefKind::AssocFn | DefKind::AssocConst) {
for &(ty, _) in tcx.assumed_wf_types(tcx.local_parent(defining_ty_def_id)) { for &(ty, _) in tcx.assumed_wf_types(tcx.local_parent(defining_ty_def_id)) {
let result: Result<_, ErrorGuaranteed> = param_env let result: Result<_, ErrorGuaranteed> =
.and(type_op::normalize::Normalize { value: ty }) param_env.and(DeeplyNormalize { value: ty }).fully_perform(self.infcx, span);
.fully_perform(self.infcx, span);
let Ok(TypeOpOutput { output: norm_ty, constraints: c, .. }) = result else { let Ok(TypeOpOutput { output: norm_ty, constraints: c, .. }) = result else {
continue; continue;
}; };
@ -360,18 +356,10 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
output: normalized_outlives, output: normalized_outlives,
constraints: constraints_normalize, constraints: constraints_normalize,
error_info: _, error_info: _,
}) = CustomTypeOp::new( }) = self
|ocx| { .param_env
ocx.deeply_normalize( .and(DeeplyNormalize { value: outlives })
&ObligationCause::dummy_with_span(span), .fully_perform(self.infcx, span)
self.param_env,
outlives,
)
.map_err(|_: Vec<ScrubbedTraitError<'tcx>>| NoSolution)
},
"normalize type outlives obligation",
)
.fully_perform(self.infcx, span)
else { else {
self.infcx.dcx().delayed_bug(format!("could not normalize {outlives:?}")); self.infcx.dcx().delayed_bug(format!("could not normalize {outlives:?}"));
return; return;

View file

@ -1116,7 +1116,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ConstraintCategory::Boring, ConstraintCategory::Boring,
); );
let sig = self.normalize(unnormalized_sig, term_location); let sig = self.deeply_normalize(unnormalized_sig, term_location);
// HACK(#114936): `WF(sig)` does not imply `WF(normalized(sig))` // HACK(#114936): `WF(sig)` does not imply `WF(normalized(sig))`
// with built-in `Fn` implementations, since the impl may not be // with built-in `Fn` implementations, since the impl may not be
// well-formed itself. // well-formed itself.

View file

@ -41,11 +41,18 @@ pub mod type_op {
pub predicate: Predicate<'tcx>, pub predicate: Predicate<'tcx>,
} }
/// Normalizes, but not in the new solver.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, TypeVisitable)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, TypeVisitable)]
pub struct Normalize<T> { pub struct Normalize<T> {
pub value: T, pub value: T,
} }
/// Normalizes, and deeply normalizes in the new solver.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, TypeVisitable)]
pub struct DeeplyNormalize<T> {
pub value: T,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, TypeVisitable)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, HashStable, TypeFoldable, TypeVisitable)]
pub struct ImpliedOutlivesBounds<'tcx> { pub struct ImpliedOutlivesBounds<'tcx> {
pub ty: Ty<'tcx>, pub ty: Ty<'tcx>,
@ -80,6 +87,9 @@ pub type CanonicalTypeOpProvePredicateGoal<'tcx> =
pub type CanonicalTypeOpNormalizeGoal<'tcx, T> = pub type CanonicalTypeOpNormalizeGoal<'tcx, T> =
CanonicalQueryInput<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>; CanonicalQueryInput<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>;
pub type CanonicalTypeOpDeeplyNormalizeGoal<'tcx, T> =
CanonicalQueryInput<'tcx, ty::ParamEnvAnd<'tcx, type_op::DeeplyNormalize<T>>>;
pub type CanonicalImpliedOutlivesBoundsGoal<'tcx> = pub type CanonicalImpliedOutlivesBoundsGoal<'tcx> =
CanonicalQueryInput<'tcx, ty::ParamEnvAnd<'tcx, type_op::ImpliedOutlivesBounds<'tcx>>>; CanonicalQueryInput<'tcx, ty::ParamEnvAnd<'tcx, type_op::ImpliedOutlivesBounds<'tcx>>>;

View file

@ -2,7 +2,7 @@ use std::fmt;
use rustc_middle::traits::ObligationCause; use rustc_middle::traits::ObligationCause;
use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::query::NoSolution;
pub use rustc_middle::traits::query::type_op::Normalize; pub use rustc_middle::traits::query::type_op::{DeeplyNormalize, Normalize};
use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{self, Lift, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; use rustc_middle::ty::{self, Lift, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::Span; use rustc_span::Span;
@ -27,13 +27,54 @@ where
T::type_op_method(tcx, canonicalized) T::type_op_method(tcx, canonicalized)
} }
fn perform_locally_with_next_solver(
_ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
_span: Span,
) -> Result<Self::QueryResponse, NoSolution> {
Ok(key.value.value)
}
}
impl<'tcx, T> super::QueryTypeOp<'tcx> for DeeplyNormalize<T>
where
T: Normalizable<'tcx> + 'tcx,
{
type QueryResponse = T;
fn try_fast_path(_tcx: TyCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>) -> Option<T> {
if !key.value.value.has_aliases() { Some(key.value.value) } else { None }
}
fn perform_query(
tcx: TyCtxt<'tcx>,
canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
T::type_op_method(
tcx,
CanonicalQueryInput {
typing_mode: canonicalized.typing_mode,
canonical: canonicalized.canonical.unchecked_map(
|ty::ParamEnvAnd { param_env, value }| ty::ParamEnvAnd {
param_env,
value: Normalize { value: value.value },
},
),
},
)
}
fn perform_locally_with_next_solver( fn perform_locally_with_next_solver(
ocx: &ObligationCtxt<'_, 'tcx>, ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>, key: ParamEnvAnd<'tcx, Self>,
span: Span, span: Span,
) -> Result<Self::QueryResponse, NoSolution> { ) -> Result<Self::QueryResponse, NoSolution> {
// FIXME(-Znext-solver): shouldn't be using old normalizer ocx.deeply_normalize(
Ok(ocx.normalize(&ObligationCause::dummy_with_span(span), key.param_env, key.value.value)) &ObligationCause::dummy_with_span(span),
key.param_env,
key.value.value,
)
.map_err(|_| NoSolution)
} }
} }
@ -81,3 +122,14 @@ impl<'tcx> Normalizable<'tcx> for ty::FnSig<'tcx> {
tcx.type_op_normalize_fn_sig(canonicalized) tcx.type_op_normalize_fn_sig(canonicalized)
} }
} }
/// This impl is not needed, since we never normalize type outlives predicates
/// in the old solver, but is required by trait bounds to be happy.
impl<'tcx> Normalizable<'tcx> for ty::PolyTypeOutlivesPredicate<'tcx> {
fn type_op_method(
_tcx: TyCtxt<'tcx>,
_canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Normalize<Self>>>,
) -> Result<CanonicalQueryResponse<'tcx, Self>, NoSolution> {
unreachable!("we never normalize PolyTypeOutlivesPredicate")
}
}

View file

@ -0,0 +1,28 @@
//@ check-pass
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Make sure that we can normalize `<T as Ref<'a>>::Assoc` to `&'a T` and get
// its implied bounds in impl header.
trait Ref<'a> {
type Assoc;
}
impl<'a, T> Ref<'a> for T where T: 'a {
type Assoc = &'a T;
}
fn outlives<'a, T: 'a>() {}
trait Trait<'a, T> {
fn test();
}
impl<'a, T> Trait<'a, T> for <T as Ref<'a>>::Assoc {
fn test() {
outlives::<'a, T>();
}
}
fn main() {}

View file

@ -0,0 +1,22 @@
//@ check-pass
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Make sure that we can normalize `<T as Ref<'a>>::Assoc` to `&'a T` and get
// its implied bounds.
trait Ref<'a> {
type Assoc;
}
impl<'a, T> Ref<'a> for T where T: 'a {
type Assoc = &'a T;
}
fn outlives<'a, T: 'a>() {}
fn test<'a, T>(_: <T as Ref<'a>>::Assoc) {
outlives::<'a, T>();
}
fn main() {}

View file

@ -1,5 +1,5 @@
error[E0597]: `s` does not live long enough error[E0597]: `s` does not live long enough
--> $DIR/check-normalized-sig-for-wf.rs:7:7 --> $DIR/check-normalized-sig-for-wf.rs:11:7
| |
LL | s: String, LL | s: String,
| - binding `s` declared here | - binding `s` declared here
@ -14,7 +14,7 @@ LL | }
| - `s` dropped here while still borrowed | - `s` dropped here while still borrowed
error[E0521]: borrowed data escapes outside of function error[E0521]: borrowed data escapes outside of function
--> $DIR/check-normalized-sig-for-wf.rs:15:5 --> $DIR/check-normalized-sig-for-wf.rs:19:5
| |
LL | fn extend<T>(input: &T) -> &'static T { LL | fn extend<T>(input: &T) -> &'static T {
| ----- - let's call the lifetime of this reference `'1` | ----- - let's call the lifetime of this reference `'1`
@ -28,7 +28,7 @@ LL | n(input).0
| argument requires that `'1` must outlive `'static` | argument requires that `'1` must outlive `'static`
error[E0521]: borrowed data escapes outside of function error[E0521]: borrowed data escapes outside of function
--> $DIR/check-normalized-sig-for-wf.rs:23:5 --> $DIR/check-normalized-sig-for-wf.rs:27:5
| |
LL | fn extend_mut<'a, T>(input: &'a mut T) -> &'static mut T { LL | fn extend_mut<'a, T>(input: &'a mut T) -> &'static mut T {
| -- ----- `input` is a reference that is only valid in the function body | -- ----- `input` is a reference that is only valid in the function body

View file

@ -0,0 +1,47 @@
error[E0597]: `s` does not live long enough
--> $DIR/check-normalized-sig-for-wf.rs:11:7
|
LL | s: String,
| - binding `s` declared here
...
LL | f(&s).0
| --^^-
| | |
| | borrowed value does not live long enough
| argument requires that `s` is borrowed for `'static`
LL |
LL | }
| - `s` dropped here while still borrowed
error[E0521]: borrowed data escapes outside of function
--> $DIR/check-normalized-sig-for-wf.rs:19:5
|
LL | fn extend<T>(input: &T) -> &'static T {
| ----- - let's call the lifetime of this reference `'1`
| |
| `input` is a reference that is only valid in the function body
...
LL | n(input).0
| ^^^^^^^^
| |
| `input` escapes the function body here
| argument requires that `'1` must outlive `'static`
error[E0521]: borrowed data escapes outside of function
--> $DIR/check-normalized-sig-for-wf.rs:27:5
|
LL | fn extend_mut<'a, T>(input: &'a mut T) -> &'static mut T {
| -- ----- `input` is a reference that is only valid in the function body
| |
| lifetime `'a` defined here
...
LL | n(input).0
| ^^^^^^^^
| |
| `input` escapes the function body here
| argument requires that `'a` must outlive `'static`
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0521, E0597.
For more information about an error, try `rustc --explain E0521`.

View file

@ -1,3 +1,7 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// <https://github.com/rust-lang/rust/issues/114936> // <https://github.com/rust-lang/rust/issues/114936>
fn whoops( fn whoops(
s: String, s: String,