getset2/
lib.rs

1/*!
2Getset2 is a derive macro, which is inspired by [getset](https://crates.io/crates/getset),
3is designed for generating the most basic getters and setters on struct fields.
4
5```rust
6use getset2::Getset2;
7
8#[derive(Default, Getset2)]
9#[getset2(get_ref, set_with)]
10pub struct Foo<T>
11where
12    T: Copy + Clone + Default,
13{
14    /// Doc comments are supported!
15    /// Multiline, even.
16    #[getset2(set, get_mut, skip(get_ref))]
17    private: T,
18
19    /// Doc comments are supported!
20    /// Multiline, even.
21    #[getset2(
22        get_copy(pub, const),
23        set(pub = "crate"),
24        get_mut(pub = "super"),
25        set_with(pub = "self", const)
26    )]
27    public: T,
28
29    #[getset2(skip)]
30    skip: (),
31}
32
33impl<T: Copy + Clone + Default> Foo<T> {
34    fn private(&self) -> &T {
35        &self.private
36    }
37    fn skip(&self) {
38        self.skip
39    }
40}
41
42// cargo expand --example simple
43
44```
45*/
46
47use quote::quote;
48
49use std::collections::HashSet;
50
51use proc_macro::TokenStream;
52use proc_macro2::TokenStream as TokenStream2;
53use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
54use syn::{
55    meta::ParseNestedMeta, parse_macro_input, Attribute, DataStruct, DeriveInput, LitStr,
56    Visibility,
57};
58
59use crate::generate::{GenMode, GenParams};
60
61mod generate;
62
63#[proc_macro_derive(Getset2, attributes(getset2))]
64#[proc_macro_error]
65pub fn getset2(input: TokenStream) -> TokenStream {
66    let ast = parse_macro_input!(input as DeriveInput);
67    produce(&ast, &new_global_gen_params_list(&ast.attrs)).into()
68}
69
70fn new_global_gen_params_list(attrs: &[Attribute]) -> Vec<GenParams> {
71    let mut list = vec![];
72    let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("getset2")) else {
73        return list;
74    };
75    let (a, skip_list) = parse_attr(attr);
76    if !skip_list.is_empty() {
77        abort!(
78            attr,
79            "The attribute of the structure do not support `skip` ident."
80        )
81    }
82    list.extend_from_slice(&a);
83    if list.iter().any(|p| p.mode == GenMode::GetCopy) {
84        list.retain_mut(|p| p.mode != GenMode::GetRef);
85    }
86    list
87}
88
89fn parse_attr(attr: &Attribute) -> (Vec<GenParams>, HashSet<GenMode>) {
90    let mut skip_list: HashSet<GenMode> = HashSet::new();
91    let mut params_list = vec![];
92    let mut had_ref_copy = false;
93    let _ = attr.parse_nested_meta(|sub_attr| {
94        match &sub_attr.path {
95            p if p.is_ident("skip") => {
96                skip_list.extend(parse_skip_attr(&sub_attr, attr).iter());
97            }
98            p if p.is_ident("get_ref") => {
99                if !had_ref_copy {
100                    params_list.push(new_gen_params(GenMode::GetRef, &sub_attr, attr));
101                    had_ref_copy = true
102                }
103            }
104            p if p.is_ident("get_copy") => {
105                if !had_ref_copy {
106                    params_list.push(new_gen_params(GenMode::GetCopy, &sub_attr, attr));
107                    had_ref_copy = true
108                }
109            }
110            p if p.is_ident("get_mut") => {
111                params_list.push(new_gen_params(GenMode::GetMut, &sub_attr, attr))
112            }
113            p if p.is_ident("set") => {
114                params_list.push(new_gen_params(GenMode::Set, &sub_attr, attr))
115            }
116            p if p.is_ident("set_with") => {
117                params_list.push(new_gen_params(GenMode::SetWith, &sub_attr, attr))
118            }
119            _ => abort!(attr, "Invalid attribute {}", quote! {attr}),
120        }
121        Ok(())
122    });
123    params_list.retain(|p| !skip_list.contains(&p.mode));
124    (params_list, skip_list)
125}
126
127fn parse_skip_attr(meta: &ParseNestedMeta<'_>, attr: &Attribute) -> Vec<GenMode> {
128    if meta.input.is_empty() {
129        return GenMode::list().to_vec();
130    }
131    let mut list = vec![];
132    let _ = meta.parse_nested_meta(|pp| {
133        match &pp.path {
134            p if p.is_ident("get_ref") => list.push(GenMode::GetRef),
135            p if p.is_ident("get_copy") => list.push(GenMode::GetCopy),
136            p if p.is_ident("get_mut") => list.push(GenMode::GetMut),
137            p if p.is_ident("set") => list.push(GenMode::Set),
138            p if p.is_ident("set_with") => list.push(GenMode::SetWith),
139            _ => abort!(
140                attr,
141                "The `skip` in the attributes is invalid {}",
142                quote! {attr}
143            ),
144        }
145        Ok(())
146    });
147    list
148}
149
150fn new_gen_params(gen_mode: GenMode, p: &ParseNestedMeta<'_>, attr: &Attribute) -> GenParams {
151    let mut vis = None;
152    let mut is_const = None;
153    let _ = p.parse_nested_meta(|pp| {
154        let (_vis, _is_const) = parse_vis_meta(&pp, attr);
155        if let Some(x) = _vis {
156            vis = Some(x);
157        }
158        if let Some(x) = _is_const {
159            is_const = Some(x);
160        }
161        Ok(())
162    });
163    GenParams {
164        mode: gen_mode,
165        vis,
166        is_const,
167    }
168}
169
170fn parse_vis_meta(p: &ParseNestedMeta<'_>, attr: &Attribute) -> (Option<Visibility>, Option<bool>) {
171    match &p.path {
172        x if x.is_ident("const") => (None, Some(true)),
173        x if x.is_ident("pub") => match p.value() {
174            Ok(v) => match v.parse::<LitStr>() {
175                Ok(vv) => (
176                    Some(match vv.value().as_str() {
177                        "crate" => syn::parse_str("pub(crate)").unwrap(),
178                        "super" => syn::parse_str("pub(crate)").unwrap(),
179                        "self" => syn::parse_str("pub(self)").unwrap(),
180                        x => abort!(attr, "Invalid visibility found: pub = \"{}\"", x),
181                    }),
182                    None,
183                ),
184                Err(e) => abort!(attr, "Invalid visibility found1: {}", e),
185            },
186            Err(_e) => (Some(syn::parse_str("pub").unwrap()), None),
187        },
188        _ => (None, None),
189    }
190}
191
192fn produce(ast: &DeriveInput, global_params_list: &[GenParams]) -> TokenStream2 {
193    let name = &ast.ident;
194    let generics = &ast.generics;
195    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
196
197    // Is it a struct?
198    if let syn::Data::Struct(DataStruct { ref fields, .. }) = ast.data {
199        let generated = fields
200            .iter()
201            .map(|f| generate::implement(f, global_params_list));
202
203        quote! {
204            impl #impl_generics #name #ty_generics #where_clause {
205                #(#generated)*
206            }
207        }
208    } else {
209        // Nope. This is an Enum. We cannot handle these!
210        abort_call_site!("#[derive(Getset2)] is only defined for structs!");
211    }
212}