diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs
index e7b8e6e69b0c6..394a263fbb595 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs
@@ -13,6 +13,7 @@ use rustc_middle::ty::{
 use rustc_span::{ErrorGuaranteed, Span};
 use rustc_trait_selection::error_reporting::traits::report_dyn_incompatibility;
 use rustc_trait_selection::traits::{self, hir_ty_lowering_dyn_compatibility_violations};
+use rustc_type_ir::elaborate::ClauseWithSupertraitSpan;
 use smallvec::{SmallVec, smallvec};
 use tracing::{debug, instrument};
 
@@ -124,16 +125,19 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             .into_iter()
             .filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()));
 
-        for (base_trait_ref, span) in regular_traits_refs_spans {
+        for (base_trait_ref, original_span) in regular_traits_refs_spans {
             let base_pred: ty::Predicate<'tcx> = base_trait_ref.upcast(tcx);
-            for pred in traits::elaborate(tcx, [base_pred]).filter_only_self() {
+            for ClauseWithSupertraitSpan { pred, original_span, supertrait_span } in
+                traits::elaborate(tcx, [ClauseWithSupertraitSpan::new(base_pred, original_span)])
+                    .filter_only_self()
+            {
                 debug!("observing object predicate `{pred:?}`");
 
                 let bound_predicate = pred.kind();
                 match bound_predicate.skip_binder() {
                     ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => {
                         let pred = bound_predicate.rebind(pred);
-                        associated_types.entry(span).or_default().extend(
+                        associated_types.entry(original_span).or_default().extend(
                             tcx.associated_items(pred.def_id())
                                 .in_definition_order()
                                 .filter(|item| item.kind == ty::AssocKind::Type)
@@ -172,8 +176,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         // the discussion in #56288 for alternatives.
                         if !references_self {
                             // Include projections defined on supertraits.
-                            projection_bounds.push((pred, span));
+                            projection_bounds.push((pred, original_span));
                         }
+
+                        self.check_elaborated_projection_mentions_input_lifetimes(
+                            pred,
+                            original_span,
+                            supertrait_span,
+                        );
                     }
                     _ => (),
                 }
@@ -360,6 +370,56 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
 
         Ty::new_dynamic(tcx, existential_predicates, region_bound, representation)
     }
+
+    /// Check that elaborating the principal of a trait ref doesn't lead to projections
+    /// that are unconstrained. This can happen because an otherwise unconstrained
+    /// *type variable* can be substituted with a type that has late-bound regions. See
+    /// `elaborated-predicates-unconstrained-late-bound.rs` for a test.
+    fn check_elaborated_projection_mentions_input_lifetimes(
+        &self,
+        pred: ty::PolyProjectionPredicate<'tcx>,
+        span: Span,
+        supertrait_span: Span,
+    ) {
+        let tcx = self.tcx();
+
+        // Find any late-bound regions declared in `ty` that are not
+        // declared in the trait-ref or assoc_item. These are not well-formed.
+        //
+        // Example:
+        //
+        //     for<'a> <T as Iterator>::Item = &'a str // <-- 'a is bad
+        //     for<'a> <T as FnMut<(&'a u32,)>>::Output = &'a str // <-- 'a is ok
+        let late_bound_in_projection_term =
+            tcx.collect_constrained_late_bound_regions(pred.map_bound(|pred| pred.projection_term));
+        let late_bound_in_term =
+            tcx.collect_referenced_late_bound_regions(pred.map_bound(|pred| pred.term));
+        debug!(?late_bound_in_projection_term);
+        debug!(?late_bound_in_term);
+
+        // FIXME: point at the type params that don't have appropriate lifetimes:
+        // struct S1<F: for<'a> Fn(&i32, &i32) -> &'a i32>(F);
+        //                         ----  ----     ^^^^^^^
+        // NOTE(associated_const_equality): This error should be impossible to trigger
+        //                                  with associated const equality constraints.
+        self.validate_late_bound_regions(
+            late_bound_in_projection_term,
+            late_bound_in_term,
+            |br_name| {
+                let item_name = tcx.item_name(pred.projection_def_id());
+                struct_span_code_err!(
+                    self.dcx(),
+                    span,
+                    E0582,
+                    "binding for associated type `{}` references {}, \
+                             which does not appear in the trait input types",
+                    item_name,
+                    br_name
+                )
+                .with_span_label(supertrait_span, "due to this supertrait")
+            },
+        );
+    }
 }
 
 fn replace_dummy_self_with_error<'tcx, T: TypeFoldable<TyCtxt<'tcx>>>(
diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs
index fd4e8f1cd4e71..d20cb368278b3 100644
--- a/compiler/rustc_middle/src/ty/predicate.rs
+++ b/compiler/rustc_middle/src/ty/predicate.rs
@@ -179,6 +179,10 @@ pub struct Clause<'tcx>(
 );
 
 impl<'tcx> rustc_type_ir::inherent::Clause<TyCtxt<'tcx>> for Clause<'tcx> {
+    fn as_predicate(self) -> Predicate<'tcx> {
+        self.as_predicate()
+    }
+
     fn instantiate_supertrait(self, tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>) -> Self {
         self.instantiate_supertrait(tcx, trait_ref)
     }
diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs
index 61736633cfa28..dac45ff2aba3f 100644
--- a/compiler/rustc_type_ir/src/elaborate.rs
+++ b/compiler/rustc_type_ir/src/elaborate.rs
@@ -44,6 +44,46 @@ pub trait Elaboratable<I: Interner> {
     ) -> Self;
 }
 
+pub struct ClauseWithSupertraitSpan<I: Interner> {
+    pub pred: I::Predicate,
+    // Span of the original elaborated predicate.
+    pub original_span: I::Span,
+    // Span of the supertrait predicatae that lead to this clause.
+    pub supertrait_span: I::Span,
+}
+impl<I: Interner> ClauseWithSupertraitSpan<I> {
+    pub fn new(pred: I::Predicate, span: I::Span) -> Self {
+        ClauseWithSupertraitSpan { pred, original_span: span, supertrait_span: span }
+    }
+}
+impl<I: Interner> Elaboratable<I> for ClauseWithSupertraitSpan<I> {
+    fn predicate(&self) -> <I as Interner>::Predicate {
+        self.pred
+    }
+
+    fn child(&self, clause: <I as Interner>::Clause) -> Self {
+        ClauseWithSupertraitSpan {
+            pred: clause.as_predicate(),
+            original_span: self.original_span,
+            supertrait_span: self.supertrait_span,
+        }
+    }
+
+    fn child_with_derived_cause(
+        &self,
+        clause: <I as Interner>::Clause,
+        supertrait_span: <I as Interner>::Span,
+        _parent_trait_pred: crate::Binder<I, crate::TraitPredicate<I>>,
+        _index: usize,
+    ) -> Self {
+        ClauseWithSupertraitSpan {
+            pred: clause.as_predicate(),
+            original_span: self.original_span,
+            supertrait_span: supertrait_span,
+        }
+    }
+}
+
 pub fn elaborate<I: Interner, O: Elaboratable<I>>(
     cx: I,
     obligations: impl IntoIterator<Item = O>,
diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs
index 59a83ea5412d5..69665df4bfc26 100644
--- a/compiler/rustc_type_ir/src/inherent.rs
+++ b/compiler/rustc_type_ir/src/inherent.rs
@@ -460,6 +460,8 @@ pub trait Clause<I: Interner<Clause = Self>>:
     + IntoKind<Kind = ty::Binder<I, ty::ClauseKind<I>>>
     + Elaboratable<I>
 {
+    fn as_predicate(self) -> I::Predicate;
+
     fn as_trait_clause(self) -> Option<ty::Binder<I, ty::TraitPredicate<I>>> {
         self.kind()
             .map_bound(|clause| if let ty::ClauseKind::Trait(t) = clause { Some(t) } else { None })
diff --git a/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs
new file mode 100644
index 0000000000000..b174776c596ee
--- /dev/null
+++ b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.rs
@@ -0,0 +1,24 @@
+// Make sure that when elaborating the principal of a dyn trait for projection predicates
+//  we don't end up in a situation where we have an unconstrained late-bound lifetime in
+// the output of a projection.
+
+// Fix for <https://github.com/rust-lang/rust/issues/130347>.
+
+trait A<T>: B<T = T> {}
+
+trait B {
+    type T;
+}
+
+struct Erase<T: ?Sized + B>(T::T);
+
+fn main() {
+    let x = {
+        let x = String::from("hello");
+
+        Erase::<dyn for<'a> A<&'a _>>(x.as_str())
+        //~^ ERROR binding for associated type `T` references lifetime `'a`, which does not appear in the trait input types
+    };
+
+    dbg!(x.0);
+}
diff --git a/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr
new file mode 100644
index 0000000000000..067eaf5e7f30f
--- /dev/null
+++ b/tests/ui/traits/object/elaborated-predicates-unconstrained-late-bound.stderr
@@ -0,0 +1,12 @@
+error[E0582]: binding for associated type `T` references lifetime `'a`, which does not appear in the trait input types
+  --> $DIR/elaborated-predicates-unconstrained-late-bound.rs:19:21
+   |
+LL | trait A<T>: B<T = T> {}
+   |               ----- due to this supertrait
+...
+LL |         Erase::<dyn for<'a> A<&'a _>>(x.as_str())
+   |                     ^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0582`.
diff --git a/tests/ui/traits/object/pretty.rs b/tests/ui/traits/object/pretty.rs
index 6660ff040f753..603d7af526002 100644
--- a/tests/ui/traits/object/pretty.rs
+++ b/tests/ui/traits/object/pretty.rs
@@ -13,7 +13,7 @@ trait SuperGeneric<'a> {
 }
 trait AnyGeneric<'a>: SuperGeneric<'a> {}
 trait FixedGeneric1<'a>: SuperGeneric<'a, Assoc2 = &'a u8> {}
-trait FixedGeneric2<'a>: Super<Assoc = &'a u8> {}
+// trait FixedGeneric2<'a>: Super<Assoc = &'a u8> {} // Unsound!
 trait FixedHrtb: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> {}
 trait AnyDifferentBinders: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> + Super {}
 trait FixedDifferentBinders: for<'a> SuperGeneric<'a, Assoc2 = &'a u8> + Super<Assoc = u8> {}
@@ -32,7 +32,7 @@ fn dyn_fixed_static(x: &dyn FixedStatic) { x } //~ERROR mismatched types
 fn dyn_super_generic(x: &dyn for<'a> SuperGeneric<'a, Assoc2 = &'a u8>) { x } //~ERROR mismatched types
 fn dyn_any_generic(x: &dyn for<'a> AnyGeneric<'a, Assoc2 = &'a u8>) { x } //~ERROR mismatched types
 fn dyn_fixed_generic1(x: &dyn for<'a> FixedGeneric1<'a>) { x } //~ERROR mismatched types
-fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x } //~ERROR mismatched types
+// fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x } // Unsound!
 fn dyn_fixed_generic_multi(x: &dyn for<'a> FixedGeneric1<'a, Assoc2 = &u8>) { x } //~ERROR mismatched types
 fn dyn_fixed_hrtb(x: &dyn FixedHrtb) { x } //~ERROR mismatched types
 fn dyn_any_different_binders(x: &dyn AnyDifferentBinders<Assoc = u8>) { x } //~ERROR mismatched types
diff --git a/tests/ui/traits/object/pretty.stderr b/tests/ui/traits/object/pretty.stderr
index ca56bdbb67a56..af941e69c5f84 100644
--- a/tests/ui/traits/object/pretty.stderr
+++ b/tests/ui/traits/object/pretty.stderr
@@ -106,17 +106,6 @@ LL | fn dyn_fixed_generic1(x: &dyn for<'a> FixedGeneric1<'a>) { x }
    = note: expected unit type `()`
               found reference `&dyn for<'a> FixedGeneric1<'a>`
 
-error[E0308]: mismatched types
-  --> $DIR/pretty.rs:35:60
-   |
-LL | fn dyn_fixed_generic2(x: &dyn for<'a> FixedGeneric2<'a>) { x }
-   |                                                         -  ^ expected `()`, found `&dyn FixedGeneric2<'a>`
-   |                                                         |
-   |                                                         help: try adding a return type: `-> &dyn for<'a> FixedGeneric2<'a>`
-   |
-   = note: expected unit type `()`
-              found reference `&dyn for<'a> FixedGeneric2<'a>`
-
 error[E0308]: mismatched types
   --> $DIR/pretty.rs:36:79
    |
@@ -172,6 +161,6 @@ LL | fn dyn_has_gat(x: &dyn HasGat<u8, Assoc<bool> = ()>) { x }
    = note: expected unit type `()`
               found reference `&dyn HasGat<u8, Assoc<bool> = ()>`
 
-error: aborting due to 15 previous errors; 1 warning emitted
+error: aborting due to 14 previous errors; 1 warning emitted
 
 For more information about this error, try `rustc --explain E0308`.