property/
lib.rs

1// Copyright (C) 2019-2020 Boyu Yang
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9extern crate proc_macro;
10
11use quote::quote;
12
13mod generate;
14mod parse;
15
16use crate::{
17    generate::{FieldType, GetType},
18    parse::{FieldDef, GetTypeConf, PropertyDef, SetTypeConf},
19};
20
21/// Generate several common methods for structs automatically.
22#[proc_macro_derive(Property, attributes(property))]
23pub fn derive_property(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24    let property = syn::parse_macro_input!(input as PropertyDef);
25    let expanded = {
26        let name = &property.name;
27        let (impl_generics, type_generics, where_clause_opt) = property.generics.split_for_impl();
28        let methods = property.fields.iter().fold(Vec::new(), |mut r, f| {
29            if !f.conf.skip {
30                r.append(&mut derive_property_for_field(f));
31            }
32            r
33        });
34        let impl_methods = quote!(
35            impl #impl_generics #name #type_generics #where_clause_opt {
36                #(#[inline] #methods)*
37            }
38        );
39        if let Some(impl_traits) = implement_traits(&property) {
40            quote!(#impl_methods #impl_traits)
41        } else {
42            impl_methods
43        }
44    };
45    expanded.into()
46}
47
48fn implement_traits(property: &PropertyDef) -> Option<proc_macro2::TokenStream> {
49    let name = &property.name;
50    let mut ordered: Vec<_> = property
51        .fields
52        .iter()
53        .filter(|f| f.conf.ord.number.is_some())
54        .collect();
55    if ordered.is_empty() {
56        None
57    } else {
58        ordered.sort_by(|f1, f2| {
59            let n1 = f1.conf.ord.number.unwrap();
60            let n2 = f2.conf.ord.number.unwrap();
61            n1.cmp(&n2)
62        });
63        let has_same_serial_number = ordered.windows(2).any(|f| {
64            let n1 = f[0].conf.ord.number.unwrap();
65            let n2 = f[1].conf.ord.number.unwrap();
66            n1 == n2
67        });
68        if has_same_serial_number {
69            panic!("there are at least two fields that have same serial number");
70        }
71        let partial_eq_stmt = ordered.iter().fold(Vec::new(), |mut r, f| {
72            if !r.is_empty() {
73                r.push(quote!(&&));
74            }
75            let field_name = &f.ident;
76            r.push(quote!(self.#field_name == other.#field_name));
77            r
78        });
79        let partial_ord_stmt = ordered.iter().fold(Vec::new(), |mut r, f| {
80            let field_name = &f.ident;
81            r.push(if f.conf.ord.sort_type.is_ascending() {
82                quote!(let result = self.#field_name.partial_cmp(&other.#field_name);)
83            } else {
84                quote!(let result = other.#field_name.partial_cmp(&self.#field_name);)
85            });
86            r.push(quote!(if result != Some(::core::cmp::Ordering::Equal) {
87                return result;
88            }));
89            r
90        });
91        let stmts = quote!(
92            impl PartialEq for #name {
93                fn eq(&self, other: &Self) -> bool {
94                    #(#partial_eq_stmt)*
95                }
96            }
97
98            impl PartialOrd for #name {
99                fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
100                    #(#partial_ord_stmt)*
101                    Some(::core::cmp::Ordering::Equal)
102                }
103            }
104        );
105        Some(stmts)
106    }
107}
108
109fn derive_property_for_field(field: &FieldDef) -> Vec<proc_macro2::TokenStream> {
110    let mut property = Vec::new();
111    let field_type = &field.ty;
112    let field_name = &field.ident;
113    let field_conf = &field.conf;
114    let prop_field_type = FieldType::from_type(field_type);
115    if let Some(ts) = field_conf.get.vis.to_ts().map(|visibility| {
116        let method_name = field_conf.get.name.complete(field_name);
117        let get_type = match field_conf.get.typ {
118            GetTypeConf::NotSet => GetType::from_field_type(&prop_field_type),
119            GetTypeConf::Ref => GetType::Ref,
120            GetTypeConf::Copy_ => GetType::Copy_,
121            GetTypeConf::Clone_ => GetType::Clone_,
122        };
123        match get_type {
124            GetType::Ref => quote!(
125                #visibility fn #method_name(&self) -> &#field_type {
126                    &self.#field_name
127                }
128            ),
129            GetType::Copy_ => quote!(
130                #visibility fn #method_name(&self) -> #field_type {
131                    self.#field_name
132                }
133            ),
134            GetType::Clone_ => quote!(
135                #visibility fn #method_name(&self) -> #field_type {
136                    self.#field_name.clone()
137                }
138            ),
139            GetType::String_ => quote!(
140                #visibility fn #method_name(&self) -> &str {
141                    &self.#field_name[..]
142                }
143            ),
144            GetType::Slice(field_type) => quote!(
145                #visibility fn #method_name(&self) -> &#field_type {
146                    &self.#field_name[..]
147                }
148            ),
149            GetType::Option_(field_type) => quote!(
150                #visibility fn #method_name(&self) -> Option<&#field_type> {
151                    self.#field_name.as_ref()
152                }
153            ),
154        }
155    }) {
156        property.push(ts);
157    }
158    if let Some(ts) = field_conf.set.vis.to_ts().map(|visibility| {
159        let method_name = field_conf.set.name.complete(field_name);
160        match prop_field_type {
161            FieldType::Vector(inner_type) => match field_conf.set.typ {
162                SetTypeConf::Ref => quote!(
163                    #visibility fn #method_name<T: Into<#inner_type>>(
164                       &mut self,
165                       val: impl IntoIterator<Item = T>
166                    ) -> &mut Self {
167                        self.#field_name = val.into_iter().map(Into::into).collect();
168                        self
169                    }
170                ),
171                SetTypeConf::Own => quote!(
172                    #visibility fn #method_name<T: Into<#inner_type>>(
173                        mut self,
174                        val: impl IntoIterator<Item = T>
175                    ) -> Self {
176                        self.#field_name = val.into_iter().map(Into::into).collect();
177                        self
178                    }
179                ),
180                SetTypeConf::None_ => quote!(
181                    #visibility fn #method_name<T: Into<#inner_type>>(
182                       &mut self,
183                       val: impl IntoIterator<Item = T>
184                    ) {
185                        self.#field_name = val.into_iter().map(Into::into).collect();
186                    }
187                ),
188                SetTypeConf::Replace => quote!(
189                    #visibility fn #method_name<T: Into<#inner_type>>(
190                       &mut self,
191                       val: impl IntoIterator<Item = T>
192                    ) -> #field_type {
193                        ::core::mem::replace(&mut self.#field_name, val.into_iter().map(Into::into).collect())
194                    }
195                ),
196            },
197            _ => match field_conf.set.typ {
198                SetTypeConf::Ref => quote!(
199                    #visibility fn #method_name<T: Into<#field_type>>(
200                        &mut self, val: T
201                    ) -> &mut Self {
202                        self.#field_name = val.into();
203                        self
204                    }
205                ),
206                SetTypeConf::Own => quote!(
207                    #visibility fn #method_name<T: Into<#field_type>>(
208                        mut self, val: T
209                    ) -> Self {
210                        self.#field_name = val.into();
211                        self
212                    }
213                ),
214                SetTypeConf::None_ => quote!(
215                    #visibility fn #method_name<T: Into<#field_type>>(
216                        &mut self, val: T
217                    ) {
218                        self.#field_name = val.into();
219                    }
220                ),
221                SetTypeConf::Replace => quote!(
222                    #visibility fn #method_name<T: Into<#field_type>>(
223                        &mut self, val: T
224                    ) -> #field_type {
225                        ::core::mem::replace(&mut self.#field_name, val.into())
226                    }
227                ),
228            },
229        }
230    }) {
231        property.push(ts);
232    }
233    if let Some(ts) = field_conf.mut_.vis.to_ts().map(|visibility| {
234        let method_name = field_conf.mut_.name.complete(field_name);
235        quote!(
236            #visibility fn #method_name(&mut self) -> &mut #field_type {
237                &mut self.#field_name
238            }
239        )
240    }) {
241        property.push(ts);
242    }
243    property
244}