From b8e47e857e7c420514ebcff576d3fe1b7ca69f3b Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Fri, 29 May 2026 09:36:24 +0000 Subject: [PATCH] Support field-wise `CoerceShared` reborrows * Implement field-wise CoerceShared validation for reborrow * Support target-driven CoerceShared field relations * Test omitted CoerceShared source field soundness * Address CoerceShared field relation review * Test foreign CoerceShared private tuple field --- compiler/rustc_borrowck/src/borrow_set.rs | 24 +- compiler/rustc_borrowck/src/type_check/mod.rs | 215 ++++++++++---- .../src/type_check/relate_tys.rs | 48 +++- compiler/rustc_codegen_cranelift/src/base.rs | 57 +++- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 125 +++++++- .../rustc_const_eval/src/interpret/step.rs | 82 +++++- .../src/coherence/builtin.rs | 267 +++++++++++------- compiler/rustc_hir_analysis/src/errors.rs | 64 ++++- compiler/rustc_hir_typeck/src/coercion.rs | 25 +- compiler/rustc_middle/src/mir/syntax.rs | 14 +- compiler/rustc_middle/src/ty/mod.rs | 1 + compiler/rustc_middle/src/ty/reborrow.rs | 90 ++++++ .../auxiliary/reborrow_foreign_private.rs | 22 ++ .../coerce-shared-alias-projection.rs | 105 +++++++ .../coerce-shared-different-layout.rs | 42 +++ .../ui/reborrow/coerce-shared-extra-marker.rs | 37 +++ .../reborrow/coerce-shared-field-relations.rs | 54 ++++ .../coerce-shared-field-relations.stderr | 16 ++ .../coerce-shared-foreign-private-field.rs | 21 ++ ...coerce-shared-foreign-private-field.stderr | 7 + ...erce-shared-foreign-private-tuple-field.rs | 24 ++ ...-shared-foreign-private-tuple-field.stderr | 7 + tests/ui/reborrow/coerce-shared-generics.rs | 42 +++ .../coerce-shared-lifetime-mismatch.rs | 21 ++ .../coerce-shared-lifetime-mismatch.stderr | 23 ++ ...coerce-shared-marker-no-target-lifetime.rs | 20 ++ ...ce-shared-marker-no-target-lifetime.stderr | 23 ++ .../coerce-shared-missing-target-field.rs | 22 ++ .../coerce-shared-missing-target-field.stderr | 7 + .../ui/reborrow/coerce-shared-multi-field.rs | 59 ++++ tests/ui/reborrow/coerce-shared-nested.rs | 63 +++++ ...hared-omitted-reborrow-field-after-dead.rs | 52 ++++ ...ce-shared-omitted-reborrow-field-locked.rs | 52 ++++ ...hared-omitted-reborrow-field-locked.stderr | 15 + .../coerce-shared-omitted-reborrow-field.rs | 46 +++ .../reborrow/coerce-shared-reordered-field.rs | 33 +++ .../reborrow/coerce-shared-wrong-generic.rs | 23 ++ .../coerce-shared-wrong-generic.stderr | 10 + tests/ui/reborrow/coerce_shared_consteval.rs | 2 +- tests/ui/reborrow/issue-156309-corrected.rs | 25 ++ tests/ui/reborrow/issue-156309.rs | 29 ++ tests/ui/reborrow/issue-156309.stderr | 62 ++++ tests/ui/reborrow/issue-156315-corrected.rs | 21 ++ .../ui/reborrow/issue-156315-corrected.stderr | 10 + tests/ui/reborrow/issue-156315.rs | 23 ++ tests/ui/reborrow/issue-156315.stderr | 40 +++ 46 files changed, 1874 insertions(+), 196 deletions(-) create mode 100644 compiler/rustc_middle/src/ty/reborrow.rs create mode 100644 tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs create mode 100644 tests/ui/reborrow/coerce-shared-alias-projection.rs create mode 100644 tests/ui/reborrow/coerce-shared-different-layout.rs create mode 100644 tests/ui/reborrow/coerce-shared-extra-marker.rs create mode 100644 tests/ui/reborrow/coerce-shared-field-relations.rs create mode 100644 tests/ui/reborrow/coerce-shared-field-relations.stderr create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-field.stderr create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr create mode 100644 tests/ui/reborrow/coerce-shared-generics.rs create mode 100644 tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs create mode 100644 tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr create mode 100644 tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs create mode 100644 tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.stderr create mode 100644 tests/ui/reborrow/coerce-shared-missing-target-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-missing-target-field.stderr create mode 100644 tests/ui/reborrow/coerce-shared-multi-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-nested.rs create mode 100644 tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs create mode 100644 tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs create mode 100644 tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr create mode 100644 tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-reordered-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-wrong-generic.rs create mode 100644 tests/ui/reborrow/coerce-shared-wrong-generic.stderr create mode 100644 tests/ui/reborrow/issue-156309-corrected.rs create mode 100644 tests/ui/reborrow/issue-156309.rs create mode 100644 tests/ui/reborrow/issue-156309.stderr create mode 100644 tests/ui/reborrow/issue-156315-corrected.rs create mode 100644 tests/ui/reborrow/issue-156315-corrected.stderr create mode 100644 tests/ui/reborrow/issue-156315.rs create mode 100644 tests/ui/reborrow/issue-156315.stderr diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index 6d68703642314..2d22b736c67df 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -305,15 +305,29 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { } 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!() + self.tcx.dcx().span_delayed_bug( + self.body.source_info(location).span, + format!("generic reborrow source is not an ADT: {borrowed_place_ty:?}"), + ); + return; + }; + let &ty::Adt(target_adt, assigned_args) = target.kind() else { + self.tcx.dcx().span_delayed_bug( + self.body.source_info(location).span, + format!("generic reborrow target is not an ADT: {target:?}"), + ); + return; }; - 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" } + self.tcx.dcx().span_delayed_bug( + self.body.source_info(location).span, + format!( + "hir-typeck passed but {} does not have a lifetime argument", + if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" } + ), ); + return; }; let region = region.as_var(); let kind = if mutability == Mutability::Mut { diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 13983f349d6a5..b30d6542a92e5 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -25,6 +25,7 @@ use rustc_middle::mir::*; use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::cast::CastTy; +use rustc_middle::ty::reborrow::{self, CoerceSharedFieldPairError}; use rustc_middle::ty::{ self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, CoroutineArgsExt, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, UserArgs, UserTypeAnnotationIndex, fold_regions, @@ -2489,9 +2490,23 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let borrowed_ty = borrowed_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!() }; + let ty::Adt(_, dest_args) = dest_ty.kind() else { + span_mirbug!( + self, + borrowed_place, + "generic reborrow target is not an ADT: {dest_ty:?}" + ); + return; + }; + let Some(ty::GenericArgKind::Lifetime(dest_region)) = dest_args.get(0).map(|r| r.kind()) + else { + span_mirbug!( + self, + borrowed_place, + "generic reborrow target does not have a lifetime argument: {dest_ty:?}" + ); + return; + }; constraints.liveness_constraints.add_location(dest_region.as_var(), location); // In Polonius mode, we also push a `loan_issued_at` fact @@ -2509,65 +2524,157 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } 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 - // calling relate_types as they are (generally) two unrelated user-defined ADTs, such as - // `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 { - 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(); - if let ( - ty::Ref(borrow_region, _, Mutability::Mut), - ty::Ref(ref_region, _, Mutability::Not), - ) = (borrowed_ty.kind(), dest_ty.kind()) - { - self.relate_types( - borrowed_ty.peel_refs(), - ty::Variance::Covariant, - dest_ty.peel_refs(), - location.to_locations(), - category, - ) - .unwrap(); - self.constraints.outlives_constraints.push(OutlivesConstraint { - sup: ref_region.as_var(), - sub: borrow_region.as_var(), - locations: location.to_locations(), - span: location.to_locations().span(self.body), - category, - variance_info: ty::VarianceDiagInfo::default(), - from_closure: false, - }); - } else { - self.relate_types( - borrowed_ty, - ty::Variance::Covariant, - dest_ty, - location.to_locations(), - category, - ) - .unwrap(); - } + // CoerceShared relates distinct ADTs field-wise. Impl validation guarantees that + // every target field has the corresponding source field and that each field relation + // is Copy-compatible, recursively CoerceShared-compatible, or `&mut T` to `&T`. + // The loan itself is still issued for `borrowed_place` as a whole, so source-only + // fields remain protected for the inferred target lifetime. Under the current + // single-lifetime impl model, that is the source lifetime. + if self + .add_generic_shared_reborrow_constraints( + location, + borrowed_place, + borrowed_ty, + dest_ty, + category, + ) + .is_err() + { + return; } } else { // Exclusive reborrow - self.relate_types( + if let Err(terr) = self.relate_types( borrowed_ty, ty::Variance::Covariant, dest_ty, location.to_locations(), category, - ) - .unwrap(); + ) { + span_mirbug!( + self, + borrowed_place, + "bad generic exclusive reborrow relation ({:?}: {:?}): {:?}", + borrowed_ty, + dest_ty, + terr + ); + } + } + } + + fn add_generic_shared_reborrow_constraints( + &mut self, + location: Location, + borrowed_place: &Place<'tcx>, + borrowed_ty: Ty<'tcx>, + dest_ty: Ty<'tcx>, + category: ConstraintCategory<'tcx>, + ) -> Result<(), ()> { + let tcx = self.tcx(); + let borrowed_ty = self.normalize(ty::Unnormalized::new_wip(borrowed_ty), location); + let dest_ty = self.normalize(ty::Unnormalized::new_wip(dest_ty), location); + + if let ( + ty::Ref(borrow_region, _, Mutability::Mut), + ty::Ref(ref_region, _, Mutability::Not), + ) = (borrowed_ty.kind(), dest_ty.kind()) + { + if let Err(terr) = self.relate_types_structurally_relating_aliases( + borrowed_ty.peel_refs(), + ty::Variance::Covariant, + dest_ty.peel_refs(), + location.to_locations(), + category, + ) { + span_mirbug!( + self, + borrowed_place, + "bad generic shared reborrow field relation ({:?}: {:?}): {:?}", + borrowed_ty, + dest_ty, + terr + ); + return Err(()); + } + + self.constraints.outlives_constraints.push(OutlivesConstraint { + sup: ref_region.as_var(), + sub: borrow_region.as_var(), + locations: location.to_locations(), + span: location.to_locations().span(self.body), + category, + variance_info: ty::VarianceDiagInfo::default(), + from_closure: false, + }); + return Ok(()); + } + + match (borrowed_ty.kind(), dest_ty.kind()) { + (ty::Adt(borrowed_adt, borrowed_args), ty::Adt(dest_adt, dest_args)) + if borrowed_adt.did() != dest_adt.did() => + { + let field_pairs = match reborrow::coerce_shared_field_pairs( + tcx, + *borrowed_adt, + *borrowed_args, + *dest_adt, + *dest_args, + ) { + Ok(field_pairs) => field_pairs, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + span_mirbug!( + self, + borrowed_place, + "generic shared reborrow has unsupported field structure: \ + {borrowed_ty:?} -> {dest_ty:?}" + ); + return Err(()); + } + Err(CoerceSharedFieldPairError::MissingSourceField { .. }) => { + span_mirbug!( + self, + borrowed_place, + "generic shared reborrow is missing a source field: \ + {borrowed_ty:?} -> {dest_ty:?}" + ); + return Err(()); + } + }; + + for field_pair in field_pairs { + self.add_generic_shared_reborrow_constraints( + location, + borrowed_place, + field_pair.source.ty, + field_pair.target.ty, + category, + )?; + } + + Ok(()) + } + _ => { + if let Err(terr) = self.relate_types_structurally_relating_aliases( + borrowed_ty, + ty::Variance::Covariant, + dest_ty, + location.to_locations(), + category, + ) { + span_mirbug!( + self, + borrowed_place, + "bad generic shared reborrow field relation ({:?}: {:?}): {:?}", + borrowed_ty, + dest_ty, + terr + ); + Err(()) + } else { + Ok(()) + } + } } } diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index f18884df0b44d..de9efb14bf782 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -40,8 +40,35 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { locations: Locations, category: ConstraintCategory<'tcx>, ) -> Result<(), NoSolution> { - NllTypeRelating::new(self, locations, category, UniverseInfo::relate(a, b), v) - .relate(a, b)?; + NllTypeRelating::new( + self, + locations, + category, + UniverseInfo::relate(a, b), + v, + StructurallyRelateAliases::No, + ) + .relate(a, b)?; + Ok(()) + } + + pub(super) fn relate_types_structurally_relating_aliases( + &mut self, + a: Ty<'tcx>, + v: ty::Variance, + b: Ty<'tcx>, + locations: Locations, + category: ConstraintCategory<'tcx>, + ) -> Result<(), NoSolution> { + NllTypeRelating::new( + self, + locations, + category, + UniverseInfo::relate(a, b), + v, + StructurallyRelateAliases::Yes, + ) + .relate(a, b)?; Ok(()) } @@ -53,8 +80,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { locations: Locations, category: ConstraintCategory<'tcx>, ) -> Result<(), NoSolution> { - NllTypeRelating::new(self, locations, category, UniverseInfo::other(), ty::Invariant) - .relate(a, b)?; + NllTypeRelating::new( + self, + locations, + category, + UniverseInfo::other(), + ty::Invariant, + StructurallyRelateAliases::No, + ) + .relate(a, b)?; Ok(()) } } @@ -81,6 +115,8 @@ struct NllTypeRelating<'a, 'b, 'tcx> { ambient_variance: ty::Variance, ambient_variance_info: ty::VarianceDiagInfo>, + + structurally_relate_aliases: StructurallyRelateAliases, } impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { @@ -90,6 +126,7 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { category: ConstraintCategory<'tcx>, universe_info: UniverseInfo<'tcx>, ambient_variance: ty::Variance, + structurally_relate_aliases: StructurallyRelateAliases, ) -> Self { Self { type_checker, @@ -98,6 +135,7 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { universe_info, ambient_variance, ambient_variance_info: ty::VarianceDiagInfo::default(), + structurally_relate_aliases, } } @@ -553,7 +591,7 @@ impl<'b, 'tcx> PredicateEmittingRelation> for NllTypeRelating<'_ } fn structurally_relate_aliases(&self) -> StructurallyRelateAliases { - StructurallyRelateAliases::No + self.structurally_relate_aliases } fn param_env(&self) -> ty::ParamEnv<'tcx> { diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 1d90c8e0dadcf..269fafe2027b8 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -12,6 +12,7 @@ use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv as _}; use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::reborrow::{self, CoerceSharedFieldPairError}; use rustc_session::config::OutputFilenames; use rustc_span::Symbol; @@ -629,10 +630,8 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: let ref_ = place.place_ref(fx, lval.layout()); lval.write_cvalue(fx, ref_); } - Rvalue::Reborrow(_, _, place) => { - let cplace = codegen_place(fx, place); - let val = cplace.to_cvalue(fx); - lval.write_cvalue(fx, val) + Rvalue::Reborrow(target_ty, _, place) => { + codegen_reborrow_into(fx, target_ty, place, lval); } Rvalue::ThreadLocalRef(def_id) => { let val = crate::constant::codegen_tls_ref(fx, def_id, lval.layout()); @@ -954,6 +953,56 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: } } +fn codegen_reborrow_into<'tcx>( + fx: &mut FunctionCx<'_, '_, 'tcx>, + target_ty: Ty<'tcx>, + source_place: Place<'tcx>, + dest: CPlace<'tcx>, +) { + let source_ty = fx.monomorphize(source_place.ty(fx.mir, fx.tcx).ty); + let target_ty = fx.monomorphize(target_ty); + let (ty::Adt(source_def, source_args), ty::Adt(target_def, target_args)) = + (source_ty.kind(), target_ty.kind()) + else { + let source = codegen_place(fx, source_place).to_cvalue(fx); + dest.write_cvalue(fx, source); + return; + }; + + if source_def.did() == target_def.did() { + let source = codegen_place(fx, source_place).to_cvalue(fx); + dest.write_cvalue(fx, source); + return; + } + + let field_pairs = match reborrow::coerce_shared_field_pairs( + fx.tcx, + *source_def, + *source_args, + *target_def, + *target_args, + ) { + Ok(field_pairs) => field_pairs, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + bug!("generic shared reborrow has mismatched field styles"); + } + Err(CoerceSharedFieldPairError::MissingSourceField { .. }) => { + bug!("generic shared reborrow is missing a source field"); + } + }; + + for field_pair in field_pairs { + let source_field_ty = fx.monomorphize(field_pair.source.ty); + let target_field_ty = fx.monomorphize(field_pair.target.ty); + let source_field_place = source_place + .project_deeper(&[PlaceElem::Field(field_pair.source.index, source_field_ty)], fx.tcx); + let field_dest = dest.place_field(fx, field_pair.target.index); + codegen_reborrow_into(fx, target_field_ty, source_field_place, field_dest); + } + + crate::discriminant::codegen_set_discriminant(fx, dest, FIRST_VARIANT); +} + fn codegen_array_len<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, place: CPlace<'tcx>) -> Value { match *place.layout().ty.kind() { ty::Array(_elem_ty, len) => { diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 90ac8c89ba9ad..b125fadf75445 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -1,7 +1,8 @@ use itertools::Itertools as _; -use rustc_abi::{self as abi, BackendRepr, FIRST_VARIANT}; +use rustc_abi::{self as abi, BackendRepr, FIRST_VARIANT, FieldIdx}; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout}; +use rustc_middle::ty::reborrow::{self, CoerceSharedFieldPairError}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_middle::{bug, mir, span_bug}; use rustc_session::config::OptLevel; @@ -195,6 +196,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { dest.codegen_set_discr(bx, variant_index); } + mir::Rvalue::Reborrow(target_ty, _, place) => { + self.codegen_reborrow_into(bx, target_ty, place, dest); + } + _ => { let temp = self.codegen_rvalue_operand(bx, rvalue); temp.store_with_annotation(bx, dest); @@ -202,6 +207,116 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } + fn codegen_reborrow_into( + &mut self, + bx: &mut Bx, + target_ty: Ty<'tcx>, + source_place: mir::Place<'tcx>, + dest: PlaceRef<'tcx, Bx::Value>, + ) { + let source_ty = source_place.ty(self.mir, bx.tcx()).ty; + let Some(fields) = self.reborrow_field_pairs(source_place, source_ty, target_ty) else { + let op = self.codegen_reborrow_operand(bx, target_ty, source_place); + op.store_with_annotation(bx, dest); + return; + }; + + let variant_dest = dest.project_downcast(bx, FIRST_VARIANT); + for (target_field, target_field_ty, source_field_place) in fields { + let target_field_dest = variant_dest.project_field(bx, target_field.as_usize()); + self.codegen_reborrow_into(bx, target_field_ty, source_field_place, target_field_dest); + } + dest.codegen_set_discr(bx, FIRST_VARIANT); + } + + fn codegen_reborrow_operand( + &mut self, + bx: &mut Bx, + target_ty: Ty<'tcx>, + source_place: mir::Place<'tcx>, + ) -> OperandRef<'tcx, Bx::Value> { + let target_ty = self.monomorphize(target_ty); + let target_layout = self.cx.layout_of(target_ty); + if target_layout.is_zst() { + return OperandRef { + val: OperandValue::ZeroSized, + layout: target_layout, + move_annotation: None, + }; + } + + let source_ty = source_place.ty(self.mir, bx.tcx()).ty; + let Some(fields) = self.reborrow_field_pairs(source_place, source_ty, target_ty) else { + let source = self.codegen_operand(bx, &mir::Operand::Copy(source_place)); + return OperandRef { val: source.val, layout: target_layout, move_annotation: None }; + }; + + if let BackendRepr::Memory { .. } = target_layout.backend_repr { + let scratch = PlaceRef::alloca(bx, target_layout); + self.codegen_reborrow_into(bx, target_ty, source_place, scratch); + return OperandRef { + val: OperandValue::Ref(scratch.val), + layout: target_layout, + move_annotation: None, + }; + } + + let mut builder = OperandRefBuilder::new(target_layout); + for (target_field, target_field_ty, source_field_place) in fields { + let op = self.codegen_reborrow_operand(bx, target_field_ty, source_field_place); + builder.insert_field(bx, FIRST_VARIANT, target_field, op); + } + builder.build(bx.cx()) + } + + fn reborrow_field_pairs( + &self, + source_place: mir::Place<'tcx>, + source_ty: Ty<'tcx>, + target_ty: Ty<'tcx>, + ) -> Option, mir::Place<'tcx>)>> { + let source_ty = self.monomorphize(source_ty); + let target_ty = self.monomorphize(target_ty); + let (ty::Adt(source_def, source_args), ty::Adt(target_def, target_args)) = + (source_ty.kind(), target_ty.kind()) + else { + return None; + }; + if source_def.did() == target_def.did() { + return None; + } + + let tcx = self.cx.tcx(); + let field_pairs = match reborrow::coerce_shared_field_pairs( + tcx, + *source_def, + *source_args, + *target_def, + *target_args, + ) { + Ok(field_pairs) => field_pairs, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + bug!("generic shared reborrow has mismatched field styles"); + } + Err(CoerceSharedFieldPairError::MissingSourceField { .. }) => { + bug!("generic shared reborrow is missing a source field"); + } + }; + + let mut fields = Vec::new(); + for field_pair in field_pairs { + let source_field_ty = self.monomorphize(field_pair.source.ty); + let target_field_ty = self.monomorphize(field_pair.target.ty); + let source_field_place = source_place.project_deeper( + &[mir::ProjectionElem::Field(field_pair.source.index, source_field_ty)], + tcx, + ); + fields.push((field_pair.target.index, target_field_ty, source_field_place)); + } + + Some(fields) + } + /// Transmutes the `src` value to the destination type by writing it to `dst`. /// /// See also [`Self::codegen_transmute_operand`] for cases that can be done @@ -525,12 +640,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.codegen_place_to_pointer(bx, place, mk_ref) } - // Note: Exclusive reborrowing is always equal to a memcpy, as the types do not change. - // Generic shared reborrowing is not (necessarily) a simple memcpy, but currently the - // coherence check places such restrictions on the CoerceShared trait as to guarantee - // that it is. - mir::Rvalue::Reborrow(_, _, place) => { - self.codegen_operand(bx, &mir::Operand::Copy(place)) + mir::Rvalue::Reborrow(target_ty, _, place) => { + self.codegen_reborrow_operand(bx, target_ty, place) } mir::Rvalue::RawPtr(kind, place) => { diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 0718638c1d219..97f9d32e256aa 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -8,6 +8,7 @@ use either::Either; use rustc_abi::{FIRST_VARIANT, FieldIdx}; use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexSlice; +use rustc_middle::ty::reborrow::{self, CoerceSharedFieldPairError}; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::{bug, mir, span_bug}; use rustc_span::Spanned; @@ -230,15 +231,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { })?; } - Reborrow(_, mutability, place) => { - let op = self.eval_place_to_op(place, None)?; - if mutability.is_not() { - // Shared generic reborrows use `CoerceShared`: a bitwise copy into a - // distinct same-layout target ADT. - self.copy_op_allow_transmute(&op, &dest)?; - } else { - self.copy_op(&op, &dest)?; - } + Reborrow(target_ty, _, place) => { + self.eval_reborrow_into_place(target_ty, place, &dest)?; } RawPtr(kind, place) => { @@ -292,6 +286,76 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(()) } + fn eval_reborrow_into_place( + &mut self, + target_ty: Ty<'tcx>, + source_place: mir::Place<'tcx>, + dest: &PlaceTy<'tcx, M::Provenance>, + ) -> InterpResult<'tcx> { + let tcx = *self.tcx; + let source_ty = self.instantiate_from_current_frame_and_normalize_erasing_regions( + source_place.ty(self.body(), tcx).ty, + )?; + let target_ty = + self.instantiate_from_current_frame_and_normalize_erasing_regions(target_ty)?; + let (ty::Adt(source_def, source_args), ty::Adt(target_def, target_args)) = + (source_ty.kind(), target_ty.kind()) + else { + let op = self.eval_place_to_op(source_place, Some(dest.layout))?; + self.copy_op(&op, dest)?; + return interp_ok(()); + }; + + if source_def.did() == target_def.did() { + let op = self.eval_place_to_op(source_place, Some(dest.layout))?; + self.copy_op(&op, dest)?; + return interp_ok(()); + } + + let field_pairs = match reborrow::coerce_shared_field_pairs( + tcx, + *source_def, + *source_args, + *target_def, + *target_args, + ) { + Ok(field_pairs) => field_pairs, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + bug!("generic shared reborrow has mismatched field styles"); + } + Err(CoerceSharedFieldPairError::MissingSourceField { .. }) => { + bug!("generic shared reborrow is missing a source field"); + } + }; + + for field_pair in field_pairs { + let source_field_ty = self + .instantiate_from_current_frame_and_normalize_erasing_regions( + field_pair.source.ty, + )?; + let target_field_ty = self + .instantiate_from_current_frame_and_normalize_erasing_regions( + field_pair.target.ty, + )?; + let source_field_place = source_place.project_deeper( + &[mir::ProjectionElem::Field(field_pair.source.index, source_field_ty)], + tcx, + ); + let field_dest = self.project_field(dest, field_pair.target.index)?; + self.eval_reborrow_into_place(target_field_ty, source_field_place, &field_dest)?; + } + + self.write_discriminant(FIRST_VARIANT, dest)?; + if M::enforce_validity(self, dest.layout()) { + self.validate_operand( + dest, + M::enforce_validity_recursively(self, dest.layout()), + /*reset_provenance_and_padding*/ true, + )?; + } + interp_ok(()) + } + /// Writes the aggregate to the destination. #[instrument(skip(self), level = "trace")] fn write_aggregate( diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 2ba7e026461f3..bba6e0a59218f 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -13,6 +13,7 @@ use rustc_infer::infer::{self, InferCtxt, RegionResolutionError, SubregionOrigin use rustc_infer::traits::{Obligation, PredicateObligations}; use rustc_middle::ty::adjustment::CoerceUnsizedInfo; use rustc_middle::ty::print::PrintTraitRefExt as _; +use rustc_middle::ty::reborrow::{self, CoerceSharedFieldPairError, ReborrowField}; use rustc_middle::ty::relate::solver_relating::RelateExt; use rustc_middle::ty::{ self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, suggest_constraining_type_params, @@ -533,7 +534,7 @@ pub(crate) fn reborrow_info<'tcx>( }; let lifetimes_count = generic_lifetime_params_count(args); - let data_fields = collect_struct_data_fields(tcx, def, args); + let data_fields = reborrow::reborrow_data_fields(tcx, def, args); if lifetimes_count != 1 { let item = tcx.hir_expect_item(impl_did); @@ -551,15 +552,15 @@ pub(crate) fn reborrow_info<'tcx>( } // We've found some data fields. They must all be either be Copy or Reborrow. - for (field, span) in data_fields { + for field in data_fields { if assert_field_type_is_reborrow( tcx, &infcx, reborrow_trait, impl_did, param_env, - field, - span, + field.ty, + field.span, ) .is_ok() { @@ -568,7 +569,7 @@ pub(crate) fn reborrow_info<'tcx>( } // Field does not implement Reborrow: it must be Copy. - assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, field, span)?; + assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, field.ty, field.span)?; } Ok(()) @@ -631,26 +632,14 @@ pub(crate) fn coerce_shared_info<'tcx>( let param_env = tcx.param_env(impl_did); assert!(!source.has_escaping_bound_vars()); - let data = match (source.kind(), target.kind()) { + match (source.kind(), target.kind()) { (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) if def_a.is_struct() && def_b.is_struct() => { - // Check that both A and B have exactly one lifetime argument, and that they have the - // same number of data fields that is not more than 1. The eventual intention is to - // support multiple lifetime arguments (with the reborrowed lifetimes inferred from - // usage one way or another) and multiple data fields with B allowed to leave out fields - // from A. The current state is just the simplest choice. - let a_lifetimes_count = generic_lifetime_params_count(args_a); - let a_data_fields = collect_struct_data_fields(tcx, def_a, args_a); - let b_lifetimes_count = generic_lifetime_params_count(args_b); - let b_data_fields = collect_struct_data_fields(tcx, def_b, args_b); - - if a_lifetimes_count != 1 - || b_lifetimes_count != 1 - || a_data_fields.len() > 1 - || b_data_fields.len() > 1 - || a_data_fields.len() != b_data_fields.len() - { + let a_lifetime = reborrow::single_lifetime_arg(args_a); + let b_lifetime = reborrow::single_lifetime_arg(args_b); + + if a_lifetime.is_none() || b_lifetime.is_none() { let item = tcx.hir_expect_item(impl_did); let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = &item.kind @@ -663,77 +652,44 @@ pub(crate) fn coerce_shared_info<'tcx>( return Err(tcx.dcx().emit_err(errors::CoerceSharedMulti { span, trait_name })); } - if a_data_fields.len() == 1 { - // We found one data field for both: we'll attempt to perform CoerceShared between - // them below. - let (a, span_a) = a_data_fields[0]; - let (b, span_b) = b_data_fields[0]; + if a_lifetime != b_lifetime { + let item = tcx.hir_expect_item(impl_did); + let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = + &item.kind + { + of_trait.trait_ref.path.span + } else { + tcx.def_span(impl_did) + }; - Some((a, b, coerce_shared_trait, span_a, span_b)) - } else { - // We found no data fields in either: this is a reborrowable marker type being - // coerced into a shared marker. That is fine too. - None + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedLifetimeMismatch { span, trait_name })); } - } - _ => { - // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. - return Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })); - } - }; + validate_reborrow_field_access(tcx, impl_did, def_a, trait_name, span)?; + validate_reborrow_field_access(tcx, impl_did, def_b, trait_name, span)?; - // We've proven that we have two types with one lifetime each and 0 or 1 data fields each. - if let Some((source, target, trait_def_id, source_field_span, _target_field_span)) = data { - // struct Source(SourceData); - // struct Target(TargetData); - // - // 1 data field each; they must be the same type and Copy, or relate to one another using - // CoerceShared. - if source.ref_mutability() == Some(ty::Mutability::Mut) - && target.ref_mutability() == Some(ty::Mutability::Not) - && infcx - .eq_structurally_relating_aliases( - param_env, - source.peel_refs(), - target.peel_refs(), - source_field_span, - ) - .is_ok() - { - // &mut T implements CoerceShared to &T, except not really. - return Ok(()); - } - if infcx - .eq_structurally_relating_aliases(param_env, source, target, source_field_span) - .is_err() - { - // The two data fields don't agree on a common type; this means - // that they must be `A: CoerceShared`. Register an obligation - // for that. - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); - let cause = traits::ObligationCause::misc(span, impl_did); - let obligation = Obligation::new( + validate_coerce_shared_fields( tcx, - cause, + &infcx, + impl_did, param_env, - ty::TraitRef::new(tcx, trait_def_id, [source, target]), - ); - ocx.register_obligation(obligation); - let errors = ocx.evaluate_obligations_error_on_ambiguity(); + coerce_shared_trait, + trait_name, + span, + def_a, + args_a, + def_b, + args_b, + ) + } - if !errors.is_empty() { - return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); - } - // Finally, resolve all regions. - ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; - } else { - // Types match: check that it is Copy. - assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, source, source_field_span)?; + _ => { + // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. + Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })) } } - - Ok(()) } fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize { @@ -748,24 +704,133 @@ fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize { args.iter().filter(|arg| arg.as_region().is_some()).count() } -// FIXME(#155345): This should return `Unnormalized` -fn collect_struct_data_fields<'tcx>( +fn validate_reborrow_field_access( + tcx: TyCtxt<'_>, + impl_did: LocalDefId, + def: ty::AdtDef<'_>, + trait_name: &'static str, + span: Span, +) -> Result<(), ErrorGuaranteed> { + let module = tcx.parent_module_from_def_id(impl_did); + let variant = def.non_enum_variant(); + if variant.field_list_has_applicable_non_exhaustive() { + return Err(tcx.dcx().emit_err(errors::CoerceSharedInaccessibleField { span, trait_name })); + } + + for field in &variant.fields { + if !field.vis.is_accessible_from(module, tcx) { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedInaccessibleField { span, trait_name })); + } + } + + Ok(()) +} + +fn validate_coerce_shared_fields<'tcx>( tcx: TyCtxt<'tcx>, - def: ty::AdtDef<'tcx>, - args: ty::GenericArgsRef<'tcx>, -) -> Vec<(Ty<'tcx>, Span)> { - def.non_enum_variant() - .fields - .iter() - .filter_map(|f| { - // Ignore PhantomData fields - let ty = f.ty(tcx, args).skip_norm_wip(); - if ty.is_phantom_data() { - return None; - } - Some((ty, tcx.def_span(f.did))) - }) - .collect() + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let field_pairs = match reborrow::coerce_shared_field_pairs( + tcx, + source_def, + source_args, + target_def, + target_args, + ) { + Ok(field_pairs) => field_pairs, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedFieldStyleMismatch { span, trait_name })); + } + Err(CoerceSharedFieldPairError::MissingSourceField { target }) => { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedMissingField { span: target.span, trait_name })); + } + }; + + for field_pair in field_pairs { + validate_coerce_shared_field( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + field_pair.source, + field_pair.target, + )?; + } + + Ok(()) +} + +fn validate_coerce_shared_field<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + source: ReborrowField<'tcx>, + target: ReborrowField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + if source.ty.ref_mutability() == Some(ty::Mutability::Mut) + && target.ty.ref_mutability() == Some(ty::Mutability::Not) + && infcx + .eq_structurally_relating_aliases( + param_env, + source.ty.peel_refs(), + target.ty.peel_refs(), + source.span, + ) + .is_ok() + { + return Ok(()); + } + + if infcx.eq_structurally_relating_aliases(param_env, source.ty, target.ty, source.span).is_ok() + { + return assert_field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span); + } + + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + ocx.register_obligation(Obligation::new( + tcx, + cause, + param_env, + ty::TraitRef::new(tcx, coerce_shared_trait, [source.ty, target.ty]), + )); + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + + if !errors.is_empty() { + return Err(tcx.dcx().emit_err(errors::CoerceSharedFieldMismatch { + span: target.span, + source_span: source.span, + source_name: source.name, + source_ty: source.ty, + target_name: target.name, + target_ty: target.ty, + trait_name, + })); + } + + ocx.resolve_regions_and_report_errors(impl_did, param_env, []) } fn assert_field_type_is_copy<'tcx>( diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 0f549e157c280..f3af2830e7374 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1371,13 +1371,75 @@ pub(crate) struct CoerceSharedNotSingleLifetimeParam { } #[derive(Diagnostic)] -#[diag("implementing `{$trait_name}` does not allow multiple lifetimes or fields to be coerced")] +#[diag( + "implementing `{$trait_name}` requires exactly one lifetime argument in the reborrowed type" +)] pub(crate) struct CoerceSharedMulti { #[primary_span] pub span: Span, pub trait_name: &'static str, } +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires source and target to use the same reborrow lifetime \ + argument" +)] +pub(crate) struct CoerceSharedLifetimeMismatch { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires corresponding fields to match, \ + be reborrowable with `CoerceShared`, or coerce a mutable reference field \ + to a shared reference field" +)] +pub(crate) struct CoerceSharedFieldMismatch<'tcx> { + #[primary_span] + #[label("target field `{$target_name}` has type `{$target_ty}`")] + pub span: Span, + #[label("source field `{$source_name}` has type `{$source_ty}`")] + pub source_span: Span, + pub source_name: Symbol, + pub source_ty: Ty<'tcx>, + pub target_name: Symbol, + pub target_ty: Ty<'tcx>, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires every target field to have a corresponding source field" +)] +pub(crate) struct CoerceSharedMissingField { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires source and target structs to use the same field style" +)] +pub(crate) struct CoerceSharedFieldStyleMismatch { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires all source and target fields to be accessible from the impl" +)] +pub(crate) struct CoerceSharedInaccessibleField { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + #[derive(Diagnostic)] #[diag("the trait `{$trait_name}` may only be implemented for a coercion between structures", code = E0377)] pub(crate) struct CoerceUnsizedNonStruct { diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 7a25b5e5e7a4e..f216d63f2071e 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -54,7 +54,7 @@ use rustc_middle::ty::adjustment::{ PointerCoercion, }; use rustc_middle::ty::error::TypeError; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized, reborrow}; use rustc_span::{BytePos, DUMMY_SP, Span}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor}; @@ -963,7 +963,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Ok(coerce) } - /// Applies generic exclusive reborrowing on type implementing `Reborrow`. + /// Applies generic shared reborrowing on types implementing `CoerceShared`. #[instrument(skip(self), level = "trace")] fn coerce_reborrow(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug_assert!(self.shallow_resolve(a) == a); @@ -1005,6 +1005,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { let Some(coerce_shared_trait_did) = self.tcx.lang_items().coerce_shared() else { return Err(TypeError::Mismatch); }; + // This is only a shape guard for producing well-formed MIR adjustments. The field-type + // relation remains owned by the `CoerceShared` obligation and builtin impl validation. + if !self.coerce_shared_reborrow_structurally_well_formed(a, b) { + return Err(TypeError::Mismatch); + } let coerce_shared_trait_ref = ty::TraitRef::new(self.tcx, coerce_shared_trait_did, [a, b]); let obligation = traits::Obligation::new( self.tcx, @@ -1031,6 +1036,22 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { } } + fn coerce_shared_reborrow_structurally_well_formed(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + let (ty::Adt(a_def, a_args), ty::Adt(b_def, b_args)) = (a.kind(), b.kind()) else { + return false; + }; + if !a_def.is_struct() || !b_def.is_struct() { + return false; + } + if reborrow::single_lifetime_arg(a_args).is_none() + || reborrow::single_lifetime_arg(b_args).is_none() + { + return false; + } + + reborrow::coerce_shared_field_pairs(self.tcx, *a_def, *a_args, *b_def, *b_args).is_ok() + } + fn coerce_from_fn_pointer( &self, a: Ty<'tcx>, diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 8b015e6cecaae..3406a2c5cc677 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1462,19 +1462,19 @@ pub enum Rvalue<'tcx> { /// Wraps a value in an unsafe binder. WrapUnsafeBinder(Operand<'tcx>, Ty<'tcx>), - /// Creates a bitwise copy of the indicated place with the same type (if Mut) or its - /// CoerceShared target type (if Not). The type is known to be an ADT with exactly one lifetime - /// parameter, and it is known to implement the Reborrow trait (for Mut), and the CoerceShared - /// trait (only if Not). The CoerceShared target type is known to also have exactly one lifetime - /// parameter, implement Copy and (currently) have the same memory layout as the source type. + /// Creates a copy of the indicated place with the same type (if Mut) or its CoerceShared + /// target type (if Not). The type is known to be an ADT with exactly one lifetime parameter, + /// and it is known to implement the Reborrow trait (for Mut), and the CoerceShared trait + /// (only if Not). The CoerceShared target type is known to also have exactly one lifetime + /// parameter and implement Copy. /// /// The borrow checker uses the single lifetime in the source and target types to create a /// Covariant outlives-bound between the source and target with the Mutability of the Reborrow. /// This makes accessing the source value for writes (and reads if Mut) for the lifetime of the /// target value a borrow check error, imitating `&mut T` and `&T`'s reborrowing on user ADTs. /// - /// Future work may add support for multiple lifetimes and changing memory layout as part of - /// CoerceShared. These may be end up implemented as multiple MIR operations. + /// Generic shared reborrows are lowered field-wise according to the validated CoerceShared + /// source-to-target field correspondence. /// /// This is produced by the [`ExprKind::Reborrow`]. /// diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 74f9e75fb48c0..83d8e6f437318 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -141,6 +141,7 @@ pub mod normalize_erasing_regions; pub mod offload_meta; pub mod pattern; pub mod print; +pub mod reborrow; pub mod relate; pub mod significant_drop_order; pub mod trait_def; diff --git a/compiler/rustc_middle/src/ty/reborrow.rs b/compiler/rustc_middle/src/ty/reborrow.rs new file mode 100644 index 0000000000000..2fb05b604de1f --- /dev/null +++ b/compiler/rustc_middle/src/ty/reborrow.rs @@ -0,0 +1,90 @@ +use rustc_abi::FieldIdx; +use rustc_hir::def::CtorKind; +use rustc_span::{Span, Symbol}; + +use crate::ty::{self, Ty, TyCtxt}; + +#[derive(Clone, Copy, Debug)] +pub struct ReborrowField<'tcx> { + pub index: FieldIdx, + pub name: Symbol, + pub ty: Ty<'tcx>, + pub span: Span, +} + +#[derive(Clone, Copy, Debug)] +pub struct CoerceSharedFieldPair<'tcx> { + pub source: ReborrowField<'tcx>, + pub target: ReborrowField<'tcx>, +} + +#[derive(Clone, Copy, Debug)] +pub enum CoerceSharedFieldPairError<'tcx> { + FieldStyleMismatch, + MissingSourceField { target: ReborrowField<'tcx> }, +} + +pub fn single_lifetime_arg<'tcx>(args: ty::GenericArgsRef<'tcx>) -> Option> { + let mut lifetimes = args.iter().filter_map(|arg| arg.as_region()); + let lifetime = lifetimes.next()?; + lifetimes.next().is_none().then_some(lifetime) +} + +/// Returns the instantiated non-`PhantomData` fields that participate in generic reborrows. +pub fn reborrow_data_fields<'tcx>( + tcx: TyCtxt<'tcx>, + def: ty::AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, +) -> Vec> { + def.non_enum_variant() + .fields + .iter_enumerated() + .filter_map(|(index, field)| { + let ty = field.ty(tcx, args).skip_norm_wip(); + (!ty.is_phantom_data()).then_some(ReborrowField { + index, + name: field.name, + ty, + span: tcx.def_span(field.did), + }) + }) + .collect() +} + +/// Canonical CoerceShared source-to-target field correspondence. +/// +/// Named structs match fields by name. Tuple structs match fields by position. `PhantomData` +/// fields are ignored on both sides, so a target data field must correspond to a source data field. +/// The field types are instantiated but intentionally not normalized here: validation, borrowck, +/// interpretation, and codegen each need to normalize or relate aliases with their phase-specific +/// inference or monomorphization context before deciding whether to copy or recurse. +pub fn coerce_shared_field_pairs<'tcx>( + tcx: TyCtxt<'tcx>, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result>, CoerceSharedFieldPairError<'tcx>> { + let source_variant = source_def.non_enum_variant(); + let target_variant = target_def.non_enum_variant(); + if source_variant.ctor_kind() != target_variant.ctor_kind() { + return Err(CoerceSharedFieldPairError::FieldStyleMismatch); + } + + let by_index = matches!(target_variant.ctor_kind(), Some(CtorKind::Fn)); + let source_fields = reborrow_data_fields(tcx, source_def, source_args); + reborrow_data_fields(tcx, target_def, target_args) + .into_iter() + .map(|target| { + let source = source_fields + .iter() + .copied() + .find(|source| { + if by_index { source.index == target.index } else { source.name == target.name } + }) + .ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; + + Ok(CoerceSharedFieldPair { source, target }) + }) + .collect() +} diff --git a/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs b/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs new file mode 100644 index 0000000000000..4f14ed56d07bc --- /dev/null +++ b/tests/ui/reborrow/auxiliary/reborrow_foreign_private.rs @@ -0,0 +1,22 @@ +#![allow(dead_code)] + +use std::marker::PhantomData; + +#[derive(Clone, Copy)] +pub struct ForeignRef<'a> { + value: &'a i32, +} + +// SAFETY invariant: the pointer is valid as `&'a i32`. +#[derive(Clone, Copy)] +pub struct ForeignPtrRef<'a>((*const i32, PhantomData<&'a ()>)); + +impl<'a> ForeignPtrRef<'a> { + pub fn new(r: &'a i32) -> Self { + ForeignPtrRef((r, PhantomData)) + } + + pub fn to_ref(self) -> &'a i32 { + unsafe { &*self.0.0 } + } +} diff --git a/tests/ui/reborrow/coerce-shared-alias-projection.rs b/tests/ui/reborrow/coerce-shared-alias-projection.rs new file mode 100644 index 0000000000000..a83f19f950a90 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-alias-projection.rs @@ -0,0 +1,105 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct InnerMut<'a, T> { + value: &'a T, +} + +impl<'a, T> Reborrow for InnerMut<'a, T> {} + +struct InnerRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for InnerRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for InnerRef<'a, T> {} + +impl<'a, T> CoerceShared> for InnerMut<'a, T> {} + +type DirectInnerRef<'a, T> = InnerRef<'a, T>; + +trait RefFamily<'a, T> { + type Ref; +} + +struct Projected; + +impl<'a, T: 'a> RefFamily<'a, T> for Projected { + type Ref = InnerRef<'a, T>; +} + +type ProjectedInnerRef<'a, T> = >::Ref; + +struct OuterMut<'a, T> { + inner: InnerMut<'a, T>, + tag: usize, +} + +impl<'a, T> Reborrow for OuterMut<'a, T> {} + +struct OuterAliasRef<'a, T> { + inner: DirectInnerRef<'a, T>, + tag: usize, +} + +impl<'a, T> Clone for OuterAliasRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OuterAliasRef<'a, T> {} + +impl<'a, T> CoerceShared> for OuterMut<'a, T> {} + +struct OuterProjectionRef<'a, T: 'a> { + inner: ProjectedInnerRef<'a, T>, + tag: usize, +} + +impl<'a, T: 'a> Clone for OuterProjectionRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: 'a> Copy for OuterProjectionRef<'a, T> {} + +impl<'a, T: 'a> CoerceShared> for OuterMut<'a, T> {} + +fn read_alias<'a>(outer: OuterAliasRef<'a, u32>) -> (&'a u32, usize) { + (outer.inner.value, outer.tag) +} + +fn read_projection<'a>(outer: OuterProjectionRef<'a, u32>) -> (&'a u32, usize) { + (outer.inner.value, outer.tag) +} + +const fn const_accept_projection(_outer: OuterProjectionRef<'_, u32>) {} + +const fn consteval_projection_reborrow() { + let value = 11; + const_accept_projection(OuterMut { inner: InnerMut { value: &value }, tag: 5 }); +} + +fn main() { + const { consteval_projection_reborrow(); } + + let value = 22; + let outer = OuterMut { inner: InnerMut { value: &value }, tag: 7 }; + + let (alias_value, alias_tag) = read_alias(outer); + assert_eq!((*alias_value, alias_tag), (22, 7)); + + let (projection_value, projection_tag) = read_projection(outer); + assert_eq!((*projection_value, projection_tag), (22, 7)); +} diff --git a/tests/ui/reborrow/coerce-shared-different-layout.rs b/tests/ui/reborrow/coerce-shared-different-layout.rs new file mode 100644 index 0000000000000..774a3e97ad5d3 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-different-layout.rs @@ -0,0 +1,42 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; +use std::ptr::NonNull; + +struct ImbrisMut<'a, T> { + ptr: NonNull, + metadata: usize, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for ImbrisMut<'a, T> {} + +struct ImbrisRef<'a, T> { + ptr: NonNull, + marker: PhantomData<&'a T>, +} + +impl<'a, T> Clone for ImbrisRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for ImbrisRef<'a, T> {} + +impl<'a, T> CoerceShared> for ImbrisMut<'a, T> {} + +fn ptr(value: ImbrisRef<'_, i32>) -> NonNull { + value.ptr +} + +fn main() { + let mut value = 1; + let raw = NonNull::from(&mut value); + let wrapped = ImbrisMut { ptr: raw, metadata: 32, marker: PhantomData }; + + assert_eq!(ptr(wrapped), raw); +} diff --git a/tests/ui/reborrow/coerce-shared-extra-marker.rs b/tests/ui/reborrow/coerce-shared-extra-marker.rs new file mode 100644 index 0000000000000..40026d68d5dca --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-extra-marker.rs @@ -0,0 +1,37 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct MarkerExtraMut<'a, T> { + value: &'a mut T, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for MarkerExtraMut<'a, T> {} + +struct MarkerExtraRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for MarkerExtraRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for MarkerExtraRef<'a, T> {} + +impl<'a, T> CoerceShared> for MarkerExtraMut<'a, T> {} + +fn get<'a>(value: MarkerExtraRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let wrapped = MarkerExtraMut { value: &mut value, marker: PhantomData }; + assert_eq!(*get(wrapped), 1); +} diff --git a/tests/ui/reborrow/coerce-shared-field-relations.rs b/tests/ui/reborrow/coerce-shared-field-relations.rs new file mode 100644 index 0000000000000..f2856af355935 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-relations.rs @@ -0,0 +1,54 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for CustomMut<'a, T> {} + +#[derive(Clone, Copy)] +struct CustomRef<'a, T> { + value: &'a T, +} + +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +struct RenamedMut<'a, T> { + source: &'a mut T, +} + +impl<'a, T> Reborrow for RenamedMut<'a, T> {} + +#[derive(Clone, Copy)] +struct RenamedRef<'a, T> { + target: &'a T, + //~^ ERROR +} + +impl<'a, T> CoerceShared> for RenamedMut<'a, T> {} + +struct BadMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for BadMut<'a, T> {} + +#[derive(Clone, Copy)] +struct BadRef<'a, T> { + value: &'a u32, + //~^ ERROR + _marker: std::marker::PhantomData, +} + +impl<'a, T> CoerceShared> for BadMut<'a, T> {} + +fn good(_value: CustomRef<'_, u32>) {} + +fn main() { + let mut value = 1; + good(CustomMut { value: &mut value }); +} diff --git a/tests/ui/reborrow/coerce-shared-field-relations.stderr b/tests/ui/reborrow/coerce-shared-field-relations.stderr new file mode 100644 index 0000000000000..7be8b3c6c8208 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-relations.stderr @@ -0,0 +1,16 @@ +error: implementing `CoerceShared` requires every target field to have a corresponding source field + --> $DIR/coerce-shared-field-relations.rs:28:5 + | +LL | target: &'a T, + | ^^^^^^^^^^^^^ + +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:42:5 + | +LL | value: &'a mut T, + | ---------------- source field `value` has type `&'a mut T` +... +LL | value: &'a u32, + | ^^^^^^^^^^^^^^ target field `value` has type `&'a u32` + +error: aborting due to 2 previous errors diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs new file mode 100644 index 0000000000000..e01dcf5d95487 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs @@ -0,0 +1,21 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +//@ aux-build: reborrow_foreign_private.rs + +#![feature(reborrow)] + +extern crate reborrow_foreign_private; + +use reborrow_foreign_private::ForeignRef; +use std::marker::{CoerceShared, Reborrow}; + +struct LocalMut<'a> { + value: &'a mut i32, +} + +impl<'a> Reborrow for LocalMut<'a> {} + +impl<'a> CoerceShared> for LocalMut<'a> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr b/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr new file mode 100644 index 0000000000000..f03cc89782fff --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr @@ -0,0 +1,7 @@ +error: implementing `CoerceShared` requires all source and target fields to be accessible from the impl + --> $DIR/coerce-shared-foreign-private-field.rs:18:1 + | +LL | impl<'a> CoerceShared> for LocalMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs new file mode 100644 index 0000000000000..d45df376f5b8f --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs @@ -0,0 +1,24 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +//@ aux-build: reborrow_foreign_private.rs + +#![feature(reborrow)] + +extern crate reborrow_foreign_private; + +use reborrow_foreign_private::ForeignPtrRef; +use std::marker::{CoerceShared, PhantomData, Reborrow}; +use std::ptr; + +struct LocalPtrMut<'a>((*const i32, PhantomData<&'a ()>)); + +impl<'a> Reborrow for LocalPtrMut<'a> {} + +impl<'a> CoerceShared> for LocalPtrMut<'a> {} +//~^ ERROR + +fn main() { + let local = LocalPtrMut((ptr::null(), PhantomData)); + let foreign: ForeignPtrRef<'_> = local; + let _ = foreign.to_ref(); +} diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr new file mode 100644 index 0000000000000..6d9317e38a2de --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr @@ -0,0 +1,7 @@ +error: implementing `CoerceShared` requires all source and target fields to be accessible from the impl + --> $DIR/coerce-shared-foreign-private-tuple-field.rs:17:1 + | +LL | impl<'a> CoerceShared> for LocalPtrMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-generics.rs b/tests/ui/reborrow/coerce-shared-generics.rs new file mode 100644 index 0000000000000..44e4acb87fb0d --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-generics.rs @@ -0,0 +1,42 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct BufferMut<'a, T, U, const N: usize> { + data: &'a mut [T; N], + meta: U, +} + +impl<'a, T, U: Copy, const N: usize> Reborrow for BufferMut<'a, T, U, N> {} + +struct BufferRef<'a, T, U, const N: usize> { + data: &'a [T; N], + meta: U, +} + +impl<'a, T, U: Copy, const N: usize> Clone for BufferRef<'a, T, U, N> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T, U: Copy, const N: usize> Copy for BufferRef<'a, T, U, N> {} + +impl<'a, T, U: Copy, const N: usize> CoerceShared> + for BufferMut<'a, T, U, N> +{ +} + +fn inspect(buffer: BufferRef<'_, u8, u16, N>) -> (usize, u16) { + (buffer.data.len(), buffer.meta) +} + +fn main() { + let mut data = [1, 2, 3, 4]; + let buffer = BufferMut { data: &mut data, meta: 9_u16 }; + + assert_eq!(inspect(buffer), (4, 9)); +} diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs new file mode 100644 index 0000000000000..3ca3a315704a9 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs @@ -0,0 +1,21 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); + +impl<'a> Reborrow for CustomMarker<'a> {} + +#[derive(Clone, Copy)] +struct StaticMarkerRef<'a>(PhantomData<&'a ()>); + +impl<'a> CoerceShared> for CustomMarker<'a> {} +//~^ ERROR + +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 new file mode 100644 index 0000000000000..8073e71e56319 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr @@ -0,0 +1,23 @@ +error: implementing `CoerceShared` requires source and target to use the same reborrow lifetime argument + --> $DIR/coerce-shared-lifetime-mismatch.rs:12:10 + | +LL | impl<'a> CoerceShared> for CustomMarker<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0597]: `a` does not live long enough + --> $DIR/coerce-shared-lifetime-mismatch.rs:19: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 + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs b/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs new file mode 100644 index 0000000000000..afcb1d709d13b --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.rs @@ -0,0 +1,20 @@ +//@ edition: 2024 + +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); +struct CustomMarkerRef; + +impl<'a> Reborrow for CustomMarker<'a> {} +impl<'a> CoerceShared for CustomMarker<'a> {} +//~^ ERROR + +fn method(_a: CustomMarkerRef) {} + +fn main() { + let a = CustomMarker(PhantomData); + method(a); + //~^ ERROR +} diff --git a/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.stderr b/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.stderr new file mode 100644 index 0000000000000..3bf4dd762fa4c --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-marker-no-target-lifetime.stderr @@ -0,0 +1,23 @@ +error: implementing `CoerceShared` requires exactly one lifetime argument in the reborrowed type + --> $DIR/coerce-shared-marker-no-target-lifetime.rs:11:10 + | +LL | impl<'a> CoerceShared for CustomMarker<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> $DIR/coerce-shared-marker-no-target-lifetime.rs:18:12 + | +LL | method(a); + | ------ ^ expected `CustomMarkerRef`, found `CustomMarker<'_>` + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/coerce-shared-marker-no-target-lifetime.rs:14:4 + | +LL | fn method(_a: CustomMarkerRef) {} + | ^^^^^^ ------------------- + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.rs b/tests/ui/reborrow/coerce-shared-missing-target-field.rs new file mode 100644 index 0000000000000..131f69ae5bbff --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.rs @@ -0,0 +1,22 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct MissingSourceMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for MissingSourceMut<'a, T> {} + +#[derive(Clone, Copy)] +struct MissingSourceRef<'a, T> { + value: &'a T, + len: usize, + //~^ ERROR +} + +impl<'a, T> CoerceShared> for MissingSourceMut<'a, T> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr new file mode 100644 index 0000000000000..d3a45cad31072 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr @@ -0,0 +1,7 @@ +error: implementing `CoerceShared` requires every target field to have a corresponding source field + --> $DIR/coerce-shared-missing-target-field.rs:16:5 + | +LL | len: usize, + | ^^^^^^^^^^ + +error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-multi-field.rs b/tests/ui/reborrow/coerce-shared-multi-field.rs new file mode 100644 index 0000000000000..7e6e8816158fd --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-multi-field.rs @@ -0,0 +1,59 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; +use std::ptr::NonNull; + +struct MatMut<'a, T> { + ptr: NonNull, + rows: usize, + cols: usize, + row_stride: usize, + col_stride: usize, + marker: PhantomData<&'a mut T>, +} + +impl<'a, T> Reborrow for MatMut<'a, T> {} + +struct MatRef<'a, T> { + ptr: NonNull, + rows: usize, + cols: usize, + row_stride: usize, + col_stride: usize, + marker: PhantomData<&'a T>, +} + +impl<'a, T> Clone for MatRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for MatRef<'a, T> {} + +impl<'a, T> CoerceShared> for MatMut<'a, T> {} + +fn dims(mat: MatRef<'_, T>) -> (usize, usize, usize, usize) { + let _ = mat.ptr; + (mat.rows, mat.cols, mat.row_stride, mat.col_stride) +} + +fn main() { + let mut value = 0; + let mat = MatMut { + ptr: NonNull::from(&mut value), + rows: 2, + cols: 3, + row_stride: 4, + col_stride: 5, + marker: PhantomData, + }; + + assert_eq!(dims(mat), (2, 3, 4, 5)); + // Reusing the same source proves repeated shared reborrows keep source-only data protected + // without consuming the reborrowable value. + assert_eq!(dims(mat), (2, 3, 4, 5)); +} diff --git a/tests/ui/reborrow/coerce-shared-nested.rs b/tests/ui/reborrow/coerce-shared-nested.rs new file mode 100644 index 0000000000000..0199c35bc651a --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-nested.rs @@ -0,0 +1,63 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct InnerMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for InnerMut<'a, T> {} + +struct InnerRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for InnerRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for InnerRef<'a, T> {} + +impl<'a, T> CoerceShared> for InnerMut<'a, T> {} + +struct OuterMut<'a, T> { + inner: InnerMut<'a, T>, + tag: usize, +} + +impl<'a, T> Reborrow for OuterMut<'a, T> {} + +struct OuterRef<'a, T> { + inner: InnerRef<'a, T>, + tag: usize, +} + +impl<'a, T> Clone for OuterRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OuterRef<'a, T> {} + +impl<'a, T> CoerceShared> for OuterMut<'a, T> {} + +fn get<'a>(outer: OuterRef<'a, i32>) -> (&'a i32, usize) { + (outer.inner.value, outer.tag) +} + +fn main() { + let mut value = 22; + let outer = OuterMut { inner: InnerMut { value: &mut value }, tag: 7 }; + + let (first, tag) = get(outer); + assert_eq!((*first, tag), (22, 7)); + + let (second, tag) = get(outer); + assert_eq!((*second, tag), (22, 7)); +} 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 new file mode 100644 index 0000000000000..d881236427ca3 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-after-dead.rs @@ -0,0 +1,52 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + +fn read(value: OmitRef<'_, i32>) { + assert_eq!(*value.value, 1); +} + +fn main() { + let mut value = 1; + let mut extra_value = 2; + + { + let extra = ExtraMut { value: &mut extra_value }; + let wrapped = OmitMut { value: &mut value, extra }; + + read(wrapped); + } + + extra_value = 3; + assert_eq!(extra_value, 3); +} diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs new file mode 100644 index 0000000000000..597b96ed500f3 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.rs @@ -0,0 +1,52 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + +fn get<'a>(value: OmitRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let mut extra_value = 2; + let extra = ExtraMut { value: &mut extra_value }; + + let mut wrapped = OmitMut { + value: &mut value, + extra, + }; + + 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 new file mode 100644 index 0000000000000..a6d252ba1f5fb --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr @@ -0,0 +1,15 @@ +error[E0506]: cannot assign to `*wrapped.extra.value` because it is borrowed + --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:48: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 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs new file mode 100644 index 0000000000000..4d8376894c4aa --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.rs @@ -0,0 +1,46 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ExtraMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for ExtraMut<'a, T> {} + +struct OmitMut<'a, T> { + value: &'a mut T, + extra: ExtraMut<'a, T>, +} + +impl<'a, T> Reborrow for OmitMut<'a, T> {} + +struct OmitRef<'a, T> { + value: &'a T, +} + +impl<'a, T> Clone for OmitRef<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Copy for OmitRef<'a, T> {} + +impl<'a, T> CoerceShared> for OmitMut<'a, T> {} + +fn get<'a>(value: OmitRef<'a, i32>) -> &'a i32 { + value.value +} + +fn main() { + let mut value = 1; + let mut extra = 2; + let extra = ExtraMut { value: &mut extra }; + let wrapped = OmitMut { value: &mut value, extra }; + + assert_eq!(*get(wrapped), 1); +} diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.rs b/tests/ui/reborrow/coerce-shared-reordered-field.rs new file mode 100644 index 0000000000000..04ea2aaa583b2 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-reordered-field.rs @@ -0,0 +1,33 @@ +//@ run-pass + +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct ReorderMut<'a> { + a: &'a mut u8, + b: &'a mut u16, +} + +impl<'a> Reborrow for ReorderMut<'a> {} + +#[derive(Clone, Copy)] +struct ReorderRef<'a> { + b: &'a u16, + a: &'a u8, +} + +impl<'a> CoerceShared> for ReorderMut<'a> {} + +fn read(value: ReorderRef<'_>) -> (u16, u8) { + (*value.b, *value.a) +} + +fn main() { + let mut a = 1; + let mut b = 2; + let wrapped = ReorderMut { a: &mut a, b: &mut b }; + + assert_eq!(read(wrapped), (2, 1)); +} diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.rs b/tests/ui/reborrow/coerce-shared-wrong-generic.rs new file mode 100644 index 0000000000000..a7cfd74d137c7 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.rs @@ -0,0 +1,23 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct GenericMut<'a, T, U> { + value: &'a mut T, + marker: PhantomData, +} + +impl<'a, T, U> Reborrow for GenericMut<'a, T, U> {} + +#[derive(Clone, Copy)] +struct GenericRef<'a, T, U> { + value: &'a U, + //~^ ERROR + marker: PhantomData, +} + +impl<'a, T, U> CoerceShared> for GenericMut<'a, T, U> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr new file mode 100644 index 0000000000000..6f5595e09e632 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr @@ -0,0 +1,10 @@ +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:16:5 + | +LL | value: &'a mut T, + | ---------------- source field `value` has type `&'a mut T` +... +LL | value: &'a U, + | ^^^^^^^^^^^^ target field `value` has type `&'a U` + +error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce_shared_consteval.rs b/tests/ui/reborrow/coerce_shared_consteval.rs index 29b44b6438631..fe1e01ebe652e 100644 --- a/tests/ui/reborrow/coerce_shared_consteval.rs +++ b/tests/ui/reborrow/coerce_shared_consteval.rs @@ -21,7 +21,7 @@ const fn consteval_reproducer() { foo(MyMut(&value)); } -const fn foo(x: MyRef<'_>) {} +const fn foo(_x: MyRef<'_>) {} fn main() { const { consteval_reproducer(); } diff --git a/tests/ui/reborrow/issue-156309-corrected.rs b/tests/ui/reborrow/issue-156309-corrected.rs new file mode 100644 index 0000000000000..4d68327d22bfe --- /dev/null +++ b/tests/ui/reborrow/issue-156309-corrected.rs @@ -0,0 +1,25 @@ +//@ run-pass +//@ edition: 2024 + +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); + +#[derive(Clone, Copy, Debug)] +struct CustomMarkerRef<'a>(PhantomData<&'a ()>); + +impl<'a> Reborrow for CustomMarker<'a> {} +impl<'a> CoerceShared> for CustomMarker<'a> {} + +fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); + let c = method(a); + let _ = (&a, b, c); +} diff --git a/tests/ui/reborrow/issue-156309.rs b/tests/ui/reborrow/issue-156309.rs new file mode 100644 index 0000000000000..45d138c25b598 --- /dev/null +++ b/tests/ui/reborrow/issue-156309.rs @@ -0,0 +1,29 @@ +//@ edition: 2024 + +#![feature(reborrow)] + +// Malformed no-ICE regression: the unrelated name and signature errors are intentional because the +// original issue combined them with CoerceShared validation. + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct CustomMarker<'a>(PhantomData<&'a ()>); + +struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); +//~^ ERROR +//~| ERROR +//~| ERROR + +impl<'a> Reborrow for CustomMarker<'a> {} +impl<'a> CoerceShared> for CustomMarker<'a> {} +//~^ ERROR + +fn method<'a>(_a: CustomMarkerRef<'a>) -> 'a () { + //~^ ERROR + &() +} + +fn main() { + let a = CustomMarker(PhantomData); + let b = method(a); +} diff --git a/tests/ui/reborrow/issue-156309.stderr b/tests/ui/reborrow/issue-156309.stderr new file mode 100644 index 0000000000000..3642bd57ae7ec --- /dev/null +++ b/tests/ui/reborrow/issue-156309.stderr @@ -0,0 +1,62 @@ +error: expected type, found lifetime + --> $DIR/issue-156309.rs:21:43 + | +LL | fn method<'a>(_a: CustomMarkerRef<'a>) -> 'a () { + | ^^ expected type + | +help: you might have meant to write a reference type here + | +LL | fn method<'a>(_a: CustomMarkerRef<'a>) -> &'a () { + | + + +error[E0573]: expected type, found derive macro `Debug` + --> $DIR/issue-156309.rs:12:41 + | +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); + | ^^^^^ not a type + | +help: consider importing this trait instead + | +LL + use std::fmt::Debug; + | + +error[E0782]: expected a type, found a trait + --> $DIR/issue-156309.rs:12:48 + | +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); + | ^^^^^ + | +help: you can add the `dyn` keyword if you want a trait object + | +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, dyn Clone, Copy)>); + | +++ + +error[E0782]: expected a type, found a trait + --> $DIR/issue-156309.rs:12:55 + | +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); + | ^^^^ + | +help: you can add the `dyn` keyword if you want a trait object + | +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, dyn Copy)>); + | +++ + +error[E0277]: the trait bound `CustomMarkerRef<'a>: Copy` is not satisfied + --> $DIR/issue-156309.rs:18:10 + | +LL | impl<'a> CoerceShared> for CustomMarker<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `CustomMarkerRef<'a>` + | +note: required by a bound in `CoerceShared` + --> $SRC_DIR/core/src/marker.rs:LL:COL +help: consider annotating `CustomMarkerRef<'a>` with `#[derive(Copy)]` + | +LL + #[derive(Copy)] +LL | struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>); + | + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0277, E0573, E0782. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/reborrow/issue-156315-corrected.rs b/tests/ui/reborrow/issue-156315-corrected.rs new file mode 100644 index 0000000000000..b7dd49cdb4554 --- /dev/null +++ b/tests/ui/reborrow/issue-156315-corrected.rs @@ -0,0 +1,21 @@ +//@ normalize-stderr: "\n\n\z" -> "\n" + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T>(&'a mut T); + +impl<'a, T> Reborrow for CustomMut<'a, T> {} + +struct CustomRef<'a, T>(&'a CustomMut<'a, T>); +//~^ ERROR + +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +fn method(_a: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + method(a); +} diff --git a/tests/ui/reborrow/issue-156315-corrected.stderr b/tests/ui/reborrow/issue-156315-corrected.stderr new file mode 100644 index 0000000000000..bb73de70742c0 --- /dev/null +++ b/tests/ui/reborrow/issue-156315-corrected.stderr @@ -0,0 +1,10 @@ +error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field + --> $DIR/issue-156315-corrected.rs:11:25 + | +LL | struct CustomMut<'a, T>(&'a mut T); + | --------- source field `0` has type `&'a mut T` +... +LL | struct CustomRef<'a, T>(&'a CustomMut<'a, T>); + | ^^^^^^^^^^^^^^^^^^^^ target field `0` has type `&'a CustomMut<'a, T>` + +error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/issue-156315.rs b/tests/ui/reborrow/issue-156315.rs new file mode 100644 index 0000000000000..15afa1d0b9ef1 --- /dev/null +++ b/tests/ui/reborrow/issue-156315.rs @@ -0,0 +1,23 @@ +#![feature(reborrow)] + +// Malformed no-ICE regression: this intentionally keeps the missing generic arguments from the +// original reproducer while the corrected test isolates the CoerceShared field error. + +use std::marker::{CoerceShared, Reborrow}; + +struct CustomMut<'a, T>(&'a mut T); + +impl<'a, T> Reborrow for CustomMut<'a, T> {} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +struct CustomRef<'a, T>(&'a CustomMut); +//~^ ERROR +//~| ERROR +//~| ERROR + +fn method(_a: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + method(a); +} diff --git a/tests/ui/reborrow/issue-156315.stderr b/tests/ui/reborrow/issue-156315.stderr new file mode 100644 index 0000000000000..d5ef9d00efc11 --- /dev/null +++ b/tests/ui/reborrow/issue-156315.stderr @@ -0,0 +1,40 @@ +error[E0106]: missing lifetime specifier + --> $DIR/issue-156315.rs:13:29 + | +LL | struct CustomRef<'a, T>(&'a CustomMut); + | ^^^^^^^^^ expected named lifetime parameter + | +help: consider using the `'a` lifetime + | +LL | struct CustomRef<'a, T>(&'a CustomMut<'a>); + | ++++ + +error[E0107]: missing generics for struct `CustomMut` + --> $DIR/issue-156315.rs:13:29 + | +LL | struct CustomRef<'a, T>(&'a CustomMut); + | ^^^^^^^^^ expected 1 generic argument + | +note: struct defined here, with 1 generic parameter: `T` + --> $DIR/issue-156315.rs:8:8 + | +LL | struct CustomMut<'a, T>(&'a mut T); + | ^^^^^^^^^ - +help: add missing generic argument + | +LL | struct CustomRef<'a, T>(&'a CustomMut); + | +++ + +error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field + --> $DIR/issue-156315.rs:13:25 + | +LL | struct CustomMut<'a, T>(&'a mut T); + | --------- source field `0` has type `&'a mut T` +... +LL | struct CustomRef<'a, T>(&'a CustomMut); + | ^^^^^^^^^^^^^ target field `0` has type `&'a CustomMut<'_, {type error}>` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0106, E0107. +For more information about an error, try `rustc --explain E0106`.