use proc_macro_error2::{abort, abort_call_site};
use quote::ToTokens;
use syn::{meta::ParseNestedMeta, parse_quote, Attribute, Item, Meta, Path};
use crate::shared::{unreachable, util::PathExt};
pub struct SplitAttributes {
pub before_compression: Vec<Attribute>,
pub after_compression: Vec<Attribute>,
}
impl SplitAttributes {
pub fn from_item(item: &Item) -> SplitAttributes {
let attrs = match item {
Item::Enum(item) => &item.attrs,
Item::Struct(item) => &item.attrs,
_ => abort_call_site!("item is not a struct or enum"; help = "`#[bitsize]` can only be used on structs and enums"),
};
let parsed = attrs.iter().map(parse_attribute);
let is_struct = matches!(item, Item::Struct(..));
let mut from_bytes = None;
let mut has_frombits = false;
let mut before_compression = vec![];
let mut after_compression = vec![];
for parsed_attr in parsed {
match parsed_attr {
ParsedAttribute::DeriveList(derives) => {
for mut derive in derives {
if derive.matches(&["zerocopy", "FromBytes"]) {
from_bytes = Some(derive.clone());
} else if derive.matches(&["bilge", "FromBits"]) {
has_frombits = true;
} else if derive.matches_core_or_std(&["fmt", "Debug"]) && is_struct {
abort!(derive.0, "use derive(DebugBits) for structs")
} else if derive.matches_core_or_std(&["default", "Default"]) && is_struct {
derive.0 = syn::parse_quote!(::bilge::DefaultBits);
}
if derive.is_custom_bitfield_derive() {
before_compression.push(derive.into_attribute());
} else {
after_compression.push(derive.into_attribute());
}
}
}
ParsedAttribute::BitsizeInternal(attr) => {
abort!(attr, "remove bitsize_internal"; help = "attribute bitsize_internal can only be applied internally by the bitsize macros")
}
ParsedAttribute::Other(attr) => {
after_compression.push(attr.to_owned())
}
};
}
if let Some(from_bytes) = from_bytes {
if !has_frombits {
abort!(from_bytes.0, "a bitfield with zerocopy::FromBytes also needs to have FromBits")
}
}
if !is_struct {
before_compression.append(&mut after_compression)
}
SplitAttributes {
before_compression,
after_compression,
}
}
}
fn parse_attribute(attribute: &Attribute) -> ParsedAttribute<'_> {
match &attribute.meta {
Meta::List(list) if list.path.is_ident("derive") => {
let mut derives = Vec::new();
let add_derive = |meta: ParseNestedMeta| {
let derive = Derive(meta.path);
derives.push(derive);
Ok(())
};
list.parse_nested_meta(add_derive)
.unwrap_or_else(|e| abort!(list.tokens, "failed to parse derive: {}", e));
ParsedAttribute::DeriveList(derives)
}
meta if contains_anywhere(meta, "bitsize_internal") => ParsedAttribute::BitsizeInternal(attribute),
_ => ParsedAttribute::Other(attribute),
}
}
enum ParsedAttribute<'attr> {
DeriveList(Vec<Derive>),
BitsizeInternal(&'attr Attribute),
Other(&'attr Attribute),
}
#[derive(Clone)]
struct Derive(Path);
impl Derive {
fn into_attribute(self) -> Attribute {
let path = self.0;
parse_quote! { #[derive(#path)] }
}
fn is_custom_bitfield_derive(&self) -> bool {
let last_segment = self.0.segments.last().unwrap_or_else(|| unreachable(()));
last_segment.ident.to_string().ends_with("Bits")
}
}
impl PathExt for Derive {
fn matches(&self, str_segments: &[&str]) -> bool {
self.0.matches(str_segments)
}
}
fn contains_anywhere(meta: &Meta, ident: &str) -> bool {
meta.to_token_stream().to_string().contains(ident)
}