Change:
The following ICEs (example by shows This means that is wrong. It might be possible to also trigger this without nightly features through const_prop.
Baring a way to do the proper structural subtyping check, the only solution I see is to go back to comparing layouts in all cases (as a fallback after types mismatch) -- but does not like this, as far as I recall. Any other proposals?

Discussion:
sign_extend, truncate, AllocId, FrameInfo, GlobalId, InterpResult, Pointer, Scalar, }; use rustc_middle::ty::layout::{self, TyAndLayout}; <del> use rustc_middle::ty::query::TyCtxtAt; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable}; </del> <ins> use rustc_middle::ty::{ self, fold::BottomUpFolder, query::TyCtxtAt, subst::SubstsRef, Ty, TyCtxt, TypeFoldable, }; </ins> use rustc_span::source_map::DUMMY_SP; <del> use rustc_target::abi::{Abi, Align, HasDataLayout, LayoutOf, Size, TargetDataLayout}; </del> <ins> use rustc_target::abi::{Align, HasDataLayout, LayoutOf, Size, TargetDataLayout}; </ins> use super::{ Immediate, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, OpTy, Operand, Place, PlaceTy,

/// Test if it is valid for a MIR assignment to assign `src`-typed place to `dest`-typed value. /// This test should be symmetric, as it is primarily about layout compatibility. pub(super) fn mir_assign_valid_types<'tcx>( <ins> tcx: TyCtxt<'tcx>, </ins> src: TyAndLayout<'tcx>, dest: TyAndLayout<'tcx>, ) -> bool {

// Equal types, all is good. return true; } <del> // Type-changing assignments can happen for (at least) two reasons: // - `&mut T` -> `&T` gets optimized from a reborrow to a mere assignment. // - Subtyping is used. While all normal lifetimes are erased, higher-ranked lifetime //   bounds are still around and can lead to type differences. // There is no good way to check the latter, so we compare layouts instead -- but only // for values with `Scalar`/`ScalarPair` abi. // FIXME: Do something more accurate, type-based. match &src.abi { Abi::Scalar(..) | Abi::ScalarPair(..) => src.layout == dest.layout, _ => false, </del> <ins> if src.layout != dest.layout { // Layout differs, definitely not equal. // We do this here because Miri would *do the wrong thing* if we allowed layout-changing // assignments. return false; </ins> } <ins> // Type-changing assignments can happen for (at least) two reasons: // 1. `&mut T` -> `&T` gets optimized from a reborrow to a mere assignment. // 2. Subtyping is used. While all normal lifetimes are erased, higher-ranked types //    with their late-bound lifetimes are still around and can lead to type differences. // Normalize both of them away. let normalize = |ty: Ty<'tcx>| { ty.fold_with(&mut BottomUpFolder { tcx, // Normalize all references to immutable. ty_op: |ty| match ty.kind { ty::Ref(_, pointee, _) => tcx.mk_imm_ref(tcx.lifetimes.re_erased, pointee), _ => ty, }, // We just erase all late-bound lifetimes, but this is not fully correct (FIXME): // lifetimes in invariant positions could matter (e.g. through associated types). // We rely on the fact that layout was confirmed to be equal above. lt_op: |_| tcx.lifetimes.re_erased, // Leave consts unchanged. ct_op: |ct| ct, }) }; normalize(src.ty) == normalize(dest.ty) </ins> } /// Use the already known layout if given (but sanity check in debug mode), /// or compute the layout. #[cfg_attr(not(debug_assertions), inline(always))] pub(super) fn from_known_layout<'tcx>( <ins> tcx: TyCtxt<'tcx>, </ins> known_layout: Option<TyAndLayout<'tcx>>, compute: impl FnOnce() -> InterpResult<'tcx, TyAndLayout<'tcx>>, ) -> InterpResult<'tcx, TyAndLayout<'tcx>> {

if cfg!(debug_assertions) { let check_layout = compute()?; assert!( <del> mir_assign_valid_types(check_layout, known_layout), </del> <ins> mir_assign_valid_types(tcx, check_layout, known_layout), </ins> "expected type differs from actual type.nexpected: {:?}nactual: {:?}", known_layout.ty, check_layout.ty,

// have to support that case (mostly by skipping all caching). match frame.locals.get(local).and_then(|state| state.layout.get()) { None => { <del> let layout = from_known_layout(layout, || { </del> <ins> let layout = from_known_layout(self.tcx.tcx, layout, || { </ins> let local_ty = frame.body.local_decls[local].ty; let local_ty = self.subst_from_frame_and_normalize_erasing_regions(frame, local_ty);

ty::ConstKind::Value(val_val) => val_val, }; // Other cases need layout. <del> let layout = from_known_layout(layout, || self.layout_of(val.ty))?; </del> <ins> let layout = from_known_layout(self.tcx.tcx, layout, || self.layout_of(val.ty))?; </ins> let op = match val_val { ConstValue::ByRef { alloc, offset } => { let id = self.tcx.alloc_map.lock().create_memory_alloc(alloc);

// We do NOT compare the types for equality, because well-typed code can // actually "transmute" `&mut T` to `&T` in an assignment without a cast. assert!( <del> mir_assign_valid_types(src.layout, dest.layout), </del> <ins> mir_assign_valid_types(self.tcx.tcx, src.layout, dest.layout), </ins> "type mismatch when copying!nsrc: {:?},ndest: {:?}", src.layout.ty, dest.layout.ty,

src: OpTy<'tcx, M::PointerTag>, dest: PlaceTy<'tcx, M::PointerTag>, ) -> InterpResult<'tcx> { <del> if mir_assign_valid_types(src.layout, dest.layout) { </del> <ins> if mir_assign_valid_types(self.tcx.tcx, src.layout, dest.layout) { </ins> // Fast path: Just use normal `copy_op` return self.copy_op(src, dest); }

<ins> // check-pass #![feature(const_fn)] const fn nested(x: (for<'a> fn(&'a ()), String)) -> (fn(&'static ()), String) { x } pub const TEST: (fn(&'static ()), String) = nested((|_x| (), String::new())); fn main() {} </ins>