// Copyright 2019 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 // , or the MIT // license , 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>`, 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::(&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, elems: usize, ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull { 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::(); // 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, _elems: usize, ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull { bytes.cast::() } ), ) }; 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::().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 = { 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 = 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 = 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 = { 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 = 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 = 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 = { 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 for RequireBoundedFields { fn from(do_require: bool) -> Self { match do_require { true => Self::Yes, false => Self::No, } } } fn impl_block( input: &DeriveInput, data: &D, trt: Trait, require_trait_bound_on_field_types: RequireBoundedFields, require_self_sized: bool, padding_check: Option, extras: Option, ) -> proc_macro2::TokenStream { // In this documentation, we will refer to this hypothetical struct: // // #[derive(FromBytes)] // struct Foo // 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 FromBytes for Foo // 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 ) }); 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) -> 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(self, t: T) -> Option; } impl BoolExt for bool { fn then_some(self, t: T) -> Option { 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(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)); } }