You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Module namespace exports are accessors, not data descriptors, for live bindings #9889
#9879 made aliased dynamic-import bindings LIVE, which is the semantics that
matters and was genuinely broken. It implements liveness by installing the
export as an accessor property on the namespace object, so the descriptor
shape now diverges from the spec.
The divergence
crates/perry-runtime/src/object/namespace_create.rs: an entry whose parallel live_flags byte is non-zero is installed with js_object_define_accessor
plus PropertyAttrs::new(false, true, false).
ECMA-262 §10.4.6 makes a module namespace an exotic object whose [[GetOwnProperty]] returns a data descriptor holding the binding's
current value:
// NodeObject.getOwnPropertyDescriptor(ns,"x")// { value: 1, writable: true, enumerable: true, configurable: false }// Perry, for a live (aliased) export after #9879// { get: [Function], set: undefined, enumerable: true, configurable: false }
So "value" in desc, desc.writable, and anything that branches on desc.get sees a different shape. Liveness itself is correct either way —
the accessor reads through to the binding.
Why it was landed anyway
Before #9879 the aliased binding was a dead snapshot: the value was simply
wrong after reassignment. Trading a wrong value for a right value with an
unusual descriptor is a clear net improvement, and the alternative — a real
module-namespace exotic object whose internal methods do the liveness — is a
much larger change than the bug warranted.
What this issue wants
Either implement the namespace as an exotic object so [[GetOwnProperty]]
synthesises a data descriptor over the live binding, or special-case getOwnPropertyDescriptor / Object.keys / JSON.stringify on namespace
objects to report the data shape while keeping the accessor storage.
A test is worth adding regardless: test_gap_dynamic_import_alias_binding.ts
covers liveness under GC stress but asserts nothing about descriptor shape, so
this deviation is currently invisible to the suite.
Introduced by #9879, landed via merge train #9888.
Fixed by #9899, landed on main via merge train #9903 — verified on main.
It takes the second option this issue described: accessors stay as the internal representation of the live binding, and the namespace's [[GetOwnProperty]] synthesises the spec's data descriptor over it (writable: true, enumerable: true, configurable: false, per §10.4.6.5). module_namespace_own_property_descriptor roots both the namespace and the key across js_object_get_field_by_name, which runs the getter and can allocate and evacuate.
The gap test now asserts the shape this issue asked for — "value" in descriptor, all three attributes, get/set both undefined — alongside liveness across a reassignment. As a gap test it is diffed against Node, so Node is the oracle for the descriptor shape rather than my reading of the spec.
Activity
proggeramlug commentedon Sep 6, 2026
Fixed by #9899, landed on
mainvia merge train #9903 — verified on main.It takes the second option this issue described: accessors stay as the internal representation of the live binding, and the namespace's
[[GetOwnProperty]]synthesises the spec's data descriptor over it (writable: true, enumerable: true, configurable: false, per §10.4.6.5).module_namespace_own_property_descriptorroots both the namespace and the key acrossjs_object_get_field_by_name, which runs the getter and can allocate and evacuate.The gap test now asserts the shape this issue asked for —
"value" in descriptor, all three attributes,get/setbothundefined— alongside liveness across a reassignment. As a gap test it is diffed against Node, so Node is the oracle for the descriptor shape rather than my reading of the spec.