Repository navigation
fix(runtime,codegen): make Object.hasOwn a real first-class value (#3527) - #3751
Merged
Merged
Conversation
) `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.
This was referenced May 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Object.hasOwn(ES2022) worked as a direct call but not as a first-class value:typeof Object.hasOwnfolded to"function", yet reading the value produced the0.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'smerge-exports.jsassigns it through a ternary into avar:The ternary defeats the fold, so
hasOwnheld a non-callable andhasOwn(module, key)threwTypeError: value is not a functionduring iconv-lite init — a hard blocker compiling Express. (Localized via a debug-symbol build +b print_uncaught→ backtrace pointing atmerge-exports.js.)Fix (two parts)
global_this.rs): reifyObject.hasOwnas a callable static on theObjectconstructor closure (object_hasown_thunk→ existingjs_object_has_own), alongside the existingkeys/values/assign/… thunks. Makes the rebound form (const O = Object; O.hasOwn(o, x)) dispatch correctly.property_get.rs): whenObject.hasOwnis read as a VALUE (PropertyGet { GlobalGet(0), "hasOwn" }), resolve the reifiedObjectconstructor closure and read thehasOwnstatic off it — mirroring the existingError.captureStackTracevalue path — instead of emitting the0.0sentinel.Verification
typeof Object.hasOwn === "function"; the value computes correctly whether called directly, stored in avarvia a ternary, passed through an array, or rebound (const O = Object).test_gap_object_methods.tsextended with the as-value cases — byte-identical tonode --experimental-strip-types.Object.hasOwn(o,k),Object.keys, andtypeof Error.isErrorall still correct.cargo fmt --checkclean.Scope
The broader "
Object.keys/values/… as escaping values" gap (a pre-existing known limitation) is unchanged; this fixes the ES2022hasOwncase 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.