diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 2ba7e026461f3..ce5283ecbda79 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, @@ -533,7 +534,7 @@ pub(crate) fn reborrow_info<'tcx>( }; let lifetimes_count = generic_lifetime_params_count(args); - let data_fields = collect_struct_data_fields(tcx, def, args); + let data_fields = collect_reborrow_data_fields(tcx, def, args); if lifetimes_count != 1 { let item = tcx.hir_expect_item(impl_did); @@ -551,15 +552,15 @@ pub(crate) fn reborrow_info<'tcx>( } // We've found some data fields. They must all be either be Copy or Reborrow. - for (field, span) in data_fields { + for field in data_fields { if assert_field_type_is_reborrow( tcx, &infcx, reborrow_trait, impl_did, param_env, - field, - span, + field.ty, + field.span, ) .is_ok() { @@ -568,7 +569,7 @@ pub(crate) fn reborrow_info<'tcx>( } // Field does not implement Reborrow: it must be Copy. - assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, field, span)?; + assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, field.ty, field.span)?; } Ok(()) @@ -631,26 +632,14 @@ pub(crate) fn coerce_shared_info<'tcx>( let param_env = tcx.param_env(impl_did); assert!(!source.has_escaping_bound_vars()); - let data = match (source.kind(), target.kind()) { + match (source.kind(), target.kind()) { (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) if def_a.is_struct() && def_b.is_struct() => { - // Check that both A and B have exactly one lifetime argument, and that they have the - // same number of data fields that is not more than 1. The eventual intention is to - // support multiple lifetime arguments (with the reborrowed lifetimes inferred from - // usage one way or another) and multiple data fields with B allowed to leave out fields - // from A. The current state is just the simplest choice. - let a_lifetimes_count = generic_lifetime_params_count(args_a); - let a_data_fields = collect_struct_data_fields(tcx, def_a, args_a); - let b_lifetimes_count = generic_lifetime_params_count(args_b); - let b_data_fields = collect_struct_data_fields(tcx, def_b, args_b); - - if a_lifetimes_count != 1 - || b_lifetimes_count != 1 - || a_data_fields.len() > 1 - || b_data_fields.len() > 1 - || a_data_fields.len() != b_data_fields.len() - { + let a_lifetime = single_region_arg(args_a); + let b_lifetime = single_region_arg(args_b); + + if a_lifetime.is_none() || b_lifetime.is_none() { let item = tcx.hir_expect_item(impl_did); let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = &item.kind @@ -663,77 +652,44 @@ pub(crate) fn coerce_shared_info<'tcx>( return Err(tcx.dcx().emit_err(errors::CoerceSharedMulti { span, trait_name })); } - if a_data_fields.len() == 1 { - // We found one data field for both: we'll attempt to perform CoerceShared between - // them below. - let (a, span_a) = a_data_fields[0]; - let (b, span_b) = b_data_fields[0]; + if a_lifetime != b_lifetime { + let item = tcx.hir_expect_item(impl_did); + let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = + &item.kind + { + of_trait.trait_ref.path.span + } else { + tcx.def_span(impl_did) + }; - Some((a, b, coerce_shared_trait, span_a, span_b)) - } else { - // We found no data fields in either: this is a reborrowable marker type being - // coerced into a shared marker. That is fine too. - None + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedLifetimeMismatch { span, trait_name })); } - } - _ => { - // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. - return Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })); - } - }; + validate_reborrow_field_access(tcx, impl_did, def_a, trait_name, span)?; + validate_reborrow_field_access(tcx, impl_did, def_b, trait_name, span)?; - // We've proven that we have two types with one lifetime each and 0 or 1 data fields each. - if let Some((source, target, trait_def_id, source_field_span, _target_field_span)) = data { - // struct Source(SourceData); - // struct Target(TargetData); - // - // 1 data field each; they must be the same type and Copy, or relate to one another using - // CoerceShared. - if source.ref_mutability() == Some(ty::Mutability::Mut) - && target.ref_mutability() == Some(ty::Mutability::Not) - && infcx - .eq_structurally_relating_aliases( - param_env, - source.peel_refs(), - target.peel_refs(), - source_field_span, - ) - .is_ok() - { - // &mut T implements CoerceShared to &T, except not really. - return Ok(()); - } - if infcx - .eq_structurally_relating_aliases(param_env, source, target, source_field_span) - .is_err() - { - // The two data fields don't agree on a common type; this means - // that they must be `A: CoerceShared`. Register an obligation - // for that. - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); - let cause = traits::ObligationCause::misc(span, impl_did); - let obligation = Obligation::new( + validate_coerce_shared_fields( tcx, - cause, + &infcx, + impl_did, param_env, - ty::TraitRef::new(tcx, trait_def_id, [source, target]), - ); - ocx.register_obligation(obligation); - let errors = ocx.evaluate_obligations_error_on_ambiguity(); + coerce_shared_trait, + trait_name, + span, + def_a, + args_a, + def_b, + args_b, + ) + } - if !errors.is_empty() { - return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); - } - // Finally, resolve all regions. - ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; - } else { - // Types match: check that it is Copy. - assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, source, source_field_span)?; + _ => { + // Note: reusing CoerceUnsizedNonStruct error as it takes trait_name as argument. + Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })) } } - - Ok(()) } fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize { @@ -748,26 +704,303 @@ fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize { args.iter().filter(|arg| arg.as_region().is_some()).count() } -// FIXME(#155345): This should return `Unnormalized` -fn collect_struct_data_fields<'tcx>( +#[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<(Ty<'tcx>, Span)> { +) -> Vec> { def.non_enum_variant() .fields .iter() - .filter_map(|f| { - // Ignore PhantomData fields - let ty = f.ty(tcx, args).skip_norm_wip(); - if ty.is_phantom_data() { - return None; - } - Some((ty, tcx.def_span(f.did))) + .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, + span: Span, +) -> Result<(), ErrorGuaranteed> { + let module = tcx.parent_module_from_def_id(impl_did); + let variant = def.non_enum_variant(); + if variant.field_list_has_applicable_non_exhaustive() { + return Err(tcx.dcx().emit_err(errors::CoerceSharedInaccessibleField { span, trait_name })); + } + + for field in &variant.fields { + if !field.vis.is_accessible_from(module, tcx) { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedInaccessibleField { span, trait_name })); + } + } + + Ok(()) +} + +fn validate_coerce_shared_fields<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + source_def: ty::AdtDef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, + target_def: ty::AdtDef<'tcx>, + target_args: ty::GenericArgsRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let field_pairs = match 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(errors::CoerceSharedFieldStyleMismatch { span, trait_name })); + } + Err(CoerceSharedFieldPairError::MissingSourceField { target }) => { + return Err(tcx + .dcx() + .emit_err(errors::CoerceSharedMissingField { span: target.span, trait_name })); + } + }; + + for field_pair in field_pairs { + validate_coerce_shared_field( + tcx, + infcx, + impl_did, + param_env, + coerce_shared_trait, + trait_name, + span, + field_pair.source, + field_pair.target, + )?; + } + + Ok(()) +} + +fn validate_coerce_shared_field<'tcx>( + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, + impl_did: LocalDefId, + param_env: ty::ParamEnv<'tcx>, + coerce_shared_trait: DefId, + trait_name: &'static str, + span: Span, + source: 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); + } + + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + let cause = traits::ObligationCause::misc(span, impl_did); + ocx.register_obligation(Obligation::new( + tcx, + cause, + param_env, + ty::TraitRef::new(tcx, coerce_shared_trait, [source.ty, target.ty]), + )); + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + + if !errors.is_empty() { + return Err(tcx.dcx().emit_err(errors::CoerceSharedFieldMismatch { + span: target.span, + source_span: source.span, + source_name: source.name, + source_ty: source.ty, + target_name: target.name, + target_ty: target.ty, + trait_name, + })); + } + + ocx.resolve_regions_and_report_errors(impl_did, param_env, []) +} + +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) = + ocx.structurally_normalize_ty(&cause, param_env, Unnormalized::new_wip(source_ty)) + else { + return false; + }; + let Ok(target_ty) = + ocx.structurally_normalize_ty(&cause, param_env, Unnormalized::new_wip(target_ty)) + else { + return false; + }; + + if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() { + return false; + } + + let (source_ty, target_ty) = match relation { + FieldRelation::Equal => (source_ty, target_ty), + 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; + } + (source_referent_ty, target_referent_ty) + } + }; + + let Ok(goals) = infcx.eq_structurally_relating_aliases(param_env, source_ty, target_ty, span) + else { + return false; + }; + + ocx.register_obligations( + goals + .into_iter() + .map(|goal| Obligation::new(tcx, cause.clone(), goal.param_env, goal.predicate)), + ); + + 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/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index b968a626d398f..061586ce1cc90 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1371,13 +1371,75 @@ pub(crate) struct CoerceSharedNotSingleLifetimeParam { } #[derive(Diagnostic)] -#[diag("implementing `{$trait_name}` does not allow multiple lifetimes or fields to be coerced")] +#[diag( + "implementing `{$trait_name}` requires exactly one lifetime argument in the reborrowed type" +)] pub(crate) struct CoerceSharedMulti { #[primary_span] pub span: Span, pub trait_name: &'static str, } +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires source and target to use the same reborrow lifetime \ + argument" +)] +pub(crate) struct CoerceSharedLifetimeMismatch { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires corresponding fields to match, \ + be reborrowable with `CoerceShared`, or coerce a mutable reference field \ + to a shared reference field" +)] +pub(crate) struct CoerceSharedFieldMismatch<'tcx> { + #[primary_span] + #[label("target field `{$target_name}` has type `{$target_ty}`")] + pub span: Span, + #[label("source field `{$source_name}` has type `{$source_ty}`")] + pub source_span: Span, + pub source_name: Symbol, + pub source_ty: Ty<'tcx>, + pub target_name: Symbol, + pub target_ty: Ty<'tcx>, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires every target field to have a corresponding source field" +)] +pub(crate) struct CoerceSharedMissingField { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires source and target structs to use the same field style" +)] +pub(crate) struct CoerceSharedFieldStyleMismatch { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + +#[derive(Diagnostic)] +#[diag( + "implementing `{$trait_name}` requires all source and target fields to be accessible from the impl" +)] +pub(crate) struct CoerceSharedInaccessibleField { + #[primary_span] + pub span: Span, + pub trait_name: &'static str, +} + #[derive(Diagnostic)] #[diag("the trait `{$trait_name}` may only be implemented for a coercion between structures", code = E0377)] pub(crate) struct CoerceUnsizedNonStruct { diff --git a/tests/ui/reborrow/coerce_shared_lifetime_mismatch.rs b/tests/ui/reborrow/coerce_shared_lifetime_mismatch.rs new file mode 100644 index 0000000000000..79fd0d04370be --- /dev/null +++ b/tests/ui/reborrow/coerce_shared_lifetime_mismatch.rs @@ -0,0 +1,19 @@ +#![feature(reborrow)] + +use std::marker::{CoerceShared, PhantomData, Reborrow}; + +struct Source<'a> { + data: &'static mut (), + marker: PhantomData<&'a ()>, +} + +impl<'a> Reborrow for Source<'a> {} + +struct Target<'a> { + data: &'a (), + //~^ ERROR implementing `CoerceShared` requires corresponding fields to match +} + +impl<'a> CoerceShared> for Source<'a> {} + +fn main() {} diff --git a/tests/ui/reborrow/coerce_shared_lifetime_mismatch.stderr b/tests/ui/reborrow/coerce_shared_lifetime_mismatch.stderr new file mode 100644 index 0000000000000..689f48d474b7b --- /dev/null +++ b/tests/ui/reborrow/coerce_shared_lifetime_mismatch.stderr @@ -0,0 +1,11 @@ +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_lifetime_mismatch.rs:13:5 + | +LL | data: &'static mut (), + | --------------------- source field `data` has type `&'static mut ()` +... +LL | data: &'a (), + | ^^^^^^^^^^^^ target field `data` has type `&'a ()` + +error: aborting due to 1 previous error +