use crate::helper::{
LocationLookup, combine_errors, ensure_snafu_implicit, get_crate_path, lookup_location_field,
};
use crate::suzu_attr;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Colon;
use syn::{Data, DeriveInput, Error, Field, FieldMutability, Fields, FieldsNamed, Visibility};
pub(crate) fn suzunari_error_impl(stream: TokenStream) -> Result<TokenStream, Error> {
let mut input: DeriveInput = syn::parse2(stream)?;
let crate_path = get_crate_path("suzunari-error");
if matches!(input.data, Data::Union(_)) {
return Err(Error::new(
input.span(),
"#[suzunari_error] cannot be used on unions",
));
}
suzu_attr::process_suzu_attrs(&mut input, &crate_path)?;
match &mut input.data {
Data::Struct(data_struct) => match &mut data_struct.fields {
Fields::Named(fields) => {
resolve_and_inject_location(fields, &crate_path)?;
}
_ => {
return Err(Error::new(
data_struct.fields.span(),
"#[suzunari_error] can only be used on structs with named fields",
));
}
},
Data::Enum(data_enum) => {
let mut errors = Vec::new();
for variant in &mut data_enum.variants {
match &mut variant.fields {
Fields::Named(fields) => {
if let Err(e) = resolve_and_inject_location(fields, &crate_path) {
errors.push(e);
}
}
Fields::Unit => {
let location_field = location_field_impl(&crate_path);
let mut fields = Punctuated::new();
fields.push(location_field);
variant.fields = Fields::Named(FieldsNamed {
brace_token: Default::default(),
named: fields,
});
}
_ => {
errors.push(Error::new(
variant.fields.span(),
"#[suzunari_error] can only be used on enum variants with named fields",
));
}
}
}
combine_errors(errors)?;
}
Data::Union(_) => unreachable!("unions are rejected before this point"),
}
let snafu_path = quote! { #crate_path::snafu };
let derive_attribute = quote! {
#[derive(Debug, #snafu_path::Snafu, #crate_path::StackError)]
#[snafu(crate_root(#snafu_path))]
};
Ok(quote! {
#derive_attribute
#input
})
}
fn resolve_and_inject_location(
fields: &mut FieldsNamed,
crate_path: &TokenStream,
) -> Result<(), Error> {
match lookup_location_field(fields, "#[suzu(location)]")? {
LocationLookup::Found {
index,
needs_stack_attr,
} => {
let field = &mut fields.named[index];
if needs_stack_attr {
field.attrs.push(syn::parse_quote!(#[stack(location)]));
}
ensure_snafu_implicit(field);
}
LocationLookup::NotFound => {
fields.named.push(location_field_impl(crate_path));
}
}
Ok(())
}
fn location_field_impl(crate_path: &TokenStream) -> Field {
Field {
attrs: vec![
syn::parse_quote!(#[snafu(implicit)]),
syn::parse_quote!(#[stack(location)]),
],
vis: Visibility::Inherited,
ident: Some(format_ident!("location")),
colon_token: Some(Colon::default()),
ty: syn::parse_quote!(#crate_path::Location),
mutability: FieldMutability::None,
}
}