Skip to content

Commit 3f9571c

Browse files
committed
Deeply normalize TypeOutlives on next solver
1 parent da2a772 commit 3f9571c

7 files changed

Lines changed: 187 additions & 114 deletions

File tree

compiler/rustc_infer/src/infer/outlives/mod.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Various code related to computing outlives relations.
22
33
use rustc_data_structures::undo_log::UndoLogs;
4-
use rustc_middle::traits::query::{NoSolution, OutlivesBound};
4+
use rustc_middle::traits::query::OutlivesBound;
55
use rustc_middle::ty;
66
use tracing::instrument;
77

88
use self::env::OutlivesEnvironment;
99
use super::region_constraints::{RegionConstraintData, UndoLog};
10-
use super::{InferCtxt, RegionResolutionError, SubregionOrigin};
10+
use super::{InferCtxt, RegionResolutionError};
1111
use crate::infer::free_regions::RegionRelations;
1212
use crate::infer::lexical_region_resolve;
1313
use crate::infer::region_constraints::ConstraintKind;
@@ -35,25 +35,12 @@ impl<'tcx> InferCtxt<'tcx> {
3535
/// result. After this, no more unification operations should be
3636
/// done -- or the compiler will panic -- but it is legal to use
3737
/// `resolve_vars_if_possible` as well as `fully_resolve`.
38-
///
39-
/// If you are in a crate that has access to `rustc_trait_selection`,
40-
/// then it's probably better to use `resolve_regions`,
41-
/// which knows how to normalize registered region obligations.
4238
#[must_use]
43-
pub fn resolve_regions_with_normalize(
39+
pub fn resolve_regions_with_outlives_env(
4440
&self,
4541
outlives_env: &OutlivesEnvironment<'tcx>,
46-
deeply_normalize_ty: impl Fn(
47-
ty::PolyTypeOutlivesPredicate<'tcx>,
48-
SubregionOrigin<'tcx>,
49-
) -> Result<ty::PolyTypeOutlivesPredicate<'tcx>, NoSolution>,
5042
) -> Vec<RegionResolutionError<'tcx>> {
51-
match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty) {
52-
Ok(()) => {}
53-
Err((clause, origin)) => {
54-
return vec![RegionResolutionError::CannotNormalize(clause, origin)];
55-
}
56-
};
43+
self.process_registered_region_obligations(outlives_env);
5744

5845
let mut storage = {
5946
let mut inner = self.inner.borrow_mut();

compiler/rustc_infer/src/infer/outlives/obligations.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,9 @@
6262
use rustc_data_structures::undo_log::UndoLogs;
6363
use rustc_middle::bug;
6464
use rustc_middle::mir::ConstraintCategory;
65-
use rustc_middle::traits::query::NoSolution;
6665
use rustc_middle::ty::outlives::{Component, push_outlives_components};
6766
use rustc_middle::ty::{
68-
self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, Ty, TyCtxt,
69-
TypeFoldable as _, TypeVisitableExt,
67+
self, GenericArgKind, GenericArgsRef, Region, Ty, TyCtxt, TypeFoldable as _, TypeVisitableExt,
7068
};
7169
use smallvec::smallvec;
7270
use tracing::{debug, instrument};
@@ -194,19 +192,11 @@ impl<'tcx> InferCtxt<'tcx> {
194192
/// flow of the inferencer. The key point is that it is
195193
/// invoked after all type-inference variables have been bound --
196194
/// right before lexical region resolution.
197-
#[instrument(level = "debug", skip(self, outlives_env, deeply_normalize_ty))]
198-
pub fn process_registered_region_obligations(
199-
&self,
200-
outlives_env: &OutlivesEnvironment<'tcx>,
201-
mut deeply_normalize_ty: impl FnMut(
202-
PolyTypeOutlivesPredicate<'tcx>,
203-
SubregionOrigin<'tcx>,
204-
)
205-
-> Result<PolyTypeOutlivesPredicate<'tcx>, NoSolution>,
206-
) -> Result<(), (PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>)> {
195+
#[instrument(level = "debug", skip(self, outlives_env))]
196+
pub fn process_registered_region_obligations(&self, outlives_env: &OutlivesEnvironment<'tcx>) {
207197
assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot");
208198

209-
// Must loop since the process of normalizing may itself register region obligations.
199+
// We loop to handle the case that processing one obligation ends up registering another.
210200
for iteration in 0.. {
211201
let my_region_obligations = self.take_registered_region_obligations();
212202
if my_region_obligations.is_empty() {
@@ -223,12 +213,12 @@ impl<'tcx> InferCtxt<'tcx> {
223213
}
224214

225215
for TypeOutlivesConstraint { sup_type, sub_region, origin } in my_region_obligations {
226-
let outlives = ty::Binder::dummy(ty::OutlivesPredicate(sup_type, sub_region));
227-
let ty::OutlivesPredicate(sup_type, sub_region) =
228-
deeply_normalize_ty(outlives, origin.clone())
229-
.map_err(|NoSolution| (outlives, origin.clone()))?
230-
.no_bound_vars()
231-
.expect("started with no bound vars, should end with no bound vars");
216+
let outlives = self.resolve_vars_if_possible(ty::Binder::dummy(
217+
ty::OutlivesPredicate(sup_type, sub_region),
218+
));
219+
let ty::OutlivesPredicate(sup_type, sub_region) = outlives
220+
.no_bound_vars()
221+
.expect("started with no bound vars, should end with no bound vars");
232222
// `TypeOutlives` is structural, so we should try to opportunistically resolve all
233223
// region vids before processing regions, so we have a better chance to match clauses
234224
// in our param-env.
@@ -256,8 +246,6 @@ impl<'tcx> InferCtxt<'tcx> {
256246
outlives.type_must_outlive(origin, sup_type, sub_region, category);
257247
}
258248
}
259-
260-
Ok(())
261249
}
262250
}
263251

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,18 @@ where
420420
Ok(goal_evaluation)
421421
}
422422

423+
/// Evaluate `goal` without adding it to `nested_goals`.
424+
///
425+
/// This is intended for goal evaluation which should not affect the
426+
/// certainty of the currently evaluated goal.
427+
pub(super) fn try_evaluate_goal(
428+
&mut self,
429+
source: GoalSource,
430+
goal: Goal<I, I::Predicate>,
431+
) -> Result<GoalEvaluation<I>, NoSolution> {
432+
self.evaluate_goal(source, goal, None)
433+
}
434+
423435
/// Recursively evaluates `goal`, returning the nested goals in case
424436
/// the nested goal is a `NormalizesTo` goal.
425437
///
@@ -1076,6 +1088,10 @@ where
10761088
self.delegate.shallow_resolve(ty)
10771089
}
10781090

1091+
pub(super) fn shallow_resolve_const(&self, ct: I::Const) -> I::Const {
1092+
self.delegate.shallow_resolve_const(ct)
1093+
}
1094+
10791095
pub(super) fn eager_resolve_region(&self, r: I::Region) -> I::Region {
10801096
if let ty::ReVar(vid) = r.kind() {
10811097
self.delegate.opportunistic_resolve_lt_var(vid)

compiler/rustc_next_trait_solver/src/solve/mod.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ mod search_graph;
2222
mod trait_goals;
2323

2424
use derive_where::derive_where;
25+
use rustc_type_ir::data_structures::ensure_sufficient_stack;
2526
use rustc_type_ir::inherent::*;
2627
pub use rustc_type_ir::solve::*;
27-
use rustc_type_ir::{self as ty, Interner, TyVid, TypingMode};
28+
use rustc_type_ir::{
29+
self as ty, FallibleTypeFolder, Interner, TyVid, TypeFoldable, TypeSuperFoldable,
30+
TypeVisitableExt, TypingMode,
31+
};
2832
use tracing::instrument;
2933

3034
pub use self::eval_ctxt::{
@@ -91,6 +95,15 @@ where
9195
goal: Goal<I, ty::OutlivesPredicate<I, I::Ty>>,
9296
) -> QueryResult<I> {
9397
let ty::OutlivesPredicate(ty, lt) = goal.predicate;
98+
99+
// With `-Znext-solver`, `TypeOutlives` goals normalize aliases before registering region
100+
// obligations so that later processing does not have to structurally process aliases.
101+
let ty = self.resolve_vars_if_possible(ty);
102+
let ty = if ty.has_aliases() {
103+
self.deeply_normalize_for_outlives(goal.param_env, ty)
104+
} else {
105+
ty
106+
};
94107
self.register_ty_outlives(ty, lt);
95108
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
96109
}
@@ -306,6 +319,131 @@ where
306319
}
307320
}
308321

322+
/// This is the solver-internal equivalent of the `deeply_normalize` helper in
323+
/// `compiler/rustc_trait_selection/src/solve/normalize.rs`.
324+
fn deeply_normalize_for_outlives(&mut self, param_env: I::ParamEnv, ty: I::Ty) -> I::Ty {
325+
debug_assert!(ty.has_aliases());
326+
327+
// We only use this for `TypeOutlives` goals,
328+
// so the input should not have escaping bound vars.
329+
debug_assert!(
330+
!ty.has_escaping_bound_vars(),
331+
"expected `TypeOutlives` ty to not have escaping bound vars: {ty:?}"
332+
);
333+
334+
struct DeepNormalizer<'ecx, 'a, D, I>
335+
where
336+
D: SolverDelegate<Interner = I>,
337+
I: Interner,
338+
{
339+
ecx: &'ecx mut EvalCtxt<'a, D, I>,
340+
param_env: I::ParamEnv,
341+
depth: usize,
342+
}
343+
344+
impl<D, I> DeepNormalizer<'_, '_, D, I>
345+
where
346+
D: SolverDelegate<Interner = I>,
347+
I: Interner,
348+
{
349+
fn normalize_alias_term(&mut self, alias_term: I::Term) -> Result<I::Term, NoSolution> {
350+
debug_assert!(alias_term.to_alias_term().is_some());
351+
352+
if alias_term.has_non_region_infer() || alias_term.has_non_region_placeholders() {
353+
return match alias_term.kind() {
354+
ty::TermKind::Ty(ty) => Ok(ty.try_super_fold_with(self)?.into()),
355+
ty::TermKind::Const(ct) => Ok(ct.try_super_fold_with(self)?.into()),
356+
};
357+
}
358+
359+
// Avoid getting stuck on self-referential normalization.
360+
if self.depth >= self.ecx.cx().recursion_limit() {
361+
return match alias_term.kind() {
362+
ty::TermKind::Ty(ty) => Ok(ty.try_super_fold_with(self)?.into()),
363+
ty::TermKind::Const(ct) => Ok(ct.try_super_fold_with(self)?.into()),
364+
};
365+
}
366+
367+
self.depth += 1;
368+
369+
let normalized_term = self.ecx.next_term_infer_of_kind(alias_term);
370+
let goal = Goal::new(
371+
self.ecx.cx(),
372+
self.param_env,
373+
ty::PredicateKind::AliasRelate(
374+
alias_term,
375+
normalized_term,
376+
ty::AliasRelationDirection::Equate,
377+
),
378+
);
379+
380+
let result = match self.ecx.try_evaluate_goal(GoalSource::TypeRelating, goal) {
381+
Ok(GoalEvaluation { certainty: Certainty::Yes, .. }) => {
382+
// Resolve the fresh term and continue normalization recursively.
383+
let term = self.ecx.resolve_vars_if_possible(normalized_term);
384+
match term.kind() {
385+
ty::TermKind::Ty(ty) => ty.try_super_fold_with(self)?.into(),
386+
ty::TermKind::Const(ct) => ct.try_super_fold_with(self)?.into(),
387+
}
388+
}
389+
Ok(GoalEvaluation { certainty: Certainty::Maybe { .. }, .. })
390+
| Err(NoSolution) => {
391+
// If normalizing this alias isn't possible right now, keep it and continue
392+
// folding inside of it.
393+
match alias_term.kind() {
394+
ty::TermKind::Ty(ty) => ty.try_super_fold_with(self)?.into(),
395+
ty::TermKind::Const(ct) => ct.try_super_fold_with(self)?.into(),
396+
}
397+
}
398+
};
399+
400+
self.depth -= 1;
401+
Ok(result)
402+
}
403+
}
404+
405+
impl<D, I> FallibleTypeFolder<I> for DeepNormalizer<'_, '_, D, I>
406+
where
407+
D: SolverDelegate<Interner = I>,
408+
I: Interner,
409+
{
410+
type Error = NoSolution;
411+
412+
fn cx(&self) -> I {
413+
self.ecx.cx()
414+
}
415+
416+
#[instrument(level = "trace", skip(self), ret)]
417+
fn try_fold_ty(&mut self, ty: I::Ty) -> Result<I::Ty, Self::Error> {
418+
let ty = self.ecx.shallow_resolve(ty);
419+
if !ty.has_aliases() {
420+
return Ok(ty);
421+
}
422+
423+
let ty::Alias(..) = ty.kind() else { return ty.try_super_fold_with(self) };
424+
let term = ensure_sufficient_stack(|| self.normalize_alias_term(ty.into()))?;
425+
Ok(term.expect_ty())
426+
}
427+
428+
#[instrument(level = "trace", skip(self), ret)]
429+
fn try_fold_const(&mut self, ct: I::Const) -> Result<I::Const, Self::Error> {
430+
let ct = self.ecx.shallow_resolve_const(ct);
431+
if !ct.has_aliases() {
432+
return Ok(ct);
433+
}
434+
435+
let ty::ConstKind::Unevaluated(..) = ct.kind() else {
436+
return ct.try_super_fold_with(self);
437+
};
438+
439+
let term = ensure_sufficient_stack(|| self.normalize_alias_term(ct.into()))?;
440+
Ok(term.expect_const())
441+
}
442+
}
443+
444+
ty.try_fold_with(&mut DeepNormalizer { ecx: self, param_env, depth: 0 }).unwrap()
445+
}
446+
309447
/// Normalize a type for when it is structurally matched on.
310448
///
311449
/// This function is necessary in nearly all cases before matching on a type.

compiler/rustc_trait_selection/src/regions.rs

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use rustc_data_structures::fx::FxHashSet;
22
use rustc_hir::def_id::LocalDefId;
33
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
4-
use rustc_infer::infer::{
5-
InferCtxt, RegionResolutionError, SubregionOrigin, TypeOutlivesConstraint,
6-
};
4+
use rustc_infer::infer::{InferCtxt, RegionResolutionError};
75
use rustc_macros::extension;
86
use rustc_middle::traits::ObligationCause;
97
use rustc_middle::ty::{self, Ty, elaborate};
@@ -58,48 +56,6 @@ fn normalize_higher_ranked_assumptions<'tcx>(
5856
elaborate::elaborate_outlives_assumptions(infcx.tcx, normalized_assumptions)
5957
}
6058

61-
fn normalize_registered_region_obligations<'tcx>(
62-
infcx: &InferCtxt<'tcx>,
63-
param_env: ty::ParamEnv<'tcx>,
64-
) -> Result<(), (ty::PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>)> {
65-
if !infcx.next_trait_solver() {
66-
return Ok(());
67-
}
68-
69-
let obligations = infcx.take_registered_region_obligations();
70-
let mut seen_outputs = FxHashSet::default();
71-
let mut normalized_obligations = vec![];
72-
73-
for TypeOutlivesConstraint { sup_type, sub_region, origin } in obligations {
74-
let outlives = infcx.resolve_vars_if_possible(ty::Binder::dummy(ty::OutlivesPredicate(
75-
sup_type, sub_region,
76-
)));
77-
let ty::OutlivesPredicate(sup_type, sub_region) = crate::solve::deeply_normalize(
78-
infcx.at(&ObligationCause::dummy_with_span(origin.span()), param_env),
79-
outlives,
80-
)
81-
.map_err(|_: Vec<ScrubbedTraitError<'tcx>>| (outlives, origin.clone()))?
82-
.no_bound_vars()
83-
.expect("started with no bound vars, should end with no bound vars");
84-
85-
if seen_outputs.insert((sup_type, sub_region)) {
86-
normalized_obligations.push(TypeOutlivesConstraint { sup_type, sub_region, origin });
87-
}
88-
}
89-
90-
for obligation in infcx.take_registered_region_obligations() {
91-
if seen_outputs.insert((obligation.sup_type, obligation.sub_region)) {
92-
normalized_obligations.push(obligation);
93-
}
94-
}
95-
96-
for obligation in normalized_obligations {
97-
infcx.register_type_outlives_constraint_inner(obligation);
98-
}
99-
100-
Ok(())
101-
}
102-
10359
#[extension(pub trait OutlivesEnvironmentBuildExt<'tcx>)]
10460
impl<'tcx> OutlivesEnvironment<'tcx> {
10561
fn new(
@@ -159,12 +115,11 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
159115

160116
#[extension(pub trait InferCtxtRegionExt<'tcx>)]
161117
impl<'tcx> InferCtxt<'tcx> {
162-
/// Resolve regions, using the deep normalizer to normalize any type-outlives
163-
/// obligations in the process. This is in `rustc_trait_selection` because
164-
/// we need to normalize.
118+
/// Resolve regions.
165119
///
166-
/// Prefer this method over `resolve_regions_with_normalize`, unless you are
167-
/// doing something specific for normalization.
120+
/// With the next solver, `TypeOutlives` goals are normalized while they are
121+
/// evaluated (and before being registered as region obligations), so region
122+
/// resolution does not need to normalize them.
168123
///
169124
/// This function assumes that all infer variables are already constrained.
170125
fn resolve_regions(
@@ -180,20 +135,4 @@ impl<'tcx> InferCtxt<'tcx> {
180135
assumed_wf_tys,
181136
))
182137
}
183-
184-
/// Don't call this directly unless you know what you're doing.
185-
fn resolve_regions_with_outlives_env(
186-
&self,
187-
outlives_env: &OutlivesEnvironment<'tcx>,
188-
) -> Vec<RegionResolutionError<'tcx>> {
189-
if let Err((outlives, origin)) =
190-
normalize_registered_region_obligations(self, outlives_env.param_env)
191-
{
192-
return vec![RegionResolutionError::CannotNormalize(outlives, origin)];
193-
}
194-
195-
self.resolve_regions_with_normalize(outlives_env, |outlives, _| {
196-
Ok(self.resolve_vars_if_possible(outlives))
197-
})
198-
}
199138
}

0 commit comments

Comments
 (0)