pub mod bitsize_args;
pub mod discriminant_assigner;
pub mod fallback;
pub mod util;
pub use bitsize_args::{BitsizeArgs, internal_attr_options, parse_bitsize_args};
use fallback::{Fallback, fallback_variant};
use manyhow::{bail, ensure};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use syn::{Attribute, DeriveInput, 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,
) -> manyhow::Result<(&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) {
bail!("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) {
bail!("Using #[non_exhaustive] on structs is currently not supported"; help = "open an issue on our repository if needed")
}
}
}
ensure!(
let Some(args) = attrs.iter().find_map(bitsize_internal_arg),
"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 {
bail!("fallback is not allowed with `TryFromBits`"; help = "use `#[derive(FromBits)]` or remove this `#[fallback]`")
}
Ok((data, arb_int, ident, bitsize, fallback))
}
pub fn bitsize_and_arbitrary_int_from(bitsize_arg: TokenStream) -> manyhow::Result<(BitSize, TokenStream)> {
let args = parse_bitsize_args(bitsize_arg)?;
Ok((args.bitsize, args.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) -> manyhow::Result<bool> {
let max_variants_count = 1u128 << bitsize;
if variants_count as u128 > max_variants_count {
bail!("enum overflows its bitsize"; help = "there should only be at most {} variants defined", max_variants_count);
}
Ok(variants_count as u128 == max_variants_count)
}
#[inline]
pub fn unreachable<T, U>(_: T) -> U {
::core::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 if let Some(suffix) = type_name.strip_prefix('i') {
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
}