use as_derive_utils::{
attribute_parsing::with_nested_meta,
datastructure::{DataStructure, DataVariant, Field, FieldMap},
return_spanned_err, return_syn_err, spanned_err,
utils::{LinearResult, SynResultExt},
};
use core_extensions::matches;
use proc_macro2::Span;
use quote::ToTokens;
use syn::{Attribute, Ident, Meta, MetaList, MetaNameValue, WherePredicate};
use std::marker::PhantomData;
pub(crate) struct ReprOffsetConfig<'a> {
pub(crate) debug_print: bool,
pub(crate) is_packed: bool,
pub(crate) use_usize_offsets: bool,
pub(crate) impl_getfieldoffset: bool,
pub(crate) offset_prefix: Ident,
pub(crate) field_map: FieldMap<FieldConfig>,
pub(crate) extra_bounds: Vec<WherePredicate>,
_marker: PhantomData<&'a ()>,
}
impl<'a> ReprOffsetConfig<'a> {
#[allow(clippy::unnecessary_wraps)]
fn new(roa: ReprOffsetAttrs<'a>) -> Result<Self, syn::Error> {
let ReprOffsetAttrs {
debug_print,
is_packed,
is_repr_stable,
use_usize_offsets,
impl_getfieldoffset,
offset_prefix,
field_map,
extra_bounds,
errors: _,
_marker: PhantomData,
} = roa;
if !is_repr_stable {
return_syn_err! {
Span::call_site(),
"Expected a struct with `#[repr(C)]` or `#[repr(transparent)]` attributes."
}
}
Ok(Self {
debug_print,
is_packed,
use_usize_offsets,
impl_getfieldoffset,
offset_prefix,
field_map,
extra_bounds,
_marker: PhantomData,
})
}
}
struct ReprOffsetAttrs<'a> {
debug_print: bool,
is_packed: bool,
is_repr_stable: bool,
use_usize_offsets: bool,
impl_getfieldoffset: bool,
offset_prefix: Ident,
field_map: FieldMap<FieldConfig>,
extra_bounds: Vec<WherePredicate>,
errors: LinearResult<()>,
_marker: PhantomData<&'a ()>,
}
pub(crate) struct FieldConfig {
pub(crate) offset_name: Option<OffsetIdent>,
}
pub(crate) enum OffsetIdent {
Prefix(Ident),
Full(Ident),
}
#[derive(Debug, Copy, Clone)]
enum ParseContext<'a> {
TypeAttr { data_variant: DataVariant },
Field { field: &'a Field<'a> },
}
pub(crate) fn parse_attrs_for_derive<'a>(
ds: &'a DataStructure<'a>,
) -> Result<ReprOffsetConfig<'a>, syn::Error> {
let mut this = ReprOffsetAttrs {
debug_print: false,
is_packed: false,
is_repr_stable: false,
use_usize_offsets: false,
impl_getfieldoffset: true,
offset_prefix: Ident::new("OFFSET_", Span::call_site()),
field_map: FieldMap::with(ds, |_| FieldConfig { offset_name: None }),
extra_bounds: vec![],
errors: LinearResult::ok(()),
_marker: PhantomData,
};
let ty_ctx = ParseContext::TypeAttr {
data_variant: ds.data_variant,
};
parse_inner(&mut this, ds.attrs, ty_ctx);
for variant in &ds.variants {
for field in variant.fields.iter() {
parse_inner(&mut this, field.attrs, ParseContext::Field { field });
}
}
this.errors.take()?;
ReprOffsetConfig::new(this)
}
fn parse_inner<'a, I>(this: &mut ReprOffsetAttrs<'a>, attrs: I, pctx: ParseContext<'a>)
where
I: IntoIterator<Item = &'a Attribute>,
{
for attr in attrs {
match attr.parse_meta() {
Ok(Meta::List(list)) => {
parse_attr_list(this, pctx, list).combine_into_err(&mut this.errors);
}
Err(e) => {
this.errors.push_err(e);
}
_ => {}
}
}
}
fn parse_attr_list<'a>(
this: &mut ReprOffsetAttrs<'a>,
pctx: ParseContext<'a>,
list: MetaList,
) -> Result<(), syn::Error> {
if list.path.is_ident("roff") {
with_nested_meta("roff", list.nested, |attr| {
parse_sabi_attr(this, pctx, attr).combine_into_err(&mut this.errors);
Ok(())
})?;
} else if list.path.is_ident("repr") && matches!(ParseContext::TypeAttr { .. } = pctx) {
with_nested_meta("repr", list.nested, |attr| {
let path = attr.path();
if path.is_ident("C") || path.is_ident("transparent") {
this.is_repr_stable = true;
} else if path.is_ident("packed") {
this.is_packed = true;
}
Ok(())
})?;
}
Ok(())
}
fn parse_sabi_attr<'a>(
this: &mut ReprOffsetAttrs<'a>,
pctx: ParseContext<'a>,
attr: Meta,
) -> Result<(), syn::Error> {
fn make_err(tokens: &dyn ToTokens) -> syn::Error {
spanned_err!(tokens, "unrecognized attribute")
}
match (pctx, attr) {
(ParseContext::Field { field, .. }, Meta::NameValue(MetaNameValue { lit, path, .. })) => {
let f_config = &mut this.field_map[field.index];
if path.is_ident("offset") {
f_config.offset_name = Some(OffsetIdent::Full(parse_lit(&lit)?));
} else if path.is_ident("offset_prefix") {
f_config.offset_name = Some(OffsetIdent::Prefix(parse_lit(&lit)?));
} else {
return Err(make_err(&path));
}
}
(ParseContext::TypeAttr { .. }, Meta::Path(path)) => {
if path.is_ident("debug_print") {
this.debug_print = true;
} else if path.is_ident("usize_offsets") {
this.use_usize_offsets = true;
} else {
return Err(make_err(&path));
}
}
(ParseContext::TypeAttr { .. }, Meta::NameValue(MetaNameValue { lit, path, .. })) => {
let ident = path.get_ident().ok_or_else(|| make_err(&path))?;
if ident == "offset_prefix" {
this.offset_prefix = parse_lit(&lit)?;
} else if ident == "bound" {
this.extra_bounds.push(parse_lit(&lit)?);
} else if path.is_ident("impl_GetFieldOffset") {
this.impl_getfieldoffset = parse_bool(&lit)?;
} else {
return Err(make_err(&path));
}
}
(_, x) => return Err(make_err(&x)),
}
Ok(())
}
fn parse_lit<T>(lit: &syn::Lit) -> Result<T, syn::Error>
where
T: syn::parse::Parse,
{
match lit {
syn::Lit::Str(x) => x.parse(),
_ => Err(spanned_err!(
lit,
"Expected string literal containing identifier"
)),
}
}
fn parse_bool(lit: &syn::Lit) -> Result<bool, syn::Error> {
match lit {
syn::Lit::Bool(x) => Ok(x.value),
_ => Err(spanned_err!(lit, "Expected boolean")),
}
}
#[allow(dead_code)]
fn parse_expr(lit: syn::Lit) -> Result<syn::Expr, syn::Error> {
match lit {
syn::Lit::Str(x) => x.parse(),
syn::Lit::Int(x) => syn::parse_str(x.base10_digits()),
_ => return_spanned_err!(lit, "Expected string or integer literal"),
}
}