pub mod discriminant_assigner;
pub mod fallback;
pub mod util;
use fallback::{fallback_variant, Fallback};
use proc_macro2::{Ident, Literal, TokenStream};
use proc_macro_error2::{abort, abort_call_site};
use quote::quote;
use syn::{Attribute, DeriveInput, LitInt, Meta, Type};
use util::PathExt;
pub const MAX_STRUCT_BIT_SIZE: BitSize = 128;
pub const MAX_ENUM_BIT_SIZE: BitSize = 64;
pub type BitSize = u8;
pub(crate) fn parse_derive(item: TokenStream) -> DeriveInput {
syn::parse2(item).unwrap_or_else(unreachable)
}
#[allow(clippy::collapsible_if)]
pub(crate) fn analyze_derive(derive_input: &DeriveInput, try_from: bool) -> (&syn::Data, TokenStream, &Ident, BitSize, Option<Fallback>) {
let DeriveInput {
attrs,
ident,
data,
..
} = derive_input;
if !try_from {
if attrs.iter().any(is_non_exhaustive_attribute) {
abort_call_site!("Item can't be FromBits and non_exhaustive"; help = "remove #[non_exhaustive] or derive(FromBits) here")
}
} else {
if let syn::Data::Struct(_) = data {
if attrs.iter().any(is_non_exhaustive_attribute) {
abort_call_site!("Using #[non_exhaustive] on structs is currently not supported"; help = "open an issue on our repository if needed")
}
}
}
let args = attrs
.iter()
.find_map(bitsize_internal_arg)
.unwrap_or_else(|| abort_call_site!("add #[bitsize] attribute above your derive attribute"));
let (bitsize, arb_int) = bitsize_and_arbitrary_int_from(args);
let fallback = fallback_variant(data, bitsize);
if fallback.is_some() && try_from {
abort_call_site!("fallback is not allowed with `TryFromBits`"; help = "use `#[derive(FromBits)]` or remove this `#[fallback]`")
}
(data, arb_int, ident, bitsize, fallback)
}
pub fn bitsize_and_arbitrary_int_from(bitsize_arg: TokenStream) -> (BitSize, TokenStream) {
let bitsize: LitInt = syn::parse2(bitsize_arg.clone())
.unwrap_or_else(|_| abort!(bitsize_arg, "attribute value is not a number"; help = "you need to define the size like this: `#[bitsize(32)]`"));
let bitsize = bitsize
.base10_parse()
.ok()
.filter(|&n| n != 0 && n <= MAX_STRUCT_BIT_SIZE)
.unwrap_or_else(|| abort!(bitsize_arg, "attribute value is not a valid number"; help = "currently, numbers from 1 to {} are allowed", MAX_STRUCT_BIT_SIZE));
let arb_int = syn::parse_str(&format!("u{bitsize}")).unwrap_or_else(unreachable);
(bitsize, arb_int)
}
pub fn generate_type_bitsize(ty: &Type) -> TokenStream {
use Type::*;
match ty {
Tuple(tuple) => {
tuple
.elems
.iter()
.map(generate_type_bitsize)
.reduce(|acc, next| quote!((#acc + #next)))
.unwrap_or_else(|| quote!(0))
}
Array(array) => {
let elem_bitsize = generate_type_bitsize(&array.elem);
let len_expr = &array.len;
quote!((#elem_bitsize * #len_expr))
}
Path(_) => {
quote!(<#ty as Bitsized>::BITS)
}
_ => unreachable(()),
}
}
pub(crate) fn generate_from_enum_impl(
arb_int: &TokenStream, enum_type: &Ident, to_int_match_arms: Vec<TokenStream>, const_: &TokenStream,
) -> TokenStream {
quote! {
impl #const_ ::core::convert::From<#enum_type> for #arb_int {
fn from(enum_value: #enum_type) -> Self {
match enum_value {
#( #to_int_match_arms )*
}
}
}
}
}
pub fn is_always_filled(ty: &Type) -> bool {
last_ident_of_path(ty).and_then(bitsize_from_type_ident).is_some()
}
pub fn last_ident_of_path(ty: &Type) -> Option<&Ident> {
if let Type::Path(type_path) = ty {
let last_segment = type_path.path.segments.last()?;
Some(&last_segment.ident)
} else {
None
}
}
pub fn enum_fills_bitsize(bitsize: u8, variants_count: usize) -> bool {
let max_variants_count = 1u128 << bitsize;
if variants_count as u128 > max_variants_count {
abort_call_site!("enum overflows its bitsize"; help = "there should only be at most {} variants defined", max_variants_count);
}
variants_count as u128 == max_variants_count
}
#[inline]
pub fn unreachable<T, U>(_: T) -> U {
unreachable!("should have already been validated")
}
pub fn is_attribute(attr: &Attribute, name: &str) -> bool {
if let Meta::Path(path) = &attr.meta {
path.is_ident(name)
} else {
false
}
}
fn is_non_exhaustive_attribute(attr: &Attribute) -> bool {
is_attribute(attr, "non_exhaustive")
}
pub(crate) fn is_fallback_attribute(attr: &Attribute) -> bool {
is_attribute(attr, "fallback")
}
pub fn bitsize_from_type_ident(type_name: &Ident) -> Option<BitSize> {
let type_name = type_name.to_string();
if type_name == "bool" {
Some(1)
} else if let Some(suffix) = type_name.strip_prefix('u') {
let bitsize = suffix.parse().ok();
bitsize.filter(|&n| n <= MAX_STRUCT_BIT_SIZE)
} else {
None
}
}
pub fn to_int_match_arm(enum_name: &Ident, variant_name: &Ident, arb_int: &TokenStream, variant_value: Literal) -> TokenStream {
quote! { #enum_name::#variant_name => #arb_int::new(#variant_value), }
}
pub(crate) fn bitsize_internal_arg(attr: &Attribute) -> Option<TokenStream> {
if let Meta::List(list) = &attr.meta {
if list.path.matches(&["bilge", "bitsize_internal"]) {
let arg = list.tokens.to_owned();
return Some(arg);
}
}
None
}