From df106b6e482b17baa5e94633439dba4c37700a4d Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 20 Jan 2026 14:26:30 +0000 Subject: [PATCH] Misc cleanups to borrowck crate --- .../src/type_check/canonical.rs | 15 +++- .../src/type_check/input_output.rs | 73 +++++++++++++------ compiler/rustc_borrowck/src/type_check/mod.rs | 23 +++--- compiler/rustc_middle/src/ty/util.rs | 5 +- .../normalization-generality-2.rs | 3 + .../normalization-generality.rs | 3 + .../higher-ranked/trait-bounds/issue-88446.rs | 3 + .../normalize-under-binder/issue-89436.rs | 3 + .../normalize-under-binder/issue-90638.rs | 3 + .../issue-112604-closure-output-normalize.rs | 3 + 10 files changed, 96 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index aece0bda34692..08bbae7e2b1f0 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -343,8 +343,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { return; } - // FIXME: Ideally MIR types are normalized, but this is not always true. - let mir_ty = self.normalize(mir_ty, Locations::All(span)); + // This is a hack. `body.local_decls` are not necessarily normalized in the old + // solver due to not deeply normalizing in writeback. So we must re-normalize here. + // + // I am not sure of a test case where this actually matters. There is a similar + // hack in `equate_inputs_and_outputs` which does have associated test cases. + let mir_ty = match self.infcx.tcx.next_trait_solver_globally() { + true => mir_ty, + false => self.normalize(mir_ty, Locations::All(span)), + }; let cause = ObligationCause::dummy_with_span(span); let param_env = self.infcx.param_env; @@ -353,6 +360,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::Boring, type_op::custom::CustomTypeOp::new( |ocx| { + // The `AscribeUserType` query would normally emit a wf + // obligation for the unnormalized user_ty here. This is + // where the "incorrectly skips the WF checks we normally do" + // happens let user_ty = ocx.normalize(&cause, param_env, user_ty); ocx.eq(&cause, param_env, user_ty, mir_ty)?; Ok(()) diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs index f3b9dcc90a845..4e993cf6794ee 100644 --- a/compiler/rustc_borrowck/src/type_check/input_output.rs +++ b/compiler/rustc_borrowck/src/type_check/input_output.rs @@ -126,6 +126,31 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } + // FIXME(BoxyUwU): This should probably be part of a larger borrowck dev-guide chapter + // + /// Enforce that the types of the locals corresponding to the inputs and output of + /// the body are equal to those of the (normalized) signature. + /// + /// This is necessary for two reasons: + /// - Locals in the MIR all start out with `'erased` regions and then are replaced + /// with unconstrained nll vars. If we have a function returning `&'a u32` then + /// the local `_0: &'?10 u32` needs to have its region var equated with the nll + /// var representing `'a`. i.e. borrow check must uphold that `'?10 = 'a`. + /// - When computing the normalized signature we may introduce new unconstrained nll + /// vars due to higher ranked where clauses ([#136547]). We then wind up with implied + /// bounds involving these vars. + /// + /// For this reason it is important that we equate with the *normalized* signature + /// which was produced when computing implied bounds. If we do not do so then we will + /// wind up with implied bounds on nll vars which cannot actually be used as the nll + /// var never gets related to anything. + /// + /// For 'closure-like' bodies this function effectively relates the *inferred* signature + /// of the closure against the locals corresponding to the closure's inputs/output. It *does + /// not* relate the user provided types for the signature to the locals, this is handled + /// separately by: [`TypeChecker::check_signature_annotation`]. + /// + /// [#136547]: #[instrument(skip(self), level = "debug")] pub(super) fn equate_inputs_and_outputs(&mut self, normalized_inputs_and_output: &[Ty<'tcx>]) { let (&normalized_output_ty, normalized_input_tys) = @@ -173,38 +198,40 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } - // Return types are a bit more complex. They may contain opaque `impl Trait` types. - let mir_output_ty = self.body.local_decls[RETURN_PLACE].ty; + // Equate expected output ty with the type of the RETURN_PLACE in MIR + let mir_output_ty = self.body.return_ty(); let output_span = self.body.local_decls[RETURN_PLACE].source_info.span; self.equate_normalized_input_or_output(normalized_output_ty, mir_output_ty, output_span); } #[instrument(skip(self), level = "debug")] fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, span: Span) { - if let Err(_) = - self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) - { - // FIXME(jackh726): This is a hack. It's somewhat like - // `rustc_traits::normalize_after_erasing_regions`. Ideally, we'd - // like to normalize *before* inserting into `local_decls`, but - // doing so ends up causing some other trouble. - let b = self.normalize(b, Locations::All(span)); - - // Note: if we have to introduce new placeholders during normalization above, then we - // won't have added those universes to the universe info, which we would want in - // `relate_tys`. - if let Err(terr) = - self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) - { + // This is a hack. `body.local_decls` are not necessarily normalized in the old + // solver due to not deeply normalizing in writeback. So we must re-normalize here. + // + // However, in most cases normalizing is unnecessary so we only do so if it may be + // necessary for type equality to hold. This leads to some (very minor) performance + // wins. + let b = match self.infcx.tcx.next_trait_solver_globally() { + true => b, + false => match self.eq_types( + a, + b, + Locations::All(span), + ConstraintCategory::BoringNoLocation, + ) { + Ok(_) => return, + Err(_) => self.normalize(b, Locations::All(span)), + }, + }; + + self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) + .unwrap_or_else(|terr| { span_mirbug!( self, Location::START, - "equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`", - a, - b, - terr + "equate_normalized_input_or_output: `{a:?}=={b:?}` failed with `{terr:?}`", ); - } - } + }); } } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index d2464c7e99ee5..676b45e9974cb 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -123,16 +123,19 @@ pub(crate) fn type_check<'tcx>( known_type_outlives_obligations, } = free_region_relations::create(infcx, universal_regions, &mut constraints); - let pre_obligations = infcx.take_registered_region_obligations(); - assert!( - pre_obligations.is_empty(), - "there should be no incoming region obligations = {pre_obligations:#?}", - ); - let pre_assumptions = infcx.take_registered_region_assumptions(); - assert!( - pre_assumptions.is_empty(), - "there should be no incoming region assumptions = {pre_assumptions:#?}", - ); + { + // Scope these variables so it's clear they're not used later + let pre_obligations = infcx.take_registered_region_obligations(); + assert!( + pre_obligations.is_empty(), + "there should be no incoming region obligations = {pre_obligations:#?}", + ); + let pre_assumptions = infcx.take_registered_region_assumptions(); + assert!( + pre_assumptions.is_empty(), + "there should be no incoming region assumptions = {pre_assumptions:#?}", + ); + } debug!(?normalized_inputs_and_output); diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 2797f2fcdb72e..c4212eee8e40b 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -609,9 +609,8 @@ impl<'tcx> TyCtxt<'tcx> { /// have the same `DefKind`. /// /// Note that closures have a `DefId`, but the closure *expression* also has a - /// `HirId` that is located within the context where the closure appears (and, sadly, - /// a corresponding `NodeId`, since those are not yet phased out). The parent of - /// the closure's `DefId` will also be the context where it appears. + /// `HirId` that is located within the context where the closure appears. The + /// parent of the closure's `DefId` will also be the context where it appears. pub fn is_closure_like(self, def_id: DefId) -> bool { matches!(self.def_kind(def_id), DefKind::Closure) } diff --git a/tests/ui/associated-types/normalization-generality-2.rs b/tests/ui/associated-types/normalization-generality-2.rs index 50287f9ee9b8e..2a50f7e449add 100644 --- a/tests/ui/associated-types/normalization-generality-2.rs +++ b/tests/ui/associated-types/normalization-generality-2.rs @@ -1,4 +1,7 @@ //@ build-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver // Ensures that we don't regress on "implementation is not general enough" when // normalizating under binders. Unlike `normalization-generality.rs`, this also produces diff --git a/tests/ui/associated-types/normalization-generality.rs b/tests/ui/associated-types/normalization-generality.rs index 35fcf53b64141..fca70bc7ec671 100644 --- a/tests/ui/associated-types/normalization-generality.rs +++ b/tests/ui/associated-types/normalization-generality.rs @@ -1,4 +1,7 @@ //@ build-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver // Ensures that we don't regress on "implementation is not general enough" when // normalizating under binders. diff --git a/tests/ui/higher-ranked/trait-bounds/issue-88446.rs b/tests/ui/higher-ranked/trait-bounds/issue-88446.rs index 0ca8387776a46..8e42465b929a4 100644 --- a/tests/ui/higher-ranked/trait-bounds/issue-88446.rs +++ b/tests/ui/higher-ranked/trait-bounds/issue-88446.rs @@ -1,4 +1,7 @@ //@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver trait Yokeable<'a> { type Output: 'a; diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs index d85c6999e26f3..226bd48f0e4e5 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs @@ -1,4 +1,7 @@ //@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver #![allow(unused)] diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs index b3feda4a531f1..0dcef54ed69c6 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs @@ -1,4 +1,7 @@ //@check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver trait Yokeable<'a>: 'static { type Output: 'a; diff --git a/tests/ui/nll/issue-112604-closure-output-normalize.rs b/tests/ui/nll/issue-112604-closure-output-normalize.rs index 117e1d91e3413..c999007599179 100644 --- a/tests/ui/nll/issue-112604-closure-output-normalize.rs +++ b/tests/ui/nll/issue-112604-closure-output-normalize.rs @@ -1,4 +1,7 @@ //@check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver use higher_kinded_types::*; mod higher_kinded_types {