Skip to content

add support for traceable collections to Rust#6384

Open
anonrig wants to merge 1 commit intomainfrom
yagiz/add-collections-support-to-rust
Open

add support for traceable collections to Rust#6384
anonrig wants to merge 1 commit intomainfrom
yagiz/add-collections-support-to-rust

Conversation

@anonrig
Copy link
Copy Markdown
Member

@anonrig anonrig commented Mar 23, 2026

@anonrig anonrig requested review from guybedford and jasnell March 23, 2026 14:28
@anonrig anonrig requested review from a team as code owners March 23, 2026 14:28
@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 23, 2026

LGTM

github run

@anonrig anonrig force-pushed the yagiz/add-collections-support-to-rust branch from ff76bff to 05b4eea Compare March 23, 2026 14:43
Copy link
Copy Markdown
Contributor

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry I just don't think this is the right approach - matching name strings is clearly in violation of scoping rules, as I've mentioned before.

Please explain to me why we aren't using a trait to handle this?

@anonrig anonrig force-pushed the yagiz/add-collections-support-to-rust branch 2 times, most recently from 26d1598 to 5580e83 Compare March 31, 2026 21:17
@anonrig anonrig force-pushed the yagiz/add-collections-support-to-rust branch from 5580e83 to 974acac Compare March 31, 2026 21:19
@anonrig anonrig requested review from guybedford and jasnell March 31, 2026 21:20
@anonrig
Copy link
Copy Markdown
Member Author

anonrig commented Mar 31, 2026

Addressed the review feedback:

  • Reworked GC tracing to be trait-driven via Traced instead of field-type string matching.
  • GarbageCollected now extends Traced; trace() lives on Traced.
  • Removed #[jsg_traceable] / #[jsg_trace] and updated resource tracing to call Traced::trace() on every field.
  • Added no-op Traced impls for non-traceable/value types and delegating impls for wrappers/collections.
  • Updated docs/comments (including jsg_struct wording around class_name() usage and macro table formatting).
  • just clippy passes on this branch.

Could you please take another look?

// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

//! Procedural macros for the JSG (JavaScript Glue) Rust bindings.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! Procedural macros for the JSG (JavaScript Glue) Rust bindings.
//! Procedural macros for the JSG Rust bindings.

Copy link
Copy Markdown
Collaborator

@jasnell jasnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings from review:

  • [MEDIUM] name + custom_trace combination silently drops the name override — extract_name_attribute parses the full attr stream as a single MetaNameValue, which fails when custom_trace is also present
  • [MEDIUM] has_custom_trace_flag uses string-based parsing rather than syn meta parsing, which is inconsistent with the rest of the crate
  • [LOW] RefCell<T> tracing uses self.borrow() which will panic if a mutable borrow is active during GC

This review was generated by an AI assistant and may contain inaccuracies.

let class_name = if attr.is_empty() {
name.to_string()
} else {
extract_name_attribute(attr).unwrap_or_else(|| name.to_string())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When attr is name = "Foo", custom_trace, has_custom_trace_flag on line 39 correctly detects the flag, but extract_name_attribute(attr) here tries to parse the entire token stream as a single syn::MetaNameValue. That parse fails on the trailing , custom_trace, causing .ok()? to return None — the name override is silently dropped and the struct's Rust name is used instead.

The README (line 403) documents this combination as supported: #[jsg_resource(name = "MyName", custom_trace)].

Consider stripping custom_trace from the token stream before passing to extract_name_attribute, or refactoring the attribute parsing to handle all flags in one pass (similar to parse_jsg_property_args).

/// Handles both bare `custom_trace` and combined forms like `name = "Foo", custom_trace`.
/// When set, `#[jsg_resource]` on a struct suppresses the auto-generated `GarbageCollected`
/// impl, letting the user write their own.
pub fn has_custom_trace_flag(attr: &TokenStream) -> bool {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this PR's motivation (replacing string-based type matching with trait-driven tracing), it's a bit inconsistent that has_custom_trace_flag itself converts the TokenStream to a string and splits on ,. Consider using syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated to look for a bare Path ident custom_trace — this would be consistent with parse_jsg_property_args and other attribute parsing in this crate. Not blocking since the current approach works and has test coverage.


impl<T: Traced> Traced for std::cell::RefCell<T> {
fn trace(&self, visitor: &mut crate::v8::GcVisitor) {
self.borrow().trace(visitor);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefCell::borrow() will panic if the RefCell is currently mutably borrowed. If GC tracing fires while a mutable borrow is active, this panics — and since panics across FFI are UB, this could abort.

In practice, GC tracing happens at well-defined points where user code isn't running, so this is unlikely to be hit. But consider either:

  • Using try_borrow() and silently skipping the trace if the borrow fails, or
  • Adding a doc comment noting that RefCell fields in resources must not be mutably borrowed during GC

(The Cell<T> impl above correctly avoids this issue by using as_ptr().)

Copy link
Copy Markdown
Contributor

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we just add one more thing to this PR - a derive macro for Traced on structs and enums?

Example:

#[derive(jsg::Traced)]
struct MyPlainData { ... }

#[derive(jsg::Traced)]
enum MyPlainData { ... }

basically works just like the current trace.rs does anyway.

//! implement `jsg::Traced`, and the generated body simply delegates to
//! `jsg::Traced::trace(&self.field, visitor)` for each named field.

/// Generates one trace delegation statement per named field.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nice to just explain the design a little more clearly here, something like:

Suggested change
/// Generates one trace delegation statement per named field.
/// Generates one trace delegation statement per named field.
/// All fields are traced for safety by default, based on enforcing they implement
/// the `Traced` trait, which all `GarbageCollected` types implement, and all non-GC
/// types implement as a no-op.

Comment on lines +144 to +145
impl Traced for &str {}
impl Traced for Number {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put these into impl_traced_noop above?

Copy link
Copy Markdown
Contributor

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking this as conditional approval on the comments and derive macro being added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants