1#![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 missing_debug_implementations,
18 )
19)]
20
21use core::mem;
22use proc_macro::TokenStream;
23use proc_macro2::{Span, TokenStream as TokenStream2};
24use quote::quote;
25use std::collections::{HashMap, HashSet};
26use syn::fold::{self, Fold};
27use syn::punctuated::Punctuated;
28use syn::spanned::Spanned;
29use syn::{
30 parse_macro_input, parse_quote, DeriveInput, Ident, Lifetime, MetaList, Token,
31 TraitBoundModifier, Type, TypeParamBound, TypePath, WherePredicate,
32};
33use synstructure::Structure;
34mod visitor;
35
36#[proc_macro_derive(ZeroFrom, attributes(zerofrom))]
52pub fn zf_derive(input: TokenStream) -> TokenStream {
53 let input = parse_macro_input!(input as DeriveInput);
54 TokenStream::from(zf_derive_impl(&input))
55}
56
57fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
58 attrs.iter().any(|a| {
59 if let Ok(i) = a.parse_args::<Ident>() {
60 if i == name {
61 return true;
62 }
63 }
64 false
65 })
66}
67
68fn get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span> {
72 let mut params = HashSet::new();
73 for attr in attrs {
74 if let Ok(list) = attr.parse_args::<MetaList>() {
75 if list.path.is_ident("may_borrow") {
76 if let Ok(list) =
77 list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)
78 {
79 params.extend(list)
80 } else {
81 return Err(attr.span());
82 }
83 }
84 }
85 }
86 Ok(params)
87}
88
89fn zf_derive_impl(input: &DeriveInput) -> TokenStream2 {
90 let mut tybounds = input
91 .generics
92 .type_params()
93 .map(|ty| {
94 let mut ty = ty.clone();
96 ty.eq_token = None;
97 ty.default = None;
98 ty
99 })
100 .collect::<Vec<_>>();
101 let typarams = tybounds
102 .iter()
103 .map(|ty| ty.ident.clone())
104 .collect::<Vec<_>>();
105 let lts = input.generics.lifetimes().count();
106 let name = &input.ident;
107 let structure = Structure::new(input);
108
109 let may_borrow_attrs = match get_may_borrow_attr(&input.attrs) {
110 Ok(mb) => mb,
111 Err(span) => {
112 return syn::Error::new(
113 span,
114 "#[zerofrom(may_borrow)] on the struct takes in a comma separated list of type parameters, like so: `#[zerofrom(may_borrow(A, B, C, D)]`",
115 ).to_compile_error();
116 }
117 };
118
119 let generics_env: HashMap<Ident, Option<Ident>> = tybounds
124 .iter_mut()
125 .map(|param| {
126 let maybe_new_param = if has_attr(¶m.attrs, "may_borrow")
128 || may_borrow_attrs.contains(¶m.ident)
129 {
130 let mut bounds = core::mem::take(&mut param.bounds);
133 while let Some(bound_pair) = bounds.pop() {
134 let bound = bound_pair.into_value();
135 if let TypeParamBound::Trait(ref trait_bound) = bound {
136 if trait_bound.path.get_ident().map(|ident| ident == "Sized") == Some(true)
137 && matches!(trait_bound.modifier, TraitBoundModifier::Maybe(_))
138 {
139 continue;
140 }
141 }
142 param.bounds.push(bound);
143 }
144 Some(Ident::new(
145 &format!("{}ZFParamC", param.ident),
146 param.ident.span(),
147 ))
148 } else {
149 None
150 };
151 (param.ident.clone(), maybe_new_param)
152 })
153 .collect();
154
155 let generics_may_borrow = generics_env.values().any(|x| x.is_some());
157
158 if lts == 0 && !generics_may_borrow {
159 let has_clone = structure
160 .variants()
161 .iter()
162 .flat_map(|variant| variant.bindings().iter())
163 .any(|binding| has_attr(&binding.ast().attrs, "clone"));
164 let (clone, clone_trait) = if has_clone {
165 (quote!(this.clone()), quote!(Clone))
166 } else {
167 (quote!(*this), quote!(Copy))
168 };
169 let bounds: Vec<WherePredicate> = typarams
170 .iter()
171 .map(|ty| parse_quote!(#ty: #clone_trait + 'static))
172 .collect();
173 quote! {
174 impl<'zf, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#(#typarams),*>> for #name<#(#typarams),*> where #(#bounds),* {
175 fn zero_from(this: &'zf Self) -> Self {
176 #clone
177 }
178 }
179 }
180 } else {
181 if lts > 1 {
182 return syn::Error::new(
183 input.generics.span(),
184 "derive(ZeroFrom) cannot have multiple lifetime parameters",
185 )
186 .to_compile_error();
187 }
188
189 let mut zf_bounds: Vec<WherePredicate> = vec![];
190 let body = structure.each_variant(|vi| {
191 vi.construct(|f, i| {
192 let binding = format!("__binding_{i}");
193 let field = Ident::new(&binding, Span::call_site());
194
195 if has_attr(&f.attrs, "clone") {
196 quote! {
197 #field.clone()
198 }
199 } else {
200 let fty = replace_lifetime(&f.ty, custom_lt("'zf"));
202 let lifetime_ty =
204 replace_lifetime_and_type(&f.ty, custom_lt("'zf_inner"), &generics_env);
205
206 let (has_ty, has_lt) = visitor::check_type_for_parameters(&f.ty, &generics_env);
207 if has_ty {
208 if has_lt {
213 zf_bounds
214 .push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #lifetime_ty>));
215 } else {
216 zf_bounds.push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #fty>));
217 }
218 }
219 if has_ty || has_lt {
220 quote! {
223 <#fty as zerofrom::ZeroFrom<'zf, #lifetime_ty>>::zero_from(#field)
224 }
225 } else {
226 quote! { *#field }
228 }
229 }
230 })
231 });
232 let (maybe_zf_lifetime, maybe_zf_inner_lifetime) = if lts == 0 {
235 (quote!(), quote!())
236 } else {
237 (quote!('zf,), quote!('zf_inner,))
238 };
239
240 let mut typarams_c = typarams.clone();
242
243 if generics_may_borrow {
244 for typaram_c in &mut typarams_c {
245 if let Some(Some(replacement)) = generics_env.get(typaram_c) {
246 let typaram_t = mem::replace(typaram_c, replacement.clone());
248 zf_bounds
249 .push(parse_quote!(#typaram_c: zerofrom::ZeroFrom<'zf_inner, #typaram_t>));
250 tybounds.push(parse_quote!(#typaram_c));
251 }
252 }
253 }
254
255 quote! {
256 impl<'zf, 'zf_inner, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#maybe_zf_inner_lifetime #(#typarams_c),*>> for #name<#maybe_zf_lifetime #(#typarams),*>
257 where
258 #(#zf_bounds,)* {
259 fn zero_from(this: &'zf #name<#maybe_zf_inner_lifetime #(#typarams_c),*>) -> Self {
260 match *this { #body }
261 }
262 }
263 }
264 }
265}
266
267fn custom_lt(s: &str) -> Lifetime {
268 Lifetime::new(s, Span::call_site())
269}
270
271fn replace_lifetime(x: &Type, lt: Lifetime) -> Type {
273 struct ReplaceLifetime(Lifetime);
274
275 impl Fold for ReplaceLifetime {
276 fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
277 self.0.clone()
278 }
279 }
280 ReplaceLifetime(lt).fold_type(x.clone())
281}
282
283fn replace_lifetime_and_type(
286 x: &Type,
287 lt: Lifetime,
288 generics_env: &HashMap<Ident, Option<Ident>>,
289) -> Type {
290 struct ReplaceLifetimeAndTy<'a>(Lifetime, &'a HashMap<Ident, Option<Ident>>);
291
292 impl Fold for ReplaceLifetimeAndTy<'_> {
293 fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
294 self.0.clone()
295 }
296 fn fold_type_path(&mut self, i: TypePath) -> TypePath {
297 if i.qself.is_none() {
298 if let Some(ident) = i.path.get_ident() {
299 if let Some(Some(replacement)) = self.1.get(ident) {
300 return parse_quote!(#replacement);
301 }
302 }
303 }
304 fold::fold_type_path(self, i)
305 }
306 }
307 ReplaceLifetimeAndTy(lt, generics_env).fold_type(x.clone())
308}