use proc_macro::TokenStream as TokenStream1;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use syn::Visibility;
use syn::spanned::Spanned;
#[proc_macro_derive(Getters, attributes(getters))]
pub fn derive_getter_methods(input: TokenStream1) -> TokenStream1 {
getters(input.into()).unwrap_or_else(|e| e.into_compile_error()).into()
}
macro_rules! ident_str {
($meta:ident ) => {
$meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default().as_str()
};
}
macro_rules! error {
($msg:literal $(,$e:expr)*) => {
syn::Error::new(proc_macro2::Span::call_site(), format!($msg $(,$e)*))
}
}
macro_rules! generic {
($segment:ident) => {{
let syn::PathArguments::AngleBracketed(angle_generic) = &$segment.arguments else {
return Err(error!("Unparseable type: {}", $segment.to_token_stream().to_string()));
};
angle_generic.args.clone()
}};
}
fn getters(input: TokenStream) -> syn::Result<TokenStream> {
let mut getters: Vec<TokenStream> = vec![];
let struct_ = syn::parse2::<syn::ItemStruct>(input)?;
let struct_opts = {
let mut struct_opts = StructOptions::default();
if let Some(attr) = struct_.attrs.iter().find(|a| a.path().is_ident("getters")) {
attr.parse_nested_meta(|meta| {
match ident_str!(meta) {
"copy" => struct_opts.copy = true,
"vis" => struct_opts.vis = meta.value()?.parse::<Visibility>()?,
other => Err(error!("Invalid option: {}", other))?,
};
Ok(())
})?;
};
struct_opts
};
for field in struct_.fields {
let field_opts = {
let mut field_opts = struct_opts.field_opts();
if let Some(getters_attr) = field.attrs.iter().find(|a| a.path().is_ident("getters")) {
getters_attr.parse_nested_meta(|meta| {
match ident_str!(meta) {
"copy" => field_opts.copy = true,
"skip" => field_opts.skip = true,
"vis" => field_opts.vis = meta.value()?.parse::<Visibility>()?,
other => Err(error!("Invalid option: {}", other))?,
}
Ok(())
})?;
}
if field_opts.skip {
continue;
}
field_opts
};
let doc = {
let mut answer = String::new();
for doc_attr in field.attrs.iter().filter(|d| d.path().is_ident("doc")) {
if let syn::Meta::NameValue(nv) = &doc_attr.meta {
if let syn::Expr::Lit(l) = &nv.value {
if let syn::Lit::Str(s) = &l.lit {
if !answer.is_empty() {
answer += "\n";
}
answer += s.value().as_str();
}
}
}
}
match answer.is_empty() {
true => quote! {},
false => quote! { #[doc = #answer] },
}
};
let span = field.span();
let field_ident =
&field.ident.ok_or_else(|| syn::Error::new(span, "Fields must be named."))?;
let field_type = &field.ty;
let vis = &field_opts.vis;
let mut const_ = quote! { const };
let (return_type, getter_impl) = match field_type {
syn::Type::Path(p) => {
let Some(segment) = &p.path.segments.last() else { Err(error!("Unparseable type."))? };
match segment.ident.to_string().as_str() {
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
| "u128" | "usize" | "f32" | "f64" | "char" =>
(quote! { #field_type }, quote! { self.#field_ident }),
"String" => (quote! { &str }, quote! { self.#field_ident.as_str() }),
"Vec" => {
let ty = generic!(segment);
(quote! { &[#ty] }, quote! { self.#field_ident.as_slice() })
},
"Box" => {
let ty = generic!(segment);
(quote! { &#ty }, quote! { &self.#field_ident })
},
"Option" => {
let ty = generic!(segment);
match field_opts.copy {
true => (quote! { Option<#ty> }, quote! { self.#field_ident }),
false => (quote! { Option<&#ty> }, quote! { self.#field_ident.as_ref() }),
}
},
"Rc" => {
let ty = generic!(segment);
const_ = quote! {};
(quote! { ::std::rc::Rc<#ty> }, quote! { ::std::rc::Rc::clone(&self.#field_ident) })
},
"Arc" => {
let ty = generic!(segment);
const_ = quote! {};
(
quote! { ::std::sync::Arc<#ty> },
quote! { ::std::sync::Arc::clone(&self.#field_ident)},
)
},
_ => match field_opts.copy {
true => (quote! { #field_type }, quote! { self.#field_ident }),
false => (quote! { &#field_type }, quote! { &self.#field_ident }),
},
}
},
syn::Type::Reference(ref_) => (quote! { #ref_ }, quote! { self.#field_ident }),
syn::Type::Ptr(ptr) => (quote! { #ptr }, quote! { self.#field_ident }),
_ => (quote! { &#field_type }, quote! { &self.#field_ident }),
};
getters.push(quote! {
#doc #[inline]
#vis #const_ fn #field_ident(&self) -> #return_type {
#getter_impl
}
});
}
let ident = &struct_.ident;
let (impl_generics, ty_generics, where_clause) = &struct_.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #impl_generics #ident #ty_generics #where_clause {
#(#getters)*
}
})
}
struct StructOptions {
copy: bool,
vis: Visibility,
}
impl StructOptions {
fn field_opts(&self) -> FieldOptions {
FieldOptions { copy: self.copy, skip: false, vis: self.vis.clone() }
}
}
impl Default for StructOptions {
fn default() -> Self {
Self { copy: false, vis: Visibility::Public(syn::Token)) }
}
}
struct FieldOptions {
copy: bool,
skip: bool,
vis: Visibility,
}