use darling::util::Shape;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::PathSep;
use syn::{Ident, Lifetime, Path, PathSegment, Token, Type, TypePath, TypeReference, TypeTuple};
pub(super) struct CompositeVariable {
ty: Type,
ty_ref: Type,
ty_static: Type,
idents: Vec<Ident>,
}
impl CompositeVariable {
pub(crate) fn new<I>(fields: I) -> Result<Self, darling::Error>
where
I: IntoIterator<Item = super::args::VariableArgs, IntoIter: ExactSizeIterator>,
{
let fields = fields
.into_iter()
.try_fold(Vec::new(), |mut v, args| match args.ident {
Some(ident) => {
v.push((args.ty, ident));
Ok(v)
}
None => Err(darling::Error::unsupported_shape_with_expected(
"Unnamed struct",
&Shape::Named,
)),
})?;
let (ty, ty_ref, ty_static, idents) = Self::to_composite_types(fields);
Ok(Self {
ty,
ty_ref,
ty_static,
idents,
})
}
pub(crate) fn ty(&self) -> &Type {
&self.ty
}
pub(crate) fn ty_ref(&self) -> &Type {
&self.ty_ref
}
pub(crate) fn ty_static(&self) -> &Type {
&self.ty_static
}
pub(crate) fn idents(&self) -> &Vec<Ident> {
&self.idents
}
pub(crate) fn composite_ident(
&self,
prefix: Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let prefix = prefix.unwrap_or(quote!());
let idents = self.idents();
if idents.len() == 1 {
quote! { #( #prefix #idents ) * }
} else {
quote! { ( #( #prefix #idents ), * ) }
}
}
fn to_composite_types(mut vec: Vec<(Type, Ident)>) -> (Type, Type, Type, Vec<Ident>) {
let (ty, ty_ref, ty_static, idents) = match vec.len() {
1 => {
let (ty, ident) = vec.swap_remove(0);
let (ty_ref, ty_static) = if ty.is_string() {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("str", ty.span()),
arguments: syn::PathArguments::None,
});
let str_ty = Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments: segments.clone(),
},
});
(
str_ty.clone().into_reference("'a"),
str_ty.into_reference("'static"),
)
} else {
(
ty.clone().into_reference("'a"),
ty.clone().into_reference("'static"),
)
};
(ty, ty_ref, ty_static, vec![ident])
}
_ => {
let mut ty_el = syn::punctuated::Punctuated::new();
let mut ty_ref_el = syn::punctuated::Punctuated::new();
let mut ty_static_el = syn::punctuated::Punctuated::new();
let mut idents = Vec::with_capacity(vec.len());
vec.into_iter().for_each(|args| {
let (ty, ident) = (args.0, args.1);
ty_el.push(ty.clone());
let (ty_ref, ty_static) = if ty.is_string() {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("str", ty.span()),
arguments: syn::PathArguments::None,
});
let str_ty = Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments: segments.clone(),
},
});
(
str_ty.clone().into_reference("'a"),
str_ty.into_reference("'static"),
)
} else {
(
ty.clone().into_reference("'a"),
ty.into_reference("'static"),
)
};
ty_ref_el.push(ty_ref);
ty_static_el.push(ty_static);
idents.push(ident);
});
let ty = Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: ty_el,
});
let ty_ref = Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: ty_ref_el,
});
let ty_static = Type::Tuple(TypeTuple {
paren_token: Default::default(),
elems: ty_static_el,
});
(ty, ty_ref, ty_static, idents)
}
};
(ty, ty_ref, ty_static, idents)
}
}
trait TypeOps {
fn is_string(&self) -> bool;
fn into_reference(self, symbol: &str) -> Type;
}
impl TypeOps for syn::Type {
fn is_string(&self) -> bool {
let mut segments: Punctuated<PathSegment, PathSep> = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("String", self.span()),
arguments: syn::PathArguments::None,
});
*self
== Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments,
},
})
}
fn into_reference(self, symbol: &str) -> Type {
Type::Reference(TypeReference {
and_token: Token),
lifetime: Some(Lifetime::new(symbol, self.span())),
mutability: None,
elem: Box::new(self),
})
}
}