Skip to content

Commit

Permalink
Add a resolved argument to #[turbo_tasks::function] (#68422)
Browse files Browse the repository at this point in the history
### 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
(vercel/turborepo#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<Return = Vc<Rv>>,
    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.
  • Loading branch information
bgw authored and ForsakenHarmony committed Aug 16, 2024
1 parent 646a110 commit 4ee4b77
Show file tree
Hide file tree
Showing 20 changed files with 463 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![feature(arbitrary_self_types)]

use turbo_tasks::{ResolvedVc, Vc};

#[turbo_tasks::value(transparent, resolved)]
struct IntegersVec(Vec<ResolvedVc<u32>>);

#[turbo_tasks::function(invalid_argument)]
fn return_contains_resolved_vc() -> Vc<IntegersVec> {
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();
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<ResolvedVc<u32>>);

#[turbo_tasks::value_impl]
impl ExampleStruct {
#[turbo_tasks::function(invalid_argument)]
fn return_contains_resolved_vc(self: Vc<Self>) -> Vc<IntegersVec> {
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();
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<Vc<u32>>);

#[turbo_tasks::value_impl]
impl ExampleStruct {
#[turbo_tasks::function(resolved)]
fn return_contains_unresolved_vc(self: Vc<Self>) -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}
}

fn main() {}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![feature(arbitrary_self_types)]
#![allow(dead_code)]

use turbo_tasks::Vc;

#[turbo_tasks::value(transparent)]
struct IntegersVec(Vec<Vc<u32>>);

#[turbo_tasks::function(resolved)]
fn return_contains_unresolved_vc() -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}

fn main() {}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<Vc<u32>>);

#[turbo_tasks::value_trait]
trait ExampleTrait {
fn return_contains_unresolved_vc(self: Vc<Self>) -> Vc<IntegersVec>;
}

#[turbo_tasks::value_impl]
impl ExampleTrait for ExampleStruct {
#[turbo_tasks::function(resolved)]
fn return_contains_unresolved_vc(self: Vc<Self>) -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}
}

fn main() {}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<ResolvedVc<u32>>);

#[turbo_tasks::value_impl]
impl ExampleStruct {
#[turbo_tasks::function(resolved)]
fn return_contains_resolved_vc(self: Vc<Self>) -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![feature(arbitrary_self_types)]
#![allow(dead_code)]

use turbo_tasks::{ResolvedVc, Vc};

#[turbo_tasks::value(transparent, resolved)]
struct IntegersVec(Vec<ResolvedVc<u32>>);

#[turbo_tasks::function(resolved)]
fn return_contains_resolved_vc() -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}

#[turbo_tasks::function(resolved)]
fn return_contains_resolved_vc_result() -> anyhow::Result<Vc<IntegersVec>> {
Ok(Vc::cell(Vec::new()))
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -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<ResolvedVc<u32>>);

#[turbo_tasks::value_trait]
trait ExampleTrait {
fn return_contains_resolved_vc(self: Vc<Self>) -> Vc<IntegersVec>;
}

#[turbo_tasks::value_impl]
impl ExampleTrait for ExampleStruct {
#[turbo_tasks::function(resolved)]
fn return_contains_resolved_vc(self: Vc<Self>) -> Vc<IntegersVec> {
Vc::cell(Vec::new())
}
}

fn main() {}
7 changes: 7 additions & 0 deletions turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct MyValue {
value: i32,
}

fn expects_resolved<T: turbo_tasks::ResolvedValue>(value: T) {}
fn expects_resolved<T: turbo_tasks::ResolvedValue>(_value: T) {}

fn main() {
let v = MyValue { value: 0 };
Expand Down
Loading

0 comments on commit 4ee4b77

Please sign in to comment.