use crate::util::{Attrs, expand_sequence};
use itertools::Itertools;
use proc_macro2::{Ident, Punct, Spacing, TokenStream};
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
use std::default::Default;
use syn::{
AngleBracketedGenericArguments, Field, Fields, GenericArgument, Index, Path, PathArguments,
PathSegment, Type,
};
pub enum TypeWrapper {
Box(Type),
Option(Type),
Iterable(Type, Path), MapLike(Type, Type, Path), ArcLike(Type, Path), CellLike(Type, Path), OtherPathZero(Path), OtherPathOne(Type, Path), OtherPathMany(Vec<Type>, Path), Tuple(Vec<Type>),
}
static SUPPORTED_ITERABLES: &[&str] = &[
"Vec",
"HashSet",
"VecDeque",
"BTreeSet",
"LinkedList",
"BinaryHeap",
];
static SUPPORTED_MAPLIKES: &[&str] = &["HashMap", "BTreeMap"];
static SUPPORTED_CELLLIKE: &[&str] = &["Cell", "RefCell"];
static SUPPORTED_ARCLIKE: &[&str] = &["Arc", "Rc"];
pub fn field_types(flds: Fields) -> Vec<Type> {
match flds {
Fields::Unit => vec![],
Fields::Named(fields) => fields.named.into_iter().flat_map(leaf_types).collect_vec(),
Fields::Unnamed(fields) => fields
.unnamed
.into_iter()
.flat_map(leaf_types)
.collect_vec(),
}
}
pub fn leaf_types(mut field: Field) -> Vec<Type> {
let attrs: Attrs = deluxe::extract_attributes(&mut field).unwrap();
if attrs.recursive || attrs.custom_with.is_some() {
return vec![];
}
leaf_types_impl(&field.ty)
}
pub fn leaf_types_impl(ty: &Type) -> Vec<Type> {
match type_wrapper(ty) {
Some(fw) => match &fw {
TypeWrapper::Box(inner)
| TypeWrapper::Option(inner)
| TypeWrapper::Iterable(inner, _) => leaf_types_impl(inner),
TypeWrapper::MapLike(k, v, _) => {
let mut ans = leaf_types_impl(k);
ans.extend(leaf_types_impl(v));
ans
}
TypeWrapper::Tuple(inner) => inner.iter().flat_map(leaf_types_impl).collect(),
TypeWrapper::CellLike(inner, _) => leaf_types_impl(inner),
TypeWrapper::ArcLike(inner, _) => leaf_types_impl(inner),
TypeWrapper::OtherPathZero(_) => vec![ty.clone()],
TypeWrapper::OtherPathOne(inner, _) => vec![inner.clone()],
TypeWrapper::OtherPathMany(inners, _) => {
inners.iter().flat_map(leaf_types_impl).collect()
}
},
None => vec![ty.clone()],
}
}
pub fn type_wrapper(ty: &Type) -> Option<TypeWrapper> {
match ty {
Type::Path(path) => {
let mut path = path.clone();
let segments = &mut path.path.segments;
let last = segments.last_mut().unwrap().clone();
let ident = last.ident.to_string();
let inners = match last.arguments {
PathArguments::AngleBracketed(ref args) => args
.args
.iter()
.filter_map(|a| match a {
GenericArgument::Type(inner) => Some(inner.clone()),
_ => {
println!("Couldn't parse generic type argument: {:#?}", a);
None
}
})
.collect_vec(),
_ => {
vec![]
}
};
let new_last = PathSegment {
ident: last.ident.clone(),
arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: Some(Default::default()),
lt_token: Default::default(),
args: inners
.iter()
.map(|a| GenericArgument::Type(a.clone()))
.collect(),
gt_token: Default::default(),
}),
};
segments.pop();
segments.push(new_last);
match ident.as_str() {
"Option" => Some(TypeWrapper::Option(inners[0].clone())),
"Box" => Some(TypeWrapper::Box(inners[0].clone())),
_itr if SUPPORTED_ITERABLES.contains(&ident.as_str()) => {
Some(TypeWrapper::Iterable(inners[0].clone(), path.path.clone()))
}
_map if SUPPORTED_MAPLIKES.contains(&ident.as_str()) => {
if inners.len() == 2 {
Some(TypeWrapper::MapLike(
inners[0].clone(),
inners[1].clone(),
path.path.clone(),
))
} else {
println!("Invalid map-like type: {:#?}", ty);
None
}
}
_cell if SUPPORTED_CELLLIKE.contains(&ident.as_str()) => {
if inners.len() == 1 {
Some(TypeWrapper::CellLike(inners[0].clone(), path.path.clone()))
} else {
println!("Invalid cell-like type: {:#?}", ty);
None
}
}
_arc if SUPPORTED_ARCLIKE.contains(&ident.as_str()) => {
if inners.len() == 1 {
Some(TypeWrapper::ArcLike(inners[0].clone(), path.path.clone()))
} else {
println!("Invalid arc-like type: {:#?}", ty);
None
}
}
_ => match inners.len() {
0 => Some(TypeWrapper::OtherPathZero(path.path.clone())),
1 => Some(TypeWrapper::OtherPathOne(
inners[0].clone(),
path.path.clone(),
)),
_ => Some(TypeWrapper::OtherPathMany(inners, path.path.clone())),
},
}
}
Type::Tuple(inner) => {
let mut tuple = Vec::new();
for ty in inner.elems.iter() {
tuple.push(ty.clone());
}
Some(TypeWrapper::Tuple(tuple))
}
_ => None,
}
}
pub fn expand_field(fld: &Field, ident: &Ident) -> (TokenStream, TokenStream) {
let mut field = fld.clone();
let attrs: Attrs = deluxe::extract_attributes(&mut field).unwrap();
if let Some(custom_with) = attrs.custom_with {
let exp_ident = format_ident!("{}_custom", ident);
let top = quote! {
let #exp_ident = (#custom_with)(&#ident);
};
let exp = quote! {
# #exp_ident.into()
};
return (exp, top);
}
let ty = &fld.ty;
expand_field_impl(ty, ident)
}
pub fn expand_field_impl(ty: &Type, ident: &Ident) -> (TokenStream, TokenStream) {
match type_wrapper(ty) {
Some(TypeWrapper::Box(inner)) => {
let (inner_exp, inner_top) = expand_field_impl(&inner, ident);
let exp = quote! {
Box::new(#inner_exp)
};
(exp, inner_top)
}
Some(TypeWrapper::Iterable(inner, path)) => {
let inner_exp_ident = format_ident!("{}_vec_like", ident);
let inner_item_ident = format_ident!("{}_vec_like_item", ident);
let (inner_exp, inner_top) = expand_field_impl(&inner, &inner_item_ident);
let top = quote! {
let #inner_exp_ident = #ident.iter().map(|#inner_item_ident: &#inner| {
#inner_top
::quote::quote! { #inner_exp }
}).collect::<Vec<_>>();
};
let seq_exp = expand_sequence(&inner_exp_ident);
let exp = if path.segments.last().unwrap().ident.to_string() == "Vec" {
quote! { vec![#seq_exp] }
} else {
quote! {
#path::from([#seq_exp])
}
};
(exp, top)
}
Some(TypeWrapper::Option(inner)) => {
let gen_ident = format_ident!("{}_option", ident);
let val_ident = format_ident!("{}_option_val", ident);
let (inner_val, inner_top) = expand_field_impl(&inner, &val_ident);
let top = quote! {
#inner_top
let #gen_ident = match #ident {
Some(#val_ident) => ::quote::quote! { Some(#inner_val) },
None => ::quote::quote! { None },
};
};
let mut exp = TokenStream::new();
exp.append(Punct::new('#', Spacing::Joint));
exp.append(gen_ident);
(exp, top)
}
Some(TypeWrapper::Tuple(inner)) => {
let mut top = TokenStream::new();
let mut expansions = Vec::<TokenStream>::new();
for i in 0..inner.len() {
let field_ident = format_ident!("{}_tuple_{}", ident, i);
let idx = Index::from(i);
top.extend(quote! {
let #field_ident = &#ident.#idx;
});
}
for (i, ty) in inner.iter().enumerate() {
let field_ident = format_ident!("{}_tuple_{}", ident, i);
let (exp, top_expr) = expand_field_impl(ty, &field_ident);
top.extend(top_expr.clone());
expansions.push(exp);
}
let exp = quote! {
(#(#expansions),*)
};
(exp, top)
}
Some(TypeWrapper::CellLike(inner, path)) => {
println!(
"WARNING: Tokenising a Cell-like type ({}) will effectively clone its value. This may not be the intended behaviour!",
path.to_token_stream()
);
let inner_ident = format_ident!("{}_cell", ident);
let (inner_exp, inner_top) = expand_field_impl(&inner, &inner_ident);
let top = quote! {
#inner_top
let #inner_ident = #ident.clone().into_inner();
};
let exp = quote! {
#path::new(#inner_exp)
};
(exp, top)
}
Some(TypeWrapper::ArcLike(inner, path)) => {
println!(
"WARNING: Arc-like type ({}) will be handled by cloning its inner value. This may not be the intended behaviour!",
path.to_token_stream()
);
let inner_ident = format_ident!("{}_arc", ident);
let (inner_exp, inner_top) = expand_field_impl(&inner, &inner_ident);
let top = quote! {
#inner_top
let #inner_ident = #ident.as_ref();
};
let exp = quote! {
#path::new(#inner_exp)
};
(exp, top)
}
Some(TypeWrapper::MapLike(kt, vt, path)) => {
let inner_ident = format_ident!("{}_map", ident);
let key_ident = format_ident!("{}_map_key", ident);
let value_ident = format_ident!("{}_map_value", ident);
let (key_exp, key_top) = expand_field_impl(&kt, &key_ident);
let (value_exp, value_top) = expand_field_impl(&vt, &value_ident);
let top = quote! {
#key_top
#value_top
let #inner_ident = #ident.iter().map(|(#key_ident, #value_ident)| {
::quote::quote! { (#key_exp, #value_exp) }
}).collect::<Vec<_>>();
};
let seq = expand_sequence(&inner_ident);
let exp = quote! {
#path::from([#seq])
};
(exp, top)
}
Some(TypeWrapper::OtherPathOne(inner, path)) => {
let (inner_exp, top) = expand_field_impl(&inner, ident);
let exp = quote! {
#path::from(#inner_exp.into())
};
(exp, top)
}
_ => {
let mut ts = TokenStream::new();
ts.append(Punct::new('#', Spacing::Joint));
ts.append(ident.clone());
(quote! { #ts.into() }, TokenStream::new())
}
}
}