// 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/docs/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, Type, TypePath, WherePredicate, }; use synstructure::Structure; mod visitor; /// Custom derive for `zerofrom::ZeroFrom`, /// /// This implements `ZeroFrom for Ty` for types /// without a lifetime parameter, and `ZeroFrom> 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::() { 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, Span> { let mut params = HashSet::new(); for attr in attrs { if let Ok(list) = attr.parse_args::() { if list.path.is_ident("may_borrow") { if let Ok(list) = list.parse_args_with(Punctuated::::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::>(); let typarams = tybounds .iter() .map(|ty| ty.ident.clone()) .collect::>(); 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> = tybounds .iter() .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) { 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 = 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 = 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>, ) -> Type { struct ReplaceLifetimeAndTy<'a>(Lifetime, &'a HashMap>); 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()) }