1use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
2use proc_macro_error2::abort;
3use syn::{
4 self, ext::IdentExt, spanned::Spanned, Expr, Field, Lit, Meta, MetaNameValue, Visibility,
5};
6
7use self::GenMode::{Get, GetClone, GetCopy, GetMut, Set, SetWith};
8use super::parse_attr;
9
10pub struct GenParams {
11 pub mode: GenMode,
12 pub global_attr: Option<Meta>,
13}
14
15#[derive(PartialEq, Eq, Copy, Clone)]
16pub enum GenMode {
17 Get,
18 GetClone,
19 GetCopy,
20 GetMut,
21 Set,
22 SetWith,
23}
24
25impl GenMode {
26 pub fn name(self) -> &'static str {
27 match self {
28 Get => "get",
29 GetClone => "get_clone",
30 GetCopy => "get_copy",
31 GetMut => "get_mut",
32 Set => "set",
33 SetWith => "set_with",
34 }
35 }
36
37 pub fn prefix(self) -> &'static str {
38 match self {
39 Get | GetClone | GetCopy | GetMut => "",
40 Set => "set_",
41 SetWith => "with_",
42 }
43 }
44
45 pub fn suffix(self) -> &'static str {
46 match self {
47 Get | GetClone | GetCopy | Set | SetWith => "",
48 GetMut => "_mut",
49 }
50 }
51
52 fn is_get(self) -> bool {
53 match self {
54 Get | GetClone | GetCopy | GetMut => true,
55 Set | SetWith => false,
56 }
57 }
58}
59
60fn expr_to_string(expr: &Expr) -> Option<String> {
62 if let Expr::Lit(expr_lit) = expr {
63 if let Lit::Str(s) = &expr_lit.lit {
64 Some(s.value())
65 } else {
66 None
67 }
68 } else {
69 None
70 }
71}
72
73fn parse_vis_str(s: &str, span: proc_macro2::Span) -> Visibility {
75 match syn::parse_str(s) {
76 Ok(vis) => vis,
77 Err(e) => abort!(span, "Invalid visibility found: {}", e),
78 }
79}
80
81pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> {
83 let meta = attr?;
84 let Meta::NameValue(MetaNameValue { value, path, .. }) = meta else {
85 return None;
86 };
87
88 if !path.is_ident(meta_name) {
89 return None;
90 }
91
92 let value_str = expr_to_string(value)?;
93 let vis_str = value_str.split(' ').find(|v| *v != "with_prefix")?;
94
95 Some(parse_vis_str(vis_str, value.span()))
96}
97
98fn has_prefix_attr(f: &Field, params: &GenParams) -> bool {
101 let meta_has_prefix = |meta: &Meta| -> bool {
103 if let Meta::NameValue(name_value) = meta {
104 if let Some(s) = expr_to_string(&name_value.value) {
105 return s.split(" ").any(|v| v == "with_prefix");
106 }
107 }
108 false
109 };
110
111 let field_attr_has_prefix = f
112 .attrs
113 .iter()
114 .filter_map(|attr| parse_attr(attr, params.mode))
115 .find(|meta| {
116 meta.path().is_ident("get")
117 || meta.path().is_ident("get_clone")
118 || meta.path().is_ident("get_copy")
119 || meta.path().is_ident("get_mut")
120 })
121 .as_ref()
122 .is_some_and(meta_has_prefix);
123
124 let global_attr_has_prefix = params.global_attr.as_ref().is_some_and(meta_has_prefix);
125
126 field_attr_has_prefix || global_attr_has_prefix
127}
128
129pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
130 let field_name = field
131 .ident
132 .clone()
133 .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name"));
134
135 let fn_name = if !has_prefix_attr(field, params)
136 && (params.mode.is_get())
137 && params.mode.suffix().is_empty()
138 && field_name.to_string().starts_with("r#")
139 {
140 field_name.clone()
141 } else {
142 Ident::new(
143 &format!(
144 "{}{}{}{}",
145 if has_prefix_attr(field, params) && (params.mode.is_get()) {
146 "get_"
147 } else {
148 ""
149 },
150 params.mode.prefix(),
151 field_name.unraw(),
152 params.mode.suffix()
153 ),
154 Span::call_site(),
155 )
156 };
157 let ty = field.ty.clone();
158
159 let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc"));
160
161 let attr = field
162 .attrs
163 .iter()
164 .filter_map(|v| parse_attr(v, params.mode))
165 .next_back()
166 .or_else(|| params.global_attr.clone());
167
168 let visibility = parse_visibility(attr.as_ref(), params.mode.name());
169 match attr {
170 Some(meta) if meta.path().is_ident("skip") => quote! {},
172 Some(_) => match params.mode {
173 Get => {
174 quote! {
175 #(#doc)*
176 #[inline(always)]
177 #visibility fn #fn_name(&self) -> &#ty {
178 &self.#field_name
179 }
180 }
181 }
182 GetClone => {
183 quote! {
184 #(#doc)*
185 #[inline(always)]
186 #visibility fn #fn_name(&self) -> #ty {
187 self.#field_name.clone()
188 }
189 }
190 }
191 GetCopy => {
192 quote! {
193 #(#doc)*
194 #[inline(always)]
195 #visibility fn #fn_name(&self) -> #ty {
196 self.#field_name
197 }
198 }
199 }
200 Set => {
201 quote! {
202 #(#doc)*
203 #[inline(always)]
204 #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self {
205 self.#field_name = val;
206 self
207 }
208 }
209 }
210 GetMut => {
211 quote! {
212 #(#doc)*
213 #[inline(always)]
214 #visibility fn #fn_name(&mut self) -> &mut #ty {
215 &mut self.#field_name
216 }
217 }
218 }
219 SetWith => {
220 quote! {
221 #(#doc)*
222 #[inline(always)]
223 #visibility fn #fn_name(mut self, val: #ty) -> Self {
224 self.#field_name = val;
225 self
226 }
227 }
228 }
229 },
230 None => quote! {},
231 }
232}
233
234pub fn implement_for_unnamed(field: &Field, params: &GenParams) -> TokenStream2 {
235 let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc"));
236 let attr = field
237 .attrs
238 .iter()
239 .filter_map(|v| parse_attr(v, params.mode))
240 .next_back()
241 .or_else(|| params.global_attr.clone());
242 let ty = field.ty.clone();
243 let visibility = parse_visibility(attr.as_ref(), params.mode.name());
244
245 match attr {
246 Some(meta) if meta.path().is_ident("skip") => quote! {},
248 Some(_) => match params.mode {
249 Get => {
250 let fn_name = Ident::new("get", Span::call_site());
251 quote! {
252 #(#doc)*
253 #[inline(always)]
254 #visibility fn #fn_name(&self) -> &#ty {
255 &self.0
256 }
257 }
258 }
259 GetClone => {
260 let fn_name = Ident::new("get", Span::call_site());
261 quote! {
262 #(#doc)*
263 #[inline(always)]
264 #visibility fn #fn_name(&self) -> #ty {
265 self.0.clone()
266 }
267 }
268 }
269 GetCopy => {
270 let fn_name = Ident::new("get", Span::call_site());
271 quote! {
272 #(#doc)*
273 #[inline(always)]
274 #visibility fn #fn_name(&self) -> #ty {
275 self.0
276 }
277 }
278 }
279 Set => {
280 let fn_name = Ident::new("set", Span::call_site());
281 quote! {
282 #(#doc)*
283 #[inline(always)]
284 #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self {
285 self.0 = val;
286 self
287 }
288 }
289 }
290 GetMut => {
291 let fn_name = Ident::new("get_mut", Span::call_site());
292 quote! {
293 #(#doc)*
294 #[inline(always)]
295 #visibility fn #fn_name(&mut self) -> &mut #ty {
296 &mut self.0
297 }
298 }
299 }
300 SetWith => {
301 let fn_name = Ident::new("set_with", Span::call_site());
302 quote! {
303 #(#doc)*
304 #[inline(always)]
305 #visibility fn #fn_name(mut self, val: #ty) -> Self {
306 self.0 = val;
307 self
308 }
309 }
310 }
311 },
312 None => quote! {},
313 }
314}