Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
25c0c9f
Propagate JavaScript exceptions through Rust JSG function calls
bartlomieju Oct 24, 2026
d9df758
Use assert_eq! in Rust JSG function call tests
bartlomieju Oct 24, 2026
38e3085
Describe caught JavaScript exceptions without running JavaScript
bartlomieju Oct 24, 2026
12751a9
Keep what JavaScript throws during Rust JSG coercions
bartlomieju Oct 24, 2026
664cc5f
Say in Rust JSG's README which conversions keep what JavaScript throws
bartlomieju Oct 24, 2026
a5c16d8
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-co…
bartlomieju Oct 25, 2026
0ad7253
Leave what JavaScript throws during Rust JSG coercions pending
bartlomieju Oct 25, 2026
eb9cdce
Pass on what reading a jsg::Error's name and message throws
bartlomieju Oct 25, 2026
13674f2
Convert Vecs and structs to JavaScript without running JavaScript
bartlomieju Oct 25, 2026
3a67032
Leave what JavaScript setters throw pending in Rust JSG
bartlomieju Oct 25, 2026
f733fbd
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-se…
bartlomieju Oct 25, 2026
88bb87a
Fix Rust JSG docs on fallible setters and key interning
bartlomieju Oct 25, 2026
c84c183
Make Rust JSG collection GC tests depend on tracing
bartlomieju Oct 26, 2026
da095b3
Add jsg::Dict to Rust JSG
bartlomieju Oct 26, 2026
cfc9f87
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-se…
bartlomieju Oct 27, 2026
5451f80
Follow fast_jsg_struct when converting a #[jsg_struct] in Rust JSG
bartlomieju Oct 27, 2026
ec0c573
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-se…
bartlomieju Oct 27, 2026
082e2f3
Merge remote-tracking branch 'origin/biwanczuk/rust-jsg-set-exception…
bartlomieju Oct 27, 2026
ba3c25a
Address review of Rust JSG struct conversion and setters
bartlomieju Oct 27, 2026
c7f3b3c
Put DictionaryTemplate in backticks in a Rust JSG test doc comment
bartlomieju Oct 27, 2026
aa681be
Merge remote-tracking branch 'origin/biwanczuk/rust-jsg-set-exception…
bartlomieju Oct 27, 2026
a5ff3f9
Test that Rust JSG's Dict::get() returns the first duplicate
bartlomieju Oct 27, 2026
3db21ea
Make Rust JSG's Dict entries private and rename get() to find()
bartlomieju Oct 27, 2026
93bd779
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-dict
bartlomieju Oct 27, 2026
e4ce0dd
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-dict
bartlomieju Oct 28, 2026
4dafc2a
Define Rust JSG Dict entries as data properties
bartlomieju Oct 28, 2026
f82f657
Don't define Rust JSG data properties while terminating
bartlomieju Oct 28, 2026
02e0a4b
Merge branch 'main' into biwanczuk/rust-jsg-dict
bartlomieju Oct 28, 2026
43248bd
Convert Rust JSG unions like C++ JSG kj::OneOf
bartlomieju Oct 27, 2026
3cb9556
Let Rust JSG unions hold every type kj::OneOf can
bartlomieju Oct 28, 2026
d9ee08b
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-oneof
bartlomieju Oct 28, 2026
e32fb0d
Merge remote-tracking branch 'origin/main' into biwanczuk/rust-jsg-oneof
bartlomieju Oct 28, 2026
ec5c442
Address review of Rust JSG unions
bartlomieju Oct 29, 2026
d3fbf0c
Match both compilers' error in a Rust JSG compile test
bartlomieju Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/rust/jsg-compile-tests/non-coercible-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// NonCoercible<T> takes only the types a value is otherwise coerced to (String, bool, f64
// and the integers), as C++ JSG's takes only a CoercibleType. A union or a resource
// would convert a value as it always does, coercion included, once its JavaScript type passed a
// coarse check.

use jsg::FromJS;
use jsg::NonCoercible;
use jsg_macros::jsg_oneof;
use jsg_macros::jsg_resource;

#[jsg_resource]
pub struct Thing;

#[jsg_resource]
impl Thing {}

#[jsg_oneof]
pub enum ThingOrString {
Thing(jsg::Rc<Thing>),
String(String),
}

pub fn union_name() -> String {
<NonCoercible<ThingOrString> as FromJS<'static>>::type_name() //~ ERROR E0277
}

pub fn resource_name() -> String {
<NonCoercible<jsg::Rc<Thing>> as FromJS<'static>>::type_name() //~ ERROR E0277
}
70 changes: 70 additions & 0 deletions src/rust/jsg-compile-tests/ok-oneof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// The unions that Web IDL allows compile, including several resources, a handle of any type with
// a dictionary, an object handle with primitives, and different resources and typed array handles
// in a union and a union nested in it.

use jsg::Nullable;
use jsg::v8;
use jsg_macros::jsg_oneof;
use jsg_macros::jsg_resource;
use jsg_macros::jsg_struct;

#[jsg_struct]
pub struct Point {
pub x: f64,
}

#[jsg_resource]
pub struct First;

#[jsg_resource]
impl First {}

#[jsg_resource]
pub struct Second;

#[jsg_resource]
impl Second {}

#[jsg_oneof]
pub enum Everything<'s> {
First(jsg::Rc<First>),
Second(jsg::Rc<Second>),
Point(Point),
Numbers(Vec<f64>),
Bytes(v8::Local<'s, v8::Uint8Array>),
String(String),
Number(i32),
Bool(bool),
Date(std::time::SystemTime),
}

#[jsg_oneof]
pub enum Handles<'s> {
Object(v8::Local<'s, v8::Object>),
String(Nullable<String>),
Number(v8::Local<'s, v8::BigInt>),
Function(v8::Local<'s, v8::Function>),
}

#[jsg_oneof]
pub enum AnyOrPoint<'s> {
Any(v8::Local<'s, v8::Value>),
Point(Point),
}

#[jsg_oneof]
pub enum SecondOrClamped<'s> {
Second(jsg::Rc<Second>),
Clamped(v8::Local<'s, v8::Uint8ClampedArray>),
}

#[jsg_oneof]
pub enum FirstOrNested<'s> {
First(jsg::Rc<First>),
Bytes(v8::Local<'s, v8::Uint8Array>),
Nested(SecondOrClamped<'s>),
}
33 changes: 33 additions & 0 deletions src/rust/jsg-compile-tests/oneof-duplicate-types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// A #[jsg_oneof] union may not contain the same type twice, as C++ JSG's
// webidl::UnionTypeValidator requires of a kj::OneOf, even of a kind it may have more than one
// of, such as resources. As there, a nullable type counts as its inner type. Each union is
// rejected twice: by conflicting trait impls, which point at the second variant, and because
// the union's names contain the resource's twice.

use jsg::Nullable;
use jsg_macros::jsg_oneof;
use jsg_macros::jsg_resource;

#[jsg_resource]
pub struct Thing;

#[jsg_resource]
impl Thing {}

type SameThing = jsg::Rc<Thing>;

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum SameResourceTwice {
First(jsg::Rc<Thing>),
Second(SameThing), //~ ERROR E0119
}

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum ResourceAndNullableResource {
Thing(jsg::Rc<Thing>),
Nullable(Nullable<jsg::Rc<Thing>>), //~ ERROR E0119
}
23 changes: 23 additions & 0 deletions src/rust/jsg-compile-tests/oneof-generic-parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// A #[jsg_oneof] union, like a kj::OneOf, has no type parameters. It may have one lifetime
// parameter, for the scope of the handles it holds.

use jsg::v8;
use jsg_macros::jsg_oneof;

#[jsg_oneof]
pub enum TypeParameter<T> {
//~^ ERROR only one lifetime parameter
Value(T),
String(String),
}

#[jsg_oneof]
pub enum TwoLifetimes<'a, 'b> {
//~^ ERROR only one lifetime parameter
First(v8::Local<'a, v8::Object>),
Second(v8::Local<'b, v8::String>),
}
74 changes: 74 additions & 0 deletions src/rust/jsg-compile-tests/oneof-nested-duplicate-types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// A #[jsg_oneof] union may not contain a type that a union nested in it also contains, as C++
// JSG's webidl::FlattenedTypeTraits flattens a nested kj::OneOf before checking for duplicate
// types. These types are in no Web IDL category with a limit, so only their names catch them.

use std::time::SystemTime;

use jsg::Nullable;
use jsg::v8;
use jsg_macros::jsg_oneof;
use jsg_macros::jsg_resource;

#[jsg_resource]
pub struct Thing;

#[jsg_resource]
impl Thing {}

#[jsg_oneof]
pub enum ThingOrString {
Thing(jsg::Rc<Thing>),
String(String),
}

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum ResourceInNestedUnion {
Thing(jsg::Rc<Thing>),
Nested(ThingOrString),
}

#[jsg_oneof]
pub enum NullableThingOrString {
Thing(Nullable<jsg::Rc<Thing>>),
String(String),
}

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum ResourceInNullableInNestedUnion {
Thing(jsg::Rc<Thing>),
Nested(NullableThingOrString),
}

#[jsg_oneof]
pub enum BytesOrString<'s> {
Bytes(v8::Local<'s, v8::Uint8Array>),
String(String),
}

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum HandleInNestedUnion<'s> {
Bytes(v8::Local<'s, v8::Uint8Array>),
Nested(BytesOrString<'s>),
}

#[jsg_oneof]
pub enum DateOrString {
Date(SystemTime),
String(String),
}

#[jsg_oneof]
pub enum DateOrBool {
Date(SystemTime),
Bool(bool),
}

#[jsg_oneof] //~ ERROR E0080: may not contain the same type twice
pub enum DateInTwoNestedUnions {
First(DateOrString),
Second(DateOrBool),
}
15 changes: 15 additions & 0 deletions src/rust/jsg-compile-tests/oneof-variant-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// A #[jsg_oneof] variant is a type that converts from JavaScript and that C++ JSG's kj::OneOf
// could hold.

use jsg_macros::jsg_oneof;

#[jsg_oneof] //~ ERROR E0277
pub enum FloatOrString {
Float(f32), //~ ERROR cannot be a `#[jsg_oneof]` variant
//~| ERROR E0277
String(String),
}
104 changes: 104 additions & 0 deletions src/rust/jsg-compile-tests/oneof-web-idl-rules.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2026 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

// A #[jsg_oneof] union follows Web IDL's rules for unions, as C++ JSG's
// webidl::UnionTypeValidator checks a kj::OneOf at compile time: at most one type of each kind
// that a value can't tell apart, counting the types of nested unions and nullable types.

use jsg::Dict;
use jsg::Nullable;
use jsg::v8;
use jsg_macros::jsg_oneof;
use jsg_macros::jsg_resource;
use jsg_macros::jsg_struct;

#[jsg_struct]
pub struct Point {
pub x: f64,
}

#[jsg_resource]
pub struct Thing;

#[jsg_resource]
impl Thing {}

#[jsg_oneof] //~ ERROR E0080: at most one boolean type
pub enum TwoBooleans {
Bool(bool),
Strict(jsg::NonCoercible<bool>),
}

#[jsg_oneof] //~ ERROR E0080: at most one numeric type
pub enum TwoNumerics {
Number(f64),
Integer(u32),
}

#[jsg_oneof] //~ ERROR E0080: at most one string type
pub enum TwoStrings<'s> {
String(String),
Handle(v8::Local<'s, v8::String>),
}

#[jsg_oneof] //~ ERROR E0080: an object type only if
pub enum ObjectAndResource<'s> {
Object(v8::Local<'s, v8::Object>),
Thing(jsg::Rc<Thing>),
}

#[jsg_oneof] //~ ERROR E0080: an object type only if
pub enum ObjectAndRecord<'s> {
Object(v8::Local<'s, v8::Object>),
Dict(Dict<f64>),
}

#[jsg_oneof] //~ ERROR E0080: an object type only if
pub enum ObjectAndSequence<'s> {
Object(v8::Local<'s, v8::Object>),
Sequence(Vec<f64>),
}

#[jsg_oneof] //~ ERROR E0080: at most one dictionary-like type
pub enum DictionaryAndRecord {
Point(Point),
Dict(Dict<f64>),
}

#[jsg_oneof] //~ ERROR E0080: at most one sequence-like type
pub enum TwoSequences {
Numbers(Vec<f64>),
Strings(Vec<String>),
}

#[jsg_oneof] //~ ERROR E0080: at most one nullable or dictionary type
pub enum NullableAndDictionary {
Number(Nullable<f64>),
Point(Point),
}

#[jsg_oneof] //~ ERROR E0080: at most one nullable or dictionary type
pub enum TwoNullables {
Number(Nullable<f64>),
Integer(Nullable<String>),
}

#[jsg_oneof] //~ ERROR E0080: may not contain Option<T> or Lenient<T>
pub enum Optional {
Number(Option<f64>),
String(String),
}

#[jsg_oneof]
pub enum StringOrBool {
String(String),
Bool(bool),
}

// The nested union's string type counts as the outer union's.
#[jsg_oneof] //~ ERROR E0080: at most one string type
pub enum NestedStrings {
String(String),
Nested(StringOrBool),
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// https://opensource.org/licenses/Apache-2.0

// `T: for<'s> FromJS<'s, ResultType = T>` (used where a value must outlive the scope) excludes
// handle types.
// handle types. Compilers word the error differently: some report that the `FromJS` impl is not
// general enough, others that the bound `for<'s> FromJS<'s>` is not satisfied (E0277).
use jsg::FromJS;
use jsg::v8;

pub fn owned<T: for<'s> FromJS<'s, ResultType = T>>() {}

pub fn t() {
owned::<v8::Local<'static, v8::Object>>(); //~ ERROR E0277
owned::<v8::Local<'static, v8::Object>>(); //~ ERROR FromJS
}
16 changes: 11 additions & 5 deletions src/rust/jsg-macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,11 +414,17 @@ impl Env {

## `#[jsg_oneof]`

Generates `jsg::Type` and `jsg::FromJS` for a union enum — the Rust equivalent of
`kj::OneOf<…>`. Each variant must be a single-field tuple whose inner type implements
`jsg::Type` + `jsg::FromJS`. Variants are tried in declaration order using
exact-type matching; if none matches, a `TypeError` is thrown listing all expected
types.
Generates `jsg::Type`, `jsg::FromJS` and `jsg::Traced` for a union enum — the Rust
equivalent of `kj::OneOf<…>` — and `jsg::ToJS` when every variant implements it. Each
variant must be a single-field tuple whose inner type converts from JavaScript and is
`jsg::Type`. A union that holds handles has one lifetime parameter,
their scope, and no `jsg::Traced` that can be used; a union has no other generic
parameters. A value converts as it would to the equivalent `kj::OneOf`: variants are
tried in passes that leave coercion for last, in declaration order within a pass. If no
variant converts the value, a `TypeError` is thrown naming the expected types, such as
`Expected string or number but got symbol`. Like `kj::OneOf`, a union that breaks Web
IDL's rules for unions, such as one with two string types, doesn't compile. See
[Union Types](../jsg/README.md#union-types).

```rust
#[jsg_oneof]
Expand Down
Loading
Loading