diff options
Diffstat (limited to 'third_party/rust/zerocopy-derive/src/lib.rs')
-rw-r--r-- | third_party/rust/zerocopy-derive/src/lib.rs | 882 |
1 files changed, 882 insertions, 0 deletions
diff --git a/third_party/rust/zerocopy-derive/src/lib.rs b/third_party/rust/zerocopy-derive/src/lib.rs new file mode 100644 index 0000000000..9af8a28a06 --- /dev/null +++ b/third_party/rust/zerocopy-derive/src/lib.rs @@ -0,0 +1,882 @@ +// Copyright 2019 The Fuchsia Authors +// +// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 +// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +//! Derive macros for [zerocopy]'s traits. +//! +//! [zerocopy]: https://docs.rs/zerocopy + +// Sometimes we want to use lints which were added after our MSRV. +// `unknown_lints` is `warn` by default and we deny warnings in CI, so without +// this attribute, any unknown lint would cause a CI failure when testing with +// our MSRV. +#![allow(unknown_lints)] +#![deny(renamed_and_removed_lints)] +#![deny(clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)] +#![deny( + rustdoc::bare_urls, + rustdoc::broken_intra_doc_links, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_html_tags, + rustdoc::invalid_rust_codeblocks, + rustdoc::missing_crate_level_docs, + rustdoc::private_intra_doc_links +)] +#![recursion_limit = "128"] + +mod ext; +mod repr; + +use { + proc_macro2::Span, + quote::quote, + syn::{ + parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit, + GenericParam, Ident, Lit, + }, +}; + +use {crate::ext::*, crate::repr::*}; + +// Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a +// `TokenStream` and returning it. +macro_rules! try_or_print { + ($e:expr) => { + match $e { + Ok(x) => x, + Err(errors) => return print_all_errors(errors).into(), + } + }; +} + +// 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: +// +// error: unsupported representation +// --> enum.rs:28:8 +// | +// 28 | #[repr(transparent)] +// | +// help: required by the derive of FromBytes +// +// Instead, we have more verbose error messages like "unsupported representation +// for deriving FromZeroes, FromBytes, AsBytes, or Unaligned on an enum" +// +// This will probably require Span::error +// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error), +// which is currently unstable. Revisit this once it's stable. + +#[proc_macro_derive(KnownLayout)] +pub fn derive_known_layout(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + + let is_repr_c_struct = match &ast.data { + Data::Struct(..) => { + let reprs = try_or_print!(repr::reprs::<Repr>(&ast.attrs)); + if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) { + Some(reprs) + } else { + None + } + } + Data::Enum(..) | Data::Union(..) => None, + }; + + let fields = ast.data.field_types(); + + let (require_self_sized, extras) = if let ( + Some(reprs), + Some((trailing_field, leading_fields)), + ) = (is_repr_c_struct, fields.split_last()) + { + 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!(NonZeroUsize::new(#repr_align as usize))) + .unwrap_or(quote!(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!(NonZeroUsize::new(#repr_packed as usize))) + .unwrap_or(quote!(None)); + + ( + false, + quote!( + // SAFETY: `LAYOUT` accurately describes the layout of `Self`. + // The layout of `Self` is reflected using a sequence of + // invocations of `DstLayout::{new_zst,extend,pad_to_align}`. + // The documentation of these items vows that invocations in + // this manner will acurately describe a type, so long as: + // + // - that type is `repr(C)`, + // - its fields are enumerated in the order they appear, + // - the presence of `repr_align` and `repr_packed` are correctly accounted for. + // + // We respect all three of these preconditions here. This + // expansion is only used if `is_repr_c_struct`, we enumerate + // the fields in order, and we extract the values of `align(N)` + // and `packed(N)`. + const LAYOUT: ::zerocopy::DstLayout = { + use ::zerocopy::macro_util::core_reexport::num::NonZeroUsize; + use ::zerocopy::{DstLayout, KnownLayout}; + + let repr_align = #repr_align; + let repr_packed = #repr_packed; + + DstLayout::new_zst(repr_align) + #(.extend(DstLayout::for_type::<#leading_fields>(), repr_packed))* + .extend(<#trailing_field as KnownLayout>::LAYOUT, repr_packed) + .pad_to_align() + }; + + // SAFETY: + // - The recursive call to `raw_from_ptr_len` preserves both address and provenance. + // - The `as` cast preserves both address and provenance. + // - `NonNull::new_unchecked` preserves both address and provenance. + #[inline(always)] + fn raw_from_ptr_len( + bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>, + elems: usize, + ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> { + use ::zerocopy::{KnownLayout}; + let trailing = <#trailing_field as KnownLayout>::raw_from_ptr_len(bytes, elems); + let slf = trailing.as_ptr() as *mut Self; + // SAFETY: Constructed from `trailing`, which is non-null. + unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) } + } + ), + ) + } else { + // For enums, unions, and non-`repr(C)` structs, we require that + // `Self` is sized, and as a result don't need to reason about the + // internals of the type. + ( + true, + quote!( + // SAFETY: `LAYOUT` is guaranteed to accurately describe the + // layout of `Self`, because that is the documented safety + // contract of `DstLayout::for_type`. + const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>(); + + // SAFETY: `.cast` preserves address and provenance. + // + // TODO(#429): Add documentation to `.cast` that promises that + // it preserves provenance. + #[inline(always)] + fn raw_from_ptr_len( + bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>, + _elems: usize, + ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> { + bytes.cast::<Self>() + } + ), + ) + }; + + match &ast.data { + Data::Struct(strct) => { + let require_trait_bound_on_field_types = if require_self_sized { + RequireBoundedFields::No + } else { + RequireBoundedFields::Trailing + }; + + // A bound on the trailing field is required, since structs are + // unsized if their trailing field is unsized. Reflecting the layout + // of an usized trailing field requires that the field is + // `KnownLayout`. + impl_block( + &ast, + strct, + Trait::KnownLayout, + require_trait_bound_on_field_types, + require_self_sized, + None, + Some(extras), + ) + } + Data::Enum(enm) => { + // A bound on the trailing field is not required, since enums cannot + // currently be unsized. + impl_block( + &ast, + enm, + Trait::KnownLayout, + RequireBoundedFields::No, + true, + None, + Some(extras), + ) + } + Data::Union(unn) => { + // A bound on the trailing field is not required, since unions + // cannot currently be unsized. + impl_block( + &ast, + unn, + Trait::KnownLayout, + RequireBoundedFields::No, + true, + None, + Some(extras), + ) + } + } + .into() +} + +#[proc_macro_derive(FromZeroes)] +pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_from_zeroes_struct(&ast, strct), + Data::Enum(enm) => derive_from_zeroes_enum(&ast, enm), + Data::Union(unn) => derive_from_zeroes_union(&ast, unn), + } + .into() +} + +#[proc_macro_derive(FromBytes)] +pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_from_bytes_struct(&ast, strct), + Data::Enum(enm) => derive_from_bytes_enum(&ast, enm), + Data::Union(unn) => derive_from_bytes_union(&ast, unn), + } + .into() +} + +#[proc_macro_derive(AsBytes)] +pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_as_bytes_struct(&ast, strct), + Data::Enum(enm) => derive_as_bytes_enum(&ast, enm), + Data::Union(unn) => derive_as_bytes_union(&ast, unn), + } + .into() +} + +#[proc_macro_derive(Unaligned)] +pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_unaligned_struct(&ast, strct), + Data::Enum(enm) => derive_unaligned_enum(&ast, enm), + Data::Union(unn) => derive_unaligned_union(&ast, unn), + } + .into() +} + +const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ + &[StructRepr::C], + &[StructRepr::Transparent], + &[StructRepr::Packed], + &[StructRepr::C, StructRepr::Packed], +]; + +// A struct is `FromZeroes` if: +// - all fields are `FromZeroes` + +fn derive_from_zeroes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + impl_block(ast, strct, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None) +} + +// An enum is `FromZeroes` if: +// - all of its variants are fieldless +// - one of the variants has a discriminant of `0` + +fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { + if !enm.is_c_like() { + return Error::new_spanned(ast, "only C-like enums can implement FromZeroes") + .to_compile_error(); + } + + let has_explicit_zero_discriminant = + enm.variants.iter().filter_map(|v| v.discriminant.as_ref()).any(|(_, e)| { + if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = e { + i.base10_parse::<usize>().ok() == Some(0) + } else { + false + } + }); + // If the first variant of an enum does not specify its discriminant, it is set to zero: + // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations + let has_implicit_zero_discriminant = + enm.variants.iter().next().map(|v| v.discriminant.is_none()) == Some(true); + + if !has_explicit_zero_discriminant && !has_implicit_zero_discriminant { + return Error::new_spanned( + ast, + "FromZeroes only supported on enums with a variant that has a discriminant of `0`", + ) + .to_compile_error(); + } + + impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None) +} + +// Like structs, unions are `FromZeroes` if +// - all fields are `FromZeroes` + +fn derive_from_zeroes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + impl_block(ast, unn, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None) +} + +// A struct is `FromBytes` if: +// - all fields are `FromBytes` + +fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + impl_block(ast, strct, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None) +} + +// An enum is `FromBytes` if: +// - Every possible bit pattern must be valid, which means that every bit +// pattern must correspond to a different enum variant. Thus, for an enum +// whose layout takes up N bytes, there must be 2^N variants. +// - Since we must know N, only representations which guarantee the layout's +// size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an +// implementation-defined size). `usize` and `isize` technically guarantee the +// layout's size, but would require us to know how large those are on the +// target platform. This isn't terribly difficult - we could emit a const +// expression that could call `core::mem::size_of` in order to determine the +// size and check against the number of enum variants, but a) this would be +// platform-specific and, b) even on Rust's smallest bit width platform (32), +// this would require ~4 billion enum variants, which obviously isn't a thing. + +fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { + if !enm.is_c_like() { + return Error::new_spanned(ast, "only C-like enums can implement FromBytes") + .to_compile_error(); + } + + let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast)); + + let variants_required = match reprs.as_slice() { + [EnumRepr::U8] | [EnumRepr::I8] => 1usize << 8, + [EnumRepr::U16] | [EnumRepr::I16] => 1usize << 16, + // `validate_reprs` has already validated that it's one of the preceding + // patterns. + _ => unreachable!(), + }; + if enm.variants.len() != variants_required { + return Error::new_spanned( + ast, + format!( + "FromBytes only supported on {} enum with {} variants", + reprs[0], variants_required + ), + ) + .to_compile_error(); + } + + impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None) +} + +#[rustfmt::skip] +const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = { + 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], + ], + } +}; + +// Like structs, unions are `FromBytes` if +// - all fields are `FromBytes` + +fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + impl_block(ast, unn, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None) +} + +// A struct is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)` or `repr(transparent)` and +// - no padding (size of struct equals sum of size of field types) +// - `repr(packed)` + +fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); + let is_transparent = reprs.contains(&StructRepr::Transparent); + let is_packed = reprs.contains(&StructRepr::Packed); + + // TODO(#10): Support type parameters for non-transparent, non-packed + // structs. + if !ast.generics.params.is_empty() && !is_transparent && !is_packed { + return Error::new( + Span::call_site(), + "unsupported on generic structs that are not repr(transparent) or repr(packed)", + ) + .to_compile_error(); + } + + // We don't need a padding check if the struct is repr(transparent) or + // repr(packed). + // - 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 of that + // field) and we require that field to be `AsBytes` (meaning there's no + // padding in that field). + // - repr(packed): Any inter-field padding bytes are removed, meaning that + // any padding bytes would need to come from the fields, all of which + // we require to be `AsBytes` (meaning they don't have any padding). + let padding_check = if is_transparent || is_packed { None } else { Some(PaddingCheck::Struct) }; + impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, false, padding_check, None) +} + +const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config { + // Since `disallowed_but_legal_combinations` is empty, this message will + // never actually be emitted. + allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""#, + derive_unaligned: false, + allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, + disallowed_but_legal_combinations: &[], +}; + +// An enum is `AsBytes` if it is C-like and has a defined repr. + +fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { + if !enm.is_c_like() { + return Error::new_spanned(ast, "only C-like enums can implement AsBytes") + .to_compile_error(); + } + + // We don't care what the repr is; we only care that it is one of the + // allowed ones. + let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast)); + impl_block(ast, enm, Trait::AsBytes, RequireBoundedFields::No, false, None, None) +} + +#[rustfmt::skip] +const ENUM_AS_BYTES_CFG: Config<EnumRepr> = { + use EnumRepr::*; + Config { + // Since `disallowed_but_legal_combinations` is empty, this message will + // never actually be emitted. + allowed_combinations_message: r#"AsBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", or "isize""#, + derive_unaligned: false, + allowed_combinations: &[ + &[C], + &[U8], + &[U16], + &[I8], + &[I16], + &[U32], + &[I32], + &[U64], + &[I64], + &[Usize], + &[Isize], + ], + disallowed_but_legal_combinations: &[], + } +}; + +// A union is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)`, `repr(transparent)`, or `repr(packed)` +// - no padding (size of union equals size of each field type) + +fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + // TODO(#10): Support type parameters. + if !ast.generics.params.is_empty() { + return Error::new(Span::call_site(), "unsupported on types with type parameters") + .to_compile_error(); + } + + try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); + + impl_block( + ast, + unn, + Trait::AsBytes, + RequireBoundedFields::Yes, + false, + Some(PaddingCheck::Union), + None, + ) +} + +// A struct is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and +// - 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 require_trait_bounds_on_field_types = (!reprs.contains(&StructRepr::Packed)).into(); + + impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, false, None, None) +} + +const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = 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))` +// - `repr(u8)` or `repr(i8)` + +fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { + if !enm.is_c_like() { + return Error::new_spanned(ast, "only C-like enums can implement Unaligned") + .to_compile_error(); + } + + // 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<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast)); + + // C-like enums cannot currently have type parameters, so this value of true + // for `require_trait_bound_on_field_types` doesn't really do anything. But + // it's marginally more future-proof in case that restriction is lifted in + // the future. + impl_block(ast, enm, Trait::Unaligned, RequireBoundedFields::Yes, false, None, None) +} + +#[rustfmt::skip] +const ENUM_UNALIGNED_CFG: Config<EnumRepr> = { + 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], + ], + disallowed_but_legal_combinations: &[ + &[C], + &[U16], + &[U32], + &[U64], + &[Usize], + &[I16], + &[I32], + &[I64], + &[Isize], + ], + } +}; + +// Like structs, a union is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and +// - 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 require_trait_bound_on_field_types = (!reprs.contains(&StructRepr::Packed)).into(); + + impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, false, None, None) +} + +// This enum describes what kind of padding check needs to be generated for the +// associated impl. +enum PaddingCheck { + // Check that the sum of the fields' sizes exactly equals the struct's size. + Struct, + // Check that the size of each field exactly equals the union's size. + Union, +} + +impl PaddingCheck { + /// Returns the ident of the macro to call in order to validate that a type + /// passes the padding check encoded by `PaddingCheck`. + fn validator_macro_ident(&self) -> Ident { + let s = match self { + PaddingCheck::Struct => "struct_has_padding", + PaddingCheck::Union => "union_has_padding", + }; + + Ident::new(s, Span::call_site()) + } +} + +#[derive(Debug, Eq, PartialEq)] +enum Trait { + KnownLayout, + FromZeroes, + FromBytes, + AsBytes, + Unaligned, +} + +impl Trait { + fn ident(&self) -> Ident { + Ident::new(format!("{:?}", self).as_str(), Span::call_site()) + } +} + +#[derive(Debug, Eq, PartialEq)] +enum RequireBoundedFields { + No, + Yes, + Trailing, +} + +impl From<bool> for RequireBoundedFields { + fn from(do_require: bool) -> Self { + match do_require { + true => Self::Yes, + false => Self::No, + } + } +} + +fn impl_block<D: DataExt>( + input: &DeriveInput, + data: &D, + trt: Trait, + require_trait_bound_on_field_types: RequireBoundedFields, + require_self_sized: bool, + padding_check: Option<PaddingCheck>, + extras: Option<proc_macro2::TokenStream>, +) -> proc_macro2::TokenStream { + // In this documentation, we will refer to this hypothetical struct: + // + // #[derive(FromBytes)] + // struct Foo<T, I: Iterator> + // where + // T: Copy, + // I: Clone, + // I::Item: Clone, + // { + // a: u8, + // b: T, + // c: I::Item, + // } + // + // We extract the field types, which in this case are `u8`, `T`, and + // `I::Item`. We re-use the existing parameters and where clauses. If + // `require_trait_bound == true` (as it is for `FromBytes), we add where + // bounds for each field's type: + // + // impl<T, I: Iterator> FromBytes for Foo<T, I> + // where + // T: Copy, + // I: Clone, + // I::Item: Clone, + // T: FromBytes, + // I::Item: FromBytes, + // { + // } + // + // NOTE: It is standard practice to only emit bounds for the type parameters + // themselves, not for field types based on those parameters (e.g., `T` vs + // `T::Foo`). For a discussion of why this is standard practice, see + // https://github.com/rust-lang/rust/issues/26925. + // + // The reason we diverge from this standard is that doing it that way for us + // would be unsound. E.g., consider a type, `T` where `T: FromBytes` but + // `T::Foo: !FromBytes`. It would not be sound for us to accept a type with + // a `T::Foo` field as `FromBytes` simply because `T: FromBytes`. + // + // While there's no getting around this requirement for us, it does have the + // pretty serious downside that, when lifetimes are involved, the trait + // solver ties itself in knots: + // + // #[derive(Unaligned)] + // #[repr(C)] + // struct Dup<'a, 'b> { + // a: PhantomData<&'a u8>, + // b: PhantomData<&'b u8>, + // } + // + // error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned` + // --> src/main.rs:6:10 + // | + // 6 | #[derive(Unaligned)] + // | ^^^^^^^^^ + // | + // = note: required by `zerocopy::Unaligned` + + let type_ident = &input.ident; + let trait_ident = trt.ident(); + let field_types = data.field_types(); + + let bound_tt = |ty| parse_quote!(#ty: ::zerocopy::#trait_ident); + let field_type_bounds: Vec<_> = match (require_trait_bound_on_field_types, &field_types[..]) { + (RequireBoundedFields::Yes, _) => field_types.iter().map(bound_tt).collect(), + (RequireBoundedFields::No, _) | (RequireBoundedFields::Trailing, []) => vec![], + (RequireBoundedFields::Trailing, [.., last]) => vec![bound_tt(last)], + }; + + // Don't bother emitting a padding check if there are no fields. + #[allow(unstable_name_collisions)] // See `BoolExt` below + let padding_check_bound = padding_check.and_then(|check| (!field_types.is_empty()).then_some(check)).map(|check| { + let fields = field_types.iter(); + let validator_macro = check.validator_macro_ident(); + parse_quote!( + ::zerocopy::macro_util::HasPadding<#type_ident, {::zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>: + ::zerocopy::macro_util::ShouldBe<false> + ) + }); + + let self_sized_bound = if require_self_sized { Some(parse_quote!(Self: Sized)) } else { None }; + + let bounds = input + .generics + .where_clause + .as_ref() + .map(|where_clause| where_clause.predicates.iter()) + .into_iter() + .flatten() + .chain(field_type_bounds.iter()) + .chain(padding_check_bound.iter()) + .chain(self_sized_bound.iter()); + + // The parameters with trait bounds, but without type defaults. + let params = input.generics.params.clone().into_iter().map(|mut param| { + match &mut param { + GenericParam::Type(ty) => ty.default = None, + GenericParam::Const(cnst) => cnst.default = None, + GenericParam::Lifetime(_) => {} + } + quote!(#param) + }); + + // The identifiers of the parameters without trait bounds or type defaults. + let param_idents = input.generics.params.iter().map(|param| match param { + GenericParam::Type(ty) => { + let ident = &ty.ident; + quote!(#ident) + } + GenericParam::Lifetime(l) => { + let ident = &l.lifetime; + quote!(#ident) + } + GenericParam::Const(cnst) => { + let ident = &cnst.ident; + quote!({#ident}) + } + }); + + quote! { + // TODO(#553): Add a test that generates a warning when + // `#[allow(deprecated)]` isn't present. + #[allow(deprecated)] + unsafe impl < #(#params),* > ::zerocopy::#trait_ident for #type_ident < #(#param_idents),* > + where + #(#bounds,)* + { + fn only_derive_is_allowed_to_implement_this_trait() {} + + #extras + } + } +} + +fn print_all_errors(errors: Vec<Error>) -> proc_macro2::TokenStream { + errors.iter().map(Error::to_compile_error).collect() +} + +// A polyfill for `Option::then_some`, which was added after our MSRV. +// +// TODO(#67): Remove this once our MSRV is >= 1.62. +trait BoolExt { + fn then_some<T>(self, t: T) -> Option<T>; +} + +impl BoolExt for bool { + fn then_some<T>(self, t: T) -> Option<T> { + if self { + Some(t) + } else { + None + } + } +} + +#[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<T: Clone + Ord>(ts: &[T]) -> bool { + let mut sorted = ts.to_vec(); + sorted.sort(); + sorted.dedup(); + ts == sorted.as_slice() + } + + fn elements_are_sorted_and_deduped<T: Clone + Ord>(lists: &[&[T]]) -> bool { + lists.iter().all(|list| is_sorted_and_deduped(list)) + } + + fn config_is_sorted<T: KindRepr + Clone>(config: &Config<T>) -> 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<T: Eq>(a: &[T], b: &[T]) -> bool { + a.iter().any(|elem| b.contains(elem)) + } + + fn config_overlaps<T: KindRepr + Eq>(config: &Config<T>) -> 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)); + } +} |