getter_methods/
lib.rs

1//! `getter_methods` is a derive macro that will implement accessor methods for each field on the
2//! struct.
3//!
4//! Using `getter_methods` is straightforward: simply derive it:
5//!
6//! ```
7//! use getter_methods::Getters;
8//!
9//! #[derive(Getters)]
10//! struct Foo {
11//!   bar: String,
12//!   baz: i64,
13//! }
14//!
15//! # fn main() {
16//! let foo = Foo { bar: "bacon".into(), baz: 42 };
17//! assert_eq!(foo.bar(), "bacon");
18//! assert_eq!(foo.baz(), 42);
19//! # }
20//! ```
21//!
22//! ## Return types
23//!
24//! Accessors will get a convenient return type depending on the type of the field on the struct:
25//!
26//! Struct Field                 | Accessor Return Type
27//! ---------------------------- | --------------------
28//! Primitive `T` (e.g. [`i64`]) | `T`
29//! [`String`]                   | [`&str`][`str`]
30//! [`Vec<T>`][Vec]              | `&[T]`
31//! [`Box<T>`][Box]              | `&T`
32//! [`Option<T>`][Option]        | `Option<&T>`
33//! [`Rc<T>`][std::rc::Rc]       | `Rc<T>`
34//! [`Arc<T>`][std::sync::Arc]   | `Arc<T>`
35//! Any `&'a T`                  | `&'a T`
36//! Any `*T`                     | `*T`
37//! Any other `T`                | `&T`
38//!
39//! ## Returning Copies
40//!
41//! If you want a non-primitive `T` that implements `Copy` to return itself rather than a
42//! reference, annotate it with `#[getters(copy)]`:
43//!
44//! ```
45//! use getter_methods::Getters;
46//!
47//! #[derive(Getters)]
48//! struct Foo {
49//!   #[getters(copy)]
50//!   bar: Option<i64>,
51//! }
52//! # fn main() {}
53//! ```
54//!
55//! ## Skipping fields
56//!
57//! If you don't want a certain field to have an accessor method, annotate it:
58//!
59//! ```compile_fail
60//! use getter_methods::Getters;
61//!
62//! #[derive(Getters)]
63//! struct Foo {
64//!   bar: String,
65//!   #[getters(skip)]
66//!   baz: i64,
67//! }
68//!
69//! # fn main() {
70//! let foo = Foo { bar: "bacon".into(), baz: 42 }
71//! assert_eq!(foo.bar(), "bacon");
72//! assert_eq!(foo.baz(), 42);  // Compile error: There is no `foo.baz()`.
73//! # }
74//! ```
75//!
76//! ## Documentation
77//!
78//! Any docstrings used on the fields are copied to the accessor method.
79
80use 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/// Derive accessor or "getter" methods for each field on the struct.
89///
90/// The output types are determined based on the input types, and follow the following rules:
91///
92/// 1. Primitives (e.g. [`i64`]) return a copy of themselves.
93/// 2. [`String`] fields return [`&str`][`str`].
94/// 3. [`Vec<T>`][Vec] fields return `&[T]`
95/// 4. [`Box<T>`][Box] fields return `&T`.
96/// 5. [`Option<T>`][Option] fields return `Option<&T>`.
97/// 6. [`Rc<T>`][std::rc::Rc] and [`Arc<T>`][std::sync::Arc] return a clone of themselves.
98/// 7. References and pointers return copies of themselves.
99/// 8. Fields of any other type `T` return `&T`.
100///
101/// Note: You can use `#[getters(copy)] to override rule 6 and make other types that implement
102/// [`Copy`] also return copies; this can be done either on the struct or on individual fields.
103#[proc_macro_derive(Getters, attributes(getters))]
104pub fn derive_getter_methods(input: TokenStream1) -> TokenStream1 {
105  getters(input.into()).unwrap_or_else(|e| e.into_compile_error()).into()
106}
107
108/// Get the ident of a meta as a &str. Used for clean `match` statements.
109macro_rules! ident_str {
110  ($meta:ident ) => {
111    $meta.path.get_ident().map(|i| i.to_string()).unwrap_or_default().as_str()
112  };
113}
114
115/// Create a `syn` Error.
116macro_rules! error {
117 ($msg:literal $(,$e:expr)*) => {
118  syn::Error::new(proc_macro2::Span::call_site(), format!($msg $(,$e)*))
119 }
120}
121
122/// Parse out the generic of a generic type, e.g. `T` from `Vec<T>`.
123macro_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  // Parse the tokens as a struct.
136  let struct_ = syn::parse2::<syn::ItemStruct>(input)?;
137
138  // Look for attributes that may modify behavior.
139  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  // Iterate over each field and create an accessor method.
155  for field in struct_.fields {
156    // Do we need to do anything unusual?
157    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    // Preserve documentation from the field to the method.
177    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    // Render the appropriate accessor method.
198    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          // 1. Primitives return copies of themselves.
209          "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          // 2. `String` returns `&str`
213          "String" => (quote! { &str }, quote! { self.#field_ident.as_str() }),
214          // 3. `Vec<T>` returns `&[T]`.
215          "Vec" => {
216            let ty = generic!(segment);
217            (quote! { &[#ty] }, quote! { self.#field_ident.as_slice() })
218          },
219          // 4. `Box<T>` returns `&T`.
220          "Box" => {
221            let ty = generic!(segment);
222            (quote! { &#ty }, quote! { &self.#field_ident })
223          },
224          // 5. `Option<T>` returns `Option<&T>` (unless `#[getters(copy)]`).
225          "Option" => {
226            let ty = generic!(segment);
227            match field_opts.copy {
228              true => (quote! { Option<#ty> }, quote! { self.#field_ident }),
229              false => (quote! { Option<&#ty> }, quote! { self.#field_ident.as_ref() }),
230            }
231          },
232          // 6. `Rc<T>` and `Arc<T>` return clones.
233          "Rc" => {
234            let ty = generic!(segment);
235            const_ = quote! {};
236            (quote! { ::std::rc::Rc<#ty> }, quote! { ::std::rc::Rc::clone(&self.#field_ident) })
237          },
238          "Arc" => {
239            let ty = generic!(segment);
240            const_ = quote! {};
241            (
242              quote! { ::std::sync::Arc<#ty> },
243              quote! { ::std::sync::Arc::clone(&self.#field_ident)},
244            )
245          },
246          // 8. Fields of any other type `T` return `&T`.
247          _ => match field_opts.copy {
248            true => (quote! { #field_type }, quote! { self.#field_ident }),
249            false => (quote! { &#field_type }, quote! { &self.#field_ident }),
250          },
251        }
252      },
253      // 7. References and pointers return copies of themselves.
254      syn::Type::Reference(ref_) => (quote! { #ref_ }, quote! { self.#field_ident }),
255      syn::Type::Ptr(ptr) => (quote! { #ptr }, quote! { self.#field_ident }),
256      _ => (quote! { &#field_type }, quote! { &self.#field_ident }),
257    };
258    getters.push(quote! {
259      #doc #[inline]
260      #vis #const_ fn #field_ident(&self) -> #return_type {
261        #getter_impl
262      }
263    });
264  }
265
266  // Write the final output with the accessor implementation.
267  let ident = &struct_.ident;
268  let (impl_generics, ty_generics, where_clause) = &struct_.generics.split_for_impl();
269  Ok(quote! {
270    #[automatically_derived]
271    impl #impl_generics #ident #ty_generics #where_clause {
272      #(#getters)*
273    }
274  })
275}
276
277/// The options for the full struct.
278struct StructOptions {
279  copy: bool,
280  vis: Visibility,
281}
282
283impl StructOptions {
284  /// Initialize a [`FieldOptions`] with inherited defaults.
285  fn field_opts(&self) -> FieldOptions {
286    FieldOptions { copy: self.copy, skip: false, vis: self.vis.clone() }
287  }
288}
289
290impl Default for StructOptions {
291  fn default() -> Self {
292    Self { copy: false, vis: Visibility::Public(syn::Token![pub](Span::call_site())) }
293  }
294}
295
296/// The optoions for a specific field.
297struct FieldOptions {
298  copy: bool,
299  skip: bool,
300  vis: Visibility,
301}