Skip to content

Module namespace exports are accessors, not data descriptors, for live bindings #9889

Closed

Description

@proggeramlug

Summary

#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:

// Node
Object.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.

Activity

  1. proggeramlug commented on Sep 6, 2026

    @proggeramlug
    ContributorAuthor

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions