| // This file is part of ICU4X. For terms of use, please see the file |
| // called LICENSE at the top level of the ICU4X source tree |
| // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
| |
| //! Custom derives for `ZeroFrom` from the `zerofrom` crate. |
| |
| // https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations |
| #![cfg_attr( |
| not(test), |
| deny( |
| clippy::indexing_slicing, |
| clippy::unwrap_used, |
| clippy::expect_used, |
| clippy::panic, |
| clippy::exhaustive_structs, |
| clippy::exhaustive_enums, |
| missing_debug_implementations, |
| ) |
| )] |
| |
| use core::mem; |
| use proc_macro::TokenStream; |
| use proc_macro2::{Span, TokenStream as TokenStream2}; |
| use quote::quote; |
| use std::collections::{HashMap, HashSet}; |
| use syn::fold::{self, Fold}; |
| use syn::punctuated::Punctuated; |
| use syn::spanned::Spanned; |
| use syn::{ |
| parse_macro_input, parse_quote, DeriveInput, Ident, Lifetime, MetaList, Token, |
| TraitBoundModifier, Type, TypeParamBound, TypePath, WherePredicate, |
| }; |
| use synstructure::Structure; |
| mod visitor; |
| |
| /// Custom derive for `zerofrom::ZeroFrom`, |
| /// |
| /// This implements `ZeroFrom<Ty> for Ty` for types |
| /// without a lifetime parameter, and `ZeroFrom<Ty<'data>> for Ty<'static>` |
| /// for types with a lifetime parameter. |
| /// |
| /// Apply the `#[zerofrom(clone)]` attribute to a field if it doesn't implement |
| /// Copy or ZeroFrom; this data will be cloned when the struct is zero_from'ed. |
| /// |
| /// Apply the `#[zerofrom(maybe_borrow(T, U, V))]` attribute to the struct to indicate |
| /// that certain type parameters may themselves contain borrows (by default |
| /// the derives assume that type parameters perform no borrows and can be copied or cloned). |
| /// |
| /// In rust versions where [this issue](https://github.com/rust-lang/rust/issues/114393) is fixed, |
| /// `#[zerofrom(may_borrow)]` can be applied directly to type parameters. |
| #[proc_macro_derive(ZeroFrom, attributes(zerofrom))] |
| pub fn zf_derive(input: TokenStream) -> TokenStream { |
| let input = parse_macro_input!(input as DeriveInput); |
| TokenStream::from(zf_derive_impl(&input)) |
| } |
| |
| fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool { |
| attrs.iter().any(|a| { |
| if let Ok(i) = a.parse_args::<Ident>() { |
| if i == name { |
| return true; |
| } |
| } |
| false |
| }) |
| } |
| |
| // Collects all idents from #[zerofrom(may_borrow(A, B, C, D))] |
| // needed since #[zerofrom(may_borrow)] doesn't work yet |
| // (https://github.com/rust-lang/rust/issues/114393) |
| fn get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span> { |
| let mut params = HashSet::new(); |
| for attr in attrs { |
| if let Ok(list) = attr.parse_args::<MetaList>() { |
| if list.path.is_ident("may_borrow") { |
| if let Ok(list) = |
| list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated) |
| { |
| params.extend(list) |
| } else { |
| return Err(attr.span()); |
| } |
| } |
| } |
| } |
| Ok(params) |
| } |
| |
| fn zf_derive_impl(input: &DeriveInput) -> TokenStream2 { |
| let mut tybounds = input |
| .generics |
| .type_params() |
| .map(|ty| { |
| // Strip out param defaults, we don't need them in the impl |
| let mut ty = ty.clone(); |
| ty.eq_token = None; |
| ty.default = None; |
| ty |
| }) |
| .collect::<Vec<_>>(); |
| let typarams = tybounds |
| .iter() |
| .map(|ty| ty.ident.clone()) |
| .collect::<Vec<_>>(); |
| let lts = input.generics.lifetimes().count(); |
| let name = &input.ident; |
| let structure = Structure::new(input); |
| |
| let may_borrow_attrs = match get_may_borrow_attr(&input.attrs) { |
| Ok(mb) => mb, |
| Err(span) => { |
| return syn::Error::new( |
| span, |
| "#[zerofrom(may_borrow)] on the struct takes in a comma separated list of type parameters, like so: `#[zerofrom(may_borrow(A, B, C, D)]`", |
| ).to_compile_error(); |
| } |
| }; |
| |
| // This contains every generic type introduced in this code. |
| // If the gneeric type is may_borrow, this additionally contains the identifier corresponding to |
| // a newly introduced mirror type parameter that we are borrowing from, similar to C in the original trait. |
| // For convenience, we are calling these "C types" |
| let generics_env: HashMap<Ident, Option<Ident>> = tybounds |
| .iter_mut() |
| .map(|param| { |
| // First one doesn't work yet https://github.com/rust-lang/rust/issues/114393 |
| let maybe_new_param = if has_attr(¶m.attrs, "may_borrow") |
| || may_borrow_attrs.contains(¶m.ident) |
| { |
| // Remove `?Sized`` bound because we need a param to be Sized in order to take a ZeroFrom of it. |
| // This only applies to fields marked as `may_borrow`. |
| let mut bounds = core::mem::take(&mut param.bounds); |
| while let Some(bound_pair) = bounds.pop() { |
| let bound = bound_pair.into_value(); |
| if let TypeParamBound::Trait(ref trait_bound) = bound { |
| if trait_bound.path.get_ident().map(|ident| ident == "Sized") == Some(true) |
| && matches!(trait_bound.modifier, TraitBoundModifier::Maybe(_)) |
| { |
| continue; |
| } |
| } |
| param.bounds.push(bound); |
| } |
| Some(Ident::new( |
| &format!("{}ZFParamC", param.ident), |
| param.ident.span(), |
| )) |
| } else { |
| None |
| }; |
| (param.ident.clone(), maybe_new_param) |
| }) |
| .collect(); |
| |
| // Do any of the generics potentially borrow? |
| let generics_may_borrow = generics_env.values().any(|x| x.is_some()); |
| |
| if lts == 0 && !generics_may_borrow { |
| let has_clone = structure |
| .variants() |
| .iter() |
| .flat_map(|variant| variant.bindings().iter()) |
| .any(|binding| has_attr(&binding.ast().attrs, "clone")); |
| let (clone, clone_trait) = if has_clone { |
| (quote!(this.clone()), quote!(Clone)) |
| } else { |
| (quote!(*this), quote!(Copy)) |
| }; |
| let bounds: Vec<WherePredicate> = typarams |
| .iter() |
| .map(|ty| parse_quote!(#ty: #clone_trait + 'static)) |
| .collect(); |
| quote! { |
| impl<'zf, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#(#typarams),*>> for #name<#(#typarams),*> where #(#bounds),* { |
| fn zero_from(this: &'zf Self) -> Self { |
| #clone |
| } |
| } |
| } |
| } else { |
| if lts > 1 { |
| return syn::Error::new( |
| input.generics.span(), |
| "derive(ZeroFrom) cannot have multiple lifetime parameters", |
| ) |
| .to_compile_error(); |
| } |
| |
| let mut zf_bounds: Vec<WherePredicate> = vec![]; |
| let body = structure.each_variant(|vi| { |
| vi.construct(|f, i| { |
| let binding = format!("__binding_{i}"); |
| let field = Ident::new(&binding, Span::call_site()); |
| |
| if has_attr(&f.attrs, "clone") { |
| quote! { |
| #field.clone() |
| } |
| } else { |
| // the field type |
| let fty = replace_lifetime(&f.ty, custom_lt("'zf")); |
| // the corresponding lifetimey type we are borrowing from (effectively, the C type) |
| let lifetime_ty = |
| replace_lifetime_and_type(&f.ty, custom_lt("'zf_inner"), &generics_env); |
| |
| let (has_ty, has_lt) = visitor::check_type_for_parameters(&f.ty, &generics_env); |
| if has_ty { |
| // For types without type parameters, the compiler can figure out that the field implements |
| // ZeroFrom on its own. However, if there are type parameters, there may be complex preconditions |
| // to `FieldTy: ZeroFrom` that need to be satisfied. We get them to be satisfied by requiring |
| // `FieldTy<'zf>: ZeroFrom<'zf, FieldTy<'zf_inner>>` |
| if has_lt { |
| zf_bounds |
| .push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #lifetime_ty>)); |
| } else { |
| zf_bounds.push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #fty>)); |
| } |
| } |
| if has_ty || has_lt { |
| // By doing this we essentially require ZF to be implemented |
| // on all fields |
| quote! { |
| <#fty as zerofrom::ZeroFrom<'zf, #lifetime_ty>>::zero_from(#field) |
| } |
| } else { |
| // No lifetimes, so we can just copy |
| quote! { *#field } |
| } |
| } |
| }) |
| }); |
| // Due to the possibility of generics_may_borrow, we might reach here with no lifetimes on self, |
| // don't accidentally feed them to self later |
| let (maybe_zf_lifetime, maybe_zf_inner_lifetime) = if lts == 0 { |
| (quote!(), quote!()) |
| } else { |
| (quote!('zf,), quote!('zf_inner,)) |
| }; |
| |
| // Array of C types. Only different if generics are allowed to borrow |
| let mut typarams_c = typarams.clone(); |
| |
| if generics_may_borrow { |
| for typaram_c in &mut typarams_c { |
| if let Some(Some(replacement)) = generics_env.get(typaram_c) { |
| // we use mem::replace here so we can be really clear about the C vs the T type |
| let typaram_t = mem::replace(typaram_c, replacement.clone()); |
| zf_bounds |
| .push(parse_quote!(#typaram_c: zerofrom::ZeroFrom<'zf_inner, #typaram_t>)); |
| tybounds.push(parse_quote!(#typaram_c)); |
| } |
| } |
| } |
| |
| quote! { |
| impl<'zf, 'zf_inner, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#maybe_zf_inner_lifetime #(#typarams_c),*>> for #name<#maybe_zf_lifetime #(#typarams),*> |
| where |
| #(#zf_bounds,)* { |
| fn zero_from(this: &'zf #name<#maybe_zf_inner_lifetime #(#typarams_c),*>) -> Self { |
| match *this { #body } |
| } |
| } |
| } |
| } |
| } |
| |
| fn custom_lt(s: &str) -> Lifetime { |
| Lifetime::new(s, Span::call_site()) |
| } |
| |
| /// Replace all lifetimes in a type with a specified one |
| fn replace_lifetime(x: &Type, lt: Lifetime) -> Type { |
| struct ReplaceLifetime(Lifetime); |
| |
| impl Fold for ReplaceLifetime { |
| fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime { |
| self.0.clone() |
| } |
| } |
| ReplaceLifetime(lt).fold_type(x.clone()) |
| } |
| |
| /// Replace all lifetimes in a type with a specified one, AND replace all types that have a corresponding C type |
| /// with the C type |
| fn replace_lifetime_and_type( |
| x: &Type, |
| lt: Lifetime, |
| generics_env: &HashMap<Ident, Option<Ident>>, |
| ) -> Type { |
| struct ReplaceLifetimeAndTy<'a>(Lifetime, &'a HashMap<Ident, Option<Ident>>); |
| |
| impl Fold for ReplaceLifetimeAndTy<'_> { |
| fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime { |
| self.0.clone() |
| } |
| fn fold_type_path(&mut self, i: TypePath) -> TypePath { |
| if i.qself.is_none() { |
| if let Some(ident) = i.path.get_ident() { |
| if let Some(Some(replacement)) = self.1.get(ident) { |
| return parse_quote!(#replacement); |
| } |
| } |
| } |
| fold::fold_type_path(self, i) |
| } |
| } |
| ReplaceLifetimeAndTy(lt, generics_env).fold_type(x.clone()) |
| } |