diff --git a/compiler/rustc_error_codes/src/error_codes/E0690.md b/compiler/rustc_error_codes/src/error_codes/E0690.md index ba706ad2b020b..9dea165c913d8 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0690.md +++ b/compiler/rustc_error_codes/src/error_codes/E0690.md @@ -1,12 +1,12 @@ A struct with the representation hint `repr(transparent)` had two or more fields -that were not guaranteed to be zero-sized. +that were not guaranteed to be zero-sized or have an alignment of 1. Erroneous code example: ```compile_fail,E0690 #[repr(transparent)] -struct LengthWithUnit { // error: transparent struct needs at most one - value: f32, // non-zero-sized field, but has 2 +struct LengthWithUnit { // error: transparent struct needs at most one field + value: f32, // with non-trivial layout, but has 2 unit: U, } ``` @@ -17,7 +17,8 @@ it is not clear how the struct should be represented. Note that fields of zero-sized types (e.g., `PhantomData`) can also exist alongside the field that contains the actual data, they do not count for this error. When generic types are involved (as in the above example), an error is -reported because the type parameter could be non-zero-sized. +reported because the type parameter could be non-zero-sized, or it could raise +the alignment requirements of the type. To combine `repr(transparent)` with type parameters, `PhantomData` may be useful: diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 597cae6ff33ca..dfaca159876d5 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -270,13 +270,17 @@ hir_analysis_transparent_enum_variant = transparent enum needs exactly one varia .many_label = too many variants in `{$path}` .multi_label = variant here -hir_analysis_transparent_non_zero_sized = transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} - .label = needs at most one non-zero-sized field, but has {$field_count} - .labels = this field is non-zero-sized - -hir_analysis_transparent_non_zero_sized_enum = the variant of a transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} - .label = needs at most one non-zero-sized field, but has {$field_count} - .labels = this field is non-zero-sized +hir_analysis_transparent_layout = transparent {$desc} needs at most one field with non-trivial layout, but has {$field_count} + .label = needs at most one field with non-trivial layout, but has {$field_count} + .non_layout_labels = this field may have an alignment larger than 1 + .non_zst_labels = this field is non-zero-sized + .note = a layout is trivial if, any only if, it is zero-sized with an alignment of 1 + +hir_analysis_transparent_layout_enum = the variant of a transparent {$desc} needs at most one field with non-trivial layout, but has {$field_count} + .label = needs at most one field with non-trivial layout, but has {$field_count} + .non_layout_labels = this field may have an alignment larger than 1 + .non_zst_labels = this field is non-zero-sized + .note = a layout is trivial if, any only if, it is zero-sized with an alignment of 1 hir_analysis_typeof_reserved_keyword_used = `typeof` is a reserved keyword but unimplemented diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index c0cf1ea34cf2d..20d8d85cc3e51 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -1130,8 +1130,8 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) return; } - // For each field, figure out if it's known to be a ZST and align(1), with "known" - // respecting #[non_exhaustive] attributes. + // For each field, collect information on its layout and whether it has a #[non_exhaustive] + // annotation. let field_infos = adt.all_fields().map(|field| { let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did)); let param_env = tcx.param_env(field.did); @@ -1139,9 +1139,8 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) // We are currently checking the type this field came from, so it must be local let span = tcx.hir().span_if_local(field.did).unwrap(); let zst = layout.is_ok_and(|layout| layout.is_zst()); - let align = layout.ok().map(|layout| layout.align.abi.bytes()); if !zst { - return (span, zst, align, None); + return (span, zst, layout, None); } fn check_non_exhaustive<'tcx>( @@ -1176,20 +1175,45 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) } } - (span, zst, align, check_non_exhaustive(tcx, ty).break_value()) + (span, zst, layout, check_non_exhaustive(tcx, ty).break_value()) }); - let non_zst_fields = field_infos + // For a transparent ADT we must know which member field controls its layout. Hence, we only + // allow at most one field with non-trivial or unknown layout. A layout is trivial if, and only + // if, it is zero-sized with an alignment of 1. A layout is unknown if, for instance, it + // contains generic type parameters or has a #[non_exhaustive] annotation in a non-local type. + // We will reject any transparent ADT with more than one field with non-trivial or unknown + // layout, but we try our best to point out the violating properties. + + let non_layout_fields = field_infos .clone() - .filter_map(|(span, zst, _align, _non_exhaustive)| if !zst { Some(span) } else { None }); + .filter_map(|(span, _, layout, _)| if layout.is_err() { Some(span) } else { None }); + let non_layout_count = non_layout_fields.clone().count(); + let non_zst_fields = + field_infos.clone().filter_map( + |(span, zst, layout, _)| if layout.is_ok() && !zst { Some(span) } else { None }, + ); let non_zst_count = non_zst_fields.clone().count(); - if non_zst_count >= 2 { - bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, tcx.def_span(adt.did())); + + let non_transparent_count = non_layout_count + non_zst_count; + if non_transparent_count >= 2 { + bad_transparent_layout( + tcx, + adt, + non_transparent_count, + non_layout_fields, + non_zst_fields, + tcx.def_span(adt.did()), + ); } + let incompatible_zst_fields = field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count(); - let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2; - for (span, zst, align, non_exhaustive) in field_infos { + let incompat = + incompatible_zst_fields + non_transparent_count >= 2 && non_transparent_count < 2; + + for (span, zst, layout, non_exhaustive) in field_infos { + let align = layout.ok().map(|layout| layout.align.abi.bytes()); if zst && align != Some(1) { let mut err = struct_span_err!( tcx.sess, diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index d833166136669..bce84dd69c72b 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -516,28 +516,33 @@ fn bad_variant_count<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>, sp: Span, d }); } -/// Emit an error when encountering two or more non-zero-sized fields in a transparent -/// enum. -fn bad_non_zero_sized_fields<'tcx>( +/// Emit an error when encountering two or more fields with non-trivial layout +/// in a transparent Adt. +fn bad_transparent_layout<'tcx>( tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>, field_count: usize, - field_spans: impl Iterator, + non_layout_spans: impl Iterator, + non_zst_spans: impl Iterator, sp: Span, ) { if adt.is_enum() { - tcx.sess.emit_err(errors::TransparentNonZeroSizedEnum { + tcx.sess.emit_err(errors::TransparentLayoutEnum { span: sp, - spans: field_spans.collect(), + non_layout_spans: non_layout_spans.collect(), + non_zst_spans: non_zst_spans.collect(), field_count, desc: adt.descr(), + note: (), }); } else { - tcx.sess.emit_err(errors::TransparentNonZeroSized { + tcx.sess.emit_err(errors::TransparentLayout { span: sp, - spans: field_spans.collect(), + non_layout_spans: non_layout_spans.collect(), + non_zst_spans: non_zst_spans.collect(), field_count, desc: adt.descr(), + note: (), }); } } diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 9471ad9ca9063..53230f8e90b2f 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -777,27 +777,35 @@ pub(crate) struct TransparentEnumVariant { } #[derive(Diagnostic)] -#[diag(hir_analysis_transparent_non_zero_sized_enum, code = "E0690")] -pub(crate) struct TransparentNonZeroSizedEnum<'a> { +#[diag(hir_analysis_transparent_layout_enum, code = "E0690")] +pub(crate) struct TransparentLayoutEnum<'a> { #[primary_span] #[label] pub span: Span, - #[label(hir_analysis_labels)] - pub spans: Vec, + #[label(hir_analysis_non_layout_labels)] + pub non_layout_spans: Vec, + #[label(hir_analysis_non_zst_labels)] + pub non_zst_spans: Vec, pub field_count: usize, pub desc: &'a str, + #[note] + pub note: (), } #[derive(Diagnostic)] -#[diag(hir_analysis_transparent_non_zero_sized, code = "E0690")] -pub(crate) struct TransparentNonZeroSized<'a> { +#[diag(hir_analysis_transparent_layout, code = "E0690")] +pub(crate) struct TransparentLayout<'a> { #[primary_span] #[label] pub span: Span, - #[label(hir_analysis_labels)] - pub spans: Vec, + #[label(hir_analysis_non_layout_labels)] + pub non_layout_spans: Vec, + #[label(hir_analysis_non_zst_labels)] + pub non_zst_spans: Vec, pub field_count: usize, pub desc: &'a str, + #[note] + pub note: (), } #[derive(Diagnostic)] diff --git a/tests/ui/repr/repr-transparent.rs b/tests/ui/repr/repr-transparent.rs index 8c9d1639c0a51..870d8a5d32424 100644 --- a/tests/ui/repr/repr-transparent.rs +++ b/tests/ui/repr/repr-transparent.rs @@ -23,14 +23,15 @@ struct ContainsMultipleZst(PhantomData<*const i32>, NoFields); struct ContainsZstAndNonZst((), [i32; 2]); #[repr(transparent)] -struct MultipleNonZst(u8, u8); //~ ERROR needs at most one non-zero-sized field +struct MultipleNonZst(u8, u8); +//~^ ERROR needs at most one field with non-trivial layout trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } #[repr(transparent)] pub struct StructWithProjection(f32, ::It); -//~^ ERROR needs at most one non-zero-sized field +//~^ ERROR needs at most one field with non-trivial layout #[repr(transparent)] struct NontrivialAlignZst(u32, [u16; 0]); //~ ERROR alignment larger than 1 @@ -58,7 +59,7 @@ enum UnitFieldEnum { enum TooManyFieldsEnum { Foo(u32, String), } -//~^^^ ERROR transparent enum needs at most one non-zero-sized field, but has 2 +//~^^^ ERROR needs at most one field with non-trivial layout, but has 2 #[repr(transparent)] enum MultipleVariants { //~ ERROR transparent enum needs exactly one variant, but has 2 @@ -82,9 +83,10 @@ union UnitUnion { } #[repr(transparent)] -union TooManyFields { //~ ERROR transparent union needs at most one non-zero-sized field, but has 2 +union TooManyFields { u: u32, s: i32 } +//~^^^^ ERROR transparent union needs at most one field with non-trivial layout, but has 2 fn main() {} diff --git a/tests/ui/repr/repr-transparent.stderr b/tests/ui/repr/repr-transparent.stderr index 028fc25db46cc..63086a61576a0 100644 --- a/tests/ui/repr/repr-transparent.stderr +++ b/tests/ui/repr/repr-transparent.stderr @@ -1,35 +1,39 @@ -error[E0690]: transparent struct needs at most one non-zero-sized field, but has 2 +error[E0690]: transparent struct needs at most one field with non-trivial layout, but has 2 --> $DIR/repr-transparent.rs:26:1 | LL | struct MultipleNonZst(u8, u8); | ^^^^^^^^^^^^^^^^^^^^^ -- -- this field is non-zero-sized | | | | | this field is non-zero-sized - | needs at most one non-zero-sized field, but has 2 + | needs at most one field with non-trivial layout, but has 2 + | + = note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1 -error[E0690]: transparent struct needs at most one non-zero-sized field, but has 2 - --> $DIR/repr-transparent.rs:32:1 +error[E0690]: transparent struct needs at most one field with non-trivial layout, but has 2 + --> $DIR/repr-transparent.rs:33:1 | LL | pub struct StructWithProjection(f32, ::It); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ --- ------------------- this field is non-zero-sized | | | | | this field is non-zero-sized - | needs at most one non-zero-sized field, but has 2 + | needs at most one field with non-trivial layout, but has 2 + | + = note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1 error[E0691]: zero-sized field in transparent struct has alignment larger than 1 - --> $DIR/repr-transparent.rs:36:32 + --> $DIR/repr-transparent.rs:37:32 | LL | struct NontrivialAlignZst(u32, [u16; 0]); | ^^^^^^^^ has alignment of 2, which is larger than 1 error[E0691]: zero-sized field in transparent struct has alignment larger than 1 - --> $DIR/repr-transparent.rs:42:24 + --> $DIR/repr-transparent.rs:43:24 | LL | struct GenericAlign(ZstAlign32, u32); | ^^^^^^^^^^^^^ has alignment of 32, which is larger than 1 error[E0084]: unsupported representation for zero-variant enum - --> $DIR/repr-transparent.rs:44:1 + --> $DIR/repr-transparent.rs:45:1 | LL | #[repr(transparent)] | ^^^^^^^^^^^^^^^^^^^^ @@ -37,23 +41,25 @@ LL | enum Void {} | --------- zero-variant enum error[E0731]: transparent enum needs exactly one variant, but has 0 - --> $DIR/repr-transparent.rs:45:1 + --> $DIR/repr-transparent.rs:46:1 | LL | enum Void {} | ^^^^^^^^^ needs exactly one variant, but has 0 -error[E0690]: the variant of a transparent enum needs at most one non-zero-sized field, but has 2 - --> $DIR/repr-transparent.rs:58:1 +error[E0690]: the variant of a transparent enum needs at most one field with non-trivial layout, but has 2 + --> $DIR/repr-transparent.rs:59:1 | LL | enum TooManyFieldsEnum { - | ^^^^^^^^^^^^^^^^^^^^^^ needs at most one non-zero-sized field, but has 2 + | ^^^^^^^^^^^^^^^^^^^^^^ needs at most one field with non-trivial layout, but has 2 LL | Foo(u32, String), | --- ------ this field is non-zero-sized | | | this field is non-zero-sized + | + = note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1 error[E0731]: transparent enum needs exactly one variant, but has 2 - --> $DIR/repr-transparent.rs:64:1 + --> $DIR/repr-transparent.rs:65:1 | LL | enum MultipleVariants { | ^^^^^^^^^^^^^^^^^^^^^ needs exactly one variant, but has 2 @@ -63,26 +69,28 @@ LL | Bar, | --- too many variants in `MultipleVariants` error[E0691]: zero-sized field in transparent enum has alignment larger than 1 - --> $DIR/repr-transparent.rs:71:14 + --> $DIR/repr-transparent.rs:72:14 | LL | Foo(u32, [u16; 0]), | ^^^^^^^^ has alignment of 2, which is larger than 1 error[E0691]: zero-sized field in transparent enum has alignment larger than 1 - --> $DIR/repr-transparent.rs:76:11 + --> $DIR/repr-transparent.rs:77:11 | LL | Foo { bar: ZstAlign32, baz: u32 } | ^^^^^^^^^^^^^^^^^^ has alignment of 32, which is larger than 1 -error[E0690]: transparent union needs at most one non-zero-sized field, but has 2 - --> $DIR/repr-transparent.rs:85:1 +error[E0690]: transparent union needs at most one field with non-trivial layout, but has 2 + --> $DIR/repr-transparent.rs:86:1 | LL | union TooManyFields { - | ^^^^^^^^^^^^^^^^^^^ needs at most one non-zero-sized field, but has 2 + | ^^^^^^^^^^^^^^^^^^^ needs at most one field with non-trivial layout, but has 2 LL | u: u32, | ------ this field is non-zero-sized LL | s: i32 | ------ this field is non-zero-sized + | + = note: a layout is trivial if, any only if, it is zero-sized with an alignment of 1 error: aborting due to 11 previous errors