Skip to content

fix(intl): built-in Intl functions have no own prototype (#5904) - #6707

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/5904-intl-numberformat
Jul 20, 2026
Merged

proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/5904-intl-numberformat

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part of the intl402/NumberFormat worklist (#5904). Fixes the largest coherent single-root subcluster: built-in Intl functions must not have an own prototype property and must not be constructable.

The Intl.NumberFormat method/getter/bound functions — the format accessor, the bound [[BoundFormat]] function, resolvedOptions, formatToParts, formatRange, formatRangeToParts, and the static supportedLocalesOf — were created via js_closure_alloc without being flagged non-constructable. That let function_would_have_own_prototype lazily synthesize a .prototype object for them and left them dynamically new-able, unlike ordinary built-ins (Math.max, Array.prototype.map) which correctly report hasOwnProperty("prototype") === false and isConstructor === false.

Per ECMA-262 §17, a built-in function only gets the auto-created .prototype when it implements [[Construct]]. The fix marks the closures created by the three shared Intl installation helpers as non-constructable — the same flag the native-module builtin methods already carry:

  • install_function — prototype methods (formatToParts/resolvedOptions/…), the static supportedLocalesOf, and the this-based instance methods (formatRange/formatRangeToParts).
  • install_bound_instance_function — the bound instance methods (format/formatToParts/resolvedOptions).
  • the accessor-getter path in install_constructor — the get …prototype.format accessor.

The constructor closures themselves are untouched, so new Intl.NumberFormat() and Reflect.construct(Intl.X, args, Custom) still work.

Tests fixed (test262, intl402/NumberFormat)

prototype/format/builtin.js
prototype/format/format-function-builtin.js
prototype/formatRange/builtin.js
prototype/formatRangeToParts/builtin.js
prototype/resolvedOptions/builtin.js
supportedLocalesOf/builtin.js

Verification

Same-methodology A/B on the pinned test262 slice (PERRY_NO_AUTO_OPTIMIZE=1, one perry binary, base commit differing only by this edit):

  • intl402/NumberFormat: 217 → 223 pass, 31 → 25 runtime-fail. The 6 fixed cases are exactly the builtin.js cluster above.

  • Full intl402 slice: 3100 → 3120 pass, 228 → 208 runtime-fail, 0 → 0 diff, 2 → 2 compile-fail — 20 cases fixed, zero regressions. Because the fix lives in the shared installation helpers, the same class of assertion now passes across every Intl service:

    NumberFormat   6  (format builtin, format-function-builtin, formatRange,
                       formatRangeToParts, resolvedOptions, supportedLocalesOf)
    DateTimeFormat 6  (format, format-function, formatRange, formatRangeToParts,
                       resolvedOptions, supportedLocalesOf)
    Collator       3  (compare-function-builtin, resolvedOptions, supportedLocalesOf)
    PluralRules    2  (resolvedOptions, supportedLocalesOf)
    DurationFormat 2  (format not-a-constructor, formatToParts not-a-constructor)
    Intl           1  (supportedValuesOf builtin)
    

Added a per-PR-visible gap test (test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts) that asserts the full ECMA-262 §17 surface ([object Function] tag, extensibility, [[Prototype]] === Function.prototype, no own prototype, not a constructor) for the NumberFormat functions plus DateTimeFormat.prototype.resolvedOptions and Collator.supportedLocalesOf; byte-identical to node --experimental-strip-types.

Code-only PR (no version bump / CHANGELOG / CLAUDE.md edit) per the worklist instructions.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected Intl built-in functions so they no longer expose an own prototype property when they are not constructable.
    • Aligned constructability behavior across Intl formatters, prototype methods, accessors, and locale utilities.
    • Preserved normal functionality for formatting, resolved options, and locale-related operations.
  • Tests

    • Added regression coverage for Intl function descriptors, constructability, and runtime behavior.

)

The Intl method/getter/bound functions were created via `js_closure_alloc`
without being flagged non-constructable, so `function_would_have_own_prototype`
lazily synthesized a `.prototype` object for them and left them dynamically
`new`-able — unlike ordinary built-ins (`Math.max`, `Array.prototype.map`),
which correctly report `hasOwnProperty("prototype") === false` and
`isConstructor === false`.

ECMA-262 §17: a built-in function only gets the auto-created `.prototype` when
it implements [[Construct]]. Mark the closures built by the three shared Intl
installation helpers — `install_function` (prototype methods, the static
`supportedLocalesOf`, and the this-based `formatRange`/`formatRangeToParts`),
`install_bound_instance_function` (bound instance methods), and the
accessor-getter path in `install_constructor` (the `get …format` accessor) —
non-constructable, the same flag the native-module builtin methods carry. The
constructor closures themselves are untouched.

Fixes 20 test262 intl402 cases across every Intl service (6 NumberFormat, 6
DateTimeFormat, 3 Collator, 2 PluralRules, 2 DurationFormat, 1
Intl.supportedValuesOf) with zero regressions on the intl402 slice
(3100 -> 3120 pass). Adds a gap test asserting the ECMA-262 §17 function
surface, byte-identical to node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Intl builtin installation now marks non-constructor functions as non-constructable, preventing own prototype properties. A regression test checks this behavior across bound methods, accessors, static methods, related Intl services, and constructors.

Changes

Intl builtin constructability

Layer / File(s) Summary
Mark Intl builtin closures
crates/perry-runtime/src/intl.rs, crates/perry-runtime/src/intl/install.rs
Bound methods, shared builtin functions, and Intl prototype accessor getters are marked non-constructable.
Verify Intl builtin behavior
test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts
Tests confirm non-constructor Intl functions lack own prototype properties while remaining callable, and constructors retain constructability.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the core fix: Intl built-in functions no longer expose an own prototype.
Description check ✅ Passed The description is detailed and covers the fix, affected Intl APIs, tests, and verification, even though some template sections are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🧹 Nitpick comments (1)
test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts (1)

1-6: 🎯 Functional Correctness | 🔵 Trivial

Run the gap suite with Node 26.5.0. The pinned runtime in .node-version is 26.5.0, and the current Node is v24.15.0.

🤖 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 `@test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts` around lines 1 - 6,
Run the gap suite for test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts
using Node 26.5.0, matching the version pinned in .node-version; do not use the
current Node v24.15.0.

Source: Coding guidelines

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

Nitpick comments:
In `@test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts`:
- Around line 1-6: Run the gap suite for
test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts using Node 26.5.0,
matching the version pinned in .node-version; do not use the current Node
v24.15.0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5241fa30-5829-4da0-8e65-2cfcccc7139c

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd379f and 8e9d031.

📒 Files selected for processing (3)
  • crates/perry-runtime/src/intl.rs
  • crates/perry-runtime/src/intl/install.rs
  • test-files/test_gap_intl_builtin_fn_no_prototype_5904.ts

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai. On the Node-version nitpick: the gap test only exercises ECMA-262 §17 built-in-function invariants ([object Function] tag, extensibility, [[Prototype]] === Function.prototype, absence of an own prototype, isConstructor) plus two locale-stable en-US outputs (1,234.5, en-US). None of these are Node-version-sensitive — they're identical across 24.x/26.x. I validated it byte-for-byte against local Node 26.3.0, and conformance-smoke runs it against the pinned 26.5.0 (.node-version), which is the authoritative gate here. No code change needed.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@proggeramlug Thanks for the detailed clarification. Agreed—the assertions are specification-level invariants plus locale-stable en-US results, and the pinned Node 26.5.0 conformance-smoke gate is authoritative. The Node-version concern does not apply here; no change is needed.

🐇 ✓


✏️ Learnings added
Learnt from: proggeramlug
URL: https://github.com/PerryTS/perry/pull/6707

Timestamp: 2026-07-20T06:53:58.022Z
Learning: For the Intl built-in-function regression gap test, assertions cover ECMA-262 §17 function invariants and locale-stable `en-US` outputs only. Its relevant conformance gate is `conformance-smoke`, which runs under the Node version pinned in `.node-version` (Node 26.5.0 for PR `#6707`); do not flag separate Node 24.x/26.x compatibility concerns for this test without evidence of version-sensitive behavior.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@proggeramlug
proggeramlug merged commit cba3afa into PerryTS:main Jul 20, 2026
22 of 27 checks passed
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