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