feat(runtime): .constructor on Date/Array/Object instances#973
Merged
Conversation
Date-fns 4.x threw `RangeError: Invalid time value` because
`constructFrom(date, value)` clones a Date via `new
date.constructor(value)` and Perry returned `undefined` from
`date.constructor`. The downstream `new undefined(...)` constructed
an empty placeholder and `cloned.getTime()` read garbage.
Three coordinated changes so `inst.constructor` reads and bare
`Date`/`Array`/`Object` identifiers resolve to the same closure
pointer, and `new <inst.constructor>(...)` dispatches to the real
factory:
- HIR: bare built-in idents lower to `PropertyGet { GlobalGet(0),
name }` so they route through the existing globalThis singleton
closure path.
- Runtime: `js_object_get_field_by_name(_f64)` returns the
appropriate global constructor for Array/Object/Date receivers
(Date is recognized via `is_registered_date_bits`); anon-shape
classes (synthetic `__AnonShape_*` for object literals) report
`Object`.
- Runtime: `js_new_function_construct` identifies the singleton
built-in closures by their stable `func_ptr` (GC-evac-safe) and
dispatches to `js_date_new_*` / `js_array_alloc` / `js_object_alloc`.
- Codegen: PropertyGet's invalid-receiver fall-through now calls the
runtime helper so raw-f64 Date receivers reach the Date arm.
NewDynamic with `PropertyGet { ... }` callee routes through
`js_new_function_construct`. Statically-typed Date receivers
shortcut to `Expr::DateNew` for the hot path.
Validation: `test-files/test_constructor_property.ts` (Date / Array /
Object constructor identity + `new <inst.constructor>(...)` clone)
passes byte-for-byte vs Node. date-fns
`format(new Date(...), 'yyyy-MM-dd')` no longer trips
`RangeError: Invalid time value`; the next blocker is an `undefined.map`
inside date-fns's `normalizeDates` chain — a separate downstream gap.
This was referenced May 18, 2026
Merged
proggeramlug
added a commit
that referenced
this pull request
May 18, 2026
…1009) PR #973 lowered bare built-in idents (`Promise`, `Array`, `Date`, ...) as `PropertyGet { GlobalGet(0), name }` so they route through the globalThis singleton closure path. Two codegen call sites that specialize `.then()` dispatch were still pattern-matching the legacy `Expr::GlobalGet(_)` shape only: - `type_analysis::is_promise_expr` for `Promise.resolve/reject/all/race/ allSettled/any(...)` and `Array.fromAsync(...)`. - `lower_call.rs`'s fused `Promise.resolve(x).then(cb)` fast path that routes to `js_promise_resolved_then`. When `is_promise_expr` returned false, `.then(cb)` fell through to the generic native method dispatch which doesn't enqueue the callback — microtask-02..07 and edge-promises went silent in compile-smoke's Native no-fallback gate, and on Linux V8 surfaced the same shape as `TypeError: then is not a function`. The Native gate had been red on every PR since #973 (admin-bypassed on #997 / #1000 / #1003 / #1004). Extract `type_analysis::is_global_builtin_named(expr, name)` that matches both shapes (legacy `GlobalGet(_)` and the post-#973 `PropertyGet { GlobalGet(0), name }`) and route both call sites through it. Validation: `scripts/run_native_no_fallback_tests.sh` — 35 passed, 0 failed (was 28/7 pre-fix).
proggeramlug
pushed a commit
that referenced
this pull request
May 18, 2026
… (regression from #973) `Number.parseFloat === parseFloat` (and every built-in wrapper-constructor static accessed as a member value) regressed to `false`; Node/spec is `true`. Also regressed the core gap test test_gap_number_math vs node --experimental-strip-types. Bisected to 5ddccbb (feat(runtime): .constructor property on Date/Array/Object instances, #973): its HIR arm rewrites bare built-in idents used as VALUES to PropertyGet{GlobalGet(0),name} for `inst.constructor === Date` identity. That fired in member-OBJECT position too, so `Number.parseFloat` resolved via globalThis.Number rather than the intrinsic static. Fix in expr_member.rs: after lowering the member object, revert #973's reroute to the intrinsic GlobalGet(0) when it fired on a member-object built-in ident. Bare-ident-as-value (#973's feature) untouched; local shadowing inherently safe (shadowing local lowers to LocalGet).
proggeramlug
added a commit
that referenced
this pull request
May 18, 2026
… (regression from #973) (#1007) `Number.parseFloat === parseFloat` (and every built-in wrapper-constructor static accessed as a member value) regressed to `false`; Node/spec is `true`. Also regressed the core gap test test_gap_number_math vs node --experimental-strip-types. Bisected to 5ddccbb (feat(runtime): .constructor property on Date/Array/Object instances, #973): its HIR arm rewrites bare built-in idents used as VALUES to PropertyGet{GlobalGet(0),name} for `inst.constructor === Date` identity. That fired in member-OBJECT position too, so `Number.parseFloat` resolved via globalThis.Number rather than the intrinsic static. Fix in expr_member.rs: after lowering the member object, revert #973's reroute to the intrinsic GlobalGet(0) when it fired on a member-object built-in ident. Bare-ident-as-value (#973's feature) untouched; local shadowing inherently safe (shadowing local lowers to LocalGet). Co-authored-by: Ralph Kuepper <ralph@skelpo.com>
This was referenced May 18, 2026
Merged
4 tasks
proggeramlug
added a commit
that referenced
this pull request
May 28, 2026
… property values (#2142) (#2176) Extends #2058/#2077 to the remaining built-in prototypes (`Array`/`Date`/`RegExp`/`String`/`Promise`/`Map`/`Set`/`Error`/typed arrays/etc.). Before this change, the typeof-fold made `typeof Array.prototype.map === "function"` pass at AST level, but any INDIRECT read — storing into a local, an array literal, or otherwise hiding the value behind a variable — returned `undefined`. The issue's repro (`for ([n,v] of [[ ... , Array.prototype.map], ...])`) hit exactly that gap. Two coordinated changes: 1. HIR (`expr_member.rs`) — broaden the #2060 typed-array-only collapse exemption. The #973 builtin-ident reroute collapsed `<Ctor>.prototype` to `globalThis.prototype` (which is undefined, not the constructor's real prototype). Typed arrays already exempted themselves so the per-kind proto with the `length` / `byteLength` / `byteOffset` / `buffer` accessor descriptors stayed reachable; the same logic applies to every built-in proto, so the exemption now fires unconditionally when the outer member is `.prototype`. 2. Runtime (`global_this.rs`) — extend `populate_builtin_prototype_methods` to install named callable closures for every well-known method on `Array`/`Object`/`Function`/`String`/`Number`/`Boolean`/`Date`/ `RegExp`/`Promise`/`Map`/`Set`/`WeakMap`/`WeakSet`/`Error*`/typed arrays. Each closure is backed by the shared `noop_thunk` so `typeof === "function"`, `.name` reads the method name, and `.length` reads the spec arity — covering Test262's `verifyProperty` / `assert.throws` introspection paths. `Array.prototype.slice` and `Object.prototype.toString` keep their dedicated thunks (ramda's curry/variadic helpers depend on `Array.prototype.slice.call(args,…)` returning a real sliced array even via indirection, and ramda's `_isArguments.js` IIFE calls `Object.prototype.toString.call(arguments)` at module-init time). The hot-path calls — `arr.map(fn)` (codegen NativeMethodCall) and `Array.prototype.map.call(arr, fn)` (HIR rewrite via `try_builtin_prototype_method_apply_call`) — are unaffected. Adds `test-files/test_issue_2142_builtin_proto_method_values.ts`, byte-identical to `node --experimental-strip-types`. Per #2142, this is the single biggest remaining cascade in Test262's `built-ins/{Object,Array,TypedArray,Date,RegExp,Function}` categories after #2058/#2059/#2060/#2063 — the `(no output)` / `reading 'name'` / `reading 'constructor'` cascades all collapse on the same prototype- method-value gap. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
This was referenced Jun 2, 2026
proggeramlug
added a commit
that referenced
this pull request
Jun 5, 2026
#4622) Two bugs made the dotted static reads broken: 1. The #973/#4139 reroute-undo collapsed value reads of Date.now/parse/UTC to GlobalGet(0).<name>, for which codegen has no intrinsic handler (unlike Object.keys / Math.max), so they mis-folded to a number (typeof 'number'). Add a Date now/parse/UTC exception to the reroute-undo so the read hits the reified Date constructor object (the aliased/computed forms already did). Direct calls (Date.now()) stay on the Expr::DateNow fast path (unaffected). 2. date_utc_static collects all args into its rest param, but Date.UTC was registered with call-arity 7 (7 fixed params before rest), so Date.UTC(2020,0) put nothing in the rest array → js_date_utc([]) → NaN. Register call-arity 0 (all args → rest) while keeping spec .length = 7. Now byte-identical to node: typeof Date.now/parse/UTC === 'function', correct .name/.length, value-calls (const u=Date.UTC; u(2020,0)) work, direct calls unchanged. Fixes test262 Date/{now,parse,UTC}/{name,length}, Date/now/15.9.4.4-0-1/3. Co-authored-by: Ralph Küpper <ralph@skelpo.com>
This was referenced Jun 5, 2026
proggeramlug
added a commit
that referenced
this pull request
Jun 5, 2026
…nction values (#4631) Same reroute-undo issue as Date.now (#4622) / Array.isArray (#4626). All six Number statics (isFinite/isInteger/isNaN/isSafeInteger/parseFloat/parseInt) are reified with metadata via install_constructor_static, but the #973/#4139 reroute-undo collapsed value reads to GlobalGet(0).<name>, whose intrinsic path dropped the metadata for isInteger/isSafeInteger (.name/.length undefined). Add Number to the reified-static reroute-undo exception. The already-named siblings (isFinite/isNaN/parseFloat/parseInt) are also reified, so routing them to the reified receiver preserves their names; direct calls keep the intrinsic fast path via the !member_is_call_callee gate. Byte-identical to node: Number.isInteger.name==='isInteger'/.length===1, isSafeInteger likewise; value-calls and direct calls work. Fixes test262 Number/{isInteger,isSafeInteger}/{name,length}. (String.fromCharCode/etc. are not reified yet — tracked in #4627.) Co-authored-by: Ralph Küpper <ralph@skelpo.com>
proggeramlug
added a commit
that referenced
this pull request
Jun 5, 2026
… function values (#4634) Completes the static-function-values series (#4622 Date, #4626 Array, #4631 Number). Unlike those, String.fromCharCode/fromCodePoint were not reified at all, so this both reifies them and wires the reroute-undo exception: - New js_string_from_code_point_array runtime helper (variadic fromCodePoint with per-element RangeError validation; lone surrogates → U+FFFD). - Reify fromCharCode/fromCodePoint via install_constructor_static_with_call_arity (call-arity 0 = all args into rest; spec .length 1) — fromCharCode reuses the existing js_string_from_char_code_array. - Explicit String fromCharCode/fromCodePoint exception in the #973/#4139 reroute-undo (NOT the whole namespace — String.raw is not reified and stays on its intrinsic path, unaffected). Byte-identical to node: typeof/name/length, value-calls (incl. astral code points), direct calls, RangeError on invalid code points; String.raw unchanged. Fixes test262 String/{fromCharCode,fromCodePoint}/{name,length}. (String.raw .name /.length tracked in #4627.) Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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
RangeError: Invalid time valueon the very firstformat(...)call becauseconstructFrom(date, value)doesnew date.constructor(value)and Perry returnedundefinedfromdate.constructor. Now bothinst.constructorreads and bareDate/Array/Objectidentifiers resolve to the same singleton closure pointer.identify_global_builtin_constructorshort-circuit injs_new_function_constructdispatchesnew <inst.constructor>(...)callsites to the realjs_date_new_*/js_array_alloc/js_object_allocfactories (matches by stableClosureHeader.func_ptrso it survives GC evacuation of the singleton closures).PropertyGetinvalid-receiver fall-through now routes through the runtime helper so raw-f64 Date receivers can reach the new Date.constructorarm.NewDynamicwith aPropertyGetcallee now reachesjs_new_function_construct; statically-typed Date receivers shortcut toExpr::DateNewfor the hot path.Date,Array,Object, …) now lower toPropertyGet { GlobalGet(0), <name> }so they route through the existing globalThis singleton path. Decorator-arg recogniser extended for the new shape.Test plan
test-files/test_constructor_property.ts— typeof / identity-equality /new <inst.constructor>(ts)round-trip for Date, Array, Object: 7/7 byte-for-byte vs Node.test-files/test_gap_closures.ts— no regression.test-files/test_gap_object_methods.ts— no regression.format(new Date(2024, 0, 15), 'yyyy-MM-dd')undercompilePackages: ["date-fns"]) no longer throwsRangeError: Invalid time value. Next downstream blocker is anundefined.mapin date-fns'snormalizeDates— separate gap, not in scope for this PR.