Skip to content

Commit

Permalink
Update repr parsing logic to handle multiple attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
djkoloski committed Sep 8, 2024
1 parent 47faccb commit a53150d
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 162 deletions.
7 changes: 0 additions & 7 deletions bytecheck_derive/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use syn::{
WherePredicate,
};

use crate::repr::Repr;

fn try_set_attribute<T: ToTokens>(
attribute: &mut Option<T>,
value: T,
Expand All @@ -25,7 +23,6 @@ fn try_set_attribute<T: ToTokens>(

#[derive(Default)]
pub struct Attributes {
pub repr: Repr,
pub bounds: Option<Punctuated<WherePredicate, Token![,]>>,
crate_path: Option<Path>,
pub verify: Option<Path>,
Expand Down Expand Up @@ -78,10 +75,6 @@ impl Attributes {
attr.parse_nested_meta(|nested| {
result.parse_check_bytes_attributes(nested)
})?;
} else if attr.path().is_ident("repr") {
attr.parse_nested_meta(|nested| {
result.repr.parse_list_meta(nested)
})?;
}
}

Expand Down
27 changes: 14 additions & 13 deletions bytecheck_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use syn::{

use crate::{
attributes::{Attributes, FieldAttributes},
repr::BaseRepr,
repr::Repr,
util::iter_fields,
};

Expand Down Expand Up @@ -273,27 +273,28 @@ fn derive_check_bytes(mut input: DeriveInput) -> Result<TokenStream, Error> {
}
},
Data::Enum(ref data) => {
let repr = match attributes.repr.base_repr {
None => {
let repr = Repr::from_attrs(&input.attrs)?;
let primitive = match repr {
Repr::Transparent => {
return Err(Error::new_spanned(
name,
"enums implementing CheckBytes must have an explicit \
repr",
"enums cannot be repr(transparent)",
))
}
Some((BaseRepr::Transparent, _)) => {
Repr::Primitive(i) => i,
Repr::C { .. } => {
return Err(Error::new_spanned(
name,
"enums cannot be repr(transparent)",
"repr(C) enums are not currently supported",
))
}
Some((BaseRepr::C, _)) => {
Repr::Rust { .. } => {
return Err(Error::new_spanned(
name,
"repr(C) enums are not currently supported",
"enums implementing CheckBytes must have an explicit \
repr",
))
}
Some((BaseRepr::Int(i), _)) => i,
};

let tag_variant_defs = data.variants.iter().map(|v| {
Expand All @@ -309,7 +310,7 @@ fn derive_check_bytes(mut input: DeriveInput) -> Result<TokenStream, Error> {
let variant = &v.ident;
quote! {
#[allow(non_upper_case_globals)]
const #variant: #repr = Tag::#variant as #repr;
const #variant: #primitive = Tag::#variant as #primitive;
}
});

Expand Down Expand Up @@ -415,7 +416,7 @@ fn derive_check_bytes(mut input: DeriveInput) -> Result<TokenStream, Error> {

quote! {
const _: () = {
#[repr(#repr)]
#[repr(#primitive)]
enum Tag {
#(#tag_variant_defs,)*
}
Expand Down Expand Up @@ -448,7 +449,7 @@ fn derive_check_bytes(mut input: DeriveInput) -> Result<TokenStream, Error> {
(),
<__C as #crate_path::rancor::Fallible>::Error,
> {
let tag = *value.cast::<#repr>();
let tag = *value.cast::<#primitive>();
match tag {
#(#tag_variant_values => #check_arms)*
_ => #no_matching_tag_arm,
Expand Down
245 changes: 103 additions & 142 deletions bytecheck_derive/src/repr.rs
Original file line number Diff line number Diff line change
@@ -1,176 +1,137 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
meta::ParseNestedMeta, parenthesized, spanned::Spanned, Error, LitInt,
};
use quote::{quote, ToTokens};
use syn::{parenthesized, token, Attribute, Error, Ident, LitInt};

#[derive(Clone, Copy)]
pub enum IntRepr {
pub enum Primitive {
I8,
I16,
I32,
I64,
I128,
Isize,
U8,
U16,
U32,
U64,
U128,
Usize,
}

impl ToTokens for IntRepr {
fn to_tokens(&self, tokens: &mut TokenStream) {
impl Primitive {
const ALL: [Self; 10] = [
Self::I8,
Self::I16,
Self::I32,
Self::I64,
Self::Isize,
Self::U8,
Self::U16,
Self::U32,
Self::U64,
Self::Usize,
];

pub const fn as_str(&self) -> &'static str {
match self {
Self::I8 => tokens.append_all(quote! { i8 }),
Self::I16 => tokens.append_all(quote! { i16 }),
Self::I32 => tokens.append_all(quote! { i32 }),
Self::I64 => tokens.append_all(quote! { i64 }),
Self::I128 => tokens.append_all(quote! { i128 }),
Self::U8 => tokens.append_all(quote! { u8 }),
Self::U16 => tokens.append_all(quote! { u16 }),
Self::U32 => tokens.append_all(quote! { u32 }),
Self::U64 => tokens.append_all(quote! { u64 }),
Self::U128 => tokens.append_all(quote! { u128 }),
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::Isize => "isize",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::Usize => "usize",
}
}
}

#[derive(Clone, Copy)]
pub enum BaseRepr {
C,
// structs only
Transparent,
// enums only
Int(IntRepr),
}

impl ToTokens for BaseRepr {
impl ToTokens for Primitive {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
BaseRepr::C => tokens.append_all(quote! { C }),
BaseRepr::Transparent => tokens.append_all(quote! { transparent }),
BaseRepr::Int(int_repr) => tokens.append_all(quote! { #int_repr }),
}
let ident = Ident::new(self.as_str(), Span::call_site());
tokens.extend(quote! { #ident });
}
}

#[derive(Clone)]
pub enum Modifier {
// structs only
Packed,
Align(LitInt),
Packed(#[allow(dead_code)] usize),
Align(#[allow(dead_code)] usize),
}

impl ToTokens for Modifier {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Modifier::Packed => tokens.append_all(quote! { packed }),
Modifier::Align(n) => tokens.append_all(quote! { align(#n) }),
}
}
}

#[derive(Clone, Default)]
pub struct Repr {
pub base_repr: Option<(BaseRepr, Span)>,
pub modifier: Option<(Modifier, Span)>,
pub enum Repr {
Transparent,
Primitive(Primitive),
C {
#[allow(dead_code)]
primitive: Option<Primitive>,
#[allow(dead_code)]
modifier: Option<Modifier>,
},
Rust {
#[allow(dead_code)]
modifier: Option<Modifier>,
},
}

impl Repr {
fn try_set_modifier<S: ToTokens>(
&mut self,
modifier: Modifier,
spanned: S,
) -> Result<(), Error> {
if self.modifier.is_some() {
Err(Error::new_spanned(
spanned,
"only one repr modifier may be specified",
))
} else {
self.modifier = Some((modifier, spanned.span()));
Ok(())
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self, Error> {
let mut c = false;
let mut transparent = false;
let mut primitive = None;
let mut modifier = None;

for attr in attrs.iter().filter(|a| a.meta.path().is_ident("repr")) {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("C") {
c = true;
Ok(())
} else if meta.path.is_ident("transparent") {
transparent = true;
Ok(())
} else if let Some(&p) = Primitive::ALL
.iter()
.find(|p| meta.path.is_ident(p.as_str()))
{
primitive = Some(p);
Ok(())
} else if meta.path.is_ident("align") {
let content;
parenthesized!(content in meta.input);
let lit = content.parse::<LitInt>()?;
let n = lit.base10_parse()?;
modifier = Some(Modifier::Align(n));
Ok(())
} else if meta.path.is_ident("packed") {
if meta.input.peek(token::Paren) {
let content;
parenthesized!(content in meta.input);
let lit = content.parse::<LitInt>()?;
let n = lit.base10_parse()?;
modifier = Some(Modifier::Packed(n));
} else {
modifier = Some(Modifier::Packed(1));
}
Ok(())
} else {
Err(Error::new_spanned(
meta.path,
"unrecognized repr argument",
))
}
})?;
}
}

fn try_set_base_repr<S: ToTokens>(
&mut self,
repr: BaseRepr,
spanned: S,
) -> Result<(), Error> {
if self.base_repr.is_some() {
Err(Error::new_spanned(
spanned,
"only one repr may be specified",
))
if c {
Ok(Repr::C {
primitive,
modifier,
})
} else if transparent {
Ok(Repr::Transparent)
} else if let Some(primitive) = primitive {
Ok(Repr::Primitive(primitive))
} else {
self.base_repr = Some((repr, spanned.span()));
Ok(())
Ok(Repr::Rust { modifier })
}
}

pub fn parse_list_meta(
&mut self,
meta: ParseNestedMeta<'_>,
) -> Result<(), Error> {
if meta.path.is_ident("packed") {
return self.try_set_modifier(Modifier::Packed, meta.path);
} else if meta.path.is_ident("align") {
let content;
parenthesized!(content in meta.input);
let alignment = content.parse()?;

if !content.is_empty() {
return Err(content.error("align requires only one argument"));
}

return self
.try_set_modifier(Modifier::Align(alignment), meta.path);
}

let parsed_repr = if meta.path.is_ident("transparent") {
BaseRepr::Transparent
} else if meta.path.is_ident("C") {
BaseRepr::C
} else if meta.path.is_ident("i8") {
BaseRepr::Int(IntRepr::I8)
} else if meta.path.is_ident("i16") {
BaseRepr::Int(IntRepr::I16)
} else if meta.path.is_ident("i32") {
BaseRepr::Int(IntRepr::I32)
} else if meta.path.is_ident("i64") {
BaseRepr::Int(IntRepr::I64)
} else if meta.path.is_ident("i128") {
BaseRepr::Int(IntRepr::I128)
} else if meta.path.is_ident("u8") {
BaseRepr::Int(IntRepr::U8)
} else if meta.path.is_ident("u16") {
BaseRepr::Int(IntRepr::U16)
} else if meta.path.is_ident("u32") {
BaseRepr::Int(IntRepr::U32)
} else if meta.path.is_ident("u64") {
BaseRepr::Int(IntRepr::U64)
} else if meta.path.is_ident("u128") {
BaseRepr::Int(IntRepr::U128)
} else {
let msg =
"invalid repr, available reprs are transparent, C, i* and u*";

return Err(Error::new_spanned(meta.path, msg));
};

self.try_set_base_repr(parsed_repr, meta.path)
}
}

impl ToTokens for Repr {
fn to_tokens(&self, tokens: &mut TokenStream) {
let base_repr = self.base_repr.as_ref().map(|(b, _)| b);
let base_repr_iter = base_repr.iter();
let modifier = self.modifier.as_ref().map(|(m, _)| m);
let modifier_iter = modifier.iter();
tokens.append_all(
quote! { #[repr(#(#base_repr_iter,)* #(#modifier_iter,)*)] },
);
}
}

0 comments on commit a53150d

Please sign in to comment.