• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This file is part of ICU4X. For terms of use, please see the file
2 // called LICENSE at the top level of the ICU4X source tree
3 // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4 
5 //! Custom derives for `ZeroFrom` from the `zerofrom` crate.
6 
7 // https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
8 #![cfg_attr(
9     not(test),
10     deny(
11         clippy::indexing_slicing,
12         clippy::unwrap_used,
13         clippy::expect_used,
14         clippy::panic,
15         clippy::exhaustive_structs,
16         clippy::exhaustive_enums,
17         clippy::trivially_copy_pass_by_ref,
18         missing_debug_implementations,
19     )
20 )]
21 
22 use core::mem;
23 use proc_macro::TokenStream;
24 use proc_macro2::{Span, TokenStream as TokenStream2};
25 use quote::quote;
26 use std::collections::{HashMap, HashSet};
27 use syn::fold::{self, Fold};
28 use syn::punctuated::Punctuated;
29 use syn::spanned::Spanned;
30 use syn::{
31     parse_macro_input, parse_quote, DeriveInput, Ident, Lifetime, MetaList, Token,
32     TraitBoundModifier, Type, TypeParamBound, TypePath, WherePredicate,
33 };
34 use synstructure::Structure;
35 mod visitor;
36 
37 /// Custom derive for `zerofrom::ZeroFrom`,
38 ///
39 /// This implements `ZeroFrom<Ty> for Ty` for types
40 /// without a lifetime parameter, and `ZeroFrom<Ty<'data>> for Ty<'static>`
41 /// for types with a lifetime parameter.
42 ///
43 /// Apply the `#[zerofrom(clone)]` attribute to a field if it doesn't implement
44 /// Copy or ZeroFrom; this data will be cloned when the struct is zero_from'ed.
45 ///
46 /// Apply the `#[zerofrom(maybe_borrow(T, U, V))]` attribute to the struct to indicate
47 /// that certain type parameters may themselves contain borrows (by default
48 /// the derives assume that type parameters perform no borrows and can be copied or cloned).
49 ///
50 /// In rust versions where [this issue](https://github.com/rust-lang/rust/issues/114393) is fixed,
51 /// `#[zerofrom(may_borrow)]` can be applied directly to type parameters.
52 #[proc_macro_derive(ZeroFrom, attributes(zerofrom))]
zf_derive(input: TokenStream) -> TokenStream53 pub fn zf_derive(input: TokenStream) -> TokenStream {
54     let input = parse_macro_input!(input as DeriveInput);
55     TokenStream::from(zf_derive_impl(&input))
56 }
57 
has_attr(attrs: &[syn::Attribute], name: &str) -> bool58 fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
59     attrs.iter().any(|a| {
60         if let Ok(i) = a.parse_args::<Ident>() {
61             if i == name {
62                 return true;
63             }
64         }
65         false
66     })
67 }
68 
69 // Collects all idents from #[zerofrom(may_borrow(A, B, C, D))]
70 // needed since #[zerofrom(may_borrow)] doesn't work yet
71 // (https://github.com/rust-lang/rust/issues/114393)
get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span>72 fn get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span> {
73     let mut params = HashSet::new();
74     for attr in attrs {
75         if let Ok(list) = attr.parse_args::<MetaList>() {
76             if list.path.is_ident("may_borrow") {
77                 if let Ok(list) =
78                     list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)
79                 {
80                     params.extend(list)
81                 } else {
82                     return Err(attr.span());
83                 }
84             }
85         }
86     }
87     Ok(params)
88 }
89 
zf_derive_impl(input: &DeriveInput) -> TokenStream290 fn zf_derive_impl(input: &DeriveInput) -> TokenStream2 {
91     let mut tybounds = input
92         .generics
93         .type_params()
94         .map(|ty| {
95             // Strip out param defaults, we don't need them in the impl
96             let mut ty = ty.clone();
97             ty.eq_token = None;
98             ty.default = None;
99             ty
100         })
101         .collect::<Vec<_>>();
102     let typarams = tybounds
103         .iter()
104         .map(|ty| ty.ident.clone())
105         .collect::<Vec<_>>();
106     let lts = input.generics.lifetimes().count();
107     let name = &input.ident;
108     let structure = Structure::new(input);
109 
110     let may_borrow_attrs = match get_may_borrow_attr(&input.attrs) {
111         Ok(mb) => mb,
112         Err(span) => {
113             return syn::Error::new(
114             span,
115             "#[zerofrom(may_borrow)] on the struct takes in a comma separated list of type parameters, like so: `#[zerofrom(may_borrow(A, B, C, D)]`",
116         ).to_compile_error();
117         }
118     };
119 
120     // This contains every generic type introduced in this code.
121     // If the gneeric type is may_borrow, this additionally contains the identifier corresponding to
122     // a newly introduced mirror type parameter that we are borrowing from, similar to C in the original trait.
123     // For convenience, we are calling these "C types"
124     let generics_env: HashMap<Ident, Option<Ident>> = tybounds
125         .iter_mut()
126         .map(|param| {
127             // First one doesn't work yet https://github.com/rust-lang/rust/issues/114393
128             let maybe_new_param = if has_attr(&param.attrs, "may_borrow")
129                 || may_borrow_attrs.contains(&param.ident)
130             {
131                 // Remove `?Sized`` bound because we need a param to be Sized in order to take a ZeroFrom of it.
132                 // This only applies to fields marked as `may_borrow`.
133                 let mut bounds = core::mem::take(&mut param.bounds);
134                 while let Some(bound_pair) = bounds.pop() {
135                     let bound = bound_pair.into_value();
136                     if let TypeParamBound::Trait(ref trait_bound) = bound {
137                         if trait_bound.path.get_ident().map(|ident| ident == "Sized") == Some(true)
138                             && matches!(trait_bound.modifier, TraitBoundModifier::Maybe(_))
139                         {
140                             continue;
141                         }
142                     }
143                     param.bounds.push(bound);
144                 }
145                 Some(Ident::new(
146                     &format!("{}ZFParamC", param.ident),
147                     param.ident.span(),
148                 ))
149             } else {
150                 None
151             };
152             (param.ident.clone(), maybe_new_param)
153         })
154         .collect();
155 
156     // Do any of the generics potentially borrow?
157     let generics_may_borrow = generics_env.values().any(|x| x.is_some());
158 
159     if lts == 0 && !generics_may_borrow {
160         let has_clone = structure
161             .variants()
162             .iter()
163             .flat_map(|variant| variant.bindings().iter())
164             .any(|binding| has_attr(&binding.ast().attrs, "clone"));
165         let (clone, clone_trait) = if has_clone {
166             (quote!(this.clone()), quote!(Clone))
167         } else {
168             (quote!(*this), quote!(Copy))
169         };
170         let bounds: Vec<WherePredicate> = typarams
171             .iter()
172             .map(|ty| parse_quote!(#ty: #clone_trait + 'static))
173             .collect();
174         quote! {
175             impl<'zf, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#(#typarams),*>> for #name<#(#typarams),*> where #(#bounds),* {
176                 fn zero_from(this: &'zf Self) -> Self {
177                     #clone
178                 }
179             }
180         }
181     } else {
182         if lts > 1 {
183             return syn::Error::new(
184                 input.generics.span(),
185                 "derive(ZeroFrom) cannot have multiple lifetime parameters",
186             )
187             .to_compile_error();
188         }
189 
190         let mut zf_bounds: Vec<WherePredicate> = vec![];
191         let body = structure.each_variant(|vi| {
192             vi.construct(|f, i| {
193                 let binding = format!("__binding_{i}");
194                 let field = Ident::new(&binding, Span::call_site());
195 
196                 if has_attr(&f.attrs, "clone") {
197                     quote! {
198                         #field.clone()
199                     }
200                 } else {
201                     // the field type
202                     let fty = replace_lifetime(&f.ty, custom_lt("'zf"));
203                     // the corresponding lifetimey type we are borrowing from (effectively, the C type)
204                     let lifetime_ty =
205                         replace_lifetime_and_type(&f.ty, custom_lt("'zf_inner"), &generics_env);
206 
207                     let (has_ty, has_lt) = visitor::check_type_for_parameters(&f.ty, &generics_env);
208                     if has_ty {
209                         // For types without type parameters, the compiler can figure out that the field implements
210                         // ZeroFrom on its own. However, if there are type parameters, there may be complex preconditions
211                         // to `FieldTy: ZeroFrom` that need to be satisfied. We get them to be satisfied by requiring
212                         // `FieldTy<'zf>: ZeroFrom<'zf, FieldTy<'zf_inner>>`
213                         if has_lt {
214                             zf_bounds
215                                 .push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #lifetime_ty>));
216                         } else {
217                             zf_bounds.push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #fty>));
218                         }
219                     }
220                     if has_ty || has_lt {
221                         // By doing this we essentially require ZF to be implemented
222                         // on all fields
223                         quote! {
224                             <#fty as zerofrom::ZeroFrom<'zf, #lifetime_ty>>::zero_from(#field)
225                         }
226                     } else {
227                         // No lifetimes, so we can just copy
228                         quote! { *#field }
229                     }
230                 }
231             })
232         });
233         // Due to the possibility of generics_may_borrow, we might reach here with no lifetimes on self,
234         // don't accidentally feed them to self later
235         let (maybe_zf_lifetime, maybe_zf_inner_lifetime) = if lts == 0 {
236             (quote!(), quote!())
237         } else {
238             (quote!('zf,), quote!('zf_inner,))
239         };
240 
241         // Array of C types. Only different if generics are allowed to borrow
242         let mut typarams_c = typarams.clone();
243 
244         if generics_may_borrow {
245             for typaram_c in &mut typarams_c {
246                 if let Some(Some(replacement)) = generics_env.get(typaram_c) {
247                     // we use mem::replace here so we can be really clear about the C vs the T type
248                     let typaram_t = mem::replace(typaram_c, replacement.clone());
249                     zf_bounds
250                         .push(parse_quote!(#typaram_c: zerofrom::ZeroFrom<'zf_inner, #typaram_t>));
251                     tybounds.push(parse_quote!(#typaram_c));
252                 }
253             }
254         }
255 
256         quote! {
257             impl<'zf, 'zf_inner, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#maybe_zf_inner_lifetime #(#typarams_c),*>> for #name<#maybe_zf_lifetime #(#typarams),*>
258                 where
259                 #(#zf_bounds,)* {
260                 fn zero_from(this: &'zf #name<#maybe_zf_inner_lifetime #(#typarams_c),*>) -> Self {
261                     match *this { #body }
262                 }
263             }
264         }
265     }
266 }
267 
custom_lt(s: &str) -> Lifetime268 fn custom_lt(s: &str) -> Lifetime {
269     Lifetime::new(s, Span::call_site())
270 }
271 
272 /// Replace all lifetimes in a type with a specified one
replace_lifetime(x: &Type, lt: Lifetime) -> Type273 fn replace_lifetime(x: &Type, lt: Lifetime) -> Type {
274     struct ReplaceLifetime(Lifetime);
275 
276     impl Fold for ReplaceLifetime {
277         fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
278             self.0.clone()
279         }
280     }
281     ReplaceLifetime(lt).fold_type(x.clone())
282 }
283 
284 /// Replace all lifetimes in a type with a specified one, AND replace all types that have a corresponding C type
285 /// with the C type
replace_lifetime_and_type( x: &Type, lt: Lifetime, generics_env: &HashMap<Ident, Option<Ident>>, ) -> Type286 fn replace_lifetime_and_type(
287     x: &Type,
288     lt: Lifetime,
289     generics_env: &HashMap<Ident, Option<Ident>>,
290 ) -> Type {
291     struct ReplaceLifetimeAndTy<'a>(Lifetime, &'a HashMap<Ident, Option<Ident>>);
292 
293     impl Fold for ReplaceLifetimeAndTy<'_> {
294         fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
295             self.0.clone()
296         }
297         fn fold_type_path(&mut self, i: TypePath) -> TypePath {
298             if i.qself.is_none() {
299                 if let Some(ident) = i.path.get_ident() {
300                     if let Some(Some(replacement)) = self.1.get(ident) {
301                         return parse_quote!(#replacement);
302                     }
303                 }
304             }
305             fold::fold_type_path(self, i)
306         }
307     }
308     ReplaceLifetimeAndTy(lt, generics_env).fold_type(x.clone())
309 }
310