From d5fab0af1962ea07a0237cde46d5c5c3a79ee3b1 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 24 Sep 2024 21:47:31 -0700 Subject: [PATCH] [derive] Overhaul repr parsing Represent the result of parsing all `#[repr(...)]` attributes on a type as a high-level type which is only capable of representing valid combinations of `#[repr(...)]` attributes and processes them into a concise representation that's easier for high-level code to work with. This prepares us to more easily fix #1748 --- zerocopy-derive/src/enum.rs | 94 +- zerocopy-derive/src/lib.rs | 613 ++++++----- zerocopy-derive/src/repr.rs | 978 +++++++++++++----- zerocopy-derive/tests/ui-nightly/enum.stderr | 200 +++- zerocopy-derive/tests/ui-nightly/struct.rs | 6 + .../tests/ui-nightly/struct.stderr | 48 +- zerocopy-derive/tests/ui-nightly/union.stderr | 12 +- 7 files changed, 1323 insertions(+), 628 deletions(-) diff --git a/zerocopy-derive/src/enum.rs b/zerocopy-derive/src/enum.rs index cfe723e97b..2ad28bfdba 100644 --- a/zerocopy-derive/src/enum.rs +++ b/zerocopy-derive/src/enum.rs @@ -6,42 +6,41 @@ // This file may not be copied, modified, or distributed except according to // those terms. -use ::proc_macro2::TokenStream; -use ::quote::quote; -use ::syn::{DataEnum, Fields, Generics, Ident}; -use syn::parse_quote; - -use crate::{derive_try_from_bytes_inner, EnumRepr, Trait}; - -/// Returns the repr for the tag enum, given the collection of reprs on the -/// enum. -/// -/// This function returns: -/// - `Some(C)` for `repr(C)` -/// - `Some(int)` for `repr(int)` and `repr(C, int)` -/// - `None` for all other reprs -pub(crate) fn tag_repr(reprs: &[EnumRepr]) -> Option<&EnumRepr> { - let mut result = None; - for repr in reprs { - match repr { - EnumRepr::C => result = Some(repr), - EnumRepr::U8 - | EnumRepr::U16 - | EnumRepr::U32 - | EnumRepr::U64 - | EnumRepr::Usize - | EnumRepr::I8 - | EnumRepr::I16 - | EnumRepr::I32 - | EnumRepr::I64 - | EnumRepr::Isize => { - return Some(repr); - } - _ => (), - } - } - result -} +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{parse_quote, DataEnum, Error, Fields, Generics, Ident}; + +use crate::{derive_try_from_bytes_inner, repr::EnumRepr, Trait}; + +// /// Returns the repr for the tag enum, given the collection of reprs on the +// /// enum. +// /// +// /// This function returns: +// /// - `Some(C)` for `repr(C)` +// /// - `Some(int)` for `repr(int)` and `repr(C, int)` +// /// - `None` for all other reprs +// pub(crate) fn tag_repr(reprs: &[EnumRepr]) -> Option<&EnumRepr> { +// let mut result = None; +// for repr in reprs { +// match repr { +// EnumRepr::C => result = Some(repr), +// EnumRepr::U8 +// | EnumRepr::U16 +// | EnumRepr::U32 +// | EnumRepr::U64 +// | EnumRepr::Usize +// | EnumRepr::I8 +// | EnumRepr::I16 +// | EnumRepr::I32 +// | EnumRepr::I64 +// | EnumRepr::Isize => { +// return Some(repr); +// } +// _ => (), +// } +// } +// result +// } /// Generates a tag enum for the given enum. This generates an enum with the /// same `repr`s, variants, and corresponding discriminants, but none of the @@ -57,7 +56,7 @@ pub(crate) fn generate_tag_enum(repr: &EnumRepr, data: &DataEnum) -> TokenStream }); quote! { - #[repr(#repr)] + #repr #[allow(dead_code)] enum ___ZerocopyTag { #(#variants,)* @@ -231,21 +230,26 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream /// - `repr(C, int)`: pub(crate) fn derive_is_bit_valid( enum_ident: &Ident, - reprs: &[EnumRepr], + repr: &EnumRepr, generics: &Generics, data: &DataEnum, -) -> TokenStream { - let repr = - tag_repr(reprs).expect("cannot derive is_bit_valid for enum without a well-defined repr"); +) -> Result { + // let repr = + // tag_repr(reprs).expect("cannot derive is_bit_valid for enum without a well-defined repr"); let trait_path = Trait::TryFromBytes.crate_path(); let tag_enum = generate_tag_enum(repr, data); let tag_consts = generate_tag_consts(data); - let (outer_tag_type, inner_tag_type) = if matches!(repr, EnumRepr::C) { + let (outer_tag_type, inner_tag_type) = if repr.is_c() { (quote! { ___ZerocopyTag }, quote! { () }) - } else { + } else if repr.is_primitive() { (quote! { () }, quote! { ___ZerocopyTag }) + } else { + return Err(Error::new( + Span::call_site(), + "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout", + )); }; let variant_structs = generate_variant_structs(enum_ident, generics, data); @@ -297,7 +301,7 @@ pub(crate) fn derive_is_bit_valid( } }); - quote! { + Ok(quote! { // SAFETY: We use `is_bit_valid` to validate that the bit pattern of the // enum's tag corresponds to one of the enum's discriminants. Then, we // check the bit validity of each field of the corresponding variant. @@ -392,5 +396,5 @@ pub(crate) fn derive_is_bit_valid( _ => false, } } - } + }) } diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 60e3769634..2741975027 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -59,6 +59,12 @@ macro_rules! try_or_print { }; } +macro_rules! try_or_print_one { + ($e:expr) => { + try_or_print!($e.map_err(|e| vec![e])) + }; +} + // TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be // made better if we could add multiple lines of error output like this: // @@ -119,47 +125,68 @@ pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { fn derive_known_layout_inner(ast: &DeriveInput) -> proc_macro2::TokenStream { let is_repr_c_struct = match &ast.data { Data::Struct(..) => { - let reprs = try_or_print!(repr::reprs::(&ast.attrs)); - if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) { - Some(reprs) + let repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + if repr.is_c() { + Some(repr) } else { None } + // let reprs = try_or_print!(repr::reprs::(&ast.attrs)); + // if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) { + // Some(reprs) + // } else { + // None + // } } Data::Enum(..) | Data::Union(..) => None, }; let fields = ast.data.fields(); - let (self_bounds, extras) = if let (Some(reprs), Some((trailing_field, leading_fields))) = + let (self_bounds, extras) = if let (Some(repr), Some((trailing_field, leading_fields))) = (is_repr_c_struct, fields.split_last()) { let (_name, trailing_field_ty) = trailing_field; let leading_fields_tys = leading_fields.iter().map(|(_name, ty)| ty); let core_path = quote!(::zerocopy::util::macro_util::core_reexport); - let repr_align = reprs - .iter() - .find_map( - |(_meta, repr)| { - if let Repr::Align(repr_align) = repr { - Some(repr_align) - } else { - None - } - }, - ) - .map(|repr_align| quote!(#core_path::num::NonZeroUsize::new(#repr_align as usize))) + // let repr_align = reprs + // .iter() + // .find_map( + // |(_meta, repr)| { + // if let Repr::Align(repr_align) = repr { + // Some(repr_align) + // } else { + // None + // } + // }, + // ) + // .map(|repr_align| quote!(#core_path::num::NonZeroUsize::new(#repr_align as usize))) + // .unwrap_or(quote!(#core_path::option::Option::None)); + + // let repr_packed = reprs + // .iter() + // .find_map(|(_meta, repr)| match repr { + // Repr::Packed => Some(1), + // Repr::PackedN(repr_packed) => Some(*repr_packed), + // _ => None, + // }) + // .map(|repr_packed| quote!(#core_path::num::NonZeroUsize::new(#repr_packed as usize))) + // .unwrap_or(quote!(#core_path::option::Option::None)); + + let repr_align = repr + .get_align() + .map(|align| { + let align = align.t.get(); + quote!(#core_path::num::NonZeroUsize::new(#align as usize)) + }) .unwrap_or(quote!(#core_path::option::Option::None)); - - let repr_packed = reprs - .iter() - .find_map(|(_meta, repr)| match repr { - Repr::Packed => Some(1), - Repr::PackedN(repr_packed) => Some(*repr_packed), - _ => None, + let repr_packed = repr + .get_packed() + .map(|packed| { + let packed = packed.get(); + quote!(#core_path::num::NonZeroUsize::new(#packed as usize)) }) - .map(|repr_packed| quote!(#core_path::num::NonZeroUsize::new(#repr_packed as usize))) .unwrap_or(quote!(#core_path::option::Option::None)); ( @@ -512,54 +539,56 @@ fn derive_try_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro ) } -const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ - &[StructRepr::C], - &[StructRepr::Transparent], - &[StructRepr::Packed], - &[StructRepr::C, StructRepr::Packed], -]; +// const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ +// &[StructRepr::C], +// &[StructRepr::Transparent], +// &[StructRepr::Packed], +// &[StructRepr::C, StructRepr::Packed], +// ]; fn derive_try_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { - let reprs = try_or_print!(ENUM_TRY_FROM_BYTES_CFG.validate_reprs(ast)); + // let reprs = try_or_print!(ENUM_TRY_FROM_BYTES_CFG.validate_reprs(ast)); + let repr = try_or_print_one!(EnumRepr::from_attrs(&ast.attrs)); // The enum derive requires some extra scaffolding - let extra = Some(r#enum::derive_is_bit_valid(&ast.ident, &reprs, &ast.generics, enm)); + let extra = + Some(try_or_print_one!(r#enum::derive_is_bit_valid(&ast.ident, &repr, &ast.generics, enm))); impl_block(ast, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF, SelfBounds::None, None, extra) } -#[rustfmt::skip] -const ENUM_TRY_FROM_BYTES_CFG: Config = { - use EnumRepr::*; - Config { - allowed_combinations_message: r#"TryFromBytes requires an enum repr of a primitive, "C", or "C" with a primitive"#, - derive_unaligned: false, - allowed_combinations: &[ - &[U8], - &[U16], - &[U32], - &[U64], - &[Usize], - &[I8], - &[I16], - &[I32], - &[I64], - &[Isize], - &[C], - &[C, U8], - &[C, U16], - &[C, U32], - &[C, U64], - &[C, Usize], - &[C, I8], - &[C, I16], - &[C, I32], - &[C, I64], - &[C, Isize], - ], - disallowed_but_legal_combinations: &[], - } -}; +// #[rustfmt::skip] +// const ENUM_TRY_FROM_BYTES_CFG: Config = { +// use EnumRepr::*; +// Config { +// allowed_combinations_message: r#"TryFromBytes requires an enum repr of a primitive, "C", or "C" with a primitive"#, +// derive_unaligned: false, +// allowed_combinations: &[ +// &[U8], +// &[U16], +// &[U32], +// &[U64], +// &[Usize], +// &[I8], +// &[I16], +// &[I32], +// &[I64], +// &[Isize], +// &[C], +// &[C, U8], +// &[C, U16], +// &[C, U32], +// &[C, U64], +// &[C, Usize], +// &[C, I8], +// &[C, I16], +// &[C, I32], +// &[C, I64], +// &[C, Isize], +// ], +// disallowed_but_legal_combinations: &[], +// } +// }; /// A struct is `FromZeros` if: /// - all fields are `FromZeros` @@ -651,7 +680,20 @@ fn find_zero_variant(enm: &DataEnum) -> Result { fn derive_from_zeros_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { // We don't actually care what the repr is; we just care that it's one of // the allowed ones. - try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast)); + // try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast)); + + let repr = try_or_print_one!(EnumRepr::from_attrs(&ast.attrs)); + + // We don't actually care what the repr is; we just care that it's one of + // the allowed ones. + match repr { + Repr::Transparent(_) + | Repr::Compound( + Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, + _, + ) => {} + Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => return Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout").to_compile_error(), + } let zero_variant = match find_zero_variant(enm) { Ok(index) => enm.variants.iter().nth(index).unwrap(), @@ -727,17 +769,19 @@ fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro /// this would require ~4 billion enum variants, which obviously isn't a thing. /// - All fields of all variants are `FromBytes`. fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { - let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast)); + // let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast)); - let variants_required = 1usize - << enum_size_from_repr(reprs.as_slice()) - .expect("internal error: `validate_reprs` has already validated that the reprs guarantee the enum's size"); + let repr = try_or_print_one!(EnumRepr::from_attrs(&ast.attrs)); + + let variants_required = 1usize << try_or_print_one!(enum_size_from_repr(&repr)); + // .expect("internal error: `validate_reprs` has already validated that the reprs guarantee the enum's size"); if enm.variants.len() != variants_required { return Error::new_spanned( ast, format!( "FromBytes only supported on {} enum with {} variants", - reprs[0], variants_required + repr.repr_type_name(), + variants_required ), ) .to_compile_error(); @@ -746,38 +790,52 @@ fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok impl_block(ast, enm, Trait::FromBytes, FieldBounds::ALL_SELF, SelfBounds::None, None, None) } +// // Returns `None` if the enum's size is not guaranteed by the repr. +// fn enum_size_from_repr(reprs: &[EnumRepr]) -> Option { +// match reprs { +// [EnumRepr::U8] | [EnumRepr::I8] => Some(8), +// [EnumRepr::U16] | [EnumRepr::I16] => Some(16), +// _ => None, +// } +// } + // Returns `None` if the enum's size is not guaranteed by the repr. -fn enum_size_from_repr(reprs: &[EnumRepr]) -> Option { - match reprs { - [EnumRepr::U8] | [EnumRepr::I8] => Some(8), - [EnumRepr::U16] | [EnumRepr::I16] => Some(16), - _ => None, +fn enum_size_from_repr(repr: &EnumRepr) -> Result { + use {CompoundRepr::*, PrimitiveRepr::*, Repr::*}; + match repr { + Transparent(span) + | Compound( + Spanned { t: C | Rust | Primitive(U32 | I32 | U64 | I64 | Usize | Isize), span }, + _, + ) => Err(Error::new(*span, "")), + Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8), + Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16), } } -#[rustfmt::skip] -const ENUM_FROM_BYTES_CFG: Config = { - use EnumRepr::*; - Config { - allowed_combinations_message: r#"FromBytes requires repr of "u8", "u16", "i8", or "i16""#, - derive_unaligned: false, - allowed_combinations: &[ - &[U8], - &[U16], - &[I8], - &[I16], - ], - disallowed_but_legal_combinations: &[ - &[C], - &[U32], - &[I32], - &[U64], - &[I64], - &[Usize], - &[Isize], - ], - } -}; +// #[rustfmt::skip] +// const ENUM_FROM_BYTES_CFG: Config = { +// use EnumRepr::*; +// Config { +// allowed_combinations_message: r#"FromBytes requires repr of "u8", "u16", "i8", or "i16""#, +// derive_unaligned: false, +// allowed_combinations: &[ +// &[U8], +// &[U16], +// &[I8], +// &[I16], +// ], +// disallowed_but_legal_combinations: &[ +// &[C], +// &[U32], +// &[I32], +// &[U64], +// &[I64], +// &[Usize], +// &[Isize], +// ], +// } +// }; /// Unions are `FromBytes` if /// - all fields are `FromBytes` and `Immutable` @@ -790,12 +848,26 @@ fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::T } fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { - let reprs = try_or_print!(STRUCT_UNION_INTO_BYTES_CFG.validate_reprs(ast)); - let is_transparent = reprs.contains(&StructRepr::Transparent); - let is_packed = reprs.contains(&StructRepr::Packed); + // let _repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + + // let reprs = try_or_print!(STRUCT_UNION_INTO_BYTES_CFG.validate_reprs(ast)); + + let repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + + let is_transparent = repr.is_transparent(); + let is_c = repr.is_c(); + let is_packed_1 = repr.is_packed_1(); + // match repr.get_packed() { + // Some(packed) => packed.get() == 1, + // None => false, + // }; + // let is_packed = matches!(Repr::Compound) + + // let is_transparent = reprs.contains(&StructRepr::Transparent); + // let is_packed = reprs.contains(&StructRepr::Packed); let num_fields = strct.fields().len(); - let (padding_check, require_unaligned_fields) = if is_transparent || is_packed { + let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 { // No padding check needed. // - repr(transparent): The layout and ABI of the whole struct is the // same as its only non-ZST field (meaning there's no padding outside @@ -806,7 +878,7 @@ fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro // which we require to be `IntoBytes` (meaning they don't have any // padding). (None, false) - } else if reprs.contains(&StructRepr::C) && num_fields <= 1 { + } else if is_c && num_fields <= 1 { // No padding check needed. A repr(C) struct with zero or one field has // no padding. (None, false) @@ -819,7 +891,7 @@ fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro // Based on the allowed reprs, we know that this type must be repr(C) by // the time we get here, but the soundness of this impl relies on it, so // let's double-check. - assert!(reprs.contains(&StructRepr::C)); + assert!(is_c); // We can't use a padding check since there are generic type arguments. // Instead, we require all field types to implement `Unaligned`. This // ensures that the `repr(C)` layout algorithm will not insert any @@ -839,14 +911,14 @@ fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro impl_block(ast, strct, Trait::IntoBytes, field_bounds, SelfBounds::None, padding_check, None) } -const STRUCT_UNION_INTO_BYTES_CFG: Config = Config { - // Since `disallowed_but_legal_combinations` is empty, this message will - // never actually be emitted. - allowed_combinations_message: r#"IntoBytes requires either a) repr "C" or "transparent" with all fields implementing IntoBytes or, b) repr "packed""#, - derive_unaligned: false, - allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, - disallowed_but_legal_combinations: &[], -}; +// const STRUCT_UNION_INTO_BYTES_CFG: Config = Config { +// // Since `disallowed_but_legal_combinations` is empty, this message will +// // never actually be emitted. +// allowed_combinations_message: r#"IntoBytes requires either a) repr "C" or "transparent" with all fields implementing IntoBytes or, b) repr "packed""#, +// derive_unaligned: false, +// allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, +// disallowed_but_legal_combinations: &[], +// }; /// If the type is an enum: /// - It must have a defined representation (`repr`s `C`, `u8`, `u16`, `u32`, @@ -856,10 +928,12 @@ const STRUCT_UNION_INTO_BYTES_CFG: Config = Config { fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { // We don't care what the repr is; we only care that it is one of the // allowed ones. - let reprs = try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast)); - let repr = r#enum::tag_repr(&reprs) - .expect("cannot derive IntoBytes for enum without a well-defined repr"); - let tag_type_definition = r#enum::generate_tag_enum(repr, enm); + // let reprs = try_or_print!(ENUM_FROM_ZEROS_INTO_BYTES_CFG.validate_reprs(ast)); + let repr = try_or_print_one!(EnumRepr::from_attrs(&ast.attrs)); + + // let repr = r#enum::tag_repr(&reprs) + // .expect("cannot derive IntoBytes for enum without a well-defined repr"); + let tag_type_definition = r#enum::generate_tag_enum(&repr, enm); impl_block( ast, @@ -872,40 +946,40 @@ fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok ) } -#[rustfmt::skip] -const ENUM_FROM_ZEROS_INTO_BYTES_CFG: Config = { - use EnumRepr::*; - Config { - // Since `disallowed_but_legal_combinations` is empty, this message will - // never actually be emitted. - allowed_combinations_message: r#"FromZeros requires an enum repr of a primitive, "C", or "C" with a primitive"#, - derive_unaligned: false, - allowed_combinations: &[ - &[U8], - &[U16], - &[U32], - &[U64], - &[Usize], - &[I8], - &[I16], - &[I32], - &[I64], - &[Isize], - &[C], - &[C, U8], - &[C, U16], - &[C, U32], - &[C, U64], - &[C, Usize], - &[C, I8], - &[C, I16], - &[C, I32], - &[C, I64], - &[C, Isize], - ], - disallowed_but_legal_combinations: &[], - } -}; +// #[rustfmt::skip] +// const ENUM_FROM_ZEROS_INTO_BYTES_CFG: Config = { +// use EnumRepr::*; +// Config { +// // Since `disallowed_but_legal_combinations` is empty, this message will +// // never actually be emitted. +// allowed_combinations_message: r#"FromZeros requires an enum repr of a primitive, "C", or "C" with a primitive"#, +// derive_unaligned: false, +// allowed_combinations: &[ +// &[U8], +// &[U16], +// &[U32], +// &[U64], +// &[Usize], +// &[I8], +// &[I16], +// &[I32], +// &[I64], +// &[Isize], +// &[C], +// &[C, U8], +// &[C, U16], +// &[C, U32], +// &[C, U64], +// &[C, Usize], +// &[C, I8], +// &[C, I16], +// &[C, I32], +// &[C, I64], +// &[C, Isize], +// ], +// disallowed_but_legal_combinations: &[], +// } +// }; /// A union is `IntoBytes` if: /// - all fields are `IntoBytes` @@ -918,7 +992,12 @@ fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::T .to_compile_error(); } - try_or_print!(STRUCT_UNION_INTO_BYTES_CFG.validate_reprs(ast)); + // try_or_print!(STRUCT_UNION_INTO_BYTES_CFG.validate_reprs(ast)); + + let repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() { + return Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout").to_compile_error(); + } impl_block( ast, @@ -937,24 +1016,30 @@ fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::T /// - all fields `Unaligned` /// - `repr(packed)` fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { - let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); - let field_bounds = if !reprs.contains(&StructRepr::Packed) { + // let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); + let repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + + try_or_print_one!(repr.unaligned_validate_no_align_gt_1()); + + let field_bounds = if repr.is_packed_1() { + FieldBounds::None + } else if repr.is_c() || repr.is_transparent() { FieldBounds::ALL_SELF } else { - FieldBounds::None + return Error::new(Span::call_site(), "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment").to_compile_error(); }; impl_block(ast, strct, Trait::Unaligned, field_bounds, SelfBounds::None, None, None) } -const STRUCT_UNION_UNALIGNED_CFG: Config = Config { - // Since `disallowed_but_legal_combinations` is empty, this message will - // never actually be emitted. - allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#, - derive_unaligned: true, - allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, - disallowed_but_legal_combinations: &[], -}; +// const STRUCT_UNION_UNALIGNED_CFG: Config = Config { +// // Since `disallowed_but_legal_combinations` is empty, this message will +// // never actually be emitted. +// allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#, +// derive_unaligned: true, +// allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, +// disallowed_but_legal_combinations: &[], +// }; /// An enum is `Unaligned` if: /// - No `repr(align(N > 1))` @@ -963,45 +1048,60 @@ fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Toke // The only valid reprs are `u8` and `i8`, and optionally `align(1)`. We // don't actually care what the reprs are so long as they satisfy that // requirement. - let _: Vec = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast)); + // let _: Vec = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast)); + + let repr = try_or_print_one!(EnumRepr::from_attrs(&ast.attrs)); + try_or_print_one!(repr.unaligned_validate_no_align_gt_1()); + + if !repr.is_u8() || !repr.is_i8() { + return Error::new(Span::call_site(), "must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment").to_compile_error(); + } + + // if let Some(span) = repr.get_align_gt_1_span() { + // return Error::new( + // n.span, + // "cannot derive `Unaligned` on type with alignment greater than 1", + // ) + // .to_compile_error(); + // } impl_block(ast, enm, Trait::Unaligned, FieldBounds::ALL_SELF, SelfBounds::None, None, None) } -#[rustfmt::skip] -const ENUM_UNALIGNED_CFG: Config = { - use EnumRepr::*; - Config { - allowed_combinations_message: - r#"Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1)))"#, - derive_unaligned: true, - allowed_combinations: &[ - &[U8], - &[I8], - &[C, U8], - &[C, I8], - ], - disallowed_but_legal_combinations: &[ - &[U16], - &[U32], - &[U64], - &[Usize], - &[I16], - &[I32], - &[I64], - &[Isize], - &[C], - &[C, U16], - &[C, U32], - &[C, U64], - &[C, Usize], - &[C, I16], - &[C, I32], - &[C, I64], - &[C, Isize], - ], - } -}; +// #[rustfmt::skip] +// const ENUM_UNALIGNED_CFG: Config = { +// use EnumRepr::*; +// Config { +// allowed_combinations_message: +// r#"Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1)))"#, +// derive_unaligned: true, +// allowed_combinations: &[ +// &[U8], +// &[I8], +// &[C, U8], +// &[C, I8], +// ], +// disallowed_but_legal_combinations: &[ +// &[U16], +// &[U32], +// &[U64], +// &[Usize], +// &[I16], +// &[I32], +// &[I64], +// &[Isize], +// &[C], +// &[C, U16], +// &[C, U32], +// &[C, U64], +// &[C, Usize], +// &[C, I16], +// &[C, I32], +// &[C, I64], +// &[C, Isize], +// ], +// } +// }; /// Like structs, a union is `Unaligned` if: /// - `repr(align)` is no more than 1 and either @@ -1009,13 +1109,24 @@ const ENUM_UNALIGNED_CFG: Config = { /// - all fields `Unaligned` /// - `repr(packed)` fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { - let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); - let field_type_trait_bounds = if !reprs.contains(&StructRepr::Packed) { + let repr = try_or_print_one!(StructUnionRepr::from_attrs(&ast.attrs)); + try_or_print_one!(repr.unaligned_validate_no_align_gt_1()); + + let field_type_trait_bounds = if repr.is_packed_1() { + FieldBounds::None + } else if repr.is_c() || repr.is_transparent() { FieldBounds::ALL_SELF } else { - FieldBounds::None + return Error::new(Span::call_site(), "must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment").to_compile_error(); }; + // let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); + // let field_type_trait_bounds = if !reprs.contains(&StructRepr::Packed) { + // FieldBounds::ALL_SELF + // } else { + // FieldBounds::None + // }; + impl_block(ast, unn, Trait::Unaligned, field_type_trait_bounds, SelfBounds::None, None, None) } @@ -1316,54 +1427,54 @@ impl BoolExt for bool { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_config_repr_orderings() { - // Validate that the repr lists in the various configs are in the - // canonical order. If they aren't, then our algorithm to look up in - // those lists won't work. - - // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once - // `Vec::is_sorted` is stabilized. - fn is_sorted_and_deduped(ts: &[T]) -> bool { - let mut sorted = ts.to_vec(); - sorted.sort(); - sorted.dedup(); - ts == sorted.as_slice() - } - - fn elements_are_sorted_and_deduped(lists: &[&[T]]) -> bool { - lists.iter().all(|list| is_sorted_and_deduped(list)) - } - - fn config_is_sorted(config: &Config) -> bool { - elements_are_sorted_and_deduped(config.allowed_combinations) - && elements_are_sorted_and_deduped(config.disallowed_but_legal_combinations) - } - - assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG)); - assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG)); - assert!(config_is_sorted(&ENUM_UNALIGNED_CFG)); - } - - #[test] - fn test_config_repr_no_overlap() { - // Validate that no set of reprs appears in both the - // `allowed_combinations` and `disallowed_but_legal_combinations` lists. - - fn overlap(a: &[T], b: &[T]) -> bool { - a.iter().any(|elem| b.contains(elem)) - } - - fn config_overlaps(config: &Config) -> bool { - overlap(config.allowed_combinations, config.disallowed_but_legal_combinations) - } - - assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG)); - assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG)); - assert!(!config_overlaps(&ENUM_UNALIGNED_CFG)); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn test_config_repr_orderings() { +// // Validate that the repr lists in the various configs are in the +// // canonical order. If they aren't, then our algorithm to look up in +// // those lists won't work. + +// // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once +// // `Vec::is_sorted` is stabilized. +// fn is_sorted_and_deduped(ts: &[T]) -> bool { +// let mut sorted = ts.to_vec(); +// sorted.sort(); +// sorted.dedup(); +// ts == sorted.as_slice() +// } + +// fn elements_are_sorted_and_deduped(lists: &[&[T]]) -> bool { +// lists.iter().all(|list| is_sorted_and_deduped(list)) +// } + +// fn config_is_sorted(config: &Config) -> bool { +// elements_are_sorted_and_deduped(config.allowed_combinations) +// && elements_are_sorted_and_deduped(config.disallowed_but_legal_combinations) +// } + +// assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG)); +// assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG)); +// assert!(config_is_sorted(&ENUM_UNALIGNED_CFG)); +// } + +// #[test] +// fn test_config_repr_no_overlap() { +// // Validate that no set of reprs appears in both the +// // `allowed_combinations` and `disallowed_but_legal_combinations` lists. + +// fn overlap(a: &[T], b: &[T]) -> bool { +// a.iter().any(|elem| b.contains(elem)) +// } + +// fn config_overlaps(config: &Config) -> bool { +// overlap(config.allowed_combinations, config.disallowed_but_legal_combinations) +// } + +// assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG)); +// assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG)); +// assert!(!config_overlaps(&ENUM_UNALIGNED_CFG)); +// } +// } diff --git a/zerocopy-derive/src/repr.rs b/zerocopy-derive/src/repr.rs index c1dc185801..109e42d937 100644 --- a/zerocopy-derive/src/repr.rs +++ b/zerocopy-derive/src/repr.rs @@ -6,202 +6,277 @@ // This file may not be copied, modified, or distributed except according to // those terms. -use core::fmt::{self, Display, Formatter}; - -use proc_macro2::Ident; -use quote::{ToTokens, TokenStreamExt as _}; +use core::{ + convert::{Infallible, TryFrom}, + num::NonZeroU64, +}; -use { - proc_macro2::Span, - syn::punctuated::Punctuated, - syn::spanned::Spanned, - syn::token::Comma, - syn::{Attribute, DeriveInput, Error, LitInt, Meta}, +use proc_macro2::{Span, TokenStream}; +use quote::{quote_spanned, ToTokens, TokenStreamExt as _}; +use syn::{ + punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, Error, LitInt, Meta, + MetaList, }; -pub(crate) struct Config { - // A human-readable message describing what combinations of representations - // are allowed. This will be printed to the user if they use an invalid - // combination. - pub(crate) allowed_combinations_message: &'static str, - // Whether we're checking as part of `derive(Unaligned)`. If not, we can - // ignore `repr(align)`, which makes the code (and the list of valid repr - // combinations we have to enumerate) somewhat simpler. If we're checking - // for `Unaligned`, then in addition to checking against illegal - // combinations, we also check to see if there exists a `repr(align(N > 1))` - // attribute. - pub(crate) derive_unaligned: bool, - // Combinations which are valid for the trait. - pub(crate) allowed_combinations: &'static [&'static [Repr]], - // Combinations which are not valid for the trait, but are legal according - // to Rust. Any combination not in this or `allowed_combinations` is either - // illegal according to Rust or the behavior is unspecified. If the behavior - // is unspecified, it might become specified in the future, and that - // specification might not play nicely with our requirements. Thus, we - // reject combinations with unspecified behavior in addition to illegal - // combinations. - pub(crate) disallowed_but_legal_combinations: &'static [&'static [Repr]], -} - -impl Config { - /// Validate that `input`'s representation attributes conform to the - /// requirements specified by this `Config`. +/// The computed representation of a type. +/// +/// This is the result of processing all `#[repr(...)]` attributes on a type, if +/// any. A `Repr` is only capable of representing legal combinations of +/// `#[repr(...)]` attributes. +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +pub(crate) enum Repr { + /// `#[repr(transparent)]` + Transparent(S), + /// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)` + /// optionally combined with `repr(packed(...))` or `repr(align(...))` + Compound(Spanned, S>, Option, S>>), +} + +/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`. +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +pub(crate) enum CompoundRepr { + C, + Rust, + Primitive(Prim), +} + +/// `repr(Int)` +#[derive(Copy, Clone)] +#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +pub(crate) enum PrimitiveRepr { + U8, + U16, + U32, + U64, + Usize, + I8, + I16, + I32, + I64, + Isize, +} + +/// `repr(packed(...))` or `repr(align(...))` +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +pub(crate) enum AlignRepr { + Packed(Packed), + Align(NonZeroU64), +} + +/// The representations which can legally appear on a struct or union type. +pub(crate) type StructUnionRepr = Repr; + +/// The representations which can legally appear on an enum type. +pub(crate) type EnumRepr = Repr; + +impl Repr { + /// Gets the name of this "repr type" - the non-align `repr(X)` that is used + /// in prose to refer to this type. /// - /// `validate_reprs` extracts the `repr` attributes, validates that they - /// conform to the requirements of `self`, and returns them. Regardless of - /// whether `align` attributes are considered during validation, they are - /// stripped out of the returned value since no callers care about them. - pub(crate) fn validate_reprs(&self, input: &DeriveInput) -> Result, Vec> { - let mut metas_reprs = reprs(&input.attrs)?; - metas_reprs.sort_by(|a: &(_, R), b| a.1.partial_cmp(&b.1).unwrap()); - - if self.derive_unaligned { - if let Some((meta, _)) = - metas_reprs.iter().find(|&repr: &&(_, R)| repr.1.is_align_gt_one()) - { - return Err(vec![Error::new_spanned( - meta, - "cannot derive Unaligned with repr(align(N > 1))", - )]); - } + /// For example, we would refer to `#[repr(C, align(4))] struct Foo { ... }` + /// as a "`repr(C)` struct". + #[allow(private_bounds)] + pub(crate) fn repr_type_name(&self) -> &str + where + Prim: Copy + With, + { + use {CompoundRepr::*, PrimitiveRepr::*, Repr::*}; + match self { + Transparent(_span) => "repr(transparent)", + Compound(Spanned { t: repr, span: _ }, _align) => match repr { + C => "repr(C)", + Rust => "repr(Rust)", + Primitive(prim) => prim.with(|prim| match prim { + U8 => "repr(u8)", + U16 => "repr(u16)", + U32 => "repr(u32)", + U64 => "repr(u64)", + Usize => "repr(usize)", + I8 => "repr(i8)", + I16 => "repr(i16)", + I32 => "repr(i32)", + I64 => "repr(i64)", + Isize => "repr(isize)", + }), + }, } + } - let mut metas = Vec::new(); - let mut reprs = Vec::new(); - metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| { - metas.push(meta); - reprs.push(repr) - }); - - if reprs.is_empty() { - // Use `Span::call_site` to report this error on the - // `#[derive(...)]` itself. - return Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]); + pub(crate) fn is_transparent(&self) -> bool { + matches!(self, Repr::Transparent(_)) + } + + pub(crate) fn is_c(&self) -> bool { + use CompoundRepr::*; + matches!(self, Repr::Compound(Spanned { t: C, span: _ }, _align)) + } + + pub(crate) fn is_primitive(&self) -> bool { + use CompoundRepr::*; + matches!(self, Repr::Compound(Spanned { t: Primitive(_), span: _ }, _align)) + } + + pub(crate) fn get_packed(&self) -> Option<&Packed> { + use {AlignRepr::*, Repr::*}; + if let Compound(_, Some(Spanned { t: Packed(p), span: _ })) = self { + Some(p) + } else { + None } + } - let initial_sp = metas[0].span(); - let err_span = metas.iter().skip(1).try_fold(initial_sp, |sp, meta| sp.join(meta.span())); + pub(crate) fn get_align(&self) -> Option> + where + S: Copy, + { + use {AlignRepr::*, Repr::*}; + if let Compound(_, Some(Spanned { t: Align(n), span })) = self { + Some(Spanned::new(*n, *span)) + } else { + None + } + } +} - if self.allowed_combinations.contains(&reprs.as_slice()) { - Ok(reprs) - } else if self.disallowed_but_legal_combinations.contains(&reprs.as_slice()) { - Err(vec![Error::new( - err_span.unwrap_or_else(|| input.span()), - self.allowed_combinations_message, - )]) +impl Repr { + /// When deriving `Unaligned`, validate that the decorated type has no + /// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists + /// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns + /// a descriptive error. + pub(crate) fn unaligned_validate_no_align_gt_1(&self) -> Result<(), Error> { + if let Some(n) = self.get_align().filter(|n| n.t.get() > 1) { + Err(Error::new( + n.span, + "cannot derive `Unaligned` on type with alignment greater than 1", + )) } else { - Err(vec![Error::new( - err_span.unwrap_or_else(|| input.span()), - "conflicting representation hints", - )]) + Ok(()) } } } -// The type of valid reprs for a particular kind (enum, struct, union). -pub(crate) trait KindRepr: 'static + Sized + Ord { - fn is_align(&self) -> bool; - fn is_align_gt_one(&self) -> bool; - fn parse(meta: &Meta) -> syn::Result; +impl Repr { + /// Does `self` describe a `#[repr(packed)]` or `#[repr(packed(1))]` type? + pub(crate) fn is_packed_1(&self) -> bool { + self.get_packed().map(|n| n.get() == 1).unwrap_or(false) + } } -// Defines an enum for reprs which are valid for a given kind (structs, enums, -// etc), and provide implementations of `KindRepr`, `Ord`, and `Display`, and -// those traits' super-traits. -macro_rules! define_kind_specific_repr { - ($type_name:expr, $repr_name:ident, [ $($repr_variant:ident),* ] , [ $($repr_variant_aligned:ident),* ]) => { - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub(crate) enum $repr_name { - $($repr_variant,)* - $($repr_variant_aligned(u64),)* +impl Repr { + fn get_primitive(&self) -> Option<&PrimitiveRepr> { + use {CompoundRepr::*, Repr::*}; + if let Compound(Spanned { t: Primitive(p), span: _ }, _align) = self { + Some(p) + } else { + None } + } - impl KindRepr for $repr_name { - fn is_align(&self) -> bool { - match self { - $($repr_name::$repr_variant_aligned(_) => true,)* - _ => false, - } - } + /// Does `self` describe a `#[repr(u8)]` type? + pub(crate) fn is_u8(&self) -> bool { + matches!(self.get_primitive(), Some(PrimitiveRepr::U8)) + } - fn is_align_gt_one(&self) -> bool { - match self { - // `packed(n)` only lowers alignment - $repr_name::Align(n) => n > &1, - _ => false, - } - } + /// Does `self` describe a `#[repr(i8)]` type? + pub(crate) fn is_i8(&self) -> bool { + matches!(self.get_primitive(), Some(PrimitiveRepr::I8)) + } +} - fn parse(meta: &Meta) -> syn::Result<$repr_name> { - match Repr::from_meta(meta)? { - $(Repr::$repr_variant => Ok($repr_name::$repr_variant),)* - $(Repr::$repr_variant_aligned(u) => Ok($repr_name::$repr_variant_aligned(u)),)* - _ => Err(Error::new_spanned(meta, concat!("unsupported representation for deriving zerocopy trait(s) on ", $type_name))) +impl ToTokens for Repr +where + Prim: With + Copy, + Packed: With + Copy, +{ + fn to_tokens(&self, ts: &mut TokenStream) { + use Repr::*; + match self { + Transparent(span) => ts.append_all(quote_spanned! { *span=> #[repr(transparent)] }), + Compound(repr, align) => { + repr.to_tokens(ts); + if let Some(align) = align { + align.to_tokens(ts); } } } + } +} - // Define a stable ordering so we can canonicalize lists of reprs. The - // ordering itself doesn't matter so long as it's stable. - impl PartialOrd for $repr_name { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } +impl + Copy> ToTokens for Spanned, Span> { + fn to_tokens(&self, ts: &mut TokenStream) { + use CompoundRepr::*; + match &self.t { + C => ts.append_all(quote_spanned! { self.span=> #[repr(C)] }), + Rust => ts.append_all(quote_spanned! { self.span=> #[repr(Rust)] }), + Primitive(prim) => prim.with(|prim| Spanned::new(prim, self.span).to_tokens(ts)), } + } +} - impl Ord for $repr_name { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - format!("{:?}", self).cmp(&format!("{:?}", other)) - } +impl ToTokens for Spanned { + fn to_tokens(&self, ts: &mut TokenStream) { + use PrimitiveRepr::*; + match self.t { + U8 => ts.append_all(quote_spanned! { self.span => #[repr(u8)] }), + U16 => ts.append_all(quote_spanned! { self.span => #[repr(u16)] }), + U32 => ts.append_all(quote_spanned! { self.span => #[repr(u32)] }), + U64 => ts.append_all(quote_spanned! { self.span => #[repr(u64)] }), + Usize => ts.append_all(quote_spanned! { self.span => #[repr(usize)] }), + I8 => ts.append_all(quote_spanned! { self.span => #[repr(i8)] }), + I16 => ts.append_all(quote_spanned! { self.span => #[repr(i16)] }), + I32 => ts.append_all(quote_spanned! { self.span => #[repr(i32)] }), + I64 => ts.append_all(quote_spanned! { self.span => #[repr(i64)] }), + Isize => ts.append_all(quote_spanned! { self.span => #[repr(isize)] }), } + } +} - impl core::fmt::Display for $repr_name { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - $($repr_name::$repr_variant => Repr::$repr_variant,)* - $($repr_name::$repr_variant_aligned(u) => Repr::$repr_variant_aligned(*u),)* - }.fmt(f) +impl + Copy> ToTokens for Spanned, Span> { + fn to_tokens(&self, ts: &mut TokenStream) { + use AlignRepr::*; + match self.t { + Packed(n) => n.with(|n| { + let n = n.get(); + ts.append_all(quote_spanned! { self.span => #[repr(packed(#n))] }) + }), + Align(n) => { + let n = n.get(); + ts.append_all(quote_spanned! { self.span => #[repr(align(#n))] }) } } } } -define_kind_specific_repr!("a struct", StructRepr, [C, Transparent, Packed], [Align, PackedN]); -define_kind_specific_repr!( - "an enum", - EnumRepr, - [C, U8, U16, U32, U64, Usize, I8, I16, I32, I64, Isize], - [Align] -); +// Used to permit implementing `With for T: Inhabited` and for `Infallible` +// without a blanket impl conflict. +trait Inhabited {} +impl Inhabited for PrimitiveRepr {} +impl Inhabited for NonZeroU64 {} -impl EnumRepr { - fn as_str(&self) -> &'static str { - match self { - EnumRepr::C => "C", - EnumRepr::U8 => "u8", - EnumRepr::U16 => "u16", - EnumRepr::U32 => "u32", - EnumRepr::U64 => "u64", - EnumRepr::Usize => "usize", - EnumRepr::I8 => "i8", - EnumRepr::I16 => "i16", - EnumRepr::I32 => "i32", - EnumRepr::I64 => "i64", - EnumRepr::Isize => "isize", - EnumRepr::Align(_) => unimplemented!("repr not yet supported"), - } +trait With { + fn with O>(self, f: F) -> O; +} + +impl With for T { + fn with O>(self, f: F) -> O { + f(self) } } -impl ToTokens for EnumRepr { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - tokens.append(Ident::new(self.as_str(), Span::call_site())); +impl With for Infallible { + fn with O>(self, _f: F) -> O { + match self {} } } -// All representations known to Rust. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub(crate) enum Repr { +/// The result of parsing a single `#[repr(...)]` attribute or a single +/// directive inside a compound `#[repr(..., ...)]` attribute. +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(Debug))] +enum RawRepr { + Transparent, + C, + Rust, U8, U16, U32, @@ -212,128 +287,531 @@ pub(crate) enum Repr { I32, I64, Isize, - C, - Transparent, + Align(NonZeroU64), + PackedN(NonZeroU64), Packed, - PackedN(u64), - Align(u64), } -impl Repr { - fn from_meta(meta: &Meta) -> Result { - let (path, list) = match meta { - Meta::Path(path) => (path, None), - Meta::List(list) => (&list.path, Some(list)), - _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), - }; +/// A value with an associated span. +#[derive(Copy, Clone)] +#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +pub(crate) struct Spanned { + pub(crate) t: T, + pub(crate) span: S, +} - let ident = path - .get_ident() - .ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?; +impl Spanned { + fn new(t: T, span: S) -> Spanned { + Spanned { t, span } + } - Ok(match (ident.to_string().as_str(), list) { - ("u8", None) => Repr::U8, - ("u16", None) => Repr::U16, - ("u32", None) => Repr::U32, - ("u64", None) => Repr::U64, - ("usize", None) => Repr::Usize, - ("i8", None) => Repr::I8, - ("i16", None) => Repr::I16, - ("i32", None) => Repr::I32, - ("i64", None) => Repr::I64, - ("isize", None) => Repr::Isize, - ("C", None) => Repr::C, - ("transparent", None) => Repr::Transparent, - ("packed", None) => Repr::Packed, - ("packed", Some(list)) => { - Repr::PackedN(list.parse_args::()?.base10_parse::()?) - } - ("align", Some(list)) => { - Repr::Align(list.parse_args::()?.base10_parse::()?) - } - _ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), + fn from(s: Spanned) -> Spanned + where + T: From, + { + let Spanned { t: u, span } = s; + Spanned::new(u.into(), span) + } +} + +impl Spanned { + /// Delegates to `T: TryFrom`, preserving span information in both the + /// success and error cases. + fn try_from(u: Spanned) -> Result, FromRawReprError>> + where + T: TryFrom>, + { + let Spanned { t: u, span } = u; + T::try_from(u).map(|t| Spanned { t, span }).map_err(|err| match err { + FromRawReprError::None => FromRawReprError::None, + FromRawReprError::Err(e) => FromRawReprError::Err(Spanned::new(e, span)), }) } } -impl KindRepr for Repr { - fn is_align(&self) -> bool { - false +/// The error from converting from a `RawRepr`. +#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +enum FromRawReprError { + /// The `RawRepr` doesn't affect the high-level repr we're parsing (e.g. + /// it's `align(...)` and we're parsing a `CompoundRepr`). + None, + /// The `RawRepr` is invalid for the high-level repr we're parsing (e.g. + /// it's `packed` repr and we're parsing an `AlignRepr` for an enum type). + Err(E), +} + +/// The representation hint is not supported for the decorated type. +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +struct UnsupportedReprError; + +impl TryFrom for PrimitiveRepr { + type Error = UnsupportedReprError; + fn try_from(raw: RawRepr) -> Result { + use RawRepr::*; + match raw { + U8 => Ok(PrimitiveRepr::U8), + U16 => Ok(PrimitiveRepr::U16), + U32 => Ok(PrimitiveRepr::U32), + U64 => Ok(PrimitiveRepr::U64), + Usize => Ok(PrimitiveRepr::Usize), + I8 => Ok(PrimitiveRepr::I8), + I16 => Ok(PrimitiveRepr::I16), + I32 => Ok(PrimitiveRepr::I32), + I64 => Ok(PrimitiveRepr::I64), + Isize => Ok(PrimitiveRepr::Isize), + Transparent | C | Rust | Align(_) | PackedN(_) | Packed => Err(UnsupportedReprError), + } } +} - fn is_align_gt_one(&self) -> bool { - false +impl TryFrom for Infallible { + type Error = UnsupportedReprError; + fn try_from(_raw: RawRepr) -> Result { + Err(UnsupportedReprError) + } +} + +impl> TryFrom for CompoundRepr { + type Error = FromRawReprError; + fn try_from( + raw: RawRepr, + ) -> Result, FromRawReprError> { + use RawRepr::*; + match raw { + C => Ok(CompoundRepr::C), + Rust => Ok(CompoundRepr::Rust), + raw @ (U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize) => { + Prim::try_from(raw).map(CompoundRepr::Primitive).map_err(FromRawReprError::Err) + } + Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None), + } } +} - fn parse(meta: &Meta) -> syn::Result { - Self::from_meta(meta) +impl TryFrom for NonZeroU64 { + type Error = UnsupportedReprError; + fn try_from(raw: RawRepr) -> Result { + use RawRepr::*; + match raw { + Packed => Ok(NonZeroU64::MIN), + PackedN(n) => Ok(n), + U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | Transparent | C + | Rust | Align(_) => Err(UnsupportedReprError), + } } } -impl Display for Repr { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - if let Repr::Align(n) = self { - return write!(f, "repr(align({}))", n); +impl> TryFrom for AlignRepr { + type Error = FromRawReprError; + fn try_from(raw: RawRepr) -> Result, FromRawReprError> { + use RawRepr::*; + match raw { + Packed | PackedN(_) => { + Pcked::try_from(raw).map(AlignRepr::Packed).map_err(FromRawReprError::Err) + } + Align(n) => Ok(AlignRepr::Align(n)), + U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | Transparent | C + | Rust => Err(FromRawReprError::None), } - if let Repr::PackedN(n) = self { - return write!(f, "repr(packed({}))", n); + } +} + +/// The error from extracting a high-level repr type from a list of `RawRepr`s. +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +enum FromRawReprsError { + /// One of the `RawRepr`s is invalid for the high-level repr we're parsing + /// (e.g. there's a `packed` repr and we're parsing an `AlignRepr` for an + /// enum type). + Single(E), + /// Two `RawRepr`s appear which both affect the high-level repr we're + /// parsing (e.g., the list is `#[repr(align(2), packed)]`). Note that we + /// conservatively treat redundant reprs as conflicting (e.g. + /// `#[repr(packed, packed)]`). + Conflict, +} + +/// Tries to extract a high-level repr from a list of `RawRepr`s. +fn try_from_raw_reprs<'a, E, S: 'a + Copy, R: TryFrom>>( + r: impl IntoIterator>, +) -> Result>, Spanned, S>> { + // Walk the list of `RawRepr`s and attempt to convert each to an `R`. Bail + // if we find any errors. If we find more than one which converts to an `R`, + // bail with a `Conflict` error. + r.into_iter().try_fold(None, |found, raw| { + let new = match Spanned::::try_from(*raw) { + Ok(r) => r, + // This `RawRepr` doesn't convert to an `R`, so keep the current + // found `R`, if any. + Err(FromRawReprError::None) => return Ok(found), + // This repr is unsupported for the decorated type (e.g. + // `repr(packed)` on an enum). + Err(FromRawReprError::Err(Spanned { t: err, span })) => { + return Err(Spanned::new(FromRawReprsError::Single(err), span)) + } + }; + + if found.is_some() { + // We already found an `R`, but this `RawRepr` also converts to an + // `R`, so that's a conflict. + Err(Spanned::new(FromRawReprsError::Conflict, new.span)) + } else { + Ok(Some(new)) } - write!( - f, - "repr({})", - match self { - Repr::U8 => "u8", - Repr::U16 => "u16", - Repr::U32 => "u32", - Repr::U64 => "u64", - Repr::Usize => "usize", - Repr::I8 => "i8", - Repr::I16 => "i16", - Repr::I32 => "i32", - Repr::I64 => "i64", - Repr::Isize => "isize", - Repr::C => "C", - Repr::Transparent => "transparent", - Repr::Packed => "packed", - _ => unreachable!(), + }) +} + +/// The error returned from [`Repr::from_attrs`]. +#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))] +enum FromAttrsError { + FromRawReprs(FromRawReprsError), + Unrecognized, +} + +impl From> for FromAttrsError { + fn from(err: FromRawReprsError) -> FromAttrsError { + FromAttrsError::FromRawReprs(err) + } +} + +impl From for FromAttrsError { + fn from(_err: UnrecognizedReprError) -> FromAttrsError { + FromAttrsError::Unrecognized + } +} + +impl From> for Error { + fn from(err: Spanned) -> Error { + let Spanned { t: err, span } = err; + match err { + FromAttrsError::FromRawReprs(FromRawReprsError::Single( + _err @ UnsupportedReprError, + )) => Error::new(span, "unsupported representation hint for the decorated type"), + FromAttrsError::FromRawReprs(FromRawReprsError::Conflict) => { + // NOTE: This says "another" rather than "a preceding" because + // when one of the reprs involved is `transparent`, we detect + // that condition in `Repr::from_attrs`, and at that point we + // can't tell which repr came first, so we might report this on + // the first involved repr rather than the second, third, etc. + Error::new(span, "this conflicts with another representation hint") } - ) + FromAttrsError::Unrecognized => Error::new(span, "unrecognized representation hint"), + } } } -pub(crate) fn reprs(attrs: &[Attribute]) -> Result, Vec> { - let mut reprs = Vec::new(); - let mut errors = Vec::new(); - for attr in attrs { - // Ignore documentation attributes. - if attr.path().is_ident("doc") { - continue; +impl Repr { + fn from_attrs_inner( + attrs: &[Attribute], + ) -> Result, Spanned> + where + Prim: TryFrom, + Packed: TryFrom, + S: FromSpan + Copy, + { + let raw_reprs = RawRepr::from_attrs(attrs).map_err(Spanned::from)?; + + let transparent = { + let mut transparents = raw_reprs.iter().filter_map(|Spanned { t, span }| match t { + RawRepr::Transparent => Some(span), + _ => None, + }); + let first = transparents.next(); + let second = transparents.next(); + match (first, second) { + (None, None) => None, + (Some(span), None) => Some(*span), + (Some(_), Some(second)) => { + return Err(Spanned::new( + FromAttrsError::FromRawReprs(FromRawReprsError::Conflict), + *second, + ) + .into()) + } + // An iterator can't produce a value only on the second call to + // `.next()`. + (None, Some(_)) => unreachable!(), + } + }; + + let compound: Option, S>> = + try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?; + let align: Option, S>> = + try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?; + + if let Some(span) = transparent { + if compound.is_some() || align.is_some() { + // Arbitrarily report the problem on the `transparent` span. Any + // span will do. + return Err(Spanned::new(FromRawReprsError::Conflict.into(), span)); + } + + Ok(Repr::Transparent(span)) + } else { + Ok(Repr::Compound( + compound + .unwrap_or(Spanned::new(CompoundRepr::Rust, S::from_span(Span::call_site()))), + align, + )) } - if let Meta::List(ref meta_list) = attr.meta { - if meta_list.path.is_ident("repr") { - let parsed: Punctuated = - match meta_list.parse_args_with(Punctuated::parse_terminated) { - Ok(parsed) => parsed, - Err(_) => { - errors.push(Error::new_spanned( - &meta_list.tokens, - "unrecognized representation hint", - )); - continue; - } - }; - for meta in parsed { - match R::parse(&meta) { - Ok(repr) => reprs.push((meta, repr)), - Err(err) => errors.push(err), + } +} + +impl Repr { + #[allow(private_bounds)] + pub(crate) fn from_attrs(attrs: &[Attribute]) -> Result, Error> + where + Prim: TryFrom, + Packed: TryFrom, + { + Repr::from_attrs_inner(attrs).map_err(Into::into) + } +} + +/// The representation hint could not be parsed or was unrecognized. +struct UnrecognizedReprError; + +impl RawRepr { + fn from_attrs( + attrs: &[Attribute], + ) -> Result>, Spanned> { + let mut reprs = Vec::new(); + for attr in attrs { + // Ignore documentation attributes. + if attr.path().is_ident("doc") { + continue; + } + if let Meta::List(ref meta_list) = attr.meta { + if meta_list.path.is_ident("repr") { + let parsed: Punctuated = + match meta_list.parse_args_with(Punctuated::parse_terminated) { + Ok(parsed) => parsed, + Err(_) => { + return Err(Spanned::new( + UnrecognizedReprError, + S::from_span(meta_list.tokens.span()), + )) + } + }; + for meta in parsed { + let s = S::from_span(meta.span()); + reprs.push( + RawRepr::from_meta(&meta) + .map(|r| Spanned::new(r, s)) + .map_err(|e| Spanned::new(e, s))?, + ); } } } } + + Ok(reprs) + } + + fn from_meta(meta: &Meta) -> Result { + let (path, list) = match meta { + Meta::Path(path) => (path, None), + Meta::List(list) => (&list.path, Some(list)), + _ => return Err(UnrecognizedReprError), + }; + + let ident = path.get_ident().ok_or(UnrecognizedReprError)?; + + // Only returns `Ok` for non-zero power-of-two values. + let parse_nzu64 = |list: &MetaList| { + list.parse_args::() + .and_then(|int| int.base10_parse::()) + .map_err(|_| UnrecognizedReprError) + .and_then( + |nz| if nz.is_power_of_two() { Ok(nz) } else { Err(UnrecognizedReprError) }, + ) + }; + + use RawRepr::*; + Ok(match (ident.to_string().as_str(), list) { + ("u8", None) => U8, + ("u16", None) => U16, + ("u32", None) => U32, + ("u64", None) => U64, + ("usize", None) => Usize, + ("i8", None) => I8, + ("i16", None) => I16, + ("i32", None) => I32, + ("i64", None) => I64, + ("isize", None) => Isize, + ("C", None) => C, + ("transparent", None) => Transparent, + ("Rust", None) => Rust, + ("packed", None) => Packed, + ("packed", Some(list)) => PackedN(parse_nzu64(list)?), + ("align", Some(list)) => Align(parse_nzu64(list)?), + _ => return Err(UnrecognizedReprError), + }) + } +} + +trait FromSpan { + fn from_span(span: Span) -> Self; +} + +impl FromSpan for Span { + fn from_span(span: Span) -> Span { + span + } +} + +#[cfg(test)] +mod tests { + use super::*; + use syn::parse_quote; + + // We use `()` for spans in testing since real spans are hard to synthesize + // and don't implement `PartialEq`. + impl FromSpan for () { + fn from_span(_span: Span) -> () { + () + } + } + + impl From for Spanned { + fn from(t: T) -> Spanned { + Spanned::new(t, ()) + } } - if !errors.is_empty() { - return Err(errors); + type StructUnionRepr = Repr; + type EnumRepr = Repr; + + #[test] + fn test() { + // Test that a given `#[repr(...)]` attribute parses and returns the + // given `Repr` or error. + macro_rules! test { + ($(#[$attr:meta])* => $repr:expr) => { + test!(@inner $(#[$attr])* => Repr => Ok($repr)); + }; + // In the error case, the caller must explicitly provide the name of + // the `Repr` type to assist in type inference. + (@error $(#[$attr:meta])* => $typ:ident => $repr:expr) => { + test!(@inner $(#[$attr])* => $typ => Err($repr)); + }; + (@inner $(#[$attr:meta])* => $typ:ident => $repr:expr) => { + let attr: Attribute = parse_quote!($(#[$attr])*); + let mut got = $typ::from_attrs_inner(&[attr]); + let expect: Result, _> = $repr; + if false { + // Force Rust to infer `got` as having the same type as + // `expect`. + got = expect; + } + assert_eq!(got, expect, stringify!($(#[$attr])*)); + }; + } + + use {AlignRepr::*, CompoundRepr::*, PrimitiveRepr::*}; + let nz = |n: u64| NonZeroU64::new(n).unwrap(); + + test!(#[repr(transparent)] => StructUnionRepr::Transparent(())); + test!(#[repr()] => StructUnionRepr::Compound(Rust.into(), None)); + test!(#[repr(packed)] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(1)).into()))); + test!(#[repr(packed(2))] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(2)).into()))); + test!(#[repr(align(1))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(1)).into()))); + test!(#[repr(align(2))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(2)).into()))); + test!(#[repr(C)] => StructUnionRepr::Compound(C.into(), None)); + test!(#[repr(C, packed)] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(1)).into()))); + test!(#[repr(C, packed(2))] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(2)).into()))); + test!(#[repr(C, align(1))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(1)).into()))); + test!(#[repr(C, align(2))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(2)).into()))); + + test!(#[repr(transparent)] => EnumRepr::Transparent(())); + test!(#[repr()] => EnumRepr::Compound(Rust.into(), None)); + test!(#[repr(align(1))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(1)).into()))); + test!(#[repr(align(2))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(2)).into()))); + + macro_rules! for_each_compound_repr { + ($($r:tt => $var:expr),*) => { + $( + test!(#[repr($r)] => EnumRepr::Compound($var.into(), None)); + test!(#[repr($r, align(1))] => EnumRepr::Compound($var.into(), Some(Align(nz(1)).into()))); + test!(#[repr($r, align(2))] => EnumRepr::Compound($var.into(), Some(Align(nz(2)).into()))); + )* + } + } + + for_each_compound_repr!( + C => C, + u8 => Primitive(U8), + u16 => Primitive(U16), + u32 => Primitive(U32), + u64 => Primitive(U64), + usize => Primitive(Usize), + i8 => Primitive(I8), + i16 => Primitive(I16), + i32 => Primitive(I32), + i64 => Primitive(I64), + isize => Primitive(Isize) + ); + + use {FromAttrsError::*, FromRawReprsError::*}; + + // Run failure tests which are valid for both `StructUnionRepr` and + // `EnumRepr`. + macro_rules! for_each_repr_type { + ($($repr:ident),*) => { + $( + // Invalid packed or align attributes + test!(@error #[repr(packed(0))] => $repr => Unrecognized.into()); + test!(@error #[repr(packed(3))] => $repr => Unrecognized.into()); + test!(@error #[repr(align(0))] => $repr => Unrecognized.into()); + test!(@error #[repr(align(3))] => $repr => Unrecognized.into()); + + // Conflicts + test!(@error #[repr(transparent, transparent)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(transparent, C)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(transparent, Rust)] => $repr => FromRawReprs(Conflict).into()); + + test!(@error #[repr(C, transparent)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(C, C)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(C, Rust)] => $repr => FromRawReprs(Conflict).into()); + + test!(@error #[repr(Rust, transparent)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(Rust, C)] => $repr => FromRawReprs(Conflict).into()); + test!(@error #[repr(Rust, Rust)] => $repr => FromRawReprs(Conflict).into()); + )* + } + } + + for_each_repr_type!(StructUnionRepr, EnumRepr); + + // Enum-specific conflicts. + // + // We don't bother to test every combination since that would be a huge + // number (enums can have primitive reprs u8, u16, u32, u64, usize, i8, + // i16, i32, i64, and isize). Instead, since the conflict logic doesn't + // care what specific value of `PrimitiveRepr` is present, we assume + // that testing against u8 alone is fine. + test!(@error #[repr(transparent, u8)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(u8, transparent)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(C, u8)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(u8, C)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(Rust, u8)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(u8, Rust)] => EnumRepr => FromRawReprs(Conflict).into()); + test!(@error #[repr(u8, u8)] => EnumRepr => FromRawReprs(Conflict).into()); + + // Illegal struct/union reprs + test!(@error #[repr(u8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(u16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(u32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(u64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(usize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(i8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(i16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(i32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(i64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(isize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + + // Illegal enum reprs + test!(@error #[repr(packed)] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(packed(1))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into()); + test!(@error #[repr(packed(2))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into()); } - Ok(reprs) } diff --git a/zerocopy-derive/tests/ui-nightly/enum.stderr b/zerocopy-derive/tests/ui-nightly/enum.stderr index 04db0af8b6..68b53f4ccc 100644 --- a/zerocopy-derive/tests/ui-nightly/enum.stderr +++ b/zerocopy-derive/tests/ui-nightly/enum.stderr @@ -10,19 +10,27 @@ error: unrecognized representation hint 25 | #[repr(foo)] | ^^^ -error: unsupported representation for deriving zerocopy trait(s) on an enum +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout + --> tests/ui-nightly/enum.rs:30:10 + | +30 | #[derive(FromBytes)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: --> tests/ui-nightly/enum.rs:31:8 | 31 | #[repr(transparent)] | ^^^^^^^^^^^ -error: conflicting representation hints - --> tests/ui-nightly/enum.rs:37:8 +error: this conflicts with another representation hint + --> tests/ui-nightly/enum.rs:37:12 | 37 | #[repr(u8, u16)] - | ^^^^^^^ + | ^^^ -error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:42:10 | 42 | #[derive(FromBytes)] @@ -31,6 +39,22 @@ error: must have a non-align #[repr(...)] attribute in order to guarantee this t = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout + --> tests/ui-nightly/enum.rs:42:10 + | +42 | #[derive(FromBytes)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: + --> tests/ui-nightly/enum.rs:42:10 + | +42 | #[derive(FromBytes)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:69:10 | 69 | #[derive(TryFromBytes)] @@ -38,7 +62,7 @@ error: must have a non-align #[repr(...)] attribute in order to guarantee this t | = note: this error originates in the derive macro `TryFromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) -error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:74:10 | 74 | #[derive(TryFromBytes)] @@ -46,6 +70,14 @@ error: must have a non-align #[repr(...)] attribute in order to guarantee this t | = note: this error originates in the derive macro `TryFromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout + --> tests/ui-nightly/enum.rs:92:10 + | +92 | #[derive(FromZeros)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info) + error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:92:10 | @@ -54,6 +86,14 @@ error: must have a non-align #[repr(...)] attribute in order to guarantee this t | = note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info) +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout + --> tests/ui-nightly/enum.rs:97:10 + | +97 | #[derive(FromZeros)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info) + error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:97:10 | @@ -62,6 +102,14 @@ error: must have a non-align #[repr(...)] attribute in order to guarantee this t | = note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info) +error: must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout + --> tests/ui-nightly/enum.rs:103:10 + | +103 | #[derive(FromZeros)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromZeros` (in Nightly builds, run with -Z macro-backtrace for more info) + error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout --> tests/ui-nightly/enum.rs:103:10 | @@ -101,125 +149,143 @@ error: FromZeros only supported on enums with a variant that has a discriminant 138 | | } | |_^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:145:8 | 145 | #[repr(C)] | ^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:151:8 | 151 | #[repr(usize)] | ^^^^^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:157:8 | 157 | #[repr(isize)] | ^^^^^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:163:8 | 163 | #[repr(u32)] | ^^^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:169:8 | 169 | #[repr(i32)] | ^^^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:175:8 | 175 | #[repr(u64)] | ^^^ -error: FromBytes requires repr of "u8", "u16", "i8", or "i16" +error: --> tests/ui-nightly/enum.rs:181:8 | 181 | #[repr(i64)] | ^^^ -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:452:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:451:10 | -452 | #[repr(C)] - | ^ +451 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:458:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:457:10 | -458 | #[repr(u16)] - | ^^^ +457 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:464:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:463:10 | -464 | #[repr(i16)] - | ^^^ +463 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:470:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:469:10 | -470 | #[repr(u32)] - | ^^^ +469 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:476:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:475:10 | -476 | #[repr(i32)] - | ^^^ +475 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:482:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:481:10 | -482 | #[repr(u64)] - | ^^^ +481 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:488:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:487:10 | -488 | #[repr(i64)] - | ^^^ +487 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:494:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:493:10 | -494 | #[repr(usize)] - | ^^^^^ +493 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> tests/ui-nightly/enum.rs:500:8 +error: must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment + --> tests/ui-nightly/enum.rs:499:10 | -500 | #[repr(isize)] - | ^^^^^ +499 | #[derive(Unaligned)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error: cannot derive Unaligned with repr(align(N > 1)) +error: cannot derive `Unaligned` on type with alignment greater than 1 --> tests/ui-nightly/enum.rs:506:12 | 506 | #[repr(u8, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) +error: cannot derive `Unaligned` on type with alignment greater than 1 --> tests/ui-nightly/enum.rs:512:12 | 512 | #[repr(i8, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) +error: this conflicts with another representation hint --> tests/ui-nightly/enum.rs:518:18 | 518 | #[repr(align(1), align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/enum.rs:524:8 +error: this conflicts with another representation hint + --> tests/ui-nightly/enum.rs:524:18 | 524 | #[repr(align(2), align(4))] - | ^^^^^^^^ + | ^^^^^^^^ error[E0565]: meta item in `repr` must be an identifier --> tests/ui-nightly/enum.rs:19:8 @@ -245,6 +311,30 @@ error[E0566]: conflicting representation hints = note: for more information, see issue #68585 = note: `#[deny(conflicting_repr_hints)]` on by default +error[E0277]: the trait bound `Generic3: TryFromBytes` is not satisfied + --> tests/ui-nightly/enum.rs:30:10 + | +30 | #[derive(FromBytes)] + | ^^^^^^^^^ the trait `TryFromBytes` is not implemented for `Generic3` + | + = note: Consider adding `#[derive(TryFromBytes)]` to `Generic3` + = help: the following other types implement trait `TryFromBytes`: + () + *const T + *mut T + ::is_bit_valid::___ZerocopyVariantStruct_A + ::is_bit_valid::___ZerocopyVariantStruct_A + AtomicBool + AtomicI16 + AtomicI32 + and $N others +note: required by a bound in `FromZeros` + --> $WORKSPACE/src/lib.rs + | + | pub unsafe trait FromZeros: TryFromBytes { + | ^^^^^^^^^^^^ required by this bound in `FromZeros` + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `UnsafeCell<()>: Immutable` is not satisfied --> tests/ui-nightly/enum.rs:51:10 | diff --git a/zerocopy-derive/tests/ui-nightly/struct.rs b/zerocopy-derive/tests/ui-nightly/struct.rs index e75fd59310..ab6b48d82e 100644 --- a/zerocopy-derive/tests/ui-nightly/struct.rs +++ b/zerocopy-derive/tests/ui-nightly/struct.rs @@ -129,6 +129,12 @@ struct IntoBytes4 { b: [u8], } +#[derive(IntoBytes)] +#[repr(C, C)] // zerocopy-derive conservatively treats these as conflicting reprs +struct IntoBytes5 { + a: u8, +} + // // Unaligned errors // diff --git a/zerocopy-derive/tests/ui-nightly/struct.stderr b/zerocopy-derive/tests/ui-nightly/struct.stderr index feef5cb800..c41070cb58 100644 --- a/zerocopy-derive/tests/ui-nightly/struct.stderr +++ b/zerocopy-derive/tests/ui-nightly/struct.stderr @@ -1,37 +1,43 @@ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/struct.rs:137:11 +error: this conflicts with another representation hint + --> tests/ui-nightly/struct.rs:133:11 | -137 | #[repr(C, align(2))] +133 | #[repr(C, C)] // zerocopy-derive conservatively treats these as conflicting reprs + | ^ + +error: cannot derive `Unaligned` on type with alignment greater than 1 + --> tests/ui-nightly/struct.rs:143:11 + | +143 | #[repr(C, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/struct.rs:141:21 +error: this conflicts with another representation hint + --> tests/ui-nightly/struct.rs:147:8 | -141 | #[repr(transparent, align(2))] - | ^^^^^^^^ +147 | #[repr(transparent, align(2))] + | ^^^^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/struct.rs:147:16 +error: this conflicts with another representation hint + --> tests/ui-nightly/struct.rs:153:16 | -147 | #[repr(packed, align(2))] +153 | #[repr(packed, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/struct.rs:151:18 +error: this conflicts with another representation hint + --> tests/ui-nightly/struct.rs:157:18 | -151 | #[repr(align(1), align(2))] +157 | #[repr(align(1), align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/struct.rs:155:8 +error: this conflicts with another representation hint + --> tests/ui-nightly/struct.rs:161:18 | -155 | #[repr(align(2), align(4))] - | ^^^^^^^^ +161 | #[repr(align(2), align(4))] + | ^^^^^^^^ error[E0692]: transparent struct cannot have other repr hints - --> tests/ui-nightly/struct.rs:141:8 + --> tests/ui-nightly/struct.rs:147:8 | -141 | #[repr(transparent, align(2))] +147 | #[repr(transparent, align(2))] | ^^^^^^^^^^^ ^^^^^^^^ error[E0277]: the size for values of type `[u8]` cannot be known at compilation time @@ -313,7 +319,7 @@ note: required by a bound in `macro_util::__size_of::size_of` | ^^^^^ required by this bound in `size_of` error[E0587]: type has conflicting packed and align representation hints - --> tests/ui-nightly/struct.rs:148:1 + --> tests/ui-nightly/struct.rs:154:1 | -148 | struct Unaligned3; +154 | struct Unaligned3; | ^^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui-nightly/union.stderr b/zerocopy-derive/tests/ui-nightly/union.stderr index 7650f84c8e..66aec9a284 100644 --- a/zerocopy-derive/tests/ui-nightly/union.stderr +++ b/zerocopy-derive/tests/ui-nightly/union.stderr @@ -6,29 +6,29 @@ error: unsupported on types with type parameters | = note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info) -error: cannot derive Unaligned with repr(align(N > 1)) +error: cannot derive `Unaligned` on type with alignment greater than 1 --> tests/ui-nightly/union.rs:51:11 | 51 | #[repr(C, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) +error: this conflicts with another representation hint --> tests/ui-nightly/union.rs:67:16 | 67 | #[repr(packed, align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) +error: this conflicts with another representation hint --> tests/ui-nightly/union.rs:73:18 | 73 | #[repr(align(1), align(2))] | ^^^^^^^^ -error: cannot derive Unaligned with repr(align(N > 1)) - --> tests/ui-nightly/union.rs:79:8 +error: this conflicts with another representation hint + --> tests/ui-nightly/union.rs:79:18 | 79 | #[repr(align(2), align(4))] - | ^^^^^^^^ + | ^^^^^^^^ error[E0277]: the trait bound `UnsafeCell<()>: zerocopy::Immutable` is not satisfied --> tests/ui-nightly/union.rs:24:10