From 5363f350887b1e5b5dd21a86f88c8af9d7fea6da Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:18:25 +0200 Subject: Merging upstream version 1.67.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/yoke-derive/src/lib.rs | 244 ++++++++++++++++++++++++++++++++++++++ vendor/yoke-derive/src/visitor.rs | 113 ++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 vendor/yoke-derive/src/lib.rs create mode 100644 vendor/yoke-derive/src/visitor.rs (limited to 'vendor/yoke-derive/src') diff --git a/vendor/yoke-derive/src/lib.rs b/vendor/yoke-derive/src/lib.rs new file mode 100644 index 000000000..615669d84 --- /dev/null +++ b/vendor/yoke-derive/src/lib.rs @@ -0,0 +1,244 @@ +// 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 `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))] +pub fn 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 + let mut ty = ty.clone(); + ty.eq_token = None; + ty.default = None; + ty + }) + .collect::>(); + let typarams = tybounds + .iter() + .map(|ty| ty.ident.clone()) + .collect::>(); + // We require all type parameters be 'static, otherwise + // the Yokeable impl becomes really unweildy to generate safely + let static_bounds: Vec = 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. + unsafe impl<'a, #(#tybounds),*> yoke::Yokeable<'a> for #name<#(#typarams),*> where #(#static_bounds),* { + type Output = Self; + #[inline] + fn transform(&self) -> &Self::Output { + self + } + #[inline] + fn transform_owned(self) -> Self::Output { + self + } + #[inline] + unsafe fn make(this: Self::Output) -> Self { + this + } + #[inline] + fn transform_mut(&'a mut self, f: F) + where + F: 'static + for<'b> FnOnce(&'b mut Self::Output) { + f(self) + } + } + // This is safe because there are no lifetime parameters. + unsafe impl<'a, #(#tybounds),*> yoke::IsCovariant<'a> for #name<#(#typarams),*> where #(#static_bounds),* {} + } + } 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| { + if let Ok(i) = a.parse_args::() { + if i == "prove_covariance_manually" { + return true; + } + } + false + }); + if manual_covariance { + let mut structure = Structure::new(input); + let generics_env = typarams.iter().cloned().collect(); + let static_bounds: Vec = typarams + .iter() + .map(|ty| parse_quote!(#ty: 'static)) + .collect(); + let mut yoke_bounds: Vec = vec![]; + structure.bind_with(|_| synstructure::BindStyle::Move); + let body = structure.each_variant(|vi| { + vi.construct(|f, i| { + let binding = format!("__binding_{}", i); + let field = Ident::new(&binding, Span::call_site()); + let fty = 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>` + if has_lt { + let a_ty = replace_lifetime(&f.ty, custom_lt("'a")); + yoke_bounds + .push(parse_quote!(#fty: yoke::Yokeable<'a, Output = #a_ty>)); + } else { + yoke_bounds.push(parse_quote!(#fty: yoke::Yokeable<'a, Output = #fty>)); + } + } + 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 as yoke::Yokeable<'a>>::transform_owned(#field) + } + } else { + // No nested lifetimes, so nothing to be done + quote! { #field } + } + }) + }); + return quote! { + unsafe impl<'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 { + unsafe { + // safety: we have asserted covariance in + // transform_owned + ::core::mem::transmute(self) + } + } + #[inline] + fn transform_owned(self) -> Self::Output { + match self { #body } + } + #[inline] + unsafe fn 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::() == mem::size_of::()); + let ptr: *const Self = (&this as *const Self::Output).cast(); + #[allow(clippy::forget_copy)] // This is a noop if the struct is copy, which Clippy doesn't like + mem::forget(this); + ptr::read(ptr) + } + #[inline] + fn transform_mut(&'a mut self, f: F) + where + F: 'static + for<'b> FnOnce(&'b mut Self::Output) { + unsafe { f(core::mem::transmute::<&'a mut Self, &'a mut Self::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 + unsafe impl<'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] + unsafe fn 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::() == mem::size_of::()); + let ptr: *const Self = (&this as *const Self::Output).cast(); + mem::forget(this); + ptr::read(ptr) + } + #[inline] + fn transform_mut(&'a mut self, f: F) + where + F: 'static + for<'b> FnOnce(&'b mut Self::Output) { + unsafe { f(core::mem::transmute::<&'a mut Self, &'a mut Self::Output>(self)) } + } + } + // This is safe because it is in the same block as the above impl, which only compiles + // if 'a is a covariant lifetime. + unsafe impl<'a, #(#tybounds),*> yoke::IsCovariant<'a> for #name<'a, #(#typarams),*> where #(#static_bounds),* {} + } + } +} + +fn static_lt() -> Lifetime { + Lifetime::new("'static", Span::call_site()) +} + +fn custom_lt(s: &str) -> Lifetime { + Lifetime::new(s, Span::call_site()) +} + +fn replace_lifetime(x: &Type, lt: Lifetime) -> Type { + use syn::fold::Fold; + struct ReplaceLifetime(Lifetime); + + impl Fold for ReplaceLifetime { + fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime { + self.0.clone() + } + } + ReplaceLifetime(lt).fold_type(x.clone()) +} diff --git a/vendor/yoke-derive/src/visitor.rs b/vendor/yoke-derive/src/visitor.rs new file mode 100644 index 000000000..daca1da13 --- /dev/null +++ b/vendor/yoke-derive/src/visitor.rs @@ -0,0 +1,113 @@ +// 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 ). + +//! Visitor for determining whether a type has type and non-static lifetime parameters + +use std::collections::HashSet; +use syn::visit::{visit_lifetime, visit_type, visit_type_path, Visit}; +use syn::{Ident, Lifetime, Type, TypePath}; + +struct TypeVisitor<'a> { + /// The type parameters in scope + typarams: &'a HashSet, + /// Whether we found a type parameter + found_typarams: bool, + /// Whether we found a non-'static lifetime parameter + found_lifetimes: bool, +} + +impl<'a, 'ast> Visit<'ast> for TypeVisitor<'a> { + fn visit_lifetime(&mut self, lt: &'ast Lifetime) { + if lt.ident != "static" { + self.found_lifetimes = true; + } + visit_lifetime(self, lt) + } + fn visit_type_path(&mut self, ty: &'ast TypePath) { + // We only need to check ty.path.get_ident() and not ty.qself or any + // generics in ty.path because the visitor will eventually visit those + // types on its own + if let Some(ident) = ty.path.get_ident() { + if self.typarams.contains(ident) { + self.found_typarams = true; + } + } + + visit_type_path(self, ty) + } +} + +/// Checks if a type has type or lifetime parameters, given the local context of +/// named type parameters. Returns (has_type_params, has_lifetime_params) +pub fn check_type_for_parameters(ty: &Type, typarams: &HashSet) -> (bool, bool) { + let mut visit = TypeVisitor { + typarams, + found_typarams: false, + found_lifetimes: false, + }; + visit_type(&mut visit, ty); + + (visit.found_typarams, visit.found_lifetimes) +} + +#[cfg(test)] +mod tests { + use proc_macro2::Span; + use std::collections::HashSet; + use syn::{parse_quote, Ident}; + + use super::check_type_for_parameters; + fn make_typarams(params: &[&str]) -> HashSet { + params + .iter() + .map(|x| Ident::new(x, Span::call_site())) + .collect() + } + + #[test] + fn test_simple_type() { + let environment = make_typarams(&["T", "U", "V"]); + + let ty = parse_quote!(Foo<'a, T>); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, true)); + + let ty = parse_quote!(Foo); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, false)); + + let ty = parse_quote!(Foo<'static, T>); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, false)); + + let ty = parse_quote!(Foo<'a>); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (false, true)); + + let ty = parse_quote!(Foo<'a, Bar, Baz<(V, u8)>>); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, true)); + + let ty = parse_quote!(Foo<'a, W>); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (false, true)); + } + + #[test] + fn test_assoc_types() { + let environment = make_typarams(&["T"]); + + let ty = parse_quote!(>::Output); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, true)); + + let ty = parse_quote!(>::Output); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, false)); + + let ty = parse_quote!(>::Output); + let check = check_type_for_parameters(&ty, &environment); + assert_eq!(check, (true, false)); + } +} -- cgit v1.2.3