| #![allow(unused_imports)] | |
| use std::{cmp, convert::TryFrom}; | |
| use proc_macro2::{Ident, Span, TokenStream, TokenTree}; | |
| use quote::{quote, ToTokens}; | |
| use syn::{ | |
| parse::{Parse, ParseStream, Parser}, | |
| punctuated::Punctuated, | |
| spanned::Spanned, | |
| Result, *, | |
| }; | |
| macro_rules! bail { | |
| ($msg:expr $(,)?) => { | |
| return Err(Error::new(Span::call_site(), &$msg[..])) | |
| }; | |
| ( $msg:expr => $span_to_blame:expr $(,)? ) => { | |
| return Err(Error::new_spanned(&$span_to_blame, $msg)) | |
| }; | |
| } | |
| pub trait Derivable { | |
| fn ident(input: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path>; | |
| fn implies_trait(_crate_name: &TokenStream) -> Option<TokenStream> { | |
| None | |
| } | |
| fn asserts( | |
| _input: &DeriveInput, _crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| Ok(quote!()) | |
| } | |
| fn check_attributes(_ty: &Data, _attributes: &[Attribute]) -> Result<()> { | |
| Ok(()) | |
| } | |
| fn trait_impl( | |
| _input: &DeriveInput, _crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| Ok((quote!(), quote!())) | |
| } | |
| fn requires_where_clause() -> bool { | |
| true | |
| } | |
| fn explicit_bounds_attribute_name() -> Option<&'static str> { | |
| None | |
| } | |
| /// If this trait has a custom meaning for "perfect derive", this function | |
| /// should be overridden to return `Some`. | |
| /// | |
| /// The default is "the fields of a struct; unions and enums not supported". | |
| fn perfect_derive_fields(_input: &DeriveInput) -> Option<Fields> { | |
| None | |
| } | |
| } | |
| pub struct Pod; | |
| impl Derivable for Pod { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::Pod)) | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| let repr = get_repr(&input.attrs)?; | |
| let completly_packed = | |
| repr.packed == Some(1) || repr.repr == Repr::Transparent; | |
| if !completly_packed && !input.generics.params.is_empty() { | |
| bail!("\ | |
| Pod requires cannot be derived for non-packed types containing \ | |
| generic parameters because the padding requirements can't be verified \ | |
| for generic non-packed structs\ | |
| " => input.generics.params.first().unwrap()); | |
| } | |
| match &input.data { | |
| Data::Struct(_) => { | |
| let assert_no_padding = if !completly_packed { | |
| Some(generate_assert_no_padding(input)?) | |
| } else { | |
| None | |
| }; | |
| let assert_fields_are_pod = generate_fields_are_trait( | |
| input, | |
| None, | |
| Self::ident(input, crate_name)?, | |
| )?; | |
| Ok(quote!( | |
| #assert_no_padding | |
| #assert_fields_are_pod | |
| )) | |
| } | |
| Data::Enum(_) => bail!("Deriving Pod is not supported for enums"), | |
| Data::Union(_) => bail!("Deriving Pod is not supported for unions"), | |
| } | |
| } | |
| fn check_attributes(_ty: &Data, attributes: &[Attribute]) -> Result<()> { | |
| let repr = get_repr(attributes)?; | |
| match repr.repr { | |
| Repr::C => Ok(()), | |
| Repr::Transparent => Ok(()), | |
| _ => { | |
| bail!("Pod requires the type to be #[repr(C)] or #[repr(transparent)]") | |
| } | |
| } | |
| } | |
| } | |
| pub struct AnyBitPattern; | |
| impl Derivable for AnyBitPattern { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::AnyBitPattern)) | |
| } | |
| fn implies_trait(crate_name: &TokenStream) -> Option<TokenStream> { | |
| Some(quote!(#crate_name::Zeroable)) | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| match &input.data { | |
| Data::Union(_) => Ok(quote!()), // unions are always `AnyBitPattern` | |
| Data::Struct(_) => { | |
| generate_fields_are_trait(input, None, Self::ident(input, crate_name)?) | |
| } | |
| Data::Enum(_) => { | |
| bail!("Deriving AnyBitPattern is not supported for enums") | |
| } | |
| } | |
| } | |
| } | |
| pub struct Zeroable; | |
| /// Helper function to get the variant with discriminant zero (implicit or | |
| /// explicit). | |
| fn get_zero_variant(enum_: &DataEnum) -> Result<Option<&Variant>> { | |
| let iter = VariantDiscriminantIterator::new(enum_.variants.iter()); | |
| let mut zero_variant = None; | |
| for res in iter { | |
| let (discriminant, variant) = res?; | |
| if discriminant == 0 { | |
| zero_variant = Some(variant); | |
| break; | |
| } | |
| } | |
| Ok(zero_variant) | |
| } | |
| impl Derivable for Zeroable { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::Zeroable)) | |
| } | |
| fn check_attributes(ty: &Data, attributes: &[Attribute]) -> Result<()> { | |
| let repr = get_repr(attributes)?; | |
| match ty { | |
| Data::Struct(_) => Ok(()), | |
| Data::Enum(_) => { | |
| if !matches!( | |
| repr.repr, | |
| Repr::C | Repr::Integer(_) | Repr::CWithDiscriminant(_) | |
| ) { | |
| bail!("Zeroable requires the enum to be an explicit #[repr(Int)] and/or #[repr(C)]") | |
| } | |
| // We ensure there is a zero variant in `asserts`, since it is needed | |
| // there anyway. | |
| Ok(()) | |
| } | |
| Data::Union(_) => Ok(()), | |
| } | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| match &input.data { | |
| Data::Union(_) => Ok(quote!()), // unions are always `Zeroable` | |
| Data::Struct(_) => { | |
| generate_fields_are_trait(input, None, Self::ident(input, crate_name)?) | |
| } | |
| Data::Enum(enum_) => { | |
| let zero_variant = get_zero_variant(enum_)?; | |
| if zero_variant.is_none() { | |
| bail!("No variant's discriminant is 0") | |
| }; | |
| generate_fields_are_trait( | |
| input, | |
| zero_variant, | |
| Self::ident(input, crate_name)?, | |
| ) | |
| } | |
| } | |
| } | |
| fn explicit_bounds_attribute_name() -> Option<&'static str> { | |
| Some("zeroable") | |
| } | |
| fn perfect_derive_fields(input: &DeriveInput) -> Option<Fields> { | |
| match &input.data { | |
| Data::Struct(struct_) => Some(struct_.fields.clone()), | |
| Data::Enum(enum_) => { | |
| // We handle `Err` returns from `get_zero_variant` in `asserts`, so it's | |
| // fine to just ignore them here and return `None`. | |
| // Otherwise, we clone the `fields` of the zero variant (if any). | |
| Some(get_zero_variant(enum_).ok()??.fields.clone()) | |
| } | |
| Data::Union(_) => None, | |
| } | |
| } | |
| } | |
| pub struct NoUninit; | |
| impl Derivable for NoUninit { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::NoUninit)) | |
| } | |
| fn check_attributes(ty: &Data, attributes: &[Attribute]) -> Result<()> { | |
| let repr = get_repr(attributes)?; | |
| match ty { | |
| Data::Struct(_) => match repr.repr { | |
| Repr::C | Repr::Transparent => Ok(()), | |
| _ => bail!("NoUninit requires the struct to be #[repr(C)] or #[repr(transparent)]"), | |
| }, | |
| Data::Enum(_) => if repr.repr.is_integer() { | |
| Ok(()) | |
| } else { | |
| bail!("NoUninit requires the enum to be an explicit #[repr(Int)]") | |
| }, | |
| Data::Union(_) => bail!("NoUninit can only be derived on enums and structs") | |
| } | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| if !input.generics.params.is_empty() { | |
| bail!("NoUninit cannot be derived for structs containing generic parameters because the padding requirements can't be verified for generic structs"); | |
| } | |
| match &input.data { | |
| Data::Struct(DataStruct { .. }) => { | |
| let assert_no_padding = generate_assert_no_padding(&input)?; | |
| let assert_fields_are_no_padding = generate_fields_are_trait( | |
| &input, | |
| None, | |
| Self::ident(input, crate_name)?, | |
| )?; | |
| Ok(quote!( | |
| #assert_no_padding | |
| #assert_fields_are_no_padding | |
| )) | |
| } | |
| Data::Enum(DataEnum { variants, .. }) => { | |
| if variants.iter().any(|variant| !variant.fields.is_empty()) { | |
| bail!("Only fieldless enums are supported for NoUninit") | |
| } else { | |
| Ok(quote!()) | |
| } | |
| } | |
| Data::Union(_) => bail!("NoUninit cannot be derived for unions"), /* shouldn't be possible since we already error in attribute check for this case */ | |
| } | |
| } | |
| fn trait_impl( | |
| _input: &DeriveInput, _crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| Ok((quote!(), quote!())) | |
| } | |
| } | |
| pub struct CheckedBitPattern; | |
| impl Derivable for CheckedBitPattern { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::CheckedBitPattern)) | |
| } | |
| fn check_attributes(ty: &Data, attributes: &[Attribute]) -> Result<()> { | |
| let repr = get_repr(attributes)?; | |
| match ty { | |
| Data::Struct(_) => match repr.repr { | |
| Repr::C | Repr::Transparent => Ok(()), | |
| _ => bail!("CheckedBitPattern derive requires the struct to be #[repr(C)] or #[repr(transparent)]"), | |
| }, | |
| Data::Enum(DataEnum { variants,.. }) => { | |
| if !enum_has_fields(variants.iter()){ | |
| if repr.repr.is_integer() { | |
| Ok(()) | |
| } else { | |
| bail!("CheckedBitPattern requires the enum to be an explicit #[repr(Int)]") | |
| } | |
| } else if matches!(repr.repr, Repr::Rust) { | |
| bail!("CheckedBitPattern requires an explicit repr annotation because `repr(Rust)` doesn't have a specified type layout") | |
| } else { | |
| Ok(()) | |
| } | |
| } | |
| Data::Union(_) => bail!("CheckedBitPattern can only be derived on enums and structs") | |
| } | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| if !input.generics.params.is_empty() { | |
| bail!("CheckedBitPattern cannot be derived for structs containing generic parameters"); | |
| } | |
| match &input.data { | |
| Data::Struct(DataStruct { .. }) => { | |
| let assert_fields_are_maybe_pod = generate_fields_are_trait( | |
| &input, | |
| None, | |
| Self::ident(input, crate_name)?, | |
| )?; | |
| Ok(assert_fields_are_maybe_pod) | |
| } | |
| // nothing needed, already guaranteed OK by NoUninit. | |
| Data::Enum(_) => Ok(quote!()), | |
| Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), /* shouldn't be possible since we already error in attribute check for this case */ | |
| } | |
| } | |
| fn trait_impl( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| match &input.data { | |
| Data::Struct(DataStruct { fields, .. }) => { | |
| generate_checked_bit_pattern_struct( | |
| &input.ident, | |
| fields, | |
| &input.attrs, | |
| crate_name, | |
| ) | |
| } | |
| Data::Enum(DataEnum { variants, .. }) => { | |
| generate_checked_bit_pattern_enum(input, variants, crate_name) | |
| } | |
| Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), /* shouldn't be possible since we already error in attribute check for this case */ | |
| } | |
| } | |
| } | |
| pub struct TransparentWrapper; | |
| impl TransparentWrapper { | |
| fn get_wrapper_type( | |
| attributes: &[Attribute], fields: &Fields, | |
| ) -> Option<TokenStream> { | |
| let transparent_param = get_simple_attr(attributes, "transparent"); | |
| transparent_param.map(|ident| ident.to_token_stream()).or_else(|| { | |
| let mut types = get_field_types(&fields); | |
| let first_type = types.next(); | |
| if let Some(_) = types.next() { | |
| // can't guess param type if there is more than one field | |
| return None; | |
| } else { | |
| first_type.map(|ty| ty.to_token_stream()) | |
| } | |
| }) | |
| } | |
| } | |
| impl Derivable for TransparentWrapper { | |
| fn ident(input: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| let fields = get_struct_fields(input)?; | |
| let ty = match Self::get_wrapper_type(&input.attrs, &fields) { | |
| Some(ty) => ty, | |
| None => bail!( | |
| "\ | |
| when deriving TransparentWrapper for a struct with more than one field \ | |
| you need to specify the transparent field using #[transparent(T)]\ | |
| " | |
| ), | |
| }; | |
| Ok(syn::parse_quote!(#crate_name::TransparentWrapper<#ty>)) | |
| } | |
| fn asserts( | |
| input: &DeriveInput, crate_name: &TokenStream, | |
| ) -> Result<TokenStream> { | |
| let (impl_generics, _ty_generics, where_clause) = | |
| input.generics.split_for_impl(); | |
| let fields = get_struct_fields(input)?; | |
| let wrapped_type = match Self::get_wrapper_type(&input.attrs, &fields) { | |
| Some(wrapped_type) => wrapped_type.to_string(), | |
| None => unreachable!(), /* other code will already reject this derive */ | |
| }; | |
| let mut wrapped_field_ty = None; | |
| let mut nonwrapped_field_tys = vec![]; | |
| for field in fields.iter() { | |
| let field_ty = &field.ty; | |
| if field_ty.to_token_stream().to_string() == wrapped_type { | |
| if wrapped_field_ty.is_some() { | |
| bail!( | |
| "TransparentWrapper can only have one field of the wrapped type" | |
| ); | |
| } | |
| wrapped_field_ty = Some(field_ty); | |
| } else { | |
| nonwrapped_field_tys.push(field_ty); | |
| } | |
| } | |
| if let Some(wrapped_field_ty) = wrapped_field_ty { | |
| Ok(quote!( | |
| const _: () = { | |
| #[repr(transparent)] | |
| #[allow(clippy::multiple_bound_locations)] | |
| struct AssertWrappedIsWrapped #impl_generics((u8, ::core::marker::PhantomData<#wrapped_field_ty>), #(#nonwrapped_field_tys),*) #where_clause; | |
| fn assert_zeroable<Z: #crate_name::Zeroable>() {} | |
| #[allow(clippy::multiple_bound_locations)] | |
| fn check #impl_generics () #where_clause { | |
| #( | |
| assert_zeroable::<#nonwrapped_field_tys>(); | |
| )* | |
| } | |
| }; | |
| )) | |
| } else { | |
| bail!("TransparentWrapper must have one field of the wrapped type") | |
| } | |
| } | |
| fn check_attributes(_ty: &Data, attributes: &[Attribute]) -> Result<()> { | |
| let repr = get_repr(attributes)?; | |
| match repr.repr { | |
| Repr::Transparent => Ok(()), | |
| _ => { | |
| bail!( | |
| "TransparentWrapper requires the struct to be #[repr(transparent)]" | |
| ) | |
| } | |
| } | |
| } | |
| fn requires_where_clause() -> bool { | |
| false | |
| } | |
| } | |
| pub struct Contiguous; | |
| impl Derivable for Contiguous { | |
| fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> { | |
| Ok(syn::parse_quote!(#crate_name::Contiguous)) | |
| } | |
| fn trait_impl( | |
| input: &DeriveInput, _crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| let repr = get_repr(&input.attrs)?; | |
| let integer_ty = if let Some(integer_ty) = repr.repr.as_integer() { | |
| integer_ty | |
| } else { | |
| bail!("Contiguous requires the enum to be #[repr(Int)]"); | |
| }; | |
| let variants = get_enum_variants(input)?; | |
| if enum_has_fields(variants.clone()) { | |
| return Err(Error::new_spanned( | |
| &input, | |
| "Only fieldless enums are supported", | |
| )); | |
| } | |
| let mut variants_with_discriminant = | |
| VariantDiscriminantIterator::new(variants); | |
| let (min, max, count) = variants_with_discriminant.try_fold( | |
| (i128::MAX, i128::MIN, 0), | |
| |(min, max, count), res| { | |
| let (discriminant, _variant) = res?; | |
| Ok::<_, Error>(( | |
| i128::min(min, discriminant), | |
| i128::max(max, discriminant), | |
| count + 1, | |
| )) | |
| }, | |
| )?; | |
| if max - min != count - 1 { | |
| bail! { | |
| "Contiguous requires the enum discriminants to be contiguous", | |
| } | |
| } | |
| let min_lit = LitInt::new(&format!("{}", min), input.span()); | |
| let max_lit = LitInt::new(&format!("{}", max), input.span()); | |
| // `from_integer` and `into_integer` are usually provided by the trait's | |
| // default implementation. We override this implementation because it | |
| // goes through `transmute_copy`, which can lead to inefficient assembly as seen in https://github.com/Lokathor/bytemuck/issues/175 . | |
| Ok(( | |
| quote!(), | |
| quote! { | |
| type Int = #integer_ty; | |
| #[allow(clippy::missing_docs_in_private_items)] | |
| const MIN_VALUE: #integer_ty = #min_lit; | |
| #[allow(clippy::missing_docs_in_private_items)] | |
| const MAX_VALUE: #integer_ty = #max_lit; | |
| #[inline] | |
| fn from_integer(value: Self::Int) -> Option<Self> { | |
| #[allow(clippy::manual_range_contains)] | |
| if Self::MIN_VALUE <= value && value <= Self::MAX_VALUE { | |
| Some(unsafe { ::core::mem::transmute(value) }) | |
| } else { | |
| None | |
| } | |
| } | |
| #[inline] | |
| fn into_integer(self) -> Self::Int { | |
| self as #integer_ty | |
| } | |
| }, | |
| )) | |
| } | |
| } | |
| fn get_struct_fields(input: &DeriveInput) -> Result<&Fields> { | |
| if let Data::Struct(DataStruct { fields, .. }) = &input.data { | |
| Ok(fields) | |
| } else { | |
| bail!("deriving this trait is only supported for structs") | |
| } | |
| } | |
| /// Extract the `Fields` off a `DeriveInput`, or, in the `enum` case, off | |
| /// those of the `enum_variant`, when provided (e.g., for `Zeroable`). | |
| /// | |
| /// We purposely allow not providing an `enum_variant` for cases where | |
| /// the caller wants to reject supporting `enum`s (e.g., `NoPadding`). | |
| fn get_fields( | |
| input: &DeriveInput, enum_variant: Option<&Variant>, | |
| ) -> Result<Fields> { | |
| match &input.data { | |
| Data::Struct(DataStruct { fields, .. }) => Ok(fields.clone()), | |
| Data::Union(DataUnion { fields, .. }) => Ok(Fields::Named(fields.clone())), | |
| Data::Enum(_) => match enum_variant { | |
| Some(variant) => Ok(variant.fields.clone()), | |
| None => bail!("deriving this trait is not supported for enums"), | |
| }, | |
| } | |
| } | |
| fn get_enum_variants<'a>( | |
| input: &'a DeriveInput, | |
| ) -> Result<impl Iterator<Item = &'a Variant> + Clone + 'a> { | |
| if let Data::Enum(DataEnum { variants, .. }) = &input.data { | |
| Ok(variants.iter()) | |
| } else { | |
| bail!("deriving this trait is only supported for enums") | |
| } | |
| } | |
| fn get_field_types<'a>( | |
| fields: &'a Fields, | |
| ) -> impl Iterator<Item = &'a Type> + 'a { | |
| fields.iter().map(|field| &field.ty) | |
| } | |
| fn generate_checked_bit_pattern_struct( | |
| input_ident: &Ident, fields: &Fields, attrs: &[Attribute], | |
| crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| let bits_ty = Ident::new(&format!("{}Bits", input_ident), input_ident.span()); | |
| let repr = get_repr(attrs)?; | |
| let field_names = fields | |
| .iter() | |
| .enumerate() | |
| .map(|(i, field)| { | |
| field.ident.clone().unwrap_or_else(|| { | |
| Ident::new(&format!("field{}", i), input_ident.span()) | |
| }) | |
| }) | |
| .collect::<Vec<_>>(); | |
| let field_tys = fields.iter().map(|field| &field.ty).collect::<Vec<_>>(); | |
| let field_name = &field_names[..]; | |
| let field_ty = &field_tys[..]; | |
| Ok(( | |
| quote! { | |
| #[doc = #GENERATED_TYPE_DOCUMENTATION] | |
| #repr | |
| #[derive(Clone, Copy, #crate_name::AnyBitPattern)] | |
| #[allow(missing_docs)] | |
| pub struct #bits_ty { | |
| #(#field_name: <#field_ty as #crate_name::CheckedBitPattern>::Bits,)* | |
| } | |
| #[allow(unexpected_cfgs)] | |
| const _: () = { | |
| #[cfg(not(target_arch = "spirv"))] | |
| impl ::core::fmt::Debug for #bits_ty { | |
| fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | |
| let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#bits_ty)); | |
| #(::core::fmt::DebugStruct::field(&mut debug_struct, ::core::stringify!(#field_name), &self.#field_name);)* | |
| ::core::fmt::DebugStruct::finish(&mut debug_struct) | |
| } | |
| } | |
| }; | |
| }, | |
| quote! { | |
| type Bits = #bits_ty; | |
| #[inline] | |
| #[allow(clippy::double_comparisons, unused)] | |
| fn is_valid_bit_pattern(bits: &#bits_ty) -> bool { | |
| #(<#field_ty as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(&{ bits.#field_name }) && )* true | |
| } | |
| }, | |
| )) | |
| } | |
| fn generate_checked_bit_pattern_enum( | |
| input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>, | |
| crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| if enum_has_fields(variants.iter()) { | |
| generate_checked_bit_pattern_enum_with_fields(input, variants, crate_name) | |
| } else { | |
| generate_checked_bit_pattern_enum_without_fields(input, variants) | |
| } | |
| } | |
| fn generate_checked_bit_pattern_enum_without_fields( | |
| input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| let span = input.span(); | |
| let mut variants_with_discriminant = | |
| VariantDiscriminantIterator::new(variants.iter()); | |
| let (min, max, count) = variants_with_discriminant.try_fold( | |
| (i128::MAX, i128::MIN, 0), | |
| |(min, max, count), res| { | |
| let (discriminant, _variant) = res?; | |
| Ok::<_, Error>(( | |
| i128::min(min, discriminant), | |
| i128::max(max, discriminant), | |
| count + 1, | |
| )) | |
| }, | |
| )?; | |
| let check = if count == 0 { | |
| quote!(false) | |
| } else if max - min == count - 1 { | |
| // contiguous range | |
| let min_lit = LitInt::new(&format!("{}", min), span); | |
| let max_lit = LitInt::new(&format!("{}", max), span); | |
| quote!(*bits >= #min_lit && *bits <= #max_lit) | |
| } else { | |
| // not contiguous range, check for each | |
| let variant_discriminant_lits = | |
| VariantDiscriminantIterator::new(variants.iter()) | |
| .map(|res| { | |
| let (discriminant, _variant) = res?; | |
| Ok(LitInt::new(&format!("{}", discriminant), span)) | |
| }) | |
| .collect::<Result<Vec<_>>>()?; | |
| // count is at least 1 | |
| let first = &variant_discriminant_lits[0]; | |
| let rest = &variant_discriminant_lits[1..]; | |
| quote!(matches!(*bits, #first #(| #rest )*)) | |
| }; | |
| let repr = get_repr(&input.attrs)?; | |
| let integer = repr.repr.as_integer().unwrap(); // should be checked in attr check already | |
| Ok(( | |
| quote!(), | |
| quote! { | |
| type Bits = #integer; | |
| #[inline] | |
| #[allow(clippy::double_comparisons)] | |
| fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { | |
| #check | |
| } | |
| }, | |
| )) | |
| } | |
| fn generate_checked_bit_pattern_enum_with_fields( | |
| input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>, | |
| crate_name: &TokenStream, | |
| ) -> Result<(TokenStream, TokenStream)> { | |
| let representation = get_repr(&input.attrs)?; | |
| let vis = &input.vis; | |
| match representation.repr { | |
| Repr::Rust => unreachable!(), | |
| repr @ (Repr::C | Repr::CWithDiscriminant(_)) => { | |
| let integer = match repr { | |
| Repr::C => quote!(::core::ffi::c_int), | |
| Repr::CWithDiscriminant(integer) => quote!(#integer), | |
| _ => unreachable!(), | |
| }; | |
| let input_ident = &input.ident; | |
| let bits_repr = Representation { repr: Repr::C, ..representation }; | |
| // the enum manually re-configured as the actual tagged union it | |
| // represents, thus circumventing the requirements rust imposes on | |
| // the tag even when using #[repr(C)] enum layout | |
| // see: https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields | |
| let bits_ty_ident = | |
| Ident::new(&format!("{input_ident}Bits"), input.span()); | |
| // the variants union part of the tagged union. These get put into a union | |
| // which gets the AnyBitPattern derive applied to it, thus checking | |
| // that the fields of the union obey the requriements of AnyBitPattern. | |
| // The types that actually go in the union are one more level of | |
| // indirection deep: we generate new structs for each variant | |
| // (`variant_struct_definitions`) which themselves have the | |
| // `CheckedBitPattern` derive applied, thus generating | |
| // `{variant_struct_ident}Bits` structs, which are the ones that go | |
| // into this union. | |
| let variants_union_ident = | |
| Ident::new(&format!("{}Variants", input.ident), input.span()); | |
| let variant_struct_idents = variants.iter().map(|v| { | |
| Ident::new(&format!("{input_ident}Variant{}", v.ident), v.span()) | |
| }); | |
| let variant_struct_definitions = | |
| variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| { | |
| let fields = v.fields.iter().map(|v| &v.ty); | |
| quote! { | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)] | |
| #[repr(C)] | |
| #vis struct #variant_struct_ident(#(#fields),*); | |
| } | |
| }); | |
| let union_fields = variant_struct_idents | |
| .clone() | |
| .zip(variants.iter()) | |
| .map(|(variant_struct_ident, v)| { | |
| let variant_struct_bits_ident = | |
| Ident::new(&format!("{variant_struct_ident}Bits"), input.span()); | |
| let field_ident = &v.ident; | |
| quote! { | |
| #field_ident: #variant_struct_bits_ident | |
| } | |
| }); | |
| let variant_checks = variant_struct_idents | |
| .clone() | |
| .zip(VariantDiscriminantIterator::new(variants.iter())) | |
| .zip(variants.iter()) | |
| .map(|((variant_struct_ident, discriminant), v)| -> Result<_> { | |
| let (discriminant, _variant) = discriminant?; | |
| let discriminant = LitInt::new(&discriminant.to_string(), v.span()); | |
| let ident = &v.ident; | |
| Ok(quote! { | |
| #discriminant => { | |
| let payload = unsafe { &bits.payload.#ident }; | |
| <#variant_struct_ident as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(payload) | |
| } | |
| }) | |
| }) | |
| .collect::<Result<Vec<_>>>()?; | |
| Ok(( | |
| quote! { | |
| #[doc = #GENERATED_TYPE_DOCUMENTATION] | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)] | |
| #bits_repr | |
| #vis struct #bits_ty_ident { | |
| tag: #integer, | |
| payload: #variants_union_ident, | |
| } | |
| #[allow(unexpected_cfgs)] | |
| const _: () = { | |
| #[cfg(not(target_arch = "spirv"))] | |
| impl ::core::fmt::Debug for #bits_ty_ident { | |
| fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | |
| let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#bits_ty_ident)); | |
| ::core::fmt::DebugStruct::field(&mut debug_struct, "tag", &self.tag); | |
| ::core::fmt::DebugStruct::field(&mut debug_struct, "payload", &self.payload); | |
| ::core::fmt::DebugStruct::finish(&mut debug_struct) | |
| } | |
| } | |
| }; | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)] | |
| #[repr(C)] | |
| #[allow(non_snake_case)] | |
| #vis union #variants_union_ident { | |
| #(#union_fields,)* | |
| } | |
| #[allow(unexpected_cfgs)] | |
| const _: () = { | |
| #[cfg(not(target_arch = "spirv"))] | |
| impl ::core::fmt::Debug for #variants_union_ident { | |
| fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | |
| let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#variants_union_ident)); | |
| ::core::fmt::DebugStruct::finish_non_exhaustive(&mut debug_struct) | |
| } | |
| } | |
| }; | |
| #(#variant_struct_definitions)* | |
| }, | |
| quote! { | |
| type Bits = #bits_ty_ident; | |
| #[inline] | |
| #[allow(clippy::double_comparisons)] | |
| fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { | |
| match bits.tag { | |
| #(#variant_checks)* | |
| _ => false, | |
| } | |
| } | |
| }, | |
| )) | |
| } | |
| Repr::Transparent => { | |
| if variants.len() != 1 { | |
| bail!("enums with more than one variant cannot be transparent") | |
| } | |
| let variant = &variants[0]; | |
| let bits_ty = Ident::new(&format!("{}Bits", input.ident), input.span()); | |
| let fields = variant.fields.iter().map(|v| &v.ty); | |
| Ok(( | |
| quote! { | |
| #[doc = #GENERATED_TYPE_DOCUMENTATION] | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)] | |
| #[repr(C)] | |
| #vis struct #bits_ty(#(#fields),*); | |
| }, | |
| quote! { | |
| type Bits = <#bits_ty as #crate_name::CheckedBitPattern>::Bits; | |
| #[inline] | |
| #[allow(clippy::double_comparisons)] | |
| fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { | |
| <#bits_ty as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(bits) | |
| } | |
| }, | |
| )) | |
| } | |
| Repr::Integer(integer) => { | |
| let bits_repr = Representation { repr: Repr::C, ..representation }; | |
| let input_ident = &input.ident; | |
| // the enum manually re-configured as the union it represents. such a | |
| // union is the union of variants as a repr(c) struct with the | |
| // discriminator type inserted at the beginning. in our case we | |
| // union the `Bits` representation of each variant rather than the variant | |
| // itself, which we generate via a nested `CheckedBitPattern` derive | |
| // on the `variant_struct_definitions` generated below. | |
| // | |
| // see: https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields | |
| let bits_ty_ident = | |
| Ident::new(&format!("{input_ident}Bits"), input.span()); | |
| let variant_struct_idents = variants.iter().map(|v| { | |
| Ident::new(&format!("{input_ident}Variant{}", v.ident), v.span()) | |
| }); | |
| let variant_struct_definitions = | |
| variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| { | |
| let fields = v.fields.iter().map(|v| &v.ty); | |
| // adding the discriminant repr integer as first field, as described above | |
| quote! { | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)] | |
| #[repr(C)] | |
| #vis struct #variant_struct_ident(#integer, #(#fields),*); | |
| } | |
| }); | |
| let union_fields = variant_struct_idents | |
| .clone() | |
| .zip(variants.iter()) | |
| .map(|(variant_struct_ident, v)| { | |
| let variant_struct_bits_ident = | |
| Ident::new(&format!("{variant_struct_ident}Bits"), input.span()); | |
| let field_ident = &v.ident; | |
| quote! { | |
| #field_ident: #variant_struct_bits_ident | |
| } | |
| }); | |
| let variant_checks = variant_struct_idents | |
| .clone() | |
| .zip(VariantDiscriminantIterator::new(variants.iter())) | |
| .zip(variants.iter()) | |
| .map(|((variant_struct_ident, discriminant), v)| -> Result<_> { | |
| let (discriminant, _variant) = discriminant?; | |
| let discriminant = LitInt::new(&discriminant.to_string(), v.span()); | |
| let ident = &v.ident; | |
| Ok(quote! { | |
| #discriminant => { | |
| let payload = unsafe { &bits.#ident }; | |
| <#variant_struct_ident as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(payload) | |
| } | |
| }) | |
| }) | |
| .collect::<Result<Vec<_>>>()?; | |
| Ok(( | |
| quote! { | |
| #[doc = #GENERATED_TYPE_DOCUMENTATION] | |
| #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)] | |
| #bits_repr | |
| #[allow(non_snake_case)] | |
| #vis union #bits_ty_ident { | |
| __tag: #integer, | |
| #(#union_fields,)* | |
| } | |
| #[allow(unexpected_cfgs)] | |
| const _: () = { | |
| #[cfg(not(target_arch = "spirv"))] | |
| impl ::core::fmt::Debug for #bits_ty_ident { | |
| fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { | |
| let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#bits_ty_ident)); | |
| ::core::fmt::DebugStruct::field(&mut debug_struct, "tag", unsafe { &self.__tag }); | |
| ::core::fmt::DebugStruct::finish_non_exhaustive(&mut debug_struct) | |
| } | |
| } | |
| }; | |
| #(#variant_struct_definitions)* | |
| }, | |
| quote! { | |
| type Bits = #bits_ty_ident; | |
| #[inline] | |
| #[allow(clippy::double_comparisons)] | |
| fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { | |
| match unsafe { bits.__tag } { | |
| #(#variant_checks)* | |
| _ => false, | |
| } | |
| } | |
| }, | |
| )) | |
| } | |
| } | |
| } | |
| /// Check that a struct has no padding by asserting that the size of the struct | |
| /// is equal to the sum of the size of it's fields | |
| fn generate_assert_no_padding(input: &DeriveInput) -> Result<TokenStream> { | |
| let struct_type = &input.ident; | |
| let enum_variant = None; // `no padding` check is not supported for `enum`s yet. | |
| let fields = get_fields(input, enum_variant)?; | |
| let mut field_types = get_field_types(&fields); | |
| let size_sum = if let Some(first) = field_types.next() { | |
| let size_first = quote!(::core::mem::size_of::<#first>()); | |
| let size_rest = quote!(#( + ::core::mem::size_of::<#field_types>() )*); | |
| quote!(#size_first #size_rest) | |
| } else { | |
| quote!(0) | |
| }; | |
| Ok(quote! {const _: fn() = || { | |
| #[doc(hidden)] | |
| struct TypeWithoutPadding([u8; #size_sum]); | |
| let _ = ::core::mem::transmute::<#struct_type, TypeWithoutPadding>; | |
| };}) | |
| } | |
| /// Check that all fields implement a given trait | |
| fn generate_fields_are_trait( | |
| input: &DeriveInput, enum_variant: Option<&Variant>, trait_: syn::Path, | |
| ) -> Result<TokenStream> { | |
| let (impl_generics, _ty_generics, where_clause) = | |
| input.generics.split_for_impl(); | |
| let fields = get_fields(input, enum_variant)?; | |
| let field_types = get_field_types(&fields); | |
| Ok(quote! {#(const _: fn() = || { | |
| #[allow(clippy::missing_const_for_fn)] | |
| #[doc(hidden)] | |
| fn check #impl_generics () #where_clause { | |
| fn assert_impl<T: #trait_>() {} | |
| assert_impl::<#field_types>(); | |
| } | |
| };)* | |
| }) | |
| } | |
| fn get_ident_from_stream(tokens: TokenStream) -> Option<Ident> { | |
| match tokens.into_iter().next() { | |
| Some(TokenTree::Group(group)) => get_ident_from_stream(group.stream()), | |
| Some(TokenTree::Ident(ident)) => Some(ident), | |
| _ => None, | |
| } | |
| } | |
| /// get a simple #[foo(bar)] attribute, returning "bar" | |
| fn get_simple_attr(attributes: &[Attribute], attr_name: &str) -> Option<Ident> { | |
| for attr in attributes { | |
| if let (AttrStyle::Outer, Meta::List(list)) = (&attr.style, &attr.meta) { | |
| if list.path.is_ident(attr_name) { | |
| if let Some(ident) = get_ident_from_stream(list.tokens.clone()) { | |
| return Some(ident); | |
| } | |
| } | |
| } | |
| } | |
| None | |
| } | |
| fn get_repr(attributes: &[Attribute]) -> Result<Representation> { | |
| attributes | |
| .iter() | |
| .filter_map(|attr| { | |
| if attr.path().is_ident("repr") { | |
| Some(attr.parse_args::<Representation>()) | |
| } else { | |
| None | |
| } | |
| }) | |
| .try_fold(Representation::default(), |a, b| { | |
| let b = b?; | |
| Ok(Representation { | |
| repr: match (a.repr, b.repr) { | |
| (a, Repr::Rust) => a, | |
| (Repr::Rust, b) => b, | |
| _ => bail!("conflicting representation hints"), | |
| }, | |
| packed: match (a.packed, b.packed) { | |
| (a, None) => a, | |
| (None, b) => b, | |
| _ => bail!("conflicting representation hints"), | |
| }, | |
| align: match (a.align, b.align) { | |
| (Some(a), Some(b)) => Some(cmp::max(a, b)), | |
| (a, None) => a, | |
| (None, b) => b, | |
| }, | |
| }) | |
| }) | |
| } | |
| mk_repr! { | |
| U8 => u8, | |
| I8 => i8, | |
| U16 => u16, | |
| I16 => i16, | |
| U32 => u32, | |
| I32 => i32, | |
| U64 => u64, | |
| I64 => i64, | |
| I128 => i128, | |
| U128 => u128, | |
| Usize => usize, | |
| Isize => isize, | |
| } | |
| // where | |
| macro_rules! mk_repr {( | |
| $( | |
| $Xn:ident => $xn:ident | |
| ),* $(,)? | |
| ) => ( | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| enum IntegerRepr { | |
| $($Xn),* | |
| } | |
| impl<'a> TryFrom<&'a str> for IntegerRepr { | |
| type Error = &'a str; | |
| fn try_from(value: &'a str) -> std::result::Result<Self, &'a str> { | |
| match value { | |
| $( | |
| stringify!($xn) => Ok(Self::$Xn), | |
| )* | |
| _ => Err(value), | |
| } | |
| } | |
| } | |
| impl ToTokens for IntegerRepr { | |
| fn to_tokens(&self, tokens: &mut TokenStream) { | |
| match self { | |
| $( | |
| Self::$Xn => tokens.extend(quote!($xn)), | |
| )* | |
| } | |
| } | |
| } | |
| )} | |
| use mk_repr; | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| enum Repr { | |
| Rust, | |
| C, | |
| Transparent, | |
| Integer(IntegerRepr), | |
| CWithDiscriminant(IntegerRepr), | |
| } | |
| impl Repr { | |
| fn is_integer(&self) -> bool { | |
| matches!(self, Self::Integer(..)) | |
| } | |
| fn as_integer(&self) -> Option<IntegerRepr> { | |
| if let Self::Integer(v) = self { | |
| Some(*v) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| struct Representation { | |
| packed: Option<u32>, | |
| align: Option<u32>, | |
| repr: Repr, | |
| } | |
| impl Default for Representation { | |
| fn default() -> Self { | |
| Self { packed: None, align: None, repr: Repr::Rust } | |
| } | |
| } | |
| impl Parse for Representation { | |
| fn parse(input: ParseStream<'_>) -> Result<Representation> { | |
| let mut ret = Representation::default(); | |
| while !input.is_empty() { | |
| let keyword = input.parse::<Ident>()?; | |
| // preëmptively call `.to_string()` *once* (rather than on `is_ident()`) | |
| let keyword_str = keyword.to_string(); | |
| let new_repr = match keyword_str.as_str() { | |
| "C" => Repr::C, | |
| "transparent" => Repr::Transparent, | |
| "packed" => { | |
| ret.packed = Some(if input.peek(token::Paren) { | |
| let contents; | |
| parenthesized!(contents in input); | |
| LitInt::base10_parse::<u32>(&contents.parse()?)? | |
| } else { | |
| 1 | |
| }); | |
| let _: Option<Token![,]> = input.parse()?; | |
| continue; | |
| } | |
| "align" => { | |
| let contents; | |
| parenthesized!(contents in input); | |
| let new_align = LitInt::base10_parse::<u32>(&contents.parse()?)?; | |
| ret.align = Some( | |
| ret | |
| .align | |
| .map_or(new_align, |old_align| cmp::max(old_align, new_align)), | |
| ); | |
| let _: Option<Token![,]> = input.parse()?; | |
| continue; | |
| } | |
| ident => { | |
| let primitive = IntegerRepr::try_from(ident) | |
| .map_err(|_| input.error("unrecognized representation hint"))?; | |
| Repr::Integer(primitive) | |
| } | |
| }; | |
| ret.repr = match (ret.repr, new_repr) { | |
| (Repr::Rust, new_repr) => { | |
| // This is the first explicit repr. | |
| new_repr | |
| } | |
| (Repr::C, Repr::Integer(integer)) | |
| | (Repr::Integer(integer), Repr::C) => { | |
| // Both the C repr and an integer repr have been specified | |
| // -> merge into a C wit discriminant. | |
| Repr::CWithDiscriminant(integer) | |
| } | |
| (_, _) => { | |
| return Err(input.error("duplicate representation hint")); | |
| } | |
| }; | |
| let _: Option<Token![,]> = input.parse()?; | |
| } | |
| Ok(ret) | |
| } | |
| } | |
| impl ToTokens for Representation { | |
| fn to_tokens(&self, tokens: &mut TokenStream) { | |
| let mut meta = Punctuated::<_, Token![,]>::new(); | |
| match self.repr { | |
| Repr::Rust => {} | |
| Repr::C => meta.push(quote!(C)), | |
| Repr::Transparent => meta.push(quote!(transparent)), | |
| Repr::Integer(primitive) => meta.push(quote!(#primitive)), | |
| Repr::CWithDiscriminant(primitive) => { | |
| meta.push(quote!(C)); | |
| meta.push(quote!(#primitive)); | |
| } | |
| } | |
| if let Some(packed) = self.packed.as_ref() { | |
| let lit = LitInt::new(&packed.to_string(), Span::call_site()); | |
| meta.push(quote!(packed(#lit))); | |
| } | |
| if let Some(align) = self.align.as_ref() { | |
| let lit = LitInt::new(&align.to_string(), Span::call_site()); | |
| meta.push(quote!(align(#lit))); | |
| } | |
| tokens.extend(quote!( | |
| #[repr(#meta)] | |
| )); | |
| } | |
| } | |
| fn enum_has_fields<'a>( | |
| mut variants: impl Iterator<Item = &'a Variant>, | |
| ) -> bool { | |
| variants.any(|v| matches!(v.fields, Fields::Named(_) | Fields::Unnamed(_))) | |
| } | |
| struct VariantDiscriminantIterator<'a, I: Iterator<Item = &'a Variant> + 'a> { | |
| inner: I, | |
| last_value: i128, | |
| } | |
| impl<'a, I: Iterator<Item = &'a Variant> + 'a> | |
| VariantDiscriminantIterator<'a, I> | |
| { | |
| fn new(inner: I) -> Self { | |
| VariantDiscriminantIterator { inner, last_value: -1 } | |
| } | |
| } | |
| impl<'a, I: Iterator<Item = &'a Variant> + 'a> Iterator | |
| for VariantDiscriminantIterator<'a, I> | |
| { | |
| type Item = Result<(i128, &'a Variant)>; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| let variant = self.inner.next()?; | |
| if let Some((_, discriminant)) = &variant.discriminant { | |
| let discriminant_value = match parse_int_expr(discriminant) { | |
| Ok(value) => value, | |
| Err(e) => return Some(Err(e)), | |
| }; | |
| self.last_value = discriminant_value; | |
| } else { | |
| // If this wraps, then either: | |
| // 1. the enum is using repr(u128), so wrapping is correct | |
| // 2. the enum is using repr(i<=128 or u<128), so the compiler will | |
| // already emit a "wrapping discriminant" E0370 error. | |
| self.last_value = self.last_value.wrapping_add(1); | |
| // Static assert that there is no integer repr > 128 bits. If that | |
| // changes, the above comment is inaccurate and needs to be updated! | |
| // FIXME(zachs18): maybe should also do something to ensure `isize::BITS | |
| // <= 128`? | |
| if let Some(repr) = None::<IntegerRepr> { | |
| match repr { | |
| IntegerRepr::U8 | |
| | IntegerRepr::I8 | |
| | IntegerRepr::U16 | |
| | IntegerRepr::I16 | |
| | IntegerRepr::U32 | |
| | IntegerRepr::I32 | |
| | IntegerRepr::U64 | |
| | IntegerRepr::I64 | |
| | IntegerRepr::I128 | |
| | IntegerRepr::U128 | |
| | IntegerRepr::Usize | |
| | IntegerRepr::Isize => (), | |
| } | |
| } | |
| } | |
| Some(Ok((self.last_value, variant))) | |
| } | |
| } | |
| fn parse_int_expr(expr: &Expr) -> Result<i128> { | |
| match expr { | |
| Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }) => { | |
| parse_int_expr(expr).map(|int| -int) | |
| } | |
| Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => int.base10_parse(), | |
| Expr::Lit(ExprLit { lit: Lit::Byte(byte), .. }) => Ok(byte.value().into()), | |
| _ => bail!("Not an integer expression"), | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use syn::parse_quote; | |
| use super::{get_repr, IntegerRepr, Repr, Representation}; | |
| #[test] | |
| fn parse_basic_repr() { | |
| let attr = parse_quote!(#[repr(C)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { repr: Repr::C, ..Default::default() }); | |
| let attr = parse_quote!(#[repr(transparent)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!( | |
| repr, | |
| Representation { repr: Repr::Transparent, ..Default::default() } | |
| ); | |
| let attr = parse_quote!(#[repr(u8)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!( | |
| repr, | |
| Representation { | |
| repr: Repr::Integer(IntegerRepr::U8), | |
| ..Default::default() | |
| } | |
| ); | |
| let attr = parse_quote!(#[repr(packed)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { packed: Some(1), ..Default::default() }); | |
| let attr = parse_quote!(#[repr(packed(1))]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { packed: Some(1), ..Default::default() }); | |
| let attr = parse_quote!(#[repr(packed(2))]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { packed: Some(2), ..Default::default() }); | |
| let attr = parse_quote!(#[repr(align(2))]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { align: Some(2), ..Default::default() }); | |
| } | |
| #[test] | |
| fn parse_advanced_repr() { | |
| let attr = parse_quote!(#[repr(align(4), align(2))]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!(repr, Representation { align: Some(4), ..Default::default() }); | |
| let attr1 = parse_quote!(#[repr(align(1))]); | |
| let attr2 = parse_quote!(#[repr(align(4))]); | |
| let attr3 = parse_quote!(#[repr(align(2))]); | |
| let repr = get_repr(&[attr1, attr2, attr3]).unwrap(); | |
| assert_eq!(repr, Representation { align: Some(4), ..Default::default() }); | |
| let attr = parse_quote!(#[repr(C, u8)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!( | |
| repr, | |
| Representation { | |
| repr: Repr::CWithDiscriminant(IntegerRepr::U8), | |
| ..Default::default() | |
| } | |
| ); | |
| let attr = parse_quote!(#[repr(u8, C)]); | |
| let repr = get_repr(&[attr]).unwrap(); | |
| assert_eq!( | |
| repr, | |
| Representation { | |
| repr: Repr::CWithDiscriminant(IntegerRepr::U8), | |
| ..Default::default() | |
| } | |
| ); | |
| } | |
| } | |
| pub fn bytemuck_crate_name(input: &DeriveInput) -> TokenStream { | |
| const ATTR_NAME: &'static str = "crate"; | |
| let mut crate_name = quote!(::bytemuck); | |
| for attr in &input.attrs { | |
| if !attr.path().is_ident("bytemuck") { | |
| continue; | |
| } | |
| attr.parse_nested_meta(|meta| { | |
| if meta.path.is_ident(ATTR_NAME) { | |
| let expr: syn::Expr = meta.value()?.parse()?; | |
| let mut value = &expr; | |
| while let syn::Expr::Group(e) = value { | |
| value = &e.expr; | |
| } | |
| if let syn::Expr::Lit(syn::ExprLit { | |
| lit: syn::Lit::Str(lit), .. | |
| }) = value | |
| { | |
| let suffix = lit.suffix(); | |
| if !suffix.is_empty() { | |
| bail!(format!("Unexpected suffix `{}` on string literal", suffix)) | |
| } | |
| let path: syn::Path = match lit.parse() { | |
| Ok(path) => path, | |
| Err(_) => { | |
| bail!(format!("Failed to parse path: {:?}", lit.value())) | |
| } | |
| }; | |
| crate_name = path.into_token_stream(); | |
| } else { | |
| bail!( | |
| "Expected bytemuck `crate` attribute to be a string: `crate = \"...\"`", | |
| ) | |
| } | |
| } | |
| Ok(()) | |
| }).unwrap(); | |
| } | |
| return crate_name; | |
| } | |
| const GENERATED_TYPE_DOCUMENTATION: &str = | |
| " `bytemuck`-generated type for internal purposes only."; |