use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::*;
pub fn derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let result = match input.data {
Data::Struct(decl) => derive_struct(input.ident, input.generics, decl),
Data::Enum(decl) => derive_enum(input.ident, input.generics, decl),
Data::Union(input) => derive_union(input.union_token),
};
match result {
Ok(ok) => ok.into(),
Err(err) => err.into_compile_error().into(),
}
}
fn derive_union(token: Token![union]) -> Result<TokenStream> {
Err(Error::new(
token.span,
"deriving Located for unions is not supported",
))
}
fn derive_struct(ident: Ident, generics: Generics, input: DataStruct) -> Result<TokenStream> {
match input.fields {
Fields::Named(named) => derive_named_struct(ident, generics, named),
Fields::Unnamed(unnamed) => derive_tuple_struct(ident, generics, unnamed),
Fields::Unit => derive_unit_struct(ident),
}
}
fn derive_named_struct(
ident: Ident,
generics: Generics,
fields: FieldsNamed,
) -> Result<TokenStream> {
let field = fields
.named
.iter()
.find(|field| field.ident == Some(Ident::new("span", Span::call_site())));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
if let Some(field) = field {
let name = field.ident.as_ref().unwrap();
Ok(quote_spanned! {
name.span() =>
impl #impl_generics formally::support::Located for #ident #ty_generics #where_clause {
fn span(&self) -> ::std::option::Option<formally::support::Span> {
self.span.clone()
}
}
})
} else {
Err(Error::new(
ident.span(),
"deriving Located requires a field called `span` of type `Option<Span>`",
))
}
}
fn derive_tuple_struct(
ident: Ident,
generics: Generics,
fields: FieldsUnnamed,
) -> Result<TokenStream> {
let expr = match fields.unnamed.len() {
0 => quote!(None),
1 => quote!(self.0.span()),
_ => {
return Err(Error::new(
ident.span(),
"deriving Located for tuple structs with more than one field is \
currently not supported",
));
}
};
let (params, args, where_) = generics.split_for_impl();
Ok(quote_spanned! {
ident.span() =>
impl #params formally::support::Located for #ident #args #where_ {
fn span(&self) -> ::std::option::Option<formally::support::Span> {
#expr
}
}
})
}
fn derive_unit_struct(ident: Ident) -> Result<TokenStream> {
Err(Error::new(
ident.span(),
"deriving Located for unions is currently not supported",
))
}
fn derive_enum(ident: Ident, generics: Generics, input: DataEnum) -> Result<TokenStream> {
let mut matches = quote! {};
for variant in input.variants {
let name = variant.ident;
match variant.fields {
Fields::Named(_) => matches.extend(quote! {
#ident::#name { span, .. } => span.clone(),
}),
Fields::Unnamed(unnamed) => match unnamed.unnamed.len() {
0 => matches.extend(quote! {
#ident::#name() => None,
}),
1 => {
matches.extend(quote! {
#ident::#name(field) => field.span(),
});
}
_ => {
return Err(Error::new(
name.span(),
"deriving Located for enum tuple variants of multiple \
fields is not yet supported",
));
}
},
Fields::Unit => {
matches.extend(quote! {
#ident::#name => None,
});
}
}
}
let (params, args, where_) = generics.split_for_impl();
Ok(quote! {
impl #params formally::support::Located for #ident #args #where_ {
fn span(&self) -> ::std::option::Option<formally::support::Span> {
match self {
#matches
}
}
}
})
}