From 926a31b570ab16537cfec2aac1903f7d227ccae3 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 28 Apr 2026 00:01:04 +0900 Subject: [PATCH] Canonicalize free regions from inputs as placeholders in root univ --- .../src/canonical/canonicalizer.rs | 46 +++++++++++++++---- .../src/canonical/mod.rs | 12 +++++ ...preserve-equality.borrowck_current.stderr} | 4 +- .../normalization-preserve-equality.rs | 13 ++++-- 4 files changed, 60 insertions(+), 15 deletions(-) rename tests/ui/implied-bounds/{normalization-preserve-equality.borrowck.stderr => normalization-preserve-equality.borrowck_current.stderr} (90%) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 0a3672343e33c..824c78094a69b 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -410,6 +410,11 @@ impl, I: Interner> TypeFolder for Canonicaliz } fn fold_region(&mut self, r: I::Region) -> I::Region { + // We canonicalize free regions from the input into placeholder regions so that + // region constraints created in nested contexts can be propagated back to the + // caller, instead of unifying them. + // See the following Zulip discussion for details: + // https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/A.20question.20on.20.23251/near/579240238 let kind = match r.kind() { ty::ReBound(..) => return r, @@ -417,7 +422,10 @@ impl, I: Interner> TypeFolder for Canonicaliz // when checking whether a `ParamEnv` candidate is global. ty::ReStatic => match self.canonicalize_mode { CanonicalizeMode::Input(CanonicalizeInputKind::Predicate) => { - CanonicalVarKind::Region(ty::UniverseIndex::ROOT) + CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion::new_anon( + ty::UniverseIndex::ROOT, + self.variables.len().into(), + )) } CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv) | CanonicalizeMode::Response { .. } => return r, @@ -431,24 +439,41 @@ impl, I: Interner> TypeFolder for Canonicaliz // `ReErased`. We may be able to short-circuit registering region // obligations if we encounter a `ReErased` on one side, for example. ty::ReErased | ty::ReError(_) => match self.canonicalize_mode { - CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Input(_) => { + CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion::new_anon( + ty::UniverseIndex::ROOT, + self.variables.len().into(), + )) + } CanonicalizeMode::Response { .. } => return r, }, ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode { - CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Input(_) => { + CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion::new_anon( + ty::UniverseIndex::ROOT, + self.variables.len().into(), + )) + } CanonicalizeMode::Response { .. } => { panic!("unexpected region in response: {r:?}") } }, ty::RePlaceholder(placeholder) => match self.canonicalize_mode { - // We canonicalize placeholder regions as existentials in query inputs. - CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Input(_) => { + CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion::new_anon( + ty::UniverseIndex::ROOT, + self.variables.len().into(), + )) + } CanonicalizeMode::Response { max_input_universe } => { // If we have a placeholder region inside of a query, it must be from - // a new universe. - if max_input_universe.can_name(placeholder.universe()) { + // a new universe, unless from the root universe, which is used for + // canonicalization of any free region from the input. + if placeholder.universe() != ty::UniverseIndex::ROOT + && max_input_universe.can_name(placeholder.universe()) + { panic!("new placeholder in universe {max_input_universe:?}: {r:?}"); } CanonicalVarKind::PlaceholderRegion(placeholder) @@ -462,7 +487,12 @@ impl, I: Interner> TypeFolder for Canonicaliz "region vid should have been resolved fully before canonicalization" ); match self.canonicalize_mode { - CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Input(_) => { + CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion::new_anon( + ty::UniverseIndex::ROOT, + self.variables.len().into(), + )) + } CanonicalizeMode::Response { .. } => { CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap()) } diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index a32a693a899cf..b4a2eab2e838a 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -211,6 +211,18 @@ where } else { // For placeholders which were already part of the input, we simply map this // universal bound variable back the placeholder of the input. + // + // For `CanonicalVarKind::PlaceholderRegion`, this differs slightly: we + // canonicalize all free regions from the input into placeholders. This is + // unlike types or consts, where only input placeholders remain placeholders + // in the canonical form. + // + // We can still map these back to the original input regions, as we + // just instantiate the canonical variable with its corresponding + // `original_value`. + // + // For more information on why we canonicalize all input regions as + // placeholders, see the comment in `Canonicalizer::fold_region`. original_values[kind.expect_placeholder_index()] } }) diff --git a/tests/ui/implied-bounds/normalization-preserve-equality.borrowck.stderr b/tests/ui/implied-bounds/normalization-preserve-equality.borrowck_current.stderr similarity index 90% rename from tests/ui/implied-bounds/normalization-preserve-equality.borrowck.stderr rename to tests/ui/implied-bounds/normalization-preserve-equality.borrowck_current.stderr index 96c76ca9ac311..fae1838b32fca 100644 --- a/tests/ui/implied-bounds/normalization-preserve-equality.borrowck.stderr +++ b/tests/ui/implied-bounds/normalization-preserve-equality.borrowck_current.stderr @@ -1,5 +1,5 @@ error: lifetime may not live long enough - --> $DIR/normalization-preserve-equality.rs:24:1 + --> $DIR/normalization-preserve-equality.rs:27:1 | LL | fn test_borrowck<'a, 'b>(_: ( as Trait>::Ty, Equal<'a, 'b>)) { | ^^^^^^^^^^^^^^^^^--^^--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -11,7 +11,7 @@ LL | fn test_borrowck<'a, 'b>(_: ( as Trait>::Ty, Equal<'a, 'b>)) = help: consider adding the following bound: `'a: 'b` error: lifetime may not live long enough - --> $DIR/normalization-preserve-equality.rs:24:1 + --> $DIR/normalization-preserve-equality.rs:27:1 | LL | fn test_borrowck<'a, 'b>(_: ( as Trait>::Ty, Equal<'a, 'b>)) { | ^^^^^^^^^^^^^^^^^--^^--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/implied-bounds/normalization-preserve-equality.rs b/tests/ui/implied-bounds/normalization-preserve-equality.rs index 712c8ce945df0..0d50d26b0488b 100644 --- a/tests/ui/implied-bounds/normalization-preserve-equality.rs +++ b/tests/ui/implied-bounds/normalization-preserve-equality.rs @@ -1,9 +1,12 @@ -// Both revisions should pass. `borrowck` revision is a bug! +// All the revisions should pass. `borrowck_current` revision is a bug! // -//@ revisions: wfcheck borrowck +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ revisions: wfcheck borrowck_current borrowck_next //@ [wfcheck] check-pass -//@ [borrowck] check-fail -//@ [borrowck] known-bug: #106569 +//@ [borrowck_current] check-fail +//@ [borrowck_current] known-bug: #106569 +//@ [borrowck_next] compile-flags: -Znext-solver +//@ [borrowck_next] check-pass struct Equal<'a, 'b>(&'a &'b (), &'b &'a ()); // implies 'a == 'b @@ -20,7 +23,7 @@ trait WfCheckTrait {} #[cfg(wfcheck)] impl<'a, 'b> WfCheckTrait for ( as Trait>::Ty, Equal<'a, 'b>) {} -#[cfg(borrowck)] +#[cfg(any(borrowck_current, borrowck_next))] fn test_borrowck<'a, 'b>(_: ( as Trait>::Ty, Equal<'a, 'b>)) { let _ = None::>; }