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};
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 Set,
20 GetMut,
21}
22
23impl GenMode {
24 pub fn name(self) -> &'static str {
25 match self {
26 Get => "get",
27 GetCopy => "get_copy",
28 Set => "set",
29 GetMut => "get_mut",
30 }
31 }
32
33 pub fn prefix(self) -> &'static str {
34 match self {
35 Get | GetCopy | GetMut => "",
36 Set => "set_",
37 }
38 }
39
40 pub fn suffix(self) -> &'static str {
41 match self {
42 Get | GetCopy | Set => "",
43 GetMut => "_mut",
44 }
45 }
46
47 fn is_get(self) -> bool {
48 match self {
49 GenMode::Get | GenMode::GetCopy | GenMode::GetMut => true,
50 GenMode::Set => false,
51 }
52 }
53}
54
55fn expr_to_string(expr: &Expr) -> Option<String> {
57 if let Expr::Lit(expr_lit) = expr {
58 if let Lit::Str(s) = &expr_lit.lit {
59 Some(s.value())
60 } else {
61 None
62 }
63 } else {
64 None
65 }
66}
67
68fn parse_vis_str(s: &str, span: proc_macro2::Span) -> Visibility {
70 match syn::parse_str(s) {
71 Ok(vis) => vis,
72 Err(e) => abort!(span, "Invalid visibility found: {}", e),
73 }
74}
75
76pub fn parse_visibility(attr: Option<&Meta>, meta_name: &str) -> Option<Visibility> {
78 let meta = attr?;
79 let Meta::NameValue(MetaNameValue { value, path, .. }) = meta else {
80 return None;
81 };
82
83 if !path.is_ident(meta_name) {
84 return None;
85 }
86
87 let value_str = expr_to_string(value)?;
88 let vis_str = value_str.split(' ').find(|v| *v != "with_prefix")?;
89
90 Some(parse_vis_str(vis_str, value.span()))
91}
92
93fn has_prefix_attr(f: &Field, params: &GenParams) -> bool {
96 let meta_has_prefix = |meta: &Meta| -> bool {
98 if let Meta::NameValue(name_value) = meta {
99 if let Some(s) = expr_to_string(&name_value.value) {
100 return s.split(" ").any(|v| v == "with_prefix");
101 }
102 }
103 false
104 };
105
106 let field_attr_has_prefix = f
107 .attrs
108 .iter()
109 .filter_map(|attr| parse_attr(attr, params.mode))
110 .find(|meta| meta.path().is_ident("get") || meta.path().is_ident("get_copy"))
111 .as_ref()
112 .map_or(false, meta_has_prefix);
113
114 let global_attr_has_prefix = params.global_attr.as_ref().map_or(false, meta_has_prefix);
115
116 field_attr_has_prefix || global_attr_has_prefix
117}
118
119pub fn implement(field: &Field, params: &GenParams) -> TokenStream2 {
120 let field_name = field
121 .ident
122 .clone()
123 .unwrap_or_else(|| abort!(field.span(), "Expected the field to have a name"));
124
125 let fn_name = if !has_prefix_attr(field, params)
126 && (params.mode.is_get())
127 && params.mode.suffix().is_empty()
128 && field_name.to_string().starts_with("r#")
129 {
130 field_name.clone()
131 } else {
132 Ident::new(
133 &format!(
134 "{}{}{}{}",
135 if has_prefix_attr(field, params) && (params.mode.is_get()) {
136 "get_"
137 } else {
138 ""
139 },
140 params.mode.prefix(),
141 field_name.unraw(),
142 params.mode.suffix()
143 ),
144 Span::call_site(),
145 )
146 };
147 let ty = field.ty.clone();
148
149 let doc = field.attrs.iter().filter(|v| v.meta.path().is_ident("doc"));
150
151 let attr = field
152 .attrs
153 .iter()
154 .filter_map(|v| parse_attr(v, params.mode))
155 .last()
156 .or_else(|| params.global_attr.clone());
157
158 let visibility = parse_visibility(attr.as_ref(), params.mode.name());
159 match attr {
160 Some(meta) if meta.path().is_ident("skip") => quote! {},
162 Some(_) => match params.mode {
163 GenMode::Get => {
164 quote! {
165 #(#doc)*
166 #[inline(always)]
167 #visibility fn #fn_name(&self) -> &#ty {
168 &self.#field_name
169 }
170 }
171 }
172 GenMode::GetCopy => {
173 quote! {
174 #(#doc)*
175 #[inline(always)]
176 #visibility fn #fn_name(&self) -> #ty {
177 self.#field_name
178 }
179 }
180 }
181 GenMode::Set => {
182 quote! {
183 #(#doc)*
184 #[inline(always)]
185 #visibility fn #fn_name(&mut self, val: #ty) -> &mut Self {
186 self.#field_name = val;
187 self
188 }
189 }
190 }
191 GenMode::GetMut => {
192 quote! {
193 #(#doc)*
194 #[inline(always)]
195 #visibility fn #fn_name(&mut self) -> &mut #ty {
196 &mut self.#field_name
197 }
198 }
199 }
200 },
201 None => quote! {},
202 }
203}