1use proc_macro::TokenStream as TokenStream1;
81use proc_macro2::Span;
82use proc_macro2::TokenStream;
83use quote::ToTokens;
84use quote::quote;
85use syn::Visibility;
86use syn::spanned::Spanned;
87
88#[proc_macro_derive(Getters, attributes(doc, getters))]
104pub fn derive_getter_methods(input: TokenStream1) -> TokenStream1 {
105 getters(input.into()).unwrap_or_else(|e| e.into_compile_error()).into()
106}
107
108macro_rules! ident_str {
110 ($meta:ident ) => {
111 $meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default().as_str()
112 };
113}
114
115macro_rules! error {
117 ($msg:literal $(,$e:expr)*) => {
118 syn::Error::new(proc_macro2::Span::call_site(), format!($msg $(,$e)*))
119 }
120}
121
122macro_rules! generic {
124 ($segment:ident) => {{
125 let syn::PathArguments::AngleBracketed(angle_generic) = &$segment.arguments else {
126 return Err(error!("Unparseable type: {}", $segment.to_token_stream().to_string()));
127 };
128 angle_generic.args.clone()
129 }};
130}
131
132fn getters(input: TokenStream) -> syn::Result<TokenStream> {
133 let mut getters: Vec<TokenStream> = vec![];
134
135 let struct_ = syn::parse2::<syn::ItemStruct>(input)?;
137
138 let struct_opts = {
140 let mut struct_opts = StructOptions::default();
141 if let Some(attr) = struct_.attrs.iter().find(|a| a.path().is_ident("getters")) {
142 attr.parse_nested_meta(|meta| {
143 match ident_str!(meta) {
144 "copy" => struct_opts.copy = true,
145 "vis" => struct_opts.vis = meta.value()?.parse::<Visibility>()?,
146 other => Err(error!("Invalid option: {}", other))?,
147 };
148 Ok(())
149 })?;
150 };
151 struct_opts
152 };
153
154 for field in struct_.fields {
156 let field_opts = {
158 let mut field_opts = struct_opts.field_opts();
159 if let Some(getters_attr) = field.attrs.iter().find(|a| a.path().is_ident("getters")) {
160 getters_attr.parse_nested_meta(|meta| {
161 match ident_str!(meta) {
162 "copy" => field_opts.copy = true,
163 "skip" => field_opts.skip = true,
164 "vis" => field_opts.vis = meta.value()?.parse::<Visibility>()?,
165 other => Err(error!("Invalid option: {}", other))?,
166 }
167 Ok(())
168 })?;
169 }
170 if field_opts.skip {
171 continue;
172 }
173 field_opts
174 };
175
176 let doc = {
178 let mut answer = String::new();
179 for doc_attr in field.attrs.iter().filter(|d| d.path().is_ident("doc")) {
180 if let syn::Meta::NameValue(nv) = &doc_attr.meta {
181 if let syn::Expr::Lit(l) = &nv.value {
182 if let syn::Lit::Str(s) = &l.lit {
183 if !answer.is_empty() {
184 answer += "\n";
185 }
186 answer += s.value().as_str();
187 }
188 }
189 }
190 }
191 match answer.is_empty() {
192 true => quote! {},
193 false => quote! { #[doc = #answer] },
194 }
195 };
196
197 let span = field.span();
199 let field_ident =
200 &field.ident.ok_or_else(|| syn::Error::new(span, "Fields must be named."))?;
201 let field_type = &field.ty;
202 let vis = &field_opts.vis;
203 let mut const_ = quote! { const };
204 let (return_type, getter_impl) = match field_type {
205 syn::Type::Path(p) => {
206 let Some(segment) = &p.path.segments.last() else { Err(error!("Unparseable type."))? };
207 match segment.ident.to_string().as_str() {
208 "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
210 | "u128" | "usize" | "f32" | "f64" | "char" =>
211 (quote! { #field_type }, quote! { self.#field_ident }),
212 "String" => (quote! { &str }, quote! { self.#field_ident.as_str() }),
214 "Vec" => {
216 let ty = generic!(segment);
217 (quote! { &[#ty] }, quote! { self.#field_ident.as_slice() })
218 },
219 "Box" => {
221 let ty = generic!(segment);
222 (quote! { &#ty }, quote! { &self.#field_ident })
223 },
224 "Option" => {
226 let ty = generic!(segment);
227 (quote! { Option<&#ty> }, quote! { self.#field_ident.as_ref() })
228 },
229 "Rc" => {
231 let ty = generic!(segment);
232 const_ = quote! {};
233 (quote! { ::std::rc::Rc<#ty> }, quote! { ::std::rc::Rc::clone(&self.#field_ident) })
234 },
235 "Arc" => {
236 let ty = generic!(segment);
237 const_ = quote! {};
238 (
239 quote! { ::std::sync::Arc<#ty> },
240 quote! { ::std::sync::Arc::clone(&self.#field_ident)},
241 )
242 },
243 _ => match field_opts.copy {
245 true => (quote! { #field_type }, quote! { self.#field_ident }),
246 false => (quote! { &#field_type }, quote! { &self.#field_ident }),
247 },
248 }
249 },
250 syn::Type::Reference(ref_) => (quote! { #ref_ }, quote! { self.#field_ident }),
252 syn::Type::Ptr(ptr) => (quote! { #ptr }, quote! { self.#field_ident }),
253 _ => (quote! { &#field_type }, quote! { &self.#field_ident }),
254 };
255 getters.push(quote! {
256 #doc #[inline]
257 #vis #const_ fn #field_ident(&self) -> #return_type {
258 #getter_impl
259 }
260 });
261 }
262
263 let ident = &struct_.ident;
265 let (impl_generics, ty_generics, where_clause) = &struct_.generics.split_for_impl();
266 Ok(quote! {
267 #[automatically_derived]
268 impl #impl_generics #ident #ty_generics #where_clause {
269 #(#getters)*
270 }
271 })
272}
273
274struct StructOptions {
276 copy: bool,
277 vis: Visibility,
278}
279
280impl StructOptions {
281 fn field_opts(&self) -> FieldOptions {
283 FieldOptions { copy: self.copy, skip: false, vis: self.vis.clone() }
284 }
285}
286
287impl Default for StructOptions {
288 fn default() -> Self {
289 Self { copy: false, vis: Visibility::Public(syn::Token)) }
290 }
291}
292
293struct FieldOptions {
295 copy: bool,
296 skip: bool,
297 vis: Visibility,
298}