use crate::*;
pub fn impl_isomorphism_macro(ast: &DeriveInput) -> Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let (impl_generics, ty_generics, where_clause) = (&impl_generics, &ty_generics, &where_clause);
let mut gen_clone = ast.generics.clone();
let lt = syn::Lifetime::new("'a", Span::call_site());
let ltp = syn::LifetimeParam::new(lt);
gen_clone.params.push(syn::GenericParam::from(ltp));
let (ref_impl_generics, _ref_ty_generics, ref_where_clause) = gen_clone.split_for_impl();
let mut ty = None::<Ident>;
let mut ty_list: Vec<Expr> = Vec::new();
let mut list = None::<syn::ExprArray>;
let mut has_default = false;
if let Some(attr) = ast.attrs.iter().find(|x| x.path().is_ident("isomorphism")) {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("has_default") {
has_default = true;
} else if meta.path.is_ident("list") {
let arg: syn::ExprArray = meta.value()?.parse()?;
list.replace(arg);
} else if meta.path.is_ident("into") {
let args: syn::ExprArray = meta.value()?.parse()?;
for arg in args.elems.into_iter() {
ty_list.push(arg);
}
} else if let Some(arg) = meta.path.get_ident().map(|x| x.clone()) {
ty.replace(arg);
} else {
return Err(Error::new(ast.span(), "Top level 'isomorphism' attribute has argments of list or Into/From type."));
}
Ok(())
})?;
}
if ty.is_some() && !ty_list.is_empty() {
return Err(Error::new(ast.span(), "To pass Into/From type, should either use format of simple ident or array."));
}
let data = match &ast.data {
Data::Struct(_) => {
let gen = quote! {
impl #impl_generics Isomorphism for #name #ty_generics #where_clause {
fn title(&self) -> &str { "" }
fn list() -> Vec<Self> { Vec::new() }
}
};
return Ok(gen.into());
},
Data::Enum(data) => {
data
},
_ => return Err(Error::new(ast.span(), "Only for Enum data type.")),
};
let mut fallback_list: Option<TokenStream> = if list.is_none() { Some(TokenStream::new()) } else { None };
let len = if ty.is_some() {1} else {ty_list.len()};
let mut quoted_into_list: Vec<TokenStream> = (0..len).map(|_| TokenStream::new()).collect();
let mut quoted_from_list: Vec<TokenStream> = (0..len).map(|_| TokenStream::new()).collect();
let mut quoted_title = TokenStream::new();
for variant in data.variants.iter() {
let matching_format = variant_matching_format(name, variant)?;
let default_format = variant_default_format(name, variant)?;
fallback_list.as_mut().map(|x| x.extend(quote! { #default_format, }));
let mut values: Vec<Expr> = Vec::new();
let mut title = None::<Expr>;
for attr in variant.attrs.iter() {
if attr.path().is_ident("into") {
if ty.is_some() {
let arg: Expr = attr.parse_args()?;
values.push(arg);
} else if !ty_list.is_empty() {
let args: syn::ExprArray = attr.parse_args()?;
for arg in args.elems.into_iter() {
values.push(arg);
}
}
} else if attr.path().is_ident("title") {
let arg: Expr = attr.parse_args()?;
title.replace(arg);
}
};
if !ty_list.is_empty() {
if ty_list.len() != values.len() {
return Err(Error::new(ast.span(), "Using Into/From type arrays, should not omit values."));
}
}
if let Some(ty) = ty.as_ref() {
quoted_into_list.get_mut(0).map(|x| x.extend(
if let Some(value) = values.first() {
quote! { #matching_format => #value, }
} else {
quote! { #matching_format => #ty::default(), }
}
));
if has_default {
if let Some(value) = values.first() {
quoted_from_list.get_mut(0).map(|x| x.extend(
quote! { #value => #default_format, }
));
};
}
} else if !ty_list.is_empty() {
for (quoted_into, (quoted_from, value)) in quoted_into_list.iter_mut().zip(quoted_from_list.iter_mut().zip(values.iter())) {
quoted_into.extend(
quote! { #matching_format => #value, }
);
if has_default {
quoted_from.extend(
quote! { #value => #default_format, }
);
}
}
}
quoted_title.extend(
if let Some(title) = title {
quote! { #matching_format => #title, }
} else {
let variant_name = &variant.ident.to_string();
quote! {#matching_format => #variant_name, }
}
);
};
let mut quoted: TokenStream = TokenStream::new();
let impl_into_from = move |quoted: &mut TokenStream, quoted_into: TokenStream, quoted_from: TokenStream, ty: TokenStream| {
quoted.extend(quote! {
impl #ref_impl_generics Into<#ty> for &'a #name #ty_generics #ref_where_clause {
fn into(self) -> #ty {
match self {
#quoted_into
}
}
}
impl #impl_generics Into<#ty> for #name #ty_generics #where_clause {
fn into(self) -> #ty {
match self {
#quoted_into
}
}
}
});
if has_default {
quoted.extend(quote! {
impl #impl_generics From<#ty> for #name #ty_generics #where_clause {
fn from(value: #ty) -> Self {
#[allow(unreachable_patterns)]
match value {
#quoted_from
_ => #name::default()
}
}
}
impl #ref_impl_generics From<&'a #ty> for #name #ty_generics #where_clause {
fn from(value: &'a #ty) -> Self {
#[allow(unreachable_patterns)]
match value {
#quoted_from
_ => #name::default()
}
}
}
});
}
};
if let Some(ty) = ty {
let quoted_into = quoted_into_list.remove(0);
let quoted_from = quoted_from_list.remove(0);
impl_into_from(&mut quoted, quoted_into, quoted_from, quote! { #ty });
} else if !ty_list.is_empty() {
for (quoted_into, (quoted_from, ty)) in quoted_into_list.into_iter().zip(quoted_from_list.into_iter().zip(ty_list.into_iter())) {
impl_into_from(&mut quoted, quoted_into, quoted_from, quote! { #ty });
}
}
let mut quoted_list = TokenStream::new();
if let Some(list) = list {
for expr in list.elems.iter() {
quoted_list.extend(
quote! { Self::#expr, }
);
}
} else {
quoted_list = fallback_list.unwrap();
}
quoted.extend(quote! {
impl #impl_generics Isomorphism for #name #ty_generics #where_clause {
fn title(&self) -> &str {
match self {
#quoted_title
}
}
fn list() -> Vec<Self> {
vec![#quoted_list]
}
}
});
Ok(quoted.into())
}