derive_getters/
getters.rs
1use std::convert::TryFrom;
3
4use proc_macro2::{TokenStream, Span};
5use quote::{quote, ToTokens};
6use syn::{
7 DeriveInput,
8 FieldsNamed,
9 Type,
10 Ident,
11 LitStr,
12 Result,
13 Error,
14 Attribute,
15 parse::{Parse, ParseStream},
16};
17
18use crate::{
19 extract::{named_fields, named_struct},
20 faultmsg::Problem,
21};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24enum Action {
25 Skip,
26 Rename(Ident),
27}
28
29impl Parse for Action {
30 fn parse(input: ParseStream) -> Result<Self> {
31 syn::custom_keyword!(skip);
32 syn::custom_keyword!(rename);
33
34 if input.peek(skip) {
35 let _ = input.parse::<skip>()?;
36 if !input.is_empty() {
37 Err(Error::new(Span::call_site(), Problem::TokensFollowSkip))
38 } else {
39 Ok(Action::Skip)
40 }
41 } else if input.peek(rename) {
42 let _ = input.parse::<rename>()?;
43 let _ = input.parse::<syn::Token![=]>()?;
44 let name = input.parse::<LitStr>()?;
45 if !input.is_empty() {
46 Err(Error::new(Span::call_site(), Problem::TokensFollowNewName))
47 } else {
48 Ok(Action::Rename(Ident::new(name.value().as_str(), Span::call_site())))
49 }
50 } else {
51 Err(Error::new(Span::call_site(), Problem::InvalidAttribute))
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
57struct Doc(TokenStream);
58
59#[derive(Debug, Clone)]
60struct Work {
61 special: Option<Action>,
63
64 docs: Vec<Doc>,
66}
67
68impl TryFrom<&[Attribute]> for Work {
69 type Error = Error;
70
71 fn try_from(attributes: &[Attribute]) -> Result<Self> {
72 let mut special: Option<Action> = None;
73 let mut docs: Vec<Doc> = Vec::new();
74
75 for attr in attributes {
76 if attr.path.is_ident("getter") {
77 special = Some(attr.parse_args::<Action>()?);
78 }
79
80 if attr.path.is_ident("doc") {
81 docs.push(Doc(attr.to_token_stream()));
82 }
83 }
84
85 Ok(Work { special, docs })
86 }
87}
88
89pub struct Field {
90 ty: Type,
91 name: Ident,
92 getter: Ident,
93 docs: Vec<Doc>,
94}
95
96impl Field {
97 fn from_field(field: &syn::Field) -> Result<Option<Self>> {
98 let name: Ident = field.ident
99 .clone()
100 .ok_or(Error::new(Span::call_site(), Problem::UnnamedField))?;
101
102 let work = Work::try_from(field.attrs.as_slice())?;
103
104 match work {
105 Work { special: Some(Action::Skip), docs: _ } => Ok(None),
106 Work { special: Some(Action::Rename(ident)), docs } => {
107 Ok(Some(Field {
108 ty: field.ty.clone(),
109 name,
110 getter: ident,
111 docs
112 }))
113 },
114 Work { special: None, docs } => {
115 Ok(Some(Field {
116 ty: field.ty.clone(),
117 name: name.clone(),
118 getter: name,
119 docs,
120 }))
121 },
122 }
123 }
124
125 fn from_fields_named(fields_named: &FieldsNamed) -> Result<Vec<Self>> {
126 fields_named.named
127 .iter()
128 .try_fold(Vec::new(), |mut fields, field| {
129 if let Some(field) = Field::from_field(field)? {
130 fields.push(field);
131 }
132
133 Ok(fields)
134 })
135 }
136
137 fn emit(&self, struct_name: &Ident) -> TokenStream {
138 let returns = &self.ty;
139 let field_name = &self.name;
140 let getter_name = &self.getter;
141
142 let doc_comments: Vec<TokenStream> = if self.docs.is_empty() {
143 let comment = format!(
144 " Get field `{}` from instance of `{}`.",
145 field_name,
146 struct_name,
147 );
148
149 vec![quote!(#[doc=#comment])]
150 } else {
151 self.docs
152 .iter()
153 .map(|d| d.0.to_owned())
154 .collect()
155 };
156
157 match &self.ty {
158 Type::Reference(tr) => {
159 let lifetime = tr.lifetime.as_ref();
160 quote!(
161 #(#doc_comments)*
162 pub fn #getter_name(&#lifetime self) -> #returns {
163 self.#field_name
164 }
165 )
166 },
167 _ => {
168 quote!(
169 #(#doc_comments)*
170 pub fn #getter_name(&self) -> &#returns {
171 &self.#field_name
172 }
173 )
174 },
175 }
176 }
177}
178
179pub struct NamedStruct<'a> {
180 original: &'a DeriveInput,
181 name: Ident,
182 fields: Vec<Field>,
183}
184
185impl<'a> NamedStruct<'a> {
186 pub fn emit(&self) -> TokenStream {
187 let (impl_generics, struct_generics, where_clause) = self.original.generics
188 .split_for_impl();
189 let struct_name = &self.name;
190 let methods: Vec<TokenStream> = self.fields
191 .iter()
192 .map(|field| field.emit(&self.name))
193 .collect();
194
195 let impl_comment = " Auto-generated by `derive_getters::Getters`.";
196 let impl_doc_comment = quote!(#[doc=#impl_comment]);
197
198 quote!(
199 #impl_doc_comment
200 impl #impl_generics #struct_name #struct_generics
201 #where_clause
202 {
203 #(#methods)*
204 }
205 )
206 }
207}
208
209impl<'a> TryFrom<&'a DeriveInput> for NamedStruct<'a> {
210 type Error = Error;
211
212 fn try_from(node: &'a DeriveInput) -> Result<Self> {
213 let struct_data = named_struct(node)?;
214 let named_fields = named_fields(struct_data)?;
215 let fields = Field::from_fields_named(named_fields)?;
216
217 Ok(NamedStruct {
218 original: node,
219 name: node.ident.clone(),
220 fields,
221 })
222 }
223}
224
225#[cfg(test)]
226mod test {
227 use super::*;
228
229 #[test]
230 fn parse_action() -> Result<()> {
231 let a: Action = syn::parse_str("skip")?;
232 assert!(a == Action::Skip);
233
234 let r: Result<Action> = syn::parse_str("skip = blah");
235 assert!(r.is_err());
236
237 let a: Action = syn::parse_str("rename = \"hello\"")?;
238 let check = Action::Rename(Ident::new("hello", Span::call_site()));
239 assert!(a == check);
240
241 let r: Result<Action> = syn::parse_str("rename + \"chooga\"");
242 assert!(r.is_err());
243
244 let r: Result<Action> = syn::parse_str("rename = \"chooga\" | bongle");
245 assert!(r.is_err());
246
247 Ok(())
248 }
249}