Revision control
Copy as Markdown
Other Tools
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// This file may not be copied, modified, or distributed except according to
// those terms.
//! Derive macros for [zerocopy]'s traits.
//!
// 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(),
}
};
}
// 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
// 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:
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
//
// 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.
// `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));
}
}