Skip to content

Commit

Permalink
Add derive macro for ResolvedValue (vercel/turborepo#8678)
Browse files Browse the repository at this point in the history
## Description

This macro allows the user to safely derive `ResolvedValue` (vercel/turborepo#8662). Most of the time, this won't be called directly, but instead as part of `#[turbo_tasks::value]` (not implemented yet).

The macro works by creating a compile-time assertion for each field of a struct, enum, or union, verifying that the type of the field implements `ResolvedValue`.

The assertions are designed to give relatively readable error messages, and [`trybuild`](https://github.com/dtolnay/trybuild) is used to test that.

## Example

An example of this macro expansion looks like this:

```
// input code
#[derive(ResolvedValue)]
struct LinkedListNode<T>
where
    T: ResolvedValue,
{
    current: T,
    next: Option<Box<LinkedListNode<T>>>,
}
```

```
// generated derived impl
unsafe impl<T> ::turbo_tasks::ResolvedValue for LinkedListNode<T> where T: ResolvedValue {}
const _: fn() = || {
    struct DeriveResolvedValueAssertion<T>(T, Option<Box<LinkedListNode<T>>>)
    where
        T: ResolvedValue;
    impl<T> DeriveResolvedValueAssertion<T>
    where
        T: ResolvedValue,
    {
        fn assert_impl_resolved_value<ExpectedResolvedValue: ResolvedValue + ?Sized>() {}
        fn field_types() {
            Self::assert_impl_resolved_value::<T>();
            Self::assert_impl_resolved_value::<Option<Box<LinkedListNode<T>>>>();
        }
    }
};
```

## Why?

https://www.notion.so/vercel/Resolved-Vcs-Vc-Lifetimes-Local-Vcs-and-Vc-Refcounts-49d666d3f9594017b5b312b87ddc5bff

## Testing Instructions

```
cargo nextest r -p turbo-tasks-macros-tests
```

I ran this multiple times to weed out any non-determinism of the tests (I had some error order non-determinism issues).

If something changes and the stderr snapshots need to be updated, you can pass in `TRYBUILD=overwrite`:

```
TRYBUILD=overwrite cargo nextest r -p turbo-tasks-macros-tests
```

I used [rust-analyzer's "expand macro" feature](https://rust-analyzer.github.io/manual.html#expand-macro-recursively) to debug some issues, and to check that the generated code is as compact as reasonably possible (we want to avoid negative impacts on compile times).
  • Loading branch information
bgw authored Jul 9, 2024
1 parent a6029e9 commit 6c3e499
Show file tree
Hide file tree
Showing 27 changed files with 454 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/turbo-tasks-macros-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ turbo-tasks-memory = { workspace = true }
turbo-tasks-testing = { workspace = true }
# TODO: turbo-tasks-testing uses a macro that requires us to depend on lazy-static.
lazy_static = { workspace = true }
trybuild = { version = "1.0.97" }

[build-dependencies]
turbo-tasks-build = { workspace = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![allow(dead_code)]

use turbo_tasks::{ResolvedValue, Vc};

#[derive(ResolvedValue)]
struct ContainsOnlyVc {
a: Vc<i32>,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0277]: the trait bound `Vc<i32>: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_contains_only_vc.rs:7:8
|
7 | a: Vc<i32>,
| ^^^^^^^ the trait `ResolvedValue` is not implemented for `Vc<i32>`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_contains_only_vc.rs:5:10
|
5 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![allow(dead_code)]

use turbo_tasks::{ResolvedValue, ResolvedVc, Vc};

#[derive(ResolvedValue)]
struct ContainsResolvedVcAndVc {
a: ResolvedVc<i32>,
b: Vc<i32>,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0277]: the trait bound `Vc<i32>: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_contains_resolved_vc_and_vc.rs:8:8
|
8 | b: Vc<i32>,
| ^^^^^^^ the trait `ResolvedValue` is not implemented for `Vc<i32>`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_contains_resolved_vc_and_vc.rs:5:10
|
5 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![allow(dead_code)]

use turbo_tasks::{ResolvedValue, Vc};

#[derive(ResolvedValue)]
struct ContainsVcInsideGeneric {
a: Option<Box<[Vc<i32>; 4]>>,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0277]: the trait bound `Vc<i32>: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_contains_vc_inside_generic.rs:7:8
|
7 | a: Option<Box<[Vc<i32>; 4]>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ResolvedValue` is not implemented for `Vc<i32>`, which is required by `Option<Box<[Vc<i32>; 4]>>: ResolvedValue`
|
= 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 for `[Vc<i32>; 4]` to implement `ResolvedValue`
= note: 2 redundant requirements hidden
= note: required for `Option<Box<[Vc<i32>; 4]>>` to implement `ResolvedValue`
note: required by a bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_contains_vc_inside_generic.rs:5:10
|
5 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![allow(dead_code)]

use turbo_tasks::ResolvedValue;

struct UnresolvedValue;

#[derive(ResolvedValue)]
enum ContainsUnresolvedValue {
Unit,
Unnamed(UnresolvedValue),
Named { a: UnresolvedValue },
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
error[E0277]: the trait bound `UnresolvedValue: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_simple_enum.rs:10:13
|
10 | Unnamed(UnresolvedValue),
| ^^^^^^^^^^^^^^^ the trait `ResolvedValue` is not implemented for `UnresolvedValue`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_simple_enum.rs:7:10
|
7 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `UnresolvedValue: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_simple_enum.rs:11:16
|
11 | Named { a: UnresolvedValue },
| ^^^^^^^^^^^^^^^ the trait `ResolvedValue` is not implemented for `UnresolvedValue`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_simple_enum.rs:7:10
|
7 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![allow(dead_code)]

use turbo_tasks::ResolvedValue;

struct UnresolvedValue;

#[derive(ResolvedValue)]
struct ContainsUnresolvedValueNamed {
a: UnresolvedValue,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0277]: the trait bound `UnresolvedValue: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_simple_named_struct.rs:9:8
|
9 | a: UnresolvedValue,
| ^^^^^^^^^^^^^^^ the trait `ResolvedValue` is not implemented for `UnresolvedValue`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_simple_named_struct.rs:7:10
|
7 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![allow(dead_code)]

use turbo_tasks::ResolvedValue;

struct UnresolvedValue;

#[derive(ResolvedValue)]
struct ContainsUnresolvedValueUnnamed(UnresolvedValue);

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0277]: the trait bound `UnresolvedValue: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_simple_unnamed_struct.rs:8:39
|
8 | struct ContainsUnresolvedValueUnnamed(UnresolvedValue);
| ^^^^^^^^^^^^^^^ the trait `ResolvedValue` is not implemented for `UnresolvedValue`
|
= 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 `DeriveResolvedValueAssertion::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_simple_unnamed_struct.rs:7:10
|
7 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#![allow(dead_code)]

use turbo_tasks::ResolvedValue;

#[derive(ResolvedValue)]
struct ContainsUnderconstrainedGeneric<T> {
value: T,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error[E0277]: the trait bound `T: ResolvedValue` is not satisfied
--> tests/derive_resolved_value/fail_underconstrained_generic.rs:7:12
|
7 | value: T,
| ^ the trait `ResolvedValue` is not implemented for `T`
|
note: required by a bound in `DeriveResolvedValueAssertion::<T>::assert_impl_resolved_value`
--> tests/derive_resolved_value/fail_underconstrained_generic.rs:5:10
|
5 | #[derive(ResolvedValue)]
| ^^^^^^^^^^^^^ required by this bound in `DeriveResolvedValueAssertion::<T>::assert_impl_resolved_value`
= note: this error originates in the derive macro `ResolvedValue` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
6 | struct ContainsUnderconstrainedGeneric<T: turbo_tasks::ResolvedValue> {
| ++++++++++++++++++++++++++++
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#![allow(dead_code)]

use turbo_tasks::{ResolvedValue, ResolvedVc};

#[derive(ResolvedValue)]
struct ContainsResolvedVcNamedStruct {
a: ResolvedVc<i32>,
}

#[derive(ResolvedValue)]
struct ContainsResolvedVcUnnamedStruct(ResolvedVc<i32>);

#[derive(ResolvedValue)]
enum ContainsResolvedVcEnum {
Unit,
Unnamed(ResolvedVc<i32>),
Named { a: ResolvedVc<i32> },
}

#[derive(ResolvedValue)]
struct ContainsResolvedAlongWithOtherValues {
a: i32,
b: ResolvedVc<i32>,
c: (),
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use turbo_tasks::ResolvedValue;

#[derive(ResolvedValue)]
struct ContainsBorrowedData<'a> {
borrowed: &'a Option<&'a [&'a str]>,
}

fn main() {
let a = ContainsBorrowedData {
borrowed: &Some(["value"].as_slice()),
};
let _ = a.borrowed;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use turbo_tasks::ResolvedValue;

#[derive(ResolvedValue)]
// use an inline type constraint here
struct LinkedList<T: ResolvedValue> {
// LinkedListNode is also a ResolvedValue
head: Option<Box<LinkedListNode<T>>>,
}

#[derive(ResolvedValue)]
struct LinkedListNode<T>
where
T: ResolvedValue, // use a where type constraint here
{
current: T,
// A self-recursive type
next: Option<Box<LinkedListNode<T>>>,
}

fn main() {
let ll = LinkedList {
head: Some(Box::new(LinkedListNode {
current: 1,
next: Some(Box::new(LinkedListNode {
current: 2,
next: None,
})),
})),
};
let _last = ll.head.unwrap().next.unwrap().current;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![allow(dead_code)]

use std::marker::PhantomData;

use turbo_tasks::{ResolvedValue, Vc};

struct Unresolved;

#[derive(ResolvedValue)]
struct PhantomDataCanContainAnything<T: Send>(
PhantomData<Vc<T>>,
PhantomData<Unresolved>,
PhantomData<Vc<Unresolved>>,
);

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

use turbo_tasks::{ResolvedValue, ResolvedVc};

#[derive(ResolvedValue)]
enum EnumI32 {
Unit,
Unnamed(i32),
Named { a: i32 },
}

#[derive(ResolvedValue)]
enum EnumResolvedVc {
Unit,
Unnamed(ResolvedVc<i32>),
Named { a: ResolvedVc<i32> },
}

fn main() {}
Loading

0 comments on commit 6c3e499

Please sign in to comment.