From e9b9610d681f8ae5e829357bc59bbe088df646b0 Mon Sep 17 00:00:00 2001 From: Alex Kirszenberg Date: Thu, 24 Aug 2023 10:40:51 +0200 Subject: [PATCH] Generic types over Vcs (vercel/turbo#5738) ### Description This PR enables types generic over `Vc`s to work as value types. In other words, where we would previously need to declare all generic/container types over a given value type at the definition site, like the following: ```rust #[turbo_tasks::value] struct Thing; #[turbo_tasks::value(transparent)] struct Things(Vec>); #[turbo_tasks::value(transparent)] struct ThingOption(Option>); #[turbo_tasks::value(transparent)] struct ThingSet(IndexSet>); #[turbo_tasks::value(transparent)] struct ThingMap(IndexMap, Vc>); let vec: Vc = Vc::cell(vec![Thing.cell()]); let option: Vc = Vc::cell(Some(Thing.cell())); // etc. ``` We can now refer to `Vc>>` directly, without needing to declare an intermediate transparent value type: ```rust #[turbo_tasks::value] struct Thing; let vec: Vc> = Vc::cell(vec![Thing.cell()]); let option: Vc> = Vc::cell(Some(Thing.cell())); // etc. ``` The following generic types and methods are currently supported: * `Option>` * `::is_some(self: Vc) -> Vc` * `::is_none(self: Vc) -> Vc` * `Vec>` * `::is_empty(self: Vc) -> Vc` * `::len(self: Vc) -> Vc` * `IndexSet>` * `::is_empty(self: Vc) -> Vc` * `::len(self: Vc) -> Vc` * `IndexMap, Vc>` * `::is_empty(self: Vc) -> Vc` * `::len(self: Vc) -> Vc` All these value types also implement `ValueDefault`, which means that you can instantiate them using `Default::default()` or `Vc::default()`: ```rust let vec: Vc>> = Default::default() ``` They also work recursively, but I would recommend creating intermediate types instead of this in most (all?) cases: ```rust let vec: Vc>>>> = Default::default() ``` ### Notes and implementation details 1. This only works when the generic type is wrapped in a `Vc`. In the example above, this would not cover `Vc>` directly. The implementation relies on the fact that all `Type>` are represented the exact same way in memory, and as such can all be cast to some common `Type>` for storage. 2. Like primitives, these generic types can currently only be declared in `turbo_tasks` itself. Technically, if a crate owns a type `A`, it could also register `A` as a generic type, so we might want to allow this in the future (e.g. `AliasMap` in `turbopack`?) 3. There's some potential performance/correctness issues around `cell`ing, I'll defer to @sokra to figure out if this approach is worth it. ### Testing Instructions I added some tests to `all_in_one.rs`. --------- Co-authored-by: Justin Ridgewell Co-authored-by: Tobias Koppers --- crates/turbo-tasks-build/src/lib.rs | 23 ++- .../src/generic_type_input.rs | 21 +++ crates/turbo-tasks-macros-shared/src/lib.rs | 2 + crates/turbo-tasks-macros/Cargo.toml | 2 +- .../src/generic_type_macro.rs | 148 ++++++++++++++++++ crates/turbo-tasks-macros/src/lib.rs | 26 +++ .../turbo-tasks-macros/src/primitive_macro.rs | 3 +- .../src/value_impl_macro.rs | 26 +-- crates/turbo-tasks-macros/src/value_macro.rs | 26 ++- crates/turbo-tasks-memory/Cargo.toml | 1 + crates/turbo-tasks-memory/tests/all_in_one.rs | 58 ++++++- crates/turbo-tasks/src/debug/internal.rs | 1 + crates/turbo-tasks/src/debug/mod.rs | 70 +++++++++ crates/turbo-tasks/src/generics/index_map.rs | 71 +++++++++ crates/turbo-tasks/src/generics/index_set.rs | 71 +++++++++ crates/turbo-tasks/src/generics/mod.rs | 4 + crates/turbo-tasks/src/generics/option.rs | 70 +++++++++ crates/turbo-tasks/src/generics/vec.rs | 66 ++++++++ crates/turbo-tasks/src/lib.rs | 1 + crates/turbo-tasks/src/raw_vc.rs | 2 +- crates/turbo-tasks/src/vc/cast.rs | 20 ++- crates/turbo-tasks/src/vc/cell_mode.rs | 7 +- crates/turbo-tasks/src/vc/mod.rs | 17 +- crates/turbo-tasks/src/vc/read.rs | 47 ++++-- 24 files changed, 743 insertions(+), 40 deletions(-) create mode 100644 crates/turbo-tasks-macros-shared/src/generic_type_input.rs create mode 100644 crates/turbo-tasks-macros/src/generic_type_macro.rs create mode 100644 crates/turbo-tasks/src/generics/index_map.rs create mode 100644 crates/turbo-tasks/src/generics/index_set.rs create mode 100644 crates/turbo-tasks/src/generics/mod.rs create mode 100644 crates/turbo-tasks/src/generics/option.rs create mode 100644 crates/turbo-tasks/src/generics/vec.rs diff --git a/crates/turbo-tasks-build/src/lib.rs b/crates/turbo-tasks-build/src/lib.rs index 10ae097733467..97bb31f0d610d 100644 --- a/crates/turbo-tasks-build/src/lib.rs +++ b/crates/turbo-tasks-build/src/lib.rs @@ -18,7 +18,7 @@ use turbo_tasks_macros_shared::{ get_impl_function_ident, get_native_function_ident, get_path_ident, get_register_trait_methods_ident, get_register_value_type_ident, get_trait_default_impl_function_ident, get_trait_impl_function_ident, get_trait_type_ident, - get_type_ident, PrimitiveInput, ValueTraitArguments, + get_type_ident, GenericTypeInput, PrimitiveInput, ValueTraitArguments, }; pub fn generate_register() { @@ -212,6 +212,7 @@ impl<'a> RegisterContext<'a> { fn process_enum(&mut self, enum_item: ItemEnum) -> Result<()> { if has_attribute(&enum_item.attrs, "value") { self.add_value(&enum_item.ident); + self.add_value_debug_impl(&enum_item.ident); } Ok(()) } @@ -286,6 +287,7 @@ impl<'a> RegisterContext<'a> { fn process_struct(&mut self, struct_item: ItemStruct) -> Result<()> { if has_attribute(&struct_item.attrs, "value") { self.add_value(&struct_item.ident); + self.add_value_debug_impl(&struct_item.ident); } Ok(()) } @@ -347,6 +349,23 @@ impl<'a> RegisterContext<'a> { }; self.add_value(&ident); + self.add_value_debug_impl(&ident); + } else if macro_item + .mac + .path + .is_ident("__turbo_tasks_internal_generic_type") + { + let input = macro_item.mac.tokens; + let input = syn::parse2::(input).unwrap(); + + let ty = input.ty; + let Some(ident) = get_type_ident(&ty) else { + return Ok(()); + }; + + // Generic types must implement `ValueDebug` manually, as there's currently no + // easy way to automate the process. + self.add_value(&ident); } Ok(()) @@ -376,7 +395,9 @@ impl<'a> RegisterContext<'a> { "{} is declared more than once", ident ); + } + fn add_value_debug_impl(&mut self, ident: &Ident) { // register default debug impl generated by proc macro self.register_debug_impl(ident).unwrap(); self.add_value_trait( diff --git a/crates/turbo-tasks-macros-shared/src/generic_type_input.rs b/crates/turbo-tasks-macros-shared/src/generic_type_input.rs new file mode 100644 index 0000000000000..2c9e08375f99a --- /dev/null +++ b/crates/turbo-tasks-macros-shared/src/generic_type_input.rs @@ -0,0 +1,21 @@ +use syn::{ + parse::{Parse, ParseStream}, + Generics, Result, Token, Type, +}; + +/// The input of the `generic_type` macro. +#[derive(Debug)] +pub struct GenericTypeInput { + pub generics: Generics, + pub ty: Type, +} + +impl Parse for GenericTypeInput { + fn parse(input: ParseStream) -> Result { + let generics: Generics = input.parse()?; + let _comma: Token![,] = input.parse()?; + let ty: Type = input.parse()?; + + Ok(GenericTypeInput { generics, ty }) + } +} diff --git a/crates/turbo-tasks-macros-shared/src/lib.rs b/crates/turbo-tasks-macros-shared/src/lib.rs index 4b0c1189dcaf0..cbeb9aa2d3399 100644 --- a/crates/turbo-tasks-macros-shared/src/lib.rs +++ b/crates/turbo-tasks-macros-shared/src/lib.rs @@ -2,11 +2,13 @@ #![feature(box_patterns)] mod expand; +mod generic_type_input; mod ident; mod primitive_input; mod value_trait_arguments; pub use expand::*; +pub use generic_type_input::GenericTypeInput; pub use ident::*; pub use primitive_input::PrimitiveInput; pub use value_trait_arguments::ValueTraitArguments; diff --git a/crates/turbo-tasks-macros/Cargo.toml b/crates/turbo-tasks-macros/Cargo.toml index ae497850cafe2..e3539ad319608 100644 --- a/crates/turbo-tasks-macros/Cargo.toml +++ b/crates/turbo-tasks-macros/Cargo.toml @@ -15,5 +15,5 @@ convert_case = "0.6.0" proc-macro-error = "1.0.4" proc-macro2 = { workspace = true } quote = { workspace = true } -syn = { workspace = true, features = ["full", "extra-traits"] } +syn = { workspace = true, features = ["full", "extra-traits", "visit-mut"] } turbo-tasks-macros-shared = { workspace = true } diff --git a/crates/turbo-tasks-macros/src/generic_type_macro.rs b/crates/turbo-tasks-macros/src/generic_type_macro.rs new file mode 100644 index 0000000000000..4bd1b50b6e980 --- /dev/null +++ b/crates/turbo-tasks-macros/src/generic_type_macro.rs @@ -0,0 +1,148 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, spanned::Spanned, visit_mut::VisitMut, GenericParam, Lifetime, Type}; +use turbo_tasks_macros_shared::{get_type_ident, GenericTypeInput}; + +use crate::value_macro::value_type_and_register; + +pub fn generic_type(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as GenericTypeInput); + + for param in &input.generics.params { + match param { + syn::GenericParam::Type(ty) => { + if ty.ident == "Vc" { + ty.span() + .unwrap() + .error("Vc is a reserved name in generic_type") + .emit(); + } + } + syn::GenericParam::Lifetime(lt) => { + lt.span() + .unwrap() + .error("lifetime parameters are not supported in generic_type") + .emit(); + } + syn::GenericParam::Const(c) => { + c.span() + .unwrap() + .error("const parameters are not supported in generic_type") + .emit(); + } + } + } + + let (impl_generics, _, where_clause) = input.generics.split_for_impl(); + + let repr = replace_generics_with_unit(input.generics.params.iter(), &input.ty); + + let ty = input.ty; + let Some(ident) = get_type_ident(&ty) else { + return quote! { + // An error occurred while parsing the ident. + } + .into(); + }; + + let mut generics_with_static = input.generics.clone(); + for param in &mut generics_with_static.params { + if let GenericParam::Type(param) = param { + param.bounds.push(syn::TypeParamBound::Lifetime(Lifetime { + ident: syn::Ident::new("static", param.ident.span()), + apostrophe: param.ident.span(), + })) + } + } + + let value_type_and_register = value_type_and_register( + &ident, + quote! { #ty }, + Some(&generics_with_static), + quote! { + turbo_tasks::VcTransparentRead<#ty, #ty, #repr> + }, + quote! { + turbo_tasks::VcCellSharedMode<#ty> + }, + quote! { + turbo_tasks::ValueType::new_with_any_serialization::<#repr>() + }, + ); + + quote! { + #value_type_and_register + + impl #impl_generics Vc<#ty> #where_clause { + /// Converts this `Vc` to a generic representation. + fn to_repr(vc: Self) -> Vc<#repr> { + unsafe { + turbo_tasks::Vc::from_raw(Vc::into_raw(vc)) + } + } + + /// Converts a generic representation of this `Vc` to the proper `Vc` type. + /// + /// # Safety + /// + /// The caller must ensure that the `repr` is a valid representation of this `Vc`. + unsafe fn from_repr(vc: Vc<#repr>) -> Self { + unsafe { + turbo_tasks::Vc::from_raw(Vc::into_raw(vc)) + } + } + } + } + .into() +} + +struct ReplaceGenericsVisitor<'a> { + generics: &'a std::collections::HashSet, +} + +impl<'a> VisitMut for ReplaceGenericsVisitor<'a> { + fn visit_type_mut(&mut self, node: &mut Type) { + if let Type::Path(type_path) = node { + if type_path.qself.is_none() + && type_path.path.segments.len() == 1 + && type_path.path.segments[0].arguments.is_none() + && self + .generics + .contains(&type_path.path.segments[0].ident.to_string()) + { + // Replace the whole path with () + *node = syn::parse_quote! { () }; + return; + } + } + + syn::visit_mut::visit_type_mut(self, node); + } +} + +/// Replaces all instances of `params` generic types in `ty` with the unit type +/// `()`. +fn replace_generics_with_unit<'a, P>(params: P, ty: &Type) -> Type +where + P: IntoIterator, +{ + let generics_set: std::collections::HashSet<_> = params + .into_iter() + .filter_map(|param| { + if let GenericParam::Type(type_param) = param { + Some(type_param.ident.to_string()) + } else { + None + } + }) + .collect(); + + let mut new_ty = ty.clone(); + let mut visitor = ReplaceGenericsVisitor { + generics: &generics_set, + }; + + syn::visit_mut::visit_type_mut(&mut visitor, &mut new_ty); + + new_ty +} diff --git a/crates/turbo-tasks-macros/src/lib.rs b/crates/turbo-tasks-macros/src/lib.rs index 2dfafc6864137..66c2304972960 100644 --- a/crates/turbo-tasks-macros/src/lib.rs +++ b/crates/turbo-tasks-macros/src/lib.rs @@ -5,6 +5,7 @@ mod derive; mod func; mod function_macro; +mod generic_type_macro; mod primitive_macro; mod value_impl_macro; mod value_macro; @@ -147,3 +148,28 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { pub fn primitive(input: TokenStream) -> TokenStream { primitive_macro::primitive(input) } + +/// Registers a value type that is generic over the `Vc` it contains. +/// +/// # Example +/// +/// ``` +/// use crate::generic_type as __turbo_tasks_internal_generic_type; +/// +/// __turbo_tasks_internal_generic_type!(, GenericType, Vc>); +/// +/// // Now you can do the following, for any `A` and `B` value types: +/// +/// let vc: Vc, Vc>> = Vc::cell( +/// GenericType::new( +/// Vc::cell(42), +/// Vc::cell("hello".to_string()) +/// ) +/// ); +/// ``` +#[allow_internal_unstable(min_specialization, into_future, trivial_bounds)] +#[proc_macro_error] +#[proc_macro] +pub fn generic_type(input: TokenStream) -> TokenStream { + generic_type_macro::generic_type(input) +} diff --git a/crates/turbo-tasks-macros/src/primitive_macro.rs b/crates/turbo-tasks-macros/src/primitive_macro.rs index c85b1e7ad5bef..e6a2590805162 100644 --- a/crates/turbo-tasks-macros/src/primitive_macro.rs +++ b/crates/turbo-tasks-macros/src/primitive_macro.rs @@ -36,8 +36,9 @@ pub fn primitive(input: TokenStream) -> TokenStream { let value_type_and_register = value_type_and_register( &ident, quote! { #ty }, + None, quote! { - turbo_tasks::VcTransparentRead<#ty, #ty> + turbo_tasks::VcTransparentRead<#ty, #ty, #ty> }, quote! { turbo_tasks::VcCellSharedMode<#ty> diff --git a/crates/turbo-tasks-macros/src/value_impl_macro.rs b/crates/turbo-tasks-macros/src/value_impl_macro.rs index 9e9c0fd45b3be..d04c3ca37e097 100644 --- a/crates/turbo-tasks-macros/src/value_impl_macro.rs +++ b/crates/turbo-tasks-macros/src/value_impl_macro.rs @@ -6,7 +6,7 @@ use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - Attribute, Error, ExprPath, ImplItem, ImplItemMethod, ItemImpl, Lit, LitStr, Meta, + Attribute, Error, ExprPath, Generics, ImplItem, ImplItemMethod, ItemImpl, Lit, LitStr, Meta, MetaNameValue, Path, Result, Token, Type, }; use turbo_tasks_macros_shared::{ @@ -158,9 +158,9 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { } #[doc(hidden)] - pub(crate) static #native_function_ident: #native_function_ty = #ty::#native_function_ident; + pub(crate) static #native_function_ident: #native_function_ty = <#ty>::#native_function_ident; #[doc(hidden)] - pub(crate) static #native_function_id_ident: #native_function_id_ty = #ty::#native_function_id_ident; + pub(crate) static #native_function_id_ident: #native_function_id_ty = <#ty>::#native_function_id_ident; }) } } @@ -176,12 +176,15 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { fn trait_value_impl( ty: &Type, + generics: &Generics, ty_ident: &Ident, trait_path: &Path, items: &[ImplItem], ) -> TokenStream2 { let trait_ident = get_path_ident(trait_path); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let register = get_register_trait_methods_ident(&trait_ident, ty_ident); let mut trait_registers = Vec::new(); @@ -263,7 +266,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { #[doc(hidden)] // #[turbo_tasks::async_trait] - impl #inline_extension_trait_ident for #ty { + impl #impl_generics #inline_extension_trait_ident for #ty #where_clause { #[allow(declare_interior_mutable_const)] #[doc(hidden)] const #native_function_ident: #native_function_ty = #native_function_def; @@ -300,9 +303,9 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { // NOTE(alexkirsz) We can't have a general `turbo_tasks::Upcast> for T where T: Trait` because // rustc complains: error[E0210]: type parameter `T` must be covered by another type when it appears before // the first local type (`dyn Trait`). - unsafe impl turbo_tasks::Upcast> for #ty {} + unsafe impl #impl_generics turbo_tasks::Upcast> for #ty #where_clause {} - impl #trait_path for #ty { + impl #impl_generics #trait_path for #ty #where_clause { #(#trait_functions)* } @@ -324,8 +327,13 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { match &item.trait_ { None => inherent_value_impl(&item.self_ty, &ty_ident, &item.items).into(), - Some((_, trait_path, _)) => { - trait_value_impl(&item.self_ty, &ty_ident, trait_path, &item.items).into() - } + Some((_, trait_path, _)) => trait_value_impl( + &item.self_ty, + &item.generics, + &ty_ident, + trait_path, + &item.items, + ) + .into(), } } diff --git a/crates/turbo-tasks-macros/src/value_macro.rs b/crates/turbo-tasks-macros/src/value_macro.rs index 2ddb9f5eaab78..f7d57a7696f79 100644 --- a/crates/turbo-tasks-macros/src/value_macro.rs +++ b/crates/turbo-tasks-macros/src/value_macro.rs @@ -6,8 +6,8 @@ use syn::{ parse_macro_input, punctuated::Punctuated, spanned::Spanned, - Error, Fields, FieldsUnnamed, Item, ItemEnum, ItemStruct, Lit, LitStr, Meta, MetaNameValue, - Result, Token, + Error, Fields, FieldsUnnamed, Generics, Item, ItemEnum, ItemStruct, Lit, LitStr, Meta, + MetaNameValue, Result, Token, }; use turbo_tasks_macros_shared::{ get_register_value_type_ident, get_value_type_id_ident, get_value_type_ident, @@ -241,7 +241,7 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { content.0 }, quote! { - turbo_tasks::VcTransparentRead::<#ident, #inner_type> + turbo_tasks::VcTransparentRead::<#ident, #inner_type, #ident> }, ) } else { @@ -354,8 +354,14 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { quote! {} }; - let value_type_and_register_code = - value_type_and_register(ident, quote! { #ident }, read, cell_mode, new_value_type); + let value_type_and_register_code = value_type_and_register( + ident, + quote! { #ident }, + None, + read, + cell_mode, + new_value_type, + ); let expanded = quote! { #derive @@ -382,6 +388,7 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { pub fn value_type_and_register( ident: &Ident, ty: proc_macro2::TokenStream, + generics: Option<&Generics>, read: proc_macro2::TokenStream, cell_mode: proc_macro2::TokenStream, new_value_type: proc_macro2::TokenStream, @@ -391,6 +398,13 @@ pub fn value_type_and_register( let value_type_id_ident = get_value_type_id_ident(ident); let register_value_type_ident = get_register_value_type_ident(ident); + let (impl_generics, where_clause) = if let Some(generics) = generics { + let (impl_generics, _, where_clause) = generics.split_for_impl(); + (quote! { #impl_generics }, quote! { #where_clause }) + } else { + (quote!(), quote!()) + }; + quote! { #[doc(hidden)] static #value_type_init_ident: turbo_tasks::macro_helpers::OnceCell< @@ -428,7 +442,7 @@ pub fn value_type_and_register( }).register(global_name); } - unsafe impl turbo_tasks::VcValueType for #ty { + unsafe impl #impl_generics turbo_tasks::VcValueType for #ty #where_clause { type Read = #read; type CellMode = #cell_mode; diff --git a/crates/turbo-tasks-memory/Cargo.toml b/crates/turbo-tasks-memory/Cargo.toml index 9f5078060a112..fe25a7cc9c36e 100644 --- a/crates/turbo-tasks-memory/Cargo.toml +++ b/crates/turbo-tasks-memory/Cargo.toml @@ -28,6 +28,7 @@ turbo-tasks-malloc = { workspace = true, default-features = false } [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } +indexmap = { workspace = true } lazy_static = { workspace = true } serde = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/crates/turbo-tasks-memory/tests/all_in_one.rs b/crates/turbo-tasks-memory/tests/all_in_one.rs index b0ead38cf16ad..f2d0644447bea 100644 --- a/crates/turbo-tasks-memory/tests/all_in_one.rs +++ b/crates/turbo-tasks-memory/tests/all_in_one.rs @@ -2,7 +2,8 @@ #![feature(async_fn_in_trait)] use anyhow::{anyhow, Result}; -use turbo_tasks::{Value, ValueToString, Vc}; +use indexmap::{IndexMap, IndexSet}; +use turbo_tasks::{debug::ValueDebug, Value, ValueToString, Vc}; use turbo_tasks_testing::{register, run}; register!(); @@ -48,6 +49,61 @@ async fn all_in_one() { let b_erased_other: Vc> = Vc::upcast(Vc::::cell(10)); let c_erased_invalid: Vc> = a_erased.add(b_erased_other); assert!(c_erased_invalid.resolve().await.is_err()); + + // Testing generic types. + + let vc_42 = Vc::cell(42); + + let option: Vc>> = Vc::cell(Some(vc_42)); + assert_eq!(*option.is_some().await?, true); + assert_eq!(*option.is_none().await?, false); + assert_eq!(&*option.await?, &Some(vc_42)); + assert_eq!(option.dbg().await?.to_string(), "Some(\n 42,\n)"); + + let option: Vc>> = Default::default(); + assert_eq!(*option.is_some().await?, false); + assert_eq!(*option.is_none().await?, true); + assert_eq!(&*option.await?, &None); + assert_eq!(option.dbg().await?.to_string(), "None"); + + let vec: Vc>> = Vc::cell(vec![vc_42]); + assert_eq!(*vec.len().await?, 1); + assert_eq!(*vec.is_empty().await?, false); + assert_eq!(&*vec.await?, &[vc_42]); + assert_eq!(vec.dbg().await?.to_string(), "[\n 42,\n]"); + + let vec: Vc>> = Default::default(); + assert_eq!(*vec.len().await?, 0); + assert_eq!(*vec.is_empty().await?, true); + assert_eq!(vec.dbg().await?.to_string(), "[]"); + + let vec: Vc>>>> = Default::default(); + assert_eq!(*vec.len().await?, 0); + assert_eq!(vec.dbg().await?.to_string(), "[]"); + + let set: Vc>> = Vc::cell(IndexSet::from([vc_42])); + assert_eq!(*set.len().await?, 1); + assert_eq!(*set.is_empty().await?, false); + assert_eq!(&*set.await?, &IndexSet::from([vc_42])); + assert_eq!(set.dbg().await?.to_string(), "{\n 42,\n}"); + + let set: Vc>> = Default::default(); + assert_eq!(*set.len().await?, 0); + assert_eq!(*set.is_empty().await?, true); + assert_eq!(&*set.await?, &IndexSet::>::default()); + assert_eq!(set.dbg().await?.to_string(), "{}"); + + let map: Vc> = Vc::cell(IndexMap::from([(vc_42, vc_42)])); + assert_eq!(*map.len().await?, 1); + assert_eq!(*map.is_empty().await?, false); + assert_eq!(&*map.await?, &IndexMap::from([(vc_42, vc_42)])); + assert_eq!(map.dbg().await?.to_string(), "{\n 42: 42,\n}"); + + let map: Vc, Vc>> = Default::default(); + assert_eq!(*map.len().await?, 0); + assert_eq!(*map.is_empty().await?, true); + assert_eq!(&*map.await?, &IndexMap::, Vc>::default()); + assert_eq!(map.dbg().await?.to_string(), "{}"); } } diff --git a/crates/turbo-tasks/src/debug/internal.rs b/crates/turbo-tasks/src/debug/internal.rs index 134cbad8c7400..3afea735f86c4 100644 --- a/crates/turbo-tasks/src/debug/internal.rs +++ b/crates/turbo-tasks/src/debug/internal.rs @@ -60,6 +60,7 @@ impl<'a> std::fmt::Debug for FormattingStruct<'a> { } /// Debug implementation that prints an unquoted, unescaped string. +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PassthroughDebug<'a>(Cow<'a, str>); impl<'a> PassthroughDebug<'a> { diff --git a/crates/turbo-tasks/src/debug/mod.rs b/crates/turbo-tasks/src/debug/mod.rs index 9eb674f832924..82d9edcfa1141 100644 --- a/crates/turbo-tasks/src/debug/mod.rs +++ b/crates/turbo-tasks/src/debug/mod.rs @@ -1,6 +1,7 @@ use std::fmt::{Debug, Display}; use auto_hash_map::{AutoMap, AutoSet}; +use indexmap::{IndexMap, IndexSet}; use turbo_tasks::Vc; pub use turbo_tasks_macros::ValueDebugFormat; @@ -249,6 +250,75 @@ where } } +impl ValueDebugFormat for IndexSet +where + T: ValueDebugFormat, +{ + fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString { + if depth == 0 { + return ValueDebugFormatString::Sync(std::any::type_name::().to_string()); + } + + let values = self + .iter() + .map(|value| value.value_debug_format(depth.saturating_sub(1))) + .collect::>(); + + ValueDebugFormatString::Async(Box::pin(async move { + let mut values_string = IndexSet::new(); + for value in values { + let value = match value { + ValueDebugFormatString::Sync(string) => string, + ValueDebugFormatString::Async(future) => future.await?, + }; + values_string.insert(PassthroughDebug::new_string(value)); + } + Ok(format!("{:#?}", values_string)) + })) + } +} + +impl ValueDebugFormat for IndexMap +where + K: ValueDebugFormat, + V: ValueDebugFormat, +{ + fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString { + if depth == 0 { + return ValueDebugFormatString::Sync(std::any::type_name::().to_string()); + } + + let values = self + .iter() + .map(|(key, value)| { + ( + key.value_debug_format(depth.saturating_sub(1)), + value.value_debug_format(depth.saturating_sub(1)), + ) + }) + .collect::>(); + + ValueDebugFormatString::Async(Box::pin(async move { + let mut values_string = IndexMap::new(); + for (key, value) in values { + let key = match key { + ValueDebugFormatString::Sync(string) => string, + ValueDebugFormatString::Async(future) => future.await?, + }; + let value = match value { + ValueDebugFormatString::Sync(string) => string, + ValueDebugFormatString::Async(future) => future.await?, + }; + values_string.insert( + PassthroughDebug::new_string(key), + PassthroughDebug::new_string(value), + ); + } + Ok(format!("{:#?}", values_string)) + })) + } +} + macro_rules! tuple_impls { ( $( $name:ident )+ ) => { impl<$($name: ValueDebugFormat),+> ValueDebugFormat for ($($name,)+) diff --git a/crates/turbo-tasks/src/generics/index_map.rs b/crates/turbo-tasks/src/generics/index_map.rs new file mode 100644 index 0000000000000..3858bfd2ded31 --- /dev/null +++ b/crates/turbo-tasks/src/generics/index_map.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use indexmap::IndexMap; +// This specific macro identifier is detected by turbo-tasks-build. +use turbo_tasks_macros::generic_type as __turbo_tasks_internal_generic_type; + +use crate::{ + self as turbo_tasks, + debug::{ValueDebug, ValueDebugFormat, ValueDebugString}, + ValueDefault, Vc, +}; + +__turbo_tasks_internal_generic_type!(, IndexMap, Vc>); + +#[turbo_tasks::function] +async fn index_map_len(index_map: Vc, Vc<()>>>) -> Result> { + let index_map = index_map.await?; + Ok(Vc::cell(index_map.len())) +} + +#[turbo_tasks::function] +async fn index_map_is_empty(index_map: Vc, Vc<()>>>) -> Result> { + let index_map = index_map.await?; + Ok(Vc::cell(index_map.is_empty())) +} + +impl Vc, Vc>> { + /// See [`IndexMap::len`]. + pub fn len(self) -> Vc { + index_map_len(Self::to_repr(self)) + } + + /// See [`IndexMap::is_empty`]. + pub fn is_empty(self) -> Vc { + index_map_is_empty(Self::to_repr(self)) + } +} + +#[turbo_tasks::function] +fn index_map_default() -> Vc, Vc<()>>> { + Vc::cell(Default::default()) +} + +impl ValueDefault for IndexMap, Vc> { + fn value_default() -> Vc { + // Safety: `index_map_default` creates an empty map, which is a valid + // representation of any index set of `Vc`. + unsafe { Vc::::from_repr(index_map_default()) } + } +} + +#[turbo_tasks::function] +async fn index_map_dbg_depth( + index_map: Vc, Vc<()>>>, + depth: usize, +) -> Result> { + index_map + .await? + .value_debug_format(depth) + .try_to_value_debug_string() + .await +} + +impl ValueDebug for IndexMap, Vc> { + fn dbg(self: Vc) -> Vc { + index_map_dbg_depth(Vc::::to_repr(self), usize::MAX) + } + + fn dbg_depth(self: Vc, depth: usize) -> Vc { + index_map_dbg_depth(Vc::::to_repr(self), depth) + } +} diff --git a/crates/turbo-tasks/src/generics/index_set.rs b/crates/turbo-tasks/src/generics/index_set.rs new file mode 100644 index 0000000000000..999f695d056da --- /dev/null +++ b/crates/turbo-tasks/src/generics/index_set.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use indexmap::IndexSet; +// This specific macro identifier is detected by turbo-tasks-build. +use turbo_tasks_macros::generic_type as __turbo_tasks_internal_generic_type; + +use crate::{ + self as turbo_tasks, + debug::{ValueDebug, ValueDebugFormat, ValueDebugString}, + ValueDefault, Vc, +}; + +__turbo_tasks_internal_generic_type!(, IndexSet>); + +#[turbo_tasks::function] +async fn index_set_len(index_set: Vc>>) -> Result> { + let index_set = index_set.await?; + Ok(Vc::cell(index_set.len())) +} + +#[turbo_tasks::function] +async fn index_set_is_empty(index_set: Vc>>) -> Result> { + let index_set = index_set.await?; + Ok(Vc::cell(index_set.is_empty())) +} + +impl Vc>> { + /// See [`IndexSet::len`]. + pub fn len(self) -> Vc { + index_set_len(Self::to_repr(self)) + } + + /// See [`IndexSet::is_empty`]. + pub fn is_empty(self) -> Vc { + index_set_is_empty(Self::to_repr(self)) + } +} + +#[turbo_tasks::function] +fn index_set_default() -> Vc>> { + Vc::cell(Default::default()) +} + +impl ValueDefault for IndexSet> { + fn value_default() -> Vc { + // Safety: `index_set_default` creates an empty set, which is a valid + // representation of any index set of `Vc`. + unsafe { Vc::::from_repr(index_set_default()) } + } +} + +#[turbo_tasks::function] +async fn index_set_dbg_depth( + index_set: Vc>>, + depth: usize, +) -> Result> { + index_set + .await? + .value_debug_format(depth) + .try_to_value_debug_string() + .await +} + +impl ValueDebug for IndexSet> { + fn dbg(self: Vc) -> Vc { + index_set_dbg_depth(Vc::::to_repr(self), usize::MAX) + } + + fn dbg_depth(self: Vc, depth: usize) -> Vc { + index_set_dbg_depth(Vc::::to_repr(self), depth) + } +} diff --git a/crates/turbo-tasks/src/generics/mod.rs b/crates/turbo-tasks/src/generics/mod.rs new file mode 100644 index 0000000000000..9bbc961eb1b69 --- /dev/null +++ b/crates/turbo-tasks/src/generics/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod index_map; +pub(crate) mod index_set; +pub(crate) mod option; +pub(crate) mod vec; diff --git a/crates/turbo-tasks/src/generics/option.rs b/crates/turbo-tasks/src/generics/option.rs new file mode 100644 index 0000000000000..df80d26f287d3 --- /dev/null +++ b/crates/turbo-tasks/src/generics/option.rs @@ -0,0 +1,70 @@ +use anyhow::Result; +// This specific macro identifier is detected by turbo-tasks-build. +use turbo_tasks_macros::generic_type as __turbo_tasks_internal_generic_type; + +use crate::{ + self as turbo_tasks, + debug::{ValueDebug, ValueDebugFormat, ValueDebugString}, + ValueDefault, Vc, +}; + +__turbo_tasks_internal_generic_type!(, Option>); + +#[turbo_tasks::function] +async fn option_is_none(option: Vc>>) -> Result> { + let option = option.await?; + Ok(Vc::cell(option.is_none())) +} + +#[turbo_tasks::function] +async fn option_is_some(option: Vc>>) -> Result> { + let option = option.await?; + Ok(Vc::cell(option.is_some())) +} + +impl Vc>> { + /// See [`Option::is_none`]. + pub fn is_none(self) -> Vc { + option_is_none(Self::to_repr(self)) + } + + /// See [`Option::is_some`]. + pub fn is_some(self) -> Vc { + option_is_some(Self::to_repr(self)) + } +} + +#[turbo_tasks::function] +fn option_default() -> Vc>> { + Vc::cell(Default::default()) +} + +impl ValueDefault for Option> { + fn value_default() -> Vc { + // Safety: `option_default` creates a None variant, which is a valid + // representation of any option of `Vc`. + unsafe { Vc::::from_repr(option_default()) } + } +} + +#[turbo_tasks::function] +async fn option_dbg_depth( + option: Vc>>, + depth: usize, +) -> Result> { + option + .await? + .value_debug_format(depth) + .try_to_value_debug_string() + .await +} + +impl ValueDebug for Option> { + fn dbg(self: Vc) -> Vc { + option_dbg_depth(Vc::::to_repr(self), usize::MAX) + } + + fn dbg_depth(self: Vc, depth: usize) -> Vc { + option_dbg_depth(Vc::::to_repr(self), depth) + } +} diff --git a/crates/turbo-tasks/src/generics/vec.rs b/crates/turbo-tasks/src/generics/vec.rs new file mode 100644 index 0000000000000..40dd9294feec9 --- /dev/null +++ b/crates/turbo-tasks/src/generics/vec.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +// This specific macro identifier is detected by turbo-tasks-build. +use turbo_tasks_macros::generic_type as __turbo_tasks_internal_generic_type; + +use crate::{ + self as turbo_tasks, + debug::{ValueDebug, ValueDebugFormat, ValueDebugString}, + ValueDefault, Vc, +}; + +__turbo_tasks_internal_generic_type!(, Vec>); + +#[turbo_tasks::function] +async fn vec_len(vec: Vc>>) -> Result> { + let vec = vec.await?; + Ok(Vc::cell(vec.len())) +} + +#[turbo_tasks::function] +async fn vec_is_empty(vec: Vc>>) -> Result> { + let vec = vec.await?; + Ok(Vc::cell(vec.is_empty())) +} + +impl Vc>> { + /// See [`Vec::len`]. + pub fn len(self) -> Vc { + vec_len(Self::to_repr(self)) + } + + /// See [`Vec::is_empty`]. + pub fn is_empty(self) -> Vc { + vec_is_empty(Self::to_repr(self)) + } +} + +#[turbo_tasks::function] +fn vec_default() -> Vc>> { + Vc::cell(Default::default()) +} + +impl ValueDefault for Vec> { + fn value_default() -> Vc { + // Safety: `vec_default` creates an empty vector, which is a valid + // representation of any vector of `Vc`s. + unsafe { Vc::::from_repr(vec_default()) } + } +} + +#[turbo_tasks::function] +async fn vec_dbg_depth(vec: Vc>>, depth: usize) -> Result> { + vec.await? + .value_debug_format(depth) + .try_to_value_debug_string() + .await +} + +impl ValueDebug for Vec> { + fn dbg(self: Vc) -> Vc { + vec_dbg_depth(Vc::::to_repr(self), usize::MAX) + } + + fn dbg_depth(self: Vc, depth: usize) -> Vc { + vec_dbg_depth(Vc::::to_repr(self), depth) + } +} diff --git a/crates/turbo-tasks/src/lib.rs b/crates/turbo-tasks/src/lib.rs index 072f6192e745b..b88e04202d977 100644 --- a/crates/turbo-tasks/src/lib.rs +++ b/crates/turbo-tasks/src/lib.rs @@ -43,6 +43,7 @@ pub mod debug; mod display; pub mod duration_span; pub mod event; +mod generics; pub mod graph; mod id; mod id_factory; diff --git a/crates/turbo-tasks/src/raw_vc.rs b/crates/turbo-tasks/src/raw_vc.rs index 7ae572577862c..7413406821938 100644 --- a/crates/turbo-tasks/src/raw_vc.rs +++ b/crates/turbo-tasks/src/raw_vc.rs @@ -282,7 +282,7 @@ pub struct ReadRawVcFuture> { } impl ReadRawVcFuture { - fn new(vc: RawVc) -> Self { + pub(crate) fn new(vc: RawVc) -> Self { let tt = turbo_tasks(); ReadRawVcFuture { turbo_tasks: tt, diff --git a/crates/turbo-tasks/src/vc/cast.rs b/crates/turbo-tasks/src/vc/cast.rs index 7916089501fe4..132627db8fbef 100644 --- a/crates/turbo-tasks/src/vc/cast.rs +++ b/crates/turbo-tasks/src/vc/cast.rs @@ -1,8 +1,8 @@ -use std::marker::PhantomData; +use std::{marker::PhantomData, mem::ManuallyDrop}; use anyhow::Result; -use crate::{backend::CellContent, ReadRef, TraitRef, VcValueTrait, VcValueType}; +use crate::{backend::CellContent, ReadRef, TraitRef, VcRead, VcValueTrait, VcValueType}; /// Trait defined to share behavior between values and traits within /// [`ReadRawVcFuture`]. See [`ValueCast`] and [`TraitCast`]. @@ -26,7 +26,21 @@ where type Output = ReadRef; fn cast(content: CellContent) -> Result { - content.cast::() + Ok( + // Safety: the `VcValueType` implementor must guarantee that both `T` and + // `Repr` are #[repr(transparent)]. + unsafe { + // Downcast the cell content to the expected representation type, then + // transmute it to the expected type. + // See https://users.rust-lang.org/t/transmute-doesnt-work-on-generic-types/87272/9 + std::mem::transmute_copy::< + ManuallyDrop>::Repr>>, + Self::Output, + >(&ManuallyDrop::new( + content.cast::<>::Repr>()?, + )) + }, + ) } } diff --git a/crates/turbo-tasks/src/vc/cell_mode.rs b/crates/turbo-tasks/src/vc/cell_mode.rs index 17201686b3cfb..5e71be6f03a27 100644 --- a/crates/turbo-tasks/src/vc/cell_mode.rs +++ b/crates/turbo-tasks/src/vc/cell_mode.rs @@ -24,7 +24,7 @@ where { fn cell(inner: >::Target) -> Vc { let cell = find_cell_by_type(T::get_value_type_id()); - cell.update_shared(>::target_to_value(inner)); + cell.update_shared(>::target_to_repr(inner)); Vc { node: cell.into(), _t: PhantomData, @@ -40,11 +40,12 @@ pub struct VcCellSharedMode { impl VcCellMode for VcCellSharedMode where - T: VcValueType + PartialEq, + T: VcValueType, + <::Read as VcRead>::Repr: PartialEq, { fn cell(inner: >::Target) -> Vc { let cell = find_cell_by_type(T::get_value_type_id()); - cell.compare_and_update_shared(>::target_to_value(inner)); + cell.compare_and_update_shared(>::target_to_repr(inner)); Vc { node: cell.into(), _t: PhantomData, diff --git a/crates/turbo-tasks/src/vc/mod.rs b/crates/turbo-tasks/src/vc/mod.rs index a4421cc2b40f6..5a7f9b151d2d4 100644 --- a/crates/turbo-tasks/src/vc/mod.rs +++ b/crates/turbo-tasks/src/vc/mod.rs @@ -281,10 +281,11 @@ where } } -impl Vc +impl Vc where - T: VcValueType>, + T: VcValueType>, Inner: Any + Send + Sync, + Repr: VcValueType, { pub fn cell(inner: Inner) -> Self { >::cell(inner) @@ -317,6 +318,18 @@ where vc.node } + /// Creates a `Vc` from a `RawVc`. + /// + /// # Safety + /// + /// The caller must ensure that `RawVc` points to a value of type `T`. + pub(crate) unsafe fn from_raw(vc: RawVc) -> Self { + Vc { + node: vc, + _t: std::marker::PhantomData, + } + } + /// Upcasts the given `Vc` to a `Vc>`. /// /// This is also available as an `Into`/`From` conversion. diff --git a/crates/turbo-tasks/src/vc/read.rs b/crates/turbo-tasks/src/vc/read.rs index 03b632300897e..f0b96bf34a422 100644 --- a/crates/turbo-tasks/src/vc/read.rs +++ b/crates/turbo-tasks/src/vc/read.rs @@ -5,24 +5,40 @@ use super::traits::VcValueType; /// Trait that controls [`Vc`]'s read representation. /// /// Has two implementations: -/// * [`VcDefaultRepr`] -/// * [`VcTransparentRepr`] +/// * [`VcDefaultRead`] +/// * [`VcTransparentRead`] /// /// This trait must remain sealed within this crate. pub trait VcRead where T: VcValueType, { - /// The read target type. + /// The read target type. This is the type that will be returned when + /// `.await`ing a `Vc` of a value type. + /// + /// For instance, the target of `.await`ing a `Vc` will be a + /// `Completion`. When using `#[turbo_tasks::value(transparent)]`, the + /// target will be different than the value type. type Target; + /// The representation type. This is what will be used to + /// serialize/deserialize the value, and this determines the + /// type that the value will be upcasted to for storage. + /// + /// For instance, when storing generic collection types such as + /// `Vec>`, we first cast them to a shared `Vec>` + /// type instead, which has an equivalent memory representation to any + /// `Vec>` type. This allows sharing implementations of methods and + /// traits between all `Vec>`. + type Repr: VcValueType; + /// Convert a reference to a value to a reference to the target type. fn value_to_target_ref(value: &T) -> &Self::Target; - /// Convert an target type to a value. - fn target_to_value(target: Self::Target) -> T; + /// Convert the target type to the repr. + fn target_to_repr(target: Self::Target) -> Self::Repr; - /// Convert a reference to an target type to a reference to a value. + /// Convert a reference to a target type to a reference to a value. fn target_to_value_ref(target: &Self::Target) -> &T; } @@ -37,14 +53,16 @@ where T: VcValueType, { type Target = T; + type Repr = T; fn value_to_target_ref(value: &T) -> &Self::Target { value } - fn target_to_value(target: Self::Target) -> T { + fn target_to_repr(target: Self::Target) -> T { target } + fn target_to_value_ref(target: &Self::Target) -> &T { target } @@ -52,31 +70,36 @@ where /// Representation for `#[turbo_tasks::value(transparent)]` types, where reads /// return a reference to the target type. -pub struct VcTransparentRead { - _phantom: PhantomData<(T, Target)>, +pub struct VcTransparentRead { + _phantom: PhantomData<(T, Target, Repr)>, } -impl VcRead for VcTransparentRead +impl VcRead for VcTransparentRead where T: VcValueType, Target: Any + Send + Sync, + Repr: VcValueType, { type Target = Target; + type Repr = Repr; fn value_to_target_ref(value: &T) -> &Self::Target { // Safety: the `VcValueType` implementor must guarantee that both `T` and // `Target` are #[repr(transparent)]. This is guaranteed by the // `#[turbo_tasks::value(transparent)]` macro. // We can't use `std::mem::transmute` here as it doesn't support generic types. + // See https://users.rust-lang.org/t/transmute-doesnt-work-on-generic-types/87272/9 unsafe { std::mem::transmute_copy::, &Self::Target>(&ManuallyDrop::new(value)) } } - fn target_to_value(target: Self::Target) -> T { + fn target_to_repr(target: Self::Target) -> Self::Repr { // Safety: see `Self::value_to_target` above. unsafe { - std::mem::transmute_copy::, T>(&ManuallyDrop::new(target)) + std::mem::transmute_copy::, Self::Repr>(&ManuallyDrop::new( + target, + )) } }