From 6b495203f02505cbc8878b8713e8e79f90340184 Mon Sep 17 00:00:00 2001 From: SLUCHABLUB <81807027+SLUCHABLUB@users.noreply.github.com> Date: Sat, 22 Mar 2025 18:19:28 +0100 Subject: [PATCH 1/5] Add `CloneGetters` --- src/generate.rs | 52 +++++++++----- src/lib.rs | 13 ++++ tests/clone_getters.rs | 149 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 tests/clone_getters.rs diff --git a/src/generate.rs b/src/generate.rs index 6ae4a26..bc5fb5a 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -4,7 +4,7 @@ use syn::{ self, ext::IdentExt, spanned::Spanned, Expr, Field, Lit, Meta, MetaNameValue, Visibility, }; -use self::GenMode::{Get, GetCopy, GetMut, Set, SetWith}; +use self::GenMode::{Get, GetClone, GetCopy, GetMut, Set, SetWith}; use super::parse_attr; pub struct GenParams { @@ -15,6 +15,7 @@ pub struct GenParams { #[derive(PartialEq, Eq, Copy, Clone)] pub enum GenMode { Get, + GetClone, GetCopy, GetMut, Set, @@ -25,6 +26,7 @@ impl GenMode { pub fn name(self) -> &'static str { match self { Get => "get", + GetClone => "get_clone", GetCopy => "get_copy", GetMut => "get_mut", Set => "set", @@ -34,7 +36,7 @@ impl GenMode { pub fn prefix(self) -> &'static str { match self { - Get | GetCopy | GetMut => "", + Get | GetClone | GetCopy | GetMut => "", Set => "set_", SetWith => "with_", } @@ -42,15 +44,15 @@ impl GenMode { pub fn suffix(self) -> &'static str { match self { - Get | GetCopy | Set | SetWith => "", + Get | GetClone | GetCopy | Set | SetWith => "", GetMut => "_mut", } } fn is_get(self) -> bool { match self { - GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true, - GenMode::Set | GenMode::SetWith => false, + Get | GetClone | GetCopy | GetMut => true, + Set | SetWith => false, } } } @@ -112,6 +114,7 @@ fn has_prefix_attr(f: &Field, params: &GenParams) -> bool { .filter_map(|attr| parse_attr(attr, params.mode)) .find(|meta| { meta.path().is_ident("get") + || meta.path().is_ident("get_clone") || meta.path().is_ident("get_copy") || meta.path().is_ident("get_mut") }) @@ -167,7 +170,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { // Generate nothing for skipped field Some(meta) if meta.path().is_ident("skip") => quote! {}, Some(_) => match params.mode { - GenMode::Get => { + Get => { quote! { #(#doc)* #[inline(always)] @@ -176,7 +179,16 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } } - GenMode::GetCopy => { + GetClone => { + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> #ty { + self.#field_name.clone() + } + } + } + GetCopy => { quote! { #(#doc)* #[inline(always)] @@ -185,7 +197,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } } - GenMode::Set => { + Set => { quote! { #(#doc)* #[inline(always)] @@ -195,7 +207,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } } - GenMode::GetMut => { + GetMut => { quote! { #(#doc)* #[inline(always)] @@ -204,7 +216,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { } } } - GenMode::SetWith => { + SetWith => { quote! { #(#doc)* #[inline(always)] @@ -234,7 +246,7 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 // Generate nothing for skipped field Some(meta) if meta.path().is_ident("skip") => quote! {}, Some(_) => match params.mode { - GenMode::Get => { + Get => { let fn_name = Ident::new("get", Span::call_site()); quote! { #(#doc)* @@ -244,7 +256,17 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 } } } - GenMode::GetCopy => { + GetClone => { + let fn_name = Ident::new("get", Span::call_site()); + quote! { + #(#doc)* + #[inline(always)] + #visibility fn #fn_name(&self) -> #ty { + self.0.clone() + } + } + } + GetCopy => { let fn_name = Ident::new("get", Span::call_site()); quote! { #(#doc)* @@ -254,7 +276,7 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 } } } - GenMode::Set => { + Set => { let fn_name = Ident::new("set", Span::call_site()); quote! { #(#doc)* @@ -265,7 +287,7 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 } } } - GenMode::GetMut => { + GetMut => { let fn_name = Ident::new("get_mut", Span::call_site()); quote! { #(#doc)* @@ -275,7 +297,7 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 } } } - GenMode::SetWith => { + SetWith => { let fn_name = Ident::new("set_with", Span::call_site()); quote! { #(#doc)* diff --git a/src/lib.rs b/src/lib.rs index 30949e3..dbefe34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,6 +226,18 @@ pub fn getters(input: TokenStream) -> TokenStream { produce(&ast, ¶ms).into() } +#[proc_macro_derive(CloneGetters, attributes(get_clone, with_prefix, getset))] +#[proc_macro_error] +pub fn clone_getters(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let params = GenParams { + mode: GenMode::GetClone, + global_attr: parse_global_attr(&ast.attrs, GenMode::GetClone), + }; + + produce(&ast, ¶ms).into() +} + #[proc_macro_derive(CopyGetters, attributes(get_copy, with_prefix, getset))] #[proc_macro_error] pub fn copy_getters(input: TokenStream) -> TokenStream { @@ -292,6 +304,7 @@ fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { .into_iter() .inspect(|meta| { if !(meta.path().is_ident("get") + || meta.path().is_ident("get_clone") || meta.path().is_ident("get_copy") || meta.path().is_ident("get_mut") || meta.path().is_ident("set") diff --git a/tests/clone_getters.rs b/tests/clone_getters.rs new file mode 100644 index 0000000..656b424 --- /dev/null +++ b/tests/clone_getters.rs @@ -0,0 +1,149 @@ +#[macro_use] +extern crate getset; + +use crate::submodule::other::{Generic, Plain, Where}; + +// For testing `pub(super)` +mod submodule { + // For testing `pub(super::other)` + pub mod other { + #[derive(CloneGetters)] + #[get_clone] + pub struct Plain { + /// A doc comment. + /// Multiple lines, even. + private_accessible: Box, + + /// A doc comment. + #[get_clone = "pub"] + public_accessible: Box, + // /// A doc comment. + // #[get_clone = "pub(crate)"] + // crate_accessible: Box, + + // /// A doc comment. + // #[get_clone = "pub(super)"] + // super_accessible: Box, + + // /// A doc comment. + // #[get_clone = "pub(super::other)"] + // scope_accessible: Box, + + // Prefixed getter. + #[get_clone = "with_prefix"] + private_prefixed: Box, + + // Prefixed getter. + #[get_clone = "pub with_prefix"] + public_prefixed: Box, + } + + impl Default for Plain { + fn default() -> Plain { + Plain { + private_accessible: Box::new(17), + public_accessible: Box::new(18), + private_prefixed: Box::new(19), + public_prefixed: Box::new(20), + } + } + } + + #[derive(CloneGetters, Default)] + #[get_clone] + pub struct Generic { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_clone = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_clone = "pub(crate)"] + // crate_accessible: T, + + // /// A doc comment. + // #[get_clone = "pub(super)"] + // super_accessible: T, + + // /// A doc comment. + // #[get_clone = "pub(super::other)"] + // scope_accessible: T, + } + + #[derive(CloneGetters, Getters, Default)] + #[get_clone] + pub struct Where + where + T: Clone + Default, + { + /// A doc comment. + /// Multiple lines, even. + private_accessible: T, + + /// A doc comment. + #[get_clone = "pub"] + public_accessible: T, + // /// A doc comment. + // #[get_clone = "pub(crate)"] + // crate_accessible: T, + + // /// A doc comment. + // #[get_clone = "pub(super)"] + // super_accessible: T, + + // /// A doc comment. + // #[get_clone = "pub(super::other)"] + // scope_accessible: T, + } + + #[test] + fn test_plain() { + let val = Plain::default(); + val.private_accessible(); + } + + #[test] + fn test_generic() { + let val = Generic::>::default(); + val.private_accessible(); + } + + #[test] + fn test_where() { + let val = Where::>::default(); + val.private_accessible(); + } + + #[test] + fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(19, *val.get_private_prefixed()); + } + } +} + +#[test] +fn test_plain() { + let val = Plain::default(); + assert_eq!(18, *val.public_accessible()); +} + +#[test] +fn test_generic() { + let val = Generic::>::default(); + assert_eq!(Box::default(), val.public_accessible()); +} + +#[test] +fn test_where() { + let val = Where::>::default(); + assert_eq!(Box::default(), val.public_accessible()); +} + +#[test] +fn test_prefixed_plain() { + let val = Plain::default(); + assert_eq!(20, *val.get_public_prefixed()); +} From 0f0ba65ef10abc13e6a0fb6bcba80825e9dee363 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Wed, 9 Apr 2025 12:30:30 +0000 Subject: [PATCH 2/5] Add a failed test for the `impl_attrs` feature. --- Cargo.toml | 3 +++ tests/clone_getters.rs | 2 +- tests/fixture/Cargo.toml | 11 ++++++++++ tests/fixture/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++ tests/impl_attrs.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/fixture/Cargo.toml create mode 100644 tests/fixture/src/lib.rs create mode 100644 tests/impl_attrs.rs diff --git a/Cargo.toml b/Cargo.toml index 4ef42a4..35f5ec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,6 @@ quote = "1" syn = "2" proc-macro2 = { version = "1", default-features = false } proc-macro-error2 = "2" + +[dev-dependencies] +fixture = { path = "tests/fixture" } diff --git a/tests/clone_getters.rs b/tests/clone_getters.rs index 656b424..34b1cc9 100644 --- a/tests/clone_getters.rs +++ b/tests/clone_getters.rs @@ -51,7 +51,7 @@ mod submodule { #[derive(CloneGetters, Default)] #[get_clone] - pub struct Generic { + pub struct Generic { /// A doc comment. /// Multiple lines, even. private_accessible: T, diff --git a/tests/fixture/Cargo.toml b/tests/fixture/Cargo.toml new file mode 100644 index 0000000..f73e456 --- /dev/null +++ b/tests/fixture/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fixture" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error2 = "2.0.1" +quote = "1.0.40" +syn = { version = "2.0.100", features = ["full"] } diff --git a/tests/fixture/src/lib.rs b/tests/fixture/src/lib.rs new file mode 100644 index 0000000..c169e29 --- /dev/null +++ b/tests/fixture/src/lib.rs @@ -0,0 +1,39 @@ +use proc_macro::TokenStream; +use proc_macro_error2::{abort, proc_macro_error}; +use quote::quote; +use syn::{parse_macro_input, spanned::Spanned as _, ImplItem, ItemImpl}; + +#[proc_macro_attribute] +#[proc_macro_error] +pub fn add_1_to_implementation(_attr: TokenStream, item_impl: TokenStream) -> TokenStream { + let input = parse_macro_input!(item_impl as ItemImpl); + let attrs = &input.attrs; + let name = &input.self_ty; + let (generics, ty_generics, where_clause) = &input.generics.split_for_impl(); + let statements = &input + .items + .iter() + .map(|item| match item { + ImplItem::Fn(function) => { + let func_attrs = &function.attrs; + let sig = &function.sig; + let body = &function.block.stmts[0]; + quote! { + #(#func_attrs)* + #sig { + #body + 1 + } + } + } + _ => abort!(item.span(), "Expected a method."), + }) + .collect::>(); + + quote! { + #(#attrs)* + impl #generics #name #ty_generics #where_clause { + #(#statements)* + } + } + .into() +} diff --git a/tests/impl_attrs.rs b/tests/impl_attrs.rs new file mode 100644 index 0000000..c256ab4 --- /dev/null +++ b/tests/impl_attrs.rs @@ -0,0 +1,46 @@ +use getset::{CloneGetters, CopyGetters}; + +#[derive(CopyGetters, CloneGetters)] +#[getset( + get_clone = "with_prefix", + get_copy, + impl_attrs = r#" + #[add_1_to_implementation] + #[cfg(target_os = "linux")] + #[add_1_to_implementation] + #[allow(unused)] + #[add_1_to_implementation] + "# +)] +struct Wardrobe { + shirts: u8, + pants: u8, +} + +#[test] +fn basic() { + let wardrobe = Wardrobe { + shirts: 2, + pants: 1, + }; + assert_eq!( + wardrobe.shirts(), + 5, + "Function attribute not applied correctly." + ); + assert_eq!( + wardrobe.pants(), + 4, + "Function attribute not applied correctly." + ); + assert_eq!( + wardrobe.get_shirts(), + 5, + "Function attribute not applied correctly." + ); + assert_eq!( + wardrobe.get_pants(), + 4, + "Function attribute not applied correctly." + ); +} From 5da5ccee6d90f27b249e932e705339d7eacec2fb Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Wed, 9 Apr 2025 13:37:45 +0000 Subject: [PATCH 3/5] Add some minor clean up. --- src/generate.rs | 2 +- src/lib.rs | 60 +++++++++++++++++++------------------------------ 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/generate.rs b/src/generate.rs index bc5fb5a..3f53ab9 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -71,7 +71,7 @@ fn expr_to_string(expr: &Expr) -> Option { } // Helper function to parse visibility -fn parse_vis_str(s: &str, span: proc_macro2::Span) -> Visibility { +fn parse_vis_str(s: &str, span: Span) -> Visibility { match syn::parse_str(s) { Ok(vis) => vis, Err(e) => abort!(span, "Invalid visibility found: {}", e), diff --git a/src/lib.rs b/src/lib.rs index dbefe34..0372307 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,7 +208,10 @@ extern crate quote; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error2::{abort, abort_call_site, proc_macro_error}; -use syn::{parse_macro_input, spanned::Spanned, DataStruct, DeriveInput, Meta}; +use syn::{ + parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataStruct, + DeriveInput, Fields, Meta, Token, +}; use crate::generate::{GenMode, GenParams}; @@ -218,10 +221,7 @@ mod generate; #[proc_macro_error] pub fn getters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::Get, - global_attr: parse_global_attr(&ast.attrs, GenMode::Get), - }; + let params = make_params(&ast.attrs, GenMode::Get); produce(&ast, ¶ms).into() } @@ -230,10 +230,7 @@ pub fn getters(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn clone_getters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::GetClone, - global_attr: parse_global_attr(&ast.attrs, GenMode::GetClone), - }; + let params = make_params(&ast.attrs, GenMode::GetClone); produce(&ast, ¶ms).into() } @@ -242,10 +239,7 @@ pub fn clone_getters(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn copy_getters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::GetCopy, - global_attr: parse_global_attr(&ast.attrs, GenMode::GetCopy), - }; + let params = make_params(&ast.attrs, GenMode::GetCopy); produce(&ast, ¶ms).into() } @@ -254,10 +248,7 @@ pub fn copy_getters(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn mut_getters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::GetMut, - global_attr: parse_global_attr(&ast.attrs, GenMode::GetMut), - }; + let params = make_params(&ast.attrs, GenMode::GetMut); produce(&ast, ¶ms).into() } @@ -266,10 +257,7 @@ pub fn mut_getters(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn setters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::Set, - global_attr: parse_global_attr(&ast.attrs, GenMode::Set), - }; + let params = make_params(&ast.attrs, GenMode::Set); produce(&ast, ¶ms).into() } @@ -278,27 +266,25 @@ pub fn setters(input: TokenStream) -> TokenStream { #[proc_macro_error] pub fn with_setters(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let params = GenParams { - mode: GenMode::SetWith, - global_attr: parse_global_attr(&ast.attrs, GenMode::SetWith), - }; + let params = make_params(&ast.attrs, GenMode::SetWith); produce(&ast, ¶ms).into() } -fn parse_global_attr(attrs: &[syn::Attribute], mode: GenMode) -> Option { - attrs.iter().filter_map(|v| parse_attr(v, mode)).last() +fn make_params(attrs: &[Attribute], mode: GenMode) -> GenParams { + GenParams { + mode, + global_attr: attrs.iter().filter_map(|v| parse_attr(v, mode)).last(), + } } -fn parse_attr(attr: &syn::Attribute, mode: GenMode) -> Option { - use syn::{punctuated::Punctuated, Token}; - +fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { if attr.path().is_ident("getset") { - let meta_list = - match attr.parse_args_with(Punctuated::::parse_terminated) { - Ok(list) => list, - Err(e) => abort!(attr.span(), "Failed to parse getset attribute: {}", e), - }; + let meta_list = match attr.parse_args_with(Punctuated::::parse_terminated) + { + Ok(list) => list, + Err(e) => abort!(attr.span(), "Failed to parse getset attribute: {}", e), + }; let (last, skip, mut collected) = meta_list .into_iter() @@ -357,9 +343,9 @@ fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // Is it a struct? - if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data { + if let Data::Struct(DataStruct { ref fields, .. }) = ast.data { // Handle unary struct - if matches!(fields, syn::Fields::Unnamed(_)) { + if matches!(fields, Fields::Unnamed(_)) { if fields.len() != 1 { abort_call_site!("Only support unary struct!"); } From 971ceee49b5c536debbeb92cd9ce28a99a43b134 Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Wed, 9 Apr 2025 15:40:05 +0000 Subject: [PATCH 4/5] Add `impl_attrs` feature. --- src/generate.rs | 12 +++++---- src/lib.rs | 64 +++++++++++++++++++++++++++++++++------------ tests/impl_attrs.rs | 1 + 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/generate.rs b/src/generate.rs index 3f53ab9..2317a17 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,7 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use proc_macro_error2::abort; use syn::{ - self, ext::IdentExt, spanned::Spanned, Expr, Field, Lit, Meta, MetaNameValue, Visibility, + self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Lit, Meta, MetaNameValue, + Visibility, }; use self::GenMode::{Get, GetClone, GetCopy, GetMut, Set, SetWith}; @@ -10,6 +11,7 @@ use super::parse_attr; pub struct GenParams { pub mode: GenMode, pub global_attr: Option, + pub impl_attrs: Vec, } #[derive(PartialEq, Eq, Copy, Clone)] @@ -58,7 +60,7 @@ impl GenMode { } // Helper function to extract string from Expr -fn expr_to_string(expr: &Expr) -> Option { +pub(crate) fn expr_to_string(expr: &Expr) -> Option { if let Expr::Lit(expr_lit) = expr { if let Lit::Str(s) = &expr_lit.lit { Some(s.value()) @@ -111,7 +113,7 @@ fn has_prefix_attr(f: &Field, params: &GenParams) -> bool { let field_attr_has_prefix = f .attrs .iter() - .filter_map(|attr| parse_attr(attr, params.mode)) + .filter_map(|attr| parse_attr(attr, params.mode, false).0) .find(|meta| { meta.path().is_ident("get") || meta.path().is_ident("get_clone") @@ -161,7 +163,7 @@ pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 { let attr = field .attrs .iter() - .filter_map(|v| parse_attr(v, params.mode)) + .filter_map(|v| parse_attr(v, params.mode, false).0) .last() .or_else(|| params.global_attr.clone()); @@ -236,7 +238,7 @@ pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 let attr = field .attrs .iter() - .filter_map(|v| parse_attr(v, params.mode)) + .filter_map(|v| parse_attr(v, params.mode, false).0) .last() .or_else(|| params.global_attr.clone()); let ty = field.ty.clone(); diff --git a/src/lib.rs b/src/lib.rs index 0372307..d5b6fa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,11 +209,11 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error2::{abort, abort_call_site, proc_macro_error}; use syn::{ - parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataStruct, - DeriveInput, Fields, Meta, Token, + parse_macro_input, parse_str, punctuated::Punctuated, spanned::Spanned, Attribute, Data, + DataStruct, DeriveInput, Fields, ItemImpl, Meta, Token, }; -use crate::generate::{GenMode, GenParams}; +use crate::generate::{expr_to_string, GenMode, GenParams}; mod generate; @@ -272,13 +272,39 @@ pub fn with_setters(input: TokenStream) -> TokenStream { } fn make_params(attrs: &[Attribute], mode: GenMode) -> GenParams { + let mut impl_attrs = vec![]; GenParams { mode, - global_attr: attrs.iter().filter_map(|v| parse_attr(v, mode)).last(), + global_attr: attrs + .iter() + .filter_map(|v| { + let (attr, impl_attrs_exist) = parse_attr(v, mode, true); + if let Some(Meta::NameValue(code)) = &impl_attrs_exist { + match expr_to_string(&code.value) { + Some(code_str) => { + match parse_str::(&format!("{} impl _ {{}}", code_str)) { + Ok(parsed_impl) => impl_attrs.extend(parsed_impl.attrs), + Err(_) => abort!( + code.value.span(), + "Syntax error, expected attributes like #[..]." + ), + } + } + None => abort!(code.value.span(), "Expected string."), + } + } + attr + }) + .last(), + impl_attrs, } } -fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { +fn parse_attr( + attr: &Attribute, + mode: GenMode, + globally_called: bool, +) -> (Option, Option) { if attr.path().is_ident("getset") { let meta_list = match attr.parse_args_with(Punctuated::::parse_terminated) { @@ -286,7 +312,7 @@ fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { Err(e) => abort!(attr.span(), "Failed to parse getset attribute: {}", e), }; - let (last, skip, mut collected) = meta_list + let (last, skip, impl_attrs, mut collected) = meta_list .into_iter() .inspect(|meta| { if !(meta.path().is_ident("get") @@ -295,21 +321,24 @@ fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { || meta.path().is_ident("get_mut") || meta.path().is_ident("set") || meta.path().is_ident("set_with") - || meta.path().is_ident("skip")) + || meta.path().is_ident("skip") + || (meta.path().is_ident("impl_attrs") && globally_called)) { abort!(meta.path().span(), "unknown setter or getter") } }) .fold( - (None, None, Vec::new()), - |(last, skip, mut collected), meta| { + (None, None, None, Vec::new()), + |(last, skip, impl_attrs, mut collected), meta| { if meta.path().is_ident(mode.name()) { - (Some(meta), skip, collected) + (Some(meta), skip, impl_attrs, collected) } else if meta.path().is_ident("skip") { - (last, Some(meta), collected) + (last, Some(meta), impl_attrs, collected) + } else if meta.path().is_ident("impl_attrs") { + (last, skip, Some(meta), collected) } else { collected.push(meta); - (last, skip, collected) + (last, skip, impl_attrs, collected) } }, ); @@ -318,7 +347,7 @@ fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { // Check if there is any setter or getter used with skip, which is // forbidden. if last.is_none() && collected.is_empty() { - skip + (skip, impl_attrs) } else { abort!( last.or_else(|| collected.pop()).unwrap().path().span(), @@ -326,18 +355,19 @@ fn parse_attr(attr: &Attribute, mode: GenMode) -> Option { ); } } else { - last + (last, impl_attrs) } } else if attr.path().is_ident(mode.name()) { // If skip is not used, return the last occurrence of matching // setter/getter, if there is any. - attr.meta.clone().into() + (attr.meta.clone().into(), None) } else { - None + (None, None) } } fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { + let impl_attrs = ¶ms.impl_attrs; let name = &ast.ident; let generics = &ast.generics; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -354,6 +384,7 @@ fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { let generated = generate::implement_for_unnamed(field, params); quote! { + #(#impl_attrs)* impl #impl_generics #name #ty_generics #where_clause { #generated } @@ -362,6 +393,7 @@ fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { let generated = fields.iter().map(|f| generate::implement(f, params)); quote! { + #(#impl_attrs)* impl #impl_generics #name #ty_generics #where_clause { #(#generated)* } diff --git a/tests/impl_attrs.rs b/tests/impl_attrs.rs index c256ab4..55f2bca 100644 --- a/tests/impl_attrs.rs +++ b/tests/impl_attrs.rs @@ -1,3 +1,4 @@ +use fixture::add_1_to_implementation; use getset::{CloneGetters, CopyGetters}; #[derive(CopyGetters, CloneGetters)] From 22073c48ee13f6c5d1e7ebf44599c83df365611b Mon Sep 17 00:00:00 2001 From: guzman-raphael Date: Wed, 16 Apr 2025 13:29:08 +0000 Subject: [PATCH 5/5] Skip coverage on generated `impl` blocks (See [rust-lang/rust#120185](https://github.com/rust-lang/rust/pull/120185)) --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d5b6fa7..10460ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,6 +385,7 @@ fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { quote! { #(#impl_attrs)* + #[automatically_derived] impl #impl_generics #name #ty_generics #where_clause { #generated } @@ -394,6 +395,7 @@ fn produce(ast: &DeriveInput, params: &GenParams) -> TokenStream2 { quote! { #(#impl_attrs)* + #[automatically_derived] impl #impl_generics #name #ty_generics #where_clause { #(#generated)* }