use super::{
config::{Config, ReprKind},
field_config::{FieldConfig, SkipWhich},
raise_skip_error, BitfieldStruct,
};
use core::convert::TryFrom;
use quote::quote;
use syn::{self, parse::Result, punctuated::Punctuated, spanned::Spanned as _};
impl TryFrom<(&mut Config, syn::ItemStruct)> for BitfieldStruct {
type Error = syn::Error;
fn try_from((config, item_struct): (&mut Config, syn::ItemStruct)) -> Result<Self> {
Self::ensure_has_fields(&item_struct)?;
Self::ensure_valid_generics(&item_struct)?;
Self::extract_attributes(&item_struct.attrs, config)?;
Self::analyse_config_for_fields(&item_struct, config)?;
config.ensure_no_conflicts()?;
Ok(Self { item_struct })
}
}
impl BitfieldStruct {
fn ensure_has_fields(item_struct: &syn::ItemStruct) -> Result<()> {
if matches!(&item_struct.fields, syn::Fields::Unit)
|| matches!(&item_struct.fields, syn::Fields::Unnamed(f) if f.unnamed.is_empty())
|| matches!(&item_struct.fields, syn::Fields::Named(f) if f.named.is_empty())
{
return Err(format_err_spanned!(
item_struct,
"encountered invalid bitfield struct without fields"
));
}
Ok(())
}
fn ensure_valid_generics(item_struct: &syn::ItemStruct) -> Result<()> {
if item_struct.generics.type_params().next().is_some()
|| item_struct.generics.lifetimes().next().is_some()
{
return Err(format_err_spanned!(
item_struct.generics,
"bitfield structs can only use const generics"
));
}
Ok(())
}
fn extract_repr_attribute(attr: &syn::Attribute, config: &mut Config) -> Result<()> {
let list = attr.meta.require_list()?;
let repr_arguments: Punctuated<syn::Meta, syn::Token![,]> =
attr.parse_args_with(Punctuated::parse_terminated)?;
let mut retained_reprs = Vec::new();
for meta in repr_arguments {
match meta {
syn::Meta::Path(path) => {
let repr_kind = if path.is_ident("u8") {
Some(ReprKind::U8)
} else if path.is_ident("u16") {
Some(ReprKind::U16)
} else if path.is_ident("u32") {
Some(ReprKind::U32)
} else if path.is_ident("u64") {
Some(ReprKind::U64)
} else if path.is_ident("u128") {
Some(ReprKind::U128)
} else {
retained_reprs.push(path.clone().into());
None
};
if let Some(repr_kind) = repr_kind {
config.repr(repr_kind, path.span())?;
}
}
other => retained_reprs.push(other),
}
}
if !retained_reprs.is_empty() {
let retained_reprs_tokens = quote! {
#( #retained_reprs ),*
};
config.push_retained_attribute(syn::Attribute {
pound_token: attr.pound_token,
style: attr.style,
bracket_token: attr.bracket_token,
meta: syn::Meta::List(syn::MetaList {
path: list.path.clone(),
delimiter: list.delimiter.clone(),
tokens: retained_reprs_tokens,
}),
});
}
Ok(())
}
fn extract_derive_debug_attribute(attr: &syn::Attribute, config: &mut Config) -> Result<()> {
let list = attr.meta.require_list()?;
let mut retained_derives = vec![];
attr.parse_nested_meta(|meta| {
let path = &meta.path;
if path.is_ident("Debug") {
config.derive_debug(path.span())?;
} else if path.is_ident("Specifier") {
config.derive_specifier(path.span())?;
} else {
retained_derives.push(path.clone());
}
Ok(())
})?;
if !retained_derives.is_empty() {
let retained_derives_tokens = quote! {
#( #retained_derives ),*
};
config.push_retained_attribute(syn::Attribute {
pound_token: attr.pound_token,
style: attr.style,
bracket_token: attr.bracket_token,
meta: syn::Meta::List(syn::MetaList {
path: list.path.clone(),
delimiter: list.delimiter.clone(),
tokens: retained_derives_tokens,
}),
});
}
Ok(())
}
fn extract_attributes(attributes: &[syn::Attribute], config: &mut Config) -> Result<()> {
for attr in attributes {
if attr.path().is_ident("repr") {
Self::extract_repr_attribute(attr, config)?;
} else if attr.path().is_ident("derive") {
Self::extract_derive_debug_attribute(attr, config)?;
} else {
config.push_retained_attribute(attr.clone());
}
}
Ok(())
}
fn analyse_config_for_fields(item_struct: &syn::ItemStruct, config: &mut Config) -> Result<()> {
for (index, field) in Self::fields(item_struct) {
let span = field.span();
let field_config = Self::extract_field_config(field)?;
config.field_config(index, span, field_config)?;
}
Ok(())
}
fn extract_field_config(field: &syn::Field) -> Result<FieldConfig> {
let mut config = FieldConfig::default();
for attr in &field.attrs {
if attr.path().is_ident("bits") {
let name_value = attr.meta.require_name_value()?;
let span = name_value.span();
match &name_value.value {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(lit_int),
..
}) => {
config.bits(lit_int.base10_parse::<usize>()?, span)?;
}
value => {
return Err(format_err!(
value.span(),
"encountered invalid value type for #[bits = N]"
))
}
}
} else if attr.path().is_ident("skip") {
match &attr.meta {
syn::Meta::Path(path) => {
assert!(path.is_ident("skip"));
config.skip(SkipWhich::All, path.span())?;
}
syn::Meta::List(meta_list) => {
let (mut getters, mut setters) = (None, None);
meta_list.parse_nested_meta(|meta| {
let path = &meta.path;
if path.is_ident("getters") {
if let Some(previous) = getters {
return raise_skip_error("(getters)", path.span(), previous);
}
getters = Some(path.span());
} else if path.is_ident("setters") {
if let Some(previous) = setters {
return raise_skip_error("(setters)", path.span(), previous);
}
setters = Some(path.span());
} else {
return Err(meta.error(
"encountered unknown or unsupported #[skip(..)] specifier",
));
}
Ok(())
})?;
if getters.is_some() == setters.is_some() {
config.skip(SkipWhich::All, meta_list.path.span())?;
} else if getters.is_some() {
config.skip(SkipWhich::Getters, meta_list.path.span())?;
} else {
config.skip(SkipWhich::Setters, meta_list.path.span())?;
}
}
meta @ syn::Meta::NameValue(..) => {
return Err(format_err!(
meta.span(),
"encountered invalid format for #[skip] field attribute"
))
}
}
} else {
config.retain_attr(attr.clone());
}
}
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::ToTokens as _;
#[test]
fn retain_repr_arguments() {
let attr: syn::Attribute = syn::parse_quote!(#[repr(C, align(8))]);
let mut config = Config::default();
BitfieldStruct::extract_repr_attribute(&attr, &mut config).unwrap();
assert_eq!(config.retained_attributes.len(), 1);
let retained = &config.retained_attributes[0];
assert_eq!(
retained.to_token_stream().to_string(),
attr.to_token_stream().to_string(),
"repr arguments should be preserved when re-emitting retained repr attributes"
);
}
}