From 08de3edfde80d84a724d4a995829c15a4de8c32d Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Fri, 5 Jun 2026 18:20:46 +0200 Subject: [PATCH 1/3] Add CoerceShared field-wise reborrow WF checks Localize the WF helper logic to builtin coherence and add the directly related UI coverage. --- .../src/coherence/builtin.rs | 852 +++++++++++++++--- .../rustc_hir_analysis/src/diagnostics.rs | 101 ++- ...coerce-shared-associated-type-field.stderr | 12 +- .../coerce-shared-decl-macro-hygiene.stderr | 11 +- .../coerce-shared-field-lifetime-swap.rs | 2 +- .../coerce-shared-field-lifetime-swap.stderr | 12 +- .../reborrow/coerce-shared-field-relations.rs | 3 +- .../coerce-shared-field-relations.stderr | 24 +- .../coerce-shared-foreign-private-field.rs | 3 +- ...coerce-shared-foreign-private-field.stderr | 10 + ...erce-shared-foreign-private-tuple-field.rs | 3 +- ...-shared-foreign-private-tuple-field.stderr | 10 + .../ui/reborrow/coerce-shared-generics.stderr | 18 +- .../coerce-shared-lifetime-mismatch.rs | 1 + .../coerce-shared-lifetime-mismatch.stderr | 12 +- .../coerce-shared-missing-target-field.rs | 2 +- .../coerce-shared-missing-target-field.stderr | 9 +- .../reborrow/coerce-shared-multi-non-zst.rs | 22 + .../coerce-shared-multi-non-zst.stderr | 14 + .../coerce-shared-mut-ref-field-validation.rs | 16 +- ...rce-shared-mut-ref-field-validation.stderr | 14 + ...d-omitted-reborrow-field-after-dead.stderr | 12 +- ...hared-omitted-reborrow-field-locked.stderr | 12 +- ...oerce-shared-omitted-reborrow-field.stderr | 12 +- .../coerce-shared-reordered-field.stderr | 12 +- .../reborrow/coerce-shared-wrong-generic.rs | 2 +- .../coerce-shared-wrong-generic.stderr | 13 +- 27 files changed, 1028 insertions(+), 186 deletions(-) create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-field.stderr create mode 100644 tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr create mode 100644 tests/ui/reborrow/coerce-shared-multi-non-zst.rs create mode 100644 tests/ui/reborrow/coerce-shared-multi-non-zst.stderr create mode 100644 tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 4ff870b405e5a..ddcc5c0376615 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -7,6 +7,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::ItemKind; +use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::{self, InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt}; @@ -17,7 +18,7 @@ use rustc_middle::ty::relate::solver_relating::RelateExt; use rustc_middle::ty::{ self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, suggest_constraining_type_params, }; -use rustc_span::{DUMMY_SP, Span, sym}; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, CopyImplementationError, InfringingFieldsReason, @@ -504,7 +505,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 = collect_reborrow_data_fields(tcx, def, args); if lifetimes_count != 1 { let item = tcx.hir_expect_item(impl_did); @@ -523,9 +524,13 @@ pub(crate) fn reborrow_info<'tcx>( let ocx = ObligationCtxt::new_with_diagnostics(&infcx); // We've found some data fields. They must all be either be Copy or Reborrow. - for (field, span) in data_fields { - let field = ocx - .deeply_normalize(&traits::ObligationCause::misc(span, impl_did), param_env, field) + for mut field in data_fields { + field.ty = ocx + .deeply_normalize( + &traits::ObligationCause::misc(field.span, impl_did), + param_env, + Unnormalized::new_wip(field.ty), + ) .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; if assert_field_type_is_reborrow( tcx, @@ -533,8 +538,8 @@ pub(crate) fn reborrow_info<'tcx>( reborrow_trait, impl_did, param_env, - field, - span, + field.ty, + field.span, ) .is_ok() { @@ -543,7 +548,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(()) @@ -572,6 +577,212 @@ fn assert_field_type_is_reborrow<'tcx>( if !errors.is_empty() { Err(errors) } else { Ok(()) } } +#[derive(Clone, Copy)] +struct CoerceSharedDiagnosticContext { + impl_span: Span, + trait_span: Span, + source_ty_span: Span, + target_ty_span: Span, + source_lifetime_span: Option, + target_lifetime_span: Option, +} + +#[derive(Clone, Copy)] +enum CoerceSharedTypeRole { + Source, + Target, +} + +impl CoerceSharedTypeRole { + fn as_str(self) -> &'static str { + match self { + CoerceSharedTypeRole::Source => "source", + CoerceSharedTypeRole::Target => "target", + } + } + + fn type_span(self, diagnostic_context: CoerceSharedDiagnosticContext) -> Span { + match self { + CoerceSharedTypeRole::Source => diagnostic_context.source_ty_span, + CoerceSharedTypeRole::Target => diagnostic_context.target_ty_span, + } + } +} + +fn coerce_shared_diagnostic_context( + tcx: TyCtxt<'_>, + impl_did: LocalDefId, +) -> CoerceSharedDiagnosticContext { + let item = tcx.hir_expect_item(impl_did); + let fallback_span = tcx.def_span(impl_did); + let mut diagnostic_context = CoerceSharedDiagnosticContext { + impl_span: item.span, + trait_span: fallback_span, + source_ty_span: fallback_span, + target_ty_span: fallback_span, + source_lifetime_span: None, + target_lifetime_span: None, + }; + + let ItemKind::Impl(impl_) = &item.kind else { + return diagnostic_context; + }; + let Some(of_trait) = impl_.of_trait else { + return diagnostic_context; + }; + + diagnostic_context.trait_span = of_trait.trait_ref.path.span; + diagnostic_context.source_ty_span = impl_.self_ty.span; + diagnostic_context.source_lifetime_span = first_explicit_lifetime_span_in_ty(impl_.self_ty) + .or_else(|| first_explicit_impl_lifetime_param_span(impl_.generics)); + + if let Some(target_ty) = coerce_shared_target_ty_from_path(of_trait.trait_ref.path) { + diagnostic_context.target_ty_span = target_ty.span; + diagnostic_context.target_lifetime_span = + first_explicit_lifetime_span_in_ambig_ty(target_ty); + } else { + diagnostic_context.target_ty_span = diagnostic_context.trait_span; + } + + diagnostic_context +} + +fn coerce_shared_target_ty_from_path<'hir>( + path: &'hir hir::Path<'hir>, +) -> Option<&'hir hir::Ty<'hir, hir::AmbigArg>> { + path.segments.last()?.args().args.iter().find_map(|arg| match arg { + hir::GenericArg::Type(ty) => Some(*ty), + hir::GenericArg::Lifetime(_) | hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => { + None + } + }) +} + +fn first_explicit_impl_lifetime_param_span(generics: &hir::Generics<'_>) -> Option { + generics.params.iter().find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) +} + +fn first_explicit_lifetime_span(lifetime: &hir::Lifetime) -> Option { + match lifetime.kind { + hir::LifetimeKind::Param(_) | hir::LifetimeKind::Static + if !lifetime.ident.span.is_dummy() => + { + Some(lifetime.ident.span) + } + hir::LifetimeKind::Param(_) + | hir::LifetimeKind::Static + | hir::LifetimeKind::ImplicitObjectLifetimeDefault + | hir::LifetimeKind::Error(_) + | hir::LifetimeKind::Infer => None, + } +} + +fn first_explicit_lifetime_span_in_ambig_ty(ty: &hir::Ty<'_, hir::AmbigArg>) -> Option { + first_explicit_lifetime_span_in_ty(ty.as_unambig_ty()) +} + +fn first_explicit_lifetime_span_in_ty(ty: &hir::Ty<'_>) -> Option { + match ty.kind { + hir::TyKind::Ref(lifetime, mut_ty) => first_explicit_lifetime_span(lifetime) + .or_else(|| first_explicit_lifetime_span_in_ty(mut_ty.ty)), + hir::TyKind::Slice(ty) + | hir::TyKind::Array(ty, _) + | hir::TyKind::Pat(ty, _) + | hir::TyKind::FieldOf(ty, _) => first_explicit_lifetime_span_in_ty(ty), + hir::TyKind::Ptr(mut_ty) => first_explicit_lifetime_span_in_ty(mut_ty.ty), + hir::TyKind::Tup(tys) => tys.iter().find_map(first_explicit_lifetime_span_in_ty), + hir::TyKind::Path(qpath) => first_explicit_lifetime_span_in_qpath(qpath), + hir::TyKind::TraitObject(bounds, lifetime) => bounds + .iter() + .find_map(|bound| first_explicit_lifetime_span_in_path(bound.trait_ref.path)) + .or_else(|| first_explicit_lifetime_span(&lifetime)), + hir::TyKind::OpaqueDef(opaque) => first_explicit_lifetime_span_in_bounds(opaque.bounds), + hir::TyKind::TraitAscription(bounds) => first_explicit_lifetime_span_in_bounds(bounds), + hir::TyKind::FnPtr(fn_ptr) => { + fn_ptr.generic_params.iter().find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) + } + hir::TyKind::UnsafeBinder(binder) => binder + .generic_params + .iter() + .find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) + .or_else(|| first_explicit_lifetime_span_in_ty(binder.inner_ty)), + hir::TyKind::InferDelegation(_) + | hir::TyKind::Never + | hir::TyKind::Infer(()) + | hir::TyKind::Err(_) => None, + } +} + +fn first_explicit_lifetime_span_in_bounds(bounds: hir::GenericBounds<'_>) -> Option { + bounds.iter().find_map(|bound| match bound { + hir::GenericBound::Trait(poly_trait_ref) => { + first_explicit_lifetime_span_in_path(poly_trait_ref.trait_ref.path) + } + hir::GenericBound::Outlives(lifetime) => first_explicit_lifetime_span(lifetime), + hir::GenericBound::Use(args, _) => args.iter().find_map(|arg| match arg { + hir::PreciseCapturingArgKind::Lifetime(lifetime) => { + first_explicit_lifetime_span(lifetime) + } + hir::PreciseCapturingArgKind::Param(_) => None, + }), + }) +} + +fn first_explicit_lifetime_span_in_qpath(qpath: hir::QPath<'_>) -> Option { + match qpath { + hir::QPath::Resolved(qself, path) => qself + .and_then(first_explicit_lifetime_span_in_ty) + .or_else(|| first_explicit_lifetime_span_in_path(path)), + hir::QPath::TypeRelative(qself, segment) => first_explicit_lifetime_span_in_ty(qself) + .or_else(|| first_explicit_lifetime_span_in_path_segment(segment)), + } +} + +fn first_explicit_lifetime_span_in_path(path: &hir::Path<'_>) -> Option { + path.segments.iter().find_map(first_explicit_lifetime_span_in_path_segment) +} + +fn first_explicit_lifetime_span_in_path_segment(segment: &hir::PathSegment<'_>) -> Option { + first_explicit_lifetime_span_in_generic_args(segment.args()) +} + +fn first_explicit_lifetime_span_in_generic_args(args: &hir::GenericArgs<'_>) -> Option { + args.args + .iter() + .find_map(|arg| match arg { + hir::GenericArg::Lifetime(lifetime) => first_explicit_lifetime_span(lifetime), + hir::GenericArg::Type(ty) => first_explicit_lifetime_span_in_ambig_ty(ty), + hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => None, + }) + .or_else(|| { + args.constraints.iter().find_map(|constraint| { + first_explicit_lifetime_span_in_generic_args(constraint.gen_args) + .or_else(|| constraint.ty().and_then(first_explicit_lifetime_span_in_ty)) + }) + }) +} + pub(crate) fn coerce_shared_info<'tcx>( tcx: TyCtxt<'tcx>, impl_did: LocalDefId, @@ -579,6 +790,7 @@ pub(crate) fn coerce_shared_info<'tcx>( debug!("compute_coerce_shared_info(impl_did={:?})", impl_did); let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); let span = tcx.def_span(impl_did); + let diagnostic_context = coerce_shared_diagnostic_context(tcx, impl_did); let trait_name = "CoerceShared"; let coerce_shared_trait = tcx.require_lang_item(LangItem::CoerceShared, span); @@ -605,138 +817,67 @@ pub(crate) fn coerce_shared_info<'tcx>( 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 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) - }; + let a_lifetime = single_region_arg(args_a); + let b_lifetime = single_region_arg(args_b); - return Err(tcx - .dcx() - .emit_err(diagnostics::CoerceSharedMulti { span, trait_name })); + if a_lifetime.is_none() || b_lifetime.is_none() { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMulti { + span: diagnostic_context.trait_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]; - let a = ocx - .deeply_normalize( - &traits::ObligationCause::misc(span_a, impl_did), - param_env, - a, - ) - .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; - let b = ocx - .deeply_normalize( - &traits::ObligationCause::misc(span_b, impl_did), - param_env, - b, - ) - .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; - - 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 + if a_lifetime != b_lifetime { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedLifetimeMismatch { + span: diagnostic_context.trait_span, + source_lifetime_span: diagnostic_context.source_lifetime_span, + target_lifetime_span: diagnostic_context.target_lifetime_span, + trait_name, + })); } - } - - _ => { - // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. - return Err(tcx - .dcx() - .emit_err(diagnostics::CoerceUnsizedNonStruct { span, trait_name })); - } - }; - // 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. - // - // FIXME(reborrow): we should do the relating inside `probe` so the region constraint - // doesn't affect later result in case that this relating fails. - // We should resolve regions if the relating succeeds. - // Besides, the regions of `Ref`s are not checked here so `&'a mut T -> &'static T` is - // allowed. - if source.ref_mutability() == Some(ty::Mutability::Mut) - && target.ref_mutability() == Some(ty::Mutability::Not) - && infcx - .relate( - param_env, - source.peel_refs(), - ty::Variance::Invariant, - target.peel_refs(), - source_field_span, - ) - .is_ok() - { - // &mut T implements CoerceShared to &T, except not really. - return Ok(()); - } + validate_reborrow_field_access( + tcx, + impl_did, + def_a, + trait_name, + diagnostic_context, + CoerceSharedTypeRole::Source, + )?; + validate_reborrow_field_access( + tcx, + impl_did, + def_b, + trait_name, + diagnostic_context, + CoerceSharedTypeRole::Target, + )?; - // FIXME(reborrow): we should do the relating inside `probe` so the region constraint - // doesn't affect later result in case that this relating fails. - if infcx - .relate(param_env, source, ty::Variance::Invariant, 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 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, + diagnostic_context, + 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. - // - // FIXME(reborrow): We should resolve regions here. - 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(diagnostics::CoerceUnsizedNonStruct { span, trait_name })) } } - - Ok(()) } fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize { @@ -751,27 +892,466 @@ fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize { args.iter().filter(|arg| arg.as_region().is_some()).count() } -fn collect_struct_data_fields<'tcx>( +#[derive(Clone, Copy)] +struct ReborrowDataField<'tcx> { + ident: Ident, + name: Symbol, + ty: Ty<'tcx>, + span: Span, +} + +#[derive(Clone, Copy)] +struct CoerceSharedFieldPair<'tcx> { + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +} + +#[derive(Clone, Copy)] +enum CoerceSharedFieldPairError<'tcx> { + FieldStyleMismatch, + MissingSourceField { target: ReborrowDataField<'tcx> }, +} + +fn single_region_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) +} + +fn collect_reborrow_data_fields<'tcx>( tcx: TyCtxt<'tcx>, def: ty::AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>, -) -> Vec<(Unnormalized<'tcx, Ty<'tcx>>, Span)> { +) -> Vec> { def.non_enum_variant() .fields .iter() - .filter_map(|f| { - // Ignore PhantomData fields - let ty = f.ty(tcx, args); - // FIXME(#155345): alias might be normalized to PhantomData. - // We probably should normalize here instead. - if ty.skip_norm_wip().is_phantom_data() { - return None; + .filter_map(|field| { + let ty = field.ty(tcx, args).skip_norm_wip(); + (!ty.is_phantom_data()).then_some(ReborrowDataField { + ident: field.ident(tcx), + name: field.name, + ty, + span: tcx.def_span(field.did), + }) + }) + .collect() +} + +// This is a coherence/WF check only. It verifies that the CoerceShared impl +// describes a structurally valid field-wise relation. Runtime lowering of the +// operation is not modeled here. +fn collect_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_position = matches!(target_variant.ctor_kind(), Some(CtorKind::Fn)); + let source_fields = collect_reborrow_data_fields(tcx, source_def, source_args); + let target_fields = collect_reborrow_data_fields(tcx, target_def, target_args); + + if by_position { + target_fields + .into_iter() + .zip(source_fields.into_iter().map(Some).chain(std::iter::repeat(None))) + .map(|(target, source)| { + let source = + source.ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; + Ok(CoerceSharedFieldPair { source, target }) + }) + .collect() + } else { + target_fields + .into_iter() + .map(|target| { + let source = source_fields + .iter() + .find(|source| { + tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id) + }) + .ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; + + Ok(CoerceSharedFieldPair { source: *source, target }) + }) + .collect() + } +} + +fn validate_reborrow_field_access( + tcx: TyCtxt<'_>, + impl_did: LocalDefId, + def: ty::AdtDef<'_>, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + role: CoerceSharedTypeRole, +) -> 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(diagnostics::CoerceSharedInaccessibleField { + span: diagnostic_context.impl_span, + type_span: role.type_span(diagnostic_context), + trait_name, + role: role.as_str(), + type_name: tcx.item_name(def.did()), + })); + } + + for field in &variant.fields { + if !field.vis.is_accessible_from(module, tcx) { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedInaccessibleField { + span: diagnostic_context.impl_span, + type_span: role.type_span(diagnostic_context), + trait_name, + role: role.as_str(), + type_name: tcx.item_name(def.did()), + })); + } + } + + Ok(()) +} + +fn validate_coerce_shared_fields<'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, + diagnostic_context: CoerceSharedDiagnosticContext, + 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 collect_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(diagnostics::CoerceSharedFieldStyleMismatch { span, trait_name })); + } + Err(CoerceSharedFieldPairError::MissingSourceField { target }) => { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMissingField { + span: target.span, + source_ty_span: diagnostic_context.source_ty_span, + trait_name, + source_ty_name: tcx.item_name(source_def.did()), + field_name: target.name, + })); + } + }; + + for field_pair in field_pairs { + validate_coerce_shared_field( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + field_pair.source, + field_pair.target, + )?; + } + + // FIXME(reborrow): remove this temporary WF-side memcpy-ability guard once + // the downstream CoerceShared implementation can correctly handle source and + // target types that are not trivially memcpy-able. Refer to #157489 + validate_coerce_shared_fields_are_memcpy_compatible( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + source_def, + source_args, + target_def, + target_args, + )?; + + Ok(()) +} + +fn validate_coerce_shared_fields_are_memcpy_compatible<'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, + diagnostic_context: CoerceSharedDiagnosticContext, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let source_non_zst_fields = + non_zst_reborrow_data_fields(tcx, infcx, param_env, source_def, source_args); + let target_non_zst_fields = + non_zst_reborrow_data_fields(tcx, infcx, param_env, target_def, target_args); + + match (&source_non_zst_fields[..], &target_non_zst_fields[..]) { + ([], []) => Ok(()), + ([source], [target]) => { + if field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::Equal, + ) { + return Ok(()); + } + + if matches!( + (source.ty.kind(), target.ty.kind()), + (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) + | (&ty::Alias(..), _) + | (_, &ty::Alias(..)) + ) && field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::MutRefToSharedRef, + ) { + return Ok(()); } - Some((ty, tcx.def_span(f.did))) + + validate_field_tys_satisfy_coerce_shared_relation( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + *source, + *target, + ) + } + _ => Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMultipleNonZstFields { + span: diagnostic_context.impl_span, + source_ty_span: diagnostic_context.source_ty_span, + target_ty_span: diagnostic_context.target_ty_span, + trait_name, + source_count: source_non_zst_fields.len(), + target_count: target_non_zst_fields.len(), + })), + } +} + +fn non_zst_reborrow_data_fields<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + def: ty::AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, +) -> Vec> { + collect_reborrow_data_fields(tcx, def, args) + .into_iter() + .filter(|field| { + !matches!( + tcx.layout_of(infcx.typing_env(param_env).as_query_input(field.ty)), + Ok(layout) if layout.is_zst() + ) }) .collect() } +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, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + if matches!( + (source.ty.kind(), target.ty.kind()), + (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) + | (&ty::Alias(..), _) + | (_, &ty::Alias(..)) + ) && field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::MutRefToSharedRef, + ) { + return Ok(()); + } + + if field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::Equal, + ) { + return assert_field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span); + } + + validate_field_tys_satisfy_coerce_shared_relation( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + source, + target, + ) +} + +fn validate_field_tys_satisfy_coerce_shared_relation<'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, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + 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(emit_coerce_shared_field_mismatch( + tcx, + trait_name, + diagnostic_context, + source, + target, + )); + } + + ocx.resolve_regions_and_report_errors(impl_did, param_env, []) +} + +fn emit_coerce_shared_field_mismatch<'tcx>( + tcx: TyCtxt<'tcx>, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> ErrorGuaranteed { + tcx.dcx().emit_err(diagnostics::CoerceSharedFieldMismatch { + span: target.span, + source_span: source.span, + impl_span: diagnostic_context.impl_span, + source_name: source.name, + source_ty: source.ty, + target_name: target.name, + target_ty: target.ty, + trait_name, + }) +} + +enum FieldRelation { + Equal, + MutRefToSharedRef, +} + +fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>( + tcx: TyCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + source_ty: Ty<'tcx>, + target_ty: Ty<'tcx>, + span: Span, + relation: FieldRelation, +) -> bool { + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let cause = traits::ObligationCause::misc(span, impl_did); + let ocx = ObligationCtxt::new(&infcx); + + let Ok((source_ty, target_ty)) = + ocx.deeply_normalize(&cause, param_env, Unnormalized::new_wip((source_ty, target_ty))) + else { + return false; + }; + + if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() { + return false; + } + + match relation { + FieldRelation::Equal => { + if infcx.relate(param_env, source_ty, ty::Variance::Invariant, target_ty, span).is_err() + { + return false; + } + } + FieldRelation::MutRefToSharedRef => { + let ( + &ty::Ref(source_region, source_referent_ty, ty::Mutability::Mut), + &ty::Ref(target_region, target_referent_ty, ty::Mutability::Not), + ) = (source_ty.kind(), target_ty.kind()) + else { + return false; + }; + if source_region != target_region { + return false; + } + if ocx.sup(&cause, param_env, target_referent_ty, source_referent_ty).is_err() { + return false; + } + } + }; + + ocx.evaluate_obligations_error_on_ambiguity().is_empty() + && ocx.resolve_regions(impl_did, param_env, []).is_empty() +} + fn assert_field_type_is_copy<'tcx>( tcx: TyCtxt<'tcx>, infcx: &InferCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index 1bfc495071284..d196aa3a3f158 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -1347,13 +1347,112 @@ 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, + #[label("source reborrow lifetime")] + pub source_lifetime_span: Option, + #[label("target reborrow lifetime")] + pub target_lifetime_span: Option, + 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, + #[label("required by this `CoerceShared` implementation")] + pub impl_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] + #[label("target field `{$field_name}` has no corresponding source field")] + pub span: Span, + #[label("source type `{$source_ty_name}` does not contain field `{$field_name}`")] + pub source_ty_span: Span, + pub trait_name: &'static str, + pub source_ty_name: Symbol, + pub field_name: Symbol, +} + +#[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 {$role} type fields to be accessible from the impl" +)] +pub(crate) struct CoerceSharedInaccessibleField { + #[primary_span] + pub span: Span, + #[label("{$role} type `{$type_name}` has inaccessible reborrow data fields")] + pub type_span: Span, + pub trait_name: &'static str, + pub role: &'static str, + pub type_name: Symbol, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` currently requires source and target to have at most one \ + non-ZST reborrow data field" +)] +#[note( + "this is a temporary restriction until `CoerceShared` lowering supports non-trivially \ + memcpy-compatible field layouts" +)] +pub(crate) struct CoerceSharedMultipleNonZstFields { + #[primary_span] + #[label("in this `CoerceShared` implementation")] + pub span: Span, + #[label("source type has {$source_count} non-ZST reborrow data fields")] + pub source_ty_span: Span, + #[label("target type has {$target_count} non-ZST reborrow data fields")] + pub target_ty_span: Span, + pub trait_name: &'static str, + pub source_count: usize, + pub target_count: usize, +} + #[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/tests/ui/reborrow/coerce-shared-associated-type-field.stderr b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr index d533fa2eb8748..31b54e7ed6c9e 100644 --- a/tests/ui/reborrow/coerce-shared-associated-type-field.stderr +++ b/tests/ui/reborrow/coerce-shared-associated-type-field.stderr @@ -1,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-associated-type-field.rs:27:10 +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 | LL | impl<'a> CoerceShared> for MyMut<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 2 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr index 0b0b7bf048591..62a73361b5cd9 100644 --- a/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr +++ b/tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr @@ -1,12 +1,17 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-decl-macro-hygiene.rs:20:14 +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 | LL | impl<'a> CoerceShared> for MyMut<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 2 non-ZST reborrow data fields + | in this `CoerceShared` implementation ... LL | my_macro!(field); | ---------------- in this macro invocation | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts = note: this error originates in the macro `my_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs index d4558dd35d775..9d102238467e6 100644 --- a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs @@ -12,10 +12,10 @@ impl Reborrow for MyMut<'_> {} #[derive(Copy, Clone)] struct MyRef<'a> { x: &'a (), + //~^ ERROR y: &'static (), } impl<'a> CoerceShared> for MyMut<'a> {} -//~^ ERROR fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr index bce87c68eb912..b6160ad40bcce 100644 --- a/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr +++ b/tests/ui/reborrow/coerce-shared-field-lifetime-swap.stderr @@ -1,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-field-lifetime-swap.rs:18: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-field-lifetime-swap.rs:14:5 | +LL | x: &'static (), + | -------------- source field `x` has type `&'static ()` +... +LL | x: &'a (), + | ^^^^^^^^^ target field `x` has type `&'a ()` +... LL | impl<'a> CoerceShared> for MyMut<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ------------------------------------------------- required by this `CoerceShared` implementation error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-field-relations.rs b/tests/ui/reborrow/coerce-shared-field-relations.rs index b2104efb6028d..3920f3eba7cb5 100644 --- a/tests/ui/reborrow/coerce-shared-field-relations.rs +++ b/tests/ui/reborrow/coerce-shared-field-relations.rs @@ -24,6 +24,7 @@ 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> {} @@ -37,11 +38,11 @@ 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> {} -//~^ ERROR 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 033a29e1e554e..2a723f954490b 100644 --- a/tests/ui/reborrow/coerce-shared-field-relations.stderr +++ b/tests/ui/reborrow/coerce-shared-field-relations.stderr @@ -1,9 +1,23 @@ -error[E0277]: the trait bound `&'a mut T: CoerceShared<&'a u32>` is not satisfied - --> $DIR/coerce-shared-field-relations.rs:43:1 +error: implementing `CoerceShared` requires every target field to have a corresponding source field + --> $DIR/coerce-shared-field-relations.rs:26:5 | +LL | target: &'a T, + | ^^^^^^^^^^^^^ target field `target` has no corresponding source field +... +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 + | +LL | value: &'a mut T, + | ---------------- source field `value` has type `&'a mut T` +... +LL | value: &'a u32, + | ^^^^^^^^^^^^^^ target field `value` has type `&'a u32` +... LL | impl<'a, T> CoerceShared> for BadMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a u32>` is not implemented for `&'a mut T` + | ------------------------------------------------------------ required by this `CoerceShared` implementation -error: aborting due to 1 previous error +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs index 3adda6733e16f..66c8ece3bd43e 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-field.rs +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.rs @@ -1,5 +1,3 @@ -//@ check-pass - //@ aux-build: reborrow_foreign_private.rs #![feature(reborrow)] @@ -16,5 +14,6 @@ struct LocalMut<'a> { 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..a328084260d0d --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-field.stderr @@ -0,0 +1,10 @@ +error: implementing `CoerceShared` requires all target type fields to be accessible from the impl + --> $DIR/coerce-shared-foreign-private-field.rs:16:1 + | +LL | impl<'a> CoerceShared> for LocalMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^^^^^^^^^^^^^^^^ + | | + | target type `ForeignRef` has inaccessible reborrow data fields + +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 index 4a70fd49e38ff..a2b88af04eb50 100644 --- a/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.rs @@ -1,5 +1,3 @@ -//@ check-pass - #![feature(reborrow)] use std::marker::{CoerceShared, PhantomData, Reborrow}; @@ -18,5 +16,6 @@ struct LocalPtrMut<'a>(*const i32, PhantomData<&'a ()>); impl<'a> Reborrow for LocalPtrMut<'a> {} impl<'a> CoerceShared> for LocalPtrMut<'a> {} +//~^ ERROR fn main() {} 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..b699e1affbc49 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-foreign-private-tuple-field.stderr @@ -0,0 +1,10 @@ +error: implementing `CoerceShared` requires all target type fields to be accessible from the impl + --> $DIR/coerce-shared-foreign-private-tuple-field.rs:18:1 + | +LL | impl<'a> CoerceShared> for LocalPtrMut<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^-----------------^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | target type `ForeignPtrRef` has inaccessible reborrow data fields + +error: aborting due to 1 previous error + diff --git a/tests/ui/reborrow/coerce-shared-generics.stderr b/tests/ui/reborrow/coerce-shared-generics.stderr index 72efdd475fc10..8e2e4e4485918 100644 --- a/tests/ui/reborrow/coerce-shared-generics.stderr +++ b/tests/ui/reborrow/coerce-shared-generics.stderr @@ -1,8 +1,18 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-generics.rs:26:38 +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 | -LL | impl<'a, T, U: Copy, const N: usize> CoerceShared> - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | impl<'a, T, U: Copy, const N: usize> CoerceShared> + | ^ ---------------------- target type has 2 non-ZST reborrow data fields + | _| + | | +LL | | +LL | | for BufferMut<'a, T, U, N> + | | ---------------------- source type has 2 non-ZST reborrow data fields +LL | | { +LL | | } + | |_^ in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs index 90bb4be4c1894..b6b5471adb0fc 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.rs @@ -13,6 +13,7 @@ impl<'a> Reborrow for CustomMarker<'a> {} struct StaticMarkerRef<'a>(PhantomData<&'a ()>); impl<'a> CoerceShared> for CustomMarker<'a> {} +//~^ ERROR fn method(_a: StaticMarkerRef<'static>) {} diff --git a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr index 337e4b6938944..7c2e3e22b0b51 100644 --- a/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr +++ b/tests/ui/reborrow/coerce-shared-lifetime-mismatch.stderr @@ -1,5 +1,13 @@ +error: implementing `CoerceShared` requires source and target to use the same reborrow lifetime argument + --> $DIR/coerce-shared-lifetime-mismatch.rs:15: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:21:12 + --> $DIR/coerce-shared-lifetime-mismatch.rs:22:12 | LL | let a = CustomMarker(PhantomData); | - binding `a` declared here @@ -12,6 +20,6 @@ LL | LL | } | - `a` dropped here while still borrowed -error: aborting due to 1 previous error +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-missing-target-field.rs b/tests/ui/reborrow/coerce-shared-missing-target-field.rs index c528fb85340ac..edd843b041fa8 100644 --- a/tests/ui/reborrow/coerce-shared-missing-target-field.rs +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.rs @@ -12,9 +12,9 @@ impl<'a, T> Reborrow for MissingSourceMut<'a, T> {} struct MissingSourceRef<'a, T> { value: &'a T, len: usize, + //~^ ERROR } impl<'a, T> CoerceShared> for MissingSourceMut<'a, T> {} -//~^ ERROR fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr index 15146341bc56c..148cf8addf0f9 100644 --- a/tests/ui/reborrow/coerce-shared-missing-target-field.stderr +++ b/tests/ui/reborrow/coerce-shared-missing-target-field.stderr @@ -1,8 +1,11 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-missing-target-field.rs:17:13 +error: implementing `CoerceShared` requires every target field to have a corresponding source field + --> $DIR/coerce-shared-missing-target-field.rs:14:5 | +LL | len: usize, + | ^^^^^^^^^^ target field `len` has no corresponding source field +... LL | impl<'a, T> CoerceShared> for MissingSourceMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ----------------------- source type `MissingSourceMut` does not contain field `len` error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-multi-non-zst.rs b/tests/ui/reborrow/coerce-shared-multi-non-zst.rs new file mode 100644 index 0000000000000..6625593a4b32e --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-multi-non-zst.rs @@ -0,0 +1,22 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, Reborrow}; + +struct Source<'a> { + a: &'a mut u8, + b: u8, +} + +#[derive(Copy, Clone)] +struct Target<'a> { + a: &'a u8, + b: u8, +} + +impl Reborrow for Source<'_> {} + +impl<'a> CoerceShared> for Source<'a> {} +//~^ ERROR + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-multi-non-zst.stderr b/tests/ui/reborrow/coerce-shared-multi-non-zst.stderr new file mode 100644 index 0000000000000..231e33f259c63 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-multi-non-zst.stderr @@ -0,0 +1,14 @@ +error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field + --> $DIR/coerce-shared-multi-non-zst.rs:19:1 + | +LL | impl<'a> CoerceShared> for Source<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^----------^^^^^^----------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 2 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts + +error: aborting due to 1 previous error + 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 a425ab5fd1ced..2a18d0dda06f0 100644 --- a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs @@ -1,5 +1,3 @@ -//@ check-pass - #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; @@ -41,4 +39,18 @@ struct InnerLifetimeRef<'a> { impl<'a> CoerceShared> for InnerLifetimeMut<'a> {} +struct RejectedInnerLifetimeMut<'a> { + value: &'a mut &'a (), +} + +impl Reborrow for RejectedInnerLifetimeMut<'_> {} + +#[derive(Copy, Clone)] +struct RejectedInnerLifetimeRef<'a> { + value: &'a &'static (), + //~^ ERROR +} + +impl<'a> CoerceShared> for RejectedInnerLifetimeMut<'a> {} + fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr new file mode 100644 index 0000000000000..98ab275b31e48 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr @@ -0,0 +1,14 @@ +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 + | +LL | value: &'a mut &'a (), + | --------------------- source field `value` has type `&'a mut &'a ()` +... +LL | value: &'a &'static (), + | ^^^^^^^^^^^^^^^^^^^^^^ target field `value` has type `&'a &'static ()` +... +LL | impl<'a> CoerceShared> for RejectedInnerLifetimeMut<'a> {} + | --------------------------------------------------------------------------------------- required by this `CoerceShared` implementation + +error: aborting due to 1 previous error + 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 70a0db88319a7..d0f2540ccbcff 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,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-omitted-reborrow-field-after-dead.rs:31:13 +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 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 1 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts error: aborting due to 1 previous error 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 d718103c80e7f..ccc3054a1b0c8 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field-locked.stderr @@ -1,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-omitted-reborrow-field-locked.rs:30:13 +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 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 1 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = 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 diff --git a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr index c663576228f62..08ddea2329405 100644 --- a/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr +++ b/tests/ui/reborrow/coerce-shared-omitted-reborrow-field.stderr @@ -1,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-omitted-reborrow-field.rs:31:13 +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 | LL | impl<'a, T> CoerceShared> for OmitMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 1 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-reordered-field.stderr b/tests/ui/reborrow/coerce-shared-reordered-field.stderr index e3cd68547c428..5469e5e3d9f49 100644 --- a/tests/ui/reborrow/coerce-shared-reordered-field.stderr +++ b/tests/ui/reborrow/coerce-shared-reordered-field.stderr @@ -1,8 +1,14 @@ -error: implementing `CoerceShared` does not allow multiple lifetimes or fields to be coerced - --> $DIR/coerce-shared-reordered-field.rs:19:10 +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 | LL | impl<'a> CoerceShared> for ReorderMut<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^--------------^^^^^^--------------^^^ + | | | | + | | | source type has 2 non-ZST reborrow data fields + | | target type has 2 non-ZST reborrow data fields + | in this `CoerceShared` implementation + | + = note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts error: aborting due to 1 previous error diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.rs b/tests/ui/reborrow/coerce-shared-wrong-generic.rs index 2dc818a7015b2..bbd9cfebcd9e8 100644 --- a/tests/ui/reborrow/coerce-shared-wrong-generic.rs +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.rs @@ -12,10 +12,10 @@ 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> {} -//~^ ERROR fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr index d0031a7c8a99e..b037106ebb619 100644 --- a/tests/ui/reborrow/coerce-shared-wrong-generic.stderr +++ b/tests/ui/reborrow/coerce-shared-wrong-generic.stderr @@ -1,9 +1,14 @@ -error[E0277]: the trait bound `&'a mut T: CoerceShared<&'a U>` is not satisfied - --> $DIR/coerce-shared-wrong-generic.rs:18:1 +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 | +LL | value: &'a mut T, + | ---------------- source field `value` has type `&'a mut T` +... +LL | value: &'a U, + | ^^^^^^^^^^^^ target field `value` has type `&'a U` +... LL | impl<'a, T, U> CoerceShared> for GenericMut<'a, T, U> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a U>` is not implemented for `&'a mut T` + | ----------------------------------------------------------------------------- required by this `CoerceShared` implementation error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. From 1d2277a828d492a431ef798e01d084e6c023e11a Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Thu, 9 Jul 2026 15:42:18 +0200 Subject: [PATCH 2/3] Check omitted CoerceShared ZST fields --- .../src/coherence/builtin.rs | 194 +++++++++++++----- .../rustc_hir_analysis/src/diagnostics.rs | 16 ++ .../coerce-shared-omitted-zst-field.rs | 59 ++++++ .../coerce-shared-omitted-zst-field.stderr | 24 +++ 4 files changed, 241 insertions(+), 52 deletions(-) create mode 100644 tests/ui/reborrow/coerce-shared-omitted-zst-field.rs create mode 100644 tests/ui/reborrow/coerce-shared-omitted-zst-field.stderr diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index ddcc5c0376615..3c79899ecb8a2 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -24,7 +24,7 @@ use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, CopyImplementationError, InfringingFieldsReason, type_allowed_to_implement_const_param_ty, type_allowed_to_implement_copy, }; -use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCause, ObligationCtxt}; +use rustc_trait_selection::traits::{self, ObligationCause, ObligationCtxt}; use tracing::debug; use crate::diagnostics; @@ -532,7 +532,7 @@ pub(crate) fn reborrow_info<'tcx>( Unnormalized::new_wip(field.ty), ) .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; - if assert_field_type_is_reborrow( + if field_type_is_reborrow( tcx, &infcx, reborrow_trait, @@ -540,9 +540,7 @@ pub(crate) fn reborrow_info<'tcx>( param_env, field.ty, field.span, - ) - .is_ok() - { + ) { // Field implements Reborrow, check remaining fields. continue; } @@ -554,29 +552,6 @@ pub(crate) fn reborrow_info<'tcx>( Ok(()) } -fn assert_field_type_is_reborrow<'tcx>( - tcx: TyCtxt<'tcx>, - infcx: &InferCtxt<'tcx>, - reborrow_trait: DefId, - impl_did: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - ty: Ty<'tcx>, - span: Span, -) -> Result<(), Vec>> { - if ty.ref_mutability() == Some(ty::Mutability::Mut) { - // Mutable references are Reborrow but not really. - return Ok(()); - } - let ocx = ObligationCtxt::new_with_diagnostics(infcx); - let cause = traits::ObligationCause::misc(span, impl_did); - let obligation = - Obligation::new(tcx, cause, param_env, ty::TraitRef::new(tcx, reborrow_trait, [ty])); - ocx.register_obligation(obligation); - let errors = ocx.evaluate_obligations_error_on_ambiguity(); - - if !errors.is_empty() { Err(errors) } else { Ok(()) } -} - #[derive(Clone, Copy)] struct CoerceSharedDiagnosticContext { impl_span: Span, @@ -906,6 +881,11 @@ struct CoerceSharedFieldPair<'tcx> { target: ReborrowDataField<'tcx>, } +struct CoerceSharedFields<'tcx> { + pairs: Vec>, + unpaired_sources: Vec>, +} + #[derive(Clone, Copy)] enum CoerceSharedFieldPairError<'tcx> { FieldStyleMismatch, @@ -947,7 +927,7 @@ fn collect_coerce_shared_field_pairs<'tcx>( source_args: ty::GenericArgsRef<'tcx>, target_def: ty::AdtDef<'tcx>, target_args: ty::GenericArgsRef<'tcx>, -) -> Result>, CoerceSharedFieldPairError<'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() { @@ -959,29 +939,39 @@ fn collect_coerce_shared_field_pairs<'tcx>( let target_fields = collect_reborrow_data_fields(tcx, target_def, target_args); if by_position { - target_fields - .into_iter() - .zip(source_fields.into_iter().map(Some).chain(std::iter::repeat(None))) - .map(|(target, source)| { - let source = - source.ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; - Ok(CoerceSharedFieldPair { source, target }) - }) - .collect() + let mut source_fields = source_fields.into_iter(); + let mut pairs = Vec::with_capacity(target_fields.len()); + + for target in target_fields { + let source = source_fields + .next() + .ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; + pairs.push(CoerceSharedFieldPair { source, target }); + } + + Ok(CoerceSharedFields { pairs, unpaired_sources: source_fields.collect() }) } else { - target_fields + let mut pairs = Vec::with_capacity(target_fields.len()); + + for target in &target_fields { + let source = source_fields + .iter() + .find(|source| tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)) + .ok_or(CoerceSharedFieldPairError::MissingSourceField { target: *target })?; + + pairs.push(CoerceSharedFieldPair { source: *source, target: *target }); + } + + let unpaired_sources = source_fields .into_iter() - .map(|target| { - let source = source_fields - .iter() - .find(|source| { - tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id) - }) - .ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; - - Ok(CoerceSharedFieldPair { source: *source, target }) + .filter(|source| { + !target_fields.iter().any(|target| { + tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id) + }) }) - .collect() + .collect(); + + Ok(CoerceSharedFields { pairs, unpaired_sources }) } } @@ -1034,14 +1024,14 @@ fn validate_coerce_shared_fields<'tcx>( target_def: ty::AdtDef<'tcx>, target_args: ty::GenericArgsRef<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let field_pairs = match collect_coerce_shared_field_pairs( + let fields = match collect_coerce_shared_field_pairs( tcx, source_def, source_args, target_def, target_args, ) { - Ok(field_pairs) => field_pairs, + Ok(fields) => fields, Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { return Err(tcx .dcx() @@ -1058,7 +1048,7 @@ fn validate_coerce_shared_fields<'tcx>( } }; - for field_pair in field_pairs { + for field_pair in fields.pairs { validate_coerce_shared_field( tcx, infcx, @@ -1073,6 +1063,20 @@ fn validate_coerce_shared_fields<'tcx>( )?; } + let reborrow_trait = tcx.require_lang_item(LangItem::Reborrow, span); + for source in fields.unpaired_sources { + validate_coerce_shared_unpaired_source_field( + tcx, + infcx, + impl_did, + param_env, + reborrow_trait, + trait_name, + diagnostic_context, + source, + )?; + } + // FIXME(reborrow): remove this temporary WF-side memcpy-ability guard once // the downstream CoerceShared implementation can correctly handle source and // target types that are not trivially memcpy-able. Refer to #157489 @@ -1242,6 +1246,47 @@ fn validate_coerce_shared_field<'tcx>( ) } +fn validate_coerce_shared_unpaired_source_field<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + reborrow_trait: DefId, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + mut source: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + source.ty = ocx + .deeply_normalize( + &traits::ObligationCause::misc(source.span, impl_did), + param_env, + Unnormalized::new_wip(source.ty), + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; + + if field_type_is_reborrow( + tcx, + infcx, + reborrow_trait, + impl_did, + param_env, + source.ty, + source.span, + ) || field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span) + { + return Ok(()); + } + + Err(tcx.dcx().emit_err(diagnostics::CoerceSharedOmittedSourceFieldNotCopyOrReborrow { + span: source.span, + impl_span: diagnostic_context.impl_span, + trait_name, + field_name: source.name, + field_ty: source.ty, + })) +} + fn validate_field_tys_satisfy_coerce_shared_relation<'tcx>( tcx: TyCtxt<'tcx>, infcx: &InferCtxt<'tcx>, @@ -1352,6 +1397,51 @@ fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>( && ocx.resolve_regions(impl_did, param_env, []).is_empty() } +fn field_type_is_reborrow<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + reborrow_trait: DefId, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + span: Span, +) -> bool { + if ty.ref_mutability() == Some(ty::Mutability::Mut) { + // Mutable references are Reborrow but not really. + return true; + } + + let ocx = ObligationCtxt::new(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + ocx.register_obligation(Obligation::new( + tcx, + cause, + param_env, + ty::TraitRef::new(tcx, reborrow_trait, [ty]), + )); + ocx.evaluate_obligations_error_on_ambiguity().is_empty() +} + +fn field_type_is_copy<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + span: Span, +) -> bool { + let copy_trait = tcx.require_lang_item(LangItem::Copy, span); + let ocx = ObligationCtxt::new(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + ocx.register_obligation(Obligation::new( + tcx, + cause, + param_env, + ty::TraitRef::new(tcx, copy_trait, [ty]), + )); + ocx.evaluate_obligations_error_on_ambiguity().is_empty() +} + fn assert_field_type_is_copy<'tcx>( tcx: TyCtxt<'tcx>, infcx: &InferCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index d196aa3a3f158..0dd17dce7e27a 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -1407,6 +1407,22 @@ pub(crate) struct CoerceSharedMissingField { pub field_name: Symbol, } +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires source fields omitted from the target to be `Copy` or \ + `Reborrow`" +)] +pub(crate) struct CoerceSharedOmittedSourceFieldNotCopyOrReborrow<'tcx> { + #[primary_span] + #[label("source field `{$field_name}` has type `{$field_ty}`")] + pub span: Span, + #[label("required by this `CoerceShared` implementation")] + pub impl_span: Span, + pub trait_name: &'static str, + pub field_name: Symbol, + pub field_ty: Ty<'tcx>, +} + #[derive(Diagnostic)] #[diag( "implementing `{$trait_name}` requires source and target structs to use the same field style" diff --git a/tests/ui/reborrow/coerce-shared-omitted-zst-field.rs b/tests/ui/reborrow/coerce-shared-omitted-zst-field.rs new file mode 100644 index 0000000000000..f74c914acd272 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-zst-field.rs @@ -0,0 +1,59 @@ +#![feature(reborrow)] +#![allow(dead_code)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct NonCopyZst; + +#[derive(Clone, Copy)] +struct CopyZst; + +struct ReborrowZst<'a>(PhantomData<&'a mut ()>); + +impl Reborrow for ReborrowZst<'_> {} + +struct Source<'a> { + value: &'a mut i32, + marker: NonCopyZst, + //~^ ERROR the trait bound `NonCopyZst: Copy` is not satisfied + //~| ERROR implementing `CoerceShared` requires source fields omitted from the target +} + +impl Reborrow for Source<'_> {} + +#[derive(Clone, Copy)] +struct Target<'a> { + value: &'a i32, +} + +impl<'a> CoerceShared> for Source<'a> {} + +struct CopyZstSource<'a> { + value: &'a mut i32, + marker: CopyZst, +} + +impl Reborrow for CopyZstSource<'_> {} + +#[derive(Clone, Copy)] +struct CopyZstTarget<'a> { + value: &'a i32, +} + +impl<'a> CoerceShared> for CopyZstSource<'a> {} + +struct ReborrowZstSource<'a> { + value: &'a mut i32, + marker: ReborrowZst<'a>, +} + +impl Reborrow for ReborrowZstSource<'_> {} + +#[derive(Clone, Copy)] +struct ReborrowZstTarget<'a> { + value: &'a i32, +} + +impl<'a> CoerceShared> for ReborrowZstSource<'a> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-omitted-zst-field.stderr b/tests/ui/reborrow/coerce-shared-omitted-zst-field.stderr new file mode 100644 index 0000000000000..43a6705ce8688 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-omitted-zst-field.stderr @@ -0,0 +1,24 @@ +error[E0277]: the trait bound `NonCopyZst: Copy` is not satisfied + --> $DIR/coerce-shared-omitted-zst-field.rs:17:5 + | +LL | marker: NonCopyZst, + | ^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `NonCopyZst` + | +help: consider annotating `NonCopyZst` with `#[derive(Copy)]` + | +LL + #[derive(Copy)] +LL | struct NonCopyZst; + | + +error: implementing `CoerceShared` requires source fields omitted from the target to be `Copy` or `Reborrow` + --> $DIR/coerce-shared-omitted-zst-field.rs:17:5 + | +LL | marker: NonCopyZst, + | ^^^^^^^^^^^^^^^^^^ source field `marker` has type `NonCopyZst` +... +LL | impl<'a> CoerceShared> for Source<'a> {} + | --------------------------------------------------- required by this `CoerceShared` implementation + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. From 13003283c5859ba4c4169f9a1758deed7fdcd7e7 Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Fri, 10 Jul 2026 01:11:54 +0200 Subject: [PATCH 3/3] Resolve CoerceShared field region obligations --- .../src/coherence/builtin.rs | 811 +----------------- .../src/coherence/builtin/coerce_shared.rs | 811 ++++++++++++++++++ .../coerce-shared-field-region-obligation.rs | 32 + ...erce-shared-field-region-obligation.stderr | 18 + 4 files changed, 864 insertions(+), 808 deletions(-) create mode 100644 compiler/rustc_hir_analysis/src/coherence/builtin/coerce_shared.rs create mode 100644 tests/ui/reborrow/coerce-shared-field-region-obligation.rs create mode 100644 tests/ui/reborrow/coerce-shared-field-region-obligation.stderr diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 3c79899ecb8a2..c6f7d6d77c69c 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -7,14 +7,12 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::ItemKind; -use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::{self, InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::ty::adjustment::CoerceUnsizedInfo; use rustc_middle::ty::print::PrintTraitRefExt as _; -use rustc_middle::ty::relate::solver_relating::RelateExt; use rustc_middle::ty::{ self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, suggest_constraining_type_params, }; @@ -29,6 +27,8 @@ use tracing::debug; use crate::diagnostics; +mod coerce_shared; + pub(super) fn check_trait<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: DefId, @@ -282,7 +282,7 @@ fn visit_implementation_of_coerce_shared(checker: &Checker<'_>) -> Result<(), Er // Just compute this for the side-effects, in particular reporting // errors; other parts of the code may demand it for the info of // course. - coerce_shared_info(tcx, impl_did) + coerce_shared::coerce_shared_info(tcx, impl_did) } fn is_from_coerce_pointee_derive(tcx: TyCtxt<'_>, span: Span) -> bool { @@ -552,309 +552,6 @@ pub(crate) fn reborrow_info<'tcx>( Ok(()) } -#[derive(Clone, Copy)] -struct CoerceSharedDiagnosticContext { - impl_span: Span, - trait_span: Span, - source_ty_span: Span, - target_ty_span: Span, - source_lifetime_span: Option, - target_lifetime_span: Option, -} - -#[derive(Clone, Copy)] -enum CoerceSharedTypeRole { - Source, - Target, -} - -impl CoerceSharedTypeRole { - fn as_str(self) -> &'static str { - match self { - CoerceSharedTypeRole::Source => "source", - CoerceSharedTypeRole::Target => "target", - } - } - - fn type_span(self, diagnostic_context: CoerceSharedDiagnosticContext) -> Span { - match self { - CoerceSharedTypeRole::Source => diagnostic_context.source_ty_span, - CoerceSharedTypeRole::Target => diagnostic_context.target_ty_span, - } - } -} - -fn coerce_shared_diagnostic_context( - tcx: TyCtxt<'_>, - impl_did: LocalDefId, -) -> CoerceSharedDiagnosticContext { - let item = tcx.hir_expect_item(impl_did); - let fallback_span = tcx.def_span(impl_did); - let mut diagnostic_context = CoerceSharedDiagnosticContext { - impl_span: item.span, - trait_span: fallback_span, - source_ty_span: fallback_span, - target_ty_span: fallback_span, - source_lifetime_span: None, - target_lifetime_span: None, - }; - - let ItemKind::Impl(impl_) = &item.kind else { - return diagnostic_context; - }; - let Some(of_trait) = impl_.of_trait else { - return diagnostic_context; - }; - - diagnostic_context.trait_span = of_trait.trait_ref.path.span; - diagnostic_context.source_ty_span = impl_.self_ty.span; - diagnostic_context.source_lifetime_span = first_explicit_lifetime_span_in_ty(impl_.self_ty) - .or_else(|| first_explicit_impl_lifetime_param_span(impl_.generics)); - - if let Some(target_ty) = coerce_shared_target_ty_from_path(of_trait.trait_ref.path) { - diagnostic_context.target_ty_span = target_ty.span; - diagnostic_context.target_lifetime_span = - first_explicit_lifetime_span_in_ambig_ty(target_ty); - } else { - diagnostic_context.target_ty_span = diagnostic_context.trait_span; - } - - diagnostic_context -} - -fn coerce_shared_target_ty_from_path<'hir>( - path: &'hir hir::Path<'hir>, -) -> Option<&'hir hir::Ty<'hir, hir::AmbigArg>> { - path.segments.last()?.args().args.iter().find_map(|arg| match arg { - hir::GenericArg::Type(ty) => Some(*ty), - hir::GenericArg::Lifetime(_) | hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => { - None - } - }) -} - -fn first_explicit_impl_lifetime_param_span(generics: &hir::Generics<'_>) -> Option { - generics.params.iter().find_map(|param| match param.kind { - hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { - Some(param.span) - } - hir::GenericParamKind::Lifetime { .. } - | hir::GenericParamKind::Type { .. } - | hir::GenericParamKind::Const { .. } => None, - }) -} - -fn first_explicit_lifetime_span(lifetime: &hir::Lifetime) -> Option { - match lifetime.kind { - hir::LifetimeKind::Param(_) | hir::LifetimeKind::Static - if !lifetime.ident.span.is_dummy() => - { - Some(lifetime.ident.span) - } - hir::LifetimeKind::Param(_) - | hir::LifetimeKind::Static - | hir::LifetimeKind::ImplicitObjectLifetimeDefault - | hir::LifetimeKind::Error(_) - | hir::LifetimeKind::Infer => None, - } -} - -fn first_explicit_lifetime_span_in_ambig_ty(ty: &hir::Ty<'_, hir::AmbigArg>) -> Option { - first_explicit_lifetime_span_in_ty(ty.as_unambig_ty()) -} - -fn first_explicit_lifetime_span_in_ty(ty: &hir::Ty<'_>) -> Option { - match ty.kind { - hir::TyKind::Ref(lifetime, mut_ty) => first_explicit_lifetime_span(lifetime) - .or_else(|| first_explicit_lifetime_span_in_ty(mut_ty.ty)), - hir::TyKind::Slice(ty) - | hir::TyKind::Array(ty, _) - | hir::TyKind::Pat(ty, _) - | hir::TyKind::FieldOf(ty, _) => first_explicit_lifetime_span_in_ty(ty), - hir::TyKind::Ptr(mut_ty) => first_explicit_lifetime_span_in_ty(mut_ty.ty), - hir::TyKind::Tup(tys) => tys.iter().find_map(first_explicit_lifetime_span_in_ty), - hir::TyKind::Path(qpath) => first_explicit_lifetime_span_in_qpath(qpath), - hir::TyKind::TraitObject(bounds, lifetime) => bounds - .iter() - .find_map(|bound| first_explicit_lifetime_span_in_path(bound.trait_ref.path)) - .or_else(|| first_explicit_lifetime_span(&lifetime)), - hir::TyKind::OpaqueDef(opaque) => first_explicit_lifetime_span_in_bounds(opaque.bounds), - hir::TyKind::TraitAscription(bounds) => first_explicit_lifetime_span_in_bounds(bounds), - hir::TyKind::FnPtr(fn_ptr) => { - fn_ptr.generic_params.iter().find_map(|param| match param.kind { - hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { - Some(param.span) - } - hir::GenericParamKind::Lifetime { .. } - | hir::GenericParamKind::Type { .. } - | hir::GenericParamKind::Const { .. } => None, - }) - } - hir::TyKind::UnsafeBinder(binder) => binder - .generic_params - .iter() - .find_map(|param| match param.kind { - hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { - Some(param.span) - } - hir::GenericParamKind::Lifetime { .. } - | hir::GenericParamKind::Type { .. } - | hir::GenericParamKind::Const { .. } => None, - }) - .or_else(|| first_explicit_lifetime_span_in_ty(binder.inner_ty)), - hir::TyKind::InferDelegation(_) - | hir::TyKind::Never - | hir::TyKind::Infer(()) - | hir::TyKind::Err(_) => None, - } -} - -fn first_explicit_lifetime_span_in_bounds(bounds: hir::GenericBounds<'_>) -> Option { - bounds.iter().find_map(|bound| match bound { - hir::GenericBound::Trait(poly_trait_ref) => { - first_explicit_lifetime_span_in_path(poly_trait_ref.trait_ref.path) - } - hir::GenericBound::Outlives(lifetime) => first_explicit_lifetime_span(lifetime), - hir::GenericBound::Use(args, _) => args.iter().find_map(|arg| match arg { - hir::PreciseCapturingArgKind::Lifetime(lifetime) => { - first_explicit_lifetime_span(lifetime) - } - hir::PreciseCapturingArgKind::Param(_) => None, - }), - }) -} - -fn first_explicit_lifetime_span_in_qpath(qpath: hir::QPath<'_>) -> Option { - match qpath { - hir::QPath::Resolved(qself, path) => qself - .and_then(first_explicit_lifetime_span_in_ty) - .or_else(|| first_explicit_lifetime_span_in_path(path)), - hir::QPath::TypeRelative(qself, segment) => first_explicit_lifetime_span_in_ty(qself) - .or_else(|| first_explicit_lifetime_span_in_path_segment(segment)), - } -} - -fn first_explicit_lifetime_span_in_path(path: &hir::Path<'_>) -> Option { - path.segments.iter().find_map(first_explicit_lifetime_span_in_path_segment) -} - -fn first_explicit_lifetime_span_in_path_segment(segment: &hir::PathSegment<'_>) -> Option { - first_explicit_lifetime_span_in_generic_args(segment.args()) -} - -fn first_explicit_lifetime_span_in_generic_args(args: &hir::GenericArgs<'_>) -> Option { - args.args - .iter() - .find_map(|arg| match arg { - hir::GenericArg::Lifetime(lifetime) => first_explicit_lifetime_span(lifetime), - hir::GenericArg::Type(ty) => first_explicit_lifetime_span_in_ambig_ty(ty), - hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => None, - }) - .or_else(|| { - args.constraints.iter().find_map(|constraint| { - first_explicit_lifetime_span_in_generic_args(constraint.gen_args) - .or_else(|| constraint.ty().and_then(first_explicit_lifetime_span_in_ty)) - }) - }) -} - -pub(crate) fn coerce_shared_info<'tcx>( - tcx: TyCtxt<'tcx>, - impl_did: LocalDefId, -) -> Result<(), ErrorGuaranteed> { - debug!("compute_coerce_shared_info(impl_did={:?})", impl_did); - let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); - let span = tcx.def_span(impl_did); - let diagnostic_context = coerce_shared_diagnostic_context(tcx, impl_did); - let trait_name = "CoerceShared"; - - let coerce_shared_trait = tcx.require_lang_item(LangItem::CoerceShared, span); - - let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip(); - let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip(); - - if trait_impl_lifetime_params_count(tcx, impl_did) != 1 { - return Err(tcx - .dcx() - .emit_err(diagnostics::CoerceSharedNotSingleLifetimeParam { span, trait_name })); - } - - assert_eq!(trait_ref.def_id, coerce_shared_trait); - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); - let param_env = tcx.param_env(impl_did); - let (source, target) = ocx - .deeply_normalize( - &traits::ObligationCause::misc(span, impl_did), - param_env, - Unnormalized::new_wip((source, trait_ref.args.type_at(1))), - ) - .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; - - assert!(!source.has_escaping_bound_vars()); - - 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() => - { - let a_lifetime = single_region_arg(args_a); - let b_lifetime = single_region_arg(args_b); - - if a_lifetime.is_none() || b_lifetime.is_none() { - return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMulti { - span: diagnostic_context.trait_span, - trait_name, - })); - } - - if a_lifetime != b_lifetime { - return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedLifetimeMismatch { - span: diagnostic_context.trait_span, - source_lifetime_span: diagnostic_context.source_lifetime_span, - target_lifetime_span: diagnostic_context.target_lifetime_span, - trait_name, - })); - } - - validate_reborrow_field_access( - tcx, - impl_did, - def_a, - trait_name, - diagnostic_context, - CoerceSharedTypeRole::Source, - )?; - validate_reborrow_field_access( - tcx, - impl_did, - def_b, - trait_name, - diagnostic_context, - CoerceSharedTypeRole::Target, - )?; - - validate_coerce_shared_fields( - tcx, - &infcx, - impl_did, - param_env, - coerce_shared_trait, - trait_name, - span, - diagnostic_context, - def_a, - args_a, - def_b, - args_b, - ) - } - - _ => { - // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. - Err(tcx.dcx().emit_err(diagnostics::CoerceUnsizedNonStruct { span, trait_name })) - } - } -} - fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize { tcx.generics_of(did) .own_params @@ -875,29 +572,6 @@ struct ReborrowDataField<'tcx> { span: Span, } -#[derive(Clone, Copy)] -struct CoerceSharedFieldPair<'tcx> { - source: ReborrowDataField<'tcx>, - target: ReborrowDataField<'tcx>, -} - -struct CoerceSharedFields<'tcx> { - pairs: Vec>, - unpaired_sources: Vec>, -} - -#[derive(Clone, Copy)] -enum CoerceSharedFieldPairError<'tcx> { - FieldStyleMismatch, - MissingSourceField { target: ReborrowDataField<'tcx> }, -} - -fn single_region_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) -} - fn collect_reborrow_data_fields<'tcx>( tcx: TyCtxt<'tcx>, def: ty::AdtDef<'tcx>, @@ -918,485 +592,6 @@ fn collect_reborrow_data_fields<'tcx>( .collect() } -// This is a coherence/WF check only. It verifies that the CoerceShared impl -// describes a structurally valid field-wise relation. Runtime lowering of the -// operation is not modeled here. -fn collect_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_position = matches!(target_variant.ctor_kind(), Some(CtorKind::Fn)); - let source_fields = collect_reborrow_data_fields(tcx, source_def, source_args); - let target_fields = collect_reborrow_data_fields(tcx, target_def, target_args); - - if by_position { - let mut source_fields = source_fields.into_iter(); - let mut pairs = Vec::with_capacity(target_fields.len()); - - for target in target_fields { - let source = source_fields - .next() - .ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?; - pairs.push(CoerceSharedFieldPair { source, target }); - } - - Ok(CoerceSharedFields { pairs, unpaired_sources: source_fields.collect() }) - } else { - let mut pairs = Vec::with_capacity(target_fields.len()); - - for target in &target_fields { - let source = source_fields - .iter() - .find(|source| tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)) - .ok_or(CoerceSharedFieldPairError::MissingSourceField { target: *target })?; - - pairs.push(CoerceSharedFieldPair { source: *source, target: *target }); - } - - let unpaired_sources = source_fields - .into_iter() - .filter(|source| { - !target_fields.iter().any(|target| { - tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id) - }) - }) - .collect(); - - Ok(CoerceSharedFields { pairs, unpaired_sources }) - } -} - -fn validate_reborrow_field_access( - tcx: TyCtxt<'_>, - impl_did: LocalDefId, - def: ty::AdtDef<'_>, - trait_name: &'static str, - diagnostic_context: CoerceSharedDiagnosticContext, - role: CoerceSharedTypeRole, -) -> 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(diagnostics::CoerceSharedInaccessibleField { - span: diagnostic_context.impl_span, - type_span: role.type_span(diagnostic_context), - trait_name, - role: role.as_str(), - type_name: tcx.item_name(def.did()), - })); - } - - for field in &variant.fields { - if !field.vis.is_accessible_from(module, tcx) { - return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedInaccessibleField { - span: diagnostic_context.impl_span, - type_span: role.type_span(diagnostic_context), - trait_name, - role: role.as_str(), - type_name: tcx.item_name(def.did()), - })); - } - } - - Ok(()) -} - -fn validate_coerce_shared_fields<'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, - diagnostic_context: CoerceSharedDiagnosticContext, - source_def: ty::AdtDef<'tcx>, - source_args: ty::GenericArgsRef<'tcx>, - target_def: ty::AdtDef<'tcx>, - target_args: ty::GenericArgsRef<'tcx>, -) -> Result<(), ErrorGuaranteed> { - let fields = match collect_coerce_shared_field_pairs( - tcx, - source_def, - source_args, - target_def, - target_args, - ) { - Ok(fields) => fields, - Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { - return Err(tcx - .dcx() - .emit_err(diagnostics::CoerceSharedFieldStyleMismatch { span, trait_name })); - } - Err(CoerceSharedFieldPairError::MissingSourceField { target }) => { - return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMissingField { - span: target.span, - source_ty_span: diagnostic_context.source_ty_span, - trait_name, - source_ty_name: tcx.item_name(source_def.did()), - field_name: target.name, - })); - } - }; - - for field_pair in fields.pairs { - validate_coerce_shared_field( - tcx, - infcx, - impl_did, - param_env, - coerce_shared_trait, - trait_name, - span, - diagnostic_context, - field_pair.source, - field_pair.target, - )?; - } - - let reborrow_trait = tcx.require_lang_item(LangItem::Reborrow, span); - for source in fields.unpaired_sources { - validate_coerce_shared_unpaired_source_field( - tcx, - infcx, - impl_did, - param_env, - reborrow_trait, - trait_name, - diagnostic_context, - source, - )?; - } - - // FIXME(reborrow): remove this temporary WF-side memcpy-ability guard once - // the downstream CoerceShared implementation can correctly handle source and - // target types that are not trivially memcpy-able. Refer to #157489 - validate_coerce_shared_fields_are_memcpy_compatible( - tcx, - infcx, - impl_did, - param_env, - coerce_shared_trait, - trait_name, - span, - diagnostic_context, - source_def, - source_args, - target_def, - target_args, - )?; - - Ok(()) -} - -fn validate_coerce_shared_fields_are_memcpy_compatible<'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, - diagnostic_context: CoerceSharedDiagnosticContext, - source_def: ty::AdtDef<'tcx>, - source_args: ty::GenericArgsRef<'tcx>, - target_def: ty::AdtDef<'tcx>, - target_args: ty::GenericArgsRef<'tcx>, -) -> Result<(), ErrorGuaranteed> { - let source_non_zst_fields = - non_zst_reborrow_data_fields(tcx, infcx, param_env, source_def, source_args); - let target_non_zst_fields = - non_zst_reborrow_data_fields(tcx, infcx, param_env, target_def, target_args); - - match (&source_non_zst_fields[..], &target_non_zst_fields[..]) { - ([], []) => Ok(()), - ([source], [target]) => { - if field_tys_satisfy_relation_after_normalization_and_resolution( - tcx, - impl_did, - param_env, - source.ty, - target.ty, - source.span, - FieldRelation::Equal, - ) { - return Ok(()); - } - - if matches!( - (source.ty.kind(), target.ty.kind()), - (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) - | (&ty::Alias(..), _) - | (_, &ty::Alias(..)) - ) && field_tys_satisfy_relation_after_normalization_and_resolution( - tcx, - impl_did, - param_env, - source.ty, - target.ty, - source.span, - FieldRelation::MutRefToSharedRef, - ) { - return Ok(()); - } - - validate_field_tys_satisfy_coerce_shared_relation( - tcx, - infcx, - impl_did, - param_env, - coerce_shared_trait, - trait_name, - span, - diagnostic_context, - *source, - *target, - ) - } - _ => Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMultipleNonZstFields { - span: diagnostic_context.impl_span, - source_ty_span: diagnostic_context.source_ty_span, - target_ty_span: diagnostic_context.target_ty_span, - trait_name, - source_count: source_non_zst_fields.len(), - target_count: target_non_zst_fields.len(), - })), - } -} - -fn non_zst_reborrow_data_fields<'tcx>( - tcx: TyCtxt<'tcx>, - infcx: &InferCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - def: ty::AdtDef<'tcx>, - args: ty::GenericArgsRef<'tcx>, -) -> Vec> { - collect_reborrow_data_fields(tcx, def, args) - .into_iter() - .filter(|field| { - !matches!( - tcx.layout_of(infcx.typing_env(param_env).as_query_input(field.ty)), - Ok(layout) if layout.is_zst() - ) - }) - .collect() -} - -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, - diagnostic_context: CoerceSharedDiagnosticContext, - source: ReborrowDataField<'tcx>, - target: ReborrowDataField<'tcx>, -) -> Result<(), ErrorGuaranteed> { - if matches!( - (source.ty.kind(), target.ty.kind()), - (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) - | (&ty::Alias(..), _) - | (_, &ty::Alias(..)) - ) && field_tys_satisfy_relation_after_normalization_and_resolution( - tcx, - impl_did, - param_env, - source.ty, - target.ty, - source.span, - FieldRelation::MutRefToSharedRef, - ) { - return Ok(()); - } - - if field_tys_satisfy_relation_after_normalization_and_resolution( - tcx, - impl_did, - param_env, - source.ty, - target.ty, - source.span, - FieldRelation::Equal, - ) { - return assert_field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span); - } - - validate_field_tys_satisfy_coerce_shared_relation( - tcx, - infcx, - impl_did, - param_env, - coerce_shared_trait, - trait_name, - span, - diagnostic_context, - source, - target, - ) -} - -fn validate_coerce_shared_unpaired_source_field<'tcx>( - tcx: TyCtxt<'tcx>, - infcx: &InferCtxt<'tcx>, - impl_did: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - reborrow_trait: DefId, - trait_name: &'static str, - diagnostic_context: CoerceSharedDiagnosticContext, - mut source: ReborrowDataField<'tcx>, -) -> Result<(), ErrorGuaranteed> { - let ocx = ObligationCtxt::new_with_diagnostics(infcx); - source.ty = ocx - .deeply_normalize( - &traits::ObligationCause::misc(source.span, impl_did), - param_env, - Unnormalized::new_wip(source.ty), - ) - .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; - - if field_type_is_reborrow( - tcx, - infcx, - reborrow_trait, - impl_did, - param_env, - source.ty, - source.span, - ) || field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span) - { - return Ok(()); - } - - Err(tcx.dcx().emit_err(diagnostics::CoerceSharedOmittedSourceFieldNotCopyOrReborrow { - span: source.span, - impl_span: diagnostic_context.impl_span, - trait_name, - field_name: source.name, - field_ty: source.ty, - })) -} - -fn validate_field_tys_satisfy_coerce_shared_relation<'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, - diagnostic_context: CoerceSharedDiagnosticContext, - source: ReborrowDataField<'tcx>, - target: ReborrowDataField<'tcx>, -) -> Result<(), ErrorGuaranteed> { - 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(emit_coerce_shared_field_mismatch( - tcx, - trait_name, - diagnostic_context, - source, - target, - )); - } - - ocx.resolve_regions_and_report_errors(impl_did, param_env, []) -} - -fn emit_coerce_shared_field_mismatch<'tcx>( - tcx: TyCtxt<'tcx>, - trait_name: &'static str, - diagnostic_context: CoerceSharedDiagnosticContext, - source: ReborrowDataField<'tcx>, - target: ReborrowDataField<'tcx>, -) -> ErrorGuaranteed { - tcx.dcx().emit_err(diagnostics::CoerceSharedFieldMismatch { - span: target.span, - source_span: source.span, - impl_span: diagnostic_context.impl_span, - source_name: source.name, - source_ty: source.ty, - target_name: target.name, - target_ty: target.ty, - trait_name, - }) -} - -enum FieldRelation { - Equal, - MutRefToSharedRef, -} - -fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>( - tcx: TyCtxt<'tcx>, - impl_did: LocalDefId, - param_env: ty::ParamEnv<'tcx>, - source_ty: Ty<'tcx>, - target_ty: Ty<'tcx>, - span: Span, - relation: FieldRelation, -) -> bool { - let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); - let cause = traits::ObligationCause::misc(span, impl_did); - let ocx = ObligationCtxt::new(&infcx); - - let Ok((source_ty, target_ty)) = - ocx.deeply_normalize(&cause, param_env, Unnormalized::new_wip((source_ty, target_ty))) - else { - return false; - }; - - if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() { - return false; - } - - match relation { - FieldRelation::Equal => { - if infcx.relate(param_env, source_ty, ty::Variance::Invariant, target_ty, span).is_err() - { - return false; - } - } - FieldRelation::MutRefToSharedRef => { - let ( - &ty::Ref(source_region, source_referent_ty, ty::Mutability::Mut), - &ty::Ref(target_region, target_referent_ty, ty::Mutability::Not), - ) = (source_ty.kind(), target_ty.kind()) - else { - return false; - }; - if source_region != target_region { - return false; - } - if ocx.sup(&cause, param_env, target_referent_ty, source_referent_ty).is_err() { - return false; - } - } - }; - - ocx.evaluate_obligations_error_on_ambiguity().is_empty() - && ocx.resolve_regions(impl_did, param_env, []).is_empty() -} - fn field_type_is_reborrow<'tcx>( tcx: TyCtxt<'tcx>, infcx: &InferCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin/coerce_shared.rs b/compiler/rustc_hir_analysis/src/coherence/builtin/coerce_shared.rs new file mode 100644 index 0000000000000..d40ba27cc3758 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/coherence/builtin/coerce_shared.rs @@ -0,0 +1,811 @@ +use rustc_errors::ErrorGuaranteed; +use rustc_hir as hir; +use rustc_hir::ItemKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::lang_items::LangItem; +use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; +use rustc_infer::traits::Obligation; +use rustc_middle::ty::relate::solver_relating::RelateExt; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized}; +use rustc_span::Span; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::traits::{self, ObligationCtxt}; +use tracing::debug; + +use super::{ + ReborrowDataField, assert_field_type_is_copy, collect_reborrow_data_fields, field_type_is_copy, + field_type_is_reborrow, trait_impl_lifetime_params_count, +}; +use crate::diagnostics; + +#[derive(Clone, Copy)] +struct CoerceSharedDiagnosticContext { + impl_span: Span, + trait_span: Span, + source_ty_span: Span, + target_ty_span: Span, + source_lifetime_span: Option, + target_lifetime_span: Option, +} + +#[derive(Clone, Copy)] +enum CoerceSharedTypeRole { + Source, + Target, +} + +impl CoerceSharedTypeRole { + fn as_str(self) -> &'static str { + match self { + CoerceSharedTypeRole::Source => "source", + CoerceSharedTypeRole::Target => "target", + } + } + + fn type_span(self, diagnostic_context: CoerceSharedDiagnosticContext) -> Span { + match self { + CoerceSharedTypeRole::Source => diagnostic_context.source_ty_span, + CoerceSharedTypeRole::Target => diagnostic_context.target_ty_span, + } + } +} + +fn coerce_shared_diagnostic_context( + tcx: TyCtxt<'_>, + impl_did: LocalDefId, +) -> CoerceSharedDiagnosticContext { + let item = tcx.hir_expect_item(impl_did); + let fallback_span = tcx.def_span(impl_did); + let mut diagnostic_context = CoerceSharedDiagnosticContext { + impl_span: item.span, + trait_span: fallback_span, + source_ty_span: fallback_span, + target_ty_span: fallback_span, + source_lifetime_span: None, + target_lifetime_span: None, + }; + + let ItemKind::Impl(impl_) = &item.kind else { + return diagnostic_context; + }; + let Some(of_trait) = impl_.of_trait else { + return diagnostic_context; + }; + + diagnostic_context.trait_span = of_trait.trait_ref.path.span; + diagnostic_context.source_ty_span = impl_.self_ty.span; + diagnostic_context.source_lifetime_span = first_explicit_lifetime_span_in_ty(impl_.self_ty) + .or_else(|| first_explicit_impl_lifetime_param_span(impl_.generics)); + + if let Some(target_ty) = coerce_shared_target_ty_from_path(of_trait.trait_ref.path) { + diagnostic_context.target_ty_span = target_ty.span; + diagnostic_context.target_lifetime_span = + first_explicit_lifetime_span_in_ambig_ty(target_ty); + } else { + diagnostic_context.target_ty_span = diagnostic_context.trait_span; + } + + diagnostic_context +} + +fn coerce_shared_target_ty_from_path<'hir>( + path: &'hir hir::Path<'hir>, +) -> Option<&'hir hir::Ty<'hir, hir::AmbigArg>> { + path.segments.last()?.args().args.iter().find_map(|arg| match arg { + hir::GenericArg::Type(ty) => Some(*ty), + hir::GenericArg::Lifetime(_) | hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => { + None + } + }) +} + +fn first_explicit_impl_lifetime_param_span(generics: &hir::Generics<'_>) -> Option { + generics.params.iter().find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) +} + +fn first_explicit_lifetime_span(lifetime: &hir::Lifetime) -> Option { + match lifetime.kind { + hir::LifetimeKind::Param(_) | hir::LifetimeKind::Static + if !lifetime.ident.span.is_dummy() => + { + Some(lifetime.ident.span) + } + hir::LifetimeKind::Param(_) + | hir::LifetimeKind::Static + | hir::LifetimeKind::ImplicitObjectLifetimeDefault + | hir::LifetimeKind::Error(_) + | hir::LifetimeKind::Infer => None, + } +} + +fn first_explicit_lifetime_span_in_ambig_ty(ty: &hir::Ty<'_, hir::AmbigArg>) -> Option { + first_explicit_lifetime_span_in_ty(ty.as_unambig_ty()) +} + +fn first_explicit_lifetime_span_in_ty(ty: &hir::Ty<'_>) -> Option { + match ty.kind { + hir::TyKind::Ref(lifetime, mut_ty) => first_explicit_lifetime_span(lifetime) + .or_else(|| first_explicit_lifetime_span_in_ty(mut_ty.ty)), + hir::TyKind::Slice(ty) + | hir::TyKind::Array(ty, _) + | hir::TyKind::Pat(ty, _) + | hir::TyKind::FieldOf(ty, _) => first_explicit_lifetime_span_in_ty(ty), + hir::TyKind::Ptr(mut_ty) => first_explicit_lifetime_span_in_ty(mut_ty.ty), + hir::TyKind::Tup(tys) => tys.iter().find_map(first_explicit_lifetime_span_in_ty), + hir::TyKind::Path(qpath) => first_explicit_lifetime_span_in_qpath(qpath), + hir::TyKind::TraitObject(bounds, lifetime) => bounds + .iter() + .find_map(|bound| first_explicit_lifetime_span_in_path(bound.trait_ref.path)) + .or_else(|| first_explicit_lifetime_span(&lifetime)), + hir::TyKind::OpaqueDef(opaque) => first_explicit_lifetime_span_in_bounds(opaque.bounds), + hir::TyKind::TraitAscription(bounds) => first_explicit_lifetime_span_in_bounds(bounds), + hir::TyKind::FnPtr(fn_ptr) => { + fn_ptr.generic_params.iter().find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) + } + hir::TyKind::UnsafeBinder(binder) => binder + .generic_params + .iter() + .find_map(|param| match param.kind { + hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } => { + Some(param.span) + } + hir::GenericParamKind::Lifetime { .. } + | hir::GenericParamKind::Type { .. } + | hir::GenericParamKind::Const { .. } => None, + }) + .or_else(|| first_explicit_lifetime_span_in_ty(binder.inner_ty)), + hir::TyKind::InferDelegation(_) + | hir::TyKind::Never + | hir::TyKind::Infer(()) + | hir::TyKind::Err(_) => None, + } +} + +fn first_explicit_lifetime_span_in_bounds(bounds: hir::GenericBounds<'_>) -> Option { + bounds.iter().find_map(|bound| match bound { + hir::GenericBound::Trait(poly_trait_ref) => { + first_explicit_lifetime_span_in_path(poly_trait_ref.trait_ref.path) + } + hir::GenericBound::Outlives(lifetime) => first_explicit_lifetime_span(lifetime), + hir::GenericBound::Use(args, _) => args.iter().find_map(|arg| match arg { + hir::PreciseCapturingArgKind::Lifetime(lifetime) => { + first_explicit_lifetime_span(lifetime) + } + hir::PreciseCapturingArgKind::Param(_) => None, + }), + }) +} + +fn first_explicit_lifetime_span_in_qpath(qpath: hir::QPath<'_>) -> Option { + match qpath { + hir::QPath::Resolved(qself, path) => qself + .and_then(first_explicit_lifetime_span_in_ty) + .or_else(|| first_explicit_lifetime_span_in_path(path)), + hir::QPath::TypeRelative(qself, segment) => first_explicit_lifetime_span_in_ty(qself) + .or_else(|| first_explicit_lifetime_span_in_path_segment(segment)), + } +} + +fn first_explicit_lifetime_span_in_path(path: &hir::Path<'_>) -> Option { + path.segments.iter().find_map(first_explicit_lifetime_span_in_path_segment) +} + +fn first_explicit_lifetime_span_in_path_segment(segment: &hir::PathSegment<'_>) -> Option { + first_explicit_lifetime_span_in_generic_args(segment.args()) +} + +fn first_explicit_lifetime_span_in_generic_args(args: &hir::GenericArgs<'_>) -> Option { + args.args + .iter() + .find_map(|arg| match arg { + hir::GenericArg::Lifetime(lifetime) => first_explicit_lifetime_span(lifetime), + hir::GenericArg::Type(ty) => first_explicit_lifetime_span_in_ambig_ty(ty), + hir::GenericArg::Const(_) | hir::GenericArg::Infer(_) => None, + }) + .or_else(|| { + args.constraints.iter().find_map(|constraint| { + first_explicit_lifetime_span_in_generic_args(constraint.gen_args) + .or_else(|| constraint.ty().and_then(first_explicit_lifetime_span_in_ty)) + }) + }) +} + +pub(super) fn coerce_shared_info<'tcx>( + tcx: TyCtxt<'tcx>, + impl_did: LocalDefId, +) -> Result<(), ErrorGuaranteed> { + debug!("compute_coerce_shared_info(impl_did={:?})", impl_did); + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let span = tcx.def_span(impl_did); + let diagnostic_context = coerce_shared_diagnostic_context(tcx, impl_did); + let trait_name = "CoerceShared"; + + let coerce_shared_trait = tcx.require_lang_item(LangItem::CoerceShared, span); + + let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip(); + let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip(); + + if trait_impl_lifetime_params_count(tcx, impl_did) != 1 { + return Err(tcx + .dcx() + .emit_err(diagnostics::CoerceSharedNotSingleLifetimeParam { span, trait_name })); + } + + assert_eq!(trait_ref.def_id, coerce_shared_trait); + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let param_env = tcx.param_env(impl_did); + let (source, target) = ocx + .deeply_normalize( + &traits::ObligationCause::misc(span, impl_did), + param_env, + Unnormalized::new_wip((source, trait_ref.args.type_at(1))), + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + if !errors.is_empty() { + return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); + } + + assert!(!source.has_escaping_bound_vars()); + + 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() => + { + let a_lifetime = single_region_arg(args_a); + let b_lifetime = single_region_arg(args_b); + + if a_lifetime.is_none() || b_lifetime.is_none() { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMulti { + span: diagnostic_context.trait_span, + trait_name, + })); + } + + if a_lifetime != b_lifetime { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedLifetimeMismatch { + span: diagnostic_context.trait_span, + source_lifetime_span: diagnostic_context.source_lifetime_span, + target_lifetime_span: diagnostic_context.target_lifetime_span, + trait_name, + })); + } + + validate_reborrow_field_access( + tcx, + impl_did, + def_a, + trait_name, + diagnostic_context, + CoerceSharedTypeRole::Source, + )?; + validate_reborrow_field_access( + tcx, + impl_did, + def_b, + trait_name, + diagnostic_context, + CoerceSharedTypeRole::Target, + )?; + + validate_coerce_shared_fields( + &infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + def_a, + args_a, + def_b, + args_b, + ) + } + + _ => { + // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. + Err(tcx.dcx().emit_err(diagnostics::CoerceUnsizedNonStruct { span, trait_name })) + } + } +} + +#[derive(Clone, Copy)] +struct CoerceSharedFieldPair<'tcx> { + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +} + +struct CoerceSharedFields<'tcx> { + pairs: Vec>, + unpaired_sources: Vec>, +} + +#[derive(Clone, Copy)] +enum CoerceSharedFieldPairError<'tcx> { + FieldStyleMismatch, + MissingSourceField { target: ReborrowDataField<'tcx> }, +} + +fn single_region_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) +} + +// This is a coherence/WF check only. It verifies that the CoerceShared impl +// describes a structurally valid field-wise relation. Runtime lowering of the +// operation is not modeled here. +fn collect_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 source_fields = collect_reborrow_data_fields(tcx, source_def, source_args); + let target_fields = collect_reborrow_data_fields(tcx, target_def, target_args); + + let mut pairs = Vec::with_capacity(target_fields.len()); + + for target in &target_fields { + let source = source_fields + .iter() + .find(|source| tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)) + .ok_or(CoerceSharedFieldPairError::MissingSourceField { target: *target })?; + + pairs.push(CoerceSharedFieldPair { source: *source, target: *target }); + } + + let unpaired_sources = source_fields + .into_iter() + .filter(|source| { + !target_fields + .iter() + .any(|target| tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)) + }) + .collect(); + + Ok(CoerceSharedFields { pairs, unpaired_sources }) +} + +fn validate_reborrow_field_access( + tcx: TyCtxt<'_>, + impl_did: LocalDefId, + def: ty::AdtDef<'_>, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + role: CoerceSharedTypeRole, +) -> 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(diagnostics::CoerceSharedInaccessibleField { + span: diagnostic_context.impl_span, + type_span: role.type_span(diagnostic_context), + trait_name, + role: role.as_str(), + type_name: tcx.item_name(def.did()), + })); + } + + for field in &variant.fields { + if !field.vis.is_accessible_from(module, tcx) { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedInaccessibleField { + span: diagnostic_context.impl_span, + type_span: role.type_span(diagnostic_context), + trait_name, + role: role.as_str(), + type_name: tcx.item_name(def.did()), + })); + } + } + + Ok(()) +} + +fn validate_coerce_shared_fields<'tcx>( + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + diagnostic_context: CoerceSharedDiagnosticContext, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let tcx = infcx.tcx; + let fields = match collect_coerce_shared_field_pairs( + tcx, + source_def, + source_args, + target_def, + target_args, + ) { + Ok(fields) => fields, + Err(CoerceSharedFieldPairError::FieldStyleMismatch) => { + return Err(tcx + .dcx() + .emit_err(diagnostics::CoerceSharedFieldStyleMismatch { span, trait_name })); + } + Err(CoerceSharedFieldPairError::MissingSourceField { target }) => { + return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMissingField { + span: target.span, + source_ty_span: diagnostic_context.source_ty_span, + trait_name, + source_ty_name: tcx.item_name(source_def.did()), + field_name: target.name, + })); + } + }; + + for field_pair in fields.pairs { + validate_coerce_shared_field( + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + field_pair.source, + field_pair.target, + )?; + } + + let reborrow_trait = tcx.require_lang_item(LangItem::Reborrow, span); + for source in fields.unpaired_sources { + validate_coerce_shared_unpaired_source_field( + infcx, + impl_did, + param_env, + reborrow_trait, + trait_name, + diagnostic_context, + source, + )?; + } + + // FIXME(reborrow): remove this temporary WF-side memcpy-ability guard once + // the downstream CoerceShared implementation can correctly handle source and + // target types that are not trivially memcpy-able. Refer to #157489 + validate_coerce_shared_fields_are_memcpy_compatible( + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + source_def, + source_args, + target_def, + target_args, + )?; + + Ok(()) +} + +fn validate_coerce_shared_fields_are_memcpy_compatible<'tcx>( + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + diagnostic_context: CoerceSharedDiagnosticContext, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let tcx = infcx.tcx; + let source_non_zst_fields = + non_zst_reborrow_data_fields(infcx, param_env, source_def, source_args); + let target_non_zst_fields = + non_zst_reborrow_data_fields(infcx, param_env, target_def, target_args); + + match (&source_non_zst_fields[..], &target_non_zst_fields[..]) { + ([], []) => Ok(()), + ([source], [target]) => { + if field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::Equal, + ) { + return Ok(()); + } + + if matches!( + (source.ty.kind(), target.ty.kind()), + (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) + | (&ty::Alias(..), _) + | (_, &ty::Alias(..)) + ) && field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::MutRefToSharedRef, + ) { + return Ok(()); + } + + validate_field_tys_satisfy_coerce_shared_relation( + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + *source, + *target, + ) + } + _ => Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMultipleNonZstFields { + span: diagnostic_context.impl_span, + source_ty_span: diagnostic_context.source_ty_span, + target_ty_span: diagnostic_context.target_ty_span, + trait_name, + source_count: source_non_zst_fields.len(), + target_count: target_non_zst_fields.len(), + })), + } +} + +fn non_zst_reborrow_data_fields<'tcx>( + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + def: ty::AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, +) -> Vec> { + let tcx = infcx.tcx; + collect_reborrow_data_fields(tcx, def, args) + .into_iter() + .filter(|field| { + !matches!( + tcx.layout_of(infcx.typing_env(param_env).as_query_input(field.ty)), + Ok(layout) if layout.is_zst() + ) + }) + .collect() +} + +fn validate_coerce_shared_field<'tcx>( + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let tcx = infcx.tcx; + if matches!( + (source.ty.kind(), target.ty.kind()), + (&ty::Ref(_, _, ty::Mutability::Mut), &ty::Ref(_, _, ty::Mutability::Not)) + | (&ty::Alias(..), _) + | (_, &ty::Alias(..)) + ) && field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::MutRefToSharedRef, + ) { + return Ok(()); + } + + if field_tys_satisfy_relation_after_normalization_and_resolution( + tcx, + impl_did, + param_env, + source.ty, + target.ty, + source.span, + FieldRelation::Equal, + ) { + return assert_field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span); + } + + validate_field_tys_satisfy_coerce_shared_relation( + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + diagnostic_context, + source, + target, + ) +} + +fn validate_coerce_shared_unpaired_source_field<'tcx>( + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + reborrow_trait: DefId, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + mut source: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let tcx = infcx.tcx; + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + source.ty = ocx + .deeply_normalize( + &traits::ObligationCause::misc(source.span, impl_did), + param_env, + Unnormalized::new_wip(source.ty), + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; + + if field_type_is_reborrow( + tcx, + infcx, + reborrow_trait, + impl_did, + param_env, + source.ty, + source.span, + ) || field_type_is_copy(tcx, infcx, impl_did, param_env, source.ty, source.span) + { + return Ok(()); + } + + Err(tcx.dcx().emit_err(diagnostics::CoerceSharedOmittedSourceFieldNotCopyOrReborrow { + span: source.span, + impl_span: diagnostic_context.impl_span, + trait_name, + field_name: source.name, + field_ty: source.ty, + })) +} + +fn validate_field_tys_satisfy_coerce_shared_relation<'tcx>( + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let tcx = infcx.tcx; + 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(emit_coerce_shared_field_mismatch( + tcx, + trait_name, + diagnostic_context, + source, + target, + )); + } + + ocx.resolve_regions_and_report_errors(impl_did, param_env, []) +} + +fn emit_coerce_shared_field_mismatch<'tcx>( + tcx: TyCtxt<'tcx>, + trait_name: &'static str, + diagnostic_context: CoerceSharedDiagnosticContext, + source: ReborrowDataField<'tcx>, + target: ReborrowDataField<'tcx>, +) -> ErrorGuaranteed { + tcx.dcx().emit_err(diagnostics::CoerceSharedFieldMismatch { + span: target.span, + source_span: source.span, + impl_span: diagnostic_context.impl_span, + source_name: source.name, + source_ty: source.ty, + target_name: target.name, + target_ty: target.ty, + trait_name, + }) +} + +enum FieldRelation { + Equal, + MutRefToSharedRef, +} + +// Normalizing the outer `CoerceShared` types does not normalize their fields: +// instantiating a field can expose projections. Each candidate relation uses a +// fresh inference context, so failed checks cannot affect the next one; this +// intentionally normalizes the fields for each check. +fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>( + tcx: TyCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + source_ty: Ty<'tcx>, + target_ty: Ty<'tcx>, + span: Span, + relation: FieldRelation, +) -> bool { + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let cause = traits::ObligationCause::misc(span, impl_did); + let ocx = ObligationCtxt::new(&infcx); + + let Ok((source_ty, target_ty)) = + ocx.deeply_normalize(&cause, param_env, Unnormalized::new_wip((source_ty, target_ty))) + else { + return false; + }; + + if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() { + return false; + } + + match relation { + FieldRelation::Equal => { + if infcx.relate(param_env, source_ty, ty::Variance::Invariant, target_ty, span).is_err() + { + return false; + } + } + FieldRelation::MutRefToSharedRef => { + let ( + &ty::Ref(source_region, source_referent_ty, ty::Mutability::Mut), + &ty::Ref(target_region, target_referent_ty, ty::Mutability::Not), + ) = (source_ty.kind(), target_ty.kind()) + else { + return false; + }; + if source_region != target_region { + return false; + } + if ocx.sup(&cause, param_env, target_referent_ty, source_referent_ty).is_err() { + return false; + } + } + }; + + ocx.evaluate_obligations_error_on_ambiguity().is_empty() + && ocx.resolve_regions(impl_did, param_env, []).is_empty() +} diff --git a/tests/ui/reborrow/coerce-shared-field-region-obligation.rs b/tests/ui/reborrow/coerce-shared-field-region-obligation.rs new file mode 100644 index 0000000000000..4a39301d6cd18 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-region-obligation.rs @@ -0,0 +1,32 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +struct FieldMut<'a, T> { + value: &'a mut T, +} + +impl<'a, T> Reborrow for FieldMut<'a, T> {} + +#[derive(Clone, Copy)] +struct FieldRef<'a, T> { + value: &'a T, +} + +impl<'a, T> CoerceShared> for FieldMut<'a, T> {} + +struct Source<'a> { + field: FieldMut<'a, &'a ()>, +} + +impl Reborrow for Source<'_> {} + +#[derive(Clone, Copy)] +struct Target<'a> { + field: FieldRef<'a, &'static ()>, +} + +impl<'a> CoerceShared> for Source<'a> {} +//~^ ERROR mismatched types + +fn main() {} diff --git a/tests/ui/reborrow/coerce-shared-field-region-obligation.stderr b/tests/ui/reborrow/coerce-shared-field-region-obligation.stderr new file mode 100644 index 0000000000000..06b89073f49e1 --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-field-region-obligation.stderr @@ -0,0 +1,18 @@ +error[E0308]: mismatched types + --> $DIR/coerce-shared-field-region-obligation.rs:29:1 + | +LL | impl<'a> CoerceShared> for Source<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch + | + = note: expected trait `CoerceShared>` + found trait `CoerceShared>` +note: the lifetime `'a` as defined here... + --> $DIR/coerce-shared-field-region-obligation.rs:29:6 + | +LL | impl<'a> CoerceShared> for Source<'a> {} + | ^^ + = note: ...does not necessarily outlive the static lifetime + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`.