diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 7e2e85ead5469..43f98c630cefe 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -37,6 +37,7 @@ use rustc_span::symbol::sym;
 use rustc_span::{DebuggerVisualizerFile, DebuggerVisualizerType};
 use rustc_target::abi::{Align, VariantIdx};
 
+use std::assert_matches::assert_matches;
 use std::collections::BTreeSet;
 use std::convert::TryFrom;
 use std::ops::{Deref, DerefMut};
@@ -197,21 +198,21 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
     src_ty: Ty<'tcx>,
     dst_ty: Ty<'tcx>,
     old_info: Option<Bx::Value>,
-) -> (Bx::Value, Bx::Value) {
+) -> OperandValue<Bx::Value> {
     debug!("unsize_ptr: {:?} => {:?}", src_ty, dst_ty);
+    let src_layout = bx.cx().layout_of(src_ty);
+    let dst_layout = bx.cx().layout_of(dst_ty);
     match (src_ty.kind(), dst_ty.kind()) {
         (&ty::Ref(_, a, _), &ty::Ref(_, b, _) | &ty::RawPtr(ty::TypeAndMut { ty: b, .. }))
         | (&ty::RawPtr(ty::TypeAndMut { ty: a, .. }), &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) => {
             assert_eq!(bx.cx().type_is_sized(a), old_info.is_none());
             let ptr_ty = bx.cx().type_ptr_to(bx.cx().backend_type(bx.cx().layout_of(b)));
-            (bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info))
+            OperandValue::Pair(bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info))
         }
-        (&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
+        (&ty::Adt(def_a, _), &ty::Adt(def_b, _)) if bx.cx().is_backend_scalar_pair(dst_layout) => {
             assert_eq!(def_a, def_b);
-            let src_layout = bx.cx().layout_of(src_ty);
-            let dst_layout = bx.cx().layout_of(dst_ty);
             if src_ty == dst_ty {
-                return (src, old_info.unwrap());
+                return OperandValue::Pair(src, old_info.unwrap());
             }
             let mut result = None;
             for i in 0..src_layout.fields.count() {
@@ -226,16 +227,29 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
 
                 let dst_f = dst_layout.field(bx.cx(), i);
                 assert_ne!(src_f.ty, dst_f.ty);
-                assert_eq!(result, None);
+                assert_matches!(result, None);
                 result = Some(unsize_ptr(bx, src, src_f.ty, dst_f.ty, old_info));
             }
-            let (lldata, llextra) = result.unwrap();
+            let OperandValue::Pair(lldata, llextra) = result.unwrap()
+            else { bug!() };
             let lldata_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 0, true);
             let llextra_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 1, true);
             // HACK(eddyb) have to bitcast pointers until LLVM removes pointee types.
-            (bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty))
+            OperandValue::Pair(bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty))
+        }
+        (&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b)) => {
+            assert_eq!(def_a, def_b);
+            let typed_metadata = bx.tcx().require_lang_item(LangItem::TypedMetadata, None);
+            assert_eq!(def_a.did(), typed_metadata);
+            if src_ty == dst_ty {
+                return OperandValue::Immediate(src);
+            }
+            let a = substs_a.type_at(0);
+            let b = substs_b.type_at(0);
+            assert_eq!(bx.cx().type_is_sized(a), old_info.is_none());
+            OperandValue::Immediate(unsized_info(bx, a, b, old_info))
         }
-        _ => bug!("unsize_ptr: called on bad types"),
+        _ => bug!("unsize_ptr: called on bad types {:?} -> {:?}", src_ty, dst_ty),
     }
 }
 
@@ -250,12 +264,12 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
     let dst_ty = dst.layout.ty;
     match (src_ty.kind(), dst_ty.kind()) {
         (&ty::Ref(..), &ty::Ref(..) | &ty::RawPtr(..)) | (&ty::RawPtr(..), &ty::RawPtr(..)) => {
-            let (base, info) = match bx.load_operand(src).val {
+            let val = match bx.load_operand(src).val {
                 OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)),
                 OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None),
                 OperandValue::Ref(..) => bug!(),
             };
-            OperandValue::Pair(base, info).store(bx, dst);
+            val.store(bx, dst);
         }
 
         (&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 4be3ae11e4e5b..edfc27ae1d075 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -1,4 +1,5 @@
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(assert_matches)]
 #![feature(box_patterns)]
 #![feature(try_blocks)]
 #![feature(let_else)]
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index a5806d64d437c..72a9276fe4dd3 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -225,7 +225,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                         operand.val
                     }
                     mir::CastKind::Pointer(PointerCast::Unsize) => {
-                        assert!(bx.cx().is_backend_scalar_pair(cast));
                         let (lldata, llextra) = match operand.val {
                             OperandValue::Pair(lldata, llextra) => {
                                 // unsize from a fat pointer -- this is a
@@ -240,9 +239,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 bug!("by-ref operand {:?} in `codegen_rvalue_operand`", operand);
                             }
                         };
-                        let (lldata, llextra) =
-                            base::unsize_ptr(&mut bx, lldata, operand.layout.ty, cast.ty, llextra);
-                        OperandValue::Pair(lldata, llextra)
+                        base::unsize_ptr(&mut bx, lldata, operand.layout.ty, cast.ty, llextra)
                     }
                     mir::CastKind::Pointer(PointerCast::MutToConstPointer)
                     | mir::CastKind::Misc
diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs
index d09ab5fa3e85a..e28df0f4c2fdd 100644
--- a/compiler/rustc_const_eval/src/interpret/cast.rs
+++ b/compiler/rustc_const_eval/src/interpret/cast.rs
@@ -3,7 +3,7 @@ use std::convert::TryFrom;
 
 use rustc_apfloat::ieee::{Double, Single};
 use rustc_apfloat::{Float, FloatConvert};
-use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar};
+use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar, ScalarMaybeUninit};
 use rustc_middle::mir::CastKind;
 use rustc_middle::ty::adjustment::PointerCast;
 use rustc_middle::ty::layout::{IntegerExt, LayoutOf, TyAndLayout};
@@ -305,22 +305,37 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         source_ty: Ty<'tcx>,
         cast_ty: Ty<'tcx>,
     ) -> InterpResult<'tcx> {
+        // We *could* forward `data` without even checking that it is initialized, but for now,
+        // let's only allow casting properly initialized pointers.
+        let (data, old_meta) = match *self.read_immediate(src)? {
+            // If the input ptr is thin, use `Uninit` for the old metadata.
+            // `unsize_just_metadata` knows how to handle that.
+            Immediate::Scalar(data) => (data.check_init()?, ScalarMaybeUninit::Uninit),
+            Immediate::ScalarPair(data, meta) => (data.check_init()?, meta),
+        };
+
+        let new_meta = self.unsize_just_metadata(old_meta, source_ty, cast_ty)?;
+        self.write_immediate(Immediate::ScalarPair(data.into(), new_meta.into()), dest)
+    }
+
+    fn unsize_just_metadata(
+        &mut self,
+        src_meta: ScalarMaybeUninit<M::PointerTag>,
+        // The pointee types
+        source_ty: Ty<'tcx>,
+        cast_ty: Ty<'tcx>,
+    ) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
         // A<Struct> -> A<Trait> conversion
         let (src_pointee_ty, dest_pointee_ty) =
             self.tcx.struct_lockstep_tails_erasing_lifetimes(source_ty, cast_ty, self.param_env);
 
-        match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) {
+        Ok(match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) {
             (&ty::Array(_, length), &ty::Slice(_)) => {
-                let ptr = self.read_immediate(src)?.to_scalar()?;
-                // u64 cast is from usize to u64, which is always good
-                let val =
-                    Immediate::new_slice(ptr, length.eval_usize(*self.tcx, self.param_env), self);
-                self.write_immediate(val, dest)
+                Scalar::from_machine_usize(length.eval_usize(*self.tcx, self.param_env), self)
             }
             (&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
-                let val = self.read_immediate(src)?;
                 if data_a.principal_def_id() == data_b.principal_def_id() {
-                    return self.write_immediate(*val, dest);
+                    return src_meta.check_init();
                 }
                 // trait upcasting coercion
                 let vptr_entry_idx = self.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
@@ -330,27 +345,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
                 if let Some(entry_idx) = vptr_entry_idx {
                     let entry_idx = u64::try_from(entry_idx).unwrap();
-                    let (old_data, old_vptr) = val.to_scalar_pair()?;
+                    let old_vptr = src_meta.check_init()?;
                     let old_vptr = self.scalar_to_ptr(old_vptr)?;
                     let new_vptr = self
                         .read_new_vtable_after_trait_upcasting_from_vtable(old_vptr, entry_idx)?;
-                    self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
+                    Scalar::from_maybe_pointer(new_vptr, self)
                 } else {
-                    self.write_immediate(*val, dest)
+                    src_meta.check_init()?
                 }
             }
             (_, &ty::Dynamic(ref data, _)) => {
                 // Initial cast from sized to dyn trait
                 let vtable = self.get_vtable(src_pointee_ty, data.principal())?;
-                let ptr = self.read_immediate(src)?.to_scalar()?;
-                let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
-                self.write_immediate(val, dest)
+                Scalar::from_maybe_pointer(vtable, &*self.tcx)
             }
 
             _ => {
-                span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", src.layout.ty, cast_ty)
+                span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", source_ty, cast_ty)
             }
-        }
+        })
     }
 
     fn unsize_into(
@@ -360,16 +373,40 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         dest: &PlaceTy<'tcx, M::PointerTag>,
     ) -> InterpResult<'tcx> {
         trace!("Unsizing {:?} of type {} into {:?}", *src, src.layout.ty, cast_ty.ty);
-        match (&src.layout.ty.kind(), &cast_ty.ty.kind()) {
-            (&ty::Ref(_, s, _), &ty::Ref(_, c, _) | &ty::RawPtr(TypeAndMut { ty: c, .. }))
-            | (&ty::RawPtr(TypeAndMut { ty: s, .. }), &ty::RawPtr(TypeAndMut { ty: c, .. })) => {
+        let typed_metadata = self.tcx.lang_items().typed_metadata();
+        match (src.layout.ty.kind(), cast_ty.ty.kind()) {
+            (ty::Ref(_, s, _), ty::Ref(_, c, _) | ty::RawPtr(TypeAndMut { ty: c, .. }))
+            | (ty::RawPtr(TypeAndMut { ty: s, .. }), ty::RawPtr(TypeAndMut { ty: c, .. })) => {
                 self.unsize_into_ptr(src, dest, *s, *c)
             }
-            (&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
+            (ty::Adt(def_a, _), ty::Adt(def_b, _)) if def_a.is_box() || def_b.is_box() => {
                 assert_eq!(def_a, def_b);
-
+                if !def_a.is_box() || !def_b.is_box() {
+                    span_bug!(
+                        self.cur_span(),
+                        "invalid unsizing between {:?} -> {:?}",
+                        src.layout.ty,
+                        cast_ty.ty
+                    );
+                }
+                self.unsize_into_ptr(src, dest, src.layout.ty.boxed_ty(), cast_ty.ty.boxed_ty())
+            }
+            (ty::Adt(def_a, substs_a), ty::Adt(def_b, substs_b))
+                if def_a == def_b && Some(def_a.did()) == typed_metadata =>
+            {
+                // unsizing of TypedMetadata container
+                // Example: `TypedMetadata<T>` -> `TypedMetadata<dyn Trait>`
+                let a_pointee = substs_a.type_at(0);
+                let b_pointee = substs_b.type_at(0);
+                let src_field = self.operand_field(src, 0)?;
+                let src = self.read_immediate(&src_field)?.to_scalar_or_uninit();
+                let dst_field = self.place_field(dest, 0)?;
+                let new_meta = self.unsize_just_metadata(src, a_pointee, b_pointee)?;
+                self.write_scalar(new_meta, &dst_field)
+            }
+            (ty::Adt(def_a, _), ty::Adt(def_b, _)) if def_a == def_b => {
                 // unsizing of generic struct with pointer fields
-                // Example: `Arc<T>` -> `Arc<Trait>`
+                // Example: `Arc<T>` -> `Arc<dyn Trait>`
                 // here we need to increase the size of every &T thin ptr field to a fat ptr
                 for i in 0..src.layout.fields.count() {
                     let cast_ty_field = cast_ty.field(self, i);
diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs
index b0bfac8e1f5ec..f4da36cd9ad70 100644
--- a/compiler/rustc_hir/src/lang_items.rs
+++ b/compiler/rustc_hir/src/lang_items.rs
@@ -182,6 +182,7 @@ language_item_table! {
     PointeeTrait,            sym::pointee_trait,       pointee_trait,              Target::Trait,          GenericRequirement::None;
     Metadata,                sym::metadata_type,       metadata_type,              Target::AssocTy,        GenericRequirement::None;
     DynMetadata,             sym::dyn_metadata,        dyn_metadata,               Target::Struct,         GenericRequirement::None;
+    TypedMetadata,           sym::typed_metadata,      typed_metadata,             Target::Struct,         GenericRequirement::Exact(1);
 
     Freeze,                  sym::freeze,              freeze_trait,               Target::Trait,          GenericRequirement::Exact(0);
 
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index 0bea2a10da896..0f5a5f673ba9a 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -215,7 +215,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
     impl_polarity => { table_direct }
     impl_defaultness => { table_direct }
     constness => { table_direct }
-    coerce_unsized_info => { table }
+    coerce_unsized_kind => { table }
     mir_const_qualif => { table }
     rendered_const => { table }
     asyncness => { table_direct }
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 75286b8906871..28c981318461b 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -1482,9 +1482,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
                     // if this is an impl of `CoerceUnsized`, create its
                     // "unsized info", else just store None
                     if Some(trait_ref.def_id) == self.tcx.lang_items().coerce_unsized_trait() {
-                        let coerce_unsized_info =
-                            self.tcx.at(item.span).coerce_unsized_info(def_id);
-                        record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info);
+                        let coerce_unsized_kind =
+                            self.tcx.at(item.span).coerce_unsized_kind(def_id);
+                        record!(self.tables.coerce_unsized_kind[def_id] <- coerce_unsized_kind);
                     }
                 }
 
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index a58c0e68ee38c..540a55086696a 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -368,7 +368,7 @@ define_tables! {
     is_intrinsic: Table<DefIndex, ()>,
     impl_defaultness: Table<DefIndex, hir::Defaultness>,
     // FIXME(eddyb) perhaps compute this on the fly if cheap enough?
-    coerce_unsized_info: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedInfo>>,
+    coerce_unsized_kind: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedKind>>,
     mir_const_qualif: Table<DefIndex, LazyValue<mir::ConstQualifs>>,
     rendered_const: Table<DefIndex, LazyValue<String>>,
     asyncness: Table<DefIndex, hir::IsAsync>,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index a1065eef8509d..98510c144fc1d 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -835,8 +835,8 @@ rustc_queries! {
     }
 
     /// Caches `CoerceUnsized` kinds for impls on custom types.
-    query coerce_unsized_info(key: DefId) -> ty::adjustment::CoerceUnsizedInfo {
-        desc { |tcx| "computing CoerceUnsized info for `{}`", tcx.def_path_str(key) }
+    query coerce_unsized_kind(key: DefId) -> ty::adjustment::CoerceUnsizedKind {
+        desc { |tcx| "computing CoerceUnsized kind for `{}`", tcx.def_path_str(key) }
         cache_on_disk_if { key.is_local() }
         separate_provide_extern
     }
diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs
index d9332f6896af6..f40bdc0965ca5 100644
--- a/compiler/rustc_middle/src/ty/adjustment.rs
+++ b/compiler/rustc_middle/src/ty/adjustment.rs
@@ -177,20 +177,15 @@ pub enum AutoBorrow<'tcx> {
 /// Information for `CoerceUnsized` impls, storing information we
 /// have computed about the coercion.
 ///
-/// This struct can be obtained via the `coerce_impl_info` query.
+/// This enum can be obtained via the `coerce_unsized_kind` query.
 /// Demanding this struct also has the side-effect of reporting errors
 /// for inappropriate impls.
-#[derive(Clone, Copy, TyEncodable, TyDecodable, Debug, HashStable)]
-pub struct CoerceUnsizedInfo {
-    /// If this is a "custom coerce" impl, then what kind of custom
-    /// coercion is it? This applies to impls of `CoerceUnsized` for
-    /// structs, primarily, where we store a bit of info about which
-    /// fields need to be coerced.
-    pub custom_kind: Option<CustomCoerceUnsized>,
-}
-
-#[derive(Clone, Copy, TyEncodable, TyDecodable, Debug, HashStable)]
-pub enum CustomCoerceUnsized {
-    /// Records the index of the field being coerced.
+#[derive(Clone, Copy, PartialEq, TyEncodable, TyDecodable, Debug, HashStable)]
+pub enum CoerceUnsizedKind {
+    /// Coercion of a raw pointer or ref.
+    Ptr,
+    /// Coercion of a struct. Records the index of the field being coerced.
     Struct(usize),
+    /// Coercion of the pointee metadata directly.
+    TypedMetadata,
 }
diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs
index 54ba9e84fdb7b..24df55b02b1fc 100644
--- a/compiler/rustc_middle/src/ty/parameterized.rs
+++ b/compiler/rustc_middle/src/ty/parameterized.rs
@@ -59,7 +59,7 @@ trivially_parameterized_over_tcx! {
     ty::ReprOptions,
     ty::TraitDef,
     ty::Visibility,
-    ty::adjustment::CoerceUnsizedInfo,
+    ty::adjustment::CoerceUnsizedKind,
     ty::fast_reject::SimplifiedTypeGen<DefId>,
     rustc_ast::Attribute,
     rustc_ast::MacArgs,
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index b9936e35b772e..3b8e373b369c1 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -191,7 +191,7 @@ use rustc_middle::mir::interpret::{ErrorHandled, GlobalAlloc, Scalar};
 use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
 use rustc_middle::mir::visit::Visitor as MirVisitor;
 use rustc_middle::mir::{self, Local, Location};
-use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCast};
+use rustc_middle::ty::adjustment::{CoerceUnsizedKind, PointerCast};
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
 use rustc_middle::ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable, VtblEntry};
@@ -1106,21 +1106,39 @@ fn find_vtable_types_for_unsizing<'tcx>(
         (&ty::Adt(source_adt_def, source_substs), &ty::Adt(target_adt_def, target_substs)) => {
             assert_eq!(source_adt_def, target_adt_def);
 
-            let CustomCoerceUnsized::Struct(coerce_index) =
-                crate::custom_coerce_unsize_info(tcx, source_ty, target_ty);
+            match crate::custom_coerce_unsize_info(tcx, source_ty, target_ty) {
+                CoerceUnsizedKind::Struct(coerce_index) => {
+                    let source_fields = &source_adt_def.non_enum_variant().fields;
+                    let target_fields = &target_adt_def.non_enum_variant().fields;
 
-            let source_fields = &source_adt_def.non_enum_variant().fields;
-            let target_fields = &target_adt_def.non_enum_variant().fields;
-
-            assert!(
-                coerce_index < source_fields.len() && source_fields.len() == target_fields.len()
-            );
+                    assert!(
+                        coerce_index < source_fields.len()
+                            && source_fields.len() == target_fields.len()
+                    );
 
-            find_vtable_types_for_unsizing(
-                tcx,
-                source_fields[coerce_index].ty(tcx, source_substs),
-                target_fields[coerce_index].ty(tcx, target_substs),
-            )
+                    find_vtable_types_for_unsizing(
+                        tcx,
+                        source_fields[coerce_index].ty(tcx, source_substs),
+                        target_fields[coerce_index].ty(tcx, target_substs),
+                    )
+                }
+                CoerceUnsizedKind::TypedMetadata => {
+                    let typed_metadata = tcx.lang_items().typed_metadata();
+                    if !typed_metadata.is_some() || source_adt_def.did() != typed_metadata.unwrap()
+                    {
+                        bug!(
+                            "find_vtable_types_for_unsizing: tried to treat {:?} as `TypedMetadata`",
+                            source_ty
+                        );
+                    }
+                    ptr_vtable(source_substs.type_at(0), target_substs.type_at(0))
+                }
+                CoerceUnsizedKind::Ptr => bug!(
+                    "find_vtable_types_for_unsizing: invalid coercion {:?} -> {:?} with Adt type but Ptr kind",
+                    source_ty,
+                    target_ty
+                ),
+            }
         }
         _ => bug!(
             "find_vtable_types_for_unsizing: invalid coercion {:?} -> {:?}",
diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs
index ef4560b5ec48e..67252bb8b994e 100644
--- a/compiler/rustc_monomorphize/src/lib.rs
+++ b/compiler/rustc_monomorphize/src/lib.rs
@@ -11,7 +11,7 @@ extern crate rustc_middle;
 
 use rustc_hir::lang_items::LangItem;
 use rustc_middle::traits;
-use rustc_middle::ty::adjustment::CustomCoerceUnsized;
+use rustc_middle::ty::adjustment::CoerceUnsizedKind;
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{self, Ty, TyCtxt};
 
@@ -24,7 +24,7 @@ fn custom_coerce_unsize_info<'tcx>(
     tcx: TyCtxt<'tcx>,
     source_ty: Ty<'tcx>,
     target_ty: Ty<'tcx>,
-) -> CustomCoerceUnsized {
+) -> CoerceUnsizedKind {
     let def_id = tcx.require_lang_item(LangItem::CoerceUnsized, None);
 
     let trait_ref = ty::Binder::dummy(ty::TraitRef {
@@ -36,7 +36,7 @@ fn custom_coerce_unsize_info<'tcx>(
         Ok(traits::ImplSource::UserDefined(traits::ImplSourceUserDefinedData {
             impl_def_id,
             ..
-        })) => tcx.coerce_unsized_info(impl_def_id).custom_kind.unwrap(),
+        })) => tcx.coerce_unsized_kind(impl_def_id),
         impl_source => {
             bug!("invalid `CoerceUnsized` impl_source: {:?}", impl_source);
         }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 4e28d2b6001ed..012ff403eb97f 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1466,6 +1466,7 @@ symbols! {
         type_length_limit,
         type_macros,
         type_name,
+        typed_metadata,
         u128,
         u16,
         u32,
diff --git a/compiler/rustc_typeck/src/coherence/builtin.rs b/compiler/rustc_typeck/src/coherence/builtin.rs
index c647c2a4c1baa..64005c546b244 100644
--- a/compiler/rustc_typeck/src/coherence/builtin.rs
+++ b/compiler/rustc_typeck/src/coherence/builtin.rs
@@ -10,7 +10,7 @@ use rustc_hir::ItemKind;
 use rustc_infer::infer;
 use rustc_infer::infer::outlives::env::OutlivesEnvironment;
 use rustc_infer::infer::TyCtxtInferExt;
-use rustc_middle::ty::adjustment::CoerceUnsizedInfo;
+use rustc_middle::ty::adjustment::CoerceUnsizedKind;
 use rustc_middle::ty::{self, suggest_constraining_type_params, Ty, TyCtxt, TypeFoldable};
 use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
 use rustc_trait_selection::traits::misc::{can_type_implement_copy, CopyImplementationError};
@@ -195,7 +195,7 @@ fn visit_implementation_of_coerce_unsized<'tcx>(tcx: TyCtxt<'tcx>, impl_did: Loc
     // errors; other parts of the code may demand it for the info of
     // course.
     let span = tcx.def_span(impl_did);
-    tcx.at(span).coerce_unsized_info(impl_did);
+    tcx.at(span).coerce_unsized_kind(impl_did);
 }
 
 fn visit_implementation_of_dispatch_from_dyn<'tcx>(tcx: TyCtxt<'tcx>, impl_did: LocalDefId) {
@@ -363,8 +363,8 @@ fn visit_implementation_of_dispatch_from_dyn<'tcx>(tcx: TyCtxt<'tcx>, impl_did:
     })
 }
 
-pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUnsizedInfo {
-    debug!("compute_coerce_unsized_info(impl_did={:?})", impl_did);
+pub fn coerce_unsized_kind<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUnsizedKind {
+    debug!("compute_coerce_unsized_kind(impl_did={:?})", impl_did);
 
     // this provider should only get invoked for local def-ids
     let impl_did = impl_did.expect_local();
@@ -376,6 +376,8 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
         tcx.sess.fatal(&format!("`CoerceUnsized` implementation {}", err));
     });
 
+    let typed_metadata = tcx.lang_items().typed_metadata();
+
     let source = tcx.type_of(impl_did);
     let trait_ref = tcx.impl_trait_ref(impl_did).unwrap();
     assert_eq!(trait_ref.def_id, coerce_unsized_trait);
@@ -385,7 +387,7 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
     let param_env = tcx.param_env(impl_did);
     assert!(!source.has_escaping_bound_vars());
 
-    let err_info = CoerceUnsizedInfo { custom_kind: None };
+    let err_info = CoerceUnsizedKind::Ptr;
 
     debug!("visit_implementation_of_coerce_unsized: {:?} -> {:?} (free)", source, target);
 
@@ -405,7 +407,7 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
                     )
                     .emit();
             }
-            (mt_a.ty, mt_b.ty, unsize_trait, None)
+            (mt_a.ty, mt_b.ty, unsize_trait, CoerceUnsizedKind::Ptr)
         };
         let (source, target, trait_def_id, kind) = match (source.kind(), target.kind()) {
             (&ty::Ref(r_a, ty_a, mutbl_a), &ty::Ref(r_b, ty_b, mutbl_b)) => {
@@ -424,6 +426,17 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
                 check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ptr(ty))
             }
 
+            (&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b))
+                if Some(def_a.did()) == typed_metadata && Some(def_b.did()) == typed_metadata =>
+            {
+                (
+                    substs_a.type_at(0),
+                    substs_b.type_at(0),
+                    unsize_trait,
+                    CoerceUnsizedKind::TypedMetadata,
+                )
+            }
+
             (&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b))
                 if def_a.is_struct() && def_b.is_struct() =>
             {
@@ -566,8 +579,7 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
                 }
 
                 let (i, a, b) = diff_fields[0];
-                let kind = ty::adjustment::CustomCoerceUnsized::Struct(i);
-                (a, b, coerce_unsized_trait, Some(kind))
+                (a, b, coerce_unsized_trait, CoerceUnsizedKind::Struct(i))
             }
 
             _ => {
@@ -608,6 +620,6 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
         let outlives_env = OutlivesEnvironment::new(param_env);
         infcx.resolve_regions_and_report_errors(impl_did.to_def_id(), &outlives_env);
 
-        CoerceUnsizedInfo { custom_kind: kind }
+        kind
     })
 }
diff --git a/compiler/rustc_typeck/src/coherence/mod.rs b/compiler/rustc_typeck/src/coherence/mod.rs
index 447ec87f30264..ff3b171686756 100644
--- a/compiler/rustc_typeck/src/coherence/mod.rs
+++ b/compiler/rustc_typeck/src/coherence/mod.rs
@@ -143,7 +143,7 @@ fn enforce_empty_impls_for_marker_traits(
 }
 
 pub fn provide(providers: &mut Providers) {
-    use self::builtin::coerce_unsized_info;
+    use self::builtin::coerce_unsized_kind;
     use self::inherent_impls::{crate_incoherent_impls, crate_inherent_impls, inherent_impls};
     use self::inherent_impls_overlap::crate_inherent_impls_overlap_check;
     use self::orphan::orphan_check_impl;
@@ -154,7 +154,7 @@ pub fn provide(providers: &mut Providers) {
         crate_incoherent_impls,
         inherent_impls,
         crate_inherent_impls_overlap_check,
-        coerce_unsized_info,
+        coerce_unsized_kind,
         orphan_check_impl,
         ..*providers
     };
diff --git a/library/core/src/ops/unsize.rs b/library/core/src/ops/unsize.rs
index a920b9165c18e..843d1014b470f 100644
--- a/library/core/src/ops/unsize.rs
+++ b/library/core/src/ops/unsize.rs
@@ -1,4 +1,6 @@
 use crate::marker::Unsize;
+#[cfg(not(bootstrap))]
+use crate::ptr::TypedMetadata;
 
 /// Trait that indicates that this is a pointer or a wrapper for one,
 /// where unsizing can be performed on the pointee.
@@ -68,6 +70,10 @@ impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *mut T {}
 #[unstable(feature = "coerce_unsized", issue = "27732")]
 impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *const T {}
 
+#[cfg(not(bootstrap))]
+#[unstable(feature = "coerce_unsized", issue = "27732")]
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<TypedMetadata<U>> for TypedMetadata<T> {}
+
 /// `DispatchFromDyn` is used in the implementation of object safety checks (specifically allowing
 /// arbitrary self types), to guarantee that a method's receiver type can be dispatched on.
 ///
diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs
index 287ae69acd198..2c43a122196a5 100644
--- a/library/core/src/ptr/metadata.rs
+++ b/library/core/src/ptr/metadata.rs
@@ -59,6 +59,30 @@ pub trait Pointee {
     type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
 }
 
+/// Just the metadata associated with some pointee.
+///
+/// This reattaches the pointee type to the metadata to allow pointee unsizing
+/// to happen.
+///
+/// # Example
+///
+/// ```rust
+/// #![feature(ptr_metadata)]
+///
+/// use std::ptr::TypedMetadata;
+///
+/// // The metadata for any sized type is ()
+/// let sized_metadata: TypedMetadata<[u8; 5]> = TypedMetadata::<[u8; 5]>(());
+///
+/// // But we can coerce the pointee from an array to a slice
+/// let unsized_metadata: TypedMetadata<[u8]> = sized_metadata;
+///
+/// // Now the metadata is the slice length
+/// assert_eq!(unsized_metadata.0, 5);
+/// ```
+#[cfg_attr(not(bootstrap), lang = "typed_metadata")]
+pub struct TypedMetadata<T: ?Sized>(pub <T as Pointee>::Metadata);
+
 /// Pointers to types implementing this trait alias are “thin”.
 ///
 /// This includes statically-`Sized` types and `extern` types.
@@ -221,6 +245,15 @@ impl<Dyn: ?Sized> fmt::Debug for DynMetadata<Dyn> {
     }
 }
 
+impl<T: ?Sized> fmt::Debug for TypedMetadata<T>
+where
+    <T as Pointee>::Metadata: fmt::Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_tuple("TypedMetadata").field(&self.0 as &dyn fmt::Debug).finish()
+    }
+}
+
 // Manual impls needed to avoid `Dyn: $Trait` bounds.
 
 impl<Dyn: ?Sized> Unpin for DynMetadata<Dyn> {}
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index 8fb0bfbe2e31b..8b7be44e2fe0c 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -392,7 +392,9 @@ pub use crate::intrinsics::write_bytes;
 mod metadata;
 pub(crate) use metadata::PtrRepr;
 #[unstable(feature = "ptr_metadata", issue = "81513")]
-pub use metadata::{from_raw_parts, from_raw_parts_mut, metadata, DynMetadata, Pointee, Thin};
+pub use metadata::{
+    from_raw_parts, from_raw_parts_mut, metadata, DynMetadata, Pointee, Thin, TypedMetadata,
+};
 
 mod non_null;
 #[stable(feature = "nonnull", since = "1.25.0")]
diff --git a/src/test/ui/coercion/coerce-just-metadata.rs b/src/test/ui/coercion/coerce-just-metadata.rs
new file mode 100644
index 0000000000000..a902bfa2a2d28
--- /dev/null
+++ b/src/test/ui/coercion/coerce-just-metadata.rs
@@ -0,0 +1,26 @@
+// build-pass
+#![feature(ptr_metadata)]
+
+use std::ptr::TypedMetadata;
+
+struct Struct;
+trait Trait {}
+
+impl Trait for Struct {}
+
+fn main() {
+    // array -> slice
+    let sized: TypedMetadata<[u8; 5]> = TypedMetadata(());
+    let _: TypedMetadata<[u8]> = sized;
+
+    // sized -> dyn
+    let sized: TypedMetadata<Struct> = TypedMetadata(());
+    let dyn_trait: TypedMetadata<dyn Trait + Sync> = sized;
+
+    // dyn -> dyn
+    let _: TypedMetadata<dyn Trait> = dyn_trait;
+
+    // identity
+    let sized: TypedMetadata<Struct> = TypedMetadata(());
+    let _ = sized as TypedMetadata<Struct>;
+}
diff --git a/src/test/ui/coercion/just-metadata-bad-coercions.rs b/src/test/ui/coercion/just-metadata-bad-coercions.rs
new file mode 100644
index 0000000000000..dbfd7f6ee42a9
--- /dev/null
+++ b/src/test/ui/coercion/just-metadata-bad-coercions.rs
@@ -0,0 +1,18 @@
+#![feature(ptr_metadata)]
+
+use std::ptr::TypedMetadata;
+
+struct Struct;
+trait Trait {}
+
+fn main() {
+    let struct_metadata: TypedMetadata<Struct> = TypedMetadata(());
+
+    let _: TypedMetadata<dyn Trait> = struct_metadata; //~ ERROR `Struct: Trait` is not satisfied
+    let _: TypedMetadata<[Struct]> = struct_metadata; //~ ERROR mismatched types
+
+    let array_metadata: TypedMetadata<[u8; 4]> = TypedMetadata(());
+
+    let _: TypedMetadata<[u32]> = array_metadata; //~ ERROR mismatched types
+    let _: TypedMetadata<Struct> = array_metadata; //~ ERROR mismatched types
+}
diff --git a/src/test/ui/coercion/just-metadata-bad-coercions.stderr b/src/test/ui/coercion/just-metadata-bad-coercions.stderr
new file mode 100644
index 0000000000000..f48c41281589a
--- /dev/null
+++ b/src/test/ui/coercion/just-metadata-bad-coercions.stderr
@@ -0,0 +1,45 @@
+error[E0308]: mismatched types
+  --> $DIR/just-metadata-bad-coercions.rs:12:38
+   |
+LL |     let _: TypedMetadata<[Struct]> = struct_metadata;
+   |            -----------------------   ^^^^^^^^^^^^^^^ expected slice, found struct `Struct`
+   |            |
+   |            expected due to this
+   |
+   = note: expected struct `TypedMetadata<[Struct]>`
+              found struct `TypedMetadata<Struct>`
+
+error[E0277]: the trait bound `Struct: Trait` is not satisfied
+  --> $DIR/just-metadata-bad-coercions.rs:11:39
+   |
+LL |     let _: TypedMetadata<dyn Trait> = struct_metadata;
+   |                                       ^^^^^^^^^^^^^^^ the trait `Trait` is not implemented for `Struct`
+   |
+   = note: required for the cast from `Struct` to the object type `dyn Trait`
+
+error[E0308]: mismatched types
+  --> $DIR/just-metadata-bad-coercions.rs:16:35
+   |
+LL |     let _: TypedMetadata<[u32]> = array_metadata;
+   |            --------------------   ^^^^^^^^^^^^^^ expected slice `[u32]`, found array `[u8; 4]`
+   |            |
+   |            expected due to this
+   |
+   = note: expected struct `TypedMetadata<[u32]>`
+              found struct `TypedMetadata<[u8; 4]>`
+
+error[E0308]: mismatched types
+  --> $DIR/just-metadata-bad-coercions.rs:17:36
+   |
+LL |     let _: TypedMetadata<Struct> = array_metadata;
+   |            ---------------------   ^^^^^^^^^^^^^^ expected struct `Struct`, found array `[u8; 4]`
+   |            |
+   |            expected due to this
+   |
+   = note: expected struct `TypedMetadata<Struct>`
+              found struct `TypedMetadata<[u8; 4]>`
+
+error: aborting due to 4 previous errors
+
+Some errors have detailed explanations: E0277, E0308.
+For more information about an error, try `rustc --explain E0277`.
diff --git a/src/test/ui/coercion/silly-box.rs b/src/test/ui/coercion/silly-box.rs
new file mode 100644
index 0000000000000..9c459a90cff3d
--- /dev/null
+++ b/src/test/ui/coercion/silly-box.rs
@@ -0,0 +1,27 @@
+// build-pass
+#![feature(coerce_unsized, ptr_metadata, unsize)]
+
+use std::marker::Unsize;
+use std::ops::CoerceUnsized;
+use std::ptr::{NonNull, TypedMetadata};
+
+struct SillyBox<T: ?Sized> {
+    data: NonNull<()>,
+    meta: TypedMetadata<T>,
+}
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<SillyBox<U>> for SillyBox<T> {}
+
+fn do_unsize_slice(it: SillyBox<[u8; 5]>) -> SillyBox<[u8]> {
+    it
+}
+
+struct S;
+trait Trait {}
+impl Trait for S {}
+
+fn do_unsize_trait(it: SillyBox<S>) -> SillyBox<dyn Trait> {
+    it
+}
+
+fn main() {}