use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error2::{Diagnostic, Level};
use quote::quote;
use std::collections::LinkedList;
use syn::{parse2, spanned::Spanned, Fields, Generics, ItemEnum, Type, Variant};
use crate::macro_comm::extract_syn;
pub fn interface(
attr: TokenStream,
item: TokenStream,
) -> Result<TokenStream, LinkedList<Diagnostic>> {
parse_attrs(attr)?;
let enum_def = extract_syn(
item.clone(),
&Ident::new("quick_from", Span::call_site()),
parse2::<ItemEnum>,
)?;
let from_defs = impl_from(&enum_def);
Ok(quote! {
#item
#from_defs
})
}
fn parse_attrs(attr: TokenStream) -> Result<(), LinkedList<Diagnostic>> {
if attr.is_empty() {
Ok(())
} else {
Err(LinkedList::from([Diagnostic::spanned(
attr.span(),
Level::Error,
"This macro takes no arguments".to_owned(),
)]))
}
}
fn get_var_name_type(var: &Variant) -> Option<(Ident, Type)> {
if let Fields::Unnamed(fs) = &var.fields {
if fs.unnamed.len() == 1 {
return Some((var.ident.clone(), fs.unnamed.first().unwrap().ty.clone()));
}
}
None
}
fn strip_generic_constraints(gens: &Generics) -> Generics {
let mut new_gens = gens.clone();
new_gens.type_params_mut().for_each(|param| {
param.bounds.clear();
});
new_gens
}
fn impl_from(enum_def: &ItemEnum) -> TokenStream {
let enum_name = &enum_def.ident;
let from_impls = enum_def
.variants
.iter()
.filter_map(|var| {
if let Some((name, dt)) = get_var_name_type(var) {
let enum_generic = enum_def.generics.clone();
let stripped_gens = strip_generic_constraints(&enum_generic);
Some(quote! {
impl #enum_generic From<#dt> for #enum_name #stripped_gens {
fn from(it: #dt) -> Self {
Self::#name(it)
}
}
})
} else {
None
}
})
.collect::<Vec<_>>();
quote! { #(#from_impls)* }
}