Skip to content

fix(runtime/hir/intl): #5800 — fix 29 Temporal/Intl regressions from #5789/#5793/#5783 - #5806

Merged
proggeramlug merged 2 commits into
mainfrom
fix/5800-temporal-regressions
Jun 29, 2026
Merged

fix(runtime/hir/intl): #5800 — fix 29 Temporal/Intl regressions from #5789/#5793/#5783#5806
proggeramlug merged 2 commits into
mainfrom
fix/5800-temporal-regressions

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the 29 newly-failing test262 cases introduced by the overnight batch (#5789, #5793, #5783), grouped into three clusters.

Cluster A — 12 tests (Temporal/**/from/order-of-operations.js)

temporal_subclass_cell in field_get_set.rs used is_valid_obj_ptr to guard against non-heap pointers. On Linux (HEAP_MIN=0x1000) revocable Proxy ids in [0xF0000, 0x100000) pass that guard, and reading the GC header at proxy_id − 8 segfaulted. Fixed by switching to is_plausible_heap_addr, which unconditionally rejects the entire handle band [0, 0x100000).

Cluster B — 16 tests (Temporal toLocaleString regressions)

Two sub-issues introduced by #5789:

  1. Wrong per-type defaults: fix(temporal): preserve locale/options in toLocaleString, fix DTF-compatible defaults (#5580) #5789 unified all non-ZDT Temporal types to {year, month, day} defaults. Restored ECMA-402 spec defaults: PlainDate→{y,m,d}, PlainDateTime/Instant/ZDT→{y,m,d,h,min,sec}, PlainTime→{h,min,sec}, PlainYearMonth→{y,m}, PlainMonthDay→{m,d}.

  2. Date args-dropping divergence: Tests comparing Date.toLocaleString(locale, opts) with a Temporal type now diverge because fix(temporal): preserve locale/options in toLocaleString, fix DTF-compatible defaults (#5580) #5789 fixed Temporal args-dropping but left Date on the HIR fold path (dropping args). Added a real Date.prototype.toLocaleString thunk (date_to_locale_string_opts) that delegates to temporal_locale_string with PlainDateTime defaults. Removed recv_class == Some("Date") from the HIR fold so Date calls with args use the thunk.

Cluster C — 1 test (intl402/NumberFormat/constructor-order.js)

NumberFormat({style:"currency", unit:"test"}) threw RangeError (from the stricter is_well_formed_unit_identifier added in #5783 rejecting "test") before the TypeError for the missing currency field. Moved the currency TypeError check ahead of the unit reads to restore spec-mandated observable order.

Test plan

  • cargo test --release -p perry-runtime -p perry-hir -p perry-codegen -p perry-transform — all pass
  • CI: lint, cargo-test, api-docs-drift, security-audit green

Closes #5800

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved Date.prototype.toLocaleString() behavior so calls with locale and options no longer take an incorrect fast-path and instead preserve provided arguments.
    • Enhanced default date/time component selection for Temporal-style toLocaleString formatting when no options are supplied.
  • Bug Fixes

    • Fixed currency formatting error reporting by validating missing currency earlier for currency style.
    • Improved internal receiver validation to prevent unsafe handling of invalid heap addresses in Temporal-related lookups.

…5789/#5793/#5783

Cluster A (12 tests): `temporal_subclass_cell` crashed on Linux when
processing Proxy ids because the old guard (`obj >= GC_HEADER_SIZE +
0x1000`) passes proxy ids (0xF0000–0xFFFFF) on Linux (HEAP_MIN=0x1000).
Switch to `is_plausible_heap_addr` which unconditionally rejects the
entire handle band [0, 0x100000).

Cluster B (16 tests): `temporal_locale_string` defaults regression from
#5789. Restore per-type ECMA-402 defaults: PlainDate→{y,m,d},
PlainDateTime/Instant/ZDT→{y,m,d,h,min,sec}, PlainTime→{h,min,sec},
PlainYearMonth→{y,m}, PlainMonthDay→{m,d}.

Tests comparing `Date.toLocaleString(locale, opts)` with a Temporal type
newly diverged because #5789 fixed Temporal args-dropping but left Date
on the fold path. Install a real `Date.prototype.toLocaleString` thunk
(`date_to_locale_string_opts`) that delegates to `temporal_locale_string`
with `PlainDateTime` defaults. Remove the `recv_class == Some("Date")`
shortcut from the HIR fold so Date calls with args also use the thunk.

Cluster C (1 test): `NumberFormat({style:"currency",unit:"test"})` threw
RangeError (from the stricter `is_well_formed_unit_identifier` added in
#5783) before the TypeError for the missing `currency` field. Move the
currency TypeError check ahead of the unit reads to restore spec order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 30ffdeaa-82b3-43a1-8784-be8edf267a60

📥 Commits

Reviewing files that changed from the base of the PR and between 028e09c and 344098d.

📒 Files selected for processing (1)
  • crates/perry-runtime/src/intl/number_format_options.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-runtime/src/intl/number_format_options.rs

📝 Walkthrough

Walkthrough

Adds a Date.prototype.toLocaleString runtime thunk and installer, updates Temporal locale default field selection, tightens Date.toLocaleString HIR folding, moves currency validation earlier, and changes temporal subclass receiver validation.

Changes

Date/Temporal locale and Intl fixes

Layer / File(s) Summary
temporal_subclass_cell guard
crates/perry-runtime/src/object/field_get_set.rs
Replaces the early receiver pointer check with is_plausible_heap_addr and keeps the GC header and temporal value validation flow unchanged.
Date.prototype.toLocaleString thunk and wiring
crates/perry-runtime/src/object/date_proto_thunks.rs, crates/perry-runtime/src/object/global_this/proto_methods.rs
Adds a Date.prototype.toLocaleString thunk that brand-checks this, handles NaN epoch values, reads locales and options from rest, delegates to temporal_locale_string, and installs it on Date.prototype.
Temporal locale defaults by context
crates/perry-runtime/src/intl/date_collator.rs
Reworks the no-options branch in temporal_locale_string to use explicit per-TemporalLocaleCtx default field mappings for PlainDate, PlainDateTime, Instant, ZonedDateTime, PlainTime, PlainYearMonth, and PlainMonthDay.
Date.toLocaleString fold condition
crates/perry-hir/src/lower/expr_call/url_date_instance.rs
Changes the DateToLocaleString fast-path fold to trigger only when args is empty, so non-empty locale/options calls use the generic method path.
Currency validation order
crates/perry-runtime/src/intl/number_format_options.rs
Moves the missing-currency TypeError for style="currency" earlier in configure_number_format and removes the later duplicate check.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PerryTS/perry#5789: Changes the same Date.toLocaleString lowering and Temporal locale formatting paths.
  • PerryTS/perry#5765: Also rewrites temporal_locale_string and TemporalLocaleCtx handling in the same runtime area.
  • PerryTS/perry#5793: Touches the same temporal subclass / internal-slot machinery as the field_get_set.rs guard update.

Poem

🐇 Hop went the bunny through locale light,
Date strings now tumble out just right.
Early the currency error appears,
Proxy paths wobble? The guard conquers fears.
Temporal fields now choose their dress,
And this rabbit approves with a happy yes!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR's main goal: fixing Temporal/Intl regressions from #5789/#5793/#5783.
Description check ✅ Passed It includes a clear summary, test plan, related issue reference, and checklist; only the explicit Changes section is missing.
Linked Issues check ✅ Passed The changes address all three linked clusters: Temporal order-of-operations, Temporal.toLocaleString defaults/Date behavior, and NumberFormat error order.
Out of Scope Changes check ✅ Passed The added Date thunk and Intl/HIR changes are all tied to the linked Temporal/Intl regressions, with no obvious unrelated edits.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/5800-temporal-regressions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/perry-runtime/src/object/field_get_set.rs (1)

75-79: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Use the validated GC-header reader here.

is_plausible_heap_addr fixes proxy-id rejection, but this still manually dereferences obj - GC_HEADER_SIZE. The canonical try_read_gc_header also rejects small-buffer slab addresses before reading the header, avoiding fake-header routing on heap-plausible non-GC allocations.

Proposed fix
-    if !crate::value::addr_class::is_plausible_heap_addr(obj) {
+    let Some(gc_header) = (unsafe { crate::value::addr_class::try_read_gc_header(obj) }) else {
         return None;
-    }
-    let gc_header = (obj as *const u8).sub(crate::gc::GC_HEADER_SIZE) as *const crate::gc::GcHeader;
-    if (*gc_header).obj_type != crate::gc::GC_TYPE_OBJECT {
+    };
+    if gc_header.obj_type != crate::gc::GC_TYPE_OBJECT {
         return None;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/object/field_get_set.rs` around lines 75 - 79, The
GC header check in field_get_set should use the validated header reader instead
of manually subtracting GC_HEADER_SIZE and dereferencing the header. Update the
logic around the object-type validation to call try_read_gc_header (or the
equivalent canonical helper) after is_plausible_heap_addr, and keep the existing
GC_TYPE_OBJECT gate on the returned header. This ensures slab/small-buffer
non-GC allocations are rejected before any header access while preserving the
current field access flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/intl/number_format_options.rs`:
- Around line 87-94: Move the missing-currency TypeError check in
number_format_options::... so it runs immediately after reading currency and
before any access to currencyDisplay or currencySign. The current guard in the
currency style branch is still too late because the getter reads can throw
first; update the option-processing order in the currency-handling path so the
early throw happens before any other currency-related option validation or
property access.

In `@crates/perry-runtime/src/object/date_proto_thunks.rs`:
- Around line 381-408: The new Date.prototype.toLocaleString options path
forwards options, but the formatting pipeline still ignores timeZone and
timeZoneName, so Date output stays in the host default zone. Update the Intl
formatting flow in temporal_locale_string/date_collator.rs to thread these
options through the component extraction and formatting logic, and make sure
Date.prototype.toLocaleString via date_to_locale_string_opts produces output in
the requested zone instead of only rejecting timeZone for
Temporal.ZonedDateTime.

---

Outside diff comments:
In `@crates/perry-runtime/src/object/field_get_set.rs`:
- Around line 75-79: The GC header check in field_get_set should use the
validated header reader instead of manually subtracting GC_HEADER_SIZE and
dereferencing the header. Update the logic around the object-type validation to
call try_read_gc_header (or the equivalent canonical helper) after
is_plausible_heap_addr, and keep the existing GC_TYPE_OBJECT gate on the
returned header. This ensures slab/small-buffer non-GC allocations are rejected
before any header access while preserving the current field access flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f924f943-e96d-4ca3-9f77-da08662eec22

📥 Commits

Reviewing files that changed from the base of the PR and between 4962196 and 028e09c.

📒 Files selected for processing (6)
  • crates/perry-hir/src/lower/expr_call/url_date_instance.rs
  • crates/perry-runtime/src/intl/date_collator.rs
  • crates/perry-runtime/src/intl/number_format_options.rs
  • crates/perry-runtime/src/object/date_proto_thunks.rs
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/object/global_this/proto_methods.rs

Comment thread crates/perry-runtime/src/intl/number_format_options.rs Outdated
Comment thread crates/perry-runtime/src/object/date_proto_thunks.rs
…rencySign reads

Per CodeRabbit: the TypeError for `{ style: "currency" }` with no currency
was firing after the `currencyDisplay` and `currencySign` GetOption calls.
A proxy trap on `get currencyDisplay()` would throw a different error first,
breaking the spec-mandated observable read order.  Move the check to
immediately after the `currency` read.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

REGRESSION: Temporal order-of-operations (12 no-output) + Temporal.toLocaleString edge cases (16) from overnight Temporal/Intl batch

1 participant