Skip to content

fix(date): ECMAScript ToNumber for Date arguments (Symbol/BigInt throw, single valueOf)#4393

Merged
proggeramlug merged 1 commit into
mainfrom
fix-date-tonumber
Jun 4, 2026
Merged

fix(date): ECMAScript ToNumber for Date arguments (Symbol/BigInt throw, single valueOf)#4393
proggeramlug merged 1 commit into
mainfrom
fix-date-tonumber

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

Date argument coercion bypassed ECMAScript ToNumber: for any non-string / non-sentinel value the helpers read the raw NaN-boxed bits as a double, so a Symbol/object/array/boolean produced garbage or an Invalid Date instead of throwing or coercing.

Before → after (vs node --experimental-strip-types)

new Date(Symbol())        Invalid Date   →  TypeError          ✓
d.setHours(Symbol())      (no throw)      →  TypeError          ✓
Date.UTC(2020, Symbol())  (no throw)      →  TypeError          ✓
new Date(true)            Invalid Date   →  1                  ✓
new Date(2020, {valueOf}) valueOf 0×     →  valueOf exactly 1× ✓

Fix

Routes the shared jsvalue_to_number helper, the new Date(v) numeric branch, and the multi-arg new Date(y,m,d,…) coerce through one ECMAScript ToNumber:

  • Symbol / BigIntTypeError (not numerically convertible);
  • Date → its time value;
  • object / array → a single valueOf/toString via OrdinaryToPrimitive(number) (so valueOf is invoked exactly once), then the primitive numeric coercion;
  • string / boolean / null / undefined → the existing ordinary coercion.

All in date.rs (no new file; under the line cap).

Results

test262 built-ins/Date: 202 → 158 (44 fixed, no regressions), measured uncapped via scripts/parity_gap_report.py. Verified Node-identical in both PERRY_NO_AUTO_OPTIMIZE=1 and default builds. cargo test -p perry-runtime --lib: 979 passed, 0 failed.

The remaining Date failures are separate root causes (a follow-up will add Date.prototype method this brand-checks, ~52 cases; plus arg-vs-date-value read ordering and toISOString RangeError edges).

…w, single valueOf)

Date argument coercion (the `new Date(v)` numeric path, `Date.UTC`, the
multi-arg `new Date(y,m,d,...)` constructor, and `Date.prototype.set*`
components) read raw NaN-boxed bits for any non-string/non-sentinel value,
so a Symbol/object/array/boolean produced garbage or an Invalid Date
instead of following ToNumber:

- `new Date(Symbol())` / `d.setHours(Symbol())` / `Date.UTC(2020, Symbol())`
  now throw TypeError (Symbol and BigInt are not numerically convertible).
- objects run a single `valueOf`/`toString` via OrdinaryToPrimitive(number)
  (`new Date(2020, {valueOf(){...}})` — valueOf invoked exactly once).
- booleans/null coerce numerically (`new Date(true)` → 1).

Routes the shared `jsvalue_to_number` helper, the constructor's numeric
branch, and the multi-arg constructor's `coerce` through one ToNumber that
handles Symbol/BigInt (throw), Date (time value), and object/array
(OrdinaryToPrimitive) before the primitive numeric coercion.

test262 built-ins/Date: 202 -> 158 (44 fixed, no regressions). Verified
Node-identical in both PERRY_NO_AUTO_OPTIMIZE and default builds.
@proggeramlug proggeramlug merged commit 786cec8 into main Jun 4, 2026
12 checks passed
@proggeramlug proggeramlug deleted the fix-date-tonumber branch June 4, 2026 12:31
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