1use 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 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 abort_call_site!("#[derive(Getset2)] is only defined for structs!");
211 }
212}