use proc_macro2::TokenStream;
use quote::quote;
use syn::{
Attribute, Fields, GenericArgument, GenericParam, ItemStruct, Meta, Token, WhereClause,
parse::Parse, punctuated::Punctuated, token::Comma,
};
use crate::{
accessors::{
AccessorArg, AccessorFieldAttrs, AccessorStructAttrs,
accessor_processor::{process_get, process_getters, process_set, process_setters},
},
constructor::{
ConstructorArg, ConstructorStructAttrs, constructor_processor::process_constructor,
},
new_from_args::NewFromArgs,
};
struct StructGenerics {
pub params: Punctuated<GenericParam, Comma>,
pub arguments: Punctuated<GenericArgument, Comma>,
pub where_clause: Option<WhereClause>,
}
impl StructGenerics {
fn single_param_into_arg(param: &GenericParam) -> GenericArgument {
match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
syn::parse_quote!(#ident)
}
GenericParam::Lifetime(lt) => {
let lt = <.lifetime;
syn::parse_quote!(#lt)
}
GenericParam::Const(konst) => {
let ident = &konst.ident;
syn::parse_quote!(#ident)
}
}
}
fn params_into_arguments(
params: &Punctuated<GenericParam, Comma>,
) -> Punctuated<GenericArgument, Comma> {
params
.iter()
.map(StructGenerics::single_param_into_arg)
.collect::<Punctuated<GenericArgument, Comma>>()
}
pub fn from_struct(input: &ItemStruct) -> Self {
let arguments = StructGenerics::params_into_arguments(&input.generics.params);
Self {
params: input.generics.params.clone(),
arguments,
where_clause: input.generics.where_clause.clone(),
}
}
pub fn has_generics(&self) -> bool {
!self.params.is_empty()
}
}
impl From<&StructGenerics> for StructGenericsTk {
fn from(value: &StructGenerics) -> Self {
let (params, arguments) = if value.has_generics() {
let g = &value.params;
let a = &value.arguments;
(quote! {<#g>}, quote! {<#a>})
} else {
(quote! {}, quote! {})
};
let where_clause = &value.where_clause;
Self {
params,
arguments,
where_clause: quote! {#where_clause},
}
}
}
struct StructGenericsTk {
pub params: TokenStream,
pub arguments: TokenStream,
pub where_clause: TokenStream,
}
pub fn parse_punctuated_attribute_args<TArg>(attribute: &Attribute) -> Punctuated<TArg, Token![,]>
where
TArg: Parse,
{
attribute
.parse_args_with(Punctuated::<TArg, Token![,]>::parse_terminated)
.expect("error on parse: ")
}
pub fn get_attrs_for_args<T, TArgs>(punctuated: Punctuated<TArgs, Token![,]>) -> T
where
T: NewFromArgs<TArgs>,
{
T::new(punctuated)
}
pub fn get_attribute_name(attribute: &Attribute) -> String {
let _expected_attribute_name = "Expected attribute name.\nNote: attributes must be *directly imported* and it's names can not be changed. You should only use #[constructor], not #[trl::constructor]";
match &attribute.meta {
Meta::Path(path) => path
.get_ident()
.expect(_expected_attribute_name)
.to_string(),
Meta::List(meta_list) => meta_list
.path
.get_ident()
.expect(_expected_attribute_name)
.to_string(),
_ => unreachable!(),
}
}
pub fn load_args_from_attribute<T, TArgs>(attribute: &Attribute) -> T
where
T: NewFromArgs<TArgs>,
TArgs: Parse,
{
if let Meta::List(_) = &attribute.meta {
let pun = parse_punctuated_attribute_args(attribute);
get_attrs_for_args::<T, TArgs>(pun)
} else {
T::new(Punctuated::<TArgs, Token![,]>::new())
}
}
pub fn process_struct_attributes(
attributes: &Vec<Attribute>,
input: &ItemStruct,
stream: &mut TokenStream,
) {
for attribute in attributes {
let name = get_attribute_name(attribute);
match name.as_str() {
"getters" => {
let attrs = load_args_from_attribute::<AccessorStructAttrs, AccessorArg>(attribute);
stream.extend(process_getters(attrs, input))
}
"setters" => {
let attrs = load_args_from_attribute::<AccessorStructAttrs, AccessorArg>(attribute);
stream.extend(process_setters(attrs, input))
}
"constructor" => {
let attrs =
load_args_from_attribute::<ConstructorStructAttrs, ConstructorArg>(attribute);
stream.extend(process_constructor(attrs, input));
}
_ => {}
};
}
}
pub fn process_field_attributes(fields: &Fields, stream: &mut TokenStream) {
fields.iter().for_each(|field| {
let attributes = &field.attrs;
for attribute in attributes {
let name = get_attribute_name(attribute);
match name.as_str() {
"get" => {
let attrs =
load_args_from_attribute::<AccessorFieldAttrs, AccessorArg>(attribute);
stream.extend(process_get(attrs, field))
}
"set" => {
let attrs =
load_args_from_attribute::<AccessorFieldAttrs, AccessorArg>(attribute);
stream.extend(process_set(attrs, field))
}
_ => {}
};
}
});
}
fn parse_struct_generics(input: &ItemStruct) -> StructGenerics {
StructGenerics::from_struct(input)
}
pub fn generate_impl_for_struct(input: &ItemStruct) -> proc_macro::TokenStream {
let mut elements = TokenStream::new();
process_struct_attributes(&input.attrs, &input, &mut elements);
process_field_attributes(&input.fields, &mut elements);
let struct_name = &input.ident;
let generics = StructGenericsTk::from(&parse_struct_generics(input));
let generic_types = generics.params;
let generic_arguments = generics.arguments;
let generic_where = generics.where_clause;
quote! {
impl #generic_types #struct_name #generic_arguments #generic_where {
#elements
}
}
.into()
}