Skip to content

fix(runtime): #1187 implement Date.prototype local-time setters#1189

Merged
proggeramlug merged 2 commits into
mainfrom
fix/1187-date-mutators
May 20, 2026
Merged

fix(runtime): #1187 implement Date.prototype local-time setters#1189
proggeramlug merged 2 commits into
mainfrom
fix/1187-date-mutators

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

  • Closes Date.prototype.setHours/setDate/etc. throw '(number).setHours is not a function' at runtime #1187. d.setHours(d.getHours() + 1) aborted with TypeError: (number).setHours is not a function because the HIR lowering only routed setUTC* — the local-time setters fell through to a generic property lookup, and since Date is stored as a raw f64 timestamp the receiver became (number).
  • Adds the local-time setters end-to-end: 8 new js_date_set_* runtime functions (setFullYear/setMonth/setDate/setHours/setMinutes/setSeconds/setMilliseconds/setTime), 8 matching DateSet* HIR variants (walker / stable_hash / lowering), and lowering arms in the LLVM, JS, and WASM backends. Mirrors the existing DateSetUtc* plumbing.
  • Mutation semantics match JS: when the receiver is a local (e.g. let d = new Date(); d.setHours(15)), the HIR wraps the setter result in LocalSet so d is updated in place and the expression still evaluates to the new ms timestamp.
  • Component setters round-trip through timestamp_to_local_components, swap the requested field, and subtract the tz offset at that instant to land back at UTC — same conversion as js_date_new_local_components. NaN passes through (Invalid Date stays Invalid Date); finite results re-register in DATE_REGISTRY so instanceof Date still holds after mutation.

Test plan

  • test-files/test_issue_1187_date_setters.ts (new) compiles + matches node --experimental-strip-types byte-for-byte
  • Exact repro from the issue (new Date(); expiry.setHours(expiry.getHours()+1); return expiry; ... .toISOString()) compiles and prints a valid ISO string ~1 hour from now
  • test-files/test_gap_date_methods.ts still parity-matches Node
  • cargo test -p perry-runtime -p perry-hir -p perry-codegen green
  • CI: lint, cargo-test, parity, compile-smoke, api-docs-drift, security-audit

Version bump + CHANGELOG.md entry intentionally omitted — Ralph folds those in at merge time.

`d.setHours(d.getHours() + 1)` threw `TypeError: (number).setHours is
not a function` because the HIR lowering only routed `setUTC*` to
dedicated `DateSetUtc*` variants. Plain `setHours` / `setDate` / etc.
fell through to the generic property-on-receiver path, and since Date
is stored as a raw f64 timestamp with no method table the receiver
became `(number)` and the call failed.

Adds the local-time setters end-to-end:

- Runtime: 8 new `js_date_set_*` functions in date.rs. The 7 component
  setters share a `rebuild_local_with` helper that round-trips through
  `timestamp_to_local_components`, swaps the requested component, and
  subtracts the tz offset at that instant to land back at UTC — same
  conversion as `js_date_new_local_components`. NaN passes through so
  Invalid Date stays Invalid Date; finite results re-register in
  DATE_REGISTRY so `instanceof Date` still holds after the mutation.
  `js_date_set_time` is one-shot — replace, register, return.
- HIR: 8 new `DateSet*` Expr variants mirroring the existing UTC ones,
  walker visits, stable_hash tags 478–485.
- HIR lowering: extended the existing setUTC* match arm to also catch
  `setFullYear` / `setMonth` / `setDate` / `setHours` / `setMinutes` /
  `setSeconds` / `setMilliseconds` / `setTime`. Same in-place mutation
  pattern — if the receiver is a `LocalGet`, wrap in `LocalSet` so
  `d.setHours(...)` writes the new timestamp back into `d`.
- LLVM codegen + runtime_decls: 8 new lowering arms and decls.
- JS / WASM backends: mirror arms so the new variants flow through
  every backend, not just LLVM.

Regression repro lives in `test-files/test_issue_1187_date_setters.ts`
and matches Node byte-for-byte.
@proggeramlug proggeramlug merged commit 8f9f076 into main May 20, 2026
9 checks passed
@proggeramlug proggeramlug deleted the fix/1187-date-mutators branch May 20, 2026 15:57
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.

Date.prototype.setHours/setDate/etc. throw '(number).setHours is not a function' at runtime

1 participant