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
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.