Skip to content

fix(runtime,codegen): make Object.hasOwn a real first-class value (#3527) - #3751

Merged
proggeramlug merged 1 commit into
mainfrom
fix/3527-object-hasown-value
May 31, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix/3527-object-hasown-value

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Object.hasOwn (ES2022) worked as a direct call but not as a first-class value: typeof Object.hasOwn folded to "function", yet reading the value produced the 0.0/undefined sentinel — a non-callable. This fixes that, unblocking the next layer of the #3527 Express compile.

Why it matters

The const-alias call-fold (const k = Object.hasOwn; k(o, x)) hid the gap, but iconv-lite's merge-exports.js assigns it through a ternary into a var:

var hasOwn = typeof Object.hasOwn === "undefined"
  ? Function.call.bind(Object.prototype.hasOwnProperty)
  : Object.hasOwn
...
if (hasOwn(module, key)) target[key] = module[key]

The ternary defeats the fold, so hasOwn held a non-callable and hasOwn(module, key) threw TypeError: value is not a function during iconv-lite init — a hard blocker compiling Express. (Localized via a debug-symbol build + b print_uncaught → backtrace pointing at merge-exports.js.)

Fix (two parts)

  • Runtime (global_this.rs): reify Object.hasOwn as a callable static on the Object constructor closure (object_hasown_thunk → existing js_object_has_own), alongside the existing keys/values/assign/… thunks. Makes the rebound form (const O = Object; O.hasOwn(o, x)) dispatch correctly.
  • Codegen (property_get.rs): when Object.hasOwn is read as a VALUE (PropertyGet { GlobalGet(0), "hasOwn" }), resolve the reified Object constructor closure and read the hasOwn static off it — mirroring the existing Error.captureStackTrace value path — instead of emitting the 0.0 sentinel.

Verification

  • typeof Object.hasOwn === "function"; the value computes correctly whether called directly, stored in a var via a ternary, passed through an array, or rebound (const O = Object).
  • test_gap_object_methods.ts extended with the as-value cases — byte-identical to node --experimental-strip-types.
  • No regression: direct Object.hasOwn(o,k), Object.keys, and typeof Error.isError all still correct. cargo fmt --check clean.
  • The Express compile now advances past iconv-lite's merge-exports to the next blocker.

Scope

The broader "Object.keys/values/… as escaping values" gap (a pre-existing known limitation) is unchanged; this fixes the ES2022 hasOwn case that Express's dependency tree actually exercises.

Part of the #3527 chain (prior: #3537/#3543/#3546 codegen, #3551 wildcard, #3733 hasOwnProperty SIGBUS). No version bump / changelog — folded in at merge per the worktree-PR convention.

)

`Object.hasOwn` worked as a direct call but not as a first-class value:
`typeof Object.hasOwn` folded to "function", yet reading the value (e.g. to
store it in a variable and call it indirectly) produced the `0.0`/undefined
sentinel — a non-callable. The const-alias call-fold (`const k = Object.hasOwn;
k(o, x)`) hid this, but iconv-lite's `merge-exports.js` assigns it through a
ternary into a `var`:

    var hasOwn = typeof Object.hasOwn === "undefined"
      ? Function.call.bind(Object.prototype.hasOwnProperty)
      : Object.hasOwn
    ...
    if (hasOwn(module, key)) target[key] = module[key]

The ternary defeats the fold, so `hasOwn` held a non-callable and
`hasOwn(module, key)` threw `TypeError: value is not a function` during
iconv-lite init — a hard blocker compiling Express (#3527).

Two parts:
- Runtime: reify `Object.hasOwn` as a callable static on the `Object`
  constructor closure (`object_hasown_thunk` → `js_object_has_own`), alongside
  the existing `keys`/`values`/`assign`/… thunks. This makes the rebound form
  (`const O = Object; O.hasOwn(o, x)`) dispatch correctly.
- Codegen: when `Object.hasOwn` is read as a VALUE (`PropertyGet { GlobalGet(0),
  "hasOwn" }`), resolve the reified `Object` constructor closure and read the
  `hasOwn` static off it — mirroring the existing `Error.captureStackTrace`
  value path — instead of emitting the `0.0` sentinel.

Now `typeof Object.hasOwn === "function"`, and the value computes correctly
whether called directly, stored in a var, passed through an array, or rebound.
The Express compile advances past iconv-lite's merge-exports. Coverage added to
test_gap_object_methods.ts (byte-identical to Node).

The broader "Object.keys/values/… as escaping values" gap is unchanged and
remains a known limitation; this fixes the ES2022 `hasOwn` case that Express's
dependency tree actually exercises.
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.

1 participant