turbo
9ce9805e - Implement TaskInput derive macro (#4828)

Commit
2 years ago
Implement TaskInput derive macro (#4828) This PR implements a `TaskInput` derive macro that allows users to define structures and enums that can be directly passed as arguments to `tt::function`s, provided they are composed exclusively of other `TaskInput` implementors (i.e. primitives for which we've manually implemented `TaskInput`, `Vc`s, and other structs that derive `TaskInput`). There are a lot of cases in Turbopack where we'd like to pass more advanced structures to `tt::function`s, but since there's no existing way to create composite structures and enums from existing `Vc`s and primitives, we end up with the following recurring patterns: ### Functions that have 6+ arguments For example: ```rust #[turbo_tasks::function] fn do_something(a: A, b: B, c: C, d: D, e: E, f: F) { // ... } ``` By creating composite structures with arguments that are often used together and passed down the call tree, this can instead become: ```rust #[derive(TaskInput, Clone)] struct Args1 { a: A, b: B, } #[derive(TaskInput, Clone)] struct Args2 { c: C, d: D, e: E, } #[turbo_tasks::function] fn do_something(args1: Args1, args2: Args2, f: F) { // ... } ``` ### Multiple optional arguments ... where only specific combinations make sense. ```rust #[turbo_tasks::function] fn do_something(value1: SomeVc, value2: Option<OtherVc>, value3: Option<AnotherVc>) { if let (Some(value2), Some(value3)) = (value2, value3) { // ... } // ... } ``` In this example, `value2` might only make sense when `value3` is also provided. An enum would be better in this case: ```rust #[derive(Clone, TaskInput) enum CustomEnum { NoValues, Values { value2: OtherVc, value3: AnotherVc, } } #[turbo_tasks::function] fn do_something(value1: SomeVc, values: CustomEnum) { if let CustomEnum::Values { value2, value3 } = values { // ... } // ... } ``` ### `Value<T>` and `serialization = "auto_for_input"` For example: ```rust #[turbo_tasks::value(serialization = "auto_for_input")] #[derive(PartialOrd, Ord, Debug, Hash, Clone, Copy)] pub enum SomeArgument { A, B, ... } // and then later some_function(Value::new(SomeArgument::B)) ``` This can now be replaced with: ```rust #[derive(TaskInput, Clone)] pub enum SomeArgument { A, B, ... } // and then later some_function(SomeArgument::B) ``` Apart from conciseness and fewer trait bounds, another benefit of this approach is that `SomeArgument` cannot be (mis)used as a `Vc`, which we always generate via the `turbo_tasks::value` proc macro. ### How does it work? The derive macro generates code to convert enums and structs into a list of `TaskInput`s, similarly to how `FromTaskInput` was previously implemented for tuples. We use an additional integer discriminant to differentiate between enum variants. ### Concerns A potential concern of this approach is that these `TaskInput`s will be cloned on every single task execution. As such, they should be as lightweight as possible, and implement `Copy` where it makes sense. ### Testing Instructions I added a series of tests in turbo-tasks-memory. link WEB-945
Author
Parents
Loading