Skip to content

Commit 32e7a4b

Browse files
committed
Auto merge of #144405 - lcnr:hir-typeck-uniquify, r=BoxyUwU
uniquify root goals during HIR typeck We need to rely on region identity to deal with hangs such as rust-lang/trait-system-refactor-initiative#210 and to keep the current behavior of `fn try_merge_responses`. This is a problem as borrowck starts by replacing each *occurrence* of a region with a unique inference variable. This frequently splits a single region during HIR typeck into multiple distinct regions. As we assume goals to always succeed during borrowck, relying on two occurances of a region being identical during HIR typeck causes ICE. See the now fixed examples in rust-lang/trait-system-refactor-initiative#27 and #139409. We've previously tried to avoid this issue by always *uniquifying* regions when canonicalizing goals. This prevents caching subtrees during canonicalization which resulted in hangs for very large types. People rely on such types in practice, which caused us to revert our attempt to reinstate `#[type_length_limit]` in #127670. The complete list of changes here: - #107981 - #110180 - #114117 - #130821 After more consideration, all occurrences of such large types need to happen outside of typeck/borrowck. We know this as we already walk over all types in the MIR body when replacing their regions with nll vars. This PR therefore enables us to rely on region identity inside of the trait solver by exclusively **uniquifying root goals during HIR typeck**. These are the only goals we assume to hold during borrowck. This is insufficient as type inference variables may "hide" regions we later uniquify. Because of this, we now stash proven goals which depend on inference variables in HIR typeck and reprove them after writeback. This closes rust-lang/trait-system-refactor-initiative#127. This was originally part of #144258 but I've moved it into a separate PR. While I believe we need to rely on region identity to fix the performance issues in some way, I don't know whether #144258 is the best approach to actually do so. Regardless of how we deal with the hangs however, this change is necessary and desirable regardless. r? `@compiler-errors` or `@BoxyUwU`
2 parents 606dcc0 + 2b065e7 commit 32e7a4b

File tree

20 files changed

+353
-57
lines changed

20 files changed

+353
-57
lines changed

compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ use rustc_hir_analysis::check::{check_abi, check_custom_abi};
5353
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
5454
use rustc_infer::traits::{ObligationCauseCode, ObligationInspector, WellFormedLoc};
5555
use rustc_middle::query::Providers;
56-
use rustc_middle::ty::{self, Ty, TyCtxt};
56+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
5757
use rustc_middle::{bug, span_bug};
5858
use rustc_session::config;
5959
use rustc_span::Span;
@@ -259,6 +259,21 @@ fn typeck_with_inspect<'tcx>(
259259

260260
let typeck_results = fcx.resolve_type_vars_in_body(body);
261261

262+
// Handle potentially region dependent goals, see `InferCtxt::in_hir_typeck`.
263+
if let None = fcx.infcx.tainted_by_errors() {
264+
for obligation in fcx.take_hir_typeck_potentially_region_dependent_goals() {
265+
let obligation = fcx.resolve_vars_if_possible(obligation);
266+
if obligation.has_non_region_infer() {
267+
bug!("unexpected inference variable after writeback: {obligation:?}");
268+
}
269+
fcx.register_predicate(obligation);
270+
}
271+
fcx.select_obligations_where_possible(|_| {});
272+
if let None = fcx.infcx.tainted_by_errors() {
273+
fcx.report_ambiguity_errors();
274+
}
275+
}
276+
262277
fcx.detect_opaque_types_added_during_writeback();
263278

264279
// Consistency check our TypeckResults instance can hold all ItemLocalIds

compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
8585
pub(crate) fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self {
8686
let hir_owner = tcx.local_def_id_to_hir_id(def_id).owner;
8787

88-
let infcx =
89-
tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id));
88+
let infcx = tcx
89+
.infer_ctxt()
90+
.ignoring_regions()
91+
.in_hir_typeck()
92+
.build(TypingMode::typeck_for_body(tcx, def_id));
9093
let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
9194
let fulfillment_cx = RefCell::new(<dyn TraitEngine<'_, _>>::new(&infcx));
9295

compiler/rustc_infer/src/infer/at.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ impl<'tcx> InferCtxt<'tcx> {
7171
tcx: self.tcx,
7272
typing_mode: self.typing_mode,
7373
considering_regions: self.considering_regions,
74+
in_hir_typeck: self.in_hir_typeck,
7475
skip_leak_check: self.skip_leak_check,
7576
inner: self.inner.clone(),
7677
lexical_region_resolutions: self.lexical_region_resolutions.clone(),
@@ -95,6 +96,7 @@ impl<'tcx> InferCtxt<'tcx> {
9596
tcx: self.tcx,
9697
typing_mode,
9798
considering_regions: self.considering_regions,
99+
in_hir_typeck: self.in_hir_typeck,
98100
skip_leak_check: self.skip_leak_check,
99101
inner: self.inner.clone(),
100102
lexical_region_resolutions: self.lexical_region_resolutions.clone(),

compiler/rustc_infer/src/infer/context.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
2222
self.next_trait_solver
2323
}
2424

25+
fn in_hir_typeck(&self) -> bool {
26+
self.in_hir_typeck
27+
}
28+
2529
fn typing_mode(&self) -> ty::TypingMode<'tcx> {
2630
self.typing_mode()
2731
}

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ use snapshot::undo_log::InferCtxtUndoLogs;
3737
use tracing::{debug, instrument};
3838
use type_variable::TypeVariableOrigin;
3939

40-
use crate::infer::region_constraints::UndoLog;
40+
use crate::infer::snapshot::undo_log::UndoLog;
4141
use crate::infer::unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey};
4242
use crate::traits::{
43-
self, ObligationCause, ObligationInspector, PredicateObligations, TraitEngine,
43+
self, ObligationCause, ObligationInspector, PredicateObligation, PredicateObligations,
44+
TraitEngine,
4445
};
4546

4647
pub mod at;
@@ -156,6 +157,12 @@ pub struct InferCtxtInner<'tcx> {
156157
/// which may cause types to no longer be considered well-formed.
157158
region_assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,
158159

160+
/// `-Znext-solver`: Successfully proven goals during HIR typeck which
161+
/// reference inference variables and get reproven after writeback.
162+
///
163+
/// See the documentation of `InferCtxt::in_hir_typeck` for more details.
164+
hir_typeck_potentially_region_dependent_goals: Vec<PredicateObligation<'tcx>>,
165+
159166
/// Caches for opaque type inference.
160167
opaque_type_storage: OpaqueTypeStorage<'tcx>,
161168
}
@@ -173,6 +180,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
173180
region_constraint_storage: Some(Default::default()),
174181
region_obligations: Default::default(),
175182
region_assumptions: Default::default(),
183+
hir_typeck_potentially_region_dependent_goals: Default::default(),
176184
opaque_type_storage: Default::default(),
177185
}
178186
}
@@ -244,9 +252,29 @@ pub struct InferCtxt<'tcx> {
244252
typing_mode: TypingMode<'tcx>,
245253

246254
/// Whether this inference context should care about region obligations in
247-
/// the root universe. Most notably, this is used during hir typeck as region
255+
/// the root universe. Most notably, this is used during HIR typeck as region
248256
/// solving is left to borrowck instead.
249257
pub considering_regions: bool,
258+
/// `-Znext-solver`: Whether this inference context is used by HIR typeck. If so, we
259+
/// need to make sure we don't rely on region identity in the trait solver or when
260+
/// relating types. This is necessary as borrowck starts by replacing each occurrence of a
261+
/// free region with a unique inference variable. If HIR typeck ends up depending on two
262+
/// regions being equal we'd get unexpected mismatches between HIR typeck and MIR typeck,
263+
/// resulting in an ICE.
264+
///
265+
/// The trait solver sometimes depends on regions being identical. As a concrete example
266+
/// the trait solver ignores other candidates if one candidate exists without any constraints.
267+
/// The goal `&'a u32: Equals<&'a u32>` has no constraints right now. If we replace each
268+
/// occurrence of `'a` with a unique region the goal now equates these regions. See
269+
/// the tests in trait-system-refactor-initiative#27 for concrete examples.
270+
///
271+
/// We handle this by *uniquifying* region when canonicalizing root goals during HIR typeck.
272+
/// This is still insufficient as inference variables may *hide* region variables, so e.g.
273+
/// `dyn TwoSuper<?x, ?x>: Super<?x>` may hold but MIR typeck could end up having to prove
274+
/// `dyn TwoSuper<&'0 (), &'1 ()>: Super<&'2 ()>` which is now ambiguous. Because of this we
275+
/// stash all successfully proven goals which reference inference variables and then reprove
276+
/// them after writeback.
277+
pub in_hir_typeck: bool,
250278

251279
/// If set, this flag causes us to skip the 'leak check' during
252280
/// higher-ranked subtyping operations. This flag is a temporary one used
@@ -506,6 +534,7 @@ pub struct TypeOutlivesConstraint<'tcx> {
506534
pub struct InferCtxtBuilder<'tcx> {
507535
tcx: TyCtxt<'tcx>,
508536
considering_regions: bool,
537+
in_hir_typeck: bool,
509538
skip_leak_check: bool,
510539
/// Whether we should use the new trait solver in the local inference context,
511540
/// which affects things like which solver is used in `predicate_may_hold`.
@@ -518,6 +547,7 @@ impl<'tcx> TyCtxt<'tcx> {
518547
InferCtxtBuilder {
519548
tcx: self,
520549
considering_regions: true,
550+
in_hir_typeck: false,
521551
skip_leak_check: false,
522552
next_trait_solver: self.next_trait_solver_globally(),
523553
}
@@ -535,6 +565,11 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
535565
self
536566
}
537567

568+
pub fn in_hir_typeck(mut self) -> Self {
569+
self.in_hir_typeck = true;
570+
self
571+
}
572+
538573
pub fn skip_leak_check(mut self, skip_leak_check: bool) -> Self {
539574
self.skip_leak_check = skip_leak_check;
540575
self
@@ -568,12 +603,18 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
568603
}
569604

570605
pub fn build(&mut self, typing_mode: TypingMode<'tcx>) -> InferCtxt<'tcx> {
571-
let InferCtxtBuilder { tcx, considering_regions, skip_leak_check, next_trait_solver } =
572-
*self;
606+
let InferCtxtBuilder {
607+
tcx,
608+
considering_regions,
609+
in_hir_typeck,
610+
skip_leak_check,
611+
next_trait_solver,
612+
} = *self;
573613
InferCtxt {
574614
tcx,
575615
typing_mode,
576616
considering_regions,
617+
in_hir_typeck,
577618
skip_leak_check,
578619
inner: RefCell::new(InferCtxtInner::new()),
579620
lexical_region_resolutions: RefCell::new(None),
@@ -978,6 +1019,22 @@ impl<'tcx> InferCtxt<'tcx> {
9781019
}
9791020
}
9801021

1022+
pub fn push_hir_typeck_potentially_region_dependent_goal(
1023+
&self,
1024+
goal: PredicateObligation<'tcx>,
1025+
) {
1026+
let mut inner = self.inner.borrow_mut();
1027+
inner.undo_log.push(UndoLog::PushHirTypeckPotentiallyRegionDependentGoal);
1028+
inner.hir_typeck_potentially_region_dependent_goals.push(goal);
1029+
}
1030+
1031+
pub fn take_hir_typeck_potentially_region_dependent_goals(
1032+
&self,
1033+
) -> Vec<PredicateObligation<'tcx>> {
1034+
assert!(!self.in_snapshot(), "cannot take goals in a snapshot");
1035+
std::mem::take(&mut self.inner.borrow_mut().hir_typeck_potentially_region_dependent_goals)
1036+
}
1037+
9811038
pub fn ty_to_string(&self, t: Ty<'tcx>) -> String {
9821039
self.resolve_vars_if_possible(t).to_string()
9831040
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ impl<'tcx> InferCtxt<'tcx> {
177177
}
178178

179179
pub fn take_registered_region_assumptions(&self) -> Vec<ty::ArgOutlivesPredicate<'tcx>> {
180+
assert!(!self.in_snapshot(), "cannot take registered region assumptions in a snapshot");
180181
std::mem::take(&mut self.inner.borrow_mut().region_assumptions)
181182
}
182183

compiler/rustc_infer/src/infer/snapshot/undo_log.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub(crate) enum UndoLog<'tcx> {
2929
ProjectionCache(traits::UndoLog<'tcx>),
3030
PushTypeOutlivesConstraint,
3131
PushRegionAssumption,
32+
PushHirTypeckPotentiallyRegionDependentGoal,
3233
}
3334

3435
macro_rules! impl_from {
@@ -79,7 +80,12 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
7980
assert_matches!(popped, Some(_), "pushed region constraint but could not pop it");
8081
}
8182
UndoLog::PushRegionAssumption => {
82-
self.region_assumptions.pop();
83+
let popped = self.region_assumptions.pop();
84+
assert_matches!(popped, Some(_), "pushed region assumption but could not pop it");
85+
}
86+
UndoLog::PushHirTypeckPotentiallyRegionDependentGoal => {
87+
let popped = self.hir_typeck_potentially_region_dependent_goals.pop();
88+
assert_matches!(popped, Some(_), "pushed goal but could not pop it");
8389
}
8490
}
8591
}

0 commit comments

Comments
 (0)