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