diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs
index 7b993279f18..b7ffb4a16fc 100644
--- a/compiler/rustc_const_eval/src/interpret/terminator.rs
+++ b/compiler/rustc_const_eval/src/interpret/terminator.rs
@@ -373,7 +373,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         if let (Some(caller), Some(callee)) = (pointee_ty(caller.ty)?, pointee_ty(callee.ty)?) {
             // This is okay if they have the same metadata type.
             let meta_ty = |ty: Ty<'tcx>| {
-                let (meta, only_if_sized) = ty.ptr_metadata_ty(*self.tcx, |ty| ty);
+                // Even if `ty` is normalized, the search for the unsized tail will project
+                // to fields, which can yield non-normalized types. So we need to provide a
+                // normalization function.
+                let normalize = |ty| self.tcx.normalize_erasing_regions(self.param_env, ty);
+                let (meta, only_if_sized) = ty.ptr_metadata_ty(*self.tcx, normalize);
                 assert!(
                     !only_if_sized,
                     "there should be no more 'maybe has that metadata' types during interpretation"
diff --git a/src/tools/miri/tests/pass/issues/issue-miri-3282-struct-tail-normalize.rs b/src/tools/miri/tests/pass/issues/issue-miri-3282-struct-tail-normalize.rs
new file mode 100644
index 00000000000..2a31df83843
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-miri-3282-struct-tail-normalize.rs
@@ -0,0 +1,20 @@
+// regression test for an ICE: https://github.com/rust-lang/miri/issues/3282
+
+trait Id {
+    type Assoc: ?Sized;
+}
+
+impl<T: ?Sized> Id for T {
+    type Assoc = T;
+}
+
+#[repr(transparent)]
+struct Foo<T: ?Sized> {
+    field: <T as Id>::Assoc,
+}
+
+fn main() {
+    let x = unsafe { std::mem::transmute::<fn(&str), fn(&Foo<str>)>(|_| ()) };
+    let foo: &Foo<str> = unsafe { &*("uwu" as *const str as *const Foo<str>) };
+    x(foo);
+}