//! Custom derives for `Yokeable` from the `yoke` crate.
use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::spanned::Spanned; use syn::{parse_macro_input, parse_quote, DeriveInput, Ident, Lifetime, Type, WherePredicate}; use synstructure::Structure;
mod visitor;
/// Custom derive for `yoke::Yokeable`, /// /// If your struct contains `zerovec::ZeroMap`, then the compiler will not /// be able to guarantee the lifetime covariance due to the generic types on /// the `ZeroMap` itself. You must add the following attribute in order for /// the custom derive to work with `ZeroMap`. /// /// ```rust,ignore /// #[derive(Yokeable)] /// #[yoke(prove_covariance_manually)] /// ``` /// /// Beyond this case, if the derive fails to compile due to lifetime issues, it /// means that the lifetime is not covariant and `Yokeable` is not safe to implement. #[proc_macro_derive(Yokeable, attributes(yoke))] pubfn yokeable_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(yokeable_derive_impl(&input))
}
fn yokeable_derive_impl(input: &DeriveInput) -> TokenStream2 { let 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<_>>(); // We require all type parameters be 'static, otherwise // the Yokeable impl becomes really unweildy to generate safely let static_bounds: Vec<WherePredicate> = typarams
.iter()
.map(|ty| parse_quote!(#ty: 'static))
.collect(); let lts = input.generics.lifetimes().count(); if lts == 0 { let name = &input.ident;
quote! { // This is safe because there are no lifetime parameters. unsafeimpl<'a, #(#tybounds),*> yoke::Yokeable<'a> for#name<#(#typarams),*> where#(#static_bounds,)* Self: Sized { type Output = Self; #[inline] fn transform(&self) -> &Self::Output { self
} #[inline] fn transform_owned(self) -> Self::Output { self
} #[inline] unsafefn make(this: Self::Output) -> Self {
this
} #[inline] fn transform_mut<F>(&'a mut self, f: F) where
F: 'static + for<'b> FnOnce(&'b mut Self::Output) {
f(self)
}
}
}
} else { if lts != 1 { return syn::Error::new(
input.generics.span(), "derive(Yokeable) cannot have multiple lifetime parameters",
)
.to_compile_error();
} let name = &input.ident; let manual_covariance = input.attrs.iter().any(|a| { iflet Ok(i) = a.parse_args::<Ident>() { if i == "prove_covariance_manually" { returntrue;
}
} false
}); if manual_covariance { letmut structure = Structure::new(input); let generics_env = typarams.iter().cloned().collect(); let static_bounds: Vec<WherePredicate> = typarams
.iter()
.map(|ty| parse_quote!(#ty: 'static))
.collect(); letmut yoke_bounds: Vec<WherePredicate> = vec![];
structure.bind_with(|_| synstructure::BindStyle::Move); let owned_body = structure.each_variant(|vi| {
vi.construct(|f, i| { let binding = format!("__binding_{i}"); let field = Ident::new(&binding, Span::call_site()); let fty_static = replace_lifetime(&f.ty, static_lt());
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 // Yokeable on its own. However, if there are type parameters, there may be complex preconditions // to `FieldTy: Yokeable` that need to be satisfied. We get them to be satisfied by requiring // `FieldTy<'static>: Yokeable<FieldTy<'a>>` if has_lt { let fty_a = replace_lifetime(&f.ty, custom_lt("'a"));
yoke_bounds.push(
parse_quote!(#fty_static: yoke::Yokeable<'a, Output = #fty_a>),
);
} else {
yoke_bounds.push(
parse_quote!(#fty_static: yoke::Yokeable<'a, Output = #fty_static>),
);
}
} if has_ty || has_lt { // By calling transform_owned on all fields, we manually prove // that the lifetimes are covariant, since this requirement // must already be true for the type that implements transform_owned().
quote! {
<#fty_staticas yoke::Yokeable<'a>>::transform_owned(#field)
}
} else { // No nested lifetimes, so nothing to be done
quote! { #field }
}
})
}); let borrowed_body = structure.each(|binding| { let f = binding.ast(); let field = &binding.binding;
let (has_ty, has_lt) = visitor::check_type_for_parameters(&f.ty, &generics_env);
if has_ty || has_lt { let fty_static = replace_lifetime(&f.ty, static_lt()); let fty_a = replace_lifetime(&f.ty, custom_lt("'a")); // We also must assert that each individual field can `transform()` correctly // // Even though transform_owned() does such an assertion already, CoerceUnsized // can cause type transformations that allow it to succeed where this would fail. // We need to check both. // // https://github.com/unicode-org/icu4x/issues/2928
quote! { let _: &#fty_a = &<#fty_staticas yoke::Yokeable<'a>>::transform(#field);
}
} else { // No nested lifetimes, so nothing to be done
quote! {}
}
}); return quote! { unsafeimpl<'a, #(#tybounds),*> yoke::Yokeable<'a> for#name<'static, #(#typarams),*> where#(#static_bounds,)* #(#yoke_bounds,)* { type Output = #name<'a, #(#typarams),*>; #[inline] fn transform(&'a self) -> &'a Self::Output { // These are just type asserts, we don't need them for anything iffalse { matchself { #borrowed_body
}
} unsafe { // safety: we have asserted covariance in // transform_owned
::core::mem::transmute(self)
}
} #[inline] fn transform_owned(self) -> Self::Output { matchself { #owned_body }
} #[inline] unsafefn make(this: Self::Output) -> Self { use core::{mem, ptr}; // unfortunately Rust doesn't think `mem::transmute` is possible since it's not sure the sizes // are the same
debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>()); let ptr: *constSelf = (&this as *constSelf::Output).cast(); #[allow(forgetting_copy_types, clippy::forget_copy, clippy::forget_non_drop)] // This is a noop if the struct is copy, which Clippy doesn't like
mem::forget(this);
ptr::read(ptr)
} #[inline] fn transform_mut<F>(&'a mut self, f: F) where
F: 'static + for<'b> FnOnce(&'b mut Self::Output) { unsafe { f(core::mem::transmute::<&'a mut Self, &'a mutSelf::Output>(self)) }
}
}
};
}
quote! { // This is safe because as long as `transform()` compiles, // we can be sure that `'a` is a covariant lifetime on `Self` // // This will not work for structs involving ZeroMap since // the compiler does not know that ZeroMap is covariant. // // This custom derive can be improved to handle this case when // necessary unsafeimpl<'a, #(#tybounds),*> yoke::Yokeable<'a> for#name<'static, #(#typarams),*> where #(#static_bounds,)* { type Output = #name<'a, #(#typarams),*>; #[inline] fn transform(&'a self) -> &'a Self::Output { self
} #[inline] fn transform_owned(self) -> Self::Output { self
} #[inline] unsafefn make(this: Self::Output) -> Self { use core::{mem, ptr}; // unfortunately Rust doesn't think `mem::transmute` is possible since it's not sure the sizes // are the same
debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>()); let ptr: *constSelf = (&this as *constSelf::Output).cast(); #[allow(forgetting_copy_types, clippy::forget_copy, clippy::forget_non_drop)] // This is a noop if the struct is copy, which Clippy doesn't like
mem::forget(this);
ptr::read(ptr)
} #[inline] fn transform_mut<F>(&'a mut self, f: F) where
F: 'static + for<'b> FnOnce(&'b mut Self::Output) { unsafe { f(core::mem::transmute::<&'a mut Self, &'a mutSelf::Output>(self)) }
}
}
}
}
}
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.