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}