diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index dd44fdd889328..488a505531da2 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -589,11 +589,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         &self,
         mutate_fulfillment_errors: impl Fn(&mut Vec<traits::FulfillmentError<'tcx>>),
     ) {
-        let mut result = self.fulfillment_cx.borrow_mut().select_where_possible(self);
-        if !result.is_empty() {
-            mutate_fulfillment_errors(&mut result);
-            self.adjust_fulfillment_errors_for_expr_obligation(&mut result);
-            self.err_ctxt().report_fulfillment_errors(result);
+        let mut errors = self.fulfillment_cx.borrow_mut().select_where_possible(self);
+        if !errors.is_empty() {
+            mutate_fulfillment_errors(&mut errors);
+            if errors.is_empty() {
+                // We sometimes skip reporting fulfillment errors while constructing
+                // a different error.
+                self.dcx().span_delayed_bug(
+                    self.tcx.def_span(self.body_id),
+                    "skipped reporting fulfillment errors",
+                );
+                return;
+            }
+            self.adjust_fulfillment_errors_for_expr_obligation(&mut errors);
+            self.err_ctxt().report_fulfillment_errors(errors);
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
index d24bec5a766b9..d0d0e904380ed 100644
--- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs
@@ -541,22 +541,6 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
                         .instantiate(tcx, &[ty::GenericArg::from(goal.predicate.self_ty())])
                 }
 
-                ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
-                    // This is the "fallback impl" for type parameters, unnormalizable projections
-                    // and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
-                    // FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
-                    // exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
-                    let sized_predicate = ty::TraitRef::from_lang_item(
-                        tcx,
-                        LangItem::Sized,
-                        DUMMY_SP,
-                        [ty::GenericArg::from(goal.predicate.self_ty())],
-                    );
-                    // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
-                    ecx.add_goal(GoalSource::Misc, goal.with(tcx, sized_predicate));
-                    tcx.types.unit
-                }
-
                 ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
                     None => tcx.types.unit,
                     Some(tail_def) => {
@@ -571,6 +555,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
                     Some(&tail_ty) => Ty::new_projection(tcx, metadata_def_id, [tail_ty]),
                 },
 
+                // The metadata of these types can only be known from param env or alias bound
+                // candidates.
+                ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => return Err(NoSolution),
+
                 ty::Infer(
                     ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_),
                 )
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
index 4bc3ff92a6751..4f4675e97bdd8 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs
@@ -80,61 +80,81 @@ pub fn suggest_new_overflow_limit<'tcx, G: EmissionGuarantee>(
 
 #[extension(pub trait TypeErrCtxtExt<'tcx>)]
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
+    #[instrument(skip(self), level = "debug")]
     fn report_fulfillment_errors(
         &self,
         mut errors: Vec<FulfillmentError<'tcx>>,
     ) -> ErrorGuaranteed {
+        if errors.is_empty() {
+            bug!("attempted to report fulfillment errors, but there we no errors");
+        }
+
         self.sub_relations
             .borrow_mut()
             .add_constraints(self, errors.iter().map(|e| e.obligation.predicate));
 
+        let mut reported = None;
+
+        // We want to ignore desugarings when filtering errors: spans are equivalent even
+        // if one is the result of a desugaring and the other is not.
+        let strip_desugaring = |span: Span| {
+            let expn_data = span.ctxt().outer_expn_data();
+            if let ExpnKind::Desugaring(_) = expn_data.kind { expn_data.call_site } else { span }
+        };
+
         #[derive(Debug)]
-        struct ErrorDescriptor<'tcx> {
+        struct ErrorDescriptor<'tcx, 'err> {
             predicate: ty::Predicate<'tcx>,
-            index: Option<usize>, // None if this is an old error
+            source: Option<(usize, &'err FulfillmentError<'tcx>)>, // None if this is an old error
         }
 
         let mut error_map: FxIndexMap<_, Vec<_>> = self
             .reported_trait_errors
             .borrow()
             .iter()
-            .map(|(&span, predicates)| {
-                (
-                    span,
-                    predicates
-                        .0
-                        .iter()
-                        .map(|&predicate| ErrorDescriptor { predicate, index: None })
-                        .collect(),
-                )
+            .map(|(&span, &(ref predicates, guar))| {
+                reported = Some(guar);
+                let span = strip_desugaring(span);
+                let reported_errors = predicates
+                    .iter()
+                    .map(|&predicate| ErrorDescriptor { predicate, source: None })
+                    .collect();
+                (span, reported_errors)
             })
             .collect();
 
+        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+        enum ErrorOrd {
+            Default,
+            Sized,
+            Metadata,
+            Coerce,
+            WellFormed,
+        }
+
         // Ensure `T: Sized` and `T: WF` obligations come last. This lets us display diagnostics
-        // with more relevant type information and hide redundant E0282 errors.
+        // with more relevant type information and hide redundant E0282 ("type annotations needed") errors.
         errors.sort_by_key(|e| match e.obligation.predicate.kind().skip_binder() {
             ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred))
                 if Some(pred.def_id()) == self.tcx.lang_items().sized_trait() =>
             {
-                1
+                ErrorOrd::Sized
+            }
+            ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred))
+                if Some(pred.def_id()) == self.tcx.lang_items().metadata_type() =>
+            {
+                ErrorOrd::Metadata
             }
-            ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => 3,
-            ty::PredicateKind::Coerce(_) => 2,
-            _ => 0,
+            ty::PredicateKind::Coerce(_) => ErrorOrd::Coerce,
+            ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => ErrorOrd::WellFormed,
+            _ => ErrorOrd::Default,
         });
 
         for (index, error) in errors.iter().enumerate() {
-            // We want to ignore desugarings here: spans are equivalent even
-            // if one is the result of a desugaring and the other is not.
-            let mut span = error.obligation.cause.span;
-            let expn_data = span.ctxt().outer_expn_data();
-            if let ExpnKind::Desugaring(_) = expn_data.kind {
-                span = expn_data.call_site;
-            }
-
+            let span = strip_desugaring(error.obligation.cause.span);
             error_map.entry(span).or_default().push(ErrorDescriptor {
                 predicate: error.obligation.predicate,
-                index: Some(index),
+                source: Some((index, error)),
             });
         }
 
@@ -144,59 +164,63 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         for (_, error_set) in error_map.iter() {
             // We want to suppress "duplicate" errors with the same span.
             for error in error_set {
-                if let Some(index) = error.index {
+                let Some((index, error_source)) = error.source else {
+                    continue;
+                };
+
+                for error2 in error_set {
                     // Suppress errors that are either:
                     // 1) strictly implied by another error.
                     // 2) implied by an error with a smaller index.
-                    for error2 in error_set {
-                        if error2.index.is_some_and(|index2| is_suppressed[index2]) {
-                            // Avoid errors being suppressed by already-suppressed
-                            // errors, to prevent all errors from being suppressed
-                            // at once.
-                            continue;
-                        }
+                    if self.error_implied_by(error.predicate, error2.predicate)
+                        && (!error2.source.is_some_and(|(index2, _)| index2 >= index)
+                            || !self.error_implied_by(error2.predicate, error.predicate))
+                    {
+                        info!("skipping `{}` (implied by `{}`)", error.predicate, error2.predicate);
+                        is_suppressed[index] = true;
+                        break;
+                    }
 
-                        if self.error_implies(error2.predicate, error.predicate)
-                            && !(error2.index >= error.index
-                                && self.error_implies(error.predicate, error2.predicate))
-                        {
-                            info!("skipping {:?} (implied by {:?})", error, error2);
-                            is_suppressed[index] = true;
-                            break;
-                        }
+                    // Also suppress the error if we are absolutely certain that a different
+                    // error is the one that the user should fix. This will suppress errors
+                    // about `<T as Pointee>::Metadata == ()` that can be fixed by `T: Sized`.
+                    if error.predicate.to_opt_poly_projection_pred().is_some()
+                        && error2.predicate.to_opt_poly_trait_pred().is_some()
+                        && self.error_fixed_by(
+                            error_source.obligation.clone(),
+                            error2.predicate.expect_clause(),
+                        )
+                    {
+                        info!("skipping `{}` (fixed by `{}`)", error.predicate, error2.predicate);
+                        is_suppressed[index] = true;
+                        break;
                     }
                 }
             }
         }
 
-        let mut reported = None;
-
         for from_expansion in [false, true] {
-            for (error, suppressed) in iter::zip(&errors, &is_suppressed) {
-                if !suppressed && error.obligation.cause.span.from_expansion() == from_expansion {
-                    let guar = self.report_fulfillment_error(error);
-                    reported = Some(guar);
-                    // We want to ignore desugarings here: spans are equivalent even
-                    // if one is the result of a desugaring and the other is not.
-                    let mut span = error.obligation.cause.span;
-                    let expn_data = span.ctxt().outer_expn_data();
-                    if let ExpnKind::Desugaring(_) = expn_data.kind {
-                        span = expn_data.call_site;
-                    }
-                    self.reported_trait_errors
-                        .borrow_mut()
-                        .entry(span)
-                        .or_insert_with(|| (vec![], guar))
-                        .0
-                        .push(error.obligation.predicate);
+            for (error, &suppressed) in iter::zip(&errors, &is_suppressed) {
+                let span = error.obligation.cause.span;
+                if suppressed || span.from_expansion() != from_expansion {
+                    continue;
                 }
+
+                let guar = self.report_fulfillment_error(error);
+                reported = Some(guar);
+
+                self.reported_trait_errors
+                    .borrow_mut()
+                    .entry(span)
+                    .or_insert_with(|| (vec![], guar))
+                    .0
+                    .push(error.obligation.predicate);
             }
         }
 
-        // It could be that we don't report an error because we have seen an `ErrorReported` from
-        // another source. We should probably be able to fix most of these, but some are delayed
-        // bugs that get a proper error after this function.
-        reported.unwrap_or_else(|| self.dcx().delayed_bug("failed to report fulfillment errors"))
+        // If all errors are suppressed, then we must have reported at least one error
+        // from a previous call to this function.
+        reported.unwrap_or_else(|| bug!("failed to report fulfillment errors"))
     }
 
     /// Reports that an overflow has occurred and halts compilation. We
@@ -1465,10 +1489,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
             && self.can_eq(param_env, goal.term, assumption.term)
     }
 
-    // returns if `cond` not occurring implies that `error` does not occur - i.e., that
-    // `error` occurring implies that `cond` occurs.
+    /// Returns whether `cond` not occurring implies that `error` does not occur - i.e., that
+    /// `error` occurring implies that `cond` occurs.
     #[instrument(level = "debug", skip(self), ret)]
-    fn error_implies(&self, cond: ty::Predicate<'tcx>, error: ty::Predicate<'tcx>) -> bool {
+    fn error_implied_by(&self, error: ty::Predicate<'tcx>, cond: ty::Predicate<'tcx>) -> bool {
         if cond == error {
             return true;
         }
@@ -1490,6 +1514,29 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
         }
     }
 
+    /// Returns whether fixing `cond` will also fix `error`.
+    #[instrument(level = "debug", skip(self), ret)]
+    fn error_fixed_by(&self, mut error: PredicateObligation<'tcx>, cond: ty::Clause<'tcx>) -> bool {
+        self.probe(|_| {
+            let ocx = ObligationCtxt::new(self);
+
+            let clauses = elaborate(self.tcx, std::iter::once(cond)).collect::<Vec<_>>();
+            let clauses = ocx.normalize(&error.cause, error.param_env, clauses);
+            let mut clauses = self.resolve_vars_if_possible(clauses);
+
+            if clauses.has_infer() {
+                return false;
+            }
+
+            clauses.extend(error.param_env.caller_bounds());
+            let clauses = self.tcx.mk_clauses(&clauses);
+            error.param_env = ty::ParamEnv::new(clauses, error.param_env.reveal());
+
+            ocx.register_obligation(error);
+            ocx.select_all_or_error().is_empty()
+        })
+    }
+
     #[instrument(skip(self), level = "debug")]
     fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) -> ErrorGuaranteed {
         if self.tcx.sess.opts.unstable_opts.next_solver.map(|c| c.dump_tree).unwrap_or_default()
diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs
index 6756b5dec2318..ab5855ebc325a 100644
--- a/compiler/rustc_trait_selection/src/traits/project.rs
+++ b/compiler/rustc_trait_selection/src/traits/project.rs
@@ -23,7 +23,6 @@ use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use crate::infer::{BoundRegionConversionTime, InferOk};
 use crate::traits::normalize::normalize_with_depth;
 use crate::traits::normalize::normalize_with_depth_to;
-use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
 use crate::traits::select::ProjectionMatchesProjection;
 use rustc_data_structures::sso::SsoHashSet;
 use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -975,9 +974,12 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                 //
                 // NOTE: This should be kept in sync with the similar code in
                 // `rustc_ty_utils::instance::resolve_associated_item()`.
-                let node_item =
-                    specialization_graph::assoc_def(selcx.tcx(), impl_data.impl_def_id, obligation.predicate.def_id)
-                        .map_err(|ErrorGuaranteed { .. }| ())?;
+                let node_item = specialization_graph::assoc_def(
+                    selcx.tcx(),
+                    impl_data.impl_def_id,
+                    obligation.predicate.def_id,
+                )
+                .map_err(|ErrorGuaranteed { .. }| ())?;
 
                 if node_item.is_final() {
                     // Non-specializable items are always projectable.
@@ -1020,7 +1022,8 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                     lang_items.async_fn_trait(),
                     lang_items.async_fn_mut_trait(),
                     lang_items.async_fn_once_trait(),
-                ].contains(&Some(trait_ref.def_id))
+                ]
+                .contains(&Some(trait_ref.def_id))
                 {
                     true
                 } else if lang_items.async_fn_kind_helper() == Some(trait_ref.def_id) {
@@ -1033,7 +1036,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                         true
                     } else {
                         obligation.predicate.args.type_at(0).to_opt_closure_kind().is_some()
-                        && obligation.predicate.args.type_at(1).to_opt_closure_kind().is_some()
+                            && obligation.predicate.args.type_at(1).to_opt_closure_kind().is_some()
                     }
                 } else if lang_items.discriminant_kind_trait() == Some(trait_ref.def_id) {
                     match self_ty.kind() {
@@ -1072,24 +1075,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                         | ty::Error(_) => false,
                     }
                 } else if lang_items.pointee_trait() == Some(trait_ref.def_id) {
-                    let tail = selcx.tcx().struct_tail_with_normalize(
-                        self_ty,
-                        |ty| {
-                            // We throw away any obligations we get from this, since we normalize
-                            // and confirm these obligations once again during confirmation
-                            normalize_with_depth(
-                                selcx,
-                                obligation.param_env,
-                                obligation.cause.clone(),
-                                obligation.recursion_depth + 1,
-                                ty,
-                            )
-                            .value
-                        },
-                        || {},
-                    );
-
-                    match tail.kind() {
+                    match self_ty.kind() {
                         ty::Bool
                         | ty::Char
                         | ty::Int(_)
@@ -1110,40 +1096,30 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                         | ty::Never
                         // Extern types have unit metadata, according to RFC 2850
                         | ty::Foreign(_)
-                        // If returned by `struct_tail_without_normalization` this is a unit struct
-                        // without any fields, or not a struct, and therefore is Sized.
+                        // The metadata of an ADT or tuple is the metadata of its tail,
+                        // or unit if it has no tail.
                         | ty::Adt(..)
-                        // If returned by `struct_tail_without_normalization` this is the empty tuple.
                         | ty::Tuple(..)
-                        // Integers and floats are always Sized, and so have unit type metadata.
-                        | ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..)) => true,
+                        // Integers and floats are always sized, and so have unit type metadata.
+                        | ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..))
+                        // The metadata of `{type error}` is `{type error}`.
+                        | ty::Error(_) => true,
 
-                        // We normalize from `Wrapper<Tail>::Metadata` to `Tail::Metadata` if able.
-                        // Otherwise, type parameters, opaques, and unnormalized projections have
-                        // unit metadata if they're known (e.g. by the param_env) to be sized.
-                        ty::Param(_) | ty::Alias(..)
-                            if self_ty != tail || selcx.infcx.predicate_must_hold_modulo_regions(
-                                &obligation.with(
-                                    selcx.tcx(),
-                                    ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]),
-                                ),
-                            ) =>
-                        {
-                            true
-                        }
+                        // The metadata of these types can only be known from param env candidates.
+                        ty::Param(_) | ty::Alias(..) | ty::Bound(..) | ty::Placeholder(..) => false,
 
-                        // FIXME(compiler-errors): are Bound and Placeholder types ever known sized?
-                        ty::Param(_)
-                        | ty::Alias(..)
-                        | ty::Bound(..)
-                        | ty::Placeholder(..)
-                        | ty::Infer(..)
-                        | ty::Error(_) => {
-                            if tail.has_infer_types() {
-                                candidate_set.mark_ambiguous();
-                            }
+                        ty::Infer(ty::TyVar(_)) => {
+                            candidate_set.mark_ambiguous();
                             false
                         }
+
+                        ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
+                            span_bug!(
+                                obligation.cause.span,
+                                "unexpected self ty `{self_ty:?}` when normalizing \
+                                `<T as Pointee>::Metadata`",
+                            )
+                        }
                     }
                 } else {
                     bug!("unexpected builtin trait with associated type: {trait_ref:?}")
@@ -1190,7 +1166,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
                     obligation.cause.span,
                     format!("Cannot project an associated type from `{impl_source:?}`"),
                 );
-                return Err(())
+                return Err(());
             }
         };
 
@@ -1488,47 +1464,79 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
     let lang_items = tcx.lang_items();
     let item_def_id = obligation.predicate.def_id;
     let trait_def_id = tcx.trait_of_item(item_def_id).unwrap();
-    let (term, obligations) = if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
+    let mut potentially_unnormalized = false;
+    let term = if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
         let discriminant_def_id = tcx.require_lang_item(LangItem::Discriminant, None);
         assert_eq!(discriminant_def_id, item_def_id);
 
-        (self_ty.discriminant_ty(tcx).into(), Vec::new())
+        self_ty.discriminant_ty(tcx).into()
     } else if lang_items.pointee_trait() == Some(trait_def_id) {
         let metadata_def_id = tcx.require_lang_item(LangItem::Metadata, None);
         assert_eq!(metadata_def_id, item_def_id);
 
-        let mut obligations = Vec::new();
-        let normalize = |ty| {
-            normalize_with_depth_to(
-                selcx,
-                obligation.param_env,
-                obligation.cause.clone(),
-                obligation.recursion_depth + 1,
-                ty,
-                &mut obligations,
-            )
-        };
-        let metadata_ty = self_ty.ptr_metadata_ty_or_tail(tcx, normalize).unwrap_or_else(|tail| {
-            if tail == self_ty {
-                // This is the "fallback impl" for type parameters, unnormalizable projections
-                // and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
-                // FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
-                // exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
-                let sized_predicate = ty::TraitRef::from_lang_item(
-                    tcx,
-                    LangItem::Sized,
-                    obligation.cause.span(),
-                    [self_ty],
+        let metadata_ty = match self_ty.kind() {
+            ty::Bool
+            | ty::Char
+            | ty::Int(..)
+            | ty::Uint(..)
+            | ty::Float(..)
+            | ty::Array(..)
+            | ty::RawPtr(..)
+            | ty::Ref(..)
+            | ty::FnDef(..)
+            | ty::FnPtr(..)
+            | ty::Closure(..)
+            | ty::CoroutineClosure(..)
+            | ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
+            | ty::Coroutine(..)
+            | ty::CoroutineWitness(..)
+            | ty::Never
+            | ty::Foreign(..)
+            | ty::Dynamic(_, _, ty::DynStar) => tcx.types.unit,
+
+            ty::Error(e) => Ty::new_error(tcx, *e),
+
+            ty::Str | ty::Slice(_) => tcx.types.usize,
+
+            ty::Dynamic(_, _, ty::Dyn) => {
+                let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None);
+                tcx.type_of(dyn_metadata).instantiate(tcx, &[self_ty.into()])
+            }
+
+            ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
+                None => tcx.types.unit,
+                Some(tail_def) => {
+                    // We know that `self_ty` has the same metadata as its tail. This allows us
+                    // to prove predicates like `Wrapper<Tail>::Metadata == Tail::Metadata`.
+                    let tail_ty = tail_def.ty(tcx, args);
+                    potentially_unnormalized = true;
+                    Ty::new_projection(tcx, metadata_def_id, [tail_ty])
+                }
+            },
+            ty::Adt(_, _) => tcx.types.unit,
+
+            ty::Tuple(elements) => match elements.last() {
+                None => tcx.types.unit,
+                Some(&tail_ty) => {
+                    potentially_unnormalized = true;
+                    Ty::new_projection(tcx, metadata_def_id, [tail_ty])
+                }
+            },
+
+            ty::Param(_)
+            | ty::Alias(..)
+            | ty::Bound(..)
+            | ty::Placeholder(..)
+            | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
+                span_bug!(
+                    obligation.cause.span,
+                    "`<{self_ty:?} as Pointee>::Metadata` projection candidate assembled, \
+                    but we cannot project further",
                 );
-                obligations.push(obligation.with(tcx, sized_predicate));
-                tcx.types.unit
-            } else {
-                // We know that `self_ty` has the same metadata as `tail`. This allows us
-                // to prove predicates like `Wrapper<Tail>::Metadata == Tail::Metadata`.
-                Ty::new_projection(tcx, metadata_def_id, [tail])
             }
-        });
-        (metadata_ty.into(), obligations)
+        };
+
+        metadata_ty.into()
     } else {
         bug!("unexpected builtin trait with associated type: {:?}", obligation.predicate);
     };
@@ -1536,9 +1544,13 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
     let predicate =
         ty::ProjectionPredicate { projection_ty: ty::AliasTy::new(tcx, item_def_id, args), term };
 
-    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
-        .with_addl_obligations(obligations)
-        .with_addl_obligations(data)
+    confirm_param_env_candidate(
+        selcx,
+        obligation,
+        ty::Binder::dummy(predicate),
+        potentially_unnormalized,
+    )
+    .with_addl_obligations(data)
 }
 
 fn confirm_fn_pointer_candidate<'cx, 'tcx>(
diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs
index a56a2578c2241..5fd0e0195bf1a 100644
--- a/library/core/src/marker.rs
+++ b/library/core/src/marker.rs
@@ -142,6 +142,19 @@ unsafe impl<T: Sync + ?Sized> Send for &T {}
 #[rustc_specialization_trait]
 #[rustc_deny_explicit_impl(implement_via_object = false)]
 #[rustc_coinductive]
+#[cfg(not(bootstrap))]
+pub trait Sized: crate::ptr::Thin {
+    // Empty.
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+#[lang = "sized"]
+#[fundamental]
+#[rustc_specialization_trait]
+#[rustc_deny_explicit_impl(implement_via_object = false)]
+#[rustc_coinductive]
+#[cfg(bootstrap)]
+#[allow(missing_docs)]
 pub trait Sized {
     // Empty.
 }
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index c234e4f9b110c..d8d078d1a3a42 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -23,7 +23,7 @@ use rustc_middle::ty::{
 };
 use rustc_span::{sym, Symbol};
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
-use rustc_trait_selection::traits::{Obligation, ObligationCause};
+use rustc_trait_selection::traits::{self, Obligation, ObligationCause};
 
 use super::UNNECESSARY_TO_OWNED;
 
@@ -323,9 +323,10 @@ fn check_other_call_arg<'tcx>(
         && let (input, n_refs) = peel_mid_ty_refs(*input)
         && let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input)
         && let Some(sized_def_id) = cx.tcx.lang_items().sized_trait()
+        && let sized_super_def_ids = traits::supertrait_def_ids(cx.tcx, sized_def_id).collect::<Vec<_>>()
         && let [trait_predicate] = trait_predicates
             .iter()
-            .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
+            .filter(|trait_predicate| !sized_super_def_ids.contains(&trait_predicate.def_id()))
             .collect::<Vec<_>>()[..]
         && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
         && let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
diff --git a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
index a32bca3d03816..d50b172e21b40 100644
--- a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -18,7 +18,7 @@ use rustc_middle::ty::{
 use rustc_session::impl_lint_pass;
 use rustc_span::symbol::sym;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
-use rustc_trait_selection::traits::{Obligation, ObligationCause};
+use rustc_trait_selection::traits::{self, Obligation, ObligationCause};
 use std::collections::VecDeque;
 
 declare_clippy_lint! {
@@ -171,6 +171,10 @@ fn needless_borrow_count<'tcx>(
     let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
     let drop_trait_def_id = cx.tcx.lang_items().drop_trait();
 
+    let sized_super_def_ids = sized_trait_def_id.map_or_else(Vec::new, |sized_def_id| {
+        traits::supertrait_def_ids(cx.tcx, sized_def_id).collect()
+    });
+
     let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder();
     let predicates = cx.tcx.param_env(fn_id).caller_bounds();
     let projection_predicates = predicates
@@ -203,7 +207,7 @@ fn needless_borrow_count<'tcx>(
         })
         .all(|trait_def_id| {
             Some(trait_def_id) == destruct_trait_def_id
-                || Some(trait_def_id) == sized_trait_def_id
+                || sized_super_def_ids.contains(&trait_def_id)
                 || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
         })
     {
diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
index f33e2e0ed71aa..0bad6ac52dc08 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
@@ -118,13 +118,14 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
         ];
 
         let sized_trait = need!(cx.tcx.lang_items().sized_trait());
+        let sized_super_traits = traits::supertrait_def_ids(cx.tcx, sized_trait).collect::<Vec<_>>();
 
         let preds = traits::elaborate(cx.tcx, cx.param_env.caller_bounds().iter())
             .filter(|p| !p.is_global())
             .filter_map(|pred| {
                 // Note that we do not want to deal with qualified predicates here.
                 match pred.kind().no_bound_vars() {
-                    Some(ty::ClauseKind::Trait(pred)) if pred.def_id() != sized_trait => Some(pred),
+                    Some(ty::ClauseKind::Trait(pred)) if !sized_super_traits.contains(&pred.def_id()) => Some(pred),
                     _ => None,
                 }
             })
diff --git a/src/tools/miri/tests/fail/layout_cycle.rs b/src/tools/miri/tests/fail/layout_cycle.rs
index 3e0dd881db84e..2ea96ef7649da 100644
--- a/src/tools/miri/tests/fail/layout_cycle.rs
+++ b/src/tools/miri/tests/fail/layout_cycle.rs
@@ -5,6 +5,7 @@ use std::mem;
 
 pub struct S<T: Tr> {
     pub f: <T as Tr>::I,
+    _tail: (), // without this, we get an overflow error instead
 }
 
 pub trait Tr {
diff --git a/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr b/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr
index b31689dbf7365..00c91c3e4f072 100644
--- a/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr
+++ b/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr
@@ -32,6 +32,11 @@ LL | impl<T, P: PointerFamily> Node<T, P>
 LL | where
 LL |     P::Pointer<Node<T, P>>: Sized,
    |                             ^^^^^ unsatisfied trait bound introduced here
+   = note: the following trait bounds were not satisfied:
+           `(dyn Deref<Target = Node<i32, RcFamily>> + 'static): Thin`
+           which is required by `(dyn Deref<Target = Node<i32, RcFamily>> + 'static): Sized`
+           `<(dyn Deref<Target = Node<i32, RcFamily>> + 'static) as Pointee>::Metadata = ()`
+           which is required by `(dyn Deref<Target = Node<i32, RcFamily>> + 'static): Sized`
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/layout/layout-cycle.rs b/tests/ui/layout/layout-cycle.rs
index 3c930def43b2b..406dbcd5a45cd 100644
--- a/tests/ui/layout/layout-cycle.rs
+++ b/tests/ui/layout/layout-cycle.rs
@@ -8,6 +8,7 @@ use std::mem;
 
 pub struct S<T: Tr> {
     pub f: <T as Tr>::I,
+    _tail: (), // without this, we get an overflow error instead
 }
 
 pub trait Tr {
diff --git a/tests/ui/sized/recursive-type-coercion-from-never.rs b/tests/ui/sized/recursive-type-coercion-from-never.rs
index 7bd87ae06c5e4..5f5a501990968 100644
--- a/tests/ui/sized/recursive-type-coercion-from-never.rs
+++ b/tests/ui/sized/recursive-type-coercion-from-never.rs
@@ -9,8 +9,8 @@ impl A for () {
     type Assoc = Foo<()>;
 }
 
-struct Foo<T: A>(T::Assoc);
+struct Foo<T: A>(T::Assoc, ());
 
 fn main() {
-    Foo::<()>(todo!());
+    Foo::<()>(todo!(), ());
 }
diff --git a/tests/ui/sized/recursive-type-pass.rs b/tests/ui/sized/recursive-type-pass.rs
index bffca39ffcbbf..1831b7363f82c 100644
--- a/tests/ui/sized/recursive-type-pass.rs
+++ b/tests/ui/sized/recursive-type-pass.rs
@@ -5,6 +5,6 @@ impl A for () {
     // FIXME: it would be nice for this to at least cause a warning.
     type Assoc = Foo<()>;
 }
-struct Foo<T: A>(T::Assoc);
+struct Foo<T: A>(T::Assoc, ());
 
 fn main() {}
diff --git a/tests/ui/sized/recursive-type-tail.rs b/tests/ui/sized/recursive-type-tail.rs
new file mode 100644
index 0000000000000..6efa2f56f21e2
--- /dev/null
+++ b/tests/ui/sized/recursive-type-tail.rs
@@ -0,0 +1,11 @@
+//@ check-fail
+
+trait A { type Assoc; }
+
+impl A for () {
+    type Assoc = Foo<()>;
+    //~^ ERROR overflow evaluating the requirement `<Foo<()> as Pointee>::Metadata == ()`
+}
+struct Foo<T: A>(T::Assoc);
+
+fn main() {}
diff --git a/tests/ui/sized/recursive-type-tail.stderr b/tests/ui/sized/recursive-type-tail.stderr
new file mode 100644
index 0000000000000..7ce0dec89afb7
--- /dev/null
+++ b/tests/ui/sized/recursive-type-tail.stderr
@@ -0,0 +1,21 @@
+error[E0275]: overflow evaluating the requirement `<Foo<()> as Pointee>::Metadata == ()`
+  --> $DIR/recursive-type-tail.rs:6:18
+   |
+LL |     type Assoc = Foo<()>;
+   |                  ^^^^^^^
+   |
+   = note: required for `<() as A>::Assoc` to implement `Thin`
+   = note: required for `<() as A>::Assoc` to implement `Sized`
+note: required by a bound in `A::Assoc`
+  --> $DIR/recursive-type-tail.rs:3:11
+   |
+LL | trait A { type Assoc; }
+   |           ^^^^^^^^^^^ required by this bound in `A::Assoc`
+help: consider relaxing the implicit `Sized` restriction
+   |
+LL | trait A { type Assoc: ?Sized; }
+   |                     ++++++++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0275`.
diff --git a/tests/ui/trait-bounds/super-assoc-mismatch.rs b/tests/ui/trait-bounds/super-assoc-mismatch.rs
index 97dfec80e3167..c2fc484f1c97c 100644
--- a/tests/ui/trait-bounds/super-assoc-mismatch.rs
+++ b/tests/ui/trait-bounds/super-assoc-mismatch.rs
@@ -6,6 +6,8 @@ impl Super for () {
 }
 trait Sub: Super<Assoc = u16> {}
 
+// direct impls (no nested obligations):
+
 trait BoundOnSelf: Sub {}
 impl BoundOnSelf for () {}
 //~^ ERROR the trait bound `(): Sub` is not satisfied
@@ -25,7 +27,7 @@ impl BoundOnAssoc for () {
 trait BoundOnGat where Self::Assoc<u8>: Sub {
     type Assoc<T>;
 }
-impl BoundOnGat for u8 {
+impl BoundOnGat for () {
     type Assoc<T> = ();
     //~^ ERROR the trait bound `(): Sub` is not satisfied
 }
@@ -33,6 +35,33 @@ impl BoundOnGat for u8 {
 fn trivial_bound() where (): Sub {}
 //~^ ERROR the trait bound `(): Sub` is not satisfied
 
+// blanket impls with nested obligations:
+
+struct Wrapper<T>(T);
+impl<T: Super> Super for Wrapper<T> {
+    type Assoc = T::Assoc;
+}
+impl<T: Sub> Sub for Wrapper<T> {}
+
+impl BoundOnSelf for Wrapper<()> {}
+//~^ ERROR the trait bound `(): Sub` is not satisfied
+
+impl BoundOnParam<Wrapper<()>> for Wrapper<()> {}
+//~^ ERROR the trait bound `(): Sub` is not satisfied
+
+impl BoundOnAssoc for Wrapper<()> {
+    type Assoc = Wrapper<()>;
+    //~^ ERROR the trait bound `(): Sub` is not satisfied
+}
+
+impl BoundOnGat for Wrapper<()> {
+    type Assoc<T> = Wrapper<()>;
+    //~^ ERROR the trait bound `(): Sub` is not satisfied
+}
+
+fn trivial_bound_wrapper() where Wrapper<()>: Sub {}
+//~^ ERROR the trait bound `(): Sub` is not satisfied
+
 // The following is an edge case where the unsatisfied projection predicate
 // `<<u8 as MultiAssoc>::Assoc1<()> as SuperGeneric<u16>>::Assoc == <u8 as MultiAssoc>::Assoc2`
 // contains both associated types of `MultiAssoc`. To suppress the error about the unsatisfied
diff --git a/tests/ui/trait-bounds/super-assoc-mismatch.stderr b/tests/ui/trait-bounds/super-assoc-mismatch.stderr
index f2c5eb47e5981..0fdc9b7eb0dcb 100644
--- a/tests/ui/trait-bounds/super-assoc-mismatch.stderr
+++ b/tests/ui/trait-bounds/super-assoc-mismatch.stderr
@@ -1,82 +1,162 @@
 error[E0277]: the trait bound `(): Sub` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:10:22
+  --> $DIR/super-assoc-mismatch.rs:12:22
    |
 LL | impl BoundOnSelf for () {}
    |                      ^^ the trait `Sub` is not implemented for `()`
    |
-help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:7:1
-   |
-LL | trait Sub: Super<Assoc = u16> {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
 note: required by a bound in `BoundOnSelf`
-  --> $DIR/super-assoc-mismatch.rs:9:20
+  --> $DIR/super-assoc-mismatch.rs:11:20
    |
 LL | trait BoundOnSelf: Sub {}
    |                    ^^^ required by this bound in `BoundOnSelf`
 
 error[E0277]: the trait bound `(): Sub` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:14:27
+  --> $DIR/super-assoc-mismatch.rs:16:27
    |
 LL | impl BoundOnParam<()> for () {}
    |                           ^^ the trait `Sub` is not implemented for `()`
    |
-help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:7:1
-   |
-LL | trait Sub: Super<Assoc = u16> {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
 note: required by a bound in `BoundOnParam`
-  --> $DIR/super-assoc-mismatch.rs:13:23
+  --> $DIR/super-assoc-mismatch.rs:15:23
    |
 LL | trait BoundOnParam<T: Sub> {}
    |                       ^^^ required by this bound in `BoundOnParam`
 
 error[E0277]: the trait bound `(): Sub` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:21:18
+  --> $DIR/super-assoc-mismatch.rs:23:18
    |
 LL |     type Assoc = ();
    |                  ^^ the trait `Sub` is not implemented for `()`
    |
-help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:7:1
-   |
-LL | trait Sub: Super<Assoc = u16> {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
 note: required by a bound in `BoundOnAssoc::Assoc`
-  --> $DIR/super-assoc-mismatch.rs:18:17
+  --> $DIR/super-assoc-mismatch.rs:20:17
    |
 LL |     type Assoc: Sub;
    |                 ^^^ required by this bound in `BoundOnAssoc::Assoc`
 
 error[E0277]: the trait bound `(): Sub` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:29:21
+  --> $DIR/super-assoc-mismatch.rs:31:21
    |
 LL |     type Assoc<T> = ();
-   |                     ^^ the trait `Sub` is not implemented for `()`, which is required by `<u8 as BoundOnGat>::Assoc<u8>: Sub`
-   |
-help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:7:1
+   |                     ^^ the trait `Sub` is not implemented for `()`, which is required by `<() as BoundOnGat>::Assoc<u8>: Sub`
    |
-LL | trait Sub: Super<Assoc = u16> {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
 note: required by a bound in `BoundOnGat`
-  --> $DIR/super-assoc-mismatch.rs:25:41
+  --> $DIR/super-assoc-mismatch.rs:27:41
    |
 LL | trait BoundOnGat where Self::Assoc<u8>: Sub {
    |                                         ^^^ required by this bound in `BoundOnGat`
 
 error[E0277]: the trait bound `(): Sub` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:33:26
+  --> $DIR/super-assoc-mismatch.rs:35:26
    |
 LL | fn trivial_bound() where (): Sub {}
    |                          ^^^^^^^ the trait `Sub` is not implemented for `()`
    |
-help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:7:1
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+   = help: see issue #48214
+help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
+   |
+LL + #![feature(trivial_bounds)]
+   |
+
+error[E0277]: the trait bound `(): Sub` is not satisfied
+  --> $DIR/super-assoc-mismatch.rs:46:22
+   |
+LL | impl BoundOnSelf for Wrapper<()> {}
+   |                      ^^^^^^^^^^^ the trait `Sub` is not implemented for `()`, which is required by `Wrapper<()>: Sub`
+   |
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+note: required for `Wrapper<()>` to implement `Sub`
+  --> $DIR/super-assoc-mismatch.rs:44:14
+   |
+LL | impl<T: Sub> Sub for Wrapper<T> {}
+   |         ---  ^^^     ^^^^^^^^^^
+   |         |
+   |         unsatisfied trait bound introduced here
+note: required by a bound in `BoundOnSelf`
+  --> $DIR/super-assoc-mismatch.rs:11:20
+   |
+LL | trait BoundOnSelf: Sub {}
+   |                    ^^^ required by this bound in `BoundOnSelf`
+
+error[E0277]: the trait bound `(): Sub` is not satisfied
+  --> $DIR/super-assoc-mismatch.rs:49:36
+   |
+LL | impl BoundOnParam<Wrapper<()>> for Wrapper<()> {}
+   |                                    ^^^^^^^^^^^ the trait `Sub` is not implemented for `()`, which is required by `Wrapper<()>: Sub`
+   |
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+note: required for `Wrapper<()>` to implement `Sub`
+  --> $DIR/super-assoc-mismatch.rs:44:14
+   |
+LL | impl<T: Sub> Sub for Wrapper<T> {}
+   |         ---  ^^^     ^^^^^^^^^^
+   |         |
+   |         unsatisfied trait bound introduced here
+note: required by a bound in `BoundOnParam`
+  --> $DIR/super-assoc-mismatch.rs:15:23
+   |
+LL | trait BoundOnParam<T: Sub> {}
+   |                       ^^^ required by this bound in `BoundOnParam`
+
+error[E0277]: the trait bound `(): Sub` is not satisfied
+  --> $DIR/super-assoc-mismatch.rs:53:18
+   |
+LL |     type Assoc = Wrapper<()>;
+   |                  ^^^^^^^^^^^ the trait `Sub` is not implemented for `()`, which is required by `Wrapper<()>: Sub`
+   |
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+note: required for `Wrapper<()>` to implement `Sub`
+  --> $DIR/super-assoc-mismatch.rs:44:14
+   |
+LL | impl<T: Sub> Sub for Wrapper<T> {}
+   |         ---  ^^^     ^^^^^^^^^^
+   |         |
+   |         unsatisfied trait bound introduced here
+note: required by a bound in `BoundOnAssoc::Assoc`
+  --> $DIR/super-assoc-mismatch.rs:20:17
+   |
+LL |     type Assoc: Sub;
+   |                 ^^^ required by this bound in `BoundOnAssoc::Assoc`
+
+error[E0277]: the trait bound `(): Sub` is not satisfied
+  --> $DIR/super-assoc-mismatch.rs:58:21
+   |
+LL |     type Assoc<T> = Wrapper<()>;
+   |                     ^^^^^^^^^^^ the trait `Sub` is not implemented for `()`, which is required by `<Wrapper<()> as BoundOnGat>::Assoc<u8>: Sub`
+   |
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+note: required for `Wrapper<()>` to implement `Sub`
+  --> $DIR/super-assoc-mismatch.rs:44:14
+   |
+LL | impl<T: Sub> Sub for Wrapper<T> {}
+   |         ---  ^^^     ^^^^^^^^^^
+   |         |
+   |         unsatisfied trait bound introduced here
+note: required by a bound in `BoundOnGat`
+  --> $DIR/super-assoc-mismatch.rs:27:41
+   |
+LL | trait BoundOnGat where Self::Assoc<u8>: Sub {
+   |                                         ^^^ required by this bound in `BoundOnGat`
+
+error[E0277]: the trait bound `(): Sub` is not satisfied
+  --> $DIR/super-assoc-mismatch.rs:62:34
+   |
+LL | fn trivial_bound_wrapper() where Wrapper<()>: Sub {}
+   |                                  ^^^^^^^^^^^^^^^^ the trait `Sub` is not implemented for `()`, which is required by `Wrapper<()>: Sub`
+   |
+   = help: the trait `Sub` is implemented for `Wrapper<T>`
+note: required for `Wrapper<()>` to implement `Sub`
+  --> $DIR/super-assoc-mismatch.rs:44:14
    |
-LL | trait Sub: Super<Assoc = u16> {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | impl<T: Sub> Sub for Wrapper<T> {}
+   |         ---  ^^^     ^^^^^^^^^^
+   |         |
+   |         unsatisfied trait bound introduced here
    = help: see issue #48214
 help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
    |
@@ -84,18 +164,18 @@ LL + #![feature(trivial_bounds)]
    |
 
 error[E0277]: the trait bound `(): SubGeneric<u16>` is not satisfied
-  --> $DIR/super-assoc-mismatch.rs:55:22
+  --> $DIR/super-assoc-mismatch.rs:84:22
    |
 LL |     type Assoc1<T> = ();
    |                      ^^ the trait `SubGeneric<u16>` is not implemented for `()`, which is required by `<u8 as MultiAssoc>::Assoc1<()>: SubGeneric<<u8 as MultiAssoc>::Assoc2>`
    |
 help: this trait has no implementations, consider adding one
-  --> $DIR/super-assoc-mismatch.rs:43:1
+  --> $DIR/super-assoc-mismatch.rs:72:1
    |
 LL | trait SubGeneric<T>: SuperGeneric<T, Assoc = T> {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 note: required by a bound in `MultiAssoc`
-  --> $DIR/super-assoc-mismatch.rs:46:23
+  --> $DIR/super-assoc-mismatch.rs:75:23
    |
 LL | trait MultiAssoc
    |       ---------- required by a bound in this trait
@@ -103,6 +183,6 @@ LL | where
 LL |     Self::Assoc1<()>: SubGeneric<Self::Assoc2>
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MultiAssoc`
 
-error: aborting due to 6 previous errors
+error: aborting due to 11 previous errors
 
 For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/traits/bad-sized.stderr b/tests/ui/traits/bad-sized.stderr
index 4c1835dfed085..0d05dfa872830 100644
--- a/tests/ui/traits/bad-sized.stderr
+++ b/tests/ui/traits/bad-sized.stderr
@@ -9,33 +9,33 @@ LL |     let x: Vec<dyn Trait + Sized> = Vec::new();
    = help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: Trait + Sized {}`
    = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>
 
-error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
+error[E0277]: the size for values of type `dyn Trait<Metadata = ()>` cannot be known at compilation time
   --> $DIR/bad-sized.rs:4:12
    |
 LL |     let x: Vec<dyn Trait + Sized> = Vec::new();
    |            ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
-   = help: the trait `Sized` is not implemented for `dyn Trait`
+   = help: the trait `Sized` is not implemented for `dyn Trait<Metadata = ()>`
 note: required by an implicit `Sized` bound in `Vec`
   --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL
 
-error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
+error[E0277]: the size for values of type `dyn Trait<Metadata = ()>` cannot be known at compilation time
   --> $DIR/bad-sized.rs:4:37
    |
 LL |     let x: Vec<dyn Trait + Sized> = Vec::new();
    |                                     ^^^^^^^^^^ doesn't have a size known at compile-time
    |
-   = help: the trait `Sized` is not implemented for `dyn Trait`
+   = help: the trait `Sized` is not implemented for `dyn Trait<Metadata = ()>`
 note: required by a bound in `Vec::<T>::new`
   --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL
 
-error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
+error[E0277]: the size for values of type `dyn Trait<Metadata = ()>` cannot be known at compilation time
   --> $DIR/bad-sized.rs:4:37
    |
 LL |     let x: Vec<dyn Trait + Sized> = Vec::new();
    |                                     ^^^ doesn't have a size known at compile-time
    |
-   = help: the trait `Sized` is not implemented for `dyn Trait`
+   = help: the trait `Sized` is not implemented for `dyn Trait<Metadata = ()>`
 note: required by an implicit `Sized` bound in `Vec`
   --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL
 
diff --git a/tests/ui/traits/issue-32963.rs b/tests/ui/traits/issue-32963.rs
index 56a68f3a2312c..9c0bb6833adb2 100644
--- a/tests/ui/traits/issue-32963.rs
+++ b/tests/ui/traits/issue-32963.rs
@@ -2,10 +2,10 @@ use std::mem;
 
 trait Misc {}
 
-fn size_of_copy<T: Copy+?Sized>() -> usize { mem::size_of::<T>() }
+fn size_of_copy<T: Copy + ?Sized>() -> usize { mem::size_of::<T>() }
 
 fn main() {
     size_of_copy::<dyn Misc + Copy>();
     //~^ ERROR only auto traits can be used as additional traits in a trait object
-    //~| ERROR the trait bound `dyn Misc: Copy` is not satisfied
+    //~| ERROR the trait bound `dyn Misc<Metadata = ()>: Copy` is not satisfied
 }
diff --git a/tests/ui/traits/issue-32963.stderr b/tests/ui/traits/issue-32963.stderr
index bad45e54d6428..057420c78748a 100644
--- a/tests/ui/traits/issue-32963.stderr
+++ b/tests/ui/traits/issue-32963.stderr
@@ -9,16 +9,16 @@ LL |     size_of_copy::<dyn Misc + Copy>();
    = help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: Misc + Copy {}`
    = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>
 
-error[E0277]: the trait bound `dyn Misc: Copy` is not satisfied
+error[E0277]: the trait bound `dyn Misc<Metadata = ()>: Copy` is not satisfied
   --> $DIR/issue-32963.rs:8:20
    |
 LL |     size_of_copy::<dyn Misc + Copy>();
-   |                    ^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `dyn Misc`
+   |                    ^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `dyn Misc<Metadata = ()>`
    |
 note: required by a bound in `size_of_copy`
   --> $DIR/issue-32963.rs:5:20
    |
-LL | fn size_of_copy<T: Copy+?Sized>() -> usize { mem::size_of::<T>() }
+LL | fn size_of_copy<T: Copy + ?Sized>() -> usize { mem::size_of::<T>() }
    |                    ^^^^ required by this bound in `size_of_copy`
 
 error: aborting due to 2 previous errors
diff --git a/tests/ui/traits/pointee-normalize-equate.rs b/tests/ui/traits/pointee-normalize-equate.rs
index 3edb010a827b8..5b7a1901c6731 100644
--- a/tests/ui/traits/pointee-normalize-equate.rs
+++ b/tests/ui/traits/pointee-normalize-equate.rs
@@ -26,6 +26,14 @@ fn wrapper_to_unit<T>(ptr: *const ()) -> *const Wrapper<T> {
     cast_same_meta(ptr)
 }
 
+// normalize `Wrapper<T>::Metadata` -> `()`
+fn wrapper_to_unit2<T: ?Sized>(ptr: *const ()) -> *const Wrapper<T>
+where
+    Wrapper<T>: Sized,
+{
+    cast_same_meta(ptr)
+}
+
 trait Project {
     type Assoc: ?Sized;
 }
@@ -45,8 +53,16 @@ where
     cast_same_meta(ptr)
 }
 
-// normalize `<[T] as Pointee>::Metadata` -> `usize`, even if `[T]: Sized`
-fn sized_slice<T>(ptr: *const [T]) -> *const str
+// normalize `WrapperProject<T>::Metadata` -> `T::Assoc::Metadata` -> `()`
+fn wrapper_project_unit2<T: ?Sized + Project>(ptr: *const ()) -> *const WrapperProject<T>
+where
+    WrapperProject<T>: Sized,
+{
+    cast_same_meta(ptr)
+}
+
+// if `[T]: Sized`, then normalize `<[T] as Pointee>::Metadata` -> `()`
+fn sized_slice<T>(ptr: *const ()) -> *const [T]
 where
     [T]: Sized,
 {
diff --git a/tests/ui/traits/pointee-normalize-shallow.rs b/tests/ui/traits/pointee-normalize-shallow.rs
new file mode 100644
index 0000000000000..032a0dc738efc
--- /dev/null
+++ b/tests/ui/traits/pointee-normalize-shallow.rs
@@ -0,0 +1,20 @@
+//@ check-pass
+
+#![feature(ptr_metadata)]
+
+use std::ptr::Thin;
+
+struct Wrapper<T: ?Sized>(T);
+
+fn check_thin<T: ?Sized + Thin>() {}
+
+// Test that normalization of `<Wrapper<[T]> as Pointee>::Metadata` respects the
+// `<[T] as Pointee>::Metadata == ()` bound from the param env.
+fn foo<T>()
+where
+    [T]: Thin,
+{
+    check_thin::<Wrapper<[T]>>();
+}
+
+fn main() {}