From 923f44c6313b86f3c1eeec264dce1a4317c8bbf6 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 10:41:10 +0200
Subject: [PATCH 01/11] consistent name for `UniversalRegions`

---
 compiler/rustc_borrowck/src/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 64ad1c968565e..b4e807646c0a1 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -330,7 +330,7 @@ fn do_mir_borrowck<'tcx>(
     // will have a lifetime tied to the inference context.
     let mut body_owned = input_body.clone();
     let mut promoted = input_promoted.to_owned();
-    let free_regions = nll::replace_regions_in_mir(&infcx, &mut body_owned, &mut promoted);
+    let universal_regions = nll::replace_regions_in_mir(&infcx, &mut body_owned, &mut promoted);
     let body = &body_owned; // no further changes
 
     let location_table = PoloniusLocationTable::new(body);
@@ -355,7 +355,7 @@ fn do_mir_borrowck<'tcx>(
     } = nll::compute_regions(
         root_cx,
         &infcx,
-        free_regions,
+        universal_regions,
         body,
         &promoted,
         &location_table,

From 8cb727424dde83b841610700e40f399848e57700 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 10:42:45 +0200
Subject: [PATCH 02/11] use input `def_id` to compute `movable_coroutine`

This previously incorrectly returned `true` for parent functions whose
first statement was `let local = <coroutine>;`. While that didn't cause
any bugs as we only ever access `movable_coroutine` for `yield`
terminators. It was still wrong.
---
 compiler/rustc_borrowck/src/lib.rs | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index b4e807646c0a1..94c1b54672aca 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -374,17 +374,8 @@ fn do_mir_borrowck<'tcx>(
     let diags_buffer = &mut BorrowckDiagnosticsBuffer::default();
     nll::dump_annotation(&infcx, body, &regioncx, &opt_closure_req, diags_buffer);
 
-    let movable_coroutine =
-    // The first argument is the coroutine type passed by value
-    if let Some(local) = body.local_decls.raw.get(1)
-    // Get the interior types and args which typeck computed
-    && let ty::Coroutine(def_id, _) = *local.ty.kind()
-    && tcx.coroutine_movability(def_id) == hir::Movability::Movable
-{
-    true
-} else {
-    false
-};
+    let movable_coroutine = body.coroutine.is_some()
+        && tcx.coroutine_movability(def.to_def_id()) == hir::Movability::Movable;
 
     // While promoteds should mostly be correct by construction, we need to check them for
     // invalid moves to detect moving out of arrays:`struct S; fn main() { &([S][0]); }`.

From 848187cc8a9afe5485958702b2342939d86010f0 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 10:46:03 +0200
Subject: [PATCH 03/11] `local_names` creation to `mbcx` creation

---
 compiler/rustc_borrowck/src/lib.rs | 40 +++++++++++++++---------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 94c1b54672aca..a38503c2668ad 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -304,26 +304,6 @@ fn do_mir_borrowck<'tcx>(
         root_cx.set_tainted_by_errors(e);
     }
 
-    let mut local_names = IndexVec::from_elem(None, &input_body.local_decls);
-    for var_debug_info in &input_body.var_debug_info {
-        if let VarDebugInfoContents::Place(place) = var_debug_info.value {
-            if let Some(local) = place.as_local() {
-                if let Some(prev_name) = local_names[local]
-                    && var_debug_info.name != prev_name
-                {
-                    span_bug!(
-                        var_debug_info.source_info.span,
-                        "local {:?} has many names (`{}` vs `{}`)",
-                        local,
-                        prev_name,
-                        var_debug_info.name
-                    );
-                }
-                local_names[local] = Some(var_debug_info.name);
-            }
-        }
-    }
-
     // Replace all regions with fresh inference variables. This
     // requires first making our own copy of the MIR. This copy will
     // be modified (in place) to contain non-lexical lifetimes. It
@@ -426,6 +406,26 @@ fn do_mir_borrowck<'tcx>(
         promoted_mbcx.report_move_errors();
     }
 
+    let mut local_names = IndexVec::from_elem(None, &body.local_decls);
+    for var_debug_info in &body.var_debug_info {
+        if let VarDebugInfoContents::Place(place) = var_debug_info.value {
+            if let Some(local) = place.as_local() {
+                if let Some(prev_name) = local_names[local]
+                    && var_debug_info.name != prev_name
+                {
+                    span_bug!(
+                        var_debug_info.source_info.span,
+                        "local {:?} has many names (`{}` vs `{}`)",
+                        local,
+                        prev_name,
+                        var_debug_info.name
+                    );
+                }
+                local_names[local] = Some(var_debug_info.name);
+            }
+        }
+    }
+
     let mut mbcx = MirBorrowckCtxt {
         root_cx,
         infcx: &infcx,

From 01864596dc67a753364df1ce2e2c0915401c57ef Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 11:09:27 +0200
Subject: [PATCH 04/11] do not buffer `#[rustc_regions]` dump

---
 compiler/rustc_borrowck/src/lib.rs | 4 ++--
 compiler/rustc_borrowck/src/nll.rs | 7 ++-----
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index a38503c2668ad..f20bbc3b2673e 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -351,12 +351,12 @@ fn do_mir_borrowck<'tcx>(
 
     // We also have a `#[rustc_regions]` annotation that causes us to dump
     // information.
-    let diags_buffer = &mut BorrowckDiagnosticsBuffer::default();
-    nll::dump_annotation(&infcx, body, &regioncx, &opt_closure_req, diags_buffer);
+    nll::dump_annotation(&infcx, body, &regioncx, &opt_closure_req);
 
     let movable_coroutine = body.coroutine.is_some()
         && tcx.coroutine_movability(def.to_def_id()) == hir::Movability::Movable;
 
+    let diags_buffer = &mut BorrowckDiagnosticsBuffer::default();
     // While promoteds should mostly be correct by construction, we need to check them for
     // invalid moves to detect moving out of arrays:`struct S; fn main() { &([S][0]); }`.
     for promoted_body in &promoted {
diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
index 8a2a34f207aa0..399417937fdbf 100644
--- a/compiler/rustc_borrowck/src/nll.rs
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -21,7 +21,7 @@ use tracing::{debug, instrument};
 
 use crate::borrow_set::BorrowSet;
 use crate::consumers::ConsumerOptions;
-use crate::diagnostics::{BorrowckDiagnosticsBuffer, RegionErrors};
+use crate::diagnostics::RegionErrors;
 use crate::polonius::PoloniusDiagnosticsContext;
 use crate::polonius::legacy::{
     PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
@@ -297,7 +297,6 @@ pub(super) fn dump_annotation<'tcx, 'infcx>(
     body: &Body<'tcx>,
     regioncx: &RegionInferenceContext<'tcx>,
     closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
-    diagnostics_buffer: &mut BorrowckDiagnosticsBuffer<'infcx, 'tcx>,
 ) {
     let tcx = infcx.tcx;
     let base_def_id = tcx.typeck_root_def_id(body.source.def_id());
@@ -335,13 +334,11 @@ pub(super) fn dump_annotation<'tcx, 'infcx>(
     } else {
         let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements");
         regioncx.annotate(tcx, &mut err);
-
         err
     };
 
     // FIXME(@lcnr): We currently don't dump the inferred hidden types here.
-
-    diagnostics_buffer.buffer_non_error(err);
+    err.emit();
 }
 
 fn for_each_region_constraint<'tcx>(

From 0e294f2c2f56c688ea14acdeb03f491268f260ac Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 11:18:32 +0200
Subject: [PATCH 05/11] `MirBorrowckCtxt::polonius_output` to ref

---
 compiler/rustc_borrowck/src/lib.rs | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index f20bbc3b2673e..e9b9cdf6e2026 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -446,9 +446,9 @@ fn do_mir_borrowck<'tcx>(
         local_names,
         region_names: RefCell::default(),
         next_region_name: RefCell::new(1),
-        polonius_output,
         move_errors: Vec::new(),
         diags_buffer,
+        polonius_output: polonius_output.as_deref(),
         polonius_diagnostics: polonius_diagnostics.as_ref(),
     };
 
@@ -505,7 +505,6 @@ fn do_mir_borrowck<'tcx>(
     };
 
     let body_with_facts = if consumer_options.is_some() {
-        let output_facts = mbcx.polonius_output;
         Some(Box::new(BodyWithBorrowckFacts {
             body: body_owned,
             promoted,
@@ -513,7 +512,7 @@ fn do_mir_borrowck<'tcx>(
             region_inference_context: regioncx,
             location_table: polonius_input.as_ref().map(|_| location_table),
             input_facts: polonius_input,
-            output_facts,
+            output_facts: polonius_output,
         }))
     } else {
         None
@@ -700,12 +699,11 @@ struct MirBorrowckCtxt<'a, 'infcx, 'tcx> {
     /// The counter for generating new region names.
     next_region_name: RefCell<usize>,
 
-    /// Results of Polonius analysis.
-    polonius_output: Option<Box<PoloniusOutput>>,
-
     diags_buffer: &'a mut BorrowckDiagnosticsBuffer<'infcx, 'tcx>,
     move_errors: Vec<MoveError<'tcx>>,
 
+    /// Results of Polonius analysis.
+    polonius_output: Option<&'a PoloniusOutput>,
     /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics.
     polonius_diagnostics: Option<&'a PoloniusDiagnosticsContext>,
 }

From 2c65469c2751f11e93e3cc5a9b1dcf1856039196 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 11:24:24 +0200
Subject: [PATCH 06/11] move `dump_polonius_mir`

---
 compiler/rustc_borrowck/src/lib.rs           | 18 ++++++++----------
 compiler/rustc_borrowck/src/polonius/dump.rs |  2 +-
 2 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index e9b9cdf6e2026..820952283bd16 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -348,6 +348,14 @@ fn do_mir_borrowck<'tcx>(
     // Dump MIR results into a file, if that is enabled. This lets us
     // write unit-tests, as well as helping with debugging.
     nll::dump_nll_mir(&infcx, body, &regioncx, &opt_closure_req, &borrow_set);
+    polonius::dump_polonius_mir(
+        &infcx,
+        body,
+        &regioncx,
+        &opt_closure_req,
+        &borrow_set,
+        polonius_diagnostics.as_ref(),
+    );
 
     // We also have a `#[rustc_regions]` annotation that causes us to dump
     // information.
@@ -465,16 +473,6 @@ fn do_mir_borrowck<'tcx>(
 
     mbcx.report_move_errors();
 
-    // If requested, dump polonius MIR.
-    polonius::dump_polonius_mir(
-        &infcx,
-        body,
-        &regioncx,
-        &borrow_set,
-        polonius_diagnostics.as_ref(),
-        &opt_closure_req,
-    );
-
     // For each non-user used mutable variable, check if it's been assigned from
     // a user-declared local. If so, then put that local into the used_mut set.
     // Note that this set is expected to be small - only upvars from closures
diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs
index eb53a98832c33..6a943e1920821 100644
--- a/compiler/rustc_borrowck/src/polonius/dump.rs
+++ b/compiler/rustc_borrowck/src/polonius/dump.rs
@@ -24,9 +24,9 @@ pub(crate) fn dump_polonius_mir<'tcx>(
     infcx: &BorrowckInferCtxt<'tcx>,
     body: &Body<'tcx>,
     regioncx: &RegionInferenceContext<'tcx>,
+    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
     borrow_set: &BorrowSet<'tcx>,
     polonius_diagnostics: Option<&PoloniusDiagnosticsContext>,
-    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
 ) {
     let tcx = infcx.tcx;
     if !tcx.sess.opts.unstable_opts.polonius.is_next_enabled() {

From c5fdddc7f49c492365eab042f3c392b57cdaa7b3 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 11:40:38 +0200
Subject: [PATCH 07/11] don't rely on `locals_are_invalidated_at_exit`

---
 compiler/rustc_borrowck/src/lib.rs | 56 +++++++++++++-----------------
 1 file changed, 24 insertions(+), 32 deletions(-)

diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 820952283bd16..1bc69abe6f9fa 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -22,6 +22,7 @@ use std::cell::RefCell;
 use std::marker::PhantomData;
 use std::ops::{ControlFlow, Deref};
 
+use borrow_set::LocalsStateAtExit;
 use root_cx::BorrowCheckRootCtxt;
 use rustc_abi::FieldIdx;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
@@ -382,7 +383,6 @@ fn do_mir_borrowck<'tcx>(
             location_table: &location_table,
             movable_coroutine,
             fn_self_span_reported: Default::default(),
-            locals_are_invalidated_at_exit,
             access_place_error_reported: Default::default(),
             reservation_error_reported: Default::default(),
             uninitialized_error_reported: Default::default(),
@@ -441,7 +441,6 @@ fn do_mir_borrowck<'tcx>(
         move_data: &move_data,
         location_table: &location_table,
         movable_coroutine,
-        locals_are_invalidated_at_exit,
         fn_self_span_reported: Default::default(),
         access_place_error_reported: Default::default(),
         reservation_error_reported: Default::default(),
@@ -643,13 +642,6 @@ struct MirBorrowckCtxt<'a, 'infcx, 'tcx> {
     location_table: &'a PoloniusLocationTable,
 
     movable_coroutine: bool,
-    /// This keeps track of whether local variables are free-ed when the function
-    /// exits even without a `StorageDead`, which appears to be the case for
-    /// constants.
-    ///
-    /// I'm not sure this is the right approach - @eddyb could you try and
-    /// figure this out?
-    locals_are_invalidated_at_exit: bool,
     /// This field keeps track of when borrow errors are reported in the access_place function
     /// so that there is no duplicate reporting. This field cannot also be used for the conflicting
     /// borrow errors that is handled by the `reservation_error_reported` field as the inclusion
@@ -925,13 +917,20 @@ impl<'a, 'tcx> ResultsVisitor<'a, 'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<
             | TerminatorKind::Return
             | TerminatorKind::TailCall { .. }
             | TerminatorKind::CoroutineDrop => {
-                // Returning from the function implicitly kills storage for all locals and statics.
-                // Often, the storage will already have been killed by an explicit
-                // StorageDead, but we don't always emit those (notably on unwind paths),
-                // so this "extra check" serves as a kind of backup.
-                for i in state.borrows.iter() {
-                    let borrow = &self.borrow_set[i];
-                    self.check_for_invalidation_at_exit(loc, borrow, span);
+                match self.borrow_set.locals_state_at_exit() {
+                    LocalsStateAtExit::AllAreInvalidated => {
+                        // Returning from the function implicitly kills storage for all locals and statics.
+                        // Often, the storage will already have been killed by an explicit
+                        // StorageDead, but we don't always emit those (notably on unwind paths),
+                        // so this "extra check" serves as a kind of backup.
+                        for i in state.borrows.iter() {
+                            let borrow = &self.borrow_set[i];
+                            self.check_for_invalidation_at_exit(loc, borrow, span);
+                        }
+                    }
+                    // If we do not implicitly invalidate all locals on exit,
+                    // we check for conflicts when dropping or moving this local.
+                    LocalsStateAtExit::SomeAreInvalidated { has_storage_dead_or_moved: _ } => {}
                 }
             }
 
@@ -1703,22 +1702,15 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
         // we'll have a memory leak) and assume that all statics have a destructor.
         //
         // FIXME: allow thread-locals to borrow other thread locals?
-
-        let (might_be_alive, will_be_dropped) =
-            if self.body.local_decls[root_place.local].is_ref_to_thread_local() {
-                // Thread-locals might be dropped after the function exits
-                // We have to dereference the outer reference because
-                // borrows don't conflict behind shared references.
-                root_place.projection = TyCtxtConsts::DEREF_PROJECTION;
-                (true, true)
-            } else {
-                (false, self.locals_are_invalidated_at_exit)
-            };
-
-        if !will_be_dropped {
-            debug!("place_is_invalidated_at_exit({:?}) - won't be dropped", place);
-            return;
-        }
+        let might_be_alive = if self.body.local_decls[root_place.local].is_ref_to_thread_local() {
+            // Thread-locals might be dropped after the function exits
+            // We have to dereference the outer reference because
+            // borrows don't conflict behind shared references.
+            root_place.projection = TyCtxtConsts::DEREF_PROJECTION;
+            true
+        } else {
+            false
+        };
 
         let sd = if might_be_alive { Deep } else { Shallow(None) };
 

From e3230b038b4d30d92bdf28aca1a6011256094ae4 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 12:03:42 +0200
Subject: [PATCH 08/11] remove redundant fields

---
 .../src/type_check/constraint_conversion.rs   | 20 ++++++++-----------
 .../src/type_check/free_region_relations.rs   |  4 ----
 compiler/rustc_borrowck/src/type_check/mod.rs | 13 +-----------
 .../rustc_borrowck/src/universal_regions.rs   |  4 ++++
 4 files changed, 13 insertions(+), 28 deletions(-)

diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
index ccb257ae09367..57516565147eb 100644
--- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
+++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
@@ -21,7 +21,6 @@ use crate::{ClosureOutlivesSubject, ClosureRegionRequirements, ConstraintCategor
 
 pub(crate) struct ConstraintConversion<'a, 'tcx> {
     infcx: &'a InferCtxt<'tcx>,
-    tcx: TyCtxt<'tcx>,
     universal_regions: &'a UniversalRegions<'tcx>,
     /// Each RBP `GK: 'a` is assumed to be true. These encode
     /// relationships like `T: 'a` that are added via implicit bounds
@@ -34,7 +33,6 @@ pub(crate) struct ConstraintConversion<'a, 'tcx> {
     /// logic expecting to see (e.g.) `ReStatic`, and if we supplied
     /// our special inference variable there, we would mess that up.
     region_bound_pairs: &'a RegionBoundPairs<'tcx>,
-    implicit_region_bound: ty::Region<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     known_type_outlives_obligations: &'a [ty::PolyTypeOutlivesPredicate<'tcx>],
     locations: Locations,
@@ -49,7 +47,6 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
         infcx: &'a InferCtxt<'tcx>,
         universal_regions: &'a UniversalRegions<'tcx>,
         region_bound_pairs: &'a RegionBoundPairs<'tcx>,
-        implicit_region_bound: ty::Region<'tcx>,
         param_env: ty::ParamEnv<'tcx>,
         known_type_outlives_obligations: &'a [ty::PolyTypeOutlivesPredicate<'tcx>],
         locations: Locations,
@@ -59,10 +56,8 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
     ) -> Self {
         Self {
             infcx,
-            tcx: infcx.tcx,
             universal_regions,
             region_bound_pairs,
-            implicit_region_bound,
             param_env,
             known_type_outlives_obligations,
             locations,
@@ -96,7 +91,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
         // into a vector. These are the regions that we will be
         // relating to one another.
         let closure_mapping = &UniversalRegions::closure_mapping(
-            self.tcx,
+            self.infcx.tcx,
             closure_args,
             closure_requirements.num_external_vids,
             closure_def_id,
@@ -111,7 +106,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
             let subject = match outlives_requirement.subject {
                 ClosureOutlivesSubject::Region(re) => closure_mapping[re].into(),
                 ClosureOutlivesSubject::Ty(subject_ty) => {
-                    subject_ty.instantiate(self.tcx, |vid| closure_mapping[vid]).into()
+                    subject_ty.instantiate(self.infcx.tcx, |vid| closure_mapping[vid]).into()
                 }
             };
 
@@ -127,14 +122,14 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
         predicate: ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>,
         constraint_category: ConstraintCategory<'tcx>,
     ) {
+        let tcx = self.infcx.tcx;
         debug!("generate: constraints at: {:#?}", self.locations);
 
         // Extract out various useful fields we'll need below.
         let ConstraintConversion {
-            tcx,
             infcx,
+            universal_regions,
             region_bound_pairs,
-            implicit_region_bound,
             known_type_outlives_obligations,
             ..
         } = *self;
@@ -145,7 +140,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
                 break;
             }
 
-            if !self.tcx.recursion_limit().value_within_limit(iteration) {
+            if !tcx.recursion_limit().value_within_limit(iteration) {
                 bug!(
                     "FIXME(-Znext-solver): Overflowed when processing region obligations: {outlives_predicates:#?}"
                 );
@@ -170,10 +165,11 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
                             );
                         }
 
+                        let implicit_region_bound =
+                            ty::Region::new_var(tcx, universal_regions.implicit_region_bound());
                         // we don't actually use this for anything, but
                         // the `TypeOutlives` code needs an origin.
                         let origin = infer::RelateParamBound(self.span, t1, None);
-
                         TypeOutlives::new(
                             &mut *self,
                             tcx,
@@ -205,7 +201,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
     /// are dealt with during trait solving.
     fn replace_placeholders_with_nll<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, value: T) -> T {
         if value.has_placeholders() {
-            fold_regions(self.tcx, value, |r, _| match r.kind() {
+            fold_regions(self.infcx.tcx, value, |r, _| match r.kind() {
                 ty::RePlaceholder(placeholder) => {
                     self.constraints.placeholder_region(self.infcx, placeholder)
                 }
diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
index eaac633b512d6..536a27763d29c 100644
--- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
+++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
@@ -49,14 +49,12 @@ pub(crate) struct CreateResult<'tcx> {
 pub(crate) fn create<'tcx>(
     infcx: &InferCtxt<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
-    implicit_region_bound: ty::Region<'tcx>,
     universal_regions: UniversalRegions<'tcx>,
     constraints: &mut MirTypeckRegionConstraints<'tcx>,
 ) -> CreateResult<'tcx> {
     UniversalRegionRelationsBuilder {
         infcx,
         param_env,
-        implicit_region_bound,
         constraints,
         universal_regions,
         region_bound_pairs: Default::default(),
@@ -181,7 +179,6 @@ struct UniversalRegionRelationsBuilder<'a, 'tcx> {
     infcx: &'a InferCtxt<'tcx>,
     param_env: ty::ParamEnv<'tcx>,
     universal_regions: UniversalRegions<'tcx>,
-    implicit_region_bound: ty::Region<'tcx>,
     constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
 
     // outputs:
@@ -320,7 +317,6 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
                 self.infcx,
                 &self.universal_regions,
                 &self.region_bound_pairs,
-                self.implicit_region_bound,
                 param_env,
                 &known_type_outlives_obligations,
                 Locations::All(span),
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index a17dff5d2715e..05e0bb3f9f343 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -113,7 +113,6 @@ pub(crate) fn type_check<'a, 'tcx>(
     move_data: &MoveData<'tcx>,
     location_map: Rc<DenseLocationMap>,
 ) -> MirTypeckResults<'tcx> {
-    let implicit_region_bound = ty::Region::new_var(infcx.tcx, universal_regions.fr_fn_body);
     let mut constraints = MirTypeckRegionConstraints {
         placeholder_indices: PlaceholderIndices::default(),
         placeholder_index_to_region: IndexVec::default(),
@@ -129,13 +128,7 @@ pub(crate) fn type_check<'a, 'tcx>(
         region_bound_pairs,
         normalized_inputs_and_output,
         known_type_outlives_obligations,
-    } = free_region_relations::create(
-        infcx,
-        infcx.param_env,
-        implicit_region_bound,
-        universal_regions,
-        &mut constraints,
-    );
+    } = free_region_relations::create(infcx, infcx.param_env, universal_regions, &mut constraints);
 
     let pre_obligations = infcx.take_registered_region_obligations();
     assert!(
@@ -160,7 +153,6 @@ pub(crate) fn type_check<'a, 'tcx>(
         user_type_annotations: &body.user_type_annotations,
         region_bound_pairs,
         known_type_outlives_obligations,
-        implicit_region_bound,
         reported_errors: Default::default(),
         universal_regions: &universal_region_relations.universal_regions,
         location_table,
@@ -226,7 +218,6 @@ struct TypeChecker<'a, 'tcx> {
     user_type_annotations: &'a CanonicalUserTypeAnnotations<'tcx>,
     region_bound_pairs: RegionBoundPairs<'tcx>,
     known_type_outlives_obligations: Vec<ty::PolyTypeOutlivesPredicate<'tcx>>,
-    implicit_region_bound: ty::Region<'tcx>,
     reported_errors: FxIndexSet<(Ty<'tcx>, Span)>,
     universal_regions: &'a UniversalRegions<'tcx>,
     location_table: &'a PoloniusLocationTable,
@@ -422,7 +413,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             self.infcx,
             self.universal_regions,
             &self.region_bound_pairs,
-            self.implicit_region_bound,
             self.infcx.param_env,
             &self.known_type_outlives_obligations,
             locations,
@@ -2507,7 +2497,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
                 self.infcx,
                 self.universal_regions,
                 &self.region_bound_pairs,
-                self.implicit_region_bound,
                 self.infcx.param_env,
                 &self.known_type_outlives_obligations,
                 locations,
diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs
index 5c57ab99a8592..c11e14d214c42 100644
--- a/compiler/rustc_borrowck/src/universal_regions.rs
+++ b/compiler/rustc_borrowck/src/universal_regions.rs
@@ -438,6 +438,10 @@ impl<'tcx> UniversalRegions<'tcx> {
         }
     }
 
+    pub(crate) fn implicit_region_bound(&self) -> RegionVid {
+        self.fr_fn_body
+    }
+
     pub(crate) fn tainted_by_errors(&self) -> Option<ErrorGuaranteed> {
         self.indices.tainted_by_errors.get()
     }

From f81a5386c9e9ee2543215b2ee002a1e517476b50 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 12:04:48 +0200
Subject: [PATCH 09/11] `NonGenericOpaqueTypeParam::ty` to `arg`

---
 compiler/rustc_trait_selection/messages.ftl        | 4 ++--
 compiler/rustc_trait_selection/src/errors.rs       | 2 +-
 compiler/rustc_trait_selection/src/opaque_types.rs | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/compiler/rustc_trait_selection/messages.ftl b/compiler/rustc_trait_selection/messages.ftl
index 05bbb42fb7c63..74e38f525c8e6 100644
--- a/compiler/rustc_trait_selection/messages.ftl
+++ b/compiler/rustc_trait_selection/messages.ftl
@@ -268,8 +268,8 @@ trait_selection_oc_type_compat = type not compatible with trait
 trait_selection_opaque_captures_lifetime = hidden type for `{$opaque_ty}` captures lifetime that does not appear in bounds
     .label = opaque type defined here
 trait_selection_opaque_type_non_generic_param =
-    expected generic {$kind} parameter, found `{$ty}`
-    .label = {STREQ($ty, "'static") ->
+    expected generic {$kind} parameter, found `{$arg}`
+    .label = {STREQ($arg, "'static") ->
         [true] cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type
         *[other] this generic parameter must be used with a generic {$kind} parameter
     }
diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs
index bb4aba9d29e42..4e5581fb1da0d 100644
--- a/compiler/rustc_trait_selection/src/errors.rs
+++ b/compiler/rustc_trait_selection/src/errors.rs
@@ -1926,7 +1926,7 @@ impl Subdiagnostic for AddPreciseCapturingForOvercapture {
 #[derive(Diagnostic)]
 #[diag(trait_selection_opaque_type_non_generic_param, code = E0792)]
 pub(crate) struct NonGenericOpaqueTypeParam<'a, 'tcx> {
-    pub ty: GenericArg<'tcx>,
+    pub arg: GenericArg<'tcx>,
     pub kind: &'a str,
     #[primary_span]
     pub span: Span,
diff --git a/compiler/rustc_trait_selection/src/opaque_types.rs b/compiler/rustc_trait_selection/src/opaque_types.rs
index 309bf4dda3d89..cce67b066dde1 100644
--- a/compiler/rustc_trait_selection/src/opaque_types.rs
+++ b/compiler/rustc_trait_selection/src/opaque_types.rs
@@ -70,7 +70,7 @@ pub fn check_opaque_type_parameter_valid<'tcx>(
             opaque_env.param_is_error(i)?;
 
             return Err(infcx.dcx().emit_err(NonGenericOpaqueTypeParam {
-                ty: arg,
+                arg,
                 kind,
                 span,
                 param_span: tcx.def_span(opaque_param.def_id),

From 839bb49f9926b3dbcd63093010b1a70320ca23a4 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Fri, 11 Apr 2025 12:49:55 +0200
Subject: [PATCH 10/11] eagerly initialize `definitions` in sub-fn

---
 compiler/rustc_borrowck/src/nll.rs            | 14 +-----
 .../rustc_borrowck/src/region_infer/mod.rs    | 43 ++++++++++---------
 2 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
index 399417937fdbf..fe899bb054fa9 100644
--- a/compiler/rustc_borrowck/src/nll.rs
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -117,11 +117,6 @@ pub(crate) fn compute_regions<'a, 'tcx>(
         Rc::clone(&location_map),
     );
 
-    // Create the region inference context, taking ownership of the
-    // region inference data that was contained in `infcx`, and the
-    // base constraints generated by the type-check.
-    let var_infos = infcx.get_region_var_infos();
-
     // If requested, emit legacy polonius facts.
     polonius::legacy::emit_facts(
         &mut polonius_facts,
@@ -134,13 +129,8 @@ pub(crate) fn compute_regions<'a, 'tcx>(
         &constraints,
     );
 
-    let mut regioncx = RegionInferenceContext::new(
-        infcx,
-        var_infos,
-        constraints,
-        universal_region_relations,
-        location_map,
-    );
+    let mut regioncx =
+        RegionInferenceContext::new(infcx, constraints, universal_region_relations, location_map);
 
     // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints
     // and use them to compute loan liveness.
diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs
index 569c46e6403f2..c82c7341f0287 100644
--- a/compiler/rustc_borrowck/src/region_infer/mod.rs
+++ b/compiler/rustc_borrowck/src/region_infer/mod.rs
@@ -9,7 +9,7 @@ use rustc_errors::Diag;
 use rustc_hir::def_id::CRATE_DEF_ID;
 use rustc_index::IndexVec;
 use rustc_infer::infer::outlives::test_type_match;
-use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq};
+use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq};
 use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin};
 use rustc_middle::bug;
 use rustc_middle::mir::{
@@ -145,7 +145,7 @@ pub struct RegionInferenceContext<'tcx> {
     /// variables are identified by their index (`RegionVid`). The
     /// definition contains information about where the region came
     /// from as well as its final inferred value.
-    pub(crate) definitions: IndexVec<RegionVid, RegionDefinition<'tcx>>,
+    pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
 
     /// The liveness constraints added to each region. For most
     /// regions, these start out empty and steadily grow, though for
@@ -385,6 +385,26 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) {
     debug!("SCC edges {:#?}", scc_node_to_edges);
 }
 
+fn create_definitions<'tcx>(
+    infcx: &BorrowckInferCtxt<'tcx>,
+    universal_regions: &UniversalRegions<'tcx>,
+) -> Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>> {
+    // Create a RegionDefinition for each inference variable.
+    let mut definitions: IndexVec<_, _> = infcx
+        .get_region_var_infos()
+        .iter()
+        .map(|info| RegionDefinition::new(info.universe, info.origin))
+        .collect();
+
+    // Add the external name for all universal regions.
+    for (external_name, variable) in universal_regions.named_universal_regions_iter() {
+        debug!("region {variable:?} has external name {external_name:?}");
+        definitions[variable].external_name = Some(external_name);
+    }
+
+    Frozen::freeze(definitions)
+}
+
 impl<'tcx> RegionInferenceContext<'tcx> {
     /// Creates a new region inference context with a total of
     /// `num_region_variables` valid inference variables; the first N
@@ -395,7 +415,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
     /// of constraints produced by the MIR type check.
     pub(crate) fn new(
         infcx: &BorrowckInferCtxt<'tcx>,
-        var_infos: VarInfos,
         constraints: MirTypeckRegionConstraints<'tcx>,
         universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
         location_map: Rc<DenseLocationMap>,
@@ -426,11 +445,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             infcx.set_tainted_by_errors(guar);
         }
 
-        // Create a RegionDefinition for each inference variable.
-        let definitions: IndexVec<_, _> = var_infos
-            .iter()
-            .map(|info| RegionDefinition::new(info.universe, info.origin))
-            .collect();
+        let definitions = create_definitions(infcx, &universal_regions);
 
         let constraint_sccs =
             outlives_constraints.add_outlives_static(&universal_regions, &definitions);
@@ -526,18 +541,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
     /// means that the `R1: !1` constraint here will cause
     /// `R1` to become `'static`.
     fn init_free_and_bound_regions(&mut self) {
-        // Update the names (if any)
-        // This iterator has unstable order but we collect it all into an IndexVec
-        for (external_name, variable) in
-            self.universal_region_relations.universal_regions.named_universal_regions_iter()
-        {
-            debug!(
-                "init_free_and_bound_regions: region {:?} has external name {:?}",
-                variable, external_name
-            );
-            self.definitions[variable].external_name = Some(external_name);
-        }
-
         for variable in self.definitions.indices() {
             let scc = self.constraint_sccs.scc(variable);
 

From 873f4fc34470c84de81fbf2763ad40704e8c27b4 Mon Sep 17 00:00:00 2001
From: lcnr <rust@lcnr.de>
Date: Mon, 7 Apr 2025 15:37:39 +0200
Subject: [PATCH 11/11] nyaaa

---
 compiler/rustc_borrowck/messages.ftl          |   6 -
 compiler/rustc_borrowck/src/dataflow.rs       |  44 +-
 .../src/diagnostics/region_errors.rs          |  43 -
 compiler/rustc_borrowck/src/lib.rs            |   1 -
 .../rustc_borrowck/src/member_constraints.rs  | 226 -----
 compiler/rustc_borrowck/src/nll.rs            |  37 +-
 .../rustc_borrowck/src/region_infer/mod.rs    | 315 +------
 .../src/region_infer/opaque_types.rs          | 846 +++++++++++++-----
 .../src/region_infer/reverse_sccs.rs          |  49 +-
 .../rustc_borrowck/src/region_infer/values.rs |  38 +-
 compiler/rustc_borrowck/src/root_cx.rs        |  13 +-
 .../rustc_borrowck/src/session_diagnostics.rs |  15 +-
 .../src/type_check/free_region_relations.rs   |   7 -
 compiler/rustc_borrowck/src/type_check/mod.rs |  43 +-
 .../src/type_check/opaque_types.rs            | 337 -------
 compiler/rustc_hir_typeck/src/writeback.rs    |   9 +-
 .../rustc_trait_selection/src/opaque_types.rs |  83 +-
 17 files changed, 807 insertions(+), 1305 deletions(-)
 delete mode 100644 compiler/rustc_borrowck/src/member_constraints.rs
 delete mode 100644 compiler/rustc_borrowck/src/type_check/opaque_types.rs

diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl
index 33b80c4b03d6f..660f4c50c6b5d 100644
--- a/compiler/rustc_borrowck/messages.ftl
+++ b/compiler/rustc_borrowck/messages.ftl
@@ -156,12 +156,6 @@ borrowck_moved_due_to_usage_in_operator =
         *[false] operator
     }
 
-borrowck_opaque_type_lifetime_mismatch =
-    opaque type used twice with different lifetimes
-    .label = lifetime `{$arg}` used here
-    .prev_lifetime_label = lifetime `{$prev}` previously used here
-    .note = if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types
-
 borrowck_partial_var_move_by_use_in_closure =
     variable {$is_partial ->
         [true] partially moved
diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs
index 7511a55b03ae5..7c508ff0902bb 100644
--- a/compiler/rustc_borrowck/src/dataflow.rs
+++ b/compiler/rustc_borrowck/src/dataflow.rs
@@ -1,7 +1,6 @@
 use std::fmt;
 
 use rustc_data_structures::fx::FxIndexMap;
-use rustc_data_structures::graph;
 use rustc_index::bit_set::DenseBitSet;
 use rustc_middle::mir::{
     self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
@@ -317,9 +316,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
             loans_out_of_scope_at_location: FxIndexMap::default(),
         };
         for (loan_idx, loan_data) in borrow_set.iter_enumerated() {
-            let issuing_region = loan_data.region;
             let loan_issued_at = loan_data.reserve_location;
-            prec.precompute_loans_out_of_scope(loan_idx, issuing_region, loan_issued_at);
+            prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at);
         }
 
         prec.loans_out_of_scope_at_location
@@ -328,45 +326,7 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
     /// Loans are in scope while they are live: whether they are contained within any live region.
     /// In the location-insensitive analysis, a loan will be contained in a region if the issuing
     /// region can reach it in the subset graph. So this is a reachability problem.
-    fn precompute_loans_out_of_scope(
-        &mut self,
-        loan_idx: BorrowIndex,
-        issuing_region: RegionVid,
-        loan_issued_at: Location,
-    ) {
-        let sccs = self.regioncx.constraint_sccs();
-        let universal_regions = self.regioncx.universal_regions();
-
-        // The loop below was useful for the location-insensitive analysis but shouldn't be
-        // impactful in the location-sensitive case. It seems that it does, however, as without it a
-        // handful of tests fail. That likely means some liveness or outlives data related to choice
-        // regions is missing
-        // FIXME: investigate the impact of loans traversing applied member constraints and why some
-        // tests fail otherwise.
-        //
-        // We first handle the cases where the loan doesn't go out of scope, depending on the
-        // issuing region's successors.
-        for successor in graph::depth_first_search(&self.regioncx.region_graph(), issuing_region) {
-            // Via applied member constraints
-            //
-            // The issuing region can flow into the choice regions, and they are either:
-            // - placeholders or free regions themselves,
-            // - or also transitively outlive a free region.
-            //
-            // That is to say, if there are applied member constraints here, the loan escapes the
-            // function and cannot go out of scope. We could early return here.
-            //
-            // For additional insurance via fuzzing and crater, we verify that the constraint's min
-            // choice indeed escapes the function. In the future, we could e.g. turn this check into
-            // a debug assert and early return as an optimization.
-            let scc = sccs.scc(successor);
-            for constraint in self.regioncx.applied_member_constraints(scc) {
-                if universal_regions.is_universal_region(constraint.min_choice) {
-                    return;
-                }
-            }
-        }
-
+    fn precompute_loans_out_of_scope(&mut self, loan_idx: BorrowIndex, loan_issued_at: Location) {
         let first_block = loan_issued_at.block;
         let first_bb_data = &self.body.basic_blocks[first_block];
 
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
index 8d530b51636a5..6d4ed4de99722 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
@@ -23,7 +23,6 @@ use rustc_trait_selection::error_reporting::infer::nice_region_error::{
     self, HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor, find_anon_type,
     find_param_with_region, suggest_adding_lifetime_params,
 };
-use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic;
 use rustc_trait_selection::infer::InferCtxtExt;
 use rustc_trait_selection::traits::{Obligation, ObligationCtxt};
 use tracing::{debug, instrument, trace};
@@ -84,9 +83,6 @@ impl<'tcx> RegionErrors<'tcx> {
         let guar = self.1.sess.dcx().delayed_bug(format!("{val:?}"));
         self.0.push((val, guar));
     }
-    pub(crate) fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
     pub(crate) fn into_iter(
         self,
     ) -> impl Iterator<Item = (RegionErrorKind<'tcx>, ErrorGuaranteed)> {
@@ -108,18 +104,6 @@ pub(crate) enum RegionErrorKind<'tcx> {
     /// A generic bound failure for a type test (`T: 'a`).
     TypeTestError { type_test: TypeTest<'tcx> },
 
-    /// An unexpected hidden region for an opaque type.
-    UnexpectedHiddenRegion {
-        /// The span for the member constraint.
-        span: Span,
-        /// The hidden type.
-        hidden_ty: Ty<'tcx>,
-        /// The opaque type.
-        key: ty::OpaqueTypeKey<'tcx>,
-        /// The unexpected region.
-        member_region: ty::Region<'tcx>,
-    },
-
     /// Higher-ranked subtyping error.
     BoundUniversalRegionError {
         /// The placeholder free region.
@@ -312,9 +296,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         // buffered in the `MirBorrowckCtxt`.
 
         let mut outlives_suggestion = OutlivesSuggestionBuilder::default();
-        let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> =
-            None;
-
         for (nll_error, _) in nll_errors.into_iter() {
             match nll_error {
                 RegionErrorKind::TypeTestError { type_test } => {
@@ -364,30 +345,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                     }
                 }
 
-                RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => {
-                    let named_ty =
-                        self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, hidden_ty);
-                    let named_key =
-                        self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, key);
-                    let named_region = self
-                        .regioncx
-                        .name_regions_for_member_constraint(self.infcx.tcx, member_region);
-                    let diag = unexpected_hidden_region_diagnostic(
-                        self.infcx,
-                        self.mir_def_id(),
-                        span,
-                        named_ty,
-                        named_region,
-                        named_key,
-                    );
-                    if last_unexpected_hidden_region != Some((span, named_ty, named_key)) {
-                        self.buffer_error(diag);
-                        last_unexpected_hidden_region = Some((span, named_ty, named_key));
-                    } else {
-                        diag.delay_as_bug();
-                    }
-                }
-
                 RegionErrorKind::BoundUniversalRegionError {
                     longer_fr,
                     placeholder,
diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs
index 1bc69abe6f9fa..5ef408df24a82 100644
--- a/compiler/rustc_borrowck/src/lib.rs
+++ b/compiler/rustc_borrowck/src/lib.rs
@@ -76,7 +76,6 @@ mod constraints;
 mod dataflow;
 mod def_use;
 mod diagnostics;
-mod member_constraints;
 mod nll;
 mod path_utils;
 mod place_ext;
diff --git a/compiler/rustc_borrowck/src/member_constraints.rs b/compiler/rustc_borrowck/src/member_constraints.rs
deleted file mode 100644
index bdd0f6fe11e0f..0000000000000
--- a/compiler/rustc_borrowck/src/member_constraints.rs
+++ /dev/null
@@ -1,226 +0,0 @@
-use std::hash::Hash;
-use std::ops::Index;
-
-use rustc_data_structures::fx::FxIndexMap;
-use rustc_index::{IndexSlice, IndexVec};
-use rustc_middle::ty::{self, Ty};
-use rustc_span::Span;
-use tracing::instrument;
-
-/// Compactly stores a set of `R0 member of [R1...Rn]` constraints,
-/// indexed by the region `R0`.
-#[derive(Debug)]
-pub(crate) struct MemberConstraintSet<'tcx, R>
-where
-    R: Copy + Eq,
-{
-    /// Stores the first "member" constraint for a given `R0`. This is an
-    /// index into the `constraints` vector below.
-    first_constraints: FxIndexMap<R, NllMemberConstraintIndex>,
-
-    /// Stores the data about each `R0 member of [R1..Rn]` constraint.
-    /// These are organized into a linked list, so each constraint
-    /// contains the index of the next constraint with the same `R0`.
-    constraints: IndexVec<NllMemberConstraintIndex, MemberConstraint<'tcx>>,
-
-    /// Stores the `R1..Rn` regions for *all* sets. For any given
-    /// constraint, we keep two indices so that we can pull out a
-    /// slice.
-    choice_regions: Vec<ty::RegionVid>,
-}
-
-/// Represents a `R0 member of [R1..Rn]` constraint
-#[derive(Debug)]
-pub(crate) struct MemberConstraint<'tcx> {
-    next_constraint: Option<NllMemberConstraintIndex>,
-
-    /// The span where the hidden type was instantiated.
-    pub(crate) definition_span: Span,
-
-    /// The hidden type in which `R0` appears. (Used in error reporting.)
-    pub(crate) hidden_ty: Ty<'tcx>,
-
-    pub(crate) key: ty::OpaqueTypeKey<'tcx>,
-
-    /// The region `R0`.
-    pub(crate) member_region_vid: ty::RegionVid,
-
-    /// Index of `R1` in `choice_regions` vector from `MemberConstraintSet`.
-    start_index: usize,
-
-    /// Index of `Rn` in `choice_regions` vector from `MemberConstraintSet`.
-    end_index: usize,
-}
-
-rustc_index::newtype_index! {
-    #[debug_format = "MemberConstraintIndex({})"]
-    pub(crate) struct NllMemberConstraintIndex {}
-}
-
-impl Default for MemberConstraintSet<'_, ty::RegionVid> {
-    fn default() -> Self {
-        Self {
-            first_constraints: Default::default(),
-            constraints: Default::default(),
-            choice_regions: Default::default(),
-        }
-    }
-}
-
-impl<'tcx> MemberConstraintSet<'tcx, ty::RegionVid> {
-    pub(crate) fn is_empty(&self) -> bool {
-        self.constraints.is_empty()
-    }
-
-    /// Pushes a member constraint into the set.
-    #[instrument(level = "debug", skip(self))]
-    pub(crate) fn add_member_constraint(
-        &mut self,
-        key: ty::OpaqueTypeKey<'tcx>,
-        hidden_ty: Ty<'tcx>,
-        definition_span: Span,
-        member_region_vid: ty::RegionVid,
-        choice_regions: &[ty::RegionVid],
-    ) {
-        let next_constraint = self.first_constraints.get(&member_region_vid).cloned();
-        let start_index = self.choice_regions.len();
-        self.choice_regions.extend(choice_regions);
-        let end_index = self.choice_regions.len();
-        let constraint_index = self.constraints.push(MemberConstraint {
-            next_constraint,
-            member_region_vid,
-            definition_span,
-            hidden_ty,
-            key,
-            start_index,
-            end_index,
-        });
-        self.first_constraints.insert(member_region_vid, constraint_index);
-    }
-}
-
-impl<'tcx, R1> MemberConstraintSet<'tcx, R1>
-where
-    R1: Copy + Hash + Eq,
-{
-    /// Remap the "member region" key using `map_fn`, producing a new
-    /// member constraint set. This is used in the NLL code to map from
-    /// the original `RegionVid` to an scc index. In some cases, we
-    /// may have multiple `R1` values mapping to the same `R2` key -- that
-    /// is ok, the two sets will be merged.
-    pub(crate) fn into_mapped<R2>(
-        self,
-        mut map_fn: impl FnMut(R1) -> R2,
-    ) -> MemberConstraintSet<'tcx, R2>
-    where
-        R2: Copy + Hash + Eq,
-    {
-        // We can re-use most of the original data, just tweaking the
-        // linked list links a bit.
-        //
-        // For example if we had two keys `Ra` and `Rb` that both now
-        // wind up mapped to the same key `S`, we would append the
-        // linked list for `Ra` onto the end of the linked list for
-        // `Rb` (or vice versa) -- this basically just requires
-        // rewriting the final link from one list to point at the other
-        // other (see `append_list`).
-
-        let MemberConstraintSet { first_constraints, mut constraints, choice_regions } = self;
-
-        let mut first_constraints2 = FxIndexMap::default();
-        first_constraints2.reserve(first_constraints.len());
-
-        for (r1, start1) in first_constraints {
-            let r2 = map_fn(r1);
-            if let Some(&start2) = first_constraints2.get(&r2) {
-                append_list(&mut constraints, start1, start2);
-            }
-            first_constraints2.insert(r2, start1);
-        }
-
-        MemberConstraintSet { first_constraints: first_constraints2, constraints, choice_regions }
-    }
-}
-
-impl<'tcx, R> MemberConstraintSet<'tcx, R>
-where
-    R: Copy + Hash + Eq,
-{
-    pub(crate) fn all_indices(&self) -> impl Iterator<Item = NllMemberConstraintIndex> {
-        self.constraints.indices()
-    }
-
-    /// Iterate down the constraint indices associated with a given
-    /// peek-region. You can then use `choice_regions` and other
-    /// methods to access data.
-    pub(crate) fn indices(
-        &self,
-        member_region_vid: R,
-    ) -> impl Iterator<Item = NllMemberConstraintIndex> {
-        let mut next = self.first_constraints.get(&member_region_vid).cloned();
-        std::iter::from_fn(move || -> Option<NllMemberConstraintIndex> {
-            if let Some(current) = next {
-                next = self.constraints[current].next_constraint;
-                Some(current)
-            } else {
-                None
-            }
-        })
-    }
-
-    /// Returns the "choice regions" for a given member
-    /// constraint. This is the `R1..Rn` from a constraint like:
-    ///
-    /// ```text
-    /// R0 member of [R1..Rn]
-    /// ```
-    pub(crate) fn choice_regions(&self, pci: NllMemberConstraintIndex) -> &[ty::RegionVid] {
-        let MemberConstraint { start_index, end_index, .. } = &self.constraints[pci];
-        &self.choice_regions[*start_index..*end_index]
-    }
-}
-
-impl<'tcx, R> Index<NllMemberConstraintIndex> for MemberConstraintSet<'tcx, R>
-where
-    R: Copy + Eq,
-{
-    type Output = MemberConstraint<'tcx>;
-
-    fn index(&self, i: NllMemberConstraintIndex) -> &MemberConstraint<'tcx> {
-        &self.constraints[i]
-    }
-}
-
-/// Given a linked list starting at `source_list` and another linked
-/// list starting at `target_list`, modify `target_list` so that it is
-/// followed by `source_list`.
-///
-/// Before:
-///
-/// ```text
-/// target_list: A -> B -> C -> (None)
-/// source_list: D -> E -> F -> (None)
-/// ```
-///
-/// After:
-///
-/// ```text
-/// target_list: A -> B -> C -> D -> E -> F -> (None)
-/// ```
-fn append_list(
-    constraints: &mut IndexSlice<NllMemberConstraintIndex, MemberConstraint<'_>>,
-    target_list: NllMemberConstraintIndex,
-    source_list: NllMemberConstraintIndex,
-) {
-    let mut p = target_list;
-    loop {
-        let r = &mut constraints[p];
-        match r.next_constraint {
-            Some(q) => p = q,
-            None => {
-                r.next_constraint = Some(source_list);
-                return;
-            }
-        }
-    }
-}
diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
index fe899bb054fa9..402fba81c61bb 100644
--- a/compiler/rustc_borrowck/src/nll.rs
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -27,6 +27,7 @@ use crate::polonius::legacy::{
     PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
 };
 use crate::region_infer::RegionInferenceContext;
+use crate::region_infer::opaque_types::handle_opaque_type_uses;
 use crate::type_check::{self, MirTypeckResults};
 use crate::universal_regions::UniversalRegions;
 use crate::{
@@ -98,22 +99,26 @@ pub(crate) fn compute_regions<'a, 'tcx>(
     let location_map = Rc::new(DenseLocationMap::new(body));
 
     // Run the MIR type-checker.
-    let MirTypeckResults {
-        constraints,
-        universal_region_relations,
-        opaque_type_values,
-        polonius_context,
-    } = type_check::type_check(
+    let MirTypeckResults { constraints, universal_region_relations, polonius_context } =
+        type_check::type_check(
+            root_cx,
+            infcx,
+            body,
+            promoted,
+            universal_regions,
+            location_table,
+            borrow_set,
+            &mut polonius_facts,
+            flow_inits,
+            move_data,
+            Rc::clone(&location_map),
+        );
+
+    let (constraints, deferred_opaque_type_errors) = handle_opaque_type_uses(
         root_cx,
         infcx,
-        body,
-        promoted,
-        universal_regions,
-        location_table,
-        borrow_set,
-        &mut polonius_facts,
-        flow_inits,
-        move_data,
+        constraints,
+        &universal_region_relations,
         Rc::clone(&location_map),
     );
 
@@ -166,10 +171,10 @@ pub(crate) fn compute_regions<'a, 'tcx>(
     if let Some(guar) = nll_errors.has_errors() {
         // Suppress unhelpful extra errors in `infer_opaque_types`.
         infcx.set_tainted_by_errors(guar);
+    } else if !deferred_opaque_type_errors.is_empty() {
+        regioncx.emit_deferred_opaque_type_errors(root_cx, infcx, deferred_opaque_type_errors);
     }
 
-    regioncx.infer_opaque_types(root_cx, infcx, opaque_type_values);
-
     NllOutput {
         regioncx,
         polonius_input: polonius_facts.map(Box::new),
diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs
index c82c7341f0287..b209a7f0a9c25 100644
--- a/compiler/rustc_borrowck/src/region_infer/mod.rs
+++ b/compiler/rustc_borrowck/src/region_infer/mod.rs
@@ -1,7 +1,6 @@
 use std::collections::VecDeque;
 use std::rc::Rc;
 
-use rustc_data_structures::binary_search_util;
 use rustc_data_structures::frozen::Frozen;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::graph::scc::{self, Sccs};
@@ -23,14 +22,12 @@ use rustc_span::hygiene::DesugaringKind;
 use rustc_span::{DUMMY_SP, Span};
 use tracing::{Level, debug, enabled, instrument, trace};
 
-use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph};
+use crate::constraints::graph::NormalConstraintGraph;
 use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet};
 use crate::dataflow::BorrowIndex;
 use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo};
-use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex};
 use crate::polonius::LiveLoans;
 use crate::polonius::legacy::PoloniusOutput;
-use crate::region_infer::reverse_sccs::ReverseSccGraph;
 use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex};
 use crate::type_check::free_region_relations::UniversalRegionRelations;
 use crate::type_check::{Locations, MirTypeckRegionConstraints};
@@ -42,7 +39,7 @@ use crate::{
 
 mod dump_mir;
 mod graphviz;
-mod opaque_types;
+pub(crate) mod opaque_types;
 mod reverse_sccs;
 
 pub(crate) mod values;
@@ -166,20 +163,6 @@ pub struct RegionInferenceContext<'tcx> {
     /// compute the values of each region.
     constraint_sccs: ConstraintSccs,
 
-    /// Reverse of the SCC constraint graph --  i.e., an edge `A -> B` exists if
-    /// `B: A`. This is used to compute the universal regions that are required
-    /// to outlive a given SCC. Computed lazily.
-    rev_scc_graph: Option<ReverseSccGraph>,
-
-    /// The "R0 member of [R1..Rn]" constraints, indexed by SCC.
-    member_constraints: Rc<MemberConstraintSet<'tcx, ConstraintSccIndex>>,
-
-    /// Records the member constraints that we applied to each scc.
-    /// This is useful for error reporting. Once constraint
-    /// propagation is done, this vector is sorted according to
-    /// `member_region_scc`.
-    member_constraints_applied: Vec<AppliedMemberConstraint>,
-
     /// Map universe indexes to information on why we created it.
     universe_causes: FxIndexMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
 
@@ -196,32 +179,6 @@ pub struct RegionInferenceContext<'tcx> {
     universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
 }
 
-/// Each time that `apply_member_constraint` is successful, it appends
-/// one of these structs to the `member_constraints_applied` field.
-/// This is used in error reporting to trace out what happened.
-///
-/// The way that `apply_member_constraint` works is that it effectively
-/// adds a new lower bound to the SCC it is analyzing: so you wind up
-/// with `'R: 'O` where `'R` is the pick-region and `'O` is the
-/// minimal viable option.
-#[derive(Debug)]
-pub(crate) struct AppliedMemberConstraint {
-    /// The SCC that was affected. (The "member region".)
-    ///
-    /// The vector if `AppliedMemberConstraint` elements is kept sorted
-    /// by this field.
-    pub(crate) member_region_scc: ConstraintSccIndex,
-
-    /// The "best option" that `apply_member_constraint` found -- this was
-    /// added as an "ad-hoc" lower-bound to `member_region_scc`.
-    pub(crate) min_choice: ty::RegionVid,
-
-    /// The "member constraint index" -- we can find out details about
-    /// the constraint from
-    /// `set.member_constraints[member_constraint_index]`.
-    pub(crate) member_constraint_index: NllMemberConstraintIndex,
-}
-
 #[derive(Debug)]
 pub(crate) struct RegionDefinition<'tcx> {
     /// What kind of variable is this -- a free region? existential
@@ -314,7 +271,6 @@ enum Trace<'a, 'tcx> {
     StartRegion,
     FromGraph(&'a OutlivesConstraint<'tcx>),
     FromStatic(RegionVid),
-    FromMember(RegionVid, RegionVid, Span),
     NotVisited,
 }
 
@@ -425,7 +381,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             placeholder_index_to_region: _,
             liveness_constraints,
             mut outlives_constraints,
-            mut member_constraints,
             universe_causes,
             type_tests,
         } = constraints;
@@ -439,7 +394,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all
             // outlives bounds that we may end up checking.
             outlives_constraints = Default::default();
-            member_constraints = Default::default();
 
             // Also taint the entire scope.
             infcx.set_tainted_by_errors(guar);
@@ -464,18 +418,12 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             scc_values.merge_liveness(scc, region, &liveness_constraints);
         }
 
-        let member_constraints =
-            Rc::new(member_constraints.into_mapped(|r| constraint_sccs.scc(r)));
-
         let mut result = Self {
             definitions,
             liveness_constraints,
             constraints,
             constraint_graph,
             constraint_sccs,
-            rev_scc_graph: None,
-            member_constraints,
-            member_constraints_applied: Vec::new(),
             universe_causes,
             scc_values,
             type_tests,
@@ -633,19 +581,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         self.scc_universe(self.constraint_sccs.scc(r))
     }
 
-    /// Once region solving has completed, this function will return the member constraints that
-    /// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`.
-    pub(crate) fn applied_member_constraints(
-        &self,
-        scc: ConstraintSccIndex,
-    ) -> &[AppliedMemberConstraint] {
-        binary_search_util::binary_search_slice(
-            &self.member_constraints_applied,
-            |applied| applied.member_region_scc,
-            &scc,
-        )
-    }
-
     /// Performs region inference and report errors if we see any
     /// unsatisfiable constraints. If this is a closure, returns the
     /// region requirements to propagate to our creator, if any.
@@ -657,7 +592,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         polonius_output: Option<Box<PoloniusOutput>>,
     ) -> (Option<ClosureRegionRequirements<'tcx>>, RegionErrors<'tcx>) {
         let mir_def_id = body.source.def_id();
-        self.propagate_constraints();
+        self.scc_values.propagate_constraints(&self.constraint_sccs);
 
         let mut errors_buffer = RegionErrors::new(infcx.tcx);
 
@@ -690,12 +625,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
 
         debug!(?errors_buffer);
 
-        if errors_buffer.is_empty() {
-            self.check_member_constraints(infcx, &mut errors_buffer);
-        }
-
-        debug!(?errors_buffer);
-
         let outlives_requirements = outlives_requirements.unwrap_or_default();
 
         if outlives_requirements.is_empty() {
@@ -709,168 +638,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         }
     }
 
-    /// Propagate the region constraints: this will grow the values
-    /// for each region variable until all the constraints are
-    /// satisfied. Note that some values may grow **too** large to be
-    /// feasible, but we check this later.
-    #[instrument(skip(self), level = "debug")]
-    fn propagate_constraints(&mut self) {
-        debug!("constraints={:#?}", {
-            let mut constraints: Vec<_> = self.outlives_constraints().collect();
-            constraints.sort_by_key(|c| (c.sup, c.sub));
-            constraints
-                .into_iter()
-                .map(|c| (c, self.constraint_sccs.scc(c.sup), self.constraint_sccs.scc(c.sub)))
-                .collect::<Vec<_>>()
-        });
-
-        // To propagate constraints, we walk the DAG induced by the
-        // SCC. For each SCC, we visit its successors and compute
-        // their values, then we union all those values to get our
-        // own.
-        for scc in self.constraint_sccs.all_sccs() {
-            self.compute_value_for_scc(scc);
-        }
-
-        // Sort the applied member constraints so we can binary search
-        // through them later.
-        self.member_constraints_applied.sort_by_key(|applied| applied.member_region_scc);
-    }
-
-    /// Computes the value of the SCC `scc_a`, which has not yet been
-    /// computed, by unioning the values of its successors.
-    /// Assumes that all successors have been computed already
-    /// (which is assured by iterating over SCCs in dependency order).
-    #[instrument(skip(self), level = "debug")]
-    fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) {
-        // Walk each SCC `B` such that `A: B`...
-        for &scc_b in self.constraint_sccs.successors(scc_a) {
-            debug!(?scc_b);
-            self.scc_values.add_region(scc_a, scc_b);
-        }
-
-        // Now take member constraints into account.
-        let member_constraints = Rc::clone(&self.member_constraints);
-        for m_c_i in member_constraints.indices(scc_a) {
-            self.apply_member_constraint(scc_a, m_c_i, member_constraints.choice_regions(m_c_i));
-        }
-
-        debug!(value = ?self.scc_values.region_value_str(scc_a));
-    }
-
-    /// Invoked for each `R0 member of [R1..Rn]` constraint.
-    ///
-    /// `scc` is the SCC containing R0, and `choice_regions` are the
-    /// `R1..Rn` regions -- they are always known to be universal
-    /// regions (and if that's not true, we just don't attempt to
-    /// enforce the constraint).
-    ///
-    /// The current value of `scc` at the time the method is invoked
-    /// is considered a *lower bound*. If possible, we will modify
-    /// the constraint to set it equal to one of the option regions.
-    /// If we make any changes, returns true, else false.
-    ///
-    /// This function only adds the member constraints to the region graph,
-    /// it does not check them. They are later checked in
-    /// `check_member_constraints` after the region graph has been computed.
-    #[instrument(skip(self, member_constraint_index), level = "debug")]
-    fn apply_member_constraint(
-        &mut self,
-        scc: ConstraintSccIndex,
-        member_constraint_index: NllMemberConstraintIndex,
-        choice_regions: &[ty::RegionVid],
-    ) {
-        // Lazily compute the reverse graph, we'll need it later.
-        self.compute_reverse_scc_graph();
-
-        // Create a mutable vector of the options. We'll try to winnow
-        // them down.
-        let mut choice_regions: Vec<ty::RegionVid> = choice_regions.to_vec();
-
-        // Convert to the SCC representative: sometimes we have inference
-        // variables in the member constraint that wind up equated with
-        // universal regions. The scc representative is the minimal numbered
-        // one from the corresponding scc so it will be the universal region
-        // if one exists.
-        for c_r in &mut choice_regions {
-            let scc = self.constraint_sccs.scc(*c_r);
-            *c_r = self.scc_representative(scc);
-        }
-
-        // If the member region lives in a higher universe, we currently choose
-        // the most conservative option by leaving it unchanged.
-        if !self.constraint_sccs().annotation(scc).min_universe().is_root() {
-            return;
-        }
-
-        // The existing value for `scc` is a lower-bound. This will
-        // consist of some set `{P} + {LB}` of points `{P}` and
-        // lower-bound free regions `{LB}`. As each choice region `O`
-        // is a free region, it will outlive the points. But we can
-        // only consider the option `O` if `O: LB`.
-        choice_regions.retain(|&o_r| {
-            self.scc_values
-                .universal_regions_outlived_by(scc)
-                .all(|lb| self.universal_region_relations.outlives(o_r, lb))
-        });
-        debug!(?choice_regions, "after lb");
-
-        // Now find all the *upper bounds* -- that is, each UB is a
-        // free region that must outlive the member region `R0` (`UB:
-        // R0`). Therefore, we need only keep an option `O` if `UB: O`
-        // for all UB.
-        let universal_region_relations = &self.universal_region_relations;
-        for ub in self.rev_scc_graph.as_ref().unwrap().upper_bounds(scc) {
-            debug!(?ub);
-            choice_regions.retain(|&o_r| universal_region_relations.outlives(ub, o_r));
-        }
-        debug!(?choice_regions, "after ub");
-
-        // At this point we can pick any member of `choice_regions` and would like to choose
-        // it to be a small as possible. To avoid potential non-determinism we will pick the
-        // smallest such choice.
-        //
-        // Because universal regions are only partially ordered (i.e, not every two regions are
-        // comparable), we will ignore any region that doesn't compare to all others when picking
-        // the minimum choice.
-        //
-        // For example, consider `choice_regions = ['static, 'a, 'b, 'c, 'd, 'e]`, where
-        // `'static: 'a, 'static: 'b, 'a: 'c, 'b: 'c, 'c: 'd, 'c: 'e`.
-        // `['d, 'e]` are ignored because they do not compare - the same goes for `['a, 'b]`.
-        let totally_ordered_subset = choice_regions.iter().copied().filter(|&r1| {
-            choice_regions.iter().all(|&r2| {
-                self.universal_region_relations.outlives(r1, r2)
-                    || self.universal_region_relations.outlives(r2, r1)
-            })
-        });
-        // Now we're left with `['static, 'c]`. Pick `'c` as the minimum!
-        let Some(min_choice) = totally_ordered_subset.reduce(|r1, r2| {
-            let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
-            let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
-            match (r1_outlives_r2, r2_outlives_r1) {
-                (true, true) => r1.min(r2),
-                (true, false) => r2,
-                (false, true) => r1,
-                (false, false) => bug!("incomparable regions in total order"),
-            }
-        }) else {
-            debug!("no unique minimum choice");
-            return;
-        };
-
-        // As we require `'scc: 'min_choice`, we have definitely already computed
-        // its `scc_values` at this point.
-        let min_choice_scc = self.constraint_sccs.scc(min_choice);
-        debug!(?min_choice, ?min_choice_scc);
-        if self.scc_values.add_region(scc, min_choice_scc) {
-            self.member_constraints_applied.push(AppliedMemberConstraint {
-                member_region_scc: scc,
-                min_choice,
-                member_constraint_index,
-            });
-        }
-    }
-
     /// Returns `true` if all the elements in the value of `scc_b` are nameable
     /// in `scc_a`. Used during constraint propagation, and only once
     /// the value of `scc_b` has been computed.
@@ -1109,12 +876,11 @@ impl<'tcx> RegionInferenceContext<'tcx> {
     #[instrument(level = "debug", skip(self))]
     pub(crate) fn approx_universal_upper_bound(&self, r: RegionVid) -> RegionVid {
         debug!("{}", self.region_value_str(r));
-
         // Find the smallest universal region that contains all other
         // universal regions within `region`.
-        let mut lub = self.universal_regions().fr_fn_body;
+        let mut lub = self.universal_region_relations.universal_regions.fr_fn_body;
         let r_scc = self.constraint_sccs.scc(r);
-        let static_r = self.universal_regions().fr_static;
+        let static_r = self.universal_region_relations.universal_regions.fr_static;
         for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
             let new_lub = self.universal_region_relations.postdom_upper_bound(lub, ur);
             debug!(?ur, ?lub, ?new_lub);
@@ -1126,9 +892,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 // Prefer the region with an `external_name` - this
                 // indicates that the region is early-bound, so working with
                 // it can produce a nicer error.
-                if self.region_definition(ur).external_name.is_some() {
+                if self.definitions[ur].external_name.is_some() {
                     lub = ur;
-                } else if self.region_definition(lub).external_name.is_some() {
+                } else if self.definitions[lub].external_name.is_some() {
                     // Leave lub unchanged
                 } else {
                     // If we get here, we don't have any reason to prefer
@@ -1655,43 +1421,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         debug!("check_bound_universal_region: all bounds satisfied");
     }
 
-    #[instrument(level = "debug", skip(self, infcx, errors_buffer))]
-    fn check_member_constraints(
-        &self,
-        infcx: &InferCtxt<'tcx>,
-        errors_buffer: &mut RegionErrors<'tcx>,
-    ) {
-        let member_constraints = Rc::clone(&self.member_constraints);
-        for m_c_i in member_constraints.all_indices() {
-            debug!(?m_c_i);
-            let m_c = &member_constraints[m_c_i];
-            let member_region_vid = m_c.member_region_vid;
-            debug!(
-                ?member_region_vid,
-                value = ?self.region_value_str(member_region_vid),
-            );
-            let choice_regions = member_constraints.choice_regions(m_c_i);
-            debug!(?choice_regions);
-
-            // Did the member region wind up equal to any of the option regions?
-            if let Some(o) =
-                choice_regions.iter().find(|&&o_r| self.eval_equal(o_r, m_c.member_region_vid))
-            {
-                debug!("evaluated as equal to {:?}", o);
-                continue;
-            }
-
-            // If not, report an error.
-            let member_region = ty::Region::new_var(infcx.tcx, member_region_vid);
-            errors_buffer.push(RegionErrorKind::UnexpectedHiddenRegion {
-                span: m_c.definition_span,
-                hidden_ty: m_c.hidden_ty,
-                key: m_c.key,
-                member_region,
-            });
-        }
-    }
-
     /// We have a constraint `fr1: fr2` that is not satisfied, where
     /// `fr2` represents some universal region. Here, `r` is some
     /// region where we know that `fr1: r` and this function has the
@@ -1815,20 +1544,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                             result.push(c);
                         }
 
-                        Trace::FromMember(sup, sub, span) => {
-                            let c = OutlivesConstraint {
-                                sup,
-                                sub,
-                                locations: Locations::All(span),
-                                span,
-                                category: ConstraintCategory::OpaqueType,
-                                variance_info: ty::VarianceDiagInfo::default(),
-                                from_closure: false,
-                            };
-                            p = c.sup;
-                            result.push(c);
-                        }
-
                         Trace::StartRegion => {
                             result.reverse();
                             return Some((result, r));
@@ -1875,15 +1590,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                     handle_trace(constraint.sub, Trace::FromGraph(constraint));
                 }
             }
-
-            // Member constraints can also give rise to `'r: 'x` edges that
-            // were not part of the graph initially, so watch out for those.
-            // (But they are extremely rare; this loop is very cold.)
-            for constraint in self.applied_member_constraints(self.constraint_sccs.scc(r)) {
-                let sub = constraint.min_choice;
-                let p_c = &self.member_constraints[constraint.member_constraint_index];
-                handle_trace(sub, Trace::FromMember(r, sub, p_c.definition_span));
-            }
         }
 
         None
@@ -2206,11 +1912,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         &self.constraint_sccs
     }
 
-    /// Access to the region graph, built from the outlives constraints.
-    pub(crate) fn region_graph(&self) -> RegionGraph<'_, 'tcx, graph::Normal> {
-        self.constraint_graph.region_graph(&self.constraints, self.universal_regions().fr_static)
-    }
-
     /// Returns the representative `RegionVid` for a given SCC.
     /// See `RegionTracker` for how a region variable ID is chosen.
     ///
@@ -2243,7 +1944,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
 }
 
 impl<'tcx> RegionDefinition<'tcx> {
-    fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self {
+    pub(crate) fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self {
         // Create a new region definition. Note that, for free
         // regions, the `external_name` field gets updated later in
         // `init_free_and_bound_regions`.
diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
index 550c57338d301..cf7d775ce00b5 100644
--- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
+++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs
@@ -1,234 +1,526 @@
-use rustc_data_structures::fx::FxIndexMap;
+use std::rc::Rc;
+
+use rustc_data_structures::frozen::Frozen;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def_id::DefId;
+use rustc_index::IndexVec;
 use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin};
 use rustc_macros::extension;
+use rustc_middle::bug;
+use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::ty::relate::{
+    Relate, RelateResult, TypeRelation, structurally_relate_consts, structurally_relate_tys,
+};
 use rustc_middle::ty::{
-    self, DefiningScopeKind, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable,
-    TypeVisitableExt, fold_regions,
+    self, DefiningScopeKind, GenericArgsRef, OpaqueHiddenType, OpaqueTypeKey, Region, RegionVid,
+    Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, fold_regions,
+};
+use rustc_mir_dataflow::points::DenseLocationMap;
+use rustc_span::{ErrorGuaranteed, Span};
+use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic;
+use rustc_trait_selection::opaque_types::{
+    InvalidOpaqueTypeArgs, check_opaque_type_parameter_valid,
 };
-use rustc_span::Span;
-use rustc_trait_selection::opaque_types::check_opaque_type_parameter_valid;
 use tracing::{debug, instrument};
 
-use super::RegionInferenceContext;
-use crate::BorrowCheckRootCtxt;
-use crate::session_diagnostics::LifetimeMismatchOpaqueParam;
-use crate::universal_regions::RegionClassification;
+use super::reverse_sccs::ReverseSccGraph;
+use super::values::RegionValues;
+use super::{ConstraintSccs, RegionDefinition, RegionInferenceContext};
+use crate::constraints::ConstraintSccIndex;
+use crate::consumers::OutlivesConstraint;
+use crate::type_check::free_region_relations::UniversalRegionRelations;
+use crate::type_check::{Locations, MirTypeckRegionConstraints};
+use crate::universal_regions::{RegionClassification, UniversalRegions};
+use crate::{BorrowCheckRootCtxt, BorrowckInferCtxt};
 
-impl<'tcx> RegionInferenceContext<'tcx> {
-    /// Resolve any opaque types that were encountered while borrow checking
-    /// this item. This is then used to get the type in the `type_of` query.
-    ///
-    /// For example consider `fn f<'a>(x: &'a i32) -> impl Sized + 'a { x }`.
-    /// This is lowered to give HIR something like
-    ///
-    /// type f<'a>::_Return<'_x> = impl Sized + '_x;
-    /// fn f<'a>(x: &'a i32) -> f<'a>::_Return<'a> { x }
-    ///
-    /// When checking the return type record the type from the return and the
-    /// type used in the return value. In this case they might be `_Return<'1>`
-    /// and `&'2 i32` respectively.
-    ///
-    /// Once we to this method, we have completed region inference and want to
-    /// call `infer_opaque_definition_from_instantiation` to get the inferred
-    /// type of `_Return<'_x>`. `infer_opaque_definition_from_instantiation`
-    /// compares lifetimes directly, so we need to map the inference variables
-    /// back to concrete lifetimes: `'static`, `ReEarlyParam` or `ReLateParam`.
-    ///
-    /// First we map the regions in the generic parameters `_Return<'1>` to
-    /// their `external_name` giving `_Return<'a>`. This step is a bit involved.
-    /// See the [rustc-dev-guide chapter] for more info.
-    ///
-    /// Then we map all the lifetimes in the concrete type to an equal
-    /// universal region that occurs in the opaque type's args, in this case
-    /// this would result in `&'a i32`. We only consider regions in the args
-    /// in case there is an equal region that does not. For example, this should
-    /// be allowed:
-    /// `fn f<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a { x }`
-    ///
-    /// This will then allow `infer_opaque_definition_from_instantiation` to
-    /// determine that `_Return<'_x> = &'_x i32`.
-    ///
-    /// There's a slight complication around closures. Given
-    /// `fn f<'a: 'a>() { || {} }` the closure's type is something like
-    /// `f::<'a>::{{closure}}`. The region parameter from f is essentially
-    /// ignored by type checking so ends up being inferred to an empty region.
-    /// Calling `universal_upper_bound` for such a region gives `fr_fn_body`,
-    /// which has no `external_name` in which case we use `'{erased}` as the
-    /// region to pass to `infer_opaque_definition_from_instantiation`.
-    ///
-    /// [rustc-dev-guide chapter]:
-    /// https://rustc-dev-guide.rust-lang.org/opaque-types-region-infer-restrictions.html
-    #[instrument(level = "debug", skip(self, root_cx, infcx), ret)]
-    pub(crate) fn infer_opaque_types(
-        &self,
-        root_cx: &mut BorrowCheckRootCtxt<'tcx>,
-        infcx: &InferCtxt<'tcx>,
-        opaque_ty_decls: FxIndexMap<OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>>,
-    ) {
-        let mut decls_modulo_regions: FxIndexMap<OpaqueTypeKey<'tcx>, (OpaqueTypeKey<'tcx>, Span)> =
-            FxIndexMap::default();
-
-        for (opaque_type_key, concrete_type) in opaque_ty_decls {
-            debug!(?opaque_type_key, ?concrete_type);
-
-            let mut arg_regions: Vec<(ty::RegionVid, ty::Region<'_>)> =
-                vec![(self.universal_regions().fr_static, infcx.tcx.lifetimes.re_static)];
-
-            let opaque_type_key =
-                opaque_type_key.fold_captured_lifetime_args(infcx.tcx, |region| {
-                    // Use the SCC representative instead of directly using `region`.
-                    // See [rustc-dev-guide chapter] § "Strict lifetime equality".
-                    let scc = self.constraint_sccs.scc(region.as_var());
-                    let vid = self.scc_representative(scc);
-                    let named = match self.definitions[vid].origin {
-                        // Iterate over all universal regions in a consistent order and find the
-                        // *first* equal region. This makes sure that equal lifetimes will have
-                        // the same name and simplifies subsequent handling.
-                        // See [rustc-dev-guide chapter] § "Semantic lifetime equality".
-                        NllRegionVariableOrigin::FreeRegion => self
-                            .universal_regions()
-                            .universal_regions_iter()
-                            .filter(|&ur| {
-                                // See [rustc-dev-guide chapter] § "Closure restrictions".
-                                !matches!(
-                                    self.universal_regions().region_classification(ur),
-                                    Some(RegionClassification::External)
-                                )
-                            })
-                            .find(|&ur| self.universal_region_relations.equal(vid, ur))
-                            .map(|ur| self.definitions[ur].external_name.unwrap()),
-                        NllRegionVariableOrigin::Placeholder(placeholder) => {
-                            Some(ty::Region::new_placeholder(infcx.tcx, placeholder))
-                        }
-                        NllRegionVariableOrigin::Existential { .. } => None,
-                    }
-                    .unwrap_or_else(|| {
-                        ty::Region::new_error_with_message(
-                            infcx.tcx,
-                            concrete_type.span,
-                            "opaque type with non-universal region args",
-                        )
-                    });
-
-                    arg_regions.push((vid, named));
-                    named
-                });
-            debug!(?opaque_type_key, ?arg_regions);
-
-            let concrete_type = fold_regions(infcx.tcx, concrete_type, |region, _| {
-                arg_regions
-                    .iter()
-                    .find(|&&(arg_vid, _)| self.eval_equal(region.as_var(), arg_vid))
-                    .map(|&(_, arg_named)| arg_named)
-                    .unwrap_or(infcx.tcx.lifetimes.re_erased)
-            });
-            debug!(?concrete_type);
-
-            let ty =
-                infcx.infer_opaque_definition_from_instantiation(opaque_type_key, concrete_type);
-
-            // Sometimes, when the hidden type is an inference variable, it can happen that
-            // the hidden type becomes the opaque type itself. In this case, this was an opaque
-            // usage of the opaque type and we can ignore it. This check is mirrored in typeck's
-            // writeback.
-            if !infcx.next_trait_solver() {
-                if let ty::Alias(ty::Opaque, alias_ty) = ty.kind()
-                    && alias_ty.def_id == opaque_type_key.def_id.to_def_id()
-                    && alias_ty.args == opaque_type_key.args
+pub(crate) enum DeferredOpaqueTypeError<'tcx> {
+    UnexpectedHiddenRegion {
+        /// The opaque type.
+        opaque_type_key: OpaqueTypeKey<'tcx>,
+        /// The hidden type containing the member region.
+        hidden_type: OpaqueHiddenType<'tcx>,
+        /// The unexpected region.
+        member_region: Region<'tcx>,
+    },
+    InvalidOpaqueTypeArgs(InvalidOpaqueTypeArgs<'tcx>),
+}
+
+pub(crate) fn handle_opaque_type_uses<'tcx>(
+    root_cx: &mut BorrowCheckRootCtxt<'tcx>,
+    infcx: &BorrowckInferCtxt<'tcx>,
+    mut constraints: MirTypeckRegionConstraints<'tcx>,
+    universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
+    location_map: Rc<DenseLocationMap>,
+) -> (MirTypeckRegionConstraints<'tcx>, Vec<DeferredOpaqueTypeError<'tcx>>) {
+    let opaque_types = infcx.take_opaque_types();
+    if opaque_types.is_empty() {
+        return (constraints, Vec::new());
+    }
+
+    let tcx = infcx.tcx;
+    // We need to eagerly map all regions to NLL vars here, as we need to make sure we've
+    // introduced nll vars for all used placeholders.
+    let mut opaque_types = opaque_types
+        .into_iter()
+        .map(|entry| {
+            fold_regions(tcx, infcx.resolve_vars_if_possible(entry), |r, _| {
+                let vid = if let ty::RePlaceholder(placeholder) = r.kind() {
+                    constraints.placeholder_region(infcx, placeholder).as_var()
+                } else {
+                    universal_region_relations.universal_regions.to_region_vid(r)
+                };
+                Region::new_var(tcx, vid)
+            })
+        })
+        .collect::<Vec<_>>();
+
+    let mut definitions: IndexVec<_, _> = infcx
+        .get_region_var_infos()
+        .iter()
+        .map(|info| RegionDefinition::new(info.universe, info.origin))
+        .collect();
+
+    // Update the names (if any)
+    // This iterator has unstable order but we collect it all into an IndexVec
+    for (external_name, variable) in
+        universal_region_relations.universal_regions.named_universal_regions_iter()
+    {
+        definitions[variable].external_name = Some(external_name);
+    }
+
+    let universal_regions = &universal_region_relations.universal_regions;
+    let fr_static = universal_regions.fr_static;
+    let constraint_sccs = &constraints.outlives_constraints.compute_sccs(fr_static, &definitions);
+    let rev_scc_graph = &ReverseSccGraph::compute(&constraint_sccs, universal_regions);
+    // Unlike the `RegionInferenceContext`, we only care about free regions
+    // and fully ignore liveness and placeholders.
+    let placeholder_indices = Default::default();
+    let mut scc_values =
+        RegionValues::new(location_map, universal_regions.len(), placeholder_indices);
+    for variable in definitions.indices() {
+        let scc = constraint_sccs.scc(variable);
+        match definitions[variable].origin {
+            NllRegionVariableOrigin::FreeRegion => {
+                scc_values.add_element(scc, variable);
+            }
+            _ => {}
+        }
+    }
+    scc_values.propagate_constraints(&constraint_sccs);
+    for entry in &mut opaque_types {
+        // Map all opaque types to their SCC representatives.
+        *entry = fold_regions(tcx, *entry, |r, _| {
+            let scc = constraint_sccs.scc(r.as_var());
+            let vid = constraint_sccs.annotation(scc).representative;
+            Region::new_var(tcx, vid)
+        })
+    }
+
+    let mut deferred_errors = Vec::new();
+
+    // We start by looking for defining uses of the opaque. These are uses where all arguments
+    // of the opaque are free regions. We apply "member constraints" to its hidden region and
+    // map the hidden type to the definition site of the opaque.
+    'entry: for &(opaque_type_key, hidden_type) in &opaque_types {
+        // Check whether the arguments are fully universal.
+        //
+        // FIXME: We currently treat `Opaque<'a, 'a>` as a defining use and then emit an error
+        // as it's not fully universal. We should share this code with `check_opaque_type_parameter_valid`
+        // to only consider actual defining uses as defining.
+        let mut arg_regions = vec![(universal_regions.fr_static, tcx.lifetimes.re_static)];
+        for (_idx, captured_arg) in opaque_type_key.iter_captured_args(tcx) {
+            if let Some(region) = captured_arg.as_region() {
+                let vid = region.as_var();
+                if matches!(definitions[vid].origin, NllRegionVariableOrigin::FreeRegion)
+                    && !matches!(
+                        universal_regions.region_classification(vid),
+                        Some(RegionClassification::External)
+                    )
                 {
-                    continue;
+                    arg_regions.push((vid, definitions[vid].external_name.unwrap()));
+                } else {
+                    continue 'entry;
                 }
             }
+        }
 
-            root_cx.add_concrete_opaque_type(
-                opaque_type_key.def_id,
-                OpaqueHiddenType { span: concrete_type.span, ty },
-            );
-
-            // Check that all opaque types have the same region parameters if they have the same
-            // non-region parameters. This is necessary because within the new solver we perform
-            // various query operations modulo regions, and thus could unsoundly select some impls
-            // that don't hold.
-            if !ty.references_error()
-                && let Some((prev_decl_key, prev_span)) = decls_modulo_regions.insert(
-                    infcx.tcx.erase_regions(opaque_type_key),
-                    (opaque_type_key, concrete_type.span),
-                )
-                && let Some((arg1, arg2)) = std::iter::zip(
-                    prev_decl_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg),
-                    opaque_type_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg),
+        debug!(?opaque_type_key, ?hidden_type, "check defining use");
+
+        let opaque_type_key = opaque_type_key.fold_captured_lifetime_args(tcx, |region| {
+            let vid = region.as_var();
+            assert!(matches!(definitions[vid].origin, NllRegionVariableOrigin::FreeRegion));
+            definitions[vid].external_name.unwrap()
+        });
+
+        let hidden_type = hidden_type.fold_with(&mut OpaqueHiddenTypeFolder {
+            infcx,
+
+            opaque_type_key,
+            hidden_type,
+            deferred_errors: &mut deferred_errors,
+
+            arg_regions: &arg_regions,
+            universal_region_relations,
+            constraint_sccs,
+            rev_scc_graph,
+            scc_values: &scc_values,
+        });
+
+        let ty = infcx
+            .infer_opaque_definition_from_instantiation(opaque_type_key, hidden_type)
+            .unwrap_or_else(|err| {
+                deferred_errors.push(DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err));
+                Ty::new_error_with_message(
+                    tcx,
+                    hidden_type.span,
+                    "deferred invalid opaque type args",
                 )
-                .find(|(arg1, arg2)| arg1 != arg2)
+            });
+
+        // TODO this doesn't seem to help
+        if !tcx.use_typing_mode_borrowck() {
+            if let ty::Alias(ty::Opaque, alias_ty) = ty.kind()
+                && alias_ty.def_id == opaque_type_key.def_id.to_def_id()
+                && alias_ty.args == opaque_type_key.args
             {
-                infcx.dcx().emit_err(LifetimeMismatchOpaqueParam {
-                    arg: arg1,
-                    prev: arg2,
-                    span: prev_span,
-                    prev_span: concrete_type.span,
-                });
+                continue 'entry;
             }
         }
+        root_cx.add_concrete_opaque_type(
+            opaque_type_key.def_id,
+            OpaqueHiddenType { span: hidden_type.span, ty },
+        );
     }
 
-    /// Map the regions in the type to named regions. This is similar to what
-    /// `infer_opaque_types` does, but can infer any universal region, not only
-    /// ones from the args for the opaque type. It also doesn't double check
-    /// that the regions produced are in fact equal to the named region they are
-    /// replaced with. This is fine because this function is only to improve the
-    /// region names in error messages.
-    ///
-    /// This differs from `MirBorrowckCtxt::name_regions` since it is particularly
-    /// lax with mapping region vids that are *shorter* than a universal region to
-    /// that universal region. This is useful for member region constraints since
-    /// we want to suggest a universal region name to capture even if it's technically
-    /// not equal to the error region.
-    pub(crate) fn name_regions_for_member_constraint<T>(&self, tcx: TyCtxt<'tcx>, ty: T) -> T
-    where
-        T: TypeFoldable<TyCtxt<'tcx>>,
-    {
-        fold_regions(tcx, ty, |region, _| match region.kind() {
-            ty::ReVar(vid) => {
-                let scc = self.constraint_sccs.scc(vid);
+    for &(key, hidden_type) in &opaque_types {
+        if !tcx.use_typing_mode_borrowck() {
+            if let ty::Alias(ty::Opaque, alias_ty) = hidden_type.ty.kind()
+                && alias_ty.def_id == key.def_id.to_def_id()
+                && alias_ty.args == key.args
+            {
+                continue;
+            }
+        }
 
-                // Special handling of higher-ranked regions.
-                if !self.scc_universe(scc).is_root() {
-                    match self.scc_values.placeholders_contained_in(scc).enumerate().last() {
-                        // If the region contains a single placeholder then they're equal.
-                        Some((0, placeholder)) => {
-                            return ty::Region::new_placeholder(tcx, placeholder);
-                        }
+        let Some(expected) = root_cx.get_concrete_opaque_type(key.def_id) else {
+            let guar =
+                tcx.dcx().span_err(hidden_type.span, "non-defining use in the defining scope");
+            root_cx.add_concrete_opaque_type(key.def_id, OpaqueHiddenType::new_error(tcx, guar));
+            infcx.set_tainted_by_errors(guar);
+            continue;
+        };
 
-                        // Fallback: this will produce a cryptic error message.
-                        _ => return region,
+        let expected = ty::fold_regions(tcx, expected.instantiate(tcx, key.args), |re, _dbi| {
+            match re.kind() {
+                ty::ReErased => infcx.next_nll_region_var(
+                    NllRegionVariableOrigin::Existential { from_forall: false },
+                    || crate::RegionCtxt::Existential(None),
+                ),
+                _ => re,
+            }
+        });
+
+        let mut relation = EquateRegions {
+            infcx,
+            span: hidden_type.span,
+            universal_regions,
+            constraints: &mut constraints,
+        };
+        match TypeRelation::relate(&mut relation, hidden_type.ty, expected.ty) {
+            Ok(_) => {}
+            Err(_) => {
+                let _ = hidden_type.build_mismatch_error(&expected, tcx).map(|d| d.emit());
+            }
+        }
+
+        // normalize -> equate
+        //
+        // Do we need to normalize?
+        // Only to support non-universal type or const args. It feels likely that we need (and want) to do so
+        // This once again needs to be careful about cycles: normalizing and equating while defining/revealing
+        // opaque types may end up introducing new defining uses.
+
+        // How can we equate here?
+        // If we need to normalize the answer is just "it's TypeChecker time"
+        // without it we could manually walk over the types using a type relation and equate region vars
+        // that way.
+    }
+
+    (constraints, deferred_errors)
+}
+
+fn to_region_vid<'tcx>(
+    constraints: &MirTypeckRegionConstraints<'tcx>,
+    universal_regions: &UniversalRegions<'tcx>,
+    r: Region<'tcx>,
+) -> RegionVid {
+    if let ty::RePlaceholder(placeholder) = r.kind() {
+        constraints.get_placeholder_region(placeholder).as_var()
+    } else {
+        universal_regions.to_region_vid(r)
+    }
+}
+
+struct OpaqueHiddenTypeFolder<'a, 'tcx> {
+    infcx: &'a BorrowckInferCtxt<'tcx>,
+    // For diagnostics.
+    opaque_type_key: OpaqueTypeKey<'tcx>,
+    hidden_type: OpaqueHiddenType<'tcx>,
+    deferred_errors: &'a mut Vec<DeferredOpaqueTypeError<'tcx>>,
+
+    arg_regions: &'a [(RegionVid, Region<'tcx>)],
+    universal_region_relations: &'a UniversalRegionRelations<'tcx>,
+    constraint_sccs: &'a ConstraintSccs,
+    rev_scc_graph: &'a ReverseSccGraph,
+    scc_values: &'a RegionValues<ConstraintSccIndex>,
+}
+
+impl<'tcx> OpaqueHiddenTypeFolder<'_, 'tcx> {
+    #[instrument(level = "debug", skip(self))]
+    fn apply_member_constraint(&mut self, member_vid: RegionVid) -> Option<Region<'tcx>> {
+        let member = self.constraint_sccs.scc(member_vid);
+        if let Some((_, reg)) = self.arg_regions.iter().copied().find(|&(vid, _)| vid == member_vid)
+        {
+            debug!("member equal to arg");
+            return Some(reg);
+        }
+
+        // If the member region lives in a higher universe, we currently choose
+        // the most conservative option by leaving it unchanged.
+        if !self.constraint_sccs.annotation(member).min_universe().is_root() {
+            debug!("member not in root universe");
+            return None;
+        }
+
+        // The existing value of `'member` is a lower-bound. If its is already larger than
+        // some universal region, we cannot equate it with that region. Said differently, we
+        // ignore choice regions which are smaller than this member region.
+        let mut choice_regions = self
+            .arg_regions
+            .iter()
+            .copied()
+            .filter(|&(choice_region, _)| {
+                self.scc_values.universal_regions_outlived_by(member).all(|lower_bound| {
+                    self.universal_region_relations.outlives(choice_region, lower_bound)
+                })
+            })
+            .collect::<Vec<_>>();
+        debug!(?choice_regions, "after enforcing lower-bound");
+
+        // Now find all the *upper bounds* -- that is, each UB is a
+        // free region that must outlive the member region `R0` (`UB:
+        // R0`). Therefore, we need only keep an option `O` if `UB: O`
+        // for all UB.
+        //
+        // If we have a requirement `'upper_bound: 'member`, equating `'member`
+        // with some region `'choice` means we now also require `'upper_bound: 'choice`.
+        // Avoid choice regions for which this does not hold.
+        for ub in self.rev_scc_graph.upper_bounds(member) {
+            choice_regions.retain(|&(choice_region, _)| {
+                self.universal_region_relations.outlives(ub, choice_region)
+            });
+        }
+
+        debug!(?choice_regions, "after enforcing upper-bound");
+
+        // At this point we can pick any member of `choice_regions` and would like to choose
+        // it to be a small as possible. To avoid potential non-determinism we will pick the
+        // smallest such choice.
+        //
+        // Because universal regions are only partially ordered (i.e, not every two regions are
+        // comparable), we will ignore any region that doesn't compare to all others when picking
+        // the minimum choice.
+        //
+        // For example, consider `choice_regions = ['static, 'a, 'b, 'c, 'd, 'e]`, where
+        // `'static: 'a, 'static: 'b, 'a: 'c, 'b: 'c, 'c: 'd, 'c: 'e`.
+        // `['d, 'e]` are ignored because they do not compare - the same goes for `['a, 'b]`.
+        let totally_ordered_subset = choice_regions.iter().copied().filter(|&(r1, _)| {
+            choice_regions.iter().all(|&(r2, _)| {
+                self.universal_region_relations.outlives(r1, r2)
+                    || self.universal_region_relations.outlives(r2, r1)
+            })
+        });
+        // Now we're left with `['static, 'c]`. Pick `'c` as the minimum!
+        let Some((_, min_choice)) = totally_ordered_subset.reduce(|(r1, r1_reg), (r2, r2_reg)| {
+            let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2);
+            let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1);
+            match (r1_outlives_r2, r2_outlives_r1) {
+                (true, true) => {
+                    if r1 < r2 {
+                        (r1, r1_reg)
+                    } else {
+                        (r2, r2_reg)
                     }
                 }
+                (true, false) => (r2, r2_reg),
+                (false, true) => (r1, r1_reg),
+                (false, false) => bug!("incomparable regions in total order"),
+            }
+        }) else {
+            debug!("no unique minimum choice");
+            return None;
+        };
+        debug!(?min_choice);
+        Some(min_choice)
+    }
 
-                // Find something that we can name
-                let upper_bound = self.approx_universal_upper_bound(vid);
-                if let Some(universal_region) = self.definitions[upper_bound].external_name {
-                    return universal_region;
-                }
+    fn fold_closure_args(
+        &mut self,
+        def_id: DefId,
+        args: GenericArgsRef<'tcx>,
+    ) -> GenericArgsRef<'tcx> {
+        let generics = self.cx().generics_of(def_id);
+        self.cx().mk_args_from_iter(args.iter().enumerate().map(|(index, arg)| {
+            if index < generics.parent_count {
+                self.cx().erase_regions(arg)
+            } else {
+                arg.fold_with(self)
+            }
+        }))
+    }
+}
 
-                // Nothing exact found, so we pick a named upper bound, if there's only one.
-                // If there's >1 universal region, then we probably are dealing w/ an intersection
-                // region which cannot be mapped back to a universal.
-                // FIXME: We could probably compute the LUB if there is one.
-                let scc = self.constraint_sccs.scc(vid);
-                let upper_bounds: Vec<_> = self
-                    .rev_scc_graph
-                    .as_ref()
-                    .unwrap()
-                    .upper_bounds(scc)
-                    .filter_map(|vid| self.definitions[vid].external_name)
-                    .filter(|r| !r.is_static())
-                    .collect();
-                match &upper_bounds[..] {
-                    [universal_region] => *universal_region,
-                    _ => region,
-                }
+impl<'tcx> TypeFolder<TyCtxt<'tcx>> for OpaqueHiddenTypeFolder<'_, 'tcx> {
+    fn cx(&self) -> TyCtxt<'tcx> {
+        self.infcx.tcx
+    }
+
+    fn fold_region(&mut self, r: Region<'tcx>) -> Region<'tcx> {
+        match r.kind() {
+            // ignore bound regions, keep visiting
+            ty::ReBound(_, _) => r,
+            _ => self.apply_member_constraint(r.as_var()).unwrap_or_else(|| {
+                self.deferred_errors.push(DeferredOpaqueTypeError::UnexpectedHiddenRegion {
+                    hidden_type: self.hidden_type,
+                    opaque_type_key: self.opaque_type_key,
+                    member_region: r,
+                });
+                ty::Region::new_error_with_message(
+                    self.cx(),
+                    self.hidden_type.span,
+                    "opaque type with non-universal region args",
+                )
+            }),
+        }
+    }
+
+    fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
+        if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) {
+            return ty;
+        }
+
+        let tcx = self.cx();
+        match *ty.kind() {
+            ty::Closure(def_id, args) => {
+                Ty::new_closure(tcx, def_id, self.fold_closure_args(def_id, args))
             }
-            _ => region,
-        })
+
+            ty::CoroutineClosure(def_id, args) => {
+                Ty::new_coroutine_closure(tcx, def_id, self.fold_closure_args(def_id, args))
+            }
+
+            ty::Coroutine(def_id, args) => {
+                Ty::new_coroutine(tcx, def_id, self.fold_closure_args(def_id, args))
+            }
+
+            ty::Alias(kind, ty::AliasTy { def_id, args, .. })
+                if let Some(variances) = tcx.opt_alias_variances(kind, def_id) =>
+            {
+                // Skip lifetime parameters that are not captured, since they do
+                // not need member constraints registered for them; we'll erase
+                // them (and hopefully in the future replace them with placeholders).
+                let args =
+                    tcx.mk_args_from_iter(std::iter::zip(variances, args.iter()).map(|(&v, s)| {
+                        if v == ty::Bivariant { tcx.erase_regions(s) } else { s.fold_with(self) }
+                    }));
+                ty::AliasTy::new_from_args(tcx, def_id, args).to_ty(tcx)
+            }
+
+            _ => ty.super_fold_with(self),
+        }
+    }
+}
+
+// FUCKING SHIT NO!wegrdtfhergfsg
+struct EquateRegions<'a, 'tcx> {
+    infcx: &'a BorrowckInferCtxt<'tcx>,
+    span: Span,
+    universal_regions: &'a UniversalRegions<'tcx>,
+    constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
+}
+
+impl<'tcx> TypeRelation<TyCtxt<'tcx>> for EquateRegions<'_, 'tcx> {
+    fn cx(&self) -> TyCtxt<'tcx> {
+        self.infcx.tcx
+    }
+
+    fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
+        &mut self,
+        _variance: ty::Variance,
+        _info: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
+        a: T,
+        b: T,
+    ) -> RelateResult<'tcx, T> {
+        self.relate(a, b)
+    }
+
+    fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
+        structurally_relate_tys(self, a, b)
+    }
+
+    fn regions(
+        &mut self,
+        a: ty::Region<'tcx>,
+        b: ty::Region<'tcx>,
+    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
+        if matches!(a.kind(), ty::ReBound(..)) || matches!(b.kind(), ty::ReBound(..)) {
+            assert_eq!(a, b);
+            return Ok(a);
+        }
+
+        let a_vid = to_region_vid(self.constraints, self.universal_regions, a);
+        let b_vid = to_region_vid(self.constraints, self.universal_regions, b);
+        let locations = Locations::All(self.span);
+        self.constraints.outlives_constraints.push(OutlivesConstraint {
+            sup: a_vid,
+            sub: b_vid,
+            locations,
+            span: self.span,
+            category: ConstraintCategory::OpaqueType,
+            variance_info: ty::VarianceDiagInfo::None,
+            from_closure: false,
+        });
+        self.constraints.outlives_constraints.push(OutlivesConstraint {
+            sup: b_vid,
+            sub: a_vid,
+            locations,
+            span: self.span,
+            category: ConstraintCategory::OpaqueType,
+            variance_info: ty::VarianceDiagInfo::None,
+            from_closure: false,
+        });
+
+        Ok(a)
+    }
+
+    fn consts(
+        &mut self,
+        a: ty::Const<'tcx>,
+        b: ty::Const<'tcx>,
+    ) -> RelateResult<'tcx, ty::Const<'tcx>> {
+        structurally_relate_consts(self, a, b)
+    }
+
+    fn binders<T>(
+        &mut self,
+        a: ty::Binder<'tcx, T>,
+        b: ty::Binder<'tcx, T>,
+    ) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
+    where
+        T: Relate<TyCtxt<'tcx>>,
+    {
+        self.relate(a.skip_binder(), b.skip_binder())?;
+        Ok(a)
     }
 }
 
@@ -262,19 +554,16 @@ impl<'tcx> InferCtxt<'tcx> {
         &self,
         opaque_type_key: OpaqueTypeKey<'tcx>,
         instantiated_ty: OpaqueHiddenType<'tcx>,
-    ) -> Ty<'tcx> {
-        if let Some(e) = self.tainted_by_errors() {
-            return Ty::new_error(self.tcx, e);
+    ) -> Result<Ty<'tcx>, InvalidOpaqueTypeArgs<'tcx>> {
+        if let Some(guar) = self.tainted_by_errors() {
+            return Err(guar.into());
         }
-
-        if let Err(guar) = check_opaque_type_parameter_valid(
+        check_opaque_type_parameter_valid(
             self,
             opaque_type_key,
             instantiated_ty.span,
             DefiningScopeKind::MirBorrowck,
-        ) {
-            return Ty::new_error(self.tcx, guar);
-        }
+        )?;
 
         let definition_ty = instantiated_ty
             .remap_generic_params_to_declaration_params(
@@ -284,10 +573,131 @@ impl<'tcx> InferCtxt<'tcx> {
             )
             .ty;
 
-        if let Err(e) = definition_ty.error_reported() {
-            return Ty::new_error(self.tcx, e);
+        definition_ty.error_reported()?;
+        Ok(definition_ty)
+    }
+}
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+    pub(crate) fn emit_deferred_opaque_type_errors(
+        &self,
+        root_cx: &mut BorrowCheckRootCtxt<'tcx>,
+        infcx: &BorrowckInferCtxt<'tcx>,
+        errors: Vec<DeferredOpaqueTypeError<'tcx>>,
+    ) {
+        let mut prev_hidden_region_errors = FxHashMap::default();
+        let mut guar = None;
+        for error in errors {
+            guar = Some(match error {
+                DeferredOpaqueTypeError::UnexpectedHiddenRegion {
+                    opaque_type_key,
+                    hidden_type,
+                    member_region,
+                } => self.report_unexpected_hidden_region_errors(
+                    root_cx,
+                    infcx,
+                    &mut prev_hidden_region_errors,
+                    opaque_type_key,
+                    hidden_type,
+                    member_region,
+                ),
+                DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx),
+            });
         }
+        let guar = guar.unwrap();
+        root_cx.set_tainted_by_errors(guar);
+        infcx.set_tainted_by_errors(guar);
+    }
 
-        definition_ty
+    fn report_unexpected_hidden_region_errors(
+        &self,
+        root_cx: &mut BorrowCheckRootCtxt<'tcx>,
+        infcx: &BorrowckInferCtxt<'tcx>,
+        prev_errors: &mut FxHashMap<(Span, Ty<'tcx>, OpaqueTypeKey<'tcx>), ErrorGuaranteed>,
+        opaque_type_key: OpaqueTypeKey<'tcx>,
+        hidden_type: OpaqueHiddenType<'tcx>,
+        member_region: Region<'tcx>,
+    ) -> ErrorGuaranteed {
+        let tcx = infcx.tcx;
+        let named_ty = self.name_regions_for_member_constraint(tcx, hidden_type.ty);
+        let named_key = self.name_regions_for_member_constraint(tcx, opaque_type_key);
+        let named_region = self.name_regions_for_member_constraint(tcx, member_region);
+
+        *prev_errors.entry((hidden_type.span, named_ty, named_key)).or_insert_with(|| {
+            let guar = unexpected_hidden_region_diagnostic(
+                infcx,
+                root_cx.root_def_id(),
+                hidden_type.span,
+                named_ty,
+                named_region,
+                named_key,
+            )
+            .emit();
+            root_cx
+                .add_concrete_opaque_type(named_key.def_id, OpaqueHiddenType::new_error(tcx, guar));
+            guar
+        })
+    }
+
+    /// Map the regions in the type to named regions. This is similar to what
+    /// `infer_opaque_types` does, but can infer any universal region, not only
+    /// ones from the args for the opaque type. It also doesn't double check
+    /// that the regions produced are in fact equal to the named region they are
+    /// replaced with. This is fine because this function is only to improve the
+    /// region names in error messages.
+    ///
+    /// This differs from `MirBorrowckCtxt::name_regions` since it is particularly
+    /// lax with mapping region vids that are *shorter* than a universal region to
+    /// that universal region. This is useful for member region constraints since
+    /// we want to suggest a universal region name to capture even if it's technically
+    /// not equal to the error region.
+    fn name_regions_for_member_constraint<T>(&self, tcx: TyCtxt<'tcx>, ty: T) -> T
+    where
+        T: TypeFoldable<TyCtxt<'tcx>>,
+    {
+        fold_regions(tcx, ty, |region, _| match region.kind() {
+            ty::ReVar(vid) => {
+                let scc = self.constraint_sccs.scc(vid);
+
+                // Special handling of higher-ranked regions.
+                if !self.constraint_sccs.annotation(scc).min_universe().is_root() {
+                    match self.scc_values.placeholders_contained_in(scc).enumerate().last() {
+                        // If the region contains a single placeholder then they're equal.
+                        Some((0, placeholder)) => {
+                            return ty::Region::new_placeholder(tcx, placeholder);
+                        }
+
+                        // Fallback: this will produce a cryptic error message.
+                        _ => return region,
+                    }
+                }
+
+                // Find something that we can name
+                let upper_bound = self.approx_universal_upper_bound(vid);
+                if let Some(universal_region) = self.definitions[upper_bound].external_name {
+                    return universal_region;
+                }
+
+                // Nothing exact found, so we pick a named upper bound, if there's only one.
+                // If there's >1 universal region, then we probably are dealing w/ an intersection
+                // region which cannot be mapped back to a universal.
+                // FIXME: We could probably compute the LUB if there is one.
+                let scc = self.constraint_sccs.scc(vid);
+                let rev_scc_graph = &ReverseSccGraph::compute(
+                    &self.constraint_sccs,
+                    &self.universal_region_relations.universal_regions,
+                );
+                let upper_bounds: Vec<_> = rev_scc_graph
+                    .upper_bounds(scc)
+                    .filter_map(|vid| self.definitions[vid].external_name)
+                    .filter(|r| !r.is_static())
+                    .collect();
+                match &upper_bounds[..] {
+                    [universal_region] => *universal_region,
+                    _ => region,
+                }
+            }
+            _ => region,
+        })
     }
 }
diff --git a/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs
index b2ed8a3582796..a78209d6914ed 100644
--- a/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs
+++ b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs
@@ -5,8 +5,9 @@ use rustc_data_structures::graph;
 use rustc_data_structures::graph::vec_graph::VecGraph;
 use rustc_middle::ty::RegionVid;
 
-use crate::RegionInferenceContext;
+use super::ConstraintSccs;
 use crate::constraints::ConstraintSccIndex;
+use crate::universal_regions::UniversalRegions;
 
 pub(crate) struct ReverseSccGraph {
     graph: VecGraph<ConstraintSccIndex>,
@@ -19,32 +20,14 @@ pub(crate) struct ReverseSccGraph {
 }
 
 impl ReverseSccGraph {
-    /// Find all universal regions that are required to outlive the given SCC.
-    pub(super) fn upper_bounds(&self, scc0: ConstraintSccIndex) -> impl Iterator<Item = RegionVid> {
-        let mut duplicates = FxIndexSet::default();
-        graph::depth_first_search(&self.graph, scc0)
-            .flat_map(move |scc1| {
-                self.scc_regions
-                    .get(&scc1)
-                    .map_or(&[][..], |range| &self.universal_regions[range.clone()])
-            })
-            .copied()
-            .filter(move |r| duplicates.insert(*r))
-    }
-}
-
-impl RegionInferenceContext<'_> {
-    /// Compute the reverse SCC-based constraint graph (lazily).
-    pub(super) fn compute_reverse_scc_graph(&mut self) {
-        if self.rev_scc_graph.is_some() {
-            return;
-        }
-
-        let graph = self.constraint_sccs.reverse();
-        let mut paired_scc_regions = self
-            .universal_regions()
+    pub(super) fn compute(
+        constraint_sccs: &ConstraintSccs,
+        universal_regions: &UniversalRegions<'_>,
+    ) -> Self {
+        let graph = constraint_sccs.reverse();
+        let mut paired_scc_regions = universal_regions
             .universal_regions_iter()
-            .map(|region| (self.constraint_sccs.scc(region), region))
+            .map(|region| (constraint_sccs.scc(region), region))
             .collect::<Vec<_>>();
         paired_scc_regions.sort();
         let universal_regions = paired_scc_regions.iter().map(|&(_, region)| region).collect();
@@ -56,7 +39,19 @@ impl RegionInferenceContext<'_> {
             scc_regions.insert(scc, start..start + chunk.len());
             start += chunk.len();
         }
+        ReverseSccGraph { graph, scc_regions, universal_regions }
+    }
 
-        self.rev_scc_graph = Some(ReverseSccGraph { graph, scc_regions, universal_regions });
+    /// Find all universal regions that are required to outlive the given SCC.
+    pub(super) fn upper_bounds(&self, scc0: ConstraintSccIndex) -> impl Iterator<Item = RegionVid> {
+        let mut duplicates = FxIndexSet::default();
+        graph::depth_first_search(&self.graph, scc0)
+            .flat_map(move |scc1| {
+                self.scc_regions
+                    .get(&scc1)
+                    .map_or(&[][..], |range| &self.universal_regions[range.clone()])
+            })
+            .copied()
+            .filter(move |r| duplicates.insert(*r))
     }
 }
diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs
index d9ac5b5cb132a..55b0354ce94ed 100644
--- a/compiler/rustc_borrowck/src/region_infer/values.rs
+++ b/compiler/rustc_borrowck/src/region_infer/values.rs
@@ -8,9 +8,11 @@ use rustc_index::interval::{IntervalSet, SparseIntervalMatrix};
 use rustc_middle::mir::{BasicBlock, Location};
 use rustc_middle::ty::{self, RegionVid};
 use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex};
-use tracing::debug;
+use tracing::{debug, instrument};
 
+use super::ConstraintSccs;
 use crate::BorrowIndex;
+use crate::constraints::ConstraintSccIndex;
 use crate::polonius::LiveLoans;
 
 rustc_index::newtype_index! {
@@ -424,6 +426,40 @@ impl ToElementIndex for ty::PlaceholderRegion {
     }
 }
 
+impl RegionValues<ConstraintSccIndex> {
+    /// Propagate the region constraints: this will grow the values
+    /// for each region variable until all the constraints are
+    /// satisfied. Note that some values may grow **too** large to be
+    /// feasible, but we check this later.
+    #[instrument(skip(self, constraint_sccs), level = "debug")]
+    pub(super) fn propagate_constraints(&mut self, constraint_sccs: &ConstraintSccs) {
+        // To propagate constraints, we walk the DAG induced by the
+        // SCC. For each SCC, we visit its successors and compute
+        // their values, then we union all those values to get our
+        // own.
+        for scc in constraint_sccs.all_sccs() {
+            self.compute_value_for_scc(constraint_sccs, scc);
+        }
+    }
+
+    /// Computes the value of the SCC `scc_a`, which has not yet been
+    /// computed, by unioning the values of its successors.
+    /// Assumes that all successors have been computed already
+    /// (which is assured by iterating over SCCs in dependency order).
+    #[instrument(skip(self, constraint_sccs), level = "debug")]
+    fn compute_value_for_scc(
+        &mut self,
+        constraint_sccs: &ConstraintSccs,
+        scc_a: ConstraintSccIndex,
+    ) {
+        // Walk each SCC `B` such that `A: B`...
+        for &scc_b in constraint_sccs.successors(scc_a) {
+            debug!(?scc_b);
+            self.add_region(scc_a, scc_b);
+        }
+    }
+}
+
 /// For debugging purposes, returns a pretty-printed string of the given points.
 pub(crate) fn pretty_print_points(
     location_map: &DenseLocationMap,
diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs
index 13daa5c722148..a8253cdcae6e8 100644
--- a/compiler/rustc_borrowck/src/root_cx.rs
+++ b/compiler/rustc_borrowck/src/root_cx.rs
@@ -2,7 +2,7 @@ use rustc_abi::FieldIdx;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def_id::LocalDefId;
 use rustc_middle::bug;
-use rustc_middle::ty::{OpaqueHiddenType, Ty, TyCtxt, TypeVisitableExt};
+use rustc_middle::ty::{EarlyBinder, OpaqueHiddenType, Ty, TyCtxt, TypeVisitableExt};
 use rustc_span::ErrorGuaranteed;
 use smallvec::SmallVec;
 
@@ -29,6 +29,10 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> {
         }
     }
 
+    pub(super) fn root_def_id(&self) -> LocalDefId {
+        self.root_def_id
+    }
+
     /// Collect all defining uses of opaque types inside of this typeck root. This
     /// expects the hidden type to be mapped to the definition parameters of the opaque
     /// and errors if we end up with distinct hidden types.
@@ -58,6 +62,13 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> {
         }
     }
 
+    pub(super) fn get_concrete_opaque_type(
+        &mut self,
+        def_id: LocalDefId,
+    ) -> Option<EarlyBinder<'tcx, OpaqueHiddenType<'tcx>>> {
+        self.concrete_opaque_types.0.get(&def_id).map(|ty| EarlyBinder::bind(*ty))
+    }
+
     pub(super) fn set_tainted_by_errors(&mut self, guar: ErrorGuaranteed) {
         self.tainted_by_errors = Some(guar);
     }
diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs
index 5143b2fa20598..652822fa7c892 100644
--- a/compiler/rustc_borrowck/src/session_diagnostics.rs
+++ b/compiler/rustc_borrowck/src/session_diagnostics.rs
@@ -1,7 +1,7 @@
 use rustc_errors::MultiSpan;
 use rustc_errors::codes::*;
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
-use rustc_middle::ty::{GenericArg, Ty};
+use rustc_middle::ty::Ty;
 use rustc_span::Span;
 
 use crate::diagnostics::RegionName;
@@ -294,19 +294,6 @@ pub(crate) struct MoveBorrow<'a> {
     pub borrow_span: Span,
 }
 
-#[derive(Diagnostic)]
-#[diag(borrowck_opaque_type_lifetime_mismatch)]
-pub(crate) struct LifetimeMismatchOpaqueParam<'tcx> {
-    pub arg: GenericArg<'tcx>,
-    pub prev: GenericArg<'tcx>,
-    #[primary_span]
-    #[label]
-    #[note]
-    pub span: Span,
-    #[label(borrowck_prev_lifetime_label)]
-    pub prev_span: Span,
-}
-
 #[derive(Subdiagnostic)]
 pub(crate) enum CaptureReasonLabel<'a> {
     #[label(borrowck_moved_due_to_call)]
diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
index 536a27763d29c..92fc3b7590b4e 100644
--- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
+++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs
@@ -156,13 +156,6 @@ impl UniversalRegionRelations<'_> {
         self.outlives.contains(fr1, fr2)
     }
 
-    /// Returns `true` if fr1 is known to equal fr2.
-    ///
-    /// This will only ever be true for universally quantified regions.
-    pub(crate) fn equal(&self, fr1: RegionVid, fr2: RegionVid) -> bool {
-        self.outlives.contains(fr1, fr2) && self.outlives.contains(fr2, fr1)
-    }
-
     /// Returns a vector of free regions `x` such that `fr1: x` is
     /// known to hold.
     pub(crate) fn regions_outlived_by(&self, fr1: RegionVid) -> Vec<RegionVid> {
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 05e0bb3f9f343..7e67afe536cbe 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -26,8 +26,8 @@ use rustc_middle::ty::adjustment::PointerCoercion;
 use rustc_middle::ty::cast::CastTy;
 use rustc_middle::ty::{
     self, Binder, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, CoroutineArgsExt,
-    Dynamic, GenericArgsRef, OpaqueHiddenType, OpaqueTypeKey, RegionVid, Ty, TyCtxt,
-    TypeVisitableExt, UserArgs, UserTypeAnnotationIndex, fold_regions,
+    Dynamic, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, UserArgs, UserTypeAnnotationIndex,
+    fold_regions,
 };
 use rustc_middle::{bug, span_bug};
 use rustc_mir_dataflow::ResultsCursor;
@@ -44,7 +44,6 @@ use tracing::{debug, instrument, trace};
 use crate::borrow_set::BorrowSet;
 use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet};
 use crate::diagnostics::UniverseInfo;
-use crate::member_constraints::MemberConstraintSet;
 use crate::polonius::legacy::{PoloniusFacts, PoloniusLocationTable};
 use crate::polonius::{PoloniusContext, PoloniusLivenessContext};
 use crate::region_infer::TypeTest;
@@ -74,7 +73,6 @@ mod constraint_conversion;
 pub(crate) mod free_region_relations;
 mod input_output;
 pub(crate) mod liveness;
-mod opaque_types;
 mod relate_tys;
 
 /// Type checks the given `mir` in the context of the inference
@@ -118,7 +116,6 @@ pub(crate) fn type_check<'a, 'tcx>(
         placeholder_index_to_region: IndexVec::default(),
         liveness_constraints: LivenessValues::with_specific_points(Rc::clone(&location_map)),
         outlives_constraints: OutlivesConstraintSet::default(),
-        member_constraints: MemberConstraintSet::default(),
         type_tests: Vec::default(),
         universe_causes: FxIndexMap::default(),
     };
@@ -169,10 +166,10 @@ pub(crate) fn type_check<'a, 'tcx>(
 
     liveness::generate(&mut typeck, &location_map, flow_inits, move_data);
 
-    let opaque_type_values =
-        opaque_types::take_opaques_and_register_member_constraints(&mut typeck);
-
     // We're done with typeck, we can finalize the polonius liveness context for region inference.
+    //
+    // FIXME: Handling opaque type uses may introduce new regions. This likely has to be moved to
+    // a later point.
     let polonius_context = typeck.polonius_liveness.take().map(|liveness_context| {
         PoloniusContext::create_from_liveness(
             liveness_context,
@@ -181,12 +178,7 @@ pub(crate) fn type_check<'a, 'tcx>(
         )
     });
 
-    MirTypeckResults {
-        constraints,
-        universal_region_relations,
-        opaque_type_values,
-        polonius_context,
-    }
+    MirTypeckResults { constraints, universal_region_relations, polonius_context }
 }
 
 #[track_caller]
@@ -233,7 +225,6 @@ struct TypeChecker<'a, 'tcx> {
 pub(crate) struct MirTypeckResults<'tcx> {
     pub(crate) constraints: MirTypeckRegionConstraints<'tcx>,
     pub(crate) universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
-    pub(crate) opaque_type_values: FxIndexMap<OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>>,
     pub(crate) polonius_context: Option<PoloniusContext>,
 }
 
@@ -265,8 +256,6 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> {
 
     pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>,
 
-    pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
-
     pub(crate) universe_causes: FxIndexMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
 
     pub(crate) type_tests: Vec<TypeTest<'tcx>>,
@@ -275,7 +264,7 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> {
 impl<'tcx> MirTypeckRegionConstraints<'tcx> {
     /// Creates a `Region` for a given `PlaceholderRegion`, or returns the
     /// region that corresponds to a previously created one.
-    fn placeholder_region(
+    pub(crate) fn placeholder_region(
         &mut self,
         infcx: &InferCtxt<'tcx>,
         placeholder: ty::PlaceholderRegion,
@@ -291,6 +280,16 @@ impl<'tcx> MirTypeckRegionConstraints<'tcx> {
             }
         }
     }
+
+    /// Same as `Self::placeholder_region`, except that never adds new regions but instead ICEs in case we
+    /// haven't already created an NLL var for this placeholders.
+    pub(crate) fn get_placeholder_region(
+        &self,
+        placeholder: ty::PlaceholderRegion,
+    ) -> ty::Region<'tcx> {
+        let placeholder_index = self.placeholder_indices.lookup_index(placeholder);
+        *self.placeholder_index_to_region.get(placeholder_index).unwrap()
+    }
 }
 
 /// The `Locations` type summarizes *where* region constraints are
@@ -368,14 +367,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
         self.body
     }
 
-    fn to_region_vid(&mut self, r: ty::Region<'tcx>) -> RegionVid {
-        if let ty::RePlaceholder(placeholder) = r.kind() {
-            self.constraints.placeholder_region(self.infcx, placeholder).as_var()
-        } else {
-            self.universal_regions.to_region_vid(r)
-        }
-    }
-
     fn unsized_feature_enabled(&self) -> bool {
         let features = self.tcx().features();
         features.unsized_locals() || features.unsized_fn_params()
diff --git a/compiler/rustc_borrowck/src/type_check/opaque_types.rs b/compiler/rustc_borrowck/src/type_check/opaque_types.rs
deleted file mode 100644
index d41cbf757d7f8..0000000000000
--- a/compiler/rustc_borrowck/src/type_check/opaque_types.rs
+++ /dev/null
@@ -1,337 +0,0 @@
-use std::iter;
-
-use rustc_data_structures::fx::FxIndexMap;
-use rustc_middle::span_bug;
-use rustc_middle::ty::{
-    self, GenericArgKind, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeSuperVisitable,
-    TypeVisitable, TypeVisitableExt, TypeVisitor, fold_regions,
-};
-use tracing::{debug, trace};
-
-use super::{MemberConstraintSet, TypeChecker};
-
-/// Once we're done with typechecking the body, we take all the opaque types
-/// defined by this function and add their 'member constraints'.
-pub(super) fn take_opaques_and_register_member_constraints<'tcx>(
-    typeck: &mut TypeChecker<'_, 'tcx>,
-) -> FxIndexMap<OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>> {
-    let infcx = typeck.infcx;
-    // Annoying: to invoke `typeck.to_region_vid`, we need access to
-    // `typeck.constraints`, but we also want to be mutating
-    // `typeck.member_constraints`. For now, just swap out the value
-    // we want and replace at the end.
-    let mut member_constraints = std::mem::take(&mut typeck.constraints.member_constraints);
-    let opaque_types = infcx
-        .take_opaque_types()
-        .into_iter()
-        .map(|(opaque_type_key, hidden_type)| {
-            let hidden_type = infcx.resolve_vars_if_possible(hidden_type);
-            register_member_constraints(
-                typeck,
-                &mut member_constraints,
-                opaque_type_key,
-                hidden_type,
-            );
-            trace!("finalized opaque type {:?} to {:#?}", opaque_type_key, hidden_type.ty.kind());
-            if hidden_type.has_non_region_infer() {
-                span_bug!(hidden_type.span, "could not resolve {:?}", hidden_type.ty);
-            }
-
-            // Convert all regions to nll vars.
-            let (opaque_type_key, hidden_type) =
-                fold_regions(infcx.tcx, (opaque_type_key, hidden_type), |r, _| {
-                    ty::Region::new_var(infcx.tcx, typeck.to_region_vid(r))
-                });
-
-            (opaque_type_key, hidden_type)
-        })
-        .collect();
-    assert!(typeck.constraints.member_constraints.is_empty());
-    typeck.constraints.member_constraints = member_constraints;
-    opaque_types
-}
-
-/// Given the map `opaque_types` containing the opaque
-/// `impl Trait` types whose underlying, hidden types are being
-/// inferred, this method adds constraints to the regions
-/// appearing in those underlying hidden types to ensure that they
-/// at least do not refer to random scopes within the current
-/// function. These constraints are not (quite) sufficient to
-/// guarantee that the regions are actually legal values; that
-/// final condition is imposed after region inference is done.
-///
-/// # The Problem
-///
-/// Let's work through an example to explain how it works. Assume
-/// the current function is as follows:
-///
-/// ```text
-/// fn foo<'a, 'b>(..) -> (impl Bar<'a>, impl Bar<'b>)
-/// ```
-///
-/// Here, we have two `impl Trait` types whose values are being
-/// inferred (the `impl Bar<'a>` and the `impl
-/// Bar<'b>`). Conceptually, this is sugar for a setup where we
-/// define underlying opaque types (`Foo1`, `Foo2`) and then, in
-/// the return type of `foo`, we *reference* those definitions:
-///
-/// ```text
-/// type Foo1<'x> = impl Bar<'x>;
-/// type Foo2<'x> = impl Bar<'x>;
-/// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. }
-///                    //  ^^^^ ^^
-///                    //  |    |
-///                    //  |    args
-///                    //  def_id
-/// ```
-///
-/// As indicating in the comments above, each of those references
-/// is (in the compiler) basically generic parameters (`args`)
-/// applied to the type of a suitable `def_id` (which identifies
-/// `Foo1` or `Foo2`).
-///
-/// Now, at this point in compilation, what we have done is to
-/// replace each of the references (`Foo1<'a>`, `Foo2<'b>`) with
-/// fresh inference variables C1 and C2. We wish to use the values
-/// of these variables to infer the underlying types of `Foo1` and
-/// `Foo2`. That is, this gives rise to higher-order (pattern) unification
-/// constraints like:
-///
-/// ```text
-/// for<'a> (Foo1<'a> = C1)
-/// for<'b> (Foo1<'b> = C2)
-/// ```
-///
-/// For these equation to be satisfiable, the types `C1` and `C2`
-/// can only refer to a limited set of regions. For example, `C1`
-/// can only refer to `'static` and `'a`, and `C2` can only refer
-/// to `'static` and `'b`. The job of this function is to impose that
-/// constraint.
-///
-/// Up to this point, C1 and C2 are basically just random type
-/// inference variables, and hence they may contain arbitrary
-/// regions. In fact, it is fairly likely that they do! Consider
-/// this possible definition of `foo`:
-///
-/// ```text
-/// fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> (impl Bar<'a>, impl Bar<'b>) {
-///         (&*x, &*y)
-///     }
-/// ```
-///
-/// Here, the values for the concrete types of the two impl
-/// traits will include inference variables:
-///
-/// ```text
-/// &'0 i32
-/// &'1 i32
-/// ```
-///
-/// Ordinarily, the subtyping rules would ensure that these are
-/// sufficiently large. But since `impl Bar<'a>` isn't a specific
-/// type per se, we don't get such constraints by default. This
-/// is where this function comes into play. It adds extra
-/// constraints to ensure that all the regions which appear in the
-/// inferred type are regions that could validly appear.
-///
-/// This is actually a bit of a tricky constraint in general. We
-/// want to say that each variable (e.g., `'0`) can only take on
-/// values that were supplied as arguments to the opaque type
-/// (e.g., `'a` for `Foo1<'a>`) or `'static`, which is always in
-/// scope. We don't have a constraint quite of this kind in the current
-/// region checker.
-///
-/// # The Solution
-///
-/// We generally prefer to make `<=` constraints, since they
-/// integrate best into the region solver. To do that, we find the
-/// "minimum" of all the arguments that appear in the args: that
-/// is, some region which is less than all the others. In the case
-/// of `Foo1<'a>`, that would be `'a` (it's the only choice, after
-/// all). Then we apply that as a least bound to the variables
-/// (e.g., `'a <= '0`).
-///
-/// In some cases, there is no minimum. Consider this example:
-///
-/// ```text
-/// fn baz<'a, 'b>() -> impl Trait<'a, 'b> { ... }
-/// ```
-///
-/// Here we would report a more complex "in constraint", like `'r
-/// in ['a, 'b, 'static]` (where `'r` is some region appearing in
-/// the hidden type).
-///
-/// # Constrain regions, not the hidden concrete type
-///
-/// Note that generating constraints on each region `Rc` is *not*
-/// the same as generating an outlives constraint on `Tc` itself.
-/// For example, if we had a function like this:
-///
-/// ```
-/// # #![feature(type_alias_impl_trait)]
-/// # fn main() {}
-/// # trait Foo<'a> {}
-/// # impl<'a, T> Foo<'a> for (&'a u32, T) {}
-/// fn foo<'a, T>(x: &'a u32, y: T) -> impl Foo<'a> {
-///   (x, y)
-/// }
-///
-/// // Equivalent to:
-/// # mod dummy { use super::*;
-/// type FooReturn<'a, T> = impl Foo<'a>;
-/// #[define_opaque(FooReturn)]
-/// fn foo<'a, T>(x: &'a u32, y: T) -> FooReturn<'a, T> {
-///   (x, y)
-/// }
-/// # }
-/// ```
-///
-/// then the hidden type `Tc` would be `(&'0 u32, T)` (where `'0`
-/// is an inference variable). If we generated a constraint that
-/// `Tc: 'a`, then this would incorrectly require that `T: 'a` --
-/// but this is not necessary, because the opaque type we
-/// create will be allowed to reference `T`. So we only generate a
-/// constraint that `'0: 'a`.
-fn register_member_constraints<'tcx>(
-    typeck: &mut TypeChecker<'_, 'tcx>,
-    member_constraints: &mut MemberConstraintSet<'tcx, ty::RegionVid>,
-    opaque_type_key: OpaqueTypeKey<'tcx>,
-    OpaqueHiddenType { span, ty: hidden_ty }: OpaqueHiddenType<'tcx>,
-) {
-    let tcx = typeck.tcx();
-    let hidden_ty = typeck.infcx.resolve_vars_if_possible(hidden_ty);
-    debug!(?hidden_ty);
-
-    let variances = tcx.variances_of(opaque_type_key.def_id);
-    debug!(?variances);
-
-    // For a case like `impl Foo<'a, 'b>`, we would generate a constraint
-    // `'r in ['a, 'b, 'static]` for each region `'r` that appears in the
-    // hidden type (i.e., it must be equal to `'a`, `'b`, or `'static`).
-    //
-    // `conflict1` and `conflict2` are the two region bounds that we
-    // detected which were unrelated. They are used for diagnostics.
-
-    // Create the set of choice regions: each region in the hidden
-    // type can be equal to any of the region parameters of the
-    // opaque type definition.
-    let fr_static = typeck.universal_regions.fr_static;
-    let choice_regions: Vec<_> = opaque_type_key
-        .args
-        .iter()
-        .enumerate()
-        .filter(|(i, _)| variances[*i] == ty::Invariant)
-        .filter_map(|(_, arg)| match arg.unpack() {
-            GenericArgKind::Lifetime(r) => Some(typeck.to_region_vid(r)),
-            GenericArgKind::Type(_) | GenericArgKind::Const(_) => None,
-        })
-        .chain(iter::once(fr_static))
-        .collect();
-
-    // FIXME(#42940): This should use the `FreeRegionsVisitor`, but that's
-    // not currently sound until we have existential regions.
-    hidden_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor {
-        tcx,
-        op: |r| {
-            member_constraints.add_member_constraint(
-                opaque_type_key,
-                hidden_ty,
-                span,
-                typeck.to_region_vid(r),
-                &choice_regions,
-            )
-        },
-    });
-}
-
-/// Visitor that requires that (almost) all regions in the type visited outlive
-/// `least_region`. We cannot use `push_outlives_components` because regions in
-/// closure signatures are not included in their outlives components. We need to
-/// ensure all regions outlive the given bound so that we don't end up with,
-/// say, `ReVar` appearing in a return type and causing ICEs when other
-/// functions end up with region constraints involving regions from other
-/// functions.
-///
-/// We also cannot use `for_each_free_region` because for closures it includes
-/// the regions parameters from the enclosing item.
-///
-/// We ignore any type parameters because impl trait values are assumed to
-/// capture all the in-scope type parameters.
-struct ConstrainOpaqueTypeRegionVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> {
-    tcx: TyCtxt<'tcx>,
-    op: OP,
-}
-
-impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for ConstrainOpaqueTypeRegionVisitor<'tcx, OP>
-where
-    OP: FnMut(ty::Region<'tcx>),
-{
-    fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
-        t.super_visit_with(self);
-    }
-
-    fn visit_region(&mut self, r: ty::Region<'tcx>) {
-        match r.kind() {
-            // ignore bound regions, keep visiting
-            ty::ReBound(_, _) => {}
-            _ => (self.op)(r),
-        }
-    }
-
-    fn visit_ty(&mut self, ty: Ty<'tcx>) {
-        // We're only interested in types involving regions
-        if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) {
-            return;
-        }
-
-        match *ty.kind() {
-            ty::Closure(_, args) => {
-                // Skip lifetime parameters of the enclosing item(s)
-
-                for upvar in args.as_closure().upvar_tys() {
-                    upvar.visit_with(self);
-                }
-                args.as_closure().sig_as_fn_ptr_ty().visit_with(self);
-            }
-
-            ty::CoroutineClosure(_, args) => {
-                // Skip lifetime parameters of the enclosing item(s)
-
-                for upvar in args.as_coroutine_closure().upvar_tys() {
-                    upvar.visit_with(self);
-                }
-
-                args.as_coroutine_closure().signature_parts_ty().visit_with(self);
-            }
-
-            ty::Coroutine(_, args) => {
-                // Skip lifetime parameters of the enclosing item(s)
-                // Also skip the witness type, because that has no free regions.
-
-                for upvar in args.as_coroutine().upvar_tys() {
-                    upvar.visit_with(self);
-                }
-                args.as_coroutine().return_ty().visit_with(self);
-                args.as_coroutine().yield_ty().visit_with(self);
-                args.as_coroutine().resume_ty().visit_with(self);
-            }
-
-            ty::Alias(kind, ty::AliasTy { def_id, args, .. })
-                if let Some(variances) = self.tcx.opt_alias_variances(kind, def_id) =>
-            {
-                // Skip lifetime parameters that are not captured, since they do
-                // not need member constraints registered for them; we'll erase
-                // them (and hopefully in the future replace them with placeholders).
-                for (v, s) in std::iter::zip(variances, args.iter()) {
-                    if *v != ty::Bivariant {
-                        s.visit_with(self);
-                    }
-                }
-            }
-
-            _ => {
-                ty.super_visit_with(self);
-            }
-        }
-    }
-}
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index 6ba7435cb790d..86729de32bf5a 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -580,15 +580,16 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
                 }
             }
 
-            if let Err(guar) = check_opaque_type_parameter_valid(
+            if let Err(err) = check_opaque_type_parameter_valid(
                 &self.fcx,
                 opaque_type_key,
                 hidden_type.span,
                 DefiningScopeKind::HirTypeck,
             ) {
-                self.typeck_results
-                    .concrete_opaque_types
-                    .insert(opaque_type_key.def_id, ty::OpaqueHiddenType::new_error(tcx, guar));
+                self.typeck_results.concrete_opaque_types.insert(
+                    opaque_type_key.def_id,
+                    ty::OpaqueHiddenType::new_error(tcx, err.report(self.fcx)),
+                );
             }
 
             let hidden_type = hidden_type.remap_generic_params_to_declaration_params(
diff --git a/compiler/rustc_trait_selection/src/opaque_types.rs b/compiler/rustc_trait_selection/src/opaque_types.rs
index cce67b066dde1..332204a0c5f06 100644
--- a/compiler/rustc_trait_selection/src/opaque_types.rs
+++ b/compiler/rustc_trait_selection/src/opaque_types.rs
@@ -13,6 +13,49 @@ use crate::errors::NonGenericOpaqueTypeParam;
 use crate::regions::OutlivesEnvironmentBuildExt;
 use crate::traits::ObligationCtxt;
 
+pub enum InvalidOpaqueTypeArgs<'tcx> {
+    AlreadyReported(ErrorGuaranteed),
+    NotAParam { opaque_type_key: OpaqueTypeKey<'tcx>, param_index: usize, span: Span },
+    DuplicateParam { opaque_type_key: OpaqueTypeKey<'tcx>, param_indices: Vec<usize>, span: Span },
+}
+impl From<ErrorGuaranteed> for InvalidOpaqueTypeArgs<'_> {
+    fn from(guar: ErrorGuaranteed) -> Self {
+        InvalidOpaqueTypeArgs::AlreadyReported(guar)
+    }
+}
+impl<'tcx> InvalidOpaqueTypeArgs<'tcx> {
+    pub fn report(self, infcx: &InferCtxt<'tcx>) -> ErrorGuaranteed {
+        let tcx = infcx.tcx;
+        match self {
+            InvalidOpaqueTypeArgs::AlreadyReported(guar) => guar,
+            InvalidOpaqueTypeArgs::NotAParam { opaque_type_key, param_index, span } => {
+                let opaque_generics = tcx.generics_of(opaque_type_key.def_id);
+                let opaque_param = opaque_generics.param_at(param_index, tcx);
+                let kind = opaque_param.kind.descr();
+                infcx.dcx().emit_err(NonGenericOpaqueTypeParam {
+                    arg: opaque_type_key.args[param_index],
+                    kind,
+                    span,
+                    param_span: tcx.def_span(opaque_param.def_id),
+                })
+            }
+            InvalidOpaqueTypeArgs::DuplicateParam { opaque_type_key, param_indices, span } => {
+                let opaque_generics = tcx.generics_of(opaque_type_key.def_id);
+                let descr = opaque_generics.param_at(param_indices[0], tcx).kind.descr();
+                let spans: Vec<_> = param_indices
+                    .into_iter()
+                    .map(|i| tcx.def_span(opaque_generics.param_at(i, tcx).def_id))
+                    .collect();
+                infcx
+                    .dcx()
+                    .struct_span_err(span, "non-defining opaque type use in defining scope")
+                    .with_span_note(spans, format!("{descr} used multiple times"))
+                    .emit()
+            }
+        }
+    }
+}
+
 /// Opaque type parameter validity check as documented in the [rustc-dev-guide chapter].
 ///
 /// [rustc-dev-guide chapter]:
@@ -22,23 +65,19 @@ pub fn check_opaque_type_parameter_valid<'tcx>(
     opaque_type_key: OpaqueTypeKey<'tcx>,
     span: Span,
     defining_scope_kind: DefiningScopeKind,
-) -> Result<(), ErrorGuaranteed> {
+) -> Result<(), InvalidOpaqueTypeArgs<'tcx>> {
     let tcx = infcx.tcx;
-    let opaque_generics = tcx.generics_of(opaque_type_key.def_id);
     let opaque_env = LazyOpaqueTyEnv::new(tcx, opaque_type_key.def_id);
     let mut seen_params: FxIndexMap<_, Vec<_>> = FxIndexMap::default();
 
     // Avoid duplicate errors in case the opaque has already been malformed in
     // HIR typeck.
     if let DefiningScopeKind::MirBorrowck = defining_scope_kind {
-        if let Err(guar) = infcx
+        infcx
             .tcx
             .type_of_opaque_hir_typeck(opaque_type_key.def_id)
             .instantiate_identity()
-            .error_reported()
-        {
-            return Err(guar);
-        }
+            .error_reported()?;
     }
 
     for (i, arg) in opaque_type_key.iter_captured_args(tcx) {
@@ -64,32 +103,18 @@ pub fn check_opaque_type_parameter_valid<'tcx>(
             }
         } else {
             // Prevent `fn foo() -> Foo<u32>` from being defining.
-            let opaque_param = opaque_generics.param_at(i, tcx);
-            let kind = opaque_param.kind.descr();
-
             opaque_env.param_is_error(i)?;
-
-            return Err(infcx.dcx().emit_err(NonGenericOpaqueTypeParam {
-                arg,
-                kind,
-                span,
-                param_span: tcx.def_span(opaque_param.def_id),
-            }));
+            return Err(InvalidOpaqueTypeArgs::NotAParam { opaque_type_key, param_index: i, span });
         }
     }
 
-    for (_, indices) in seen_params {
-        if indices.len() > 1 {
-            let descr = opaque_generics.param_at(indices[0], tcx).kind.descr();
-            let spans: Vec<_> = indices
-                .into_iter()
-                .map(|i| tcx.def_span(opaque_generics.param_at(i, tcx).def_id))
-                .collect();
-            return Err(infcx
-                .dcx()
-                .struct_span_err(span, "non-defining opaque type use in defining scope")
-                .with_span_note(spans, format!("{descr} used multiple times"))
-                .emit());
+    for (_, param_indices) in seen_params {
+        if param_indices.len() > 1 {
+            return Err(InvalidOpaqueTypeArgs::DuplicateParam {
+                opaque_type_key,
+                param_indices,
+                span,
+            });
         }
     }