Conversation
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR adds #[jsg_prototype_property], #[jsg_instance_property], and #[jsg_inspect_property] macro support to the Rust JSG layer, mirroring the corresponding C++ JSG_PROTOTYPE_PROPERTY, JSG_INSTANCE_PROPERTY, JSG_LAZY_INSTANCE_PROPERTY, and JSG_INSPECT_PROPERTY macros.
Issues found (ranked by severity):
- [HIGH]
extract_name_attributesilently drops name override whenlazyflag is present.#[jsg_instance_property(lazy, name = "cachedLabel")]— the documented combined form — does not work. - [MEDIUM] Lazy instance properties don't actually cache the getter result — the Rust side uses
SetAccessorPropertyinstead of C++'sSetLazyDataProperty, so the getter fires on every access. The user-facing docs claim caching behavior. - [LOW] Inspect properties don't register in the
inspectPropertiesdictionary —node:util inspect()may not discover Rust-defined inspect properties.
This review was generated by an AI assistant and may contain inaccuracies.
|
Review posted successfully. Here's a summary of what I found: 3 issues filed on PR #6401 (all posted as inline comments in a single batch review):
|
036c771 to
d9f1930
Compare
127a643 to
9f7bd00
Compare
9f7bd00 to
f4a70ad
Compare
|
/bigbonk review this PR again |
There was a problem hiding this comment.
Follow-up review after lazy support removal and inspect properties fix.
Previously flagged issues — status:
- [HIGH]
extract_name_attribute+lazy— No longer applicable.lazyhas been removed;#[jsg_property]now usesparse_jsg_property_argswhich properly handles comma-separated meta items.extract_name_attributeis only called wherename = "..."is the sole argument. - [MEDIUM] Lazy properties don't cache — Resolved. Lazy support removed per jasnell's feedback.
- [LOW] Missing
inspectPropertiesdictionary — Fixed. Lines 768-773 create the ObjectTemplate and line 845 registers each inspect symbol. - jasnell's
NewFromUtf8length check — Addressed withKJ_REQUIREat line 779.
New findings below (posted as inline comments).
This review was generated by an AI assistant and may contain inaccuracies.
|
Review posted on PR #6401 with 4 inline comments. Here's a summary: Previously flagged issues (all resolved):
New findings:
|
…Rust JSG - Prototype read-write properties were registered with DontEnum; they should use None (enumerable) to match C++ registerPrototypeProperty which uses Gcb::enumerable = true (resource.h:1454-1455). - Prototype read-only properties were registered with ReadOnly|DontEnum; they should use ReadOnly only, matching registerReadonlyPrototypeProperty (resource.h:1498-1501) with Gcb::enumerable = true. - The spec-compliant path of makePropFn was passing the constructor signature to FunctionTemplate::New; C++ passes an empty v8::Local<v8::Signature>() for spec-compliant prototype properties (resource.h:1438-1441). - Add three new tests that assert prototype properties are enumerable and appear in for...in, providing regression coverage for the DontEnum fix.
- PropertyKind::Prototype doc said 'Not directly enumerable' which is wrong after the DontEnum fix; update to say enumerable. - jsg_inspect_property docstring claimed no get_/set_ stripping occurs for the auto-derived symbol name, but derive_js_name() does strip those prefixes; correct the description to match the implementation. - scan_property_annotations used BTreeMap which sorted property groups alphabetically, causing registration order to differ from source order. Replace with a Vec-based find-or-insert to preserve source order. - methods and static_constants loops in create_resource_template were missing the KJ_REQUIRE length check added for properties; add it for consistency. - Remove redundant static_cast<v8::PropertyAttribute> wrapping a single enum value in the read-only instance property branch. - Remove dead 'let _ = p' binding in the readonly match arm of parse_jsg_property_args; p is already consumed by the guard expression.
| switch (prop.kind) { | ||
| case PropertyKind::Prototype: { | ||
| // Mirrors registerPrototypeProperty / registerReadonlyPrototypeProperty in resource.h. | ||
| auto getterFn = makePropFn(prop.getter_callback, true); |
There was a problem hiding this comment.
If we're going to have bool args, let's at least label their callsites.
| auto getterFn = makePropFn(prop.getter_callback, true); | |
| auto getterFn = makePropFn(prop.getter_callback, true /* is getter */); |
| // Mirrors registerPrototypeProperty / registerReadonlyPrototypeProperty in resource.h. | ||
| auto getterFn = makePropFn(prop.getter_callback, true); | ||
| KJ_IF_SOME(setterCb, prop.setter_callback) { | ||
| auto setterFn = makePropFn(setterCb, false); |
There was a problem hiding this comment.
| auto setterFn = makePropFn(setterCb, false); | |
| auto setterFn = makePropFn(setterCb, false /* is getter */); |
| KJ_REQUIRE(constant.name.size() <= static_cast<size_t>(v8::String::kMaxLength), | ||
| "Rust constant name exceeds V8 string length limit", constant.name); | ||
| auto name = ::workerd::jsg::check(v8::String::NewFromUtf8( | ||
| isolate, constant.name.data(), v8::NewStringType::kInternalized, constant.name.size())); |
There was a problem hiding this comment.
This is duplicated a couple of times, likely worth putting into a utility method.
| /// Registered under a unique symbol on the prototype. Invisible to normal | ||
| /// enumeration and string-key lookup; surfaced by `node:util` `inspect()`. | ||
| Inspect, | ||
| } |
There was a problem hiding this comment.
The fact this enum is duplicated here and in jsg-macros/lib.rs is a bit annoying. Any chance to condense?
There was a problem hiding this comment.
Unfortunately not. There is a comment before jsg-macros/lib.rs enum declaration.
There was a problem hiding this comment.
A shared "types" crate could be used to define these without duplication, could it not? A bit more work on build but easier to maintain in one location. Also, that explains why this and the one in jsg-macros are duplicated, but this one and the third copy in v8.rs can be deduplicated. And, if nothing else, all three should cross reference each other with a comment reminding folks coming into this code in the future that the three need to be kept in sync.
There was a problem hiding this comment.
marking this unresolved because the two definitions in lib.rs and v8.rs ought to be able to be deduplicated.
- Keep the CXX bridge definition in v8.rs
- Replace the standalone pub enum PropertyKind in lib.rs with a re-export:
pub use v8::ffi::PropertyKind;
- Eliminate the manual conversion in resource.rs:936–940 that currently maps between the two:
// This entire match goes away: let ffi_kind = match kind { PropertyKind::Prototype => v8::ffi::PropertyKind::Prototype, PropertyKind::Instance => v8::ffi::PropertyKind::Instance, PropertyKind::Inspect => v8::ffi::PropertyKind::Inspect, };
| struct Counter { value: Cell<f64> } | ||
|
|
||
| #[jsg_resource] | ||
| impl Counter { |
There was a problem hiding this comment.
This appears to be duplicating the content in jsg-macros/README.md ... or that is duplicating this content. Either way, we should condense this.
| Instance = 1, | ||
| /// Registered under a unique symbol; invisible to normal enumeration. | ||
| Inspect = 2, | ||
| } |
There was a problem hiding this comment.
Now a third definition of the same enum?
| }; | ||
| let rust_method_name = method.sig.ident.clone(); | ||
| let rust_name_str = rust_method_name.to_string(); | ||
| let is_setter = rust_name_str.starts_with("set_"); |
There was a problem hiding this comment.
We aren't validating here that a property declaration must either start with get_ or set_.
If someone defines #[jsg_property] foo () surely that isn't supported? Or do we want to support that for a getter only variant?
I think it's good to be stricter though and validate invalid patterns to ensure we have a well-defined model.
| /// Parses the argument list of `#[jsg_property(placement [, name = "..."] [, readonly])]`. | ||
| /// | ||
| /// `placement` must be either `instance` or `prototype` (required). | ||
| /// Returns `(PropertyKind, Option<js_name_override>, is_readonly)`. | ||
| fn parse_jsg_property_args( | ||
| tokens: TokenStream, | ||
| ) -> Result<(PropertyKind, Option<String>, bool), quote::__private::TokenStream> { |
There was a problem hiding this comment.
Can we make the placement macro argument optional? I think the default for JS of considering properties as instance properties is a fairly safe one to assume.
No description provided.