From 4ee4b77cf77273a5b887f0e8d4f973b92d7dc2cc Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Tue, 6 Aug 2024 18:36:22 -0400 Subject: [PATCH] Add a resolved argument to `#[turbo_tasks::function]` (#68422) ### What & Why? We want to enforce that values that functions return contain only `ResolvedVc` and not `Vc` (though the outermost `Vc` is okay). We can do that using the `ResolvedValue` marker trait (https://github.com/vercel/turbo/pull/8678). This PR allows enforcing that by passing a `resolved` argument to the `#[turbo_tasks::function(...)]` macro. This enforcement behavior is currently opt-in, but the goal is eventually to make it opt-out, so that most functions can easily be converted to use local tasks. Bigger picture: https://www.notion.so/vercel/Resolved-Vcs-Vc-Lifetimes-Local-Vcs-and-Vc-Refcounts-49d666d3f9594017b5b312b87ddc5bff ### How? The key part of the macro is this bit: ``` fn assert_returns_resolved_value< ReturnType, Rv, >() where ReturnType: turbo_tasks::task::TaskOutput>, Rv: turbo_tasks::ResolvedValue + Send, {} assert_returns_resolved_value::<#return_type, _>() ``` That creates no-op code that successfully compiles when the return value (inside of the outermost `Vc`) is a `ResolvedValue`, but fails when it isn't. This is the same trick that the [`static_assertions` library](https://docs.rs/static_assertions/latest/static_assertions/macro.assert_type_eq_all.html) uses. ### Test Plan Lots of [trybuild](https://github.com/dtolnay/trybuild) tests! ``` cargo nextest r -p turbo-tasks-macros-tests ``` **Hint:** Use `TRYBUILD=overwrite` when intentionally changing the tests. --- .../function/fail_attribute_invalid_args.rs | 17 +++ .../fail_attribute_invalid_args.stderr | 5 + ...il_attribute_invalid_args_inherent_impl.rs | 23 +++ ...ttribute_invalid_args_inherent_impl.stderr | 5 + .../function/fail_resolved_inherent_impl.rs | 20 +++ .../fail_resolved_inherent_impl.stderr | 21 +++ .../tests/function/fail_resolved_static.rs | 14 ++ .../function/fail_resolved_static.stderr | 21 +++ .../function/fail_resolved_trait_impl.rs | 25 ++++ .../function/fail_resolved_trait_impl.stderr | 21 +++ .../function/pass_resolved_inherent_impl.rs | 20 +++ .../tests/function/pass_resolved_static.rs | 19 +++ .../function/pass_resolved_trait_impl.rs | 25 ++++ .../tests/trybuild.rs | 7 + .../tests/value/pass_resolved.rs | 2 +- .../crates/turbo-tasks-macros/src/func.rs | 141 +++++++++++++++++- .../turbo-tasks-macros/src/function_macro.rs | 14 +- .../src/value_impl_macro.rs | 61 ++++++-- .../src/value_trait_macro.rs | 11 +- .../crates/turbo-tasks/src/vc/resolved.rs | 18 ++- 20 files changed, 463 insertions(+), 27 deletions(-) create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.stderr create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.stderr create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.stderr create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.stderr create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.stderr create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_inherent_impl.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_static.rs create mode 100644 turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_trait_impl.rs diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.rs new file mode 100644 index 00000000000000..3cb20817b548e8 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.rs @@ -0,0 +1,17 @@ +#![feature(arbitrary_self_types)] + +use turbo_tasks::{ResolvedVc, Vc}; + +#[turbo_tasks::value(transparent, resolved)] +struct IntegersVec(Vec>); + +#[turbo_tasks::function(invalid_argument)] +fn return_contains_resolved_vc() -> Vc { + Vc::cell(Vec::new()) +} + +fn main() { + // the macro should be error-tolerent and this function should still be created + // despite the earlier compilation error, so this line should not also error + let _ = return_contains_resolved_vc(); +} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.stderr b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.stderr new file mode 100644 index 00000000000000..53a00bd8cb53e8 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.stderr @@ -0,0 +1,5 @@ +error: unexpected token, expected one of: "fs", "network", "resolved" + --> tests/function/fail_attribute_invalid_args.rs:8:25 + | +8 | #[turbo_tasks::function(invalid_argument)] + | ^^^^^^^^^^^^^^^^ diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.rs new file mode 100644 index 00000000000000..02522f2eeaf438 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.rs @@ -0,0 +1,23 @@ +#![feature(arbitrary_self_types)] + +use turbo_tasks::{ResolvedVc, Vc}; + +#[turbo_tasks::value] +struct ExampleStruct; + +#[turbo_tasks::value(transparent, resolved)] +struct IntegersVec(Vec>); + +#[turbo_tasks::value_impl] +impl ExampleStruct { + #[turbo_tasks::function(invalid_argument)] + fn return_contains_resolved_vc(self: Vc) -> Vc { + Vc::cell(Vec::new()) + } +} + +fn main() { + // the macro should be error-tolerent and this function should still be created + // despite the earlier compilation error, so this line should not also error + let _ = ExampleStruct.cell().return_contains_resolved_vc(); +} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.stderr b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.stderr new file mode 100644 index 00000000000000..b2f62044abe76d --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.stderr @@ -0,0 +1,5 @@ +error: unexpected token, expected one of: "fs", "network", "resolved" + --> tests/function/fail_attribute_invalid_args_inherent_impl.rs:13:29 + | +13 | #[turbo_tasks::function(invalid_argument)] + | ^^^^^^^^^^^^^^^^ diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.rs new file mode 100644 index 00000000000000..97d36beffbd2d4 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.rs @@ -0,0 +1,20 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::Vc; + +#[turbo_tasks::value] +struct ExampleStruct; + +#[turbo_tasks::value(transparent)] +struct IntegersVec(Vec>); + +#[turbo_tasks::value_impl] +impl ExampleStruct { + #[turbo_tasks::function(resolved)] + fn return_contains_unresolved_vc(self: Vc) -> Vc { + Vc::cell(Vec::new()) + } +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.stderr b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.stderr new file mode 100644 index 00000000000000..bf19f47a4dabee --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_inherent_impl.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `IntegersVec: ResolvedValue` is not satisfied + --> tests/function/fail_resolved_inherent_impl.rs:14:29 + | +14 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ the trait `ResolvedValue` is not implemented for `IntegersVec` + | + = help: the following other types implement trait `ResolvedValue`: + &T + &mut T + () + (A, Z, Y, X, W, V, U, T) + (B, A, Z, Y, X, W, V, U, T) + (C, B, A, Z, Y, X, W, V, U, T) + (D, C, B, A, Z, Y, X, W, V, U, T) + (E, D, C, B, A, Z, Y, X, W, V, U, T) + and $N others +note: required by a bound in `assert_returns_resolved_value` + --> tests/function/fail_resolved_inherent_impl.rs:14:29 + | +14 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ required by this bound in `assert_returns_resolved_value` diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.rs new file mode 100644 index 00000000000000..79a20dcaf7ea03 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.rs @@ -0,0 +1,14 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::Vc; + +#[turbo_tasks::value(transparent)] +struct IntegersVec(Vec>); + +#[turbo_tasks::function(resolved)] +fn return_contains_unresolved_vc() -> Vc { + Vc::cell(Vec::new()) +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.stderr b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.stderr new file mode 100644 index 00000000000000..70fd159e750771 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_static.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `IntegersVec: ResolvedValue` is not satisfied + --> tests/function/fail_resolved_static.rs:9:25 + | +9 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ the trait `ResolvedValue` is not implemented for `IntegersVec` + | + = help: the following other types implement trait `ResolvedValue`: + &T + &mut T + () + (A, Z, Y, X, W, V, U, T) + (B, A, Z, Y, X, W, V, U, T) + (C, B, A, Z, Y, X, W, V, U, T) + (D, C, B, A, Z, Y, X, W, V, U, T) + (E, D, C, B, A, Z, Y, X, W, V, U, T) + and $N others +note: required by a bound in `assert_returns_resolved_value` + --> tests/function/fail_resolved_static.rs:9:25 + | +9 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ required by this bound in `assert_returns_resolved_value` diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.rs new file mode 100644 index 00000000000000..918bf97845a924 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.rs @@ -0,0 +1,25 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::Vc; + +#[turbo_tasks::value] +struct ExampleStruct; + +#[turbo_tasks::value(transparent)] +struct IntegersVec(Vec>); + +#[turbo_tasks::value_trait] +trait ExampleTrait { + fn return_contains_unresolved_vc(self: Vc) -> Vc; +} + +#[turbo_tasks::value_impl] +impl ExampleTrait for ExampleStruct { + #[turbo_tasks::function(resolved)] + fn return_contains_unresolved_vc(self: Vc) -> Vc { + Vc::cell(Vec::new()) + } +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.stderr b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.stderr new file mode 100644 index 00000000000000..78804532a0db90 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_resolved_trait_impl.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `IntegersVec: ResolvedValue` is not satisfied + --> tests/function/fail_resolved_trait_impl.rs:19:29 + | +19 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ the trait `ResolvedValue` is not implemented for `IntegersVec` + | + = help: the following other types implement trait `ResolvedValue`: + &T + &mut T + () + (A, Z, Y, X, W, V, U, T) + (B, A, Z, Y, X, W, V, U, T) + (C, B, A, Z, Y, X, W, V, U, T) + (D, C, B, A, Z, Y, X, W, V, U, T) + (E, D, C, B, A, Z, Y, X, W, V, U, T) + and $N others +note: required by a bound in `assert_returns_resolved_value` + --> tests/function/fail_resolved_trait_impl.rs:19:29 + | +19 | #[turbo_tasks::function(resolved)] + | ^^^^^^^^ required by this bound in `assert_returns_resolved_value` diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_inherent_impl.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_inherent_impl.rs new file mode 100644 index 00000000000000..bcd96cc1a80789 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_inherent_impl.rs @@ -0,0 +1,20 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::{ResolvedVc, Vc}; + +#[turbo_tasks::value] +struct ExampleStruct; + +#[turbo_tasks::value(transparent, resolved)] +struct IntegersVec(Vec>); + +#[turbo_tasks::value_impl] +impl ExampleStruct { + #[turbo_tasks::function(resolved)] + fn return_contains_resolved_vc(self: Vc) -> Vc { + Vc::cell(Vec::new()) + } +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_static.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_static.rs new file mode 100644 index 00000000000000..26e98b52c86d58 --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_static.rs @@ -0,0 +1,19 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::{ResolvedVc, Vc}; + +#[turbo_tasks::value(transparent, resolved)] +struct IntegersVec(Vec>); + +#[turbo_tasks::function(resolved)] +fn return_contains_resolved_vc() -> Vc { + Vc::cell(Vec::new()) +} + +#[turbo_tasks::function(resolved)] +fn return_contains_resolved_vc_result() -> anyhow::Result> { + Ok(Vc::cell(Vec::new())) +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_trait_impl.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_trait_impl.rs new file mode 100644 index 00000000000000..c1f37f2bd9213f --- /dev/null +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/function/pass_resolved_trait_impl.rs @@ -0,0 +1,25 @@ +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +use turbo_tasks::{ResolvedVc, Vc}; + +#[turbo_tasks::value] +struct ExampleStruct; + +#[turbo_tasks::value(transparent, resolved)] +struct IntegersVec(Vec>); + +#[turbo_tasks::value_trait] +trait ExampleTrait { + fn return_contains_resolved_vc(self: Vc) -> Vc; +} + +#[turbo_tasks::value_impl] +impl ExampleTrait for ExampleStruct { + #[turbo_tasks::function(resolved)] + fn return_contains_resolved_vc(self: Vc) -> Vc { + Vc::cell(Vec::new()) + } +} + +fn main() {} diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs index 8620bbd901638d..de2fdb1b5416e9 100644 --- a/turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs @@ -5,6 +5,13 @@ fn derive_resolved_value() { t.compile_fail("tests/derive_resolved_value/fail_*.rs"); } +#[test] +fn function() { + let t = trybuild::TestCases::new(); + t.pass("tests/function/pass_*.rs"); + t.compile_fail("tests/function/fail_*.rs"); +} + #[test] fn value() { let t = trybuild::TestCases::new(); diff --git a/turbopack/crates/turbo-tasks-macros-tests/tests/value/pass_resolved.rs b/turbopack/crates/turbo-tasks-macros-tests/tests/value/pass_resolved.rs index 5fd5ea50638e88..a4ee6fc7239439 100644 --- a/turbopack/crates/turbo-tasks-macros-tests/tests/value/pass_resolved.rs +++ b/turbopack/crates/turbo-tasks-macros-tests/tests/value/pass_resolved.rs @@ -5,7 +5,7 @@ struct MyValue { value: i32, } -fn expects_resolved(value: T) {} +fn expects_resolved(_value: T) {} fn main() { let v = MyValue { value: 0 }; diff --git a/turbopack/crates/turbo-tasks-macros/src/func.rs b/turbopack/crates/turbo-tasks-macros/src/func.rs index d6840ded4fe59a..b4284fe1577560 100644 --- a/turbopack/crates/turbo-tasks-macros/src/func.rs +++ b/turbopack/crates/turbo-tasks-macros/src/func.rs @@ -1,11 +1,17 @@ -use proc_macro2::Ident; +use std::collections::HashSet; + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned}; use syn::{ + parenthesized, + parse::{Parse, ParseStream}, parse_quote, punctuated::{Pair, Punctuated}, spanned::Spanned, - AngleBracketedGenericArguments, Block, Expr, ExprPath, FnArg, GenericArgument, Pat, PatIdent, - PatType, Path, PathArguments, PathSegment, Receiver, ReturnType, Signature, Token, Type, - TypeGroup, TypePath, TypeTuple, + token::Paren, + AngleBracketedGenericArguments, Block, Expr, ExprPath, FnArg, GenericArgument, Meta, Pat, + PatIdent, PatType, Path, PathArguments, PathSegment, Receiver, ReturnType, Signature, Token, + Type, TypeGroup, TypePath, TypeTuple, }; #[derive(Debug)] @@ -16,6 +22,8 @@ pub struct TurboFn { output: Type, this: Option, inputs: Vec, + /// Should we check that the return type contains a `ResolvedValue`? + resolved: Option, } #[derive(Debug)] @@ -28,6 +36,7 @@ impl TurboFn { pub fn new( original_signature: &Signature, definition_context: DefinitionContext, + args: FunctionArguments, ) -> Option { if !original_signature.generics.params.is_empty() { original_signature @@ -247,6 +256,7 @@ impl TurboFn { output, this, inputs, + resolved: args.resolved, }) } @@ -310,15 +320,38 @@ impl TurboFn { }) } + fn get_assertions(&self) -> TokenStream { + if let Some(span) = self.resolved { + let return_type = &self.output; + quote_spanned! { + span => + { + fn assert_returns_resolved_value< + ReturnType, + Rv, + >() where + ReturnType: turbo_tasks::task::TaskOutput>, + Rv: turbo_tasks::ResolvedValue + Send, + {} + assert_returns_resolved_value::<#return_type, _>() + } + } + } else { + quote! {} + } + } + /// The block of the exposed function for a dynamic dispatch call to the /// given trait. pub fn dynamic_block(&self, trait_type_id_ident: &Ident) -> Block { let ident = &self.ident; let output = &self.output; + let assertions = self.get_assertions(); if let Some(converted_this) = self.converted_this() { let inputs = self.inputs(); parse_quote! { { + #assertions <#output as turbo_tasks::task::TaskOutput>::try_from_raw_vc( turbo_tasks::trait_call( *#trait_type_id_ident, @@ -343,9 +376,11 @@ impl TurboFn { pub fn static_block(&self, native_function_id_ident: &Ident) -> Block { let output = &self.output; let inputs = self.inputs(); + let assertions = self.get_assertions(); if let Some(converted_this) = self.converted_this() { parse_quote! { { + #assertions <#output as turbo_tasks::task::TaskOutput>::try_from_raw_vc( turbo_tasks::dynamic_this_call( *#native_function_id_ident, @@ -358,6 +393,7 @@ impl TurboFn { } else { parse_quote! { { + #assertions <#output as turbo_tasks::task::TaskOutput>::try_from_raw_vc( turbo_tasks::dynamic_call( *#native_function_id_ident, @@ -374,6 +410,103 @@ impl TurboFn { } } +/// An indication of what kind of IO this function does. Currently only used for +/// static analysis, and ignored within this macro. +#[derive(Hash, PartialEq, Eq)] +enum IoMarker { + Filesystem, + Network, +} + +/// Unwraps a parenthesized set of tokens. +/// +/// Syn's lower-level [`parenthesized`] macro which this uses requires a +/// [`ParseStream`] and cannot be used with [`parse_macro_input`], +/// [`syn::parse2`] or anything else accepting a [`TokenStream`]. This can be +/// used with those [`TokenStream`]-based parsing APIs. +pub struct Parenthesized { + pub _paren_token: Paren, + pub inner: T, +} + +impl Parse for Parenthesized { + fn parse(input: ParseStream) -> syn::Result { + let inner; + Ok(Self { + _paren_token: parenthesized!(inner in input), + inner: ::parse(&inner)?, + }) + } +} + +/// A newtype wrapper for [`Option`][Parenthesized] that +/// implements [`Parse`]. +pub struct MaybeParenthesized { + pub parenthesized: Option>, +} + +impl Parse for MaybeParenthesized { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + parenthesized: if input.peek(Paren) { + Some(Parenthesized::::parse(input)?) + } else { + None + }, + }) + } +} + +/// Arguments to the `#[turbo_tasks::function]` macro. +#[derive(Default)] +pub struct FunctionArguments { + /// Manually annotated metadata about what kind of IO this function does. + /// Currently only used by some static analysis tools. May be exposed via + /// `tracing` or used as part of an optimization heuristic in the + /// future. + /// + /// This should only be used by the task that directly performs the IO. + /// Tasks that transitively perform IO should not be manually annotated. + io_markers: HashSet, + /// Should we check that the return type contains a `ResolvedValue`? This is + /// a span for error reporting reasons. + resolved: Option, +} + +impl Parse for FunctionArguments { + fn parse(input: ParseStream) -> syn::Result { + let mut parsed_args = FunctionArguments::default(); + let punctuated: Punctuated = input.parse_terminated(Meta::parse)?; + for meta in punctuated { + match ( + meta.path() + .get_ident() + .map(ToString::to_string) + .as_deref() + .unwrap_or_default(), + &meta, + ) { + ("fs", Meta::Path(_)) => { + parsed_args.io_markers.insert(IoMarker::Filesystem); + } + ("network", Meta::Path(_)) => { + parsed_args.io_markers.insert(IoMarker::Network); + } + ("resolved", Meta::Path(_)) => { + parsed_args.resolved = Some(meta.span()); + } + (_, meta) => { + return Err(syn::Error::new_spanned( + meta, + "unexpected token, expected one of: \"fs\", \"network\", \"resolved\"", + )) + } + } + } + Ok(parsed_args) + } +} + fn return_type_to_type(return_type: &ReturnType) -> Type { match return_type { ReturnType::Default => parse_quote! { () }, diff --git a/turbopack/crates/turbo-tasks-macros/src/function_macro.rs b/turbopack/crates/turbo-tasks-macros/src/function_macro.rs index 519abdb8f4ebe1..951925a993355b 100644 --- a/turbopack/crates/turbo-tasks-macros/src/function_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/function_macro.rs @@ -4,7 +4,7 @@ use quote::quote; use syn::{parse_macro_input, parse_quote, ExprPath, ItemFn}; use turbo_tasks_macros_shared::{get_native_function_id_ident, get_native_function_ident}; -use crate::func::{DefinitionContext, NativeFn, TurboFn}; +use crate::func::{DefinitionContext, FunctionArguments, NativeFn, TurboFn}; /// This macro generates the virtual function that powers turbo tasks. /// An annotated task is replaced with a stub function that returns a @@ -25,8 +25,9 @@ use crate::func::{DefinitionContext, NativeFn, TurboFn}; /// // access filesystem /// } /// ``` -pub fn function(_args: TokenStream, input: TokenStream) -> TokenStream { +pub fn function(args: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as ItemFn); + let mut errors = Vec::new(); let ItemFn { attrs, @@ -35,7 +36,12 @@ pub fn function(_args: TokenStream, input: TokenStream) -> TokenStream { block, } = &item; - let Some(turbo_fn) = TurboFn::new(sig, DefinitionContext::NakedFn) else { + let args = syn::parse::(args); + if let Err(err) = &args { + errors.push(err.to_compile_error()); + } + let Some(turbo_fn) = TurboFn::new(sig, DefinitionContext::NakedFn, args.unwrap_or_default()) + else { return quote! { // An error occurred while parsing the function signature. } @@ -78,6 +84,8 @@ pub fn function(_args: TokenStream, input: TokenStream) -> TokenStream { #[doc(hidden)] pub(crate) static #native_function_id_ident: #native_function_id_ty = #native_function_id_def; + + #(#errors)* } .into() } diff --git a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs index 93eff9cdece0f5..5821906da9135e 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs @@ -15,7 +15,7 @@ use turbo_tasks_macros_shared::{ get_trait_impl_function_ident, get_type_ident, }; -use crate::func::{DefinitionContext, NativeFn, TurboFn}; +use crate::func::{DefinitionContext, FunctionArguments, MaybeParenthesized, NativeFn, TurboFn}; fn is_attribute(attr: &Attribute, name: &str) -> bool { let path = &attr.path; @@ -32,18 +32,31 @@ fn is_attribute(attr: &Attribute, name: &str) -> bool { } } -fn strip_function_attribute<'a>(item: &'a ImplItem, attrs: &'a [Attribute]) -> Vec<&'a Attribute> { - let (function_attrs, attrs): (Vec<_>, Vec<_>) = attrs +fn split_function_attributes<'a>( + item: &'a ImplItem, + attrs: &'a [Attribute], +) -> (syn::Result, Vec<&'a Attribute>) { + let (func_attrs_vec, attrs): (Vec<_>, Vec<_>) = attrs .iter() // TODO(alexkirsz) Replace this with function .partition(|attr| is_attribute(attr, "function")); - if function_attrs.is_empty() { - item.span() - .unwrap() - .error("#[turbo_tasks::function] attribute missing") - .emit(); - } - attrs + let func_args = if let Some(func_attr) = func_attrs_vec.first() { + if func_attrs_vec.len() == 1 { + syn::parse2::>(func_attr.tokens.clone()) + .map(|a| a.parenthesized.map(|a| a.inner).unwrap_or_default()) + } else { + Err(syn::Error::new( + func_attr.span(), + "Only one #[turbo_tasks::function] attribute is allowed per method", + )) + } + } else { + Err(syn::Error::new( + item.span(), + "#[turbo_tasks::function] attribute missing", + )) + }; + (func_args, attrs) } struct ValueImplArguments { @@ -90,6 +103,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { fn inherent_value_impl(ty: &Type, ty_ident: &Ident, items: &[ImplItem]) -> TokenStream2 { let mut all_definitions = Vec::new(); let mut exposed_impl_items = Vec::new(); + let mut errors = Vec::new(); for item in items.iter() { if let ImplItem::Method(ImplItemMethod { @@ -100,9 +114,11 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { block, }) = item { - let attrs = strip_function_attribute(item, attrs); - let ident = &sig.ident; + let (func_args, attrs) = split_function_attributes(item, attrs); + if let Err(err) = &func_args { + errors.push(err.to_compile_error()); + } // TODO(alexkirsz) These should go into their own utilities. let inline_function_ident: Ident = @@ -111,7 +127,11 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { let mut inline_signature = sig.clone(); inline_signature.ident = inline_function_ident; - let Some(turbo_fn) = TurboFn::new(sig, DefinitionContext::ValueInherentImpl) else { + let Some(turbo_fn) = TurboFn::new( + sig, + DefinitionContext::ValueInherentImpl, + func_args.unwrap_or_default(), + ) else { return quote! { // An error occurred while parsing the function signature. }; @@ -172,6 +192,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { } #(#all_definitions)* + #(#errors)* } } @@ -191,6 +212,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { let mut trait_registers = Vec::new(); let mut trait_functions = Vec::with_capacity(items.len()); let mut all_definitions = Vec::with_capacity(items.len()); + let mut errors = Vec::new(); for item in items.iter() { if let ImplItem::Method(ImplItemMethod { @@ -199,14 +221,20 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { { let ident = &sig.ident; - let Some(turbo_fn) = TurboFn::new(sig, DefinitionContext::ValueTraitImpl) else { + let (func_args, attrs) = split_function_attributes(item, attrs); + if let Err(err) = &func_args { + errors.push(err.to_compile_error()); + } + let Some(turbo_fn) = TurboFn::new( + sig, + DefinitionContext::ValueTraitImpl, + func_args.unwrap_or_default(), + ) else { return quote! { // An error occurred while parsing the function signature. }; }; - let attrs = strip_function_attribute(item, attrs); - // TODO(alexkirsz) These should go into their own utilities. let inline_function_ident: Ident = Ident::new(&format!("{}_inline", ident), ident.span()); @@ -310,6 +338,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream { } #(#all_definitions)* + #(#errors)* } } diff --git a/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs index 5eabf273b78512..03dc1405b61246 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs @@ -10,7 +10,7 @@ use turbo_tasks_macros_shared::{ get_trait_type_id_ident, get_trait_type_ident, ValueTraitArguments, }; -use crate::func::{DefinitionContext, NativeFn, TurboFn}; +use crate::func::{DefinitionContext, FunctionArguments, NativeFn, TurboFn}; pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { let ValueTraitArguments { debug, resolved } = parse_macro_input!(args as ValueTraitArguments); @@ -85,7 +85,14 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream { let ident = &sig.ident; - let Some(turbo_fn) = TurboFn::new(sig, DefinitionContext::ValueTrait) else { + // Value trait method declarations don't have `#[turbo_tasks::function]` + // annotations on them, though their `impl`s do. It may make sense to require it + // in the future when defining a default implementation. + let Some(turbo_fn) = TurboFn::new( + sig, + DefinitionContext::ValueTrait, + FunctionArguments::default(), + ) else { return quote! { // An error occurred while parsing the function signature. } diff --git a/turbopack/crates/turbo-tasks/src/vc/resolved.rs b/turbopack/crates/turbo-tasks/src/vc/resolved.rs index 8bdc40f648e702..5c808290448195 100644 --- a/turbopack/crates/turbo-tasks/src/vc/resolved.rs +++ b/turbopack/crates/turbo-tasks/src/vc/resolved.rs @@ -17,9 +17,16 @@ use std::{ use auto_hash_map::{AutoMap, AutoSet}; use indexmap::{IndexMap, IndexSet}; +use serde::{Deserialize, Serialize}; -use crate::{vc::Vc, RcStr}; +use crate::{ + trace::{TraceRawVcs, TraceRawVcsContext}, + vc::Vc, + RcStr, +}; +#[derive(Serialize, Deserialize)] +#[serde(transparent)] pub struct ResolvedVc where T: ?Sized + Send, @@ -80,6 +87,15 @@ where } } +impl TraceRawVcs for ResolvedVc +where + T: ?Sized + Send, +{ + fn trace_raw_vcs(&self, trace_context: &mut TraceRawVcsContext) { + TraceRawVcs::trace_raw_vcs(&self.node, trace_context); + } +} + /// Indicates that a type does not contain any instances of [`Vc`]. It may /// contain [`ResolvedVc`]. ///