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<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))] pubfn 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| { iflet Ok(i) = a.parse_args::<Ident>() { if i == name { returntrue;
}
} 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> { letmut params = HashSet::new(); for attr in attrs { iflet Ok(list) = attr.parse_args::<MetaList>() { if list.path.is_ident("may_borrow") { iflet 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 { letmut tybounds = input
.generics
.type_params()
.map(|ty| { // Strip out param defaults, we don't need them in the impl letmut 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()
.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());
letmut 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! {
<#ftyas 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 letmut typarams_c = typarams.clone();
if generics_may_borrow { for typaram_c in &mut typarams_c { iflet 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));
}
}
}
/// 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>>);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.