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))] |
53 | pub fn zf_derive(input: TokenStream) -> TokenStream { |
54 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); |
55 | TokenStream::from(zf_derive_impl(&input)) |
56 | } |
57 | |
58 | fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool { |
59 | attrs.iter().any(|a: &Attribute| { |
60 | if let Ok(i: Ident) = 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) |
72 | fn get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span> { |
73 | let mut params: HashSet = HashSet::new(); |
74 | for attr: &Attribute in attrs { |
75 | if let Ok(list: MetaList) = attr.parse_args::<MetaList>() { |
76 | if list.path.is_ident("may_borrow" ) { |
77 | if let Ok(list: Punctuated) = |
78 | list.parse_args_with(parser:Punctuated::<Ident, Token![,]>::parse_terminated) |
79 | { |
80 | params.extend(iter:list) |
81 | } else { |
82 | return Err(attr.span()); |
83 | } |
84 | } |
85 | } |
86 | } |
87 | Ok(params) |
88 | } |
89 | |
90 | 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(¶m.attrs, "may_borrow" ) |
129 | || may_borrow_attrs.contains(¶m.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 | |
268 | fn custom_lt(s: &str) -> Lifetime { |
269 | Lifetime::new(symbol:s, Span::call_site()) |
270 | } |
271 | |
272 | /// Replace all lifetimes in a type with a specified one |
273 | 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 |
286 | 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: &Ident) = i.path.get_ident() { |
300 | if let Some(Some(replacement: &Ident)) = self.1.get(ident) { |
301 | return parse_quote!(#replacement); |
302 | } |
303 | } |
304 | } |
305 | fold::fold_type_path(self, node:i) |
306 | } |
307 | } |
308 | ReplaceLifetimeAndTy(lt, generics_env).fold_type(x.clone()) |
309 | } |
310 | |