Change:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
Sorry you hit this ICE. This should be fixed by , which landed 15 minutes ago, though will probably not make it into nightly until two days from now (10 April 2024).
Actually, it's not yet fixed. I have a follow-up PR that will fix this :facepalm:

Discussion:
if place.local == ty::CAPTURE_STRUCT_LOCAL && let Some((&mir::ProjectionElem::Field(idx, _), projection)) = place.projection.split_first() <del> && let Some(&(remapped_idx, remapped_ty, needs_deref, additional_projections)) = </del> <ins> && let Some(&(remapped_idx, remapped_ty, needs_deref, bridging_projections)) = </ins> self.field_remapping.get(&idx) { // As noted before, if the parent closure captures a field by value, and // the child captures a field by ref, then for the by-move body we're // generating, we also are taking that field by value. Peel off a deref, <del> // since a layer of reffing has now become redundant. let final_deref = if needs_deref { </del> <ins> // since a layer of ref'ing has now become redundant. let final_projections = if needs_deref { </ins> let Some((mir::ProjectionElem::Deref, projection)) = projection.split_first() else { bug!(

projection }; <del> // The only thing that should be left is a deref, if the parent captured // an upvar by-ref. std::assert_matches::assert_matches!(final_deref, [] | [mir::ProjectionElem::Deref]); // For all of the additional projections that come out of precise capturing, // re-apply these projections. let additional_projections = additional_projections.iter().map(|elem| match elem.kind { ProjectionKind::Deref => mir::ProjectionElem::Deref, ProjectionKind::Field(idx, VariantIdx::ZERO) => { mir::ProjectionElem::Field(idx, elem.ty) } _ => unreachable!("precise captures only through fields and derefs"), }); </del> <ins> // These projections are applied in order to "bridge" the local that we are // currently transforming *from* the old upvar that the by-ref coroutine used // to capture *to* the upvar of the parent coroutine-closure. For example, if // the parent captures `&s` but the child captures `&(s.field)`, then we will // apply a field projection. let bridging_projections = bridging_projections.iter().map(|elem| match elem.kind { ProjectionKind::Deref => mir::ProjectionElem::Deref, ProjectionKind::Field(idx, VariantIdx::ZERO) => { mir::ProjectionElem::Field(idx, elem.ty) } _ => unreachable!("precise captures only through fields and derefs"), }); </ins> // We start out with an adjusted field index (and ty), representing the // upvar that we get from our parent closure. We apply any of the additional

projection: self.tcx.mk_place_elems_from_iter( [mir::ProjectionElem::Field(remapped_idx, remapped_ty)] .into_iter() <del> .chain(additional_projections) .chain(final_deref.iter().copied()), </del> <ins> .chain(bridging_projections) .chain(final_projections.iter().copied()), </ins> ), }; }

<ins> //@ edition: 2021 //@ check-pass #![feature(async_closure)] pub struct Struct { pub path: String, } // In `upvar.rs`, `truncate_capture_for_optimization` means that we don't actually // capture `&(*s.path)` here, but instead just `&(*s)`, but ONLY when the upvar is // immutable. This means that the assumption we have in `ByMoveBody` pass is wrong. pub fn test(s: &Struct) { let c = async move || { let path = &s.path; }; } fn main() {} </ins>