mod split;
use proc_macro2::{Ident, TokenStream};
use proc_macro_error2::{abort, abort_call_site};
use quote::quote;
use split::SplitAttributes;
use syn::{punctuated::Iter, spanned::Spanned, Fields, Item, ItemEnum, ItemStruct, Type, Variant};
use crate::shared::{self, enum_fills_bitsize, is_fallback_attribute, unreachable, BitSize, MAX_ENUM_BIT_SIZE};
struct ItemIr {
expanded: TokenStream,
}
pub(super) fn bitsize(args: TokenStream, item: TokenStream) -> TokenStream {
let (item, declared_bitsize) = parse(item, args);
let attrs = SplitAttributes::from_item(&item);
let ir = match item {
Item::Struct(mut item) => {
modify_special_field_names(&mut item.fields);
analyze_struct(&item.fields);
let expanded = generate_struct(&item, declared_bitsize);
ItemIr { expanded }
}
Item::Enum(item) => {
analyze_enum(declared_bitsize, item.variants.iter());
let expanded = generate_enum(&item);
ItemIr { expanded }
}
_ => unreachable(()),
};
generate_common(ir, attrs, declared_bitsize)
}
fn parse(item: TokenStream, args: TokenStream) -> (Item, BitSize) {
let item = syn::parse2(item).unwrap_or_else(unreachable);
if args.is_empty() {
abort_call_site!("missing attribute value"; help = "you need to define the size like this: `#[bitsize(32)]`")
}
let (declared_bitsize, _arb_int) = shared::bitsize_and_arbitrary_int_from(args);
(item, declared_bitsize)
}
fn check_type_is_supported(ty: &Type) {
use Type::*;
match ty {
Tuple(tuple) => tuple.elems.iter().for_each(check_type_is_supported),
Array(array) => check_type_is_supported(&array.elem),
Path(_) => (),
BareFn(_) | Group(_) | ImplTrait(_) | Infer(_) | Macro(_) | Never(_) |
Ptr(_) | Reference(_) |
Slice(_) |
TraitObject(_) |
Verbatim(_) | Paren(_) => abort!(ty, "This field type is not supported"),
_ => abort!(ty, "This field type is currently not supported"),
}
}
fn modify_special_field_names(fields: &mut Fields) {
let mut reserved_count = 0;
let mut padding_count = 0;
let field_idents_mut = fields.iter_mut().filter_map(|field| field.ident.as_mut());
for ident in field_idents_mut {
if ident == "reserved" || ident == "_reserved" {
reserved_count += 1;
let span = ident.span();
let name = format!("reserved_{}", "i".repeat(reserved_count));
*ident = Ident::new(&name, span)
} else if ident == "padding" || ident == "_padding" {
padding_count += 1;
let span = ident.span();
let name = format!("padding_{}", "i".repeat(padding_count));
*ident = Ident::new(&name, span)
}
}
}
fn analyze_struct(fields: &Fields) {
if fields.is_empty() {
abort_call_site!("structs without fields are not supported")
}
for field in fields {
check_type_is_supported(&field.ty)
}
}
fn analyze_enum(bitsize: BitSize, variants: Iter<Variant>) {
if bitsize > MAX_ENUM_BIT_SIZE {
abort_call_site!("enum bitsize is limited to {}", MAX_ENUM_BIT_SIZE)
}
let variant_count = variants.clone().count();
if variant_count == 0 {
abort_call_site!("empty enums are not supported");
}
let has_fallback = variants.flat_map(|variant| &variant.attrs).any(is_fallback_attribute);
if !has_fallback {
let _ = enum_fills_bitsize(bitsize, variant_count);
}
}
fn generate_struct(item: &ItemStruct, declared_bitsize: u8) -> TokenStream {
let ItemStruct { vis, ident, fields, .. } = item;
let declared_bitsize = declared_bitsize as usize;
let computed_bitsize = fields.iter().fold(quote!(0), |acc, next| {
let field_size = shared::generate_type_bitsize(&next.ty);
quote!(#acc + #field_size)
});
let is_tuple_struct = fields.iter().any(|field| field.ident.is_none());
let fields_def = if is_tuple_struct {
let fields = fields.iter();
quote! {
( #(#fields,)* );
}
} else {
let fields = fields.iter();
quote! {
{ #(#fields,)* }
}
};
quote! {
#vis struct #ident #fields_def
const _: () = assert!(
(#computed_bitsize) == (#declared_bitsize),
concat!("struct size and declared bit size differ: ",
" != ",
stringify!(#declared_bitsize))
);
}
}
fn generate_enum(item: &ItemEnum) -> TokenStream {
let ItemEnum { vis, ident, variants, .. } = item;
quote! {
#vis enum #ident {
#variants
}
}
}
fn generate_common(ir: ItemIr, attrs: SplitAttributes, declared_bitsize: u8) -> TokenStream {
let ItemIr { expanded } = ir;
let SplitAttributes {
before_compression,
after_compression,
} = attrs;
let bitsize_internal_attr = quote! {#[::bilge::bitsize_internal(#declared_bitsize)]};
quote! {
#(#before_compression)*
#bitsize_internal_attr
#(#after_compression)*
#expanded
}
}