use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::{Render, core::Field, params};
#[derive(Clone, Default)]
pub struct StructSyntax;
impl Render for StructSyntax {
type Args = params::StructParams;
fn render(&self, args: Self::Args) -> syn::Result<TokenStream> {
let ident = &args.input.ident;
let (impl_generics, type_generics, where_generics) = &args.input.generics.split_for_impl();
let fields: Vec<_> = args
.data
.fields
.iter()
.enumerate()
.map(|(i, field)| Field::parse(i, field))
.collect::<syn::Result<Vec<_>>>()?;
let fields: Vec<_> = fields
.into_iter()
.filter(|field| field.attrs().exists("get"))
.collect();
let methods: Vec<TokenStream> = fields
.iter()
.map(|field| -> syn::Result<TokenStream> {
let fname = field.name();
let ty = field.ty();
let docs = self.render_docs(field);
let method_name = match self.render_custom_method_name(field)? {
Some(id) => id,
None => format_ident!("{}", fname.to_string()),
};
let on_callback = self.render_on_callback(field)?;
let is_option = self.render_option_inner(field).is_some();
let is_bool = self.render_is_bool(field);
let has_copy = self.render_has_modifier(field, "copy")?;
let has_clone = self.render_has_modifier(field, "clone")?;
let has_mut = self.render_has_modifier(field, "mutable")?;
let callback = on_callback
.as_ref()
.map(|expr| quote!(#expr;))
.unwrap_or_default();
let getter = if is_option {
let inner_ty = self.render_option_inner(field).unwrap();
quote! {
#(#docs)*
pub fn #method_name(&self) -> ::std::option::Option<&<#inner_ty as ::std::ops::Deref>::Target> {
#callback
self.#fname.as_deref()
}
}
} else if is_bool || has_copy {
quote! {
#(#docs)*
pub fn #method_name(&self) -> #ty {
#callback
self.#fname
}
}
} else if has_clone {
quote! {
#(#docs)*
pub fn #method_name(&self) -> #ty {
#callback
self.#fname.clone()
}
}
} else {
quote! {
#(#docs)*
pub fn #method_name(&self) -> &<#ty as ::std::ops::Deref>::Target {
#callback
&self.#fname
}
}
};
let mut_getter = if has_mut {
let mut_name = format_ident!("{}_mut", fname.to_string());
quote! {
pub fn #mut_name(&mut self) -> &mut #ty {
&mut self.#fname
}
}
} else {
quote!()
};
Ok(quote! {
#getter
#mut_getter
})
})
.collect::<syn::Result<Vec<_>>>()?;
Ok(quote! {
impl #impl_generics #ident #type_generics #where_generics {
#(#methods)*
}
})
}
}
impl StructSyntax {
fn render_option_inner<'a>(&self, field: &'a Field) -> Option<&'a syn::Type> {
let syn::Type::Path(type_path) = field.ty() else {
return None;
};
let segment = type_path.path.segments.last()?;
if segment.ident != "Option" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let syn::GenericArgument::Type(inner) = args.args.first()? else {
return None;
};
Some(inner)
}
fn render_is_bool(&self, field: &Field) -> bool {
matches!(field.ty(), syn::Type::Path(p) if p.path.is_ident("bool"))
}
fn render_has_modifier(&self, field: &Field, name: &str) -> syn::Result<bool> {
let get_args = field.attrs().get("get")?;
Ok(get_args.iter().any(|arg| {
arg.as_attr()
.is_some_and(|attr| attr.args().iter().any(|a| a.path().is_ident(name)))
}))
}
fn render_custom_method_name(&self, field: &Field) -> syn::Result<Option<proc_macro2::Ident>> {
let get_args = field.attrs().get("get")?;
Ok(get_args
.iter()
.find_map(|arg| arg.as_attr())
.and_then(|attr| {
attr.args().iter().find_map(|a| {
if !a.path().is_ident("__value") {
return None;
}
a.as_lit().and_then(|lit| match lit {
syn::Lit::Str(s) => Some(format_ident!("{}", s.value())),
_ => None,
})
})
}))
}
fn render_on_callback(&self, field: &Field) -> syn::Result<Option<proc_macro2::TokenStream>> {
let get_args = field.attrs().get("get")?;
Ok(get_args
.iter()
.find_map(|arg| arg.as_attr())
.and_then(|attr| {
attr.args()
.iter()
.find(|a| a.path().is_ident("on"))
.and_then(|a| a.as_value_tokens())
}))
}
fn render_docs<'a>(&self, field: &'a Field) -> Vec<&'a syn::Attribute> {
field
.raw_attrs()
.iter()
.filter(|a| a.path().is_ident("doc"))
.collect()
}
}