use crate::context::{Context, Tokens};
use proc_macro2::TokenStream;
use quote::quote;
pub struct Derive {
input: syn::DeriveInput,
}
impl syn::parse::Parse for Derive {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
input: input.parse()?,
})
}
}
impl Derive {
pub(super) fn expand(self, cx: &Context) -> Result<TokenStream, ()> {
let attr = cx.type_attrs(&self.input.attrs);
let tokens = cx.tokens_with_module(attr.module.as_ref());
let mut expander = Expander { cx, tokens };
match &self.input.data {
syn::Data::Struct(st) => Ok(expander.expand_struct(&self.input, st)?),
syn::Data::Enum(en) => {
expander.cx.error(syn::Error::new_spanned(
en.enum_token,
"not supported on enums",
));
Err(())
}
syn::Data::Union(un) => {
expander.cx.error(syn::Error::new_spanned(
un.union_token,
"not supported on unions",
));
Err(())
}
}
}
}
struct Expander<'cx> {
cx: &'cx Context,
tokens: Tokens,
}
impl Expander<'_> {
fn expand_struct(
&mut self,
input: &syn::DeriveInput,
st: &syn::DataStruct,
) -> Result<TokenStream, ()> {
let accessor = self.pick_field(&st.fields)?;
let ident = &input.ident;
let opaque = &self.tokens.opaque;
let id = &self.tokens.id;
let (gen_impl, gen_type, gen_where) = input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #gen_impl #opaque for #ident #gen_type #gen_where {
fn id(&self) -> #id {
#accessor
}
}
})
}
fn pick_field(&mut self, fields: &syn::Fields) -> Result<TokenStream, ()> {
let mut field = None;
for (n, f) in fields.iter().enumerate() {
let attrs = self.cx.field_attrs(&f.attrs);
if attrs.id.is_some() {
if field.is_some() {
self.cx.error(syn::Error::new_spanned(
f,
"only one field can be marked `#[rune(id)]`",
));
}
field = Some((n, f));
}
}
let Some((n, f)) = field else {
self.cx.error(syn::Error::new_spanned(
fields,
"Could not find a suitable identifier field",
));
return Err(());
};
Ok(match &f.ident {
Some(ident) => quote!(self.#ident),
None => quote!(self.#n),
})
}
}