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::GetterMethods;
8//!
9//! #[derive(GetterMethods)]
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//! [`String`]                   | [`&str`][`str`]
29//! Primitive `T` (e.g. [`i64`]) | `T`
30//! Any other `T`                | `&T`
31//!
32//! ## Returning Copies
33//!
34//! If you want a non-primitive `T` that implements `Copy` to return itself rather than a
35//! reference, annotate it with `#[getter_methods(copy)]`:
36//!
37//! ```
38//! use getter_methods::GetterMethods;
39//!
40//! #[derive(GetterMethods)]
41//! struct Foo {
42//!   #[getter_methods(copy)]
43//!   bar: Option<i64>,
44//! }
45//! # fn main() {}
46//! ```
47//!
48//! ## Skipping fields
49//!
50//! If you don't want a certain field to have an accessor method, annotate it:
51//!
52//! ```compile_fail
53//! use getter_methods::GetterMethods;
54//!
55//! #[derive(GetterMethods)]
56//! struct Foo {
57//!   bar: String,
58//!   #[getter_methods(skip)]
59//!   baz: i64,
60//! }
61//!
62//! # fn main() {
63//! let foo = Foo { bar: "bacon".into(), baz: 42 }
64//! assert_eq!(foo.bar(), "bacon");
65//! assert_eq!(foo.baz(), 42);  // Compile error: There is no `foo.baz()`.
66//! # }
67//! ```
68//!
69//! ## Documentation
70//!
71//! Any docstrings used on the fields are copied to the accessor method.
72
73use proc_macro::TokenStream as TokenStream1;
74use proc_macro2::TokenStream;
75use quote::quote;
76use syn::punctuated::Punctuated;
77use syn::spanned::Spanned;
78
79/// Derive accessor or "getter" methods for each field on the struct.
80///
81/// The output types are determined based on the input types, and follow the following rules:
82///
83/// 1. Primitives (e.g. [`i64`]) return a copy of themselves.
84/// 2. [`String`] fields return [`&str`][`str`].
85/// 3. Fields of any other type `T` return `&T`.
86///
87/// Note: You can use `#[getter_methods(copy)] to override rule 3 and make other types that
88/// implement [`Copy`] also return copies; this can be done either on the struct or on individual
89/// fields.
90#[proc_macro_derive(GetterMethods, attributes(doc, getter_methods))]
91pub fn derive_getter_methods(input: TokenStream1) -> TokenStream1 {
92  getters(input.into()).unwrap_or_else(|e| e.into_compile_error()).into()
93}
94
95fn getters(input: TokenStream) -> syn::Result<TokenStream> {
96  let mut getters: Vec<TokenStream> = vec![];
97
98  // Parse the tokens as a struct.
99  let struct_ = syn::parse2::<syn::ItemStruct>(input)?;
100
101  // Look for attributes that may modify behavior.
102  let copy_all = match struct_.attrs.iter().find(|a| a.path().is_ident("getter_methods")) {
103    Some(attr) => {
104      let args =
105        attr.parse_args_with(Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated)?;
106      args[0].path().is_ident("copy")
107    },
108    None => false,
109  };
110
111  // Iterate over each field and create an accessor method.
112  'field: for field in struct_.fields {
113    // Sanity check: Do we need to do anything unusual?
114    let mut copy = copy_all;
115    if let Some(getters_attr) = field.attrs.iter().find(|a| a.path().is_ident("getter_methods")) {
116      let nested =
117        getters_attr.parse_args_with(Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated)?;
118      for m in nested {
119        // Do we need to skip?
120        if m.path().is_ident("skip") {
121          continue 'field;
122        }
123        if m.path().is_ident("copy") {
124          copy = true;
125        }
126      }
127    }
128
129    // Preserve documentation from the field to the method.
130    let doc = {
131      let mut answer = String::new();
132      for doc_attr in field.attrs.iter().filter(|d| d.path().is_ident("doc")) {
133        if let syn::Meta::NameValue(nv) = &doc_attr.meta {
134          if let syn::Expr::Lit(l) = &nv.value {
135            if let syn::Lit::Str(s) = &l.lit {
136              if !answer.is_empty() {
137                answer += "\n";
138              }
139              answer += s.value().as_str();
140            }
141          }
142        }
143      }
144      answer
145    };
146
147    // Render the appropriate accessor method.
148    let span = field.span();
149    let field_ident =
150      &field.ident.ok_or_else(|| syn::Error::new(span, "Fields must be named."))?;
151    let field_type = &field.ty;
152    let (return_type, getter_impl) = match field_type {
153      syn::Type::Path(p) => {
154        let ident = &p.path.segments.last().unwrap().ident;
155        match ident.to_string().as_str() {
156          "String" => (quote! { &str }, quote! { self.#field_ident.as_str() }),
157          "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
158          | "u128" | "usize" | "f32" | "f64" | "char" =>
159            (quote! { #field_type }, quote! { self.#field_ident }),
160          _ => match copy {
161            true => (quote! { #field_type }, quote! { self.#field_ident }),
162            false => (quote! { &#field_type }, quote! { &self.#field_ident }),
163          },
164        }
165      },
166      _ => (quote! { &#field_type }, quote! { &self.#field_ident }),
167    };
168    let const_ = match return_type.to_string() == "& str" {
169      true => quote! {},
170      false => quote! { const },
171    };
172    getters.push(quote! {
173      #[doc = #doc]
174      #[inline]
175      pub #const_ fn #field_ident(&self) -> #return_type {
176        #getter_impl
177      }
178    });
179  }
180
181  // Write the final output with the accessor implementation.
182  let ident = &struct_.ident;
183  let generics = &struct_.generics;
184  Ok(quote! {
185    impl #generics #ident #generics {
186      #(#getters)*
187    }
188  })
189}