281 lines
5.1 KiB
Rust
281 lines
5.1 KiB
Rust
use proc_macro2::TokenStream;
|
|
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
|
use syn::{
|
|
Attribute, Field, Fields, FieldsNamed, Generics, Ident, ItemStruct, LitStr,
|
|
Meta,
|
|
};
|
|
|
|
#[derive(Default)]
|
|
enum Visibility {
|
|
#[default]
|
|
Inherited,
|
|
Pub,
|
|
}
|
|
|
|
impl ToTokens for Visibility {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
match self {
|
|
Visibility::Pub => tokens.append_all(quote! { pub }),
|
|
Visibility::Inherited => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct GetOptions {
|
|
visibility: Visibility,
|
|
rename: Option<String>,
|
|
}
|
|
impl GetOptions {
|
|
fn from_attr(attr: &Attribute) -> syn::Result<Self> {
|
|
let mut out = Self::default();
|
|
attr.parse_nested_meta(|nm| {
|
|
if nm.path.is_ident("pub") {
|
|
out.visibility = Visibility::Pub;
|
|
}
|
|
|
|
if nm.path.is_ident("rename") {
|
|
let name = nm.value()?;
|
|
let name: LitStr = name.parse()?;
|
|
|
|
out.rename = Some(name.value());
|
|
}
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(out)
|
|
}
|
|
}
|
|
|
|
fn named_fields(
|
|
name: Ident,
|
|
generics: Generics,
|
|
n: FieldsNamed,
|
|
) -> TokenStream {
|
|
let (ipl, ty, _) = generics.split_for_impl();
|
|
|
|
n.named
|
|
.into_iter()
|
|
.flat_map(|field| {
|
|
let field_name =
|
|
field.ident.as_ref().expect("named should have ident");
|
|
let field_ty = &field.ty;
|
|
|
|
fn options_for(
|
|
attr_name: &str,
|
|
field: &Field,
|
|
) -> Option<syn::Result<GetOptions>> {
|
|
field.attrs.iter().find_map(|a| match &a.meta {
|
|
Meta::List(list) => list
|
|
.path
|
|
.get_ident()
|
|
.filter(|i| *i == attr_name)
|
|
.map(|_| GetOptions::from_attr(a)),
|
|
Meta::Path(p) => p
|
|
.get_ident()
|
|
.filter(|i| *i == attr_name)
|
|
.map(|_| Ok(GetOptions::default())),
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
let get = options_for("get", &field);
|
|
|
|
fn output_for(
|
|
options: Option<syn::Result<GetOptions>>,
|
|
field_name: &Ident,
|
|
f: impl FnOnce(Ident, Visibility) -> TokenStream,
|
|
) -> Option<TokenStream> {
|
|
match options {
|
|
Some(Ok(get)) => {
|
|
let GetOptions {
|
|
visibility, rename, ..
|
|
} = get;
|
|
|
|
let fn_name = if let Some(rename) = rename {
|
|
format_ident!("{}", rename)
|
|
} else {
|
|
field_name.clone()
|
|
};
|
|
|
|
Some(f(fn_name, visibility))
|
|
},
|
|
Some(Err(e)) => Some(e.into_compile_error()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
let get_out = output_for(get, field_name, |fn_name, vis| {
|
|
quote! {
|
|
impl #ipl #name #ty {
|
|
#vis fn #fn_name(&self) -> #field_ty {
|
|
self.#field_name
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let get_ref = options_for("get_ref", &field);
|
|
|
|
let get_ref_out =
|
|
output_for(get_ref, field_name, |fn_name, vis| {
|
|
quote! {
|
|
impl #ipl #name #ty {
|
|
#vis fn #fn_name(&self) -> &#field_ty {
|
|
&self.#field_name
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let get_mut = options_for("get_mut", &field);
|
|
let get_mut_renamed = get_mut
|
|
.as_ref()
|
|
.is_some_and(|r| r.as_ref().is_ok_and(|o| o.rename.is_some()));
|
|
let get_mut_out =
|
|
output_for(get_mut, field_name, |fn_name, vis| {
|
|
let fn_name = if get_mut_renamed {
|
|
fn_name
|
|
} else {
|
|
format_ident!("{}_mut", fn_name)
|
|
};
|
|
|
|
quote! {
|
|
impl #ipl #name #ty {
|
|
#vis fn #fn_name(&mut self) -> &mut #field_ty {
|
|
&mut self.#field_name
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
get_out.into_iter().chain(get_ref_out).chain(get_mut_out)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn e_easy_get_core(input: TokenStream) -> TokenStream {
|
|
let item: ItemStruct = match syn::parse2(input) {
|
|
Ok(i) => i,
|
|
Err(e) => return e.into_compile_error(),
|
|
};
|
|
|
|
match item.fields {
|
|
Fields::Named(n) => named_fields(item.ident, item.generics, n),
|
|
_ => todo!("aaaaaa"),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use proc_macro2::TokenStream;
|
|
use quote::quote;
|
|
|
|
use crate::e_easy_get_core;
|
|
|
|
fn expect_output(input: TokenStream, output: TokenStream) {
|
|
assert_eq!(e_easy_get_core(input).to_string(), output.to_string())
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_pub() {
|
|
expect_output(
|
|
quote! {
|
|
#[derive(EasyGet)]
|
|
pub struct Foo {
|
|
#[get(pub)]
|
|
bar: usize,
|
|
}
|
|
},
|
|
quote! {
|
|
impl Foo {
|
|
pub fn bar(&self) -> usize {
|
|
self.bar
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_generic() {
|
|
expect_output(
|
|
quote! {
|
|
#[derive(EasyGet)]
|
|
pub struct Foo<T> {
|
|
#[get(pub)]
|
|
bar: T,
|
|
}
|
|
},
|
|
quote! {
|
|
impl<T> Foo<T> {
|
|
pub fn bar(&self) -> T {
|
|
self.bar
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_ref_generic_default() {
|
|
expect_output(
|
|
quote! {
|
|
#[derive(EasyGet)]
|
|
pub struct Foo<T = usize> {
|
|
#[get_ref(pub)]
|
|
bar: T,
|
|
}
|
|
},
|
|
quote! {
|
|
impl<T> Foo<T> {
|
|
pub fn bar(&self) -> &T {
|
|
&self.bar
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_rename() {
|
|
expect_output(
|
|
quote! {
|
|
#[derive(EasyGet)]
|
|
pub struct Foo<T = usize> {
|
|
#[get_ref(pub, rename = "baz")]
|
|
bar: T,
|
|
}
|
|
},
|
|
quote! {
|
|
impl<T> Foo<T> {
|
|
pub fn baz(&self) -> &T {
|
|
&self.bar
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_mut() {
|
|
expect_output(
|
|
quote! {
|
|
#[derive(EasyGet)]
|
|
pub struct Foo<T = usize> {
|
|
#[get_mut(pub)]
|
|
bar: T,
|
|
}
|
|
},
|
|
quote! {
|
|
impl<T> Foo<T> {
|
|
pub fn bar_mut(&mut self) -> &mut T {
|
|
&mut self.bar
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|