From 7805f3e432873f5b3ab019adf7a7fd39d2daeb22 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 28 May 2026 18:18:42 +0300 Subject: [PATCH 01/11] Implement Reborrow as a recursive operation If Reborrow finds '&'a mut T' fields then it inserts a Deref and borrow of the T, and likewise if it finds a 'T: Reborrow' field then the field type is recursed into. This makes Reborrow always produce the correct borrow checking logic at the cost of most probably being inconsiderately expensive. The thinking is that performance will be a followup consideration. --- compiler/rustc_borrowck/src/borrow_set.rs | 198 +++++++++++++++--- ...ce-shared-omitted-reborrow-field-locked.rs | 1 - ...hared-omitted-reborrow-field-locked.stderr | 15 +- tests/ui/reborrow/custom_marker_identity.rs | 17 ++ .../ui/reborrow/custom_marker_identity.stderr | 24 +++ .../custom_marker_mut_field_borrow.rs | 17 ++ .../custom_marker_mut_field_borrow.stderr | 14 ++ .../reborrow/custom_marker_place_conflict.rs | 17 ++ .../custom_marker_place_conflict.stderr | 14 ++ .../custom_marker_place_conflict_deref.rs | 25 +++ .../custom_marker_place_conflict_deref.stderr | 14 ++ .../custom_marker_place_conflict_field.rs | 17 ++ .../custom_marker_place_conflict_field.stderr | 14 ++ ...om_marker_place_conflict_parallel_field.rs | 21 ++ ...arker_place_conflict_parallel_field.stderr | 14 ++ tests/ui/reborrow/custom_mut_identity.rs | 17 ++ .../ui/reborrow/custom_mut_place_conflict.rs | 17 ++ .../reborrow/custom_mut_place_conflict.stderr | 14 ++ .../custom_mut_place_conflict_field.rs | 17 ++ .../custom_mut_place_conflict_field.stderr | 14 ++ 20 files changed, 460 insertions(+), 41 deletions(-) create mode 100644 tests/ui/reborrow/custom_marker_identity.rs create mode 100644 tests/ui/reborrow/custom_marker_identity.stderr create mode 100644 tests/ui/reborrow/custom_marker_mut_field_borrow.rs create mode 100644 tests/ui/reborrow/custom_marker_mut_field_borrow.stderr create mode 100644 tests/ui/reborrow/custom_marker_place_conflict.rs create mode 100644 tests/ui/reborrow/custom_marker_place_conflict.stderr create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_deref.rs create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_deref.stderr create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_field.rs create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_field.stderr create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_parallel_field.rs create mode 100644 tests/ui/reborrow/custom_marker_place_conflict_parallel_field.stderr create mode 100644 tests/ui/reborrow/custom_mut_identity.rs create mode 100644 tests/ui/reborrow/custom_mut_place_conflict.rs create mode 100644 tests/ui/reborrow/custom_mut_place_conflict.stderr create mode 100644 tests/ui/reborrow/custom_mut_place_conflict_field.rs create mode 100644 tests/ui/reborrow/custom_mut_place_conflict_field.stderr diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index a9d6b31e2ee2f..89862b68ca52a 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -7,7 +7,7 @@ use rustc_hir::Mutability; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor}; -use rustc_middle::mir::{self, Body, Local, Location, traversal}; +use rustc_middle::mir::{self, Body, Local, Location, PlaceElem, traversal}; use rustc_middle::ty::data_structures::IndexSet; use rustc_middle::ty::{RegionVid, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; @@ -265,6 +265,152 @@ impl<'a, 'tcx> GatherBorrows<'a, 'tcx> { } idx } + + fn insert_borrows( + &mut self, + location: Location, + borrows: SmallVec<[BorrowData<'tcx>; 1]>, + ) -> SmallVec<[BorrowIndex; 1]> { + let mut idxs = SmallVec::<[BorrowIndex; 1]>::with_capacity(borrows.len()); + // FIXME(reborrow): why doesn't SmallVec offer reserve? + for borrow in borrows { + idxs.push(self.borrows.push(borrow)); + } + match self.location_map.entry(location) { + Entry::Occupied(entry) => { + bug!( + "Inserting borrows {idxs:?} at {location:?} attempted to override an existing list {entry:?}" + ); + } + Entry::Vacant(entry) => { + entry.insert(idxs.clone()); + } + } + idxs + } + + fn gather_reborrows( + &mut self, + v: &mut SmallVec<[BorrowData<'tcx>; 1]>, + kind: mir::BorrowKind, + location: Location, + target_adt: ty::AdtDef<'tcx>, + target_args: &'tcx ty::List>, + target_place: mir::Place<'tcx>, + source_adt: ty::AdtDef<'tcx>, + source_args: &'tcx ty::List>, + source_place: mir::Place<'tcx>, + ) { + let mut did_reborrow = false; + for (source_idx, source_field) in source_adt.all_fields().enumerate() { + let source_field_ty = source_field.ty(self.tcx, source_args).skip_norm_wip(); + match source_field_ty.kind() { + ty::Ref(source_region, _, source_mutability) if source_mutability.is_mut() => { + if source_region.is_static() { + bug!( + "Cannot implement Reborrow on a type containing a &'static mut T field" + ); + } + let Some((target_idx, target_field)) = target_adt + .all_fields() + .enumerate() + .find(|(_, f)| f.name == source_field.name) + else { + // Reborrow dropped this field. + continue; + }; + let ty::Ref(target_region, _, _) = + target_field.ty(self.tcx, target_args).skip_norm_wip().kind() + else { + bug!( + "Reborrow source field type is &mut T but target field is not a reference" + ); + }; + + did_reborrow = true; + let source_field_deref_place = source_place.project_deeper( + &[PlaceElem::Field(source_idx.into(), source_field_ty), PlaceElem::Deref], + self.tcx, + ); + let target_field_place = target_place.project_to_field( + target_idx.into(), + &self.body.local_decls, + self.tcx, + ); + v.push(BorrowData { + kind, + region: target_region.as_var(), + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + borrowed_place: source_field_deref_place, + assigned_place: target_field_place, + }); + } + ty::Adt(source_field_adt, source_field_args) + if source_field_args.get(0).is_some_and(|f| f.as_region().is_some()) + && !self.tcx.type_is_copy_modulo_regions( + self.body.typing_env(self.tcx), + self.tcx.erase_and_anonymize_regions(source_field_ty), + ) => + { + let Some((target_idx, target_field)) = target_adt + .all_fields() + .enumerate() + .find(|(_, f)| f.name == source_field.name) + else { + // Reborrow dropped this field. + continue; + }; + let ty::Adt(target_field_adt, target_field_args) = + target_field.ty(self.tcx, target_args).skip_norm_wip().kind() + else { + bug!("Reborrow source field type is a !Copy ADT but target field is not"); + }; + + did_reborrow = true; + let source_field_place = source_place.project_to_field( + source_idx.into(), + &self.body.local_decls, + self.tcx, + ); + let target_field_place = target_place.project_to_field( + target_idx.into(), + &self.body.local_decls, + self.tcx, + ); + self.gather_reborrows( + v, + kind, + location, + *target_field_adt, + target_field_args, + target_field_place, + *source_field_adt, + source_field_args, + source_field_place, + ); + } + _ => continue, + } + } + if !did_reborrow { + // If source contained no reference, borrow it directly. + if target_args.regions().count() != 1 { + bug!( + "ADT containing no '&mut T' or 'T: Reborrow' fields must only have one lifetime to implement Reborrow" + ); + } + let target_region = target_args.regions().next().unwrap(); + v.push(BorrowData { + kind, + region: target_region.as_var(), + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + borrowed_place: source_place, + assigned_place: target_place, + }); + } + } } impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { @@ -323,23 +469,14 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { }; self.local_map.entry(borrowed_place.local).or_default().insert(idx); - } else if let &mir::Rvalue::Reborrow(target, mutability, borrowed_place) = rvalue { - let borrowed_place_ty = borrowed_place.ty(self.body, self.tcx).ty; - let &ty::Adt(reborrowed_adt, _reborrowed_args) = borrowed_place_ty.kind() else { - unreachable!() - }; - let &ty::Adt(target_adt, assigned_args) = target.kind() else { unreachable!() }; - let Some(ty::GenericArgKind::Lifetime(region)) = assigned_args.get(0).map(|r| r.kind()) - else { - bug!( - "hir-typeck passed but {} does not have a lifetime argument", - if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" } - ); - }; - let region = region.as_var(); + } else if let &mir::Rvalue::Reborrow(target, mutability, source_place) = rvalue { + let source_ty = source_place.ty(self.body, self.tcx).ty; + let &ty::Adt(source_adt, source_args) = source_ty.kind() else { unreachable!() }; + let &ty::Adt(target_adt, target_args) = target.kind() else { unreachable!() }; + let kind = if mutability == Mutability::Mut { // Reborrow - if target_adt.did() != reborrowed_adt.did() { + if target_adt.did() != source_adt.did() { bug!( "hir-typeck passed but Reborrow involves mismatching types at {location:?}" ) @@ -348,24 +485,33 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default } } else { // CoerceShared - if target_adt.did() == reborrowed_adt.did() { + if target_adt.did() == source_adt.did() { bug!( "hir-typeck passed but CoerceShared involves matching types at {location:?}" ) } mir::BorrowKind::Shared }; - let borrow = BorrowData { + + let mut reborrows = smallvec![]; + self.gather_reborrows( + &mut reborrows, kind, - region, - reserve_location: location, - activation_location: TwoPhaseActivation::NotTwoPhase, - borrowed_place, - assigned_place: *assigned_place, - }; - let idx = self.insert_borrow(location, borrow); + location, + target_adt, + target_args, + *assigned_place, + source_adt, + source_args, + source_place, + ); - self.local_map.entry(borrowed_place.local).or_default().insert(idx); + let idxs = self.insert_borrows(location, reborrows); + + let locals = self.local_map.entry(source_place.local).or_default(); + for idx in idxs { + locals.insert(idx); + } } self.super_assign(assigned_place, rvalue, location) diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs index ade1890068d76..fb4eb86781a0e 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs @@ -47,7 +47,6 @@ fn main() { let shared = get(wrapped); *wrapped.extra.value = 3; - //~^ ERROR cannot assign to `*wrapped.extra.value` because it is borrowed let _ = shared; } diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr index ccc3054a1b0c8..4c41ea5ea1175 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr @@ -10,18 +10,5 @@ LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} | = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts -error[E0506]: cannot assign to `*wrapped.extra.value` because it is borrowed - --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:49:5 - | -LL | let shared = get(wrapped); - | ------- `*wrapped.extra.value` is borrowed here -LL | -LL | *wrapped.extra.value = 3; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | `*wrapped.extra.value` is assigned to here but it was already borrowed - | borrow later used here - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/reborrow/custom_marker_identity.rs b/tests/ui/reborrow/custom_marker_identity.rs new file mode 100644 index 0000000000000..c0bd126f818a0 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_identity.rs @@ -0,0 +1,17 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { //~ERROR cannot return reference to temporary value + //~^ ERROR cannot return value referencing function parameter `a` + a +} + +fn main() { + let a = CustomMarker(PhantomData); + let _ = method(a); +} diff --git a/tests/ui/reborrow/custom_marker_identity.stderr b/tests/ui/reborrow/custom_marker_identity.stderr new file mode 100644 index 0000000000000..1ea3a6bd2442b --- /dev/null +++ b/tests/ui/reborrow/custom_marker_identity.stderr @@ -0,0 +1,24 @@ +error[E0515]: cannot return reference to temporary value + --> $DIR/custom_marker_identity.rs:9:56 + | +LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { + | ________________________________________________________^ +LL | | +LL | | a +LL | | } + | |_^ returns a reference to data owned by the current function + +error[E0515]: cannot return value referencing function parameter `a` + --> $DIR/custom_marker_identity.rs:9:56 + | +LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { + | ________________________________________________________^ +LL | | +LL | | a + | | - `a` is borrowed here +LL | | } + | |_^ returns a value referencing data owned by the current function + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/reborrow/custom_marker_mut_field_borrow.rs b/tests/ui/reborrow/custom_marker_mut_field_borrow.rs new file mode 100644 index 0000000000000..41fd01ed77a59 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_field_borrow.rs @@ -0,0 +1,17 @@ +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarker<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let x = &a.0; + let y = method(a); + //~^ ERROR: cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = (x, y); +} diff --git a/tests/ui/reborrow/custom_marker_mut_field_borrow.stderr b/tests/ui/reborrow/custom_marker_mut_field_borrow.stderr new file mode 100644 index 0000000000000..6601ec172228d --- /dev/null +++ b/tests/ui/reborrow/custom_marker_mut_field_borrow.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_marker_mut_field_borrow.rs:14:20 + | +LL | let x = &a.0; + | ---- immutable borrow occurs here +LL | let y = method(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = (x, y); + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_marker_place_conflict.rs b/tests/ui/reborrow/custom_marker_place_conflict.rs new file mode 100644 index 0000000000000..c137e81cea584 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict.rs @@ -0,0 +1,17 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn reborrow(_: CustomMarker) {} + +fn main() { + let a = CustomMarker(PhantomData); + let b: &CustomMarker = &a; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = b; +} diff --git a/tests/ui/reborrow/custom_marker_place_conflict.stderr b/tests/ui/reborrow/custom_marker_place_conflict.stderr new file mode 100644 index 0000000000000..224562cde5884 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_marker_place_conflict.rs:14:14 + | +LL | let b: &CustomMarker = &a; + | -- immutable borrow occurs here +LL | reborrow(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = b; + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_marker_place_conflict_deref.rs b/tests/ui/reborrow/custom_marker_place_conflict_deref.rs new file mode 100644 index 0000000000000..8a8119ccfe599 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_deref.rs @@ -0,0 +1,25 @@ +//@ check-fail + +#![feature(reborrow)] +use std::{marker::{Reborrow, PhantomData}, ops::Deref}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +impl<'a> Deref for CustomMarker<'a> { + type Target = (); + + fn deref(&self) -> &() { + unsafe { std::mem::transmute::<&Self, &()>(self) } + } +} + +fn reborrow(_: CustomMarker) {} + +fn main() { + let a = CustomMarker(PhantomData); + let b: &() = &a; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = b; +} diff --git a/tests/ui/reborrow/custom_marker_place_conflict_deref.stderr b/tests/ui/reborrow/custom_marker_place_conflict_deref.stderr new file mode 100644 index 0000000000000..22a8dead7fe72 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_deref.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_marker_place_conflict_deref.rs:22:14 + | +LL | let b: &() = &a; + | -- immutable borrow occurs here +LL | reborrow(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = b; + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_marker_place_conflict_field.rs b/tests/ui/reborrow/custom_marker_place_conflict_field.rs new file mode 100644 index 0000000000000..b967651179b36 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_field.rs @@ -0,0 +1,17 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +fn reborrow(_: CustomMarker) {} + +fn main() { + let a = CustomMarker(PhantomData); + let b: &PhantomData<&()> = &a.0; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = b; +} diff --git a/tests/ui/reborrow/custom_marker_place_conflict_field.stderr b/tests/ui/reborrow/custom_marker_place_conflict_field.stderr new file mode 100644 index 0000000000000..2972eff893f13 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_field.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_marker_place_conflict_field.rs:14:14 + | +LL | let b: &PhantomData<&()> = &a.0; + | ---- immutable borrow occurs here +LL | reborrow(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = b; + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.rs b/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.rs new file mode 100644 index 0000000000000..9c02f81820754 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.rs @@ -0,0 +1,21 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a> Reborrow for CustomMarker<'a> {} + +struct CustomMarkerTwo<'a>(CustomMarker<'a>, u64); +impl<'a> Reborrow for CustomMarkerTwo<'a> {} + +fn reborrow(_: CustomMarkerTwo) {} + +fn main() { + let a = CustomMarker(PhantomData); + let a = CustomMarkerTwo(a, 0); + let b: &u64 = &a.1; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = b; +} diff --git a/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.stderr b/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.stderr new file mode 100644 index 0000000000000..e48645562f8d3 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_place_conflict_parallel_field.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_marker_place_conflict_parallel_field.rs:18:14 + | +LL | let b: &u64 = &a.1; + | ---- immutable borrow occurs here +LL | reborrow(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = b; + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_mut_identity.rs b/tests/ui/reborrow/custom_mut_identity.rs new file mode 100644 index 0000000000000..6b67d5f342562 --- /dev/null +++ b/tests/ui/reborrow/custom_mut_identity.rs @@ -0,0 +1,17 @@ +//@ run-pass + +#![feature(reborrow)] +use std::marker::Reborrow; + +#[allow(unused)] +struct CustomMut<'a, T>(&'a mut T); +impl<'a, T> Reborrow for CustomMut<'a, T> {} + +fn method(a: CustomMut<()>) -> CustomMut<()> { + a +} + +fn main() { + let a = CustomMut(&mut ()); + let _ = method(a); +} diff --git a/tests/ui/reborrow/custom_mut_place_conflict.rs b/tests/ui/reborrow/custom_mut_place_conflict.rs new file mode 100644 index 0000000000000..8a57a93eb6bb7 --- /dev/null +++ b/tests/ui/reborrow/custom_mut_place_conflict.rs @@ -0,0 +1,17 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMut<'a>(&'a mut ()); +impl<'a> Reborrow for CustomMut<'a> {} + +fn reborrow(_: CustomMut) {} + +fn main() { + let a = CustomMut(&mut ()); + let b: &CustomMut = &a; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable because it is also borrowed as immutable + let _ = b; +} diff --git a/tests/ui/reborrow/custom_mut_place_conflict.stderr b/tests/ui/reborrow/custom_mut_place_conflict.stderr new file mode 100644 index 0000000000000..d73776b564a6a --- /dev/null +++ b/tests/ui/reborrow/custom_mut_place_conflict.stderr @@ -0,0 +1,14 @@ +error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable + --> $DIR/custom_mut_place_conflict.rs:14:14 + | +LL | let b: &CustomMut = &a; + | -- immutable borrow occurs here +LL | reborrow(a); + | ^ mutable borrow occurs here +LL | +LL | let _ = b; + | - immutable borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/tests/ui/reborrow/custom_mut_place_conflict_field.rs b/tests/ui/reborrow/custom_mut_place_conflict_field.rs new file mode 100644 index 0000000000000..6b65d794999a0 --- /dev/null +++ b/tests/ui/reborrow/custom_mut_place_conflict_field.rs @@ -0,0 +1,17 @@ +//@ check-fail + +#![feature(reborrow)] +use std::marker::{Reborrow, PhantomData}; + +struct CustomMut<'a>(&'a mut ()); +impl<'a> Reborrow for CustomMut<'a> {} + +fn reborrow(_: CustomMut) {} + +fn main() { + let a = CustomMut(&mut ()); + let b: &mut () = a.0; + reborrow(a); + //~^ ERROR cannot borrow `a` as mutable more than once at a time + let _ = b; +} diff --git a/tests/ui/reborrow/custom_mut_place_conflict_field.stderr b/tests/ui/reborrow/custom_mut_place_conflict_field.stderr new file mode 100644 index 0000000000000..326faf598594b --- /dev/null +++ b/tests/ui/reborrow/custom_mut_place_conflict_field.stderr @@ -0,0 +1,14 @@ +error[E0499]: cannot borrow `a` as mutable more than once at a time + --> $DIR/custom_mut_place_conflict_field.rs:14:14 + | +LL | let b: &mut () = a.0; + | --- first mutable borrow occurs here +LL | reborrow(a); + | ^ second mutable borrow occurs here +LL | +LL | let _ = b; + | - first borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. From 831d153f4d05ff8e29b6cef464f9940d74f7e268 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Jul 2026 17:52:26 +0300 Subject: [PATCH 02/11] PhantomDeref --- compiler/rustc_borrowck/src/borrow_set.rs | 6 ++++-- .../src/diagnostics/conflict_errors.rs | 7 +++++++ compiler/rustc_borrowck/src/diagnostics/mod.rs | 2 ++ .../src/diagnostics/mutability_errors.rs | 9 +++++++++ compiler/rustc_borrowck/src/lib.rs | 7 ++++++- compiler/rustc_borrowck/src/places_conflict.rs | 13 +++++++++++++ compiler/rustc_borrowck/src/prefixes.rs | 3 +++ compiler/rustc_borrowck/src/type_check/mod.rs | 2 ++ compiler/rustc_codegen_cranelift/src/base.rs | 1 + compiler/rustc_codegen_ssa/src/mir/place.rs | 3 +++ .../rustc_const_eval/src/check_consts/qualifs.rs | 1 + .../rustc_const_eval/src/interpret/projection.rs | 3 +++ compiler/rustc_middle/src/mir/pretty.rs | 5 +++-- compiler/rustc_middle/src/mir/statement.rs | 11 ++++++++--- compiler/rustc_middle/src/mir/syntax.rs | 2 ++ compiler/rustc_middle/src/mir/visit.rs | 2 ++ .../rustc_mir_build/src/builder/expr/as_place.rs | 2 ++ compiler/rustc_mir_dataflow/src/move_paths/mod.rs | 1 + compiler/rustc_mir_transform/src/coroutine/mod.rs | 3 ++- compiler/rustc_mir_transform/src/gvn.rs | 1 + compiler/rustc_mir_transform/src/promote_consts.rs | 4 +++- .../rustc_public/src/unstable/convert/stable/mir.rs | 1 + .../clippy/clippy_utils/src/qualify_min_const_fn.rs | 3 ++- tests/ui/reborrow/custom_marker_identity.rs | 2 +- tests/ui/reborrow/custom_marker_identity.stderr | 2 +- 25 files changed, 83 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index 89862b68ca52a..f53702f3fde13 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -394,7 +394,9 @@ impl<'a, 'tcx> GatherBorrows<'a, 'tcx> { } } if !did_reborrow { - // If source contained no reference, borrow it directly. + // If source contained no reference, perform a phantom dereference. + let source_phantom_deref_place = + source_place.project_deeper(&[PlaceElem::PhantomDeref], self.tcx); if target_args.regions().count() != 1 { bug!( "ADT containing no '&mut T' or 'T: Reborrow' fields must only have one lifetime to implement Reborrow" @@ -406,7 +408,7 @@ impl<'a, 'tcx> GatherBorrows<'a, 'tcx> { region: target_region.as_var(), reserve_location: location, activation_location: TwoPhaseActivation::NotTwoPhase, - borrowed_place: source_place, + borrowed_place: source_phantom_deref_place, assigned_place: target_place, }); } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 7267ae113de3e..6dd1d36c50064 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -4272,6 +4272,13 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { } StorageDeadOrDrop::Destructor(_) => kind, }, + ProjectionElem::PhantomDeref => match kind { + StorageDeadOrDrop::LocalStorageDead + | StorageDeadOrDrop::BoxedStorageDead => { + StorageDeadOrDrop::BoxedStorageDead + } + StorageDeadOrDrop::Destructor(_) => kind, + }, ProjectionElem::OpaqueCast { .. } | ProjectionElem::Field(..) | ProjectionElem::Downcast(..) => { diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 349337f273aab..0e5ab5c00bd76 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -398,6 +398,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { } } } + ProjectionElem::PhantomDeref => (), ProjectionElem::Downcast(..) if opt.including_downcast => return None, ProjectionElem::Downcast(..) => (), ProjectionElem::OpaqueCast(..) => (), @@ -486,6 +487,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { PlaceTy::from_ty(*ty) } ProjectionElem::Field(_, field_type) => PlaceTy::from_ty(*field_type), + ProjectionElem::PhantomDeref => unreachable!("not a field"), }, }; self.describe_field_from_ty( diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 49e6dc334ff80..ed47b2e9f43b7 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -182,6 +182,15 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { } } + PlaceRef { local: _, projection: [ProjectionElem::PhantomDeref] } => { + item_msg = String::new(); + reason = String::new(); + } + PlaceRef { local: _, projection: [_proj_base @ .., ProjectionElem::PhantomDeref] } => { + item_msg = String::new(); + reason = String::new(); + } + PlaceRef { local: _, projection: diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index cc61ba92da280..f800a1e7cbe62 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -2022,7 +2022,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { // So it's safe to skip these. ProjectionElem::OpaqueCast(_) | ProjectionElem::Downcast(_, _) - | ProjectionElem::UnwrapUnsafeBinder(_) => (), + | ProjectionElem::UnwrapUnsafeBinder(_) + | ProjectionElem::PhantomDeref => (), } place_ty = place_ty.projection_ty(tcx, elem); @@ -2246,6 +2247,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { for (place_base, elem) in place.iter_projections().rev() { match elem { ProjectionElem::Index(_/*operand*/) + | ProjectionElem::PhantomDeref | ProjectionElem::OpaqueCast(_) // assigning to P[i] requires P to be valid. | ProjectionElem::ConstantIndex { .. } @@ -2642,6 +2644,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { _ => bug!("Deref of unexpected type: {:?}", base_ty), } } + ProjectionElem::PhantomDeref => { + bug!("encountered PhantomDeref in is_mutable") + } // Check as the inner reference type if it is a field projection // from the `&pin` pattern ProjectionElem::Field(FieldIdx::ZERO, _) diff --git a/compiler/rustc_borrowck/src/places_conflict.rs b/compiler/rustc_borrowck/src/places_conflict.rs index e966e83435ef3..e35dd3eb082ca 100644 --- a/compiler/rustc_borrowck/src/places_conflict.rs +++ b/compiler/rustc_borrowck/src/places_conflict.rs @@ -244,6 +244,7 @@ fn place_components_conflict<'tcx>( (ProjectionElem::Deref, _, Deep) | (ProjectionElem::Deref, _, AccessDepth::Drop) + | (ProjectionElem::PhantomDeref, _, _) | (ProjectionElem::Field { .. }, _, _) | (ProjectionElem::Index { .. }, _, _) | (ProjectionElem::ConstantIndex { .. }, _, _) @@ -301,6 +302,11 @@ fn place_projection_conflict<'tcx>( debug!("place_element_conflict: DISJOINT-OR-EQ-DEREF"); Overlap::EqualOrDisjoint } + (ProjectionElem::PhantomDeref, ProjectionElem::PhantomDeref) => { + // phantom derefs (e.g., `x` vs. `x`) - recur. + debug!("place_element_conflict: DISJOINT-OR-EQ-PHANTOM-DEREF"); + Overlap::EqualOrDisjoint + } (ProjectionElem::OpaqueCast(_), ProjectionElem::OpaqueCast(_)) => { // casts to other types may always conflict irrespective of the type being cast to. debug!("place_element_conflict: DISJOINT-OR-EQ-OPAQUE"); @@ -504,8 +510,15 @@ fn place_projection_conflict<'tcx>( debug!("place_element_conflict: DISJOINT-OR-EQ-SLICE-SUBSLICES"); Overlap::EqualOrDisjoint } + (ProjectionElem::PhantomDeref, ProjectionElem::Field(idx, _)) + | (ProjectionElem::Field(idx, _), ProjectionElem::PhantomDeref) => { + eprintln!("idx: {idx:?}"); + debug!("place_element_conflict: DISJOINT-OR-EQ-PHANTOM-DEREF-FIELD"); + Overlap::EqualOrDisjoint + } ( ProjectionElem::Deref + | ProjectionElem::PhantomDeref | ProjectionElem::Field(..) | ProjectionElem::Index(..) | ProjectionElem::ConstantIndex { .. } diff --git a/compiler/rustc_borrowck/src/prefixes.rs b/compiler/rustc_borrowck/src/prefixes.rs index 7ac63e02e318d..7ff258de3af4f 100644 --- a/compiler/rustc_borrowck/src/prefixes.rs +++ b/compiler/rustc_borrowck/src/prefixes.rs @@ -65,6 +65,9 @@ impl<'tcx> Iterator for Prefixes<'tcx> { | ProjectionElem::Index(_) => { cursor = cursor_base; } + ProjectionElem::PhantomDeref => { + unreachable!("PhantomDeref should not be present in prefixes") + } ProjectionElem::Deref => { match self.kind { PrefixSet::Shallow => { diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 14b1c9b31ef9f..556ed74fd1732 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1897,6 +1897,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // All these projections don't add any constraints, so there's nothing to // do here. We check their invariants in the MIR validator after all. ProjectionElem::Deref + | ProjectionElem::PhantomDeref | ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } @@ -2468,6 +2469,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } ProjectionElem::Field(..) + | ProjectionElem::PhantomDeref | ProjectionElem::Downcast(..) | ProjectionElem::OpaqueCast(..) | ProjectionElem::Index(..) diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 27bb19c8d53c5..f9cf35e754ea0 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -994,6 +994,7 @@ pub(crate) fn codegen_place<'tcx>( PlaceElem::Deref => { cplace = cplace.place_deref(fx); } + PlaceElem::PhantomDeref => bug!("encountered PhantomDeref in codegen"), PlaceElem::OpaqueCast(ty) => bug!("encountered OpaqueCast({ty}) in codegen"), PlaceElem::UnwrapUnsafeBinder(ty) => { cplace = cplace.place_transmute_type(fx, fx.monomorphize(ty)); diff --git a/compiler/rustc_codegen_ssa/src/mir/place.rs b/compiler/rustc_codegen_ssa/src/mir/place.rs index 14a5f71fbceaa..c51f1dfdc686d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/place.rs +++ b/compiler/rustc_codegen_ssa/src/mir/place.rs @@ -360,6 +360,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { for elem in place_ref.projection[base..].iter() { cg_base = match *elem { mir::ProjectionElem::Deref => bx.load_operand(cg_base).deref(bx.cx()), + mir::ProjectionElem::PhantomDeref => { + bug!("encountered PhantomDeref in codegen") + } mir::ProjectionElem::Field(ref field, _) => { assert!( !cg_base.layout.ty.is_any_ptr(), diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index b2b8a567860e0..1db9f2d12ee46 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -290,6 +290,7 @@ where ProjectionElem::Index(index) if in_local(index) => return true, ProjectionElem::Deref + | ProjectionElem::PhantomDeref | ProjectionElem::Field(_, _) | ProjectionElem::OpaqueCast(_) | ProjectionElem::ConstantIndex { .. } diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs index be31393879fff..ba2e68208846f 100644 --- a/compiler/rustc_const_eval/src/interpret/projection.rs +++ b/compiler/rustc_const_eval/src/interpret/projection.rs @@ -411,6 +411,9 @@ where OpaqueCast(ty) => { span_bug!(self.cur_span(), "OpaqueCast({ty}) encountered after borrowck") } + PhantomDeref => { + span_bug!(self.cur_span(), "PhantomDeref encountered after borrowck") + } UnwrapUnsafeBinder(target) => base.transmute(self.layout_of(target)?, self)?, Field(field, _) => self.project_field(base, field)?, Downcast(_, variant) => self.project_downcast(base, variant)?, diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 021c1c176d788..a1685e80c8469 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1352,7 +1352,8 @@ fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> match elem { ProjectionElem::OpaqueCast(_) | ProjectionElem::Downcast(_, _) - | ProjectionElem::Field(_, _) => { + | ProjectionElem::Field(_, _) + | ProjectionElem::PhantomDeref => { write!(fmt, "(")?; } ProjectionElem::Deref => { @@ -1382,7 +1383,7 @@ fn post_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> ProjectionElem::Downcast(None, index) => { write!(fmt, " as variant#{index:?})")?; } - ProjectionElem::Deref => { + ProjectionElem::Deref | ProjectionElem::PhantomDeref => { write!(fmt, ")")?; } ProjectionElem::Field(field, ty) => { diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 3f13f12713396..7383e9163cb23 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -222,6 +222,7 @@ impl<'tcx> PlaceTy<'tcx> { }); PlaceTy::from_ty(ty) } + ProjectionElem::PhantomDeref => PlaceTy::from_ty(structurally_normalize(self.ty)), ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { PlaceTy::from_ty(self.ty.builtin_index().unwrap()) } @@ -260,7 +261,7 @@ impl ProjectionElem { /// than the base. pub fn is_indirect(&self) -> bool { match self { - Self::Deref => true, + Self::Deref | Self::PhantomDeref => true, Self::Field(_, _) | Self::Index(_) @@ -282,7 +283,8 @@ impl ProjectionElem { | Self::ConstantIndex { .. } | Self::Subslice { .. } | Self::Downcast(_, _) - | Self::UnwrapUnsafeBinder(..) => true, + | Self::UnwrapUnsafeBinder(..) + | Self::PhantomDeref => true, } } @@ -306,7 +308,8 @@ impl ProjectionElem { Self::ConstantIndex { from_end: true, .. } | Self::Index(_) | Self::OpaqueCast(_) - | Self::Subslice { .. } => false, + | Self::Subslice { .. } + | Self::PhantomDeref => false, // FIXME(unsafe_binders): Figure this out. Self::UnwrapUnsafeBinder(..) => false, @@ -326,6 +329,7 @@ impl ProjectionElem { ) -> Option> { Some(match self { ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::PhantomDeref => bug!("PhantomDeref shouldn't hopefully come here"), ProjectionElem::Downcast(name, read_variant) => { ProjectionElem::Downcast(name, read_variant) } @@ -560,6 +564,7 @@ impl<'tcx> PlaceRef<'tcx> { std::iter::once(self.local).chain(self.projection.iter().filter_map(|proj| match proj { ProjectionElem::Index(local) => Some(*local), ProjectionElem::Deref + | ProjectionElem::PhantomDeref | ProjectionElem::Field(_, _) | ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 4e2d16625266c..b41138e9267fe 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1255,6 +1255,8 @@ pub enum ProjectionElem { /// A transmute from an unsafe binder to the type that it wraps. This is a projection /// of a place, so it doesn't necessarily constitute a move out of the binder. UnwrapUnsafeBinder(T), + + PhantomDeref, } /// Alias for projections as they appear in places, where the base is a place diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 0ae59e99c2b5a..6e8addede16d4 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -1178,6 +1178,7 @@ macro_rules! visit_place_fns { if ty != new_ty { Some(PlaceElem::UnwrapUnsafeBinder(new_ty)) } else { None } } PlaceElem::Deref + | PlaceElem::PhantomDeref | PlaceElem::ConstantIndex { .. } | PlaceElem::Subslice { .. } | PlaceElem::Downcast(..) => None, @@ -1262,6 +1263,7 @@ macro_rules! visit_place_fns { ); } ProjectionElem::Deref + | ProjectionElem::PhantomDeref | ProjectionElem::Subslice { from: _, to: _, from_end: _ } | ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } | ProjectionElem::Downcast(_, _) => {} diff --git a/compiler/rustc_mir_build/src/builder/expr/as_place.rs b/compiler/rustc_mir_build/src/builder/expr/as_place.rs index e92f74722626b..4c717d70c742b 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_place.rs @@ -88,6 +88,7 @@ fn convert_to_hir_projections_and_truncate_for_capture( for mir_projection in mir_projections { let hir_projection = match mir_projection { ProjectionElem::Deref => HirProjectionKind::Deref, + ProjectionElem::PhantomDeref => continue, ProjectionElem::Field(field, _) => { let variant = variant.unwrap_or(FIRST_VARIANT); HirProjectionKind::Field(*field, variant) @@ -802,6 +803,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } ProjectionElem::Field(..) + | ProjectionElem::PhantomDeref | ProjectionElem::Downcast(..) | ProjectionElem::OpaqueCast(..) | ProjectionElem::ConstantIndex { .. } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs index 83d40a5a2f284..e198a6e03fa5b 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs @@ -452,6 +452,7 @@ impl MoveSubPath { let subpath = match elem { // correspond to a MoveSubPath ProjectionKind::Deref => MoveSubPath::Deref, + ProjectionKind::PhantomDeref => return MoveSubPathResult::Skip, ProjectionKind::Field(idx, _) => MoveSubPath::Field(idx), ProjectionKind::ConstantIndex { offset, min_length: _, from_end: false } => { MoveSubPath::ConstantIndex(offset) diff --git a/compiler/rustc_mir_transform/src/coroutine/mod.rs b/compiler/rustc_mir_transform/src/coroutine/mod.rs index c5c65553dae8c..e4fa76b0c8aaf 100644 --- a/compiler/rustc_mir_transform/src/coroutine/mod.rs +++ b/compiler/rustc_mir_transform/src/coroutine/mod.rs @@ -454,7 +454,8 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { | PlaceElem::Deref | PlaceElem::ConstantIndex { .. } | PlaceElem::Subslice { .. } - | PlaceElem::Downcast(..) => None, + | PlaceElem::Downcast(..) + | PlaceElem::PhantomDeref => None, } } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 9d751a7cc5bd0..15e9e34159f76 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -861,6 +861,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { return None; } } + ProjectionElem::PhantomDeref => bug!("PhantomDeref in GVN"), ProjectionElem::Downcast(name, index) => ProjectionElem::Downcast(name, index), ProjectionElem::Field(f, _) => match self.get(value) { Value::Aggregate(_, fields) => return Some((projection_ty, fields[f.as_usize()])), diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index ae2028f1c62ea..3af51f5991b79 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -299,7 +299,9 @@ impl<'tcx> Validator<'_, 'tcx> { | ProjectionElem::UnwrapUnsafeBinder(_) => {} // Never recurse. - ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) => { + ProjectionElem::PhantomDeref + | ProjectionElem::OpaqueCast(..) + | ProjectionElem::Downcast(..) => { return Err(Unpromotable); } diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 124329526028d..792c5d08e3f6e 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -444,6 +444,7 @@ impl<'tcx> Stable<'tcx> for mir::PlaceElem<'tcx> { use rustc_middle::mir::ProjectionElem::*; match self { Deref => crate::mir::ProjectionElem::Deref, + PhantomDeref => bug!("Hopefully we don't come here"), Field(idx, ty) => { crate::mir::ProjectionElem::Field(idx.stable(tables, cx), ty.stable(tables, cx)) } diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 820c8b550548c..aaa892aab308f 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -323,7 +323,8 @@ fn check_place<'tcx>( | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } | ProjectionElem::Index(_) - | ProjectionElem::UnwrapUnsafeBinder(_) => {}, + | ProjectionElem::UnwrapUnsafeBinder(_) + | ProjectionElem::PhantomDeref => {}, } } diff --git a/tests/ui/reborrow/custom_marker_identity.rs b/tests/ui/reborrow/custom_marker_identity.rs index c0bd126f818a0..476f7011e6359 100644 --- a/tests/ui/reborrow/custom_marker_identity.rs +++ b/tests/ui/reborrow/custom_marker_identity.rs @@ -7,7 +7,7 @@ struct CustomMarker<'a>(PhantomData<&'a ()>); impl<'a> Reborrow for CustomMarker<'a> {} fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { //~ERROR cannot return reference to temporary value - //~^ ERROR cannot return value referencing function parameter `a` + //~^ ERROR cannot return value referencing local data `a` a } diff --git a/tests/ui/reborrow/custom_marker_identity.stderr b/tests/ui/reborrow/custom_marker_identity.stderr index 1ea3a6bd2442b..46d8f05b42342 100644 --- a/tests/ui/reborrow/custom_marker_identity.stderr +++ b/tests/ui/reborrow/custom_marker_identity.stderr @@ -8,7 +8,7 @@ LL | | a LL | | } | |_^ returns a reference to data owned by the current function -error[E0515]: cannot return value referencing function parameter `a` +error[E0515]: cannot return value referencing local data `a` --> $DIR/custom_marker_identity.rs:9:56 | LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { From 66cd9269846d16ebc1c6c7894673de50a052f6a5 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Jul 2026 12:04:06 +0300 Subject: [PATCH 03/11] Simpler deref test Co-authored-by: Oli Scherer --- tests/ui/reborrow/custom_marker_place_conflict_deref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/reborrow/custom_marker_place_conflict_deref.rs b/tests/ui/reborrow/custom_marker_place_conflict_deref.rs index 8a8119ccfe599..a0c65c9753741 100644 --- a/tests/ui/reborrow/custom_marker_place_conflict_deref.rs +++ b/tests/ui/reborrow/custom_marker_place_conflict_deref.rs @@ -10,7 +10,7 @@ impl<'a> Deref for CustomMarker<'a> { type Target = (); fn deref(&self) -> &() { - unsafe { std::mem::transmute::<&Self, &()>(self) } + &() } } From 3f8262f020cd43b3b57761c2b69ee27e0310e200 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Jul 2026 12:05:43 +0300 Subject: [PATCH 04/11] Add more PhantomDeref unreachability assertions --- compiler/rustc_borrowck/src/lib.rs | 5 ++++- compiler/rustc_borrowck/src/type_check/mod.rs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index f800a1e7cbe62..afead7f60a32f 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -2247,7 +2247,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { for (place_base, elem) in place.iter_projections().rev() { match elem { ProjectionElem::Index(_/*operand*/) - | ProjectionElem::PhantomDeref | ProjectionElem::OpaqueCast(_) // assigning to P[i] requires P to be valid. | ProjectionElem::ConstantIndex { .. } @@ -2274,6 +2273,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { break; } + ProjectionElem::PhantomDeref => { + panic!("we don't allow assignments to PhantomDeref, location {location:?}"); + } + ProjectionElem::Subslice { .. } => { panic!("we don't allow assignments to subslices, location: {location:?}"); } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 556ed74fd1732..4d9e5dd9fc06b 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2468,8 +2468,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { _ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place), } } + ProjectionElem::PhantomDeref => { + bug!("unexpected PhantomDeref in add_reborrow_constraint") + } ProjectionElem::Field(..) - | ProjectionElem::PhantomDeref | ProjectionElem::Downcast(..) | ProjectionElem::OpaqueCast(..) | ProjectionElem::Index(..) From f1b3938aebcfe69d294e53df87da59fafd37e529 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Jul 2026 12:05:58 +0300 Subject: [PATCH 05/11] Write out lifetime omission --- tests/ui/reborrow/custom_marker_place_conflict.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/reborrow/custom_marker_place_conflict.rs b/tests/ui/reborrow/custom_marker_place_conflict.rs index c137e81cea584..6abbb847b9235 100644 --- a/tests/ui/reborrow/custom_marker_place_conflict.rs +++ b/tests/ui/reborrow/custom_marker_place_conflict.rs @@ -6,7 +6,7 @@ use std::marker::{Reborrow, PhantomData}; struct CustomMarker<'a>(PhantomData<&'a ()>); impl<'a> Reborrow for CustomMarker<'a> {} -fn reborrow(_: CustomMarker) {} +fn reborrow(_: CustomMarker<'_>) {} fn main() { let a = CustomMarker(PhantomData); From fe75a0b45de0cb77e27435be8976179dc0d87896 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Jul 2026 13:00:54 +0300 Subject: [PATCH 06/11] Document ProjectionElem::PhantomDeref --- compiler/rustc_middle/src/mir/syntax.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index b41138e9267fe..18df98c51fada 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1256,6 +1256,24 @@ pub enum ProjectionElem { /// of a place, so it doesn't necessarily constitute a move out of the binder. UnwrapUnsafeBinder(T), + /// A symbolic dereference of a `Reborrow` type that does not contain any `&mut T` fields. + /// + /// If a type is `Reborrow` and contains a `&mut T` field then reborrowing it reborrows the `T`, + /// producing a borrow on an indirect place, producing a value that can be returned from the + /// function since it does not capture any local place. If no such field exists, then + /// reborrowing the type must dereference the type itself to find an indirect place, but + /// generally such types will not implements `Deref`. Therefore, in borrow checking we instead + /// perform a "phantom dereference" (named so because the type will usually contain some + /// `PhantomData<&'a ()>` or equivalent that captures the lifetime) to access an indeterminate + /// indirect place. + /// + /// FIXME(reborrow): currently this variant is not considered an indirect place for whatever + /// reason. This variant makes no sense if that cannot be fixed. + /// + /// FIXME(reborrow): if the Reborrow traits experiment is rejected, this variant can be removed: + /// see the [PR]. + /// + /// [PR]: https://github.com/rust-lang/rust/pull/159103 PhantomDeref, } From 8e9f637b667e38aa923bf3b419e991a233b0d63f Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Jul 2026 12:49:32 +0300 Subject: [PATCH 07/11] Comment half of reborrow tests --- compiler/rustc_borrowck/src/type_check/mod.rs | 1 + compiler/rustc_middle/src/mir/statement.rs | 4 +++- .../ui/reborrow/coerce-shared-associated-type-field.rs | 3 +++ .../coerce-shared-associated-type-field.stderr | 2 +- tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs | 4 ++++ .../reborrow/coerce-shared-decl-macro-hygiene.stderr | 2 +- tests/ui/reborrow/coerce-shared-extra-marker.rs | 2 ++ tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs | 2 ++ .../reborrow/coerce-shared-field-lifetime-swap.stderr | 2 +- tests/ui/reborrow/coerce-shared-field-relations.rs | 7 +++++++ tests/ui/reborrow/coerce-shared-field-relations.stderr | 4 ++-- .../ui/reborrow/coerce-shared-foreign-private-field.rs | 3 +++ .../coerce-shared-foreign-private-field.stderr | 2 +- .../coerce-shared-foreign-private-tuple-field.rs | 3 +++ .../coerce-shared-foreign-private-tuple-field.stderr | 2 +- tests/ui/reborrow/coerce-shared-generics.rs | 2 ++ tests/ui/reborrow/coerce-shared-generics.stderr | 2 +- tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs | 3 +++ .../ui/reborrow/coerce-shared-lifetime-mismatch.stderr | 4 ++-- .../ui/reborrow/coerce-shared-missing-target-field.rs | 2 ++ .../reborrow/coerce-shared-missing-target-field.stderr | 2 +- .../reborrow/coerce-shared-mut-ref-field-validation.rs | 2 ++ .../coerce-shared-mut-ref-field-validation.stderr | 2 +- .../coerce-shared-omitted-reborrow-field-after-dead.rs | 10 ++++++++-- ...rce-shared-omitted-reborrow-field-after-dead.stderr | 2 +- .../coerce-shared-omitted-reborrow-field-locked.rs | 4 ++++ .../coerce-shared-omitted-reborrow-field-locked.stderr | 2 +- .../reborrow/coerce-shared-omitted-reborrow-field.rs | 3 +++ .../coerce-shared-omitted-reborrow-field.stderr | 2 +- tests/ui/reborrow/coerce-shared-reordered-field.rs | 2 ++ tests/ui/reborrow/coerce-shared-reordered-field.stderr | 2 +- tests/ui/reborrow/coerce-shared-wrong-generic.rs | 2 ++ tests/ui/reborrow/coerce-shared-wrong-generic.stderr | 2 +- tests/ui/reborrow/custom_marker_assign_deref.rs | 2 ++ tests/ui/reborrow/custom_marker_coerce_shared_copy.rs | 3 +++ tests/ui/reborrow/custom_marker_coerce_shared_move.rs | 3 +++ .../reborrow/custom_marker_coerce_shared_move.stderr | 2 +- tests/ui/reborrow/custom_marker_deref.rs | 3 +++ tests/ui/reborrow/custom_marker_identity.rs | 4 ++++ tests/ui/reborrow/custom_marker_identity.stderr | 4 ++-- 40 files changed, 91 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 4d9e5dd9fc06b..eead7655e03f8 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2533,6 +2533,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } + // FIXME: copy in code from coercion.rs to re-check CoerceShared lifetime relations. if mutability.is_not() { // FIXME(reborrow): for CoerceShared we need to relate the types manually, field by // field. We cannot just attempt to relate `T` and `::Target` by diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 7383e9163cb23..30fefe1de8bda 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -222,7 +222,9 @@ impl<'tcx> PlaceTy<'tcx> { }); PlaceTy::from_ty(ty) } - ProjectionElem::PhantomDeref => PlaceTy::from_ty(structurally_normalize(self.ty)), + ProjectionElem::PhantomDeref => { + PlaceTy::from_ty(normalize(Unnormalized::new_wip(self.ty))) + } ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { PlaceTy::from_ty(self.ty.builtin_index().unwrap()) } diff --git a/tests/ui/reborrow/coerce-shared-associated-type-field.rs b/tests/ui/reborrow/coerce-shared-associated-type-field.rs index df744e9442dd8..c40abe9de7b31 100644 --- a/tests/ui/reborrow/coerce-shared-associated-type-field.rs +++ b/tests/ui/reborrow/coerce-shared-associated-type-field.rs @@ -1,3 +1,6 @@ +//! Test that CoerceShared can resolve field type equivalence through GATs. +//! This should eventually pass. + #![feature(reborrow)] #![allow(dead_code)] diff --git a/tests/ui/reborrow/coerce-shared-associated-type-field.stderr b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr index 31b54e7ed6c9e..7a78190595380 100644 --- a/tests/ui/reborrow/coerce-shared-associated-type-field.stderr +++ b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-associated-type-field.rs:27:1 + --> $DIR/coerce-shared-associated-type-field.rs:30:1 | LL | impl<'a> CoerceShared> for MyMut<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^ diff --git a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs index 3b6e9e25d8bb4..59e0a8d96a33e 100644 --- a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs +++ b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs @@ -1,3 +1,7 @@ +//! Test that Reborrow and CoerceShared can be derived in macros. +//! This should eventually pass. + + #![feature(reborrow, decl_macro)] #![allow(incomplete_features)] diff --git a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr index 62a73361b5cd9..0c9d89dd009ec 100644 --- a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr +++ b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-decl-macro-hygiene.rs:20:5 + --> $DIR/coerce-shared-decl-macro-hygiene.rs:24:5 | LL | impl<'a> CoerceShared> for MyMut<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^ diff --git a/tests/ui/reborrow/coerce-shared-extra-marker.rs b/tests/ui/reborrow/coerce-shared-extra-marker.rs index 40026d68d5dca..d32d5c3ec1fff 100644 --- a/tests/ui/reborrow/coerce-shared-extra-marker.rs +++ b/tests/ui/reborrow/coerce-shared-extra-marker.rs @@ -1,5 +1,7 @@ //@ run-pass +//! Test that CoerceShared can drop a PhantomData marker field and pass a data reference through. + #![feature(reborrow)] #![allow(dead_code)] diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs index 9d102238467e6..71caf8a7ec6bf 100644 --- a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared cannot be used to swap 'static and 'a lifetimes around. + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr index b6160ad40bcce..4958640257918 100644 --- a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field - --> $DIR/coerce-shared-field-lifetime-swap.rs:14:5 + --> $DIR/coerce-shared-field-lifetime-swap.rs:16:5 | LL | x: &'static (), | -------------- source field `x` has type `&'static ()` diff --git a/tests/ui/reborrow/coerce-shared-field-relations.rs b/tests/ui/reborrow/coerce-shared-field-relations.rs index 3920f3eba7cb5..63c82bab9606d 100644 --- a/tests/ui/reborrow/coerce-shared-field-relations.rs +++ b/tests/ui/reborrow/coerce-shared-field-relations.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared cannot produce a field from thin air. + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; @@ -13,6 +15,7 @@ struct CustomRef<'a, T> { value: &'a T, } +// No error expected here: value: &'a mut T -> value: &'a T. impl<'a, T> CoerceShared> for CustomMut<'a, T> {} struct RenamedMut<'a, T> { @@ -27,6 +30,8 @@ struct RenamedRef<'a, T> { //~^ ERROR } +// Should error: source: &'a mut T -> target: &'a T attempts to drop 'source' and produce +// 'target' from thin air. impl<'a, T> CoerceShared> for RenamedMut<'a, T> {} struct BadMut<'a, T> { @@ -42,6 +47,8 @@ struct BadRef<'a, T> { _marker: std::marker::PhantomData, } +// Should error: value: &'a mut T -> &'a u32 attempts a reference transmute, and also +// '_marker' field is created from thin air. impl<'a, T> CoerceShared> for BadMut<'a, T> {} fn good(_value: CustomRef<'_, u32>) {} diff --git a/tests/ui/reborrow/coerce-shared-field-relations.stderr b/tests/ui/reborrow/coerce-shared-field-relations.stderr index 2a723f954490b..33c04d7b7f1b5 100644 --- a/tests/ui/reborrow/coerce-shared-field-relations.stderr +++ b/tests/ui/reborrow/coerce-shared-field-relations.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires every target field to have a corresponding source field - --> $DIR/coerce-shared-field-relations.rs:26:5 + --> $DIR/coerce-shared-field-relations.rs:29:5 | LL | target: &'a T, | ^^^^^^^^^^^^^ target field `target` has no corresponding source field @@ -8,7 +8,7 @@ LL | impl<'a, T> CoerceShared> for RenamedMut<'a, T> {} | ----------------- source type `RenamedMut` does not contain field `target` error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field - --> $DIR/coerce-shared-field-relations.rs:40:5 + --> $DIR/coerce-shared-field-relations.rs:45:5 | LL | value: &'a mut T, | ---------------- source field `value` has type `&'a mut T` diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs index 66c8ece3bd43e..fc039df249eae 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs @@ -1,5 +1,7 @@ //@ aux-build: reborrow_foreign_private.rs +//! Test that CoerceShared cannot be implemented targeting a foreign struct with private fields. + #![feature(reborrow)] extern crate reborrow_foreign_private; @@ -13,6 +15,7 @@ struct LocalMut<'a> { impl<'a> Reborrow for LocalMut<'a> {} +// Should error: ForeignRef has private fields. impl<'a> CoerceShared> for LocalMut<'a> {} //~^ ERROR diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr b/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr index a328084260d0d..a00a6b84ec788 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires all target type fields to be accessible from the impl - --> $DIR/coerce-shared-foreign-private-field.rs:16:1 + --> $DIR/coerce-shared-foreign-private-field.rs:19:1 | LL | impl<'a> CoerceShared> for LocalMut<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs index a2b88af04eb50..466fe2c315263 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs @@ -1,3 +1,6 @@ +//! Test that CoerceShared cannot be implemented targeting a foreign tuple struct with private +//! fields. + #![feature(reborrow)] use std::marker::{CoerceShared, PhantomData, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr index b699e1affbc49..2ae5963bbc55b 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires all target type fields to be accessible from the impl - --> $DIR/coerce-shared-foreign-private-tuple-field.rs:18:1 + --> $DIR/coerce-shared-foreign-private-tuple-field.rs:21:1 | LL | impl<'a> CoerceShared> for LocalPtrMut<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^-----------------^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/reborrow/coerce-shared-generics.rs b/tests/ui/reborrow/coerce-shared-generics.rs index 9edab02835761..2a609ffd1c2df 100644 --- a/tests/ui/reborrow/coerce-shared-generics.rs +++ b/tests/ui/reborrow/coerce-shared-generics.rs @@ -1,3 +1,5 @@ +//! Test that Reborrow and CoerceShared can be implemented with generics. + #![feature(reborrow)] #![allow(dead_code)] diff --git a/tests/ui/reborrow/coerce-shared-generics.stderr b/tests/ui/reborrow/coerce-shared-generics.stderr index 8e2e4e4485918..2be03590fb657 100644 --- a/tests/ui/reborrow/coerce-shared-generics.stderr +++ b/tests/ui/reborrow/coerce-shared-generics.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-generics.rs:26:1 + --> $DIR/coerce-shared-generics.rs:28:1 | LL | impl<'a, T, U: Copy, const N: usize> CoerceShared> | ^ ---------------------- target type has 2 non-ZST reborrow data fields diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs index b6b5471adb0fc..be49673a0ad9d 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared cannot be implemented with spurious 'static lifetimes. + #![feature(reborrow)] // The impl is accepted, but using it to coerce a local marker into a `'static` @@ -12,6 +14,7 @@ impl<'a> Reborrow for CustomMarker<'a> {} #[derive(Clone, Copy)] struct StaticMarkerRef<'a>(PhantomData<&'a ()>); +// Should error: for two types with only one lifetime each, both should use the same lifetime. impl<'a> CoerceShared> for CustomMarker<'a> {} //~^ ERROR diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr index 7c2e3e22b0b51..036b245678039 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires source and target to use the same reborrow lifetime argument - --> $DIR/coerce-shared-lifetime-mismatch.rs:15:10 + --> $DIR/coerce-shared-lifetime-mismatch.rs:18:10 | LL | impl<'a> CoerceShared> for CustomMarker<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^ -- source reborrow lifetime @@ -7,7 +7,7 @@ LL | impl<'a> CoerceShared> for CustomMarker<'a> {} | target reborrow lifetime error[E0597]: `a` does not live long enough - --> $DIR/coerce-shared-lifetime-mismatch.rs:22:12 + --> $DIR/coerce-shared-lifetime-mismatch.rs:25:12 | LL | let a = CustomMarker(PhantomData); | - binding `a` declared here diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.rs b/tests/ui/reborrow/coerce-shared-missing-target-field.rs index edd843b041fa8..dc96610d3703d 100644 --- a/tests/ui/reborrow/coerce-shared-missing-target-field.rs +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared cannot create a field from thin air. + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr index 148cf8addf0f9..0884d52f41b19 100644 --- a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires every target field to have a corresponding source field - --> $DIR/coerce-shared-missing-target-field.rs:14:5 + --> $DIR/coerce-shared-missing-target-field.rs:16:5 | LL | len: usize, | ^^^^^^^^^^ target field `len` has no corresponding source field diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs index 2a18d0dda06f0..ee7685242070e 100644 --- a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs @@ -1,3 +1,5 @@ +//! Test that reference shared coercing does not allow changing lifetime relations. + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr index 98ab275b31e48..57159ff8ddd56 100644 --- a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field - --> $DIR/coerce-shared-mut-ref-field-validation.rs:50:5 + --> $DIR/coerce-shared-mut-ref-field-validation.rs:52:5 | LL | value: &'a mut &'a (), | --------------------- source field `value` has type `&'a mut &'a ()` diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs index 4f066079c749b..174aeabd3ebe5 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs @@ -1,3 +1,7 @@ +//! Test that CoerceShared does not capture an omitted field, and that captured fields do not stay +//! captured after the local lifetime ends. +//! This should eventually pass. + #![feature(reborrow)] #![allow(dead_code)] @@ -46,6 +50,8 @@ fn main() { read(wrapped); } - extra_value = 3; - assert_eq!(extra_value, 3); + value = 3; + assert_eq!(value, 3); + extra_value = 4; + assert_eq!(extra_value, 4); } diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr index d0f2540ccbcff..7a92db2b1323a 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-omitted-reborrow-field-after-dead.rs:31:1 + --> $DIR/coerce-shared-omitted-reborrow-field-after-dead.rs:35:1 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs index fb4eb86781a0e..b0985c4f974bd 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs @@ -1,3 +1,7 @@ +//! Test that CoerceShared doesn't capture an omitted field, and that the source's omitted field can +//! be used as exclusive while the captured field is still captured. +//! This should eventually pass. + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr index 4c41ea5ea1175..a5426a3b4dc81 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:30:1 + --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:34:1 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs index 55cc010c19af4..f12c29416bdba 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs @@ -1,3 +1,6 @@ +//! Test that CoerceShared can omit a reborrowed field. +//! This should eventually pass. + #![feature(reborrow)] #![allow(dead_code)] diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr index 08ddea2329405..fb8b6384143b3 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-omitted-reborrow-field.rs:31:1 + --> $DIR/coerce-shared-omitted-reborrow-field.rs:34:1 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.rs b/tests/ui/reborrow/coerce-shared-reordered-field.rs index f4630fe1f7d83..b182d9df26f59 100644 --- a/tests/ui/reborrow/coerce-shared-reordered-field.rs +++ b/tests/ui/reborrow/coerce-shared-reordered-field.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared can be implemented even if field order changes. + #![feature(reborrow)] #![allow(dead_code)] diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.stderr b/tests/ui/reborrow/coerce-shared-reordered-field.stderr index 5469e5e3d9f49..4dbc344aa9196 100644 --- a/tests/ui/reborrow/coerce-shared-reordered-field.stderr +++ b/tests/ui/reborrow/coerce-shared-reordered-field.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field - --> $DIR/coerce-shared-reordered-field.rs:19:1 + --> $DIR/coerce-shared-reordered-field.rs:21:1 | LL | impl<'a> CoerceShared> for ReorderMut<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.rs b/tests/ui/reborrow/coerce-shared-wrong-generic.rs index bbd9cfebcd9e8..b3dee8e8a9eb2 100644 --- a/tests/ui/reborrow/coerce-shared-wrong-generic.rs +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.rs @@ -1,3 +1,5 @@ +//! Test that CoerceShared cannot switch generic type usage around. + #![feature(reborrow)] use std::marker::{CoerceShared, PhantomData, Reborrow}; diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr index b037106ebb619..2a472cee46af2 100644 --- a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr @@ -1,5 +1,5 @@ error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field - --> $DIR/coerce-shared-wrong-generic.rs:14:5 + --> $DIR/coerce-shared-wrong-generic.rs:16:5 | LL | value: &'a mut T, | ---------------- source field `value` has type `&'a mut T` diff --git a/tests/ui/reborrow/custom_marker_assign_deref.rs b/tests/ui/reborrow/custom_marker_assign_deref.rs index 9c0501644f83b..fa3c390f5d32e 100644 --- a/tests/ui/reborrow/custom_marker_assign_deref.rs +++ b/tests/ui/reborrow/custom_marker_assign_deref.rs @@ -1,5 +1,7 @@ //@ run-pass +//! Test that assignment to DerefMut of a Reborrow type does not ICE. + #![feature(reborrow)] use std::marker::{Reborrow, PhantomData}; diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs b/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs index b84b63234b8c2..ae52e065435cf 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs +++ b/tests/ui/reborrow/custom_marker_coerce_shared_copy.rs @@ -1,5 +1,8 @@ //@ run-pass +//! Test that CoerceShared of custom ZST marker type reborrows the type automatically as shared and +//! the original stays concurrently usable through shared references. + #![feature(reborrow)] use std::marker::{CoerceShared, PhantomData, Reborrow}; diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.rs b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs index 027e45bd5ba49..a6acfd89e08ef 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.rs +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs @@ -1,3 +1,6 @@ +//! Test that CoerceShared of custom ZST marker type reborrows the type automatically as shared but +//! moving the original invalidates the results. + #![feature(reborrow)] use std::marker::{CoerceShared, PhantomData, Reborrow}; diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr index 0089c03e36c77..f0ad934cacbf3 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr @@ -1,5 +1,5 @@ error[E0505]: cannot move out of `a` because it is borrowed - --> $DIR/custom_marker_coerce_shared_move.rs:18:14 + --> $DIR/custom_marker_coerce_shared_move.rs:22:14 | LL | let a = CustomMarker(PhantomData); | - binding `a` declared here diff --git a/tests/ui/reborrow/custom_marker_deref.rs b/tests/ui/reborrow/custom_marker_deref.rs index 3dcf26d6829d1..be1cb7a3f84c8 100644 --- a/tests/ui/reborrow/custom_marker_deref.rs +++ b/tests/ui/reborrow/custom_marker_deref.rs @@ -1,5 +1,8 @@ //@ run-pass +//! Test that CoerceShared of custom ZST marker type reborrows the type automatically from a +//! `&mut CustomMarker` deref. + #![feature(reborrow)] use std::marker::{Reborrow, PhantomData}; diff --git a/tests/ui/reborrow/custom_marker_identity.rs b/tests/ui/reborrow/custom_marker_identity.rs index 476f7011e6359..75fb62e761f46 100644 --- a/tests/ui/reborrow/custom_marker_identity.rs +++ b/tests/ui/reborrow/custom_marker_identity.rs @@ -1,5 +1,9 @@ //@ check-fail +//! Check that the result of a Reborrow retains the original lifetime and does not capture local +//! values, therefore enabling an identity function to compile. +//! This should eventually pass. + #![feature(reborrow)] use std::marker::{Reborrow, PhantomData}; diff --git a/tests/ui/reborrow/custom_marker_identity.stderr b/tests/ui/reborrow/custom_marker_identity.stderr index 46d8f05b42342..455f2b083dd30 100644 --- a/tests/ui/reborrow/custom_marker_identity.stderr +++ b/tests/ui/reborrow/custom_marker_identity.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return reference to temporary value - --> $DIR/custom_marker_identity.rs:9:56 + --> $DIR/custom_marker_identity.rs:13:56 | LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { | ________________________________________________________^ @@ -9,7 +9,7 @@ LL | | } | |_^ returns a reference to data owned by the current function error[E0515]: cannot return value referencing local data `a` - --> $DIR/custom_marker_identity.rs:9:56 + --> $DIR/custom_marker_identity.rs:13:56 | LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { | ________________________________________________________^ From 0e8ceed389390ef3d6ce29007385c3e692b4067b Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Mon, 27 Jul 2026 21:09:23 +0300 Subject: [PATCH 08/11] fix PhantomDeref conflicting with AccessDepth::Shallow --- .../rustc_borrowck/src/places_conflict.rs | 13 +++++----- compiler/rustc_middle/src/mir/pretty.rs | 6 +++-- compiler/rustc_middle/src/mir/statement.rs | 5 +++- .../rustc_mir_dataflow/src/move_paths/mod.rs | 4 +++- .../coerce-shared-lifetime-mismatch.rs | 2 -- .../coerce-shared-lifetime-mismatch.stderr | 19 ++------------- tests/ui/reborrow/custom_marker_identity.rs | 5 ++-- .../ui/reborrow/custom_marker_identity.stderr | 24 ------------------- .../reborrow/reborrow-promotion-rejected.rs | 3 +-- .../reborrow-promotion-rejected.stderr | 13 ---------- 10 files changed, 23 insertions(+), 71 deletions(-) delete mode 100644 tests/ui/reborrow/custom_marker_identity.stderr delete mode 100644 tests/ui/reborrow/reborrow-promotion-rejected.stderr diff --git a/compiler/rustc_borrowck/src/places_conflict.rs b/compiler/rustc_borrowck/src/places_conflict.rs index e35dd3eb082ca..6afc03d0ecf29 100644 --- a/compiler/rustc_borrowck/src/places_conflict.rs +++ b/compiler/rustc_borrowck/src/places_conflict.rs @@ -234,6 +234,13 @@ fn place_components_conflict<'tcx>( return false; } + (ProjectionElem::PhantomDeref, _, Shallow(None)) => { + // e.g., a reborrow of `x.y` while we shallowly access `x.y` or some prefix + // thereof - the shallow access cannot invalidate the reborrowed copy. + debug!("borrow_conflicts_with_place: shallow access behind reborrow"); + return false; + } + (ProjectionElem::Field { .. }, ty::Adt(def, _), AccessDepth::Drop) => { // Drop can read/write arbitrary projections, so places // conflict regardless of further projections. @@ -510,12 +517,6 @@ fn place_projection_conflict<'tcx>( debug!("place_element_conflict: DISJOINT-OR-EQ-SLICE-SUBSLICES"); Overlap::EqualOrDisjoint } - (ProjectionElem::PhantomDeref, ProjectionElem::Field(idx, _)) - | (ProjectionElem::Field(idx, _), ProjectionElem::PhantomDeref) => { - eprintln!("idx: {idx:?}"); - debug!("place_element_conflict: DISJOINT-OR-EQ-PHANTOM-DEREF-FIELD"); - Overlap::EqualOrDisjoint - } ( ProjectionElem::Deref | ProjectionElem::PhantomDeref diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index a1685e80c8469..7b35d9eb48868 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1352,8 +1352,7 @@ fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> match elem { ProjectionElem::OpaqueCast(_) | ProjectionElem::Downcast(_, _) - | ProjectionElem::Field(_, _) - | ProjectionElem::PhantomDeref => { + | ProjectionElem::Field(_, _) => { write!(fmt, "(")?; } ProjectionElem::Deref => { @@ -1365,6 +1364,9 @@ fn pre_fmt_projection(projection: &[PlaceElem<'_>], fmt: &mut Formatter<'_>) -> ProjectionElem::UnwrapUnsafeBinder(_) => { write!(fmt, "unwrap_binder!(")?; } + ProjectionElem::PhantomDeref => { + write!(fmt, "reborrow!(")?; + } } } diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 30fefe1de8bda..3db51c4c78da6 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -494,7 +494,10 @@ impl<'tcx> PlaceRef<'tcx> { pub fn local_or_deref_local(&self) -> Option { match *self { PlaceRef { local, projection: [] } - | PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local), + | PlaceRef { + local, + projection: [ProjectionElem::Deref | ProjectionElem::PhantomDeref], + } => Some(local), _ => None, } } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs index e198a6e03fa5b..b6565588ae3f1 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs @@ -452,7 +452,9 @@ impl MoveSubPath { let subpath = match elem { // correspond to a MoveSubPath ProjectionKind::Deref => MoveSubPath::Deref, - ProjectionKind::PhantomDeref => return MoveSubPathResult::Skip, + ProjectionKind::PhantomDeref => { + unreachable!("unexpected PhantomDeref in MoveSubPath::of") + } ProjectionKind::Field(idx, _) => MoveSubPath::Field(idx), ProjectionKind::ConstantIndex { offset, min_length: _, from_end: false } => { MoveSubPath::ConstantIndex(offset) diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs index be49673a0ad9d..c7e3b4b12d4bc 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs @@ -14,7 +14,6 @@ impl<'a> Reborrow for CustomMarker<'a> {} #[derive(Clone, Copy)] struct StaticMarkerRef<'a>(PhantomData<&'a ()>); -// Should error: for two types with only one lifetime each, both should use the same lifetime. impl<'a> CoerceShared> for CustomMarker<'a> {} //~^ ERROR @@ -23,5 +22,4 @@ fn method(_a: StaticMarkerRef<'static>) {} fn main() { let a = CustomMarker(PhantomData); method(a); - //~^ ERROR } diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr index 036b245678039..38c3d3637b566 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr @@ -1,25 +1,10 @@ error: implementing `CoerceShared` requires source and target to use the same reborrow lifetime argument - --> $DIR/coerce-shared-lifetime-mismatch.rs:18:10 + --> $DIR/coerce-shared-lifetime-mismatch.rs:17:10 | LL | impl<'a> CoerceShared> for CustomMarker<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^ -- source reborrow lifetime | | | target reborrow lifetime -error[E0597]: `a` does not live long enough - --> $DIR/coerce-shared-lifetime-mismatch.rs:25:12 - | -LL | let a = CustomMarker(PhantomData); - | - binding `a` declared here -LL | method(a); - | -------^- - | | | - | | borrowed value does not live long enough - | argument requires that `a` is borrowed for `'static` -LL | -LL | } - | - `a` dropped here while still borrowed - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/reborrow/custom_marker_identity.rs b/tests/ui/reborrow/custom_marker_identity.rs index 75fb62e761f46..27038a803d86c 100644 --- a/tests/ui/reborrow/custom_marker_identity.rs +++ b/tests/ui/reborrow/custom_marker_identity.rs @@ -1,4 +1,4 @@ -//@ check-fail +//@ check-pass //! Check that the result of a Reborrow retains the original lifetime and does not capture local //! values, therefore enabling an identity function to compile. @@ -10,8 +10,7 @@ use std::marker::{Reborrow, PhantomData}; struct CustomMarker<'a>(PhantomData<&'a ()>); impl<'a> Reborrow for CustomMarker<'a> {} -fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { //~ERROR cannot return reference to temporary value - //~^ ERROR cannot return value referencing local data `a` +fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { a } diff --git a/tests/ui/reborrow/custom_marker_identity.stderr b/tests/ui/reborrow/custom_marker_identity.stderr deleted file mode 100644 index 455f2b083dd30..0000000000000 --- a/tests/ui/reborrow/custom_marker_identity.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error[E0515]: cannot return reference to temporary value - --> $DIR/custom_marker_identity.rs:13:56 - | -LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { - | ________________________________________________________^ -LL | | -LL | | a -LL | | } - | |_^ returns a reference to data owned by the current function - -error[E0515]: cannot return value referencing local data `a` - --> $DIR/custom_marker_identity.rs:13:56 - | -LL | fn method<'a>(a: CustomMarker<'a>) -> CustomMarker<'a> { - | ________________________________________________________^ -LL | | -LL | | a - | | - `a` is borrowed here -LL | | } - | |_^ returns a value referencing data owned by the current function - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0515`. diff --git a/tests/ui/reborrow/reborrow-promotion-rejected.rs b/tests/ui/reborrow/reborrow-promotion-rejected.rs index 38366cd9ac2a7..7265c25da9795 100644 --- a/tests/ui/reborrow/reborrow-promotion-rejected.rs +++ b/tests/ui/reborrow/reborrow-promotion-rejected.rs @@ -1,4 +1,4 @@ -//@ check-fail +//@ check-pass #![feature(reborrow)] @@ -16,6 +16,5 @@ const fn coerce(x: MyRef<'_>) -> MyRef<'_> { } static BAD: &'static MyRef<'static> = &coerce(MyMut(&1)); -//~^ ERROR temporary value dropped while borrowed fn main() {} diff --git a/tests/ui/reborrow/reborrow-promotion-rejected.stderr b/tests/ui/reborrow/reborrow-promotion-rejected.stderr deleted file mode 100644 index f7e1560f02089..0000000000000 --- a/tests/ui/reborrow/reborrow-promotion-rejected.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0716]: temporary value dropped while borrowed - --> $DIR/reborrow-promotion-rejected.rs:18:47 - | -LL | static BAD: &'static MyRef<'static> = &coerce(MyMut(&1)); - | --------^^^^^^^^^- - | | | | - | | | temporary value is freed at the end of this statement - | | creates a temporary value which is freed while still in use - | using this value as a static requires that borrow lasts for `'static` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0716`. From 5b17ec69108f3c3fb3b614cad65eb2f58257824f Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 7 Aug 2026 20:06:09 +0300 Subject: [PATCH 09/11] Recheck CoerceShared in borrowck TypeChecker to ensure its lifetimes make sense --- compiler/rustc_borrowck/src/type_check/mod.rs | 21 ++++++++++++++++++- .../custom_marker_coerce_shared_move.stderr | 3 ++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index eead7655e03f8..8000cf9304d3c 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -18,7 +18,7 @@ use rustc_infer::infer::region_constraints::RegionConstraintData; use rustc_infer::infer::{ BoundRegionConversionTime, InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, }; -use rustc_infer::traits::PredicateObligations; +use rustc_infer::traits::{Obligation, ObligationCause, PredicateObligations}; use rustc_middle::bug; use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; @@ -34,6 +34,7 @@ use rustc_mir_dataflow::points::DenseLocationMap; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::{Span, Spanned, sym}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::query::type_op::custom::scrape_region_constraints; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; use tracing::{debug, instrument, trace}; @@ -2535,6 +2536,24 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // FIXME: copy in code from coercion.rs to re-check CoerceShared lifetime relations. if mutability.is_not() { + let Some(coerce_shared_trait_did) = self.tcx().lang_items().coerce_shared() else { + bug!("HIR type check passed CoerceShared but MIR found no such lang item"); + }; + let coerce_shared_trait_ref = + ty::TraitRef::new(self.tcx(), coerce_shared_trait_did, [borrowed_ty, dest_ty]); + let obligation = Obligation::new( + self.tcx(), + ObligationCause::dummy(), + self.infcx.param_env, + ty::Binder::dummy(coerce_shared_trait_ref), + ); + let ocx = ObligationCtxt::new(&self.infcx); + ocx.register_obligation(obligation); + let errs = ocx.evaluate_obligations_error_on_ambiguity(); + if !errs.no_errors() { + bug!("HIR type check passed CoerceShared but MIR found an issue"); + } + // FIXME(reborrow): for CoerceShared we need to relate the types manually, field by // field. We cannot just attempt to relate `T` and `::Target` by // calling relate_types as they are (generally) two unrelated user-defined ADTs, such as diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr index f0ad934cacbf3..58483336cff9f 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr @@ -7,9 +7,10 @@ LL | let b = method(a); | - borrow of `a` occurs here LL | let c = method(a); LL | let _ = (a, b, c); - | ^ - borrow later used here + | ^ | | | move out of `a` occurs here + | borrow later used here error: aborting due to 1 previous error From c289b888e4a1f967af38488c7680cac1185522cc Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 13 Aug 2026 18:59:56 +0300 Subject: [PATCH 10/11] Fix rebase --- compiler/rustc_middle/src/mir/statement.rs | 4 +--- tests/ui/reborrow/custom_marker_coerce_shared_move.stderr | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 3db51c4c78da6..6fbb6086b8f79 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -222,9 +222,7 @@ impl<'tcx> PlaceTy<'tcx> { }); PlaceTy::from_ty(ty) } - ProjectionElem::PhantomDeref => { - PlaceTy::from_ty(normalize(Unnormalized::new_wip(self.ty))) - } + ProjectionElem::PhantomDeref => PlaceTy::from_ty(self.ty), ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => { PlaceTy::from_ty(self.ty.builtin_index().unwrap()) } diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr index 58483336cff9f..44318a4ca7494 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr @@ -1,5 +1,5 @@ error[E0505]: cannot move out of `a` because it is borrowed - --> $DIR/custom_marker_coerce_shared_move.rs:22:14 + --> $DIR/custom_marker_coerce_shared_move.rs:21:14 | LL | let a = CustomMarker(PhantomData); | - binding `a` declared here From 9d6d6eaf6468d28bdbf9d4bb3231401559297c2e Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 13 Aug 2026 20:52:29 +0300 Subject: [PATCH 11/11] Changes... but where to? --- compiler/rustc_borrowck/src/type_check/mod.rs | 77 +++++++++++-------- .../custom_marker_coerce_shared_move.rs | 6 +- .../custom_marker_coerce_shared_move.stderr | 12 +-- ...m_marker_coerce_shared_move_no_conflict.rs | 26 +++++++ ...rker_coerce_shared_move_no_conflict.stderr | 17 ++++ ...r_coerce_shared_move_no_conflict_manual.rs | 27 +++++++ ...erce_shared_move_no_conflict_manual.stderr | 8 ++ 7 files changed, 134 insertions(+), 39 deletions(-) create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.stderr create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.rs create mode 100644 tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.stderr diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 8000cf9304d3c..d8448ff989c51 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2489,41 +2489,40 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { &mut self, mutability: Mutability, location: Location, - borrowed_place: &Place<'tcx>, - dest_ty: Ty<'tcx>, + src_place: &Place<'tcx>, + dst_ty: Ty<'tcx>, ) { let Self { borrow_set, location_table, polonius_facts, constraints, infcx, body, .. } = self; debug!( "add_generic_reborrow_constraint({:?}, {:?}, {:?}, {:?})", - mutability, location, borrowed_place, dest_ty + mutability, location, src_place, dst_ty ); let tcx = infcx.tcx; let def = body.source.def_id().expect_local(); let upvars = tcx.closure_captures(def); - let field = - path_utils::is_upvar_field_projection(tcx, upvars, borrowed_place.as_ref(), body); + let field = path_utils::is_upvar_field_projection(tcx, upvars, src_place.as_ref(), body); let category = if let Some(field) = field { ConstraintCategory::ClosureUpvar(field) } else { ConstraintCategory::Boring }; - let borrowed_ty = borrowed_place.ty(self.body, tcx).ty; + let src_ty = src_place.ty(self.body, tcx).ty; - let ty::Adt(dest_adt, dest_args) = dest_ty.kind() else { bug!() }; - let [dest_arg, ..] = ***dest_args else { bug!() }; - let ty::GenericArgKind::Lifetime(dest_region) = dest_arg.kind() else { bug!() }; - constraints.liveness_constraints.add_location(dest_region.as_var(), location); + let ty::Adt(dst_adt, dst_args) = dst_ty.kind() else { bug!() }; + let [dst_arg, ..] = ***dst_args else { bug!() }; + let ty::GenericArgKind::Lifetime(dst_region) = dst_arg.kind() else { bug!() }; + constraints.liveness_constraints.add_location(dst_region.as_var(), location); // In Polonius mode, we also push a `loan_issued_at` fact // linking the loan to the region. if let Some(polonius_facts) = polonius_facts { let _prof_timer = infcx.tcx.prof.generic_activity("polonius_fact_generation"); if let Some(borrows) = borrow_set.borrows_at_location(&location) { - let region_vid = dest_region.as_var(); + let region_vid = dst_region.as_var(); for borrow_index in borrows { polonius_facts.loan_issued_at.push(( region_vid.into(), @@ -2534,13 +2533,23 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } - // FIXME: copy in code from coercion.rs to re-check CoerceShared lifetime relations. if mutability.is_not() { + // FIXME(reborrow): this CoerceShared trait obligation takes into account lifetimes + // (purposefully, that's its purpose), but if the trait is implemented as + // `CoerceShared> for Source<'a> {}` then we will here generate an invariance + // relationship between the CoerceShared src and dst lifetimes. That then means + // CoerceShared will make reborrowing or moving the source impossible. + // + // The reason why we do this obligation here (again: it's already done in THIR) is + // because we'd want to catch impls like `CoerceShared> for Source<'a>` + // and, in those cases, correctly generate a `'a: 'static` bound. + // + // I'm not sure what would be the right way to resolve this conundrum. let Some(coerce_shared_trait_did) = self.tcx().lang_items().coerce_shared() else { bug!("HIR type check passed CoerceShared but MIR found no such lang item"); }; let coerce_shared_trait_ref = - ty::TraitRef::new(self.tcx(), coerce_shared_trait_did, [borrowed_ty, dest_ty]); + ty::TraitRef::new(self.tcx(), coerce_shared_trait_did, [src_ty, dst_ty]); let obligation = Obligation::new( self.tcx(), ObligationCause::dummy(), @@ -2560,32 +2569,35 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // `CustomMut<'a>` and `CustomRef<'a>`, or `CustomMut<'a, T>` and `CustomRef<'a, T>`. // Field-by-field relate_types is expected to work based on the wf-checks that the // CoerceShared trait performs. - let ty::Adt(borrowed_adt, borrowed_args) = borrowed_ty.kind() else { unreachable!() }; - let borrowed_fields = borrowed_adt.all_fields().collect::>(); - for dest_field in dest_adt.all_fields() { - let Some(borrowed_field) = - borrowed_fields.iter().find(|f| f.name == dest_field.name) - else { + let ty::Adt(src_adt, src_args) = src_ty.kind() else { unreachable!() }; + let src_fields = src_adt.all_fields().collect::>(); + for dst_field in dst_adt.all_fields() { + let Some(src_field) = src_fields.iter().find(|f| f.name == dst_field.name) else { continue; }; - let dest_ty = dest_field.ty(tcx, dest_args).skip_norm_wip(); - let borrowed_ty = borrowed_field.ty(tcx, borrowed_args).skip_norm_wip(); + let dst_ty = dst_field.ty(tcx, dst_args).skip_norm_wip(); + let src_ty = src_field.ty(tcx, src_args).skip_norm_wip(); if let ( - ty::Ref(borrow_region, _, Mutability::Mut), - ty::Ref(ref_region, _, Mutability::Not), - ) = (borrowed_ty.kind(), dest_ty.kind()) + ty::Ref(src_region, _, Mutability::Mut), + ty::Ref(dst_region, _, Mutability::Not), + ) = (src_ty.kind(), dst_ty.kind()) { + // FIXME(reborrow): the covariance relations here seem confused even after we + // flipped them around. relate_types does dst <: src while outlives does + // src <: dst. That seems incomprehensible. self.relate_types( - borrowed_ty.peel_refs(), + // dst <: src + src_ty.peel_refs(), ty::Variance::Covariant, - dest_ty.peel_refs(), + dst_ty.peel_refs(), location.to_locations(), category, ) .unwrap(); self.constraints.outlives_constraints.push(OutlivesConstraint { - sup: ref_region.as_var(), - sub: borrow_region.as_var(), + // 'src: 'dst + sup: src_region.as_var(), + sub: dst_region.as_var(), locations: location.to_locations(), span: location.to_locations().span(self.body), category, @@ -2594,9 +2606,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { }); } else { self.relate_types( - borrowed_ty, + // dst <: src + src_ty, ty::Variance::Covariant, - dest_ty, + dst_ty, location.to_locations(), category, ) @@ -2606,9 +2619,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } else { // Exclusive reborrow self.relate_types( - borrowed_ty, + src_ty, ty::Variance::Covariant, - dest_ty, + dst_ty, location.to_locations(), category, ) diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.rs b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs index a6acfd89e08ef..472efe3f37bb7 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.rs +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.rs @@ -14,10 +14,14 @@ fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { &() } +fn move_into(_: T) {} + fn main() { let a = CustomMarker(PhantomData); let b = method(a); let c = method(a); - let _ = (a, b, c); + move_into(a); //~^ ERROR: cannot move out of `a` because it is borrowed + let _ = b; + let _ = c; } diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr index 44318a4ca7494..e39d5e28c65b0 100644 --- a/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move.stderr @@ -1,16 +1,16 @@ error[E0505]: cannot move out of `a` because it is borrowed - --> $DIR/custom_marker_coerce_shared_move.rs:21:14 + --> $DIR/custom_marker_coerce_shared_move.rs:23:15 | LL | let a = CustomMarker(PhantomData); | - binding `a` declared here LL | let b = method(a); | - borrow of `a` occurs here LL | let c = method(a); -LL | let _ = (a, b, c); - | ^ - | | - | move out of `a` occurs here - | borrow later used here +LL | move_into(a); + | ^ + | | + | move out of `a` occurs here + | borrow later used here error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.rs b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.rs new file mode 100644 index 0000000000000..8b0ab4c9df417 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.rs @@ -0,0 +1,26 @@ +//! Test that CoerceShared of custom ZST marker type reborrows the type automatically as shared but +//! moving the original is possible afterwards if the shared results do not remain alive. +//! This should pass eventually. + +#![feature(reborrow)] +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +#[derive(Reborrow, CoerceShared)] +#[coerce_shared(CustomMarkerRef<'a>)] +struct CustomMarker<'a>(PhantomData<&'a ()>); +#[derive(Clone, Copy)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn move_into(_: T) {} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + move_into(a); + //~^ ERROR: cannot move out of `a` because it is borrowed +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.stderr new file mode 100644 index 0000000000000..19e41ea7bbdf2 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict.stderr @@ -0,0 +1,17 @@ +error[E0505]: cannot move out of `a` because it is borrowed + --> $DIR/custom_marker_coerce_shared_move_no_conflict.rs:24:15 + | +LL | let a = CustomMarker(PhantomData); + | - binding `a` declared here +LL | let b = method(a); + | - borrow of `a` occurs here +LL | let c = method(a); +LL | move_into(a); + | ^ + | | + | move out of `a` occurs here + | borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0505`. diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.rs b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.rs new file mode 100644 index 0000000000000..4519e36a40b61 --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.rs @@ -0,0 +1,27 @@ +//! Test that CoerceShared with manually set lifetime bounds does allow moving a reborrowable type +//! after CoerceShared. +//! This should probably work eventually, but right now it fails from trait well-formedness checks. + +#![feature(reborrow)] +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +#[derive(Reborrow)] +struct CustomMarker<'a>(PhantomData<&'a ()>); +impl<'a: 'b, 'b> CoerceShared> for CustomMarker<'a> {} +//~^ ERROR: implementing `CoerceShared` requires that a single lifetime parameter is passed between source and target + +#[derive(Clone, Copy)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn move_into(_: T) {} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + move_into(a); +} diff --git a/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.stderr b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.stderr new file mode 100644 index 0000000000000..ce928c86d835c --- /dev/null +++ b/tests/ui/reborrow/custom_marker_coerce_shared_move_no_conflict_manual.stderr @@ -0,0 +1,8 @@ +error: implementing `CoerceShared` requires that a single lifetime parameter is passed between source and target + --> $DIR/custom_marker_coerce_shared_move_no_conflict_manual.rs:10:1 + | +LL | impl<'a: 'b, 'b> CoerceShared> for CustomMarker<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error +