Skip to content

Add ECMA-402 Intl namespace with ICU-first data strategy (#542)#590

Merged
frostney merged 17 commits into
mainfrom
issue-542-intl-namespace
May 10, 2026
Merged

Add ECMA-402 Intl namespace with ICU-first data strategy (#542)#590
frostney merged 17 commits into
mainfrom
issue-542-intl-namespace

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented May 8, 2026

Summary

  • Full implementation of the ECMA-402 Internationalization API: Intl.Locale, Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules, Intl.RelativeTimeFormat, Intl.ListFormat, Intl.DisplayNames, Intl.Segmenter, Intl.DurationFormat, plus Intl.getCanonicalLocales, Intl.supportedValuesOf, and supportedLocalesOf on all constructors.
  • Data strategy mirrors Temporal timezone: system ICU library (macOS libicucore, Linux libicui18n, Windows icu.dll shared with Temporal) as primary, embedded CLDR resource as fallback. Cross-platform ICU handle extracted to source/shared/ICU.pas — Temporal refactored to use the shared handle instead of private DLL loading.
  • Shared units in source/shared/ (ICU.pas, BCP47.pas, IntlTypes.pas, IntlICU.pas, IntlCLDRData.pas, IntlLocaleResolver.pas) provide platform-independent Intl infrastructure with pure Pascal types. Engine units in source/units/ (Goccia.Builtins.Intl.pas + 10 Goccia.Values.Intl*.pas value types) bridge to the GocciaScript object model.
  • Existing locale stubs on String.prototype (localeCompare, toLocaleUpperCase, toLocaleLowerCase) wired to ICU — always uses locale-aware casing with NUL-safe output via explicit-length Move instead of PAnsiChar.
  • NUL-poisoned strings rejected across all constructors, option values, locale tags, and supportedLocalesOf per ECMA-402 (fixes test262 staging/sm/extensions/quote-string-for-nul-character.js).
  • BCP 47 parser validates RFC 5646 structural rules: rejects empty subtags, duplicate variants, duplicate singletons, lone x without subtags, and NUL characters.
  • NumberFormat validates required currency/unit options and digit range constraints per spec.
  • Collator sensitivity correctly maps to ICU strength levels with UCOL_CASE_LEVEL for case sensitivity.
  • Compiler flag GOCCIA_INTL_EMBEDDED_CLDR / GOCCIA_INTL_NO_EMBEDDED_CLDR follows the exact Temporal pattern.
  • Follow-up conformance gaps tracked in Intl: resolvedOptions() missing required fields across all constructors #595Intl: Locale info methods missing (getCalendars, getCollations, getWeekInfo, etc.) #606.
  • Closes Engine: ECMA-402 / Intl namespace not implemented (242 test262 tests) #542

Testing

  • Verified no regressions and confirmed the new feature or bugfix in end-to-end JavaScript/TypeScript tests
    • 119 new Intl tests (19 test files, including NUL validation) — all pass
    • Full suite: 9106/9111 pass (5 pre-existing FFI fixture failures)
    • Both interpreter and bytecode modes verified
  • Updated documentation
    • New docs/built-ins-intl.md with full API reference
    • Updated docs/built-ins.md, docs/build-system.md, docs/language-tables.md, docs/test262.md, docs/decision-log.md
    • Updated scripts/test262_compatibility_roadmap.json — Intl status changed from excluded-by-language-design to implemented
  • Optional: Verified no regressions and confirmed the new feature or bugfix in native Pascal tests (if AST, scope, evaluator, or value types changed)
  • Optional: Verified no benchmark regressions or confirmed benchmark coverage for the change

Full implementation of the ECMA-402 Internationalization API: Intl.Locale,
Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules,
Intl.RelativeTimeFormat, Intl.ListFormat, Intl.DisplayNames, Intl.Segmenter,
Intl.DurationFormat, plus Intl.getCanonicalLocales and Intl.supportedValuesOf.

Data strategy mirrors Temporal timezone: system ICU library (macOS libicucore,
Linux libicui18n, Windows icu.dll shared with Temporal) as primary, embedded
CLDR resource as fallback. Shared units in source/shared/ (ICU.pas, BCP47.pas,
IntlTypes.pas, IntlICU.pas, IntlCLDRData.pas, IntlLocaleResolver.pas) provide
platform-independent Intl infrastructure. Temporal refactored to use shared ICU
handle. Existing locale stubs on String.prototype wired to ICU formatters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
gocciascript-homepage Ignored Ignored Preview May 10, 2026 2:19pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e493ef18-eb89-405a-a883-8c9acfe6fd97

📥 Commits

Reviewing files that changed from the base of the PR and between fe78ae9 and ff812b6.

📒 Files selected for processing (4)
  • source/units/Goccia.Values.IntlCollator.pas
  • source/units/Goccia.Values.IntlDisplayNames.pas
  • source/units/Goccia.Values.IntlNumberFormat.pas
  • source/units/Goccia.Values.IntlSegmenter.pas
🚧 Files skipped from review as they are similar to previous changes (4)
  • source/units/Goccia.Values.IntlDisplayNames.pas
  • source/units/Goccia.Values.IntlCollator.pas
  • source/units/Goccia.Values.IntlSegmenter.pas
  • source/units/Goccia.Values.IntlNumberFormat.pas

📝 Walkthrough

Walkthrough

Adds full ECMA-402 Intl: ICU loader/operations, embedded CLDR generator and resource, BCP47 and locale resolver, TGoccia Intl builtin and value types, String locale methods, Temporal ICU refactor, build/system docs, and tests.

Changes

Intl Implementation

Layer / File(s) Summary
Docs
docs/*.md
Adds Intl built-ins documentation, build-system notes for generated CLDR resource, decision log entry, language table update, and test262 guidance.
CLDR Generator & Roadmap
scripts/generate-intl-data.js, scripts/test262_compatibility_roadmap.json
New CLI script to download/extract CLDR, pack binary resource and emit .pas/.res; roadmap and test harness updated to mark Intl implemented.
Types & Parsing
source/shared/IntlTypes.pas, source/shared/BCP47.pas, source/shared/EmbeddedResourceReader.pas
New Intl shared types/enums/defaults, BCP47 parsing/canonicalization, and embedded-resource byte helpers.
ICU Loader & Ops
source/shared/ICU.pas, source/shared/IntlICU.pas
Cross-platform lazy ICU loader and ICU-backed Try* APIs for locale canonicalization, collation, number/date formatting, plural, display names, segmentation, list/relative-time, and casing.
CLDR Lookup & Resolver
source/units/Goccia.Intl.CLDRData.pas, source/shared/IntlLocaleResolver.pas, source/units/Goccia.Error.Messages.pas
Unit to read embedded CLDR resource sections (when embedded), locale canonicalization, lookup/best-fit matching, and Intl error messages.
Build Define
source/units/Goccia.inc
Adds GOCCIA_INTL_EMBEDDED_CLDR controlled by GOCCIA_INTL_NO_EMBEDDED_CLDR.
Engine & Builtin
source/units/Goccia.Builtins.Intl.pas, source/units/Goccia.Engine.pas, source/units/Goccia.Builtins.TestingLibrary.pas
New TGocciaIntlBuiltin registering Intl namespace/constructors, engine wiring and lifecycle, and testing-library matcher extension.
Intl Values
source/units/Goccia.Values.Intl*.pas
Implements Intl value types: Locale, Collator, NumberFormat, DateTimeFormat, PluralRules, RelativeTimeFormat, ListFormat, DisplayNames, Segmenter (+Segments/Iterator), DurationFormat; ICU-first with CLDR fallbacks.
Helpers & String Methods
source/units/Goccia.Intl.Helpers.pas, source/units/Goccia.Values.StringObjectValue.pas
Helpers for format-part conversion and validated option reads; String.prototype locale-aware case/compare that prefer ICU.
Temporal / TZ Refactor
source/units/Goccia.Temporal.TimeZone*.pas
Refactors Windows ICU calendar binding to use shared ICU loader; caches embedded TZ resource bytes.
Tests
tests/built-ins/Intl/*, tests/built-ins/String/*
Adds extensive tests for constructors, methods, format/parts, getCanonicalLocales, supportedValuesOf, NUL validation, and string locale behavior.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Suite Timing

Test Runner (interpreted: 9,214 passed; bytecode: 9,214 passed)
Metric Interpreted Bytecode
Total 9214 9214
Passed 9214 ✅ 9214 ✅
Workers 4 4
Test Duration 2.20s 2.20s
Lex (cumulative) 255.2ms 172.2ms
Parse (cumulative) 265.6ms 273.1ms
Compile (cumulative) 611.3ms
Execute (cumulative) 2.54s 2.08s
Engine Total (cumulative) 3.06s 3.13s
Lex (avg/worker) 63.8ms 43.0ms
Parse (avg/worker) 66.4ms 68.3ms
Compile (avg/worker) 152.8ms
Execute (avg/worker) 633.8ms 519.1ms
Engine Total (avg/worker) 764.0ms 783.2ms

Memory

GC rows aggregate the main thread plus all worker thread-local GCs. Test runner worker shutdown frees thread-local heaps in bulk; that shutdown reclamation is not counted as GC collections or collected objects.

Metric Interpreted Bytecode
GC Live 244.29 MiB 238.73 MiB
GC Peak Live 244.29 MiB 238.73 MiB
GC Allocated During Run 248.19 MiB 242.62 MiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 1 1
GC Collected Objects 88 88
Heap Start Allocated 149.3 KiB 149.3 KiB
Heap End Allocated 2.54 MiB 2.54 MiB
Heap Delta Allocated 2.39 MiB 2.39 MiB
Heap Delta Free 140.1 KiB 140.1 KiB
Benchmarks (interpreted: 407; bytecode: 407)
Metric Interpreted Bytecode
Total 407 407
Workers 4 4
Duration 2.52min 2.42min

Memory

GC rows aggregate the main thread plus all worker thread-local GCs. Benchmark runner performs explicit between-file collections, so collection and collected-object counts can be much higher than the test runner.

Metric Interpreted Bytecode
GC Live 3.65 MiB 3.64 MiB
GC Peak Live 100.32 MiB 80.87 MiB
GC Allocated During Run 15.24 GiB 10.47 GiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 2,822 2,643
GC Collected Objects 282,811,941 247,514,565
Heap Start Allocated 2.29 MiB 2.29 MiB
Heap End Allocated 2.29 MiB 2.29 MiB
Heap Delta Allocated 128 B 128 B

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Benchmark Results

407 benchmarks

Interpreted: 🟢 187 improved · 🔴 25 regressed · 195 unchanged · avg +4.0%
Bytecode: 🟢 268 improved · 🔴 42 regressed · 97 unchanged · avg +5.6%

arraybuffer.js — Interp: 🟢 10, 4 unch. · avg +8.8% · Bytecode: 🟢 10, 🔴 1, 3 unch. · avg +3.3%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 155,610 ops/sec [113,726..165,251] → 177,590 ops/sec [134,741..179,473] ~ overlap (+14.1%) 202,507 ops/sec [187,718..214,513] → 218,823 ops/sec [211,604..220,367] ~ overlap (+8.1%)
create ArrayBuffer(64) 162,684 ops/sec [159,944..163,959] → 171,635 ops/sec [170,936..174,405] 🟢 +5.5% 200,192 ops/sec [199,280..200,703] → 209,190 ops/sec [208,279..212,353] 🟢 +4.5%
create ArrayBuffer(1024) 138,174 ops/sec [119,714..141,125] → 146,315 ops/sec [145,092..148,249] 🟢 +5.9% 173,877 ops/sec [173,819..174,064] → 173,757 ops/sec [172,189..174,800] ~ overlap (-0.1%)
create ArrayBuffer(8192) 75,419 ops/sec [74,703..76,429] → 76,189 ops/sec [75,548..77,032] ~ overlap (+1.0%) 98,133 ops/sec [96,678..98,588] → 83,733 ops/sec [82,736..84,218] 🔴 -14.7%
slice full buffer (64 bytes) 194,095 ops/sec [187,817..210,915] → 204,011 ops/sec [189,839..205,140] ~ overlap (+5.1%) 248,547 ops/sec [245,007..249,710] → 257,460 ops/sec [253,025..258,170] 🟢 +3.6%
slice half buffer (512 of 1024 bytes) 187,226 ops/sec [176,284..189,930] → 180,318 ops/sec [178,410..181,737] ~ overlap (-3.7%) 223,284 ops/sec [221,661..224,434] → 233,291 ops/sec [229,389..234,730] 🟢 +4.5%
slice with negative indices 161,073 ops/sec [111,350..163,237] → 180,423 ops/sec [178,122..182,260] 🟢 +12.0% 235,242 ops/sec [232,526..238,973] → 247,379 ops/sec [246,059..248,490] 🟢 +5.2%
slice empty range 184,199 ops/sec [183,084..186,463] → 204,889 ops/sec [203,361..206,465] 🟢 +11.2% 242,798 ops/sec [239,987..247,177] → 254,582 ops/sec [249,306..259,170] 🟢 +4.9%
byteLength access 409,068 ops/sec [407,197..410,882] → 473,392 ops/sec [466,400..482,412] 🟢 +15.7% 467,335 ops/sec [460,428..470,868] → 527,012 ops/sec [522,892..528,978] 🟢 +12.8%
Symbol.toStringTag access 337,651 ops/sec [335,729..339,553] → 389,394 ops/sec [382,412..391,945] 🟢 +15.3% 339,390 ops/sec [335,864..341,305] → 361,435 ops/sec [354,883..367,037] 🟢 +6.5%
ArrayBuffer.isView 254,264 ops/sec [248,937..258,621] → 287,247 ops/sec [262,991..290,089] 🟢 +13.0% 309,245 ops/sec [308,980..313,984] → 331,220 ops/sec [323,162..336,304] 🟢 +7.1%
clone ArrayBuffer(64) 173,959 ops/sec [171,569..178,656] → 192,164 ops/sec [191,524..192,625] 🟢 +10.5% 223,086 ops/sec [222,433..224,160] → 227,850 ops/sec [227,069..228,113] 🟢 +2.1%
clone ArrayBuffer(1024) 147,772 ops/sec [143,838..150,168] → 159,929 ops/sec [158,014..161,229] 🟢 +8.2% 191,497 ops/sec [191,240..193,021] → 188,312 ops/sec [183,585..191,599] ~ overlap (-1.7%)
clone ArrayBuffer inside object 121,803 ops/sec [120,049..123,540] → 133,622 ops/sec [132,264..134,908] 🟢 +9.7% 147,666 ops/sec [144,787..148,585] → 152,877 ops/sec [150,919..154,088] 🟢 +3.5%
arrays.js — Interp: 🟢 10, 9 unch. · avg +4.6% · Bytecode: 🟢 18, 1 unch. · avg +9.3%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 3,660 ops/sec [2,828..4,006] → 4,176 ops/sec [4,060..4,245] 🟢 +14.1% 6,457 ops/sec [6,216..6,699] → 7,226 ops/sec [7,110..7,426] 🟢 +11.9%
Array.from 10 elements 92,483 ops/sec [88,128..93,271] → 98,898 ops/sec [97,726..98,962] 🟢 +6.9% 97,927 ops/sec [96,834..98,249] → 103,718 ops/sec [102,730..105,684] 🟢 +5.9%
Array.of 10 elements 112,939 ops/sec [112,070..114,258] → 120,642 ops/sec [117,232..123,484] 🟢 +6.8% 127,251 ops/sec [126,098..127,867] → 132,393 ops/sec [130,356..133,988] 🟢 +4.0%
spread into new array 132,844 ops/sec [132,131..133,711] → 147,221 ops/sec [141,527..150,042] 🟢 +10.8% 83,384 ops/sec [81,941..84,572] → 82,058 ops/sec [79,467..83,744] ~ overlap (-1.6%)
map over 50 elements 6,952 ops/sec [6,812..6,986] → 7,307 ops/sec [7,228..7,989] 🟢 +5.1% 12,256 ops/sec [11,365..12,484] → 12,888 ops/sec [12,625..13,003] 🟢 +5.2%
filter over 50 elements 6,648 ops/sec [6,569..7,529] → 6,918 ops/sec [6,865..6,991] ~ overlap (+4.1%) 11,775 ops/sec [11,369..11,840] → 12,099 ops/sec [12,042..13,057] 🟢 +2.7%
reduce sum 50 elements 7,235 ops/sec [7,194..7,315] → 7,490 ops/sec [7,289..7,640] ~ overlap (+3.5%) 11,302 ops/sec [11,180..11,453] → 13,606 ops/sec [13,551..13,679] 🟢 +20.4%
forEach over 50 elements 6,526 ops/sec [6,397..6,689] → 6,545 ops/sec [6,412..6,746] ~ overlap (+0.3%) 12,395 ops/sec [12,210..12,519] → 14,672 ops/sec [14,607..14,733] 🟢 +18.4%
find in 50 elements 9,673 ops/sec [9,362..9,755] → 9,614 ops/sec [8,996..9,693] ~ overlap (-0.6%) 17,438 ops/sec [17,354..17,460] → 20,290 ops/sec [20,190..20,374] 🟢 +16.4%
sort 20 elements 3,670 ops/sec [3,648..3,699] → 3,754 ops/sec [3,597..3,791] ~ overlap (+2.3%) 6,916 ops/sec [6,869..6,934] → 8,000 ops/sec [7,920..8,082] 🟢 +15.7%
flat nested array 48,850 ops/sec [48,398..49,343] → 51,452 ops/sec [50,800..52,178] 🟢 +5.3% 53,814 ops/sec [53,406..54,001] → 59,045 ops/sec [58,676..59,483] 🟢 +9.7%
flatMap 26,955 ops/sec [26,657..27,371] → 28,518 ops/sec [28,390..29,286] 🟢 +5.8% 36,406 ops/sec [36,217..36,485] → 39,921 ops/sec [39,732..40,118] 🟢 +9.7%
map inside map (5x5) 7,189 ops/sec [7,126..7,227] → 7,599 ops/sec [7,580..7,619] 🟢 +5.7% 10,481 ops/sec [10,445..10,561] → 11,451 ops/sec [11,399..11,654] 🟢 +9.3%
filter inside map (5x10) 5,109 ops/sec [5,053..5,174] → 5,307 ops/sec [5,088..5,407] ~ overlap (+3.9%) 8,466 ops/sec [8,433..8,510] → 9,017 ops/sec [8,677..9,113] 🟢 +6.5%
reduce inside map (5x10) 5,859 ops/sec [5,778..5,920] → 5,958 ops/sec [5,820..6,099] ~ overlap (+1.7%) 8,993 ops/sec [8,917..9,009] → 9,609 ops/sec [9,534..9,676] 🟢 +6.8%
forEach inside forEach (5x10) 5,205 ops/sec [5,146..5,247] → 5,370 ops/sec [5,051..5,492] ~ overlap (+3.2%) 9,747 ops/sec [9,633..9,782] → 10,409 ops/sec [10,311..10,607] 🟢 +6.8%
find inside some (10x10) 4,205 ops/sec [4,119..4,261] → 4,325 ops/sec [4,285..4,468] 🟢 +2.9% 6,997 ops/sec [6,960..7,069] → 7,891 ops/sec [7,871..7,923] 🟢 +12.8%
map+filter chain nested (5x20) 1,554 ops/sec [1,542..1,579] → 1,616 ops/sec [1,580..1,633] 🟢 +4.0% 2,615 ops/sec [2,605..2,623] → 2,851 ops/sec [2,823..2,855] 🟢 +9.0%
reduce flatten (10x5) 16,322 ops/sec [16,088..16,401] → 16,704 ops/sec [15,338..17,321] ~ overlap (+2.3%) 7,414 ops/sec [7,346..7,650] → 7,918 ops/sec [7,824..7,924] 🟢 +6.8%
async-await.js — Interp: 🟢 3, 3 unch. · avg +6.1% · Bytecode: 🟢 3, 3 unch. · avg +2.3%
Benchmark Interpreted Δ Bytecode Δ
single await 129,613 ops/sec [110,838..147,805] → 153,759 ops/sec [119,664..159,243] ~ overlap (+18.6%) 160,043 ops/sec [117,695..163,325] → 163,822 ops/sec [128,061..167,939] ~ overlap (+2.4%)
multiple awaits 70,758 ops/sec [70,498..70,992] → 73,801 ops/sec [73,344..74,069] 🟢 +4.3% 69,540 ops/sec [69,348..70,790] → 71,913 ops/sec [71,396..73,203] 🟢 +3.4%
await non-Promise value 289,558 ops/sec [283,791..296,514] → 298,045 ops/sec [289,196..300,182] ~ overlap (+2.9%) 444,049 ops/sec [443,274..445,910] → 443,618 ops/sec [441,181..447,898] ~ overlap (-0.1%)
await with try/catch 121,610 ops/sec [120,441..122,815] → 126,399 ops/sec [121,353..127,264] ~ overlap (+3.9%) 157,417 ops/sec [155,348..160,672] → 159,094 ops/sec [155,031..164,947] ~ overlap (+1.1%)
await Promise.all 24,120 ops/sec [23,899..24,248] → 25,104 ops/sec [24,575..25,486] 🟢 +4.1% 22,952 ops/sec [22,553..23,028] → 24,004 ops/sec [23,746..24,254] 🟢 +4.6%
nested async function call 79,070 ops/sec [76,943..79,892] → 81,131 ops/sec [80,182..82,945] 🟢 +2.6% 99,567 ops/sec [98,809..99,732] → 102,278 ops/sec [101,205..102,601] 🟢 +2.7%
async-generators.js — Interp: 🟢 1, 1 unch. · avg +5.4% · Bytecode: 🟢 1, 1 unch. · avg +2.8%
Benchmark Interpreted Δ Bytecode Δ
for-await-of over async generator 2,326 ops/sec [1,537..2,387] → 2,553 ops/sec [2,508..2,569] 🟢 +9.8% 2,908 ops/sec [2,864..2,931] → 2,977 ops/sec [2,847..2,988] ~ overlap (+2.4%)
async generator with await in body 23,160 ops/sec [23,023..23,311] → 23,390 ops/sec [23,233..24,536] ~ overlap (+1.0%) 24,171 ops/sec [23,942..24,298] → 24,961 ops/sec [24,848..25,215] 🟢 +3.3%
base64.js — Interp: 🟢 8, 2 unch. · avg +2.5% · Bytecode: 🟢 2, 🔴 7, 1 unch. · avg -3.8%
Benchmark Interpreted Δ Bytecode Δ
short ASCII (13 chars) 3,970 ops/sec [3,853..4,004] → 4,073 ops/sec [4,036..4,123] 🟢 +2.6% 3,982 ops/sec [3,588..4,035] → 4,132 ops/sec [4,071..4,195] 🟢 +3.8%
medium ASCII (450 chars) 145 ops/sec [144..147] → 152 ops/sec [150..155] 🟢 +5.1% 150 ops/sec [148..153] → 153 ops/sec [150..154] ~ overlap (+2.0%)
Latin-1 characters 5,669 ops/sec [5,642..5,687] → 5,920 ops/sec [5,786..6,210] 🟢 +4.4% 5,807 ops/sec [5,740..5,896] → 6,077 ops/sec [5,947..6,099] 🟢 +4.6%
short base64 (20 chars) 660 ops/sec [644..663] → 675 ops/sec [670..683] 🟢 +2.3% 739 ops/sec [735..741] → 688 ops/sec [665..698] 🔴 -6.8%
medium base64 (600 chars) 24 ops/sec [24..24] → 25 ops/sec [24..25] 🟢 +2.5% 27 ops/sec [27..27] → 25 ops/sec [24..25] 🔴 -8.0%
Latin-1 output 1,034 ops/sec [1,019..1,044] → 1,049 ops/sec [1,045..1,066] 🟢 +1.5% 1,133 ops/sec [1,130..1,141] → 1,064 ops/sec [1,051..1,076] 🔴 -6.1%
forgiving (no padding) 1,651 ops/sec [1,631..1,654] → 1,661 ops/sec [1,645..1,668] ~ overlap (+0.6%) 1,791 ops/sec [1,785..1,795] → 1,703 ops/sec [1,688..1,722] 🔴 -4.9%
with whitespace 632 ops/sec [610..640] → 639 ops/sec [629..651] ~ overlap (+1.0%) 692 ops/sec [685..698] → 640 ops/sec [629..657] 🔴 -7.4%
atob(btoa(short)) 563 ops/sec [563..566] → 584 ops/sec [581..587] 🟢 +3.6% 625 ops/sec [616..632] → 573 ops/sec [570..576] 🔴 -8.3%
atob(btoa(medium)) 21 ops/sec [21..21] → 21 ops/sec [21..21] 🟢 +1.5% 23 ops/sec [23..23] → 21 ops/sec [21..21] 🔴 -7.1%
classes.js — Interp: 🟢 7, 🔴 5, 19 unch. · avg +0.4% · Bytecode: 🟢 18, 13 unch. · avg +4.3%
Benchmark Interpreted Δ Bytecode Δ
simple class new 60,363 ops/sec [57,684..60,626] → 58,029 ops/sec [57,461..58,242] ~ overlap (-3.9%) 73,590 ops/sec [72,822..75,355] → 76,576 ops/sec [75,522..77,776] 🟢 +4.1%
class with defaults 49,155 ops/sec [49,062..49,620] → 47,300 ops/sec [47,106..47,812] 🔴 -3.8% 50,892 ops/sec [50,678..51,057] → 54,058 ops/sec [53,956..54,148] 🟢 +6.2%
50 instances via Array.from 2,201 ops/sec [2,165..2,237] → 2,159 ops/sec [2,048..2,168] ~ overlap (-1.9%) 2,727 ops/sec [2,709..2,748] → 2,997 ops/sec [2,960..3,011] 🟢 +9.9%
instance method call 29,477 ops/sec [29,404..29,763] → 28,295 ops/sec [27,520..29,008] 🔴 -4.0% 34,368 ops/sec [34,298..34,379] → 37,965 ops/sec [37,718..38,479] 🟢 +10.5%
static method call 46,602 ops/sec [46,390..47,124] → 44,820 ops/sec [44,271..45,199] 🔴 -3.8% 74,546 ops/sec [73,569..75,315] → 77,478 ops/sec [76,033..78,661] 🟢 +3.9%
single-level inheritance 24,473 ops/sec [24,259..24,702] → 23,843 ops/sec [23,357..24,085] 🔴 -2.6% 27,604 ops/sec [27,563..27,842] → 29,199 ops/sec [28,650..29,398] 🟢 +5.8%
two-level inheritance 21,254 ops/sec [20,860..21,621] → 20,551 ops/sec [20,421..21,074] ~ overlap (-3.3%) 22,030 ops/sec [21,777..22,240] → 22,783 ops/sec [22,590..23,038] 🟢 +3.4%
private field access 32,538 ops/sec [32,130..32,935] → 31,553 ops/sec [31,126..31,933] 🔴 -3.0% 24,648 ops/sec [24,356..24,813] → 26,274 ops/sec [26,005..26,457] 🟢 +6.6%
private methods 33,257 ops/sec [32,826..33,409] → 34,832 ops/sec [34,043..35,638] 🟢 +4.7% 28,513 ops/sec [28,358..28,921] → 30,408 ops/sec [30,217..30,560] 🟢 +6.6%
getter/setter access 30,596 ops/sec [29,944..31,080] → 31,789 ops/sec [31,167..31,951] 🟢 +3.9% 39,762 ops/sec [39,522..39,936] → 41,481 ops/sec [41,111..41,528] 🟢 +4.3%
class decorator (identity) 40,945 ops/sec [40,528..41,778] → 42,865 ops/sec [41,976..43,271] 🟢 +4.7% 46,790 ops/sec [45,941..47,359] → 47,791 ops/sec [46,162..48,431] ~ overlap (+2.1%)
class decorator (wrapping) 24,496 ops/sec [24,281..24,710] → 25,130 ops/sec [24,729..25,510] 🟢 +2.6% 24,556 ops/sec [24,383..24,755] → 25,464 ops/sec [25,092..25,920] 🟢 +3.7%
identity method decorator 30,610 ops/sec [30,314..30,691] → 30,579 ops/sec [30,089..30,755] ~ overlap (-0.1%) 39,011 ops/sec [38,378..39,952] → 40,300 ops/sec [39,198..41,727] ~ overlap (+3.3%)
wrapping method decorator 24,805 ops/sec [24,541..25,188] → 25,565 ops/sec [25,369..25,744] 🟢 +3.1% 29,034 ops/sec [28,015..29,957] → 29,996 ops/sec [29,066..31,164] ~ overlap (+3.3%)
stacked method decorators (x3) 17,205 ops/sec [17,035..17,523] → 17,890 ops/sec [17,297..18,281] ~ overlap (+4.0%) 21,039 ops/sec [20,415..21,281] → 21,231 ops/sec [20,858..22,033] ~ overlap (+0.9%)
identity field decorator 34,822 ops/sec [34,306..35,404] → 34,527 ops/sec [34,057..35,204] ~ overlap (-0.8%) 36,149 ops/sec [35,628..36,915] → 37,680 ops/sec [36,017..39,276] ~ overlap (+4.2%)
field initializer decorator 27,701 ops/sec [27,377..28,030] → 29,135 ops/sec [28,114..30,069] 🟢 +5.2% 31,646 ops/sec [30,855..32,403] → 32,789 ops/sec [32,117..33,717] ~ overlap (+3.6%)
getter decorator (identity) 31,724 ops/sec [31,282..32,463] → 31,804 ops/sec [31,394..32,223] ~ overlap (+0.3%) 30,148 ops/sec [29,710..30,247] → 31,817 ops/sec [31,400..32,456] 🟢 +5.5%
setter decorator (identity) 25,637 ops/sec [25,526..25,777] → 25,799 ops/sec [25,602..25,975] ~ overlap (+0.6%) 23,635 ops/sec [23,082..23,847] → 24,660 ops/sec [24,160..24,878] 🟢 +4.3%
static method decorator 32,147 ops/sec [31,530..33,049] → 32,599 ops/sec [32,303..33,322] ~ overlap (+1.4%) 39,700 ops/sec [38,666..40,952] → 40,795 ops/sec [40,268..41,589] ~ overlap (+2.8%)
static field decorator 39,079 ops/sec [38,289..39,947] → 38,553 ops/sec [36,798..39,545] ~ overlap (-1.3%) 42,685 ops/sec [41,969..43,962] → 46,944 ops/sec [45,300..49,191] 🟢 +10.0%
private method decorator 25,318 ops/sec [25,033..25,682] → 25,524 ops/sec [25,227..26,999] ~ overlap (+0.8%) 29,511 ops/sec [27,814..29,833] → 30,103 ops/sec [29,771..30,976] ~ overlap (+2.0%)
private field decorator 28,253 ops/sec [28,070..28,545] → 28,604 ops/sec [27,514..28,846] ~ overlap (+1.2%) 25,114 ops/sec [23,824..26,176] → 27,449 ops/sec [26,968..28,240] 🟢 +9.3%
plain auto-accessor (no decorator) 47,207 ops/sec [46,513..49,903] → 47,863 ops/sec [47,355..48,309] ~ overlap (+1.4%) 42,201 ops/sec [41,649..43,593] → 43,809 ops/sec [43,189..43,928] ~ overlap (+3.8%)
auto-accessor with decorator 26,737 ops/sec [26,511..28,040] → 27,495 ops/sec [27,288..29,058] ~ overlap (+2.8%) 26,907 ops/sec [26,591..28,355] → 28,095 ops/sec [27,523..29,004] ~ overlap (+4.4%)
decorator writing metadata 21,497 ops/sec [21,125..21,860] → 22,446 ops/sec [22,004..22,516] 🟢 +4.4% 25,289 ops/sec [24,862..25,959] → 24,808 ops/sec [24,541..25,920] ~ overlap (-1.9%)
static getter read 54,965 ops/sec [54,375..55,254] → 54,404 ops/sec [48,638..56,713] ~ overlap (-1.0%) 70,291 ops/sec [70,096..70,448] → 71,870 ops/sec [70,544..72,566] 🟢 +2.2%
static getter/setter pair 40,451 ops/sec [39,902..40,496] → 40,401 ops/sec [40,081..41,010] ~ overlap (-0.1%) 52,844 ops/sec [52,425..54,085] → 53,575 ops/sec [52,152..54,274] ~ overlap (+1.4%)
inherited static getter 32,942 ops/sec [32,832..33,719] → 32,988 ops/sec [31,945..34,019] ~ overlap (+0.1%) 39,751 ops/sec [39,157..39,898] → 40,915 ops/sec [40,562..41,611] 🟢 +2.9%
inherited static setter 36,146 ops/sec [35,958..36,967] → 36,390 ops/sec [35,748..36,583] ~ overlap (+0.7%) 44,452 ops/sec [43,132..45,306] → 43,874 ops/sec [42,766..45,106] ~ overlap (-1.3%)
inherited static getter with this binding 30,090 ops/sec [29,488..30,672] → 30,061 ops/sec [29,817..30,220] ~ overlap (-0.1%) 34,658 ops/sec [34,266..35,160] → 36,293 ops/sec [35,557..36,584] 🟢 +4.7%
closures.js — Interp: 🟢 9, 2 unch. · avg +8.3% · Bytecode: 🟢 9, 2 unch. · avg +6.5%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 46,452 ops/sec [45,230..47,325] → 50,953 ops/sec [50,594..51,504] 🟢 +9.7% 158,930 ops/sec [154,374..163,833] → 168,787 ops/sec [162,650..172,867] ~ overlap (+6.2%)
closure over multiple variables 46,817 ops/sec [43,366..47,452] → 52,446 ops/sec [51,727..53,623] 🟢 +12.0% 142,191 ops/sec [138,463..143,345] → 148,923 ops/sec [145,259..152,686] 🟢 +4.7%
nested closures 51,768 ops/sec [51,502..51,915] → 57,849 ops/sec [57,221..58,807] 🟢 +11.7% 144,717 ops/sec [142,001..145,924] → 148,084 ops/sec [147,129..151,123] 🟢 +2.3%
function as argument 35,374 ops/sec [34,714..35,847] → 39,718 ops/sec [39,398..40,289] 🟢 +12.3% 148,949 ops/sec [145,319..149,966] → 160,806 ops/sec [158,671..165,690] 🟢 +8.0%
function returning function 45,769 ops/sec [44,364..47,323] → 48,002 ops/sec [47,745..48,469] 🟢 +4.9% 169,386 ops/sec [166,733..173,412] → 176,104 ops/sec [174,622..179,439] 🟢 +4.0%
compose two functions 27,659 ops/sec [27,522..28,405] → 29,716 ops/sec [29,347..29,812] 🟢 +7.4% 97,952 ops/sec [94,239..99,016] → 103,140 ops/sec [100,366..106,526] 🟢 +5.3%
fn.call 62,170 ops/sec [61,691..63,228] → 67,511 ops/sec [66,927..67,728] 🟢 +8.6% 93,302 ops/sec [92,186..94,720] → 100,126 ops/sec [98,051..101,059] 🟢 +7.3%
fn.apply 47,886 ops/sec [45,722..49,354] → 52,090 ops/sec [50,407..52,858] 🟢 +8.8% 95,261 ops/sec [94,836..97,750] → 98,165 ops/sec [95,986..100,107] ~ overlap (+3.0%)
fn.bind 58,009 ops/sec [57,556..58,580] → 59,751 ops/sec [58,364..61,530] ~ overlap (+3.0%) 167,695 ops/sec [167,218..169,256] → 175,574 ops/sec [169,485..186,899] 🟢 +4.7%
recursive sum to 50 4,036 ops/sec [3,981..4,122] → 4,314 ops/sec [4,199..4,451] 🟢 +6.9% 19,082 ops/sec [19,037..19,166] → 21,953 ops/sec [19,417..22,553] 🟢 +15.0%
recursive tree traversal 7,455 ops/sec [7,367..7,568] → 7,866 ops/sec [7,323..8,091] ~ overlap (+5.5%) 18,956 ops/sec [18,870..18,961] → 20,959 ops/sec [20,480..21,917] 🟢 +10.6%
collections.js — Interp: 🟢 7, 5 unch. · avg +3.3% · Bytecode: 🟢 3, 🔴 6, 3 unch. · avg -0.3%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 3,090 ops/sec [2,830..3,190] → 3,240 ops/sec [3,232..3,273] 🟢 +4.9% 3,773 ops/sec [3,711..3,821] → 3,852 ops/sec [3,822..3,878] 🟢 +2.1%
has lookup (50 elements) 45,412 ops/sec [44,342..46,900] → 47,649 ops/sec [47,431..47,883] 🟢 +4.9% 54,774 ops/sec [54,477..55,076] → 53,272 ops/sec [51,965..54,175] 🔴 -2.7%
delete elements 25,784 ops/sec [25,224..26,392] → 26,197 ops/sec [26,068..26,396] ~ overlap (+1.6%) 29,028 ops/sec [28,733..29,230] → 27,868 ops/sec [27,673..27,964] 🔴 -4.0%
forEach iteration 5,598 ops/sec [5,491..5,671] → 5,656 ops/sec [5,619..5,770] ~ overlap (+1.0%) 9,254 ops/sec [9,074..9,327] → 10,367 ops/sec [10,257..10,465] 🟢 +12.0%
spread to array 16,174 ops/sec [15,927..16,741] → 17,839 ops/sec [17,429..17,970] 🟢 +10.3% 110,288 ops/sec [109,207..112,414] → 111,877 ops/sec [110,908..113,371] ~ overlap (+1.4%)
deduplicate array 22,154 ops/sec [21,610..22,178] → 22,848 ops/sec [22,506..23,290] 🟢 +3.1% 39,625 ops/sec [39,017..39,882] → 38,774 ops/sec [38,195..39,148] ~ overlap (-2.1%)
set 50 entries 2,364 ops/sec [2,298..2,395] → 2,378 ops/sec [2,356..2,449] ~ overlap (+0.6%) 3,030 ops/sec [2,995..3,063] → 2,943 ops/sec [2,881..2,971] 🔴 -2.9%
get lookup (50 entries) 45,080 ops/sec [44,968..45,184] → 46,133 ops/sec [45,886..46,429] 🟢 +2.3% 52,539 ops/sec [52,071..52,900] → 49,070 ops/sec [48,371..49,715] 🔴 -6.6%
has check 64,414 ops/sec [63,603..64,870] → 67,097 ops/sec [66,394..67,712] 🟢 +4.2% 76,620 ops/sec [76,007..77,987] → 73,013 ops/sec [72,756..73,495] 🔴 -4.7%
delete entries 24,890 ops/sec [24,733..24,917] → 25,346 ops/sec [24,799..25,814] ~ overlap (+1.8%) 28,015 ops/sec [27,752..28,275] → 26,566 ops/sec [26,009..26,893] 🔴 -5.2%
forEach iteration 5,578 ops/sec [5,519..5,608] → 5,747 ops/sec [5,702..5,809] 🟢 +3.0% 9,832 ops/sec [9,613..9,979] → 10,488 ops/sec [10,341..10,929] 🟢 +6.7%
keys/values/entries 4,607 ops/sec [4,565..4,690] → 4,682 ops/sec [4,588..4,752] ~ overlap (+1.6%) 15,644 ops/sec [15,250..15,817] → 16,027 ops/sec [15,632..16,354] ~ overlap (+2.5%)
csv.js — Interp: 🟢 2, 11 unch. · avg +1.5% · Bytecode: 🟢 13 · avg +11.9%
Benchmark Interpreted Δ Bytecode Δ
parse simple 3-column CSV 51,709 ops/sec [50,300..52,626] → 53,737 ops/sec [51,275..54,476] ~ overlap (+3.9%) 50,402 ops/sec [48,935..50,767] → 55,263 ops/sec [54,533..55,389] 🟢 +9.6%
parse 10-row CSV 15,091 ops/sec [14,576..15,607] → 15,102 ops/sec [14,698..15,813] ~ overlap (+0.1%) 13,650 ops/sec [13,588..13,862] → 15,212 ops/sec [14,980..15,665] 🟢 +11.4%
parse 100-row CSV 2,319 ops/sec [2,297..2,371] → 2,329 ops/sec [2,258..2,378] ~ overlap (+0.5%) 2,135 ops/sec [2,121..2,155] → 2,298 ops/sec [2,285..2,309] 🟢 +7.6%
parse CSV with quoted fields 75,913 ops/sec [74,485..78,779] → 77,021 ops/sec [74,921..80,699] ~ overlap (+1.5%) 73,302 ops/sec [72,503..74,905] → 79,145 ops/sec [78,726..79,666] 🟢 +8.0%
parse without headers (array of arrays) 6,190 ops/sec [5,597..6,379] → 6,317 ops/sec [6,043..6,524] ~ overlap (+2.0%) 6,076 ops/sec [6,066..6,100] → 6,353 ops/sec [6,257..6,514] 🟢 +4.6%
parse with semicolon delimiter 10,476 ops/sec [10,283..10,653] → 10,623 ops/sec [10,415..11,063] ~ overlap (+1.4%) 9,969 ops/sec [9,843..10,104] → 10,735 ops/sec [10,697..10,795] 🟢 +7.7%
stringify array of objects 73,172 ops/sec [72,602..74,308] → 75,978 ops/sec [75,520..76,047] 🟢 +3.8% 64,012 ops/sec [63,892..64,355] → 80,108 ops/sec [79,489..80,652] 🟢 +25.1%
stringify array of arrays 27,075 ops/sec [26,950..27,335] → 27,504 ops/sec [26,924..27,929] ~ overlap (+1.6%) 21,825 ops/sec [21,469..21,963] → 27,862 ops/sec [27,700..28,068] 🟢 +27.7%
stringify with values needing escaping 56,245 ops/sec [55,621..57,224] → 57,272 ops/sec [57,134..57,497] ~ overlap (+1.8%) 48,565 ops/sec [47,999..48,636] → 59,262 ops/sec [58,960..59,544] 🟢 +22.0%
reviver converts numbers 1,413 ops/sec [1,396..1,436] → 1,443 ops/sec [1,426..1,458] ~ overlap (+2.1%) 1,550 ops/sec [1,534..1,577] → 1,652 ops/sec [1,612..1,669] 🟢 +6.6%
reviver filters empty to null 11,282 ops/sec [11,020..11,380] → 11,470 ops/sec [11,388..11,544] 🟢 +1.7% 13,716 ops/sec [13,606..13,870] → 14,070 ops/sec [13,980..14,477] 🟢 +2.6%
parse then stringify 9,241 ops/sec [9,029..9,332] → 9,172 ops/sec [7,721..9,591] ~ overlap (-0.8%) 8,369 ops/sec [8,348..8,442] → 9,364 ops/sec [9,257..9,435] 🟢 +11.9%
stringify then parse 9,001 ops/sec [8,875..9,128] → 8,976 ops/sec [8,927..9,050] ~ overlap (-0.3%) 8,168 ops/sec [8,105..8,172] → 9,002 ops/sec [8,879..9,254] 🟢 +10.2%
destructuring.js — Interp: 🟢 14, 8 unch. · avg +4.2% · Bytecode: 🟢 8, 14 unch. · avg +2.7%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 167,122 ops/sec [166,467..167,281] → 181,296 ops/sec [179,284..184,127] 🟢 +8.5% 120,044 ops/sec [119,121..121,344] → 126,441 ops/sec [125,963..127,815] 🟢 +5.3%
with rest element 117,038 ops/sec [105,582..119,364] → 123,284 ops/sec [122,061..124,300] 🟢 +5.3% 89,638 ops/sec [88,882..90,624] → 95,369 ops/sec [92,472..96,884] 🟢 +6.4%
with defaults 169,138 ops/sec [166,222..173,154] → 180,219 ops/sec [174,663..181,258] 🟢 +6.6% 131,676 ops/sec [127,368..134,335] → 132,340 ops/sec [129,203..138,629] ~ overlap (+0.5%)
skip elements 182,864 ops/sec [178,702..188,155] → 188,403 ops/sec [181,009..193,026] ~ overlap (+3.0%) 137,156 ops/sec [135,533..139,611] → 137,839 ops/sec [134,232..141,221] ~ overlap (+0.5%)
nested array destructuring 88,072 ops/sec [86,910..88,769] → 88,768 ops/sec [87,400..89,404] ~ overlap (+0.8%) 46,639 ops/sec [45,020..47,263] → 45,784 ops/sec [44,451..47,328] ~ overlap (-1.8%)
swap variables 205,857 ops/sec [200,123..208,414] → 214,218 ops/sec [209,699..217,686] 🟢 +4.1% 159,203 ops/sec [140,419..165,278] → 163,268 ops/sec [159,336..167,633] ~ overlap (+2.6%)
simple object destructuring 141,374 ops/sec [139,558..143,383] → 151,125 ops/sec [147,061..159,189] 🟢 +6.9% 159,178 ops/sec [157,429..160,407] → 173,428 ops/sec [166,341..175,073] 🟢 +9.0%
with defaults 154,564 ops/sec [153,469..158,412] → 166,945 ops/sec [163,301..172,434] 🟢 +8.0% 207,017 ops/sec [199,285..209,047] → 219,052 ops/sec [218,585..227,585] 🟢 +5.8%
with renaming 152,009 ops/sec [149,884..156,928] → 159,934 ops/sec [157,052..163,135] 🟢 +5.2% 163,684 ops/sec [161,624..167,638] → 161,699 ops/sec [157,393..168,071] ~ overlap (-1.2%)
nested object destructuring 78,432 ops/sec [76,298..79,216] → 78,611 ops/sec [77,588..82,228] ~ overlap (+0.2%) 84,272 ops/sec [82,421..85,238] → 84,774 ops/sec [81,911..86,236] ~ overlap (+0.6%)
rest properties 56,835 ops/sec [55,640..59,292] → 58,656 ops/sec [57,214..60,075] ~ overlap (+3.2%) 79,107 ops/sec [77,340..79,897] → 79,756 ops/sec [78,589..80,961] ~ overlap (+0.8%)
object parameter 42,890 ops/sec [42,245..43,230] → 43,130 ops/sec [39,646..45,836] ~ overlap (+0.6%) 67,894 ops/sec [67,334..67,939] → 70,075 ops/sec [68,718..71,380] 🟢 +3.2%
array parameter 52,981 ops/sec [52,731..53,394] → 55,768 ops/sec [54,996..56,287] 🟢 +5.3% 62,190 ops/sec [61,377..62,673] → 62,353 ops/sec [61,908..62,884] ~ overlap (+0.3%)
mixed destructuring in map 11,992 ops/sec [11,888..12,091] → 12,500 ops/sec [12,249..12,792] 🟢 +4.2% 17,392 ops/sec [17,258..17,494] → 18,457 ops/sec [18,043..18,839] 🟢 +6.1%
forEach with array destructuring 26,714 ops/sec [26,417..27,308] → 28,071 ops/sec [26,485..28,391] ~ overlap (+5.1%) 23,674 ops/sec [23,328..24,366] → 24,190 ops/sec [23,736..24,945] ~ overlap (+2.2%)
map with array destructuring 27,630 ops/sec [27,165..28,184] → 28,787 ops/sec [28,609..28,872] 🟢 +4.2% 22,611 ops/sec [22,289..23,054] → 22,732 ops/sec [22,513..22,931] ~ overlap (+0.5%)
filter with array destructuring 27,708 ops/sec [26,829..28,379] → 29,126 ops/sec [28,878..29,980] 🟢 +5.1% 24,101 ops/sec [23,825..24,730] → 24,340 ops/sec [23,909..24,689] ~ overlap (+1.0%)
reduce with array destructuring 30,696 ops/sec [30,484..30,837] → 31,604 ops/sec [29,326..32,492] ~ overlap (+3.0%) 24,954 ops/sec [24,695..25,465] → 24,754 ops/sec [24,474..25,222] ~ overlap (-0.8%)
map with object destructuring 26,651 ops/sec [26,533..27,175] → 27,964 ops/sec [27,315..29,013] 🟢 +4.9% 39,501 ops/sec [39,119..41,478] → 41,774 ops/sec [40,901..42,811] ~ overlap (+5.8%)
map with nested destructuring 22,894 ops/sec [22,645..23,063] → 23,649 ops/sec [23,559..24,475] 🟢 +3.3% 36,300 ops/sec [34,709..36,664] → 37,741 ops/sec [37,418..37,973] 🟢 +4.0%
map with rest in destructuring 17,246 ops/sec [17,079..17,382] → 17,861 ops/sec [17,724..18,094] 🟢 +3.6% 12,315 ops/sec [12,183..12,577] → 12,569 ops/sec [12,284..12,624] ~ overlap (+2.1%)
map with defaults in destructuring 21,708 ops/sec [20,898..22,394] → 22,247 ops/sec [22,124..22,380] ~ overlap (+2.5%) 28,574 ops/sec [28,536..29,231] → 30,499 ops/sec [30,185..31,712] 🟢 +6.7%
fibonacci.js — Interp: 🟢 5, 3 unch. · avg +3.8% · Bytecode: 🟢 6, 2 unch. · avg +11.0%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 111 ops/sec [110..113] → 115 ops/sec [114..116] 🟢 +3.7% 565 ops/sec [549..573] → 619 ops/sec [601..621] 🟢 +9.6%
recursive fib(20) 10 ops/sec [10..10] → 10 ops/sec [10..11] 🟢 +4.1% 50 ops/sec [48..50] → 56 ops/sec [55..56] 🟢 +12.5%
recursive fib(15) typed 111 ops/sec [109..118] → 117 ops/sec [116..119] ~ overlap (+5.2%) 551 ops/sec [522..562] → 632 ops/sec [622..640] 🟢 +14.6%
recursive fib(20) typed 10 ops/sec [10..10] → 11 ops/sec [10..11] 🟢 +4.1% 50 ops/sec [49..50] → 58 ops/sec [56..58] 🟢 +16.1%
iterative fib(20) via reduce 5,099 ops/sec [5,035..5,155] → 5,124 ops/sec [4,983..5,214] ~ overlap (+0.5%) 9,434 ops/sec [8,901..9,866] → 10,163 ops/sec [9,271..10,360] ~ overlap (+7.7%)
iterator fib(20) 3,784 ops/sec [3,753..3,865] → 3,984 ops/sec [3,909..4,101] 🟢 +5.3% 6,069 ops/sec [6,049..6,099] → 6,904 ops/sec [6,784..6,913] 🟢 +13.8%
iterator fib(20) via Iterator.from + take 5,089 ops/sec [5,065..5,137] → 5,301 ops/sec [5,192..5,464] 🟢 +4.2% 7,050 ops/sec [6,638..7,485] → 7,618 ops/sec [7,307..7,835] ~ overlap (+8.1%)
iterator fib(20) last value via reduce 3,935 ops/sec [3,919..4,003] → 4,067 ops/sec [4,001..4,402] ~ overlap (+3.4%) 5,552 ops/sec [5,223..5,765] → 5,867 ops/sec [5,836..5,963] 🟢 +5.7%
float16array.js — Interp: 🟢 19, 🔴 1, 12 unch. · avg +3.8% · Bytecode: 🟢 11, 🔴 6, 15 unch. · avg -0.8%
Benchmark Interpreted Δ Bytecode Δ
new Float16Array(0) 121,491 ops/sec [120,472..122,641] → 125,620 ops/sec [124,821..127,540] 🟢 +3.4% 145,771 ops/sec [139,775..146,426] → 146,797 ops/sec [145,113..148,216] ~ overlap (+0.7%)
new Float16Array(100) 117,660 ops/sec [115,801..119,166] → 122,278 ops/sec [119,914..124,696] 🟢 +3.9% 139,155 ops/sec [133,246..141,473] → 140,005 ops/sec [137,297..141,120] ~ overlap (+0.6%)
new Float16Array(1000) 97,053 ops/sec [96,135..98,281] → 99,529 ops/sec [98,139..100,746] ~ overlap (+2.6%) 110,099 ops/sec [105,198..117,055] → 112,189 ops/sec [111,184..113,342] ~ overlap (+1.9%)
Float16Array.from([...100]) 5,078 ops/sec [4,988..5,124] → 5,328 ops/sec [4,985..5,504] ~ overlap (+4.9%) 5,145 ops/sec [5,108..5,193] → 5,308 ops/sec [5,246..5,406] 🟢 +3.2%
Float16Array.of(1.5, 2.5, 3.5, 4.5, 5.5) 133,015 ops/sec [131,208..133,411] → 141,891 ops/sec [140,524..142,860] 🟢 +6.7% 112,000 ops/sec [111,382..113,291] → 111,725 ops/sec [111,462..114,936] ~ overlap (-0.2%)
new Float16Array(float64Array) 85,883 ops/sec [84,014..88,485] → 85,877 ops/sec [85,318..85,986] ~ overlap (-0.0%) 89,151 ops/sec [88,624..89,654] → 93,435 ops/sec [92,461..94,952] 🟢 +4.8%
sequential write 100 elements 1,377 ops/sec [1,356..1,440] → 1,449 ops/sec [1,442..1,453] 🟢 +5.2% 4,609 ops/sec [4,556..4,660] → 4,504 ops/sec [4,438..4,560] ~ overlap (-2.3%)
sequential read 100 elements 1,611 ops/sec [1,537..1,628] → 1,635 ops/sec [1,599..1,646] ~ overlap (+1.5%) 6,103 ops/sec [6,009..6,142] → 6,096 ops/sec [5,991..6,151] ~ overlap (-0.1%)
write special values (NaN, Inf, -0) 67,138 ops/sec [65,373..68,291] → 67,883 ops/sec [67,497..69,795] ~ overlap (+1.1%) 125,640 ops/sec [124,768..127,823] → 131,325 ops/sec [130,380..136,260] 🟢 +4.5%
Float16Array write 1,381 ops/sec [1,358..1,401] → 1,434 ops/sec [1,412..1,455] 🟢 +3.9% 4,642 ops/sec [4,580..4,668] → 4,564 ops/sec [4,471..4,643] ~ overlap (-1.7%)
Float32Array write 1,396 ops/sec [1,375..1,411] → 1,479 ops/sec [1,469..1,516] 🟢 +6.0% 4,682 ops/sec [4,613..4,721] → 4,586 ops/sec [4,531..4,596] 🔴 -2.0%
Float64Array write 1,390 ops/sec [1,358..1,426] → 1,416 ops/sec [1,393..1,430] ~ overlap (+1.9%) 4,601 ops/sec [4,592..4,610] → 4,605 ops/sec [4,556..4,663] ~ overlap (+0.1%)
Float16Array read 1,479 ops/sec [1,462..1,492] → 1,590 ops/sec [1,566..1,619] 🟢 +7.5% 5,936 ops/sec [5,860..5,995] → 5,450 ops/sec [5,418..5,488] 🔴 -8.2%
Float32Array read 1,525 ops/sec [1,510..1,543] → 1,579 ops/sec [1,541..1,597] ~ overlap (+3.5%) 6,216 ops/sec [6,108..6,285] → 6,270 ops/sec [6,193..6,411] ~ overlap (+0.9%)
Float64Array read 1,514 ops/sec [1,496..1,532] → 1,579 ops/sec [1,569..1,598] 🟢 +4.3% 6,164 ops/sec [6,111..6,338] → 6,268 ops/sec [6,135..6,362] ~ overlap (+1.7%)
fill(1.5) 24,102 ops/sec [24,032..24,132] → 22,750 ops/sec [22,430..23,256] 🔴 -5.6% 23,569 ops/sec [22,884..23,725] → 22,972 ops/sec [22,766..22,978] ~ overlap (-2.5%)
slice() 84,356 ops/sec [68,624..86,373] → 88,019 ops/sec [86,313..88,316] ~ overlap (+4.3%) 92,454 ops/sec [91,890..92,606] → 96,247 ops/sec [95,681..96,714] 🟢 +4.1%
map(x => x * 2) 2,630 ops/sec [2,606..2,738] → 2,837 ops/sec [2,750..2,865] 🟢 +7.9% 3,854 ops/sec [3,782..3,871] → 3,872 ops/sec [3,803..3,888] ~ overlap (+0.4%)
filter(x => x > 25) 2,697 ops/sec [2,656..2,723] → 2,846 ops/sec [2,807..2,858] 🟢 +5.5% 4,235 ops/sec [4,198..4,343] → 4,369 ops/sec [4,333..4,416] ~ overlap (+3.2%)
reduce (sum) 2,733 ops/sec [2,703..2,783] → 2,842 ops/sec [2,799..2,878] 🟢 +4.0% 3,564 ops/sec [3,532..3,634] → 3,673 ops/sec [3,616..3,699] ~ overlap (+3.0%)
sort() 17,537 ops/sec [17,436..17,610] → 17,590 ops/sec [17,559..17,718] ~ overlap (+0.3%) 23,869 ops/sec [23,757..24,023] → 18,005 ops/sec [17,848..18,122] 🔴 -24.6%
indexOf() 95,385 ops/sec [94,886..96,081] → 98,596 ops/sec [97,346..99,207] 🟢 +3.4% 131,427 ops/sec [130,659..131,843] → 107,403 ops/sec [105,985..110,314] 🔴 -18.3%
reverse() 110,134 ops/sec [108,605..111,415] → 114,994 ops/sec [114,474..115,691] 🟢 +4.4% 118,332 ops/sec [117,378..118,761] → 128,989 ops/sec [128,188..129,278] 🟢 +9.0%
toReversed() 49,766 ops/sec [49,027..49,918] → 49,881 ops/sec [49,646..50,895] ~ overlap (+0.2%) 60,973 ops/sec [60,895..61,022] → 51,610 ops/sec [51,315..52,045] 🔴 -15.4%
toSorted() 690 ops/sec [684..693] → 690 ops/sec [670..698] ~ overlap (-0.1%) 924 ops/sec [909..932] → 690 ops/sec [686..692] 🔴 -25.3%
create view over existing buffer 135,876 ops/sec [134,015..137,432] → 142,602 ops/sec [136,575..145,414] ~ overlap (+4.9%) 159,018 ops/sec [157,955..159,539] → 167,507 ops/sec [166,675..171,920] 🟢 +5.3%
subarray() 188,040 ops/sec [186,282..189,337] → 201,727 ops/sec [199,918..202,950] 🟢 +7.3% 221,343 ops/sec [214,954..226,870] → 234,336 ops/sec [230,094..236,445] 🟢 +5.9%
set() from array 213,185 ops/sec [209,955..217,533] → 223,505 ops/sec [222,113..224,386] 🟢 +4.8% 244,943 ops/sec [242,702..251,643] → 264,331 ops/sec [262,996..268,723] 🟢 +7.9%
for-of loop 2,208 ops/sec [2,185..2,219] → 2,377 ops/sec [2,269..2,397] 🟢 +7.7% 9,007 ops/sec [8,882..9,020] → 9,303 ops/sec [9,244..9,363] 🟢 +3.3%
spread into array 8,888 ops/sec [8,783..8,991] → 9,381 ops/sec [9,198..9,617] 🟢 +5.5% 35,573 ops/sec [35,477..36,034] → 38,428 ops/sec [38,123..38,818] 🟢 +8.0%
f16round(1.337) 262,504 ops/sec [253,596..263,800] → 274,607 ops/sec [273,073..278,902] 🟢 +4.6% 247,501 ops/sec [245,318..249,374] → 262,232 ops/sec [257,878..267,434] 🟢 +6.0%
f16round over 100 values 1,527 ops/sec [1,520..1,560] → 1,636 ops/sec [1,616..1,667] 🟢 +7.2% 2,856 ops/sec [2,804..2,971] → 2,868 ops/sec [2,856..2,876] ~ overlap (+0.4%)
for-of.js — Interp: 🟢 5, 2 unch. · avg +3.8% · Bytecode: 🟢 5, 2 unch. · avg +7.1%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 19,864 ops/sec [19,539..20,290] → 20,940 ops/sec [20,587..21,087] 🟢 +5.4% 111,123 ops/sec [109,710..112,278] → 121,682 ops/sec [118,361..129,571] 🟢 +9.5%
for...of with 100-element array 2,294 ops/sec [2,210..2,333] → 2,406 ops/sec [2,385..2,434] 🟢 +4.9% 14,007 ops/sec [13,287..14,458] → 16,397 ops/sec [16,289..16,579] 🟢 +17.1%
for...of with string (10 chars) 14,926 ops/sec [14,648..15,047] → 15,288 ops/sec [15,178..15,705] 🟢 +2.4% 33,180 ops/sec [30,718..33,486] → 34,693 ops/sec [34,399..34,754] 🟢 +4.6%
for...of with Set (10 elements) 20,527 ops/sec [20,400..20,857] → 20,995 ops/sec [20,586..21,283] ~ overlap (+2.3%) 104,862 ops/sec [104,445..106,633] → 118,150 ops/sec [116,893..119,106] 🟢 +12.7%
for...of with Map entries (10 entries) 13,621 ops/sec [13,146..13,864] → 14,121 ops/sec [14,064..14,210] 🟢 +3.7% 16,501 ops/sec [16,359..16,814] → 16,812 ops/sec [16,719..17,220] ~ overlap (+1.9%)
for...of with destructuring 16,683 ops/sec [16,574..16,859] → 16,999 ops/sec [16,633..17,063] ~ overlap (+1.9%) 21,059 ops/sec [20,757..21,296] → 21,879 ops/sec [21,634..22,836] 🟢 +3.9%
for-await-of with sync array 18,605 ops/sec [18,419..18,879] → 19,699 ops/sec [19,565..19,904] 🟢 +5.9% 17,915 ops/sec [17,386..18,025] → 17,957 ops/sec [17,749..18,321] ~ overlap (+0.2%)
generators.js — Interp: 🟢 3, 1 unch. · avg +5.1% · Bytecode: 🟢 3, 1 unch. · avg +4.1%
Benchmark Interpreted Δ Bytecode Δ
manual next over object generator 916 ops/sec [912..937] → 964 ops/sec [947..1,000] 🟢 +5.2% 1,069 ops/sec [1,062..1,080] → 1,155 ops/sec [1,134..1,171] 🟢 +8.0%
for...of over object generator 1,396 ops/sec [1,374..1,408] → 1,468 ops/sec [1,454..1,532] 🟢 +5.2% 2,140 ops/sec [2,118..2,159] → 2,212 ops/sec [2,187..2,221] 🟢 +3.4%
yield delegation 1,398 ops/sec [1,381..1,437] → 1,490 ops/sec [1,473..1,526] 🟢 +6.6% 2,185 ops/sec [2,146..2,201] → 2,207 ops/sec [2,185..2,230] ~ overlap (+1.0%)
class generator method 1,458 ops/sec [1,436..1,499] → 1,512 ops/sec [1,484..1,541] ~ overlap (+3.7%) 2,151 ops/sec [2,128..2,161] → 2,236 ops/sec [2,203..2,278] 🟢 +3.9%
iterators.js — Interp: 🟢 10, 🔴 6, 26 unch. · avg +1.1% · Bytecode: 🟢 30, 🔴 2, 10 unch. · avg +4.9%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 4,724 ops/sec [4,681..4,809] → 4,840 ops/sec [4,724..4,946] ~ overlap (+2.5%) 6,819 ops/sec [6,743..6,913] → 7,175 ops/sec [7,137..7,245] 🟢 +5.2%
Iterator.from({next}).toArray() — 50 elements 2,015 ops/sec [2,002..2,051] → 2,081 ops/sec [2,023..2,158] ~ overlap (+3.2%) 3,103 ops/sec [3,096..3,121] → 3,227 ops/sec [3,165..3,277] 🟢 +4.0%
spread pre-wrapped iterator — 20 elements 4,801 ops/sec [4,692..4,837] → 4,979 ops/sec [4,813..5,089] ~ overlap (+3.7%) 6,826 ops/sec [6,761..6,858] → 7,184 ops/sec [7,069..7,302] 🟢 +5.2%
Iterator.from({next}).forEach — 50 elements 1,492 ops/sec [1,487..1,507] → 1,528 ops/sec [1,503..1,559] ~ overlap (+2.5%) 2,320 ops/sec [2,279..2,341] → 2,446 ops/sec [2,438..2,505] 🟢 +5.4%
Iterator.from({next}).reduce — 50 elements 1,585 ops/sec [1,572..1,602] → 1,532 ops/sec [1,501..1,563] 🔴 -3.3% 2,177 ops/sec [2,157..2,213] → 2,306 ops/sec [2,260..2,347] 🟢 +5.9%
wrap array iterator 78,812 ops/sec [64,390..79,969] → 77,352 ops/sec [73,477..80,652] ~ overlap (-1.9%) 78,123 ops/sec [77,738..78,806] → 83,152 ops/sec [82,838..83,531] 🟢 +6.4%
wrap plain {next()} object 3,274 ops/sec [3,241..3,313] → 3,305 ops/sec [3,266..3,414] ~ overlap (+0.9%) 4,928 ops/sec [4,702..4,952] → 5,067 ops/sec [5,022..5,100] 🟢 +2.8%
map + toArray (50 elements) 1,528 ops/sec [1,511..1,544] → 1,540 ops/sec [1,527..1,547] ~ overlap (+0.7%) 2,177 ops/sec [2,117..2,200] → 2,394 ops/sec [2,371..2,405] 🟢 +10.0%
filter + toArray (50 elements) 1,580 ops/sec [1,487..1,664] → 1,515 ops/sec [1,491..1,528] ~ overlap (-4.1%) 2,273 ops/sec [2,258..2,292] → 2,398 ops/sec [2,327..2,412] 🟢 +5.5%
take(10) + toArray (50 element source) 9,651 ops/sec [9,576..9,684] → 9,142 ops/sec [9,048..9,172] 🔴 -5.3% 12,489 ops/sec [12,331..12,885] → 13,567 ops/sec [13,381..13,642] 🟢 +8.6%
drop(40) + toArray (50 element source) 2,161 ops/sec [2,064..2,179] → 2,075 ops/sec [2,053..2,096] ~ overlap (-4.0%) 3,100 ops/sec [3,041..3,194] → 3,211 ops/sec [3,170..3,246] ~ overlap (+3.6%)
chained map + filter + take (100 element source) 3,225 ops/sec [2,829..3,316] → 3,128 ops/sec [3,107..3,142] ~ overlap (-3.0%) 4,286 ops/sec [4,207..4,496] → 4,572 ops/sec [4,558..4,587] 🟢 +6.7%
some + every (50 elements) 842 ops/sec [833..863] → 896 ops/sec [887..923] 🟢 +6.4% 1,330 ops/sec [1,293..1,387] → 1,393 ops/sec [1,371..1,398] ~ overlap (+4.7%)
find (50 elements) 1,832 ops/sec [1,803..1,834] → 2,003 ops/sec [1,934..2,070] 🟢 +9.3% 2,805 ops/sec [2,782..2,854] → 3,045 ops/sec [3,032..3,098] 🟢 +8.5%
concat 2 arrays (10 + 10 elements) 68,168 ops/sec [67,504..70,172] → 70,701 ops/sec [69,514..71,471] ~ overlap (+3.7%) 70,023 ops/sec [69,146..70,911] → 74,705 ops/sec [73,864..76,028] 🟢 +6.7%
concat 5 arrays (10 elements each) 40,424 ops/sec [39,610..40,946] → 41,761 ops/sec [40,840..42,099] ~ overlap (+3.3%) 42,058 ops/sec [41,577..43,326] → 44,880 ops/sec [44,616..45,137] 🟢 +6.7%
concat 2 arrays (20 + 20 elements) 58,701 ops/sec [57,036..60,644] → 61,295 ops/sec [60,317..61,906] ~ overlap (+4.4%) 59,102 ops/sec [58,381..59,912] → 63,882 ops/sec [63,051..64,953] 🟢 +8.1%
concat + filter + toArray (20 + 20 elements) 6,265 ops/sec [6,157..6,277] → 6,339 ops/sec [6,244..6,512] ~ overlap (+1.2%) 9,223 ops/sec [9,183..9,321] → 10,065 ops/sec [9,965..10,160] 🟢 +9.1%
concat + map + take (20 + 20 elements, take 10) 19,334 ops/sec [19,055..19,499] → 19,678 ops/sec [19,248..20,137] ~ overlap (+1.8%) 24,592 ops/sec [24,307..24,796] → 27,699 ops/sec [27,182..27,909] 🟢 +12.6%
concat Sets (15 + 15 elements) 68,462 ops/sec [67,648..68,905] → 68,689 ops/sec [66,996..69,103] ~ overlap (+0.3%) 62,945 ops/sec [62,820..63,769] → 70,453 ops/sec [69,161..71,308] 🟢 +11.9%
concat strings (13 + 13 characters) 49,226 ops/sec [48,770..50,605] → 49,914 ops/sec [49,519..50,207] ~ overlap (+1.4%) 43,413 ops/sec [42,955..43,635] → 49,407 ops/sec [48,979..49,823] 🟢 +13.8%
zip 2 arrays (10 + 10 elements) 28,629 ops/sec [28,165..29,143] → 29,337 ops/sec [28,932..29,728] ~ overlap (+2.5%) 28,263 ops/sec [28,125..28,354] → 29,809 ops/sec [29,265..30,403] 🟢 +5.5%
zip 3 arrays (10 elements each) 26,341 ops/sec [25,523..26,566] → 27,080 ops/sec [26,495..27,550] ~ overlap (+2.8%) 26,302 ops/sec [26,269..26,355] → 27,158 ops/sec [26,904..28,161] 🟢 +3.3%
zip 2 arrays (20 + 20 elements) 19,141 ops/sec [17,236..19,315] → 20,159 ops/sec [19,780..20,331] 🟢 +5.3% 18,357 ops/sec [18,232..18,392] → 19,695 ops/sec [19,389..20,266] 🟢 +7.3%
zip 2 arrays (50 + 50 elements) 9,734 ops/sec [9,612..10,033] → 10,104 ops/sec [9,971..10,230] ~ overlap (+3.8%) 9,306 ops/sec [9,263..9,366] → 9,847 ops/sec [9,697..10,041] 🟢 +5.8%
zip shortest mode (20 + 10 elements) 28,147 ops/sec [27,768..28,508] → 29,426 ops/sec [28,543..31,680] 🟢 +4.5% 28,410 ops/sec [28,136..28,966] → 29,101 ops/sec [28,700..29,652] ~ overlap (+2.4%)
zip longest mode (10 + 20 elements) 16,881 ops/sec [16,545..17,196] → 17,630 ops/sec [17,227..17,807] 🟢 +4.4% 17,091 ops/sec [17,055..17,155] → 17,639 ops/sec [17,198..17,890] 🟢 +3.2%
zip strict mode (20 + 20 elements) 18,230 ops/sec [18,053..18,457] → 18,585 ops/sec [18,408..18,747] ~ overlap (+1.9%) 18,322 ops/sec [18,220..18,502] → 18,459 ops/sec [18,338..18,641] ~ overlap (+0.7%)
zip + map + toArray (20 + 20 elements) 7,478 ops/sec [7,336..7,589] → 7,649 ops/sec [7,617..7,719] 🟢 +2.3% 5,829 ops/sec [5,817..5,859] → 5,694 ops/sec [5,628..5,747] 🔴 -2.3%
zip + filter + toArray (20 + 20 elements) 7,231 ops/sec [7,142..7,304] → 7,580 ops/sec [7,474..7,721] 🟢 +4.8% 5,780 ops/sec [5,748..5,807] → 5,671 ops/sec [5,590..5,711] 🔴 -1.9%
zip Sets (15 + 15 elements) 23,655 ops/sec [23,217..24,039] → 24,195 ops/sec [23,830..24,908] ~ overlap (+2.3%) 23,536 ops/sec [23,475..23,678] → 23,869 ops/sec [23,695..24,361] 🟢 +1.4%
zipKeyed 2 keys (10 elements each) 29,461 ops/sec [28,770..29,712] → 29,897 ops/sec [29,505..30,689] ~ overlap (+1.5%) 29,332 ops/sec [28,871..29,594] → 29,633 ops/sec [29,191..29,862] ~ overlap (+1.0%)
zipKeyed 3 keys (20 elements each) 15,889 ops/sec [15,620..16,210] → 15,169 ops/sec [14,905..15,533] 🔴 -4.5% 14,567 ops/sec [14,457..14,657] → 14,699 ops/sec [14,349..15,010] ~ overlap (+0.9%)
zipKeyed longest mode (10 + 20 elements) 18,490 ops/sec [16,770..18,650] → 17,631 ops/sec [17,425..17,709] ~ overlap (-4.6%) 16,839 ops/sec [16,694..16,922] → 16,828 ops/sec [16,544..17,212] ~ overlap (-0.1%)
zipKeyed strict mode (20 + 20 elements) 18,750 ops/sec [18,475..19,504] → 18,567 ops/sec [18,171..18,689] ~ overlap (-1.0%) 17,669 ops/sec [17,416..17,770] → 17,441 ops/sec [17,306..17,743] ~ overlap (-1.3%)
zipKeyed + filter + map (20 elements) 5,790 ops/sec [5,629..5,801] → 5,542 ops/sec [5,515..5,622] 🔴 -4.3% 6,863 ops/sec [6,832..6,879] → 7,201 ops/sec [7,178..7,289] 🟢 +4.9%
array.values().map().filter().toArray() 3,131 ops/sec [3,098..3,174] → 2,872 ops/sec [2,770..2,938] 🔴 -8.3% 4,595 ops/sec [4,516..4,643] → 4,965 ops/sec [4,915..5,015] 🟢 +8.1%
array.values().take(5).toArray() 103,730 ops/sec [103,059..104,050] → 97,714 ops/sec [95,146..98,284] 🔴 -5.8% 108,531 ops/sec [106,800..112,150] → 110,189 ops/sec [108,903..111,559] ~ overlap (+1.5%)
array.values().drop(45).toArray() 75,317 ops/sec [74,697..76,218] → 81,122 ops/sec [80,029..82,845] 🟢 +7.7% 83,656 ops/sec [82,999..84,700] → 87,283 ops/sec [86,116..89,154] 🟢 +4.3%
map.entries() chained helpers 4,093 ops/sec [4,064..4,124] → 4,212 ops/sec [4,142..4,265] 🟢 +2.9% 2,964 ops/sec [2,943..2,972] → 3,077 ops/sec [2,962..3,222] ~ overlap (+3.8%)
set.values() chained helpers 6,315 ops/sec [6,297..6,396] → 6,499 ops/sec [6,365..6,633] ~ overlap (+2.9%) 10,480 ops/sec [10,094..10,505] → 10,754 ops/sec [10,593..10,937] 🟢 +2.6%
string iterator map + toArray 5,446 ops/sec [5,334..5,515] → 5,613 ops/sec [5,572..5,812] 🟢 +3.1% 5,771 ops/sec [5,730..5,798] → 6,043 ops/sec [6,028..6,046] 🟢 +4.7%
json.js — Interp: 🟢 4, 16 unch. · avg +2.4% · Bytecode: 🟢 20 · avg +10.0%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 80,234 ops/sec [77,393..80,535] → 80,145 ops/sec [79,248..81,735] ~ overlap (-0.1%) 75,595 ops/sec [74,712..76,860] → 83,988 ops/sec [82,177..84,849] 🟢 +11.1%
parse nested object 53,211 ops/sec [52,175..54,173] → 54,039 ops/sec [52,808..55,270] ~ overlap (+1.6%) 50,011 ops/sec [49,368..50,294] → 55,670 ops/sec [54,465..56,819] 🟢 +11.3%
parse array of objects 31,909 ops/sec [31,797..33,163] → 32,676 ops/sec [31,831..33,154] ~ overlap (+2.4%) 30,626 ops/sec [30,250..31,358] → 32,064 ops/sec [31,726..33,556] 🟢 +4.7%
parse large flat object 35,313 ops/sec [34,354..36,092] → 35,398 ops/sec [33,947..35,715] ~ overlap (+0.2%) 31,666 ops/sec [31,137..32,146] → 34,880 ops/sec [34,436..35,233] 🟢 +10.1%
parse mixed types 39,825 ops/sec [38,464..40,712] → 40,287 ops/sec [39,284..41,510] ~ overlap (+1.2%) 39,169 ops/sec [39,071..39,345] → 41,231 ops/sec [40,885..41,986] 🟢 +5.3%
stringify simple object 82,134 ops/sec [63,910..83,380] → 85,689 ops/sec [84,115..86,620] 🟢 +4.3% 78,235 ops/sec [77,933..78,416] → 83,451 ops/sec [82,803..84,060] 🟢 +6.7%
stringify nested object 48,706 ops/sec [46,602..50,025] → 50,638 ops/sec [49,461..51,387] ~ overlap (+4.0%) 42,317 ops/sec [38,866..43,627] → 48,042 ops/sec [47,870..48,281] 🟢 +13.5%
stringify array of objects 20,712 ops/sec [20,600..20,815] → 21,769 ops/sec [21,271..22,321] 🟢 +5.1% 19,975 ops/sec [18,490..20,430] → 21,467 ops/sec [21,282..21,574] 🟢 +7.5%
stringify mixed types 32,174 ops/sec [31,318..33,239] → 34,010 ops/sec [33,055..34,724] ~ overlap (+5.7%) 28,370 ops/sec [28,043..28,822] → 31,263 ops/sec [31,085..34,187] 🟢 +10.2%
reviver doubles numbers 15,271 ops/sec [14,797..15,686] → 15,917 ops/sec [15,430..16,428] ~ overlap (+4.2%) 19,173 ops/sec [18,798..19,243] → 22,030 ops/sec [21,423..22,378] 🟢 +14.9%
reviver filters properties 14,644 ops/sec [14,469..14,865] → 15,277 ops/sec [14,821..15,499] ~ overlap (+4.3%) 16,070 ops/sec [15,883..16,510] → 17,850 ops/sec [17,316..18,205] 🟢 +11.1%
reviver on nested object 17,916 ops/sec [17,540..18,056] → 18,465 ops/sec [18,099..18,540] 🟢 +3.1% 20,384 ops/sec [19,937..20,629] → 22,369 ops/sec [22,124..22,862] 🟢 +9.7%
reviver on array 9,291 ops/sec [9,232..9,385] → 9,675 ops/sec [9,575..9,700] 🟢 +4.1% 11,853 ops/sec [11,755..11,998] → 13,110 ops/sec [13,072..13,182] 🟢 +10.6%
replacer function doubles numbers 17,494 ops/sec [17,291..17,698] → 17,928 ops/sec [17,492..18,515] ~ overlap (+2.5%) 22,313 ops/sec [21,888..22,547] → 26,224 ops/sec [25,138..26,370] 🟢 +17.5%
replacer function excludes properties 23,353 ops/sec [23,042..24,169] → 24,271 ops/sec [23,595..24,490] ~ overlap (+3.9%) 27,297 ops/sec [26,685..28,113] → 28,904 ops/sec [28,292..29,260] 🟢 +5.9%
array replacer (allowlist) 51,888 ops/sec [50,964..53,825] → 52,735 ops/sec [52,215..52,999] ~ overlap (+1.6%) 45,828 ops/sec [45,399..46,353] → 48,938 ops/sec [47,752..49,359] 🟢 +6.8%
stringify with 2-space indent 42,370 ops/sec [40,985..42,834] → 42,436 ops/sec [41,712..43,133] ~ overlap (+0.2%) 38,976 ops/sec [38,229..39,981] → 41,166 ops/sec [40,872..42,040] 🟢 +5.6%
stringify with tab indent 42,297 ops/sec [41,992..43,225] → 42,665 ops/sec [42,533..42,868] ~ overlap (+0.9%) 38,410 ops/sec [38,046..39,067] → 42,396 ops/sec [42,211..42,504] 🟢 +10.4%
parse then stringify 26,195 ops/sec [25,905..26,768] → 25,952 ops/sec [25,854..26,043] ~ overlap (-0.9%) 24,223 ops/sec [24,035..24,637] → 27,844 ops/sec [27,612..28,201] 🟢 +15.0%
stringify then parse 15,753 ops/sec [15,203..16,734] → 15,865 ops/sec [15,474..16,224] ~ overlap (+0.7%) 14,305 ops/sec [14,214..15,051] → 16,132 ops/sec [15,611..16,406] 🟢 +12.8%
jsx.jsx — Interp: 🟢 9, 🔴 1, 11 unch. · avg +3.1% · Bytecode: 🟢 16, 5 unch. · avg +7.0%
Benchmark Interpreted Δ Bytecode Δ
simple element 93,268 ops/sec [91,625..94,491] → 97,117 ops/sec [95,037..97,725] 🟢 +4.1% 140,548 ops/sec [140,223..140,831] → 153,910 ops/sec [152,515..157,512] 🟢 +9.5%
self-closing element 95,568 ops/sec [94,055..96,932] → 101,067 ops/sec [100,192..102,316] 🟢 +5.8% 158,995 ops/sec [156,162..165,054] → 171,601 ops/sec [169,448..172,150] 🟢 +7.9%
element with string attribute 82,883 ops/sec [82,313..83,303] → 81,186 ops/sec [79,943..81,971] 🔴 -2.0% 111,803 ops/sec [109,188..118,493] → 120,366 ops/sec [119,079..121,077] 🟢 +7.7%
element with multiple attributes 70,745 ops/sec [58,642..71,701] → 72,766 ops/sec [70,304..74,713] ~ overlap (+2.9%) 82,916 ops/sec [82,175..83,972] → 93,613 ops/sec [92,342..95,311] 🟢 +12.9%
element with expression attribute 74,364 ops/sec [73,088..75,274] → 76,464 ops/sec [75,731..78,571] 🟢 +2.8% 116,999 ops/sec [115,301..124,701] → 127,040 ops/sec [124,370..131,004] ~ overlap (+8.6%)
text child 92,601 ops/sec [88,657..94,864] → 97,742 ops/sec [96,211..97,901] 🟢 +5.6% 149,512 ops/sec [147,109..150,984] → 153,705 ops/sec [152,490..163,581] 🟢 +2.8%
expression child 92,630 ops/sec [91,319..94,247] → 95,856 ops/sec [93,716..99,225] ~ overlap (+3.5%) 135,397 ops/sec [135,237..136,114] → 143,178 ops/sec [139,119..144,466] 🟢 +5.7%
mixed text and expression 86,398 ops/sec [86,002..88,185] → 88,182 ops/sec [87,389..89,423] ~ overlap (+2.1%) 117,883 ops/sec [116,869..118,782] → 125,810 ops/sec [123,611..131,593] 🟢 +6.7%
nested elements (3 levels) 36,699 ops/sec [36,472..37,456] → 37,123 ops/sec [35,936..38,030] ~ overlap (+1.2%) 57,112 ops/sec [56,359..57,958] → 60,257 ops/sec [57,849..62,029] ~ overlap (+5.5%)
sibling children 27,309 ops/sec [26,980..27,518] → 28,084 ops/sec [27,734..28,494] 🟢 +2.8% 39,180 ops/sec [38,308..41,774] → 43,154 ops/sec [41,652..43,493] ~ overlap (+10.1%)
component element 70,413 ops/sec [68,968..71,718] → 73,467 ops/sec [73,123..73,686] 🟢 +4.3% 105,360 ops/sec [103,772..107,109] → 109,184 ops/sec [107,437..111,336] 🟢 +3.6%
component with children 42,778 ops/sec [42,033..43,764] → 44,923 ops/sec [43,561..45,459] ~ overlap (+5.0%) 62,808 ops/sec [61,282..64,454] → 66,363 ops/sec [65,517..68,403] 🟢 +5.7%
dotted component 59,928 ops/sec [59,581..60,796] → 62,271 ops/sec [61,278..63,379] 🟢 +3.9% 83,895 ops/sec [81,973..86,043] → 85,104 ops/sec [84,530..85,824] ~ overlap (+1.4%)
empty fragment 94,520 ops/sec [94,100..100,024] → 99,674 ops/sec [98,293..100,598] ~ overlap (+5.5%) 177,181 ops/sec [173,796..184,040] → 181,734 ops/sec [180,006..183,493] ~ overlap (+2.6%)
fragment with children 26,926 ops/sec [26,243..27,724] → 28,023 ops/sec [27,512..28,308] ~ overlap (+4.1%) 39,712 ops/sec [39,371..40,144] → 43,514 ops/sec [43,151..43,783] 🟢 +9.6%
spread attributes 51,774 ops/sec [51,405..52,078] → 52,847 ops/sec [51,209..54,342] ~ overlap (+2.1%) 58,429 ops/sec [57,727..59,044] → 64,532 ops/sec [62,958..65,575] 🟢 +10.4%
spread with overrides 45,993 ops/sec [45,486..46,523] → 47,593 ops/sec [46,778..48,624] 🟢 +3.5% 51,025 ops/sec [50,305..51,040] → 55,158 ops/sec [54,342..55,911] 🟢 +8.1%
shorthand props 73,749 ops/sec [73,514..74,162] → 75,638 ops/sec [72,324..77,086] ~ overlap (+2.6%) 93,035 ops/sec [88,880..95,608] → 98,322 ops/sec [97,806..99,039] 🟢 +5.7%
nav bar structure 13,299 ops/sec [13,200..13,335] → 13,440 ops/sec [13,227..13,618] ~ overlap (+1.1%) 17,973 ops/sec [17,718..18,186] → 20,270 ops/sec [20,159..20,724] 🟢 +12.8%
card component tree 15,419 ops/sec [15,221..15,515] → 15,674 ops/sec [15,429..16,019] ~ overlap (+1.7%) 19,763 ops/sec [19,639..20,351] → 20,729 ops/sec [20,635..20,894] 🟢 +4.9%
10 list items via Array.from 6,804 ops/sec [6,670..6,842] → 6,988 ops/sec [6,947..7,041] 🟢 +2.7% 8,948 ops/sec [8,935..8,975] → 9,283 ops/sec [9,240..9,513] 🟢 +3.7%
modules.js — Interp: 🟢 1, 8 unch. · avg +2.3% · Bytecode: 🟢 9 · avg +13.4%
Benchmark Interpreted Δ Bytecode Δ
call imported function 163,230 ops/sec [160,719..165,670] → 168,720 ops/sec [165,178..171,250] ~ overlap (+3.4%) 675,676 ops/sec [659,005..684,298] → 748,085 ops/sec [740,359..773,849] 🟢 +10.7%
call two imported functions 92,436 ops/sec [90,524..96,068] → 94,345 ops/sec [92,366..97,324] ~ overlap (+2.1%) 435,851 ops/sec [425,672..448,120] → 507,675 ops/sec [505,895..510,289] 🟢 +16.5%
read imported constant 516,295 ops/sec [501,361..526,419] → 525,962 ops/sec [519,643..558,368] ~ overlap (+1.9%) 1,648,372 ops/sec [1,593,098..1,672,500] → 1,896,162 ops/sec [1,887,099..1,905,410] 🟢 +15.0%
read imported string 521,690 ops/sec [497,819..524,532] → 521,651 ops/sec [504,028..551,793] ~ overlap (-0.0%) 1,643,407 ops/sec [1,625,764..1,649,915] → 1,868,607 ops/sec [1,822,780..1,877,740] 🟢 +13.7%
read JSON string property 508,277 ops/sec [490,796..534,342] → 514,805 ops/sec [510,283..520,787] ~ overlap (+1.3%) 1,636,693 ops/sec [1,634,997..1,638,114] → 1,863,503 ops/sec [1,856,093..1,902,523] 🟢 +13.9%
read JSON number property 515,551 ops/sec [493,276..532,730] → 521,016 ops/sec [509,800..530,514] ~ overlap (+1.1%) 1,658,795 ops/sec [1,630,086..1,687,207] → 1,885,053 ops/sec [1,826,972..1,894,710] 🟢 +13.6%
read JSON boolean property 510,076 ops/sec [492,367..530,272] → 537,883 ops/sec [534,016..544,441] 🟢 +5.5% 1,631,818 ops/sec [1,562,687..1,651,156] → 1,841,181 ops/sec [1,825,623..1,860,340] 🟢 +12.8%
read JSON array property 518,594 ops/sec [497,959..537,392] → 526,063 ops/sec [516,155..528,845] ~ overlap (+1.4%) 1,626,411 ops/sec [1,601,671..1,652,304] → 1,836,043 ops/sec [1,791,197..1,851,850] 🟢 +12.9%
read multiple JSON properties 315,338 ops/sec [305,883..328,606] → 327,882 ops/sec [320,433..331,545] ~ overlap (+4.0%) 1,377,398 ops/sec [1,368,204..1,393,341] → 1,529,776 ops/sec [1,525,520..1,533,721] 🟢 +11.1%
numbers.js — Interp: 🟢 5, 6 unch. · avg +4.1% · Bytecode: 🟢 9, 2 unch. · avg +7.0%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 165,474 ops/sec [161,864..170,978] → 171,306 ops/sec [169,510..173,554] ~ overlap (+3.5%) 719,801 ops/sec [712,247..723,874] → 752,992 ops/sec [747,176..773,737] 🟢 +4.6%
floating point arithmetic 195,919 ops/sec [187,602..197,941] → 202,606 ops/sec [182,652..208,456] ~ overlap (+3.4%) 313,257 ops/sec [307,594..314,925] → 319,772 ops/sec [317,157..322,072] 🟢 +2.1%
number coercion 75,729 ops/sec [75,253..76,410] → 81,740 ops/sec [79,953..82,499] 🟢 +7.9% 90,394 ops/sec [90,206..90,577] → 100,872 ops/sec [97,968..101,428] 🟢 +11.6%
toFixed 43,412 ops/sec [42,933..43,987] → 44,317 ops/sec [43,820..45,335] ~ overlap (+2.1%) 42,941 ops/sec [42,012..43,250] → 43,183 ops/sec [42,487..43,272] ~ overlap (+0.6%)
toString 65,640 ops/sec [64,849..66,633] → 68,425 ops/sec [67,426..68,837] 🟢 +4.2% 73,171 ops/sec [72,907..75,037] → 73,565 ops/sec [72,768..75,113] ~ overlap (+0.5%)
valueOf 97,898 ops/sec [96,564..100,546] → 102,136 ops/sec [100,058..103,829] ~ overlap (+4.3%) 109,452 ops/sec [109,132..109,594] → 114,164 ops/sec [113,336..114,792] 🟢 +4.3%
toPrecision 39,276 ops/sec [38,632..39,504] → 39,650 ops/sec [39,004..40,277] ~ overlap (+1.0%) 35,477 ops/sec [35,197..36,361] → 38,420 ops/sec [38,258..38,650] 🟢 +8.3%
Number.isNaN 118,292 ops/sec [116,467..122,301] → 125,493 ops/sec [123,026..126,519] 🟢 +6.1% 114,604 ops/sec [113,318..117,095] → 129,391 ops/sec [128,029..130,962] 🟢 +12.9%
Number.isFinite 118,234 ops/sec [116,107..119,411] → 122,707 ops/sec [115,874..126,387] ~ overlap (+3.8%) 100,056 ops/sec [97,124..100,832] → 111,380 ops/sec [111,129..111,655] 🟢 +11.3%
Number.isInteger 121,010 ops/sec [119,812..121,668] → 126,045 ops/sec [124,287..129,039] 🟢 +4.2% 106,389 ops/sec [104,031..107,546] → 118,201 ops/sec [117,994..118,556] 🟢 +11.1%
Number.parseInt and parseFloat 95,030 ops/sec [94,577..96,043] → 99,365 ops/sec [98,061..102,900] 🟢 +4.6% 79,482 ops/sec [78,704..83,306] → 86,885 ops/sec [85,930..87,219] 🟢 +9.3%
objects.js — Interp: 🟢 6, 1 unch. · avg +6.1% · Bytecode: 🟢 7 · avg +11.7%
Benchmark Interpreted Δ Bytecode Δ
create simple object 212,584 ops/sec [211,430..213,227] → 212,912 ops/sec [210,511..217,972] ~ overlap (+0.2%) 248,131 ops/sec [238,262..252,007] → 265,917 ops/sec [264,241..266,596] 🟢 +7.2%
create nested object 110,383 ops/sec [108,186..110,910] → 116,863 ops/sec [114,622..119,079] 🟢 +5.9% 103,074 ops/sec [101,708..104,861] → 116,484 ops/sec [113,950..119,711] 🟢 +13.0%
create 50 objects via Array.from 4,002 ops/sec [3,911..4,035] → 4,194 ops/sec [4,135..4,424] 🟢 +4.8% 4,338 ops/sec [4,296..4,377] → 4,734 ops/sec [4,567..4,828] 🟢 +9.1%
property read 192,540 ops/sec [185,188..196,059] → 207,808 ops/sec [204,157..211,014] 🟢 +7.9% 261,784 ops/sec [261,200..266,327] → 321,347 ops/sec [314,574..322,440] 🟢 +22.8%
Object.keys 123,871 ops/sec [122,199..125,230] → 128,746 ops/sec [127,084..129,769] 🟢 +3.9% 128,239 ops/sec [124,881..129,179] → 147,191 ops/sec [141,693..150,201] 🟢 +14.8%
Object.entries 49,712 ops/sec [49,362..50,077] → 52,354 ops/sec [51,790..52,895] 🟢 +5.3% 52,834 ops/sec [51,894..53,615] → 56,273 ops/sec [54,737..58,717] 🟢 +6.5%
spread operator 87,392 ops/sec [84,871..91,557] → 99,985 ops/sec [92,041..100,824] 🟢 +14.4% 94,564 ops/sec [92,258..96,292] → 102,960 ops/sec [99,662..103,390] 🟢 +8.9%
promises.js — Interp: 12 unch. · avg +0.4% · Bytecode: 🟢 6, 6 unch. · avg +2.8%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 213,047 ops/sec [207,161..215,333] → 215,764 ops/sec [212,677..221,074] ~ overlap (+1.3%) 231,250 ops/sec [230,376..231,819] → 237,769 ops/sec [236,272..239,413] 🟢 +2.8%
new Promise(resolve => resolve(value)) 82,021 ops/sec [79,656..85,337] → 83,215 ops/sec [81,531..86,673] ~ overlap (+1.5%) 111,133 ops/sec [108,968..112,247] → 111,363 ops/sec [110,485..112,770] ~ overlap (+0.2%)
Promise.reject(reason) 213,325 ops/sec [212,791..215,674] → 220,047 ops/sec [215,171..230,958] ~ overlap (+3.2%) 230,737 ops/sec [229,358..232,702] → 232,524 ops/sec [230,442..237,440] ~ overlap (+0.8%)
resolve + then (1 handler) 77,319 ops/sec [76,477..81,416] → 78,619 ops/sec [77,494..80,389] ~ overlap (+1.7%) 95,861 ops/sec [94,315..97,502] → 98,683 ops/sec [97,699..100,563] 🟢 +2.9%
resolve + then chain (3 deep) 32,964 ops/sec [32,394..33,536] → 32,105 ops/sec [31,834..33,367] ~ overlap (-2.6%) 41,499 ops/sec [40,762..42,127] → 41,752 ops/sec [40,784..42,791] ~ overlap (+0.6%)
resolve + then chain (10 deep) 10,802 ops/sec [10,704..10,825] → 10,775 ops/sec [10,506..11,044] ~ overlap (-0.3%) 13,698 ops/sec [13,544..13,917] → 14,185 ops/sec [14,016..15,155] 🟢 +3.6%
reject + catch + then 47,021 ops/sec [46,656..47,530] → 47,581 ops/sec [46,035..48,134] ~ overlap (+1.2%) 55,611 ops/sec [55,410..56,028] → 56,192 ops/sec [54,752..60,309] ~ overlap (+1.0%)
resolve + finally + then 40,751 ops/sec [40,115..42,143] → 40,751 ops/sec [38,844..41,256] ~ overlap (-0.0%) 46,755 ops/sec [46,019..47,181] → 46,585 ops/sec [46,394..51,066] ~ overlap (-0.4%)
Promise.all (5 resolved) 16,741 ops/sec [16,557..17,077] → 16,443 ops/sec [16,292..16,987] ~ overlap (-1.8%) 16,259 ops/sec [16,090..16,307] → 17,675 ops/sec [17,352..17,784] 🟢 +8.7%
Promise.race (5 resolved) 17,458 ops/sec [17,189..17,592] → 17,664 ops/sec [17,298..18,445] ~ overlap (+1.2%) 17,310 ops/sec [17,160..17,481] → 18,303 ops/sec [17,988..18,500] 🟢 +5.7%
Promise.allSettled (5 mixed) 14,201 ops/sec [13,942..14,327] → 14,363 ops/sec [13,455..14,553] ~ overlap (+1.1%) 14,257 ops/sec [14,185..14,356] → 15,213 ops/sec [15,078..15,264] 🟢 +6.7%
Promise.any (5 mixed) 16,753 ops/sec [16,461..16,971] → 16,561 ops/sec [16,231..17,094] ~ overlap (-1.1%) 16,143 ops/sec [15,857..16,363] → 16,255 ops/sec [16,052..16,394] ~ overlap (+0.7%)
regexp.js — Interp: 🟢 7, 4 unch. · avg +2.5% · Bytecode: 🟢 5, 🔴 6 · avg -2.3%
Benchmark Interpreted Δ Bytecode Δ
regex literal creation 119,587 ops/sec [114,926..121,701] → 123,124 ops/sec [121,992..123,431] 🟢 +3.0% 96,932 ops/sec [95,925..97,518] → 106,192 ops/sec [105,800..106,591] 🟢 +9.6%
new RegExp(pattern, flags) 94,394 ops/sec [92,682..96,389] → 97,740 ops/sec [97,026..99,365] 🟢 +3.5% 91,376 ops/sec [90,940..91,621] → 102,139 ops/sec [101,658..102,797] 🟢 +11.8%
RegExp(existingRegex) returns the same regex 261,559 ops/sec [256,436..270,579] → 281,082 ops/sec [276,908..289,713] 🟢 +7.5% 381,076 ops/sec [370,206..396,385] → 424,079 ops/sec [417,796..427,376] 🟢 +11.3%
test() on a global regex 43,562 ops/sec [43,174..44,040] → 44,753 ops/sec [44,503..45,601] 🟢 +2.7% 43,376 ops/sec [43,227..43,458] → 47,420 ops/sec [47,259..47,736] 🟢 +9.3%
exec() with capture groups 14,261 ops/sec [14,222..14,272] → 14,493 ops/sec [14,393..14,613] 🟢 +1.6% 16,420 ops/sec [16,185..16,638] → 15,000 ops/sec [14,936..15,077] 🔴 -8.6%
toString() 195,078 ops/sec [193,103..201,761] → 210,070 ops/sec [208,778..211,646] 🟢 +7.7% 235,005 ops/sec [232,346..236,019] → 254,703 ops/sec [253,895..256,420] 🟢 +8.4%
match() with global regex 1,518 ops/sec [1,508..1,530] → 1,543 ops/sec [1,537..1,569] 🟢 +1.7% 1,756 ops/sec [1,736..1,773] → 1,522 ops/sec [1,513..1,535] 🔴 -13.4%
matchAll() with capture groups 3,843 ops/sec [3,786..3,950] → 3,908 ops/sec [3,835..4,009] ~ overlap (+1.7%) 4,456 ops/sec [4,450..4,480] → 4,131 ops/sec [4,080..4,142] 🔴 -7.3%
replace() with global regex 1,560 ops/sec [1,535..1,585] → 1,547 ops/sec [1,510..1,565] ~ overlap (-0.8%) 1,761 ops/sec [1,756..1,766] → 1,522 ops/sec [1,482..1,552] 🔴 -13.5%
search() with regex 1,671 ops/sec [1,649..1,679] → 1,669 ops/sec [1,635..1,682] ~ overlap (-0.1%) 1,943 ops/sec [1,907..1,944] → 1,607 ops/sec [1,588..1,672] 🔴 -17.3%
split() with regex separator 1,357 ops/sec [1,351..1,360] → 1,349 ops/sec [1,335..1,360] ~ overlap (-0.6%) 1,558 ops/sec [1,537..1,569] → 1,319 ops/sec [1,260..1,378] 🔴 -15.4%
strings.js — Interp: 🟢 14, 5 unch. · avg +7.1% · Bytecode: 🟢 15, 4 unch. · avg +6.5%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 150,577 ops/sec [145,064..152,749] → 168,457 ops/sec [159,009..177,643] 🟢 +11.9% 888,435 ops/sec [880,663..896,567] → 1,031,516 ops/sec [969,288..1,088,393] 🟢 +16.1%
template literal 279,386 ops/sec [272,123..289,344] → 313,871 ops/sec [308,229..320,637] 🟢 +12.3% 604,846 ops/sec [573,904..616,101] → 674,264 ops/sec [655,368..714,066] 🟢 +11.5%
string repeat 169,744 ops/sec [167,029..171,138] → 198,984 ops/sec [192,807..206,569] 🟢 +17.2% 199,181 ops/sec [198,033..199,457] → 214,768 ops/sec [212,603..215,202] 🟢 +7.8%
split and join 30,123 ops/sec [29,552..31,082] → 32,179 ops/sec [30,732..32,828] ~ overlap (+6.8%) 27,500 ops/sec [27,285..27,667] → 31,876 ops/sec [31,374..34,131] 🟢 +15.9%
indexOf and includes 53,810 ops/sec [52,864..54,049] → 62,843 ops/sec [60,404..63,940] 🟢 +16.8% 49,768 ops/sec [49,481..50,318] → 54,242 ops/sec [52,254..56,180] 🟢 +9.0%
toUpperCase and toLowerCase 86,150 ops/sec [85,026..87,421] → 97,165 ops/sec [88,613..98,436] 🟢 +12.8% 94,481 ops/sec [94,101..95,676] → 93,501 ops/sec [92,880..94,483] ~ overlap (-1.0%)
slice and substring 52,815 ops/sec [51,170..53,451] → 55,675 ops/sec [54,216..56,691] 🟢 +5.4% 55,411 ops/sec [54,615..56,638] → 59,819 ops/sec [56,638..60,751] 🟢 +8.0%
trim operations 77,300 ops/sec [76,375..78,368] → 82,688 ops/sec [80,312..84,033] 🟢 +7.0% 80,255 ops/sec [79,339..80,587] → 84,312 ops/sec [82,816..86,632] 🟢 +5.1%
replace and replaceAll 80,725 ops/sec [79,410..81,060] → 85,756 ops/sec [84,210..87,273] 🟢 +6.2% 76,761 ops/sec [75,618..77,855] → 78,646 ops/sec [77,768..79,956] ~ overlap (+2.5%)
startsWith and endsWith 50,567 ops/sec [48,745..51,982] → 52,837 ops/sec [51,561..53,636] ~ overlap (+4.5%) 46,207 ops/sec [44,969..46,627] → 47,044 ops/sec [46,523..47,370] ~ overlap (+1.8%)
padStart and padEnd 75,892 ops/sec [75,526..77,599] → 78,843 ops/sec [77,563..80,811] ~ overlap (+3.9%) 74,423 ops/sec [72,937..74,690] → 77,622 ops/sec [76,773..79,176] 🟢 +4.3%
identity tag, no substitutions 171,491 ops/sec [168,318..172,942] → 170,699 ops/sec [167,944..177,742] ~ overlap (-0.5%) 538,756 ops/sec [532,560..547,181] → 603,519 ops/sec [584,330..621,205] 🟢 +12.0%
tag with 1 substitution 36,478 ops/sec [36,097..37,393] → 38,066 ops/sec [37,737..38,187] 🟢 +4.4% 53,046 ops/sec [52,047..54,022] → 54,481 ops/sec [54,194..55,337] 🟢 +2.7%
tag with 3 substitutions 19,857 ops/sec [19,424..20,296] → 20,566 ops/sec [19,822..20,924] ~ overlap (+3.6%) 30,854 ops/sec [30,261..31,505] → 31,929 ops/sec [31,640..32,894] 🟢 +3.5%
tag with 6 substitutions 11,546 ops/sec [11,389..11,574] → 11,920 ops/sec [11,779..12,542] 🟢 +3.2% 18,201 ops/sec [17,974..18,412] → 19,270 ops/sec [18,941..19,428] 🟢 +5.9%
String.raw, no substitutions 234,083 ops/sec [233,176..236,780] → 247,630 ops/sec [239,705..251,541] 🟢 +5.8% 245,694 ops/sec [244,030..246,958] → 243,008 ops/sec [238,545..250,048] ~ overlap (-1.1%)
String.raw, 2 substitutions 169,518 ops/sec [163,680..171,287] → 176,875 ops/sec [171,716..179,179] 🟢 +4.3% 150,457 ops/sec [138,788..151,283] → 160,396 ops/sec [159,197..162,668] 🟢 +6.6%
tag accessing .raw array 70,509 ops/sec [69,124..71,420] → 73,501 ops/sec [71,980..74,045] 🟢 +4.2% 87,766 ops/sec [84,267..90,379] → 92,901 ops/sec [91,543..95,436] 🟢 +5.9%
method as tag (this binding) 26,430 ops/sec [26,170..26,589] → 27,786 ops/sec [27,027..28,090] 🟢 +5.1% 38,777 ops/sec [37,888..40,669] → 41,680 ops/sec [41,111..42,734] 🟢 +7.5%
tsv.js — Interp: 🟢 1, 🔴 1, 7 unch. · avg -1.7% · Bytecode: 🟢 8, 1 unch. · avg +10.8%
Benchmark Interpreted Δ Bytecode Δ
parse simple 3-column TSV 51,771 ops/sec [51,134..52,566] → 51,262 ops/sec [50,372..51,696] ~ overlap (-1.0%) 49,013 ops/sec [45,849..49,560] → 52,121 ops/sec [51,325..53,527] 🟢 +6.3%
parse 10-row TSV 14,637 ops/sec [14,188..14,948] → 14,184 ops/sec [14,162..14,224] ~ overlap (-3.1%) 13,100 ops/sec [12,961..13,571] → 14,167 ops/sec [14,086..14,493] 🟢 +8.1%
parse 100-row TSV 2,279 ops/sec [2,231..2,346] → 2,152 ops/sec [2,046..2,275] ~ overlap (-5.6%) 2,104 ops/sec [2,095..2,153] → 2,216 ops/sec [2,168..2,224] 🟢 +5.3%
parse TSV with backslash-escaped fields 10,693 ops/sec [10,628..10,842] → 10,332 ops/sec [10,220..10,591] 🔴 -3.4% 9,986 ops/sec [9,841..10,170] → 10,345 ops/sec [10,231..10,486] 🟢 +3.6%
parse without headers (array of arrays) 6,448 ops/sec [6,257..6,585] → 6,206 ops/sec [6,087..6,452] ~ overlap (-3.7%) 6,120 ops/sec [6,029..6,187] → 6,360 ops/sec [6,162..6,563] ~ overlap (+3.9%)
stringify array of objects 44,475 ops/sec [44,117..45,168] → 45,584 ops/sec [45,479..45,765] 🟢 +2.5% 40,130 ops/sec [39,082..40,726] → 46,526 ops/sec [46,060..47,744] 🟢 +15.9%
stringify array of arrays 13,120 ops/sec [12,824..13,236] → 13,104 ops/sec [13,036..13,321] ~ overlap (-0.1%) 10,373 ops/sec [10,313..10,735] → 13,256 ops/sec [12,933..13,362] 🟢 +27.8%
stringify with values needing escaping 35,631 ops/sec [35,488..35,883] → 36,268 ops/sec [35,548..36,578] ~ overlap (+1.8%) 31,356 ops/sec [30,957..31,910] → 36,837 ops/sec [36,225..37,492] 🟢 +17.5%
parse then stringify 8,065 ops/sec [7,825..8,102] → 7,814 ops/sec [7,773..7,862] ~ overlap (-3.1%) 7,224 ops/sec [7,171..7,348] → 7,873 ops/sec [7,817..7,934] 🟢 +9.0%
typed-arrays.js — Interp: 🟢 10, 🔴 4, 8 unch. · avg +12.7% · Bytecode: 🟢 11, 🔴 8, 3 unch. · avg -3.9%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 121,474 ops/sec [120,376..122,028] → 128,417 ops/sec [127,045..131,119] 🟢 +5.7% 139,776 ops/sec [138,407..141,719] → 150,439 ops/sec [148,948..152,532] 🟢 +7.6%
new Int32Array(100) 114,472 ops/sec [113,315..117,054] → 120,951 ops/sec [117,653..123,416] 🟢 +5.7% 131,597 ops/sec [130,997..133,897] → 140,170 ops/sec [139,175..142,426] 🟢 +6.5%
new Int32Array(1000) 83,677 ops/sec [83,155..85,360] → 87,051 ops/sec [86,319..87,737] 🟢 +4.0% 101,066 ops/sec [99,794..101,625] → 95,356 ops/sec [94,659..96,651] 🔴 -5.7%
new Float64Array(100) 111,286 ops/sec [110,665..112,022] → 116,002 ops/sec [115,393..117,137] 🟢 +4.2% 125,759 ops/sec [125,578..129,903] → 131,829 ops/sec [130,745..132,432] 🟢 +4.8%
Int32Array.from([...]) 5,188 ops/sec [5,117..5,292] → 5,125 ops/sec [4,945..5,161] ~ overlap (-1.2%) 5,153 ops/sec [5,076..5,223] → 5,411 ops/sec [5,328..5,431] 🟢 +5.0%
Int32Array.of(1, 2, 3, 4, 5) 138,161 ops/sec [136,841..138,536] → 142,210 ops/sec [139,369..145,894] 🟢 +2.9% 151,074 ops/sec [150,402..151,732] → 161,361 ops/sec [160,549..163,522] 🟢 +6.8%
sequential write 100 elements 1,523 ops/sec [1,510..1,555] → 1,547 ops/sec [1,540..1,550] ~ overlap (+1.5%) 6,723 ops/sec [6,674..6,814] → 6,831 ops/sec [6,776..8,014] ~ overlap (+1.6%)
sequential read 100 elements 1,604 ops/sec [1,595..1,616] → 1,597 ops/sec [1,583..1,627] ~ overlap (-0.4%) 6,485 ops/sec [6,385..6,512] → 6,896 ops/sec [6,732..7,019] 🟢 +6.3%
Float64Array write 100 elements 1,412 ops/sec [1,404..1,414] → 1,415 ops/sec [1,403..2,134] ~ overlap (+0.2%) 4,690 ops/sec [4,614..4,710] → 4,725 ops/sec [4,677..4,819] ~ overlap (+0.7%)
fill(42) 28,648 ops/sec [28,293..34,187] → 41,564 ops/sec [41,525..41,759] 🟢 +45.1% 27,070 ops/sec [26,916..27,108] → 27,515 ops/sec [27,473..28,904] 🟢 +1.6%
slice() 113,654 ops/sec [112,639..114,841] → 157,800 ops/sec [155,233..159,618] 🟢 +38.8% 120,698 ops/sec [119,452..121,358] → 135,339 ops/sec [132,891..136,039] 🟢 +12.1%
map(x => x * 2) 2,835 ops/sec [2,808..2,849] → 2,758 ops/sec [2,729..2,782] 🔴 -2.7% 4,026 ops/sec [3,985..4,096] → 4,494 ops/sec [4,489..4,669] 🟢 +11.6%
filter(x => x > 50) 2,838 ops/sec [2,811..2,874] → 2,798 ops/sec [2,762..2,811] 🔴 -1.4% 8,582 ops/sec [8,517..8,610] → 5,155 ops/sec [5,108..5,194] 🔴 -39.9%
reduce (sum) 2,853 ops/sec [2,800..2,882] → 2,773 ops/sec [2,750..2,821] ~ overlap (-2.8%) 7,136 ops/sec [7,097..7,161] → 4,478 ops/sec [4,223..4,629] 🔴 -37.3%
sort() 104,787 ops/sec [103,424..105,848] → 105,910 ops/sec [105,246..107,151] ~ overlap (+1.1%) 150,209 ops/sec [100,598..191,582] → 122,332 ops/sec [120,672..124,890] ~ overlap (-18.6%)
indexOf() 203,061 ops/sec [200,025..205,972] → 303,509 ops/sec [300,732..306,325] 🟢 +49.5% 227,369 ops/sec [223,645..228,492] → 241,281 ops/sec [235,846..243,747] 🟢 +6.1%
reverse() 176,879 ops/sec [169,490..241,013] → 172,710 ops/sec [170,192..242,198] ~ overlap (-2.4%) 172,871 ops/sec [168,742..178,399] → 199,033 ops/sec [198,290..200,373] 🟢 +15.1%
create view over existing buffer 138,437 ops/sec [137,789..230,593] → 224,567 ops/sec [147,401..229,743] ~ overlap (+62.2%) 275,613 ops/sec [273,374..278,167] → 171,806 ops/sec [170,470..173,052] 🔴 -37.7%
subarray() 184,157 ops/sec [181,548..184,465] → 191,848 ops/sec [189,621..293,263] 🟢 +4.2% 369,653 ops/sec [365,766..373,323] → 302,223 ops/sec [220,935..357,879] 🔴 -18.2%
set() from array 215,057 ops/sec [212,137..358,610] → 384,853 ops/sec [378,822..386,875] 🟢 +79.0% 485,546 ops/sec [483,470..487,117] → 476,737 ops/sec [475,206..477,958] 🔴 -1.8%
for-of loop 3,696 ops/sec [3,672..3,719] → 3,500 ops/sec [2,989..3,555] 🔴 -5.3% 19,346 ops/sec [19,193..19,410] → 17,873 ops/sec [17,805..17,900] 🔴 -7.6%
spread into array 14,499 ops/sec [14,423..14,586] → 13,211 ops/sec [13,042..13,455] 🔴 -8.9% 65,665 ops/sec [65,321..66,238] → 61,772 ops/sec [61,501..61,924] 🔴 -5.9%
uint8array-encoding.js — Interp: 🟢 3, 🔴 7, 8 unch. · avg -19.3% · Bytecode: 🟢 12, 🔴 3, 3 unch. · avg +6.0%
Benchmark Interpreted Δ Bytecode Δ
short (5 bytes) 280,735 ops/sec [276,127..282,893] → 291,501 ops/sec [288,981..305,361] 🟢 +3.8% 376,344 ops/sec [365,192..380,160] → 398,660 ops/sec [390,674..406,172] 🟢 +5.9%
medium (450 bytes) 162,818 ops/sec [161,140..167,061] → 162,872 ops/sec [161,894..163,379] ~ overlap (+0.0%) 171,226 ops/sec [170,558..174,429] → 190,567 ops/sec [185,741..194,314] 🟢 +11.3%
large (4096 bytes) 36,478 ops/sec [34,959..37,054] → 33,807 ops/sec [33,242..34,480] 🔴 -7.3% 32,895 ops/sec [32,277..33,451] → 34,928 ops/sec [34,249..35,316] 🟢 +6.2%
base64url alphabet 116,786 ops/sec [115,004..117,347] → 115,326 ops/sec [108,313..116,882] ~ overlap (-1.2%) 109,865 ops/sec [108,248..112,060] → 118,560 ops/sec [117,133..119,326] 🟢 +7.9%
omitPadding 165,490 ops/sec [162,629..169,584] → 180,429 ops/sec [176,061..181,065] 🟢 +9.0% 186,554 ops/sec [185,650..187,077] → 190,649 ops/sec [188,267..194,653] 🟢 +2.2%
short (8 chars) 242,161 ops/sec [149,369..245,011] → 158,986 ops/sec [155,435..162,827] ~ overlap (-34.3%) 152,722 ops/sec [151,832..153,123] → 160,810 ops/sec [158,364..162,116] 🟢 +5.3%
medium (600 chars) 123,618 ops/sec [117,432..124,366] → 76,387 ops/sec [75,932..77,556] 🔴 -38.2% 71,923 ops/sec [71,689..72,037] → 77,254 ops/sec [76,789..79,743] 🟢 +7.4%
large (5464 chars) 25,317 ops/sec [23,933..25,359] → 15,199 ops/sec [14,536..24,700] ~ overlap (-40.0%) 13,692 ops/sec [13,564..13,778] → 14,734 ops/sec [14,544..15,046] 🟢 +7.6%
short (5 bytes) 474,235 ops/sec [473,490..477,378] → 499,617 ops/sec [494,679..502,474] 🟢 +5.4% 440,932 ops/sec [433,736..445,821] → 461,925 ops/sec [458,046..487,803] 🟢 +4.8%
medium (450 bytes) 222,324 ops/sec [221,356..224,686] → 224,692 ops/sec [224,255..225,137] ~ overlap (+1.1%) 166,642 ops/sec [162,965..166,759] → 175,672 ops/sec [174,693..177,384] 🟢 +5.4%
large (4096 bytes) 42,196 ops/sec [39,275..42,413] → 41,138 ops/sec [40,363..41,573] ~ overlap (-2.5%) 26,782 ops/sec [26,104..28,384] → 28,713 ops/sec [28,176..29,628] ~ overlap (+7.2%)
short (10 chars) 256,561 ops/sec [254,819..258,068] → 176,614 ops/sec [171,166..269,980] ~ overlap (-31.2%) 176,895 ops/sec [176,565..177,670] → 183,426 ops/sec [182,738..279,556] 🟢 +3.7%
medium (900 chars) 175,678 ops/sec [173,587..177,195] → 113,716 ops/sec [112,210..116,482] 🔴 -35.3% 118,940 ops/sec [115,219..198,322] → 184,150 ops/sec [182,487..187,186] ~ overlap (+54.8%)
large (8192 chars) 48,215 ops/sec [47,911..48,374] → 29,864 ops/sec [28,870..31,488] 🔴 -38.1% 57,651 ops/sec [57,390..57,993] → 48,768 ops/sec [48,628..49,082] 🔴 -15.4%
setFromBase64 (450 bytes) 119,207 ops/sec [75,175..123,170] → 76,050 ops/sec [75,060..76,539] ~ overlap (-36.2%) 119,890 ops/sec [119,469..120,706] → 128,323 ops/sec [128,005..128,754] 🟢 +7.0%
setFromHex (450 bytes) 164,650 ops/sec [163,682..165,385] → 107,488 ops/sec [107,199..107,744] 🔴 -34.7% 193,321 ops/sec [192,965..194,768] → 179,134 ops/sec [174,577..179,740] 🔴 -7.3%
toBase64 → fromBase64 (450 bytes) 90,936 ops/sec [90,628..91,493] → 59,633 ops/sec [59,109..60,170] 🔴 -34.4% 89,692 ops/sec [89,535..89,822] → 89,600 ops/sec [89,325..89,618] ~ overlap (-0.1%)
toHex → fromHex (450 bytes) 112,352 ops/sec [111,990..113,079] → 75,547 ops/sec [72,763..77,217] 🔴 -32.8% 124,691 ops/sec [124,543..125,513] → 116,788 ops/sec [116,306..117,277] 🔴 -6.3%
weak-collections.js — Interp: 🟢 14, 1 unch. · avg +34.3% · Bytecode: 🟢 10, 🔴 3, 2 unch. · avg +28.8%
Benchmark Interpreted Δ Bytecode Δ
constructor from 50 entries 12,768 ops/sec [12,691..12,791] → 14,179 ops/sec [13,952..14,338] 🟢 +11.0% 12,506 ops/sec [12,027..12,579] → 14,123 ops/sec [14,026..14,539] 🟢 +12.9%
set 50 object keys 4,529 ops/sec [4,408..4,583] → 4,920 ops/sec [4,888..5,171] 🟢 +8.6% 6,295 ops/sec [6,078..6,340] → 6,395 ops/sec [6,373..6,409] 🟢 +1.6%
get lookups (50 entries) 60,255 ops/sec [59,397..60,549] → 65,048 ops/sec [63,853..67,272] 🟢 +8.0% 87,290 ops/sec [86,272..88,053] → 93,723 ops/sec [92,439..95,864] 🟢 +7.4%
has checks (50 entries) 78,304 ops/sec [77,423..78,589] → 83,043 ops/sec [82,261..87,002] 🟢 +6.1% 109,716 ops/sec [107,941..110,023] → 198,183 ops/sec [197,645..199,377] 🟢 +80.6%
delete entries 4,376 ops/sec [4,284..4,423] → 4,660 ops/sec [4,627..4,671] 🟢 +6.5% 5,725 ops/sec [5,616..5,823] → 10,142 ops/sec [9,969..10,210] 🟢 +77.2%
non-registered symbol keys 10,358 ops/sec [10,322..10,436] → 17,256 ops/sec [16,969..17,519] 🟢 +66.6% 13,459 ops/sec [13,394..13,582] → 24,580 ops/sec [24,372..24,785] 🟢 +82.6%
getOrInsert 4,271 ops/sec [4,232..4,298] → 7,167 ops/sec [7,104..7,266] 🟢 +67.8% 5,429 ops/sec [5,370..5,472] → 9,025 ops/sec [8,970..9,098] 🟢 +66.2%
getOrInsertComputed 2,120 ops/sec [2,114..2,161] → 3,375 ops/sec [2,959..3,406] 🟢 +59.2% 2,666 ops/sec [2,655..2,682] → 4,570 ops/sec [4,517..4,595] 🟢 +71.4%
forced gc live-key retention 4,436 ops/sec [4,372..4,592] → 4,677 ops/sec [4,637..4,756] 🟢 +5.4% 5,312 ops/sec [5,294..5,375] → 8,740 ops/sec [8,645..8,786] 🟢 +64.5%
constructor from 50 values 17,102 ops/sec [16,477..17,591] → 29,014 ops/sec [28,642..29,719] 🟢 +69.7% 16,254 ops/sec [16,062..16,469] → 30,047 ops/sec [29,752..30,824] 🟢 +84.9%
add 50 object values 4,936 ops/sec [4,896..4,973] → 5,112 ops/sec [5,076..8,425] 🟢 +3.6% 11,702 ops/sec [11,521..11,759] → 11,700 ops/sec [11,513..11,728] ~ overlap (-0.0%)
has checks (50 values) 80,764 ops/sec [80,012..81,674] → 131,729 ops/sec [129,116..134,152] 🟢 +63.1% 208,291 ops/sec [205,041..209,368] → 202,070 ops/sec [200,542..203,321] 🔴 -3.0%
delete values 13,017 ops/sec [12,877..13,148] → 22,310 ops/sec [20,044..22,598] 🟢 +71.4% 24,984 ops/sec [24,805..25,272] → 16,355 ops/sec [16,248..26,066] ~ overlap (-34.5%)
non-registered symbol values 10,996 ops/sec [10,934..11,001] → 18,669 ops/sec [18,367..18,903] 🟢 +69.8% 26,105 ops/sec [26,036..26,212] → 15,509 ops/sec [15,005..15,663] 🔴 -40.6%
forced gc pruning smoke 8,917 ops/sec [5,358..9,079] → 8,731 ops/sec [8,660..8,848] ~ overlap (-2.1%) 11,229 ops/sec [11,130..11,259] → 6,853 ops/sec [6,808..6,862] 🔴 -39.0%

Deterministic profile diff

Deterministic profile diff: no significant changes.

Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

test262 Conformance

Category Run Passed Δ Pass Failed Pass-rate Δ Rate
built-ins 23,449 14,608 +2 8,837 62.3% ±0pp
harness 116 71 ±0 45 61.2% ±0pp
intl402 3,324 691 +524 2,633 20.8% +15.8pp
language 23,635 12,575 ±0 11,060 53.2% ±0pp
staging 1,484 485 +1 997 32.7% +0.1pp
total 52,008 28,430 +527 23,572 54.7% +1.0pp

Areas closest to 100%

Area Pass rate Δ vs main Passing
built-ins/WeakSet 98.8% ±0pp 84 / 85
built-ins/WeakMap 98.6% ±0pp 139 / 141
language/asi 97.1% ±0pp 99 / 102
Per-test deltas (+527 / -0)

Newly passing (527):

  • built-ins/Array/prototype/toLocaleString/invoke-element-tolocalestring.js
  • built-ins/Number/prototype/toExponential/undefined-fractiondigits.js
  • intl402/BigInt/prototype/toLocaleString/taint-Intl-NumberFormat.js
  • intl402/Collator/builtin.js
  • intl402/Collator/default-options-object-prototype.js
  • intl402/Collator/instance-proto-and-extensible.js
  • intl402/Collator/legacy-regexp-statics-not-modified.js
  • intl402/Collator/length.js
  • intl402/Collator/name.js
  • intl402/Collator/prop-desc.js
  • intl402/Collator/prototype/compare/bound-to-collator-instance.js
  • intl402/Collator/prototype/compare/compare-function-builtin.js
  • intl402/Collator/prototype/compare/compare-function-length.js
  • intl402/Collator/prototype/compare/non-normative-basic.js
  • intl402/Collator/prototype/compare/non-normative-phonebook.js
  • intl402/Collator/prototype/compare/non-normative-sensitivity.js
  • intl402/Collator/prototype/constructor/prop-desc.js
  • intl402/Collator/prototype/constructor/value.js
  • intl402/Collator/prototype/resolvedOptions/builtin.js
  • intl402/Collator/prototype/resolvedOptions/ignorePunctuation-not-default.js
  • intl402/Collator/prototype/resolvedOptions/length.js
  • intl402/Collator/prototype/resolvedOptions/name.js
  • intl402/Collator/prototype/resolvedOptions/prop-desc.js
  • intl402/Collator/prototype/this-value-collator-prototype.js
  • intl402/Collator/prototype/this-value-not-collator.js
  • intl402/Collator/prototype/toStringTag/toString-changed-tag.js
  • intl402/Collator/prototype/toStringTag/toString.js
  • intl402/Collator/prototype/toStringTag/toStringTag.js
  • intl402/Collator/supportedLocalesOf/builtin.js
  • intl402/Collator/supportedLocalesOf/length.js
  • intl402/Collator/supportedLocalesOf/name.js
  • intl402/Collator/supportedLocalesOf/prop-desc.js
  • intl402/Collator/supportedLocalesOf/taint-Object-prototype.js
  • intl402/Collator/test-option-ignorePunctuation.js
  • intl402/Collator/this-value-ignored.js
  • intl402/Collator/unicode-ext-seq-in-private-tag.js
  • intl402/constructors-string-and-single-element-array.js
  • intl402/Date/prototype/toLocaleString/default-options-object-prototype.js
  • intl402/DateTimeFormat/builtin.js
  • intl402/DateTimeFormat/canonicalize-timezone.js
  • intl402/DateTimeFormat/canonicalize-utc-timezone.js
  • intl402/DateTimeFormat/casing-numbering-system-calendar-options.js
  • intl402/DateTimeFormat/constructor-calendar-numberingSystem-order.js
  • intl402/DateTimeFormat/constructor-default-value.js
  • intl402/DateTimeFormat/constructor-no-instanceof.js
  • intl402/DateTimeFormat/constructor-options-dateStyle-valid.js
  • intl402/DateTimeFormat/constructor-options-dayPeriod-valid.js
  • intl402/DateTimeFormat/constructor-options-fractionalSecondDigits-valid.js
  • intl402/DateTimeFormat/constructor-options-order-dayPeriod.js
  • intl402/DateTimeFormat/constructor-options-timeStyle-valid.js
  • intl402/DateTimeFormat/constructor-options-timeZoneName-valid.js
  • intl402/DateTimeFormat/instance-proto-and-extensible.js
  • intl402/DateTimeFormat/intl-legacy-constructed-symbol-on-unwrap.js
  • intl402/DateTimeFormat/intl-legacy-constructed-symbol.js
  • intl402/DateTimeFormat/legacy-regexp-statics-not-modified.js
  • intl402/DateTimeFormat/length.js
  • intl402/DateTimeFormat/name.js
  • intl402/DateTimeFormat/numbering-system-calendar-options.js
  • intl402/DateTimeFormat/prop-desc.js
  • intl402/DateTimeFormat/prototype/constructor/prop-desc.js
  • intl402/DateTimeFormat/prototype/constructor/value.js
  • intl402/DateTimeFormat/prototype/format/format-function-builtin.js
  • intl402/DateTimeFormat/prototype/format/format-function-length.js
  • intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js
  • intl402/DateTimeFormat/prototype/format/temporal-zoneddatetime-not-supported.js
  • intl402/DateTimeFormat/prototype/formatRange/argument-tonumber-throws.js
  • intl402/DateTimeFormat/prototype/formatRange/builtin.js
  • intl402/DateTimeFormat/prototype/formatRange/fails-on-distinct-temporal-types.js
  • intl402/DateTimeFormat/prototype/formatRange/length.js
  • intl402/DateTimeFormat/prototype/formatRange/name.js
  • intl402/DateTimeFormat/prototype/formatRange/prop-desc.js
  • intl402/DateTimeFormat/prototype/formatRange/temporal-objects-throws-with-different-calendars.js
  • intl402/DateTimeFormat/prototype/formatRange/temporal-zoneddatetime-not-supported.js
  • intl402/DateTimeFormat/prototype/formatRange/this-bad-object.js
  • intl402/DateTimeFormat/prototype/formatRange/this-is-not-object-throws.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/date-undefined-throws.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/fails-on-distinct-temporal-types.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-throws-typeerror-with-dissimilar-types.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-objects-throws-with-different-calendars.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/temporal-zoneddatetime-not-supported.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/this-bad-object.js
  • intl402/DateTimeFormat/prototype/formatRangeToParts/this-is-not-object-throws.js
  • intl402/DateTimeFormat/prototype/formatToParts/formatToParts.js
  • intl402/DateTimeFormat/prototype/formatToParts/length.js
  • intl402/DateTimeFormat/prototype/formatToParts/name.js
  • intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js
  • intl402/DateTimeFormat/prototype/formatToParts/return-abrupt-tonumber-date.js
  • intl402/DateTimeFormat/prototype/formatToParts/temporal-zoneddatetime-not-supported.js
  • intl402/DateTimeFormat/prototype/formatToParts/this-has-not-internal-throws.js
  • intl402/DateTimeFormat/prototype/formatToParts/this-is-not-object-throws.js
  • intl402/DateTimeFormat/prototype/formatToParts/time-clip-to-integer.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/builtin.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/length.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/name.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/no-instanceof.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js
  • intl402/DateTimeFormat/prototype/resolvedOptions/prop-desc.js
  • intl402/DateTimeFormat/prototype/this-value-datetimeformat-prototype.js
  • intl402/DateTimeFormat/prototype/this-value-not-datetimeformat.js
  • intl402/DateTimeFormat/prototype/toStringTag/toString-changed-tag.js
  • … 427 more

Steady-state failures are non-blocking; regressions vs the cached main baseline (lower total pass count, or any PASS → non-PASS transition) fail the conformance gate. Measured on ubuntu-latest x64, bytecode mode. Areas grouped by the first two test262 path components; minimum 25 attempted tests, areas already at 100% excluded. Δ vs main compares against the most recent cached main baseline.

frostney and others added 14 commits May 9, 2026 10:05
toLocaleUpperCase/toLocaleLowerCase and localeCompare now only route
through ICU when a locale argument is explicitly provided. Without an
explicit locale, they fall back to the non-locale variants, preserving
NUL character handling and avoiding context-sensitive casing differences
(e.g. Greek final sigma) that changed observable behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Locale methods (toLocaleUpperCase, toLocaleLowerCase, localeCompare) now
always route through ICU using the default locale, matching ECMA-402.
NUL character handling fixed by using Move with explicit length instead
of PAnsiChar on ICU casemap output. Greek final-sigma test updated to
expect the correct Unicode SpecialCasing behavior.

DefaultLocale now uses uloc_getDefault for the system locale instead of
returning the first available locale alphabetically. supportedLocalesOf
added to all 10 Intl constructors. NumberFormat validates required
currency/unit options and digit range constraints per spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- IntlICU: fix uloc_getDefault missing call parentheses and nil check,
  fix icsCase collator sensitivity to use UCOL_PRIMARY + UCOL_CASE_LEVEL
  instead of wrong UCOL_PRIMARY alone, check ucol_setAttribute status
- IntlNumberFormat: fix tautological digit range validation that never
  rejected values > 100 / > 21
- BCP47: reject lone "x" without subtags, reject empty subtags from
  consecutive hyphens, reject duplicate variants and duplicate extension
  singletons per RFC 5646, lowercase private-use in NormalizeBcp47Case
- ICU: reset Linux UCHandle per loop iteration, unload on finalization
- IntlLocaleResolver: BestAvailableLocale singleton-skip loop per
  ECMA-402 §9.2.2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strings containing embedded NUL characters (U+0000) were silently
truncated at the NUL boundary when passed to ICU C APIs, causing
option values like "base\0cookie" to be accepted as "base". Per
ECMA-402, these must throw RangeError.

Added ContainsNulCharacter validator to IntlTypes and wired NUL
rejection into BCP47 tag parsing, all 10 Intl constructors' option
reading, Locale constructor properties, RelativeTimeFormat unit
arguments, and supportedLocalesOf localeMatcher handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests that assert ICU-specific output (grouping separators, currency
symbols, plural rule selection, segmenter decomposition, final sigma)
now use test.runIf(hasICU) or conditional expects so they skip cleanly
on CI runners where libicu is unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Linux, ICU functions are split across libicui18n.so (formatting) and
libicuuc.so (locale utilities). TryGetICULibraryHandle returned only the
libicui18n handle, so uloc_* symbols from libicuuc were never found —
making ICU appear unavailable on Linux even when both libraries loaded.

Added ICUGetProcAddress that resolves from both handles on Linux. Updated
IntlICU and Temporal TimeZone to use it. Reverted test.runIf(hasICU)
guards — tests now assert unconditionally since ICU will be available on
all CI platforms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…matters

scripts/generate-intl-data.js downloads CLDR 45.0 JSON data from npm,
extracts plural rules, number symbols/patterns, list patterns, relative
time patterns, display names, currency data, likely subtags, and alias
tables for 20 locales, packs into a 1.1 MB binary resource with the same
container format as timezone data, and generates Generated.IntlData.res
via fpcres.

IntlCLDRData.pas now reads the embedded resource on first access and
provides binary-search lookups with locale subtag fallback. Each value
type falls back to CLDR data when ICU is unavailable:
- NumberFormat: locale-specific grouping/decimal/currency/percent symbols
- ListFormat: CLDR conjunction/disjunction/unit templates
- PluralRules: evaluates zero/one/two categories from CLDR rule data
- Segmenter: UTF-8 grapheme/word/sentence splitting via TextSemantics
- RelativeTimeFormat: CLDR future/past templates with {0} substitution
- DisplayNames: CLDR language/territory/script name lookups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLDR resource loading now reads via TResourceStream on each lookup call,
matching Temporal's TryGetEmbeddedTimeZoneFile pattern. Removed module-
level CachedResource, CachedResourceLoaded, CachedHeader, and
EnsureResourceLoaded. Each lookup function reads the resource, parses
the header, finds the section, and extracts data in a single call chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IntlCLDRData.pas was in source/shared/ with {$I Shared.inc}, so
GOCCIA_INTL_EMBEDDED_CLDR was never defined and all lookup functions
compiled as stubs returning False. Moved to source/units/ as
Goccia.Intl.CLDRData.pas with {$I Goccia.inc}, exactly matching
Goccia.Temporal.TimeZoneData.pas.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Goccia.Intl.CLDRData was only referenced from IntlLocaleResolver (a
shared unit). With production smart linking (-CX -XX), the resource
from Generated.IntlData was not pulled into the binary. Adding the
unit to Goccia.Builtins.Intl uses clause ensures it participates in
the engine compilation chain, matching how Goccia.Temporal.TimeZoneData
is referenced from Goccia.Temporal.TimeZone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generate-intl-data.js now sorts key-value entries alphabetically before
packing, matching the binary search assumption in TryGetKeyValue. Without
sorting, keys like "end" and "middle" in list-patterns were unreachable —
only keys that happened to land at the binary search midpoint were found.

NumberFormat CLDR fallback now maps common currency codes (USD→$, EUR→€,
GBP→£, JPY/CNY→¥) to symbols when TryGetCurrencyInfo returns no data,
instead of using the raw currency code as the symbol.

toLocaleLowerCase test accepts both ς (ICU final-sigma) and σ (non-ICU)
since CLDR does not include context-sensitive case mapping rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ndle

significantDigits validation used > 0 as the guard, allowing 0 through
when ECMA-402 requires [1, 21]. Changed to >= 0 with explicit < 1 check.

ICU library handle now unloaded in finalization on all platforms, matching
the UCHandle cleanup pattern on Linux.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…plate

EmbeddedResourceReader.pas: extract HasBytesAvailable, ReadUInt32LE,
TryUInt32ToInteger, CopyStringFromBytes from both TimeZoneData and
CLDRData into a shared unit — eliminates four duplicate functions.

Resource caching: both Temporal TimeZoneData and Intl CLDRData now
cache the resource buffer on first read. Temporal reads 723 KB on
every timezone lookup; Intl reads 1.1 MB per format() call (4-5 symbol
lookups). Caching eliminates ~4.4 MB allocation per format() call.

Goccia.Intl.Helpers.pas: extract FormatPartsToArray (was duplicated
in NumberFormat and DateTimeFormat) and ReadValidatedStringOption /
TryReadStringOption (NUL-validated option reading boilerplate repeated
~47 times across 10 value types).

IntlICU EnsureLoaded: check-before-lock guard avoids critical section
enter/leave on the hot path after ICU is loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney marked this pull request as ready for review May 10, 2026 13:35
@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification internal Refactoring, CI, tooling, cleanup labels May 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
source/units/Goccia.Temporal.TimeZoneData.pas (1)

231-257: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Synchronize the embedded-resource cache initialization.

CachedTZResource and CachedTZResourceLoaded are process-wide mutable globals, but this cold path reads and writes them without any synchronization. Two callers can race through the first-load path and publish/read the managed TBytes state concurrently, which makes the cache nondeterministic once timezone lookups happen from more than one thread.

🤖 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 `@source/units/Goccia.Temporal.TimeZoneData.pas` around lines 231 - 257, The
cache access must be synchronized: introduce a global lock (e.g.
CachedTZResourceLock: TCriticalSection) and use double-checked locking around
CachedTZResource/CachedTZResourceLoaded so only the assignment/publish is
protected but the resource I/O is not held under the lock; specifically, before
creating TResourceStream check CachedTZResourceLoaded under the lock (or check
once without lock then Enter the lock and re-check), if still not loaded perform
the Stream read into a local ABuffer, then Enter the lock, re-check
CachedTZResourceLoaded, and if still false assign CachedTZResource := ABuffer
and set CachedTZResourceLoaded := True, finally Leave the lock and ensure Stream
is freed in a try/finally; this ensures safe publication of the managed TBytes
and prevents races on CachedTZResource/CachedTZResourceLoaded.
🟠 Major comments (27)
scripts/test262_compatibility_roadmap.json-165-205 (1)

165-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the new Intl statuses to the roadmap allowlist.

These entries now use implemented/in-progress, but eligibleStatuses still only admits supported, active, and compat. Any consumer that keys off the allowlist will keep treating the newly implemented Intl features as non-eligible until the schema is updated or these entries are mapped back onto an existing eligible status.

Also applies to: 1271-1272

🤖 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 `@scripts/test262_compatibility_roadmap.json` around lines 165 - 205, The
roadmap's allowlist validation currently only accepts "supported", "active", and
"compat" via the eligibleStatuses configuration but new Intl entries use
"implemented" and "in-progress"; update the schema/allowlist by adding
"implemented" and "in-progress" to eligibleStatuses (or add a mapping layer that
treats those values as eligible) so keys such as "Intl.DateTimeFormat",
"Intl.DurationFormat", and "Intl.Era-monthcode" are recognized as eligible;
ensure the change also covers the duplicate occurrences noted around the other
lines (the other mention at lines referenced in the review).
scripts/generate-intl-data.js-208-305 (1)

208-305: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Embedded fallback data is missing required Intl variants and categories.

The generated payload only includes long-form list patterns, the base relative-time fields, three DisplayNames categories, and currency fraction metadata. On builds that rely on embedded CLDR, that leaves no data for Intl.ListFormat short/narrow, Intl.RelativeTimeFormat short/narrow, Intl.DisplayNames types like currency/calendar/dateTimeField, or localized currency symbol/name rendering. That’s a real behavior gap for the ICU-less fallback path this PR is introducing.

Also applies to: 401-422

🤖 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 `@scripts/generate-intl-data.js` around lines 208 - 305, The embedded CLDR
extraction is incomplete: update extractListPatterns to also pull short and
narrow variants (e.g., listPattern-type-short / -narrow) and include their
pair/start/middle/end entries; update extractRelativeTimePatterns to extract
short and narrow forms (look for relativeTime-type-short and
relativeTime-type-narrow in dateFields.json and include their
future/past/relative-* entries alongside the long forms); expand
extractDisplayNames to return additional categories/types (include currency,
calendar, dateTimeField and any types under localeDisplayNames that the runtime
needs, not just languages/territories/scripts); and enhance extractCurrencyData
to include localized currency names/symbols (pull from cldr-numbers-modern
main/*/currencies.json or other currency name sources) so Intl currency display
works. Locate these changes in the functions extractListPatterns,
extractRelativeTimePatterns, extractDisplayNames, and extractCurrencyData and
add the extra variant/category parsing and merge into the returned result
objects.
source/shared/ICU.pas-27-31 (1)

27-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The Linux ICU version range is too narrow and excludes current production distributions.

This loader checks only ICU versions 70–76. Current distributions ship:

  • RHEL 9 (enterprise stable): version 67 — below minimum
  • Arch Linux & openSUSE Tumbleweed: version 78 — above maximum
  • Debian testing/unstable: version 78 — above maximum

Users on these distros will see ICU unavailable even when it is installed, unless unversioned libicui18n.so/libicuuc.so symlinks exist. Widen the range or discover installed sonames dynamically instead of hardcoding the bounds.

🤖 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 `@source/shared/ICU.pas` around lines 27 - 31, The hardcoded ICU range
(ICU_VERSION_MIN / ICU_VERSION_MAX) is too narrow and excludes common distro
sonames; update ICU_VERSION_MIN to 67 and ICU_VERSION_MAX to 78 (and keep
ICU_I18N_BASE / ICU_UC_BASE as fallbacks) or, preferably, replace the hard
bounds with a dynamic discovery: attempt to dlopen the unversioned ICU_I18N_BASE
/ ICU_UC_BASE first and if that fails iterate available sonames (e.g., try
libicui18n.so.78 down to .67) until a successful load; ensure the code that
references ICU_VERSION_MIN/ICU_VERSION_MAX or the loader logic uses the new
widened range or the dynamic probe instead.
source/units/Goccia.Values.StringObjectValue.pas-2009-2027 (1)

2009-2027: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

localeCompare does not forward the options parameter to ICU collation settings.

The third argument (options object) is never accessed or parsed. Instead, the ICU call hard-codes sensitivity to icsVariant (quaternary) and ignorePunctuation to False. This prevents proper handling of ECMA-402 options like { sensitivity: "base" }, { ignorePunctuation: true }, usage, numeric, caseFirst, and collation, which should be read from the options parameter and mapped to corresponding ICU collation strengths and settings.

🤖 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 `@source/units/Goccia.Values.StringObjectValue.pas` around lines 2009 - 2027,
In TGocciaStringObjectValue.StringLocaleCompare, read and parse the third
argument (AArgs.GetElement(2)) when present (and not
TGocciaUndefinedLiteralValue) instead of hard-coding icsVariant and False;
extract options fields (sensitivity, ignorePunctuation, usage, numeric,
caseFirst, collation) from that object, map sensitivity -> ICU strength (e.g.
"base"->icsPrimary, "accent"->icsSecondary, "case"->icsTertiary,
"variant"->icsVariant), set ignorePunctuation to the corresponding boolean,
enable numeric collation if numeric=true, apply caseFirst mapping to ICU case
ordering, and pass the resulting strength/flags/locale/collation into
TryICUCompareStrings (replace the fixed icsVariant/False values). Locate this
logic in StringLocaleCompare and update how ICUResult parameters are constructed
before calling TryICUCompareStrings.
source/shared/BCP47.pas-294-300 (1)

294-300: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add per-subtag validation for private-use subtags and normalize case in canonicalization.

The parser accepts private-use tails verbatim without validating that each subtag is 1–8 alphanumerics per RFC 5646. Tags like x-abcdefghi (9 chars) or en-x-abc def (space) would incorrectly validate. Additionally, the canonicalizer returns private-use-only tags without case normalization: line 432 returns ATag.PrivateUse directly instead of using the normalized result from NormalizeBcp47Case, so x-FOO-Bar stays uppercase instead of becoming x-foo-bar.

Split each private-use tail by hyphens, validate each segment as 1–8 alphanumerics during parsing, store the lowercase form, and ensure the private-use-only branch at line 432 applies case normalization before returning.

Also applies to: 366–375

🤖 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 `@source/shared/BCP47.pas` around lines 294 - 300, When handling private-use
tags in the parser (the branch that checks IsPrivateUsePrefix(Subtags[0]) and
sets Result.PrivateUse/Result.IsValid), split the tail (everything after the 'x'
prefix) by '-' and validate each subtag is 1–8 characters and contains only
ASCII alphanumerics; if any subtag fails, treat the tag as invalid/Exit; when
storing Result.PrivateUse store the normalized lowercase form for each subtag
(e.g., join the validated lowercased segments with '-'). Repeat the same
per-subtag validation+lowercasing for the other private-use parsing branch
referenced in the comment (the similar block around SubtagCount handling).
Finally, in the canonicalization/return path that currently returns
ATag.PrivateUse directly, return the case-normalized private-use string produced
by calling NormalizeBcp47Case (or the same per-subtag lowercased value) so
private-use-only tags are returned in lowercase form.
source/shared/IntlICU.pas-701-738 (1)

701-738: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Apply number options to the ICU formatter before formatting.

The function receives TIntlNumberFormatOptions with 15 fields (Currency, CurrencyDisplay, CurrencySign, UnitIdentifier, UnitDisplay, Notation, CompactDisplay, SignDisplay, UseGrouping, RoundingMode, TrailingZeroDisplay, MinimumIntegerDigits, MinimumFractionDigits, MaximumFractionDigits) but only uses Style. The remaining options are silently ignored. ICU's legacy unum_* APIs support these settings via unum_setAttribute (for digit ranges, grouping, sign display) and unum_setTextAttribute (for currency code), which are already loaded in IntlFunctions but never called after UnumOpen.

🤖 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 `@source/shared/IntlICU.pas` around lines 701 - 738, TryICUFormatNumber
currently only sets ICU style and never applies the other
TIntlNumberFormatOptions fields; update the function to call the appropriate
IntlFunctions UnumSetAttribute and UnumSetTextAttribute APIs after creating
Formatter (and before UnumFormatDouble) to apply options such as UseGrouping,
MinimumIntegerDigits, MinimumFractionDigits, MaximumFractionDigits,
RoundingMode, SignDisplay and any currency/unit text via UnumSetTextAttribute
(and numeric flags via UnumSetAttribute); reference the option names (e.g.,
AOptions.UseGrouping, AOptions.MinimumIntegerDigits,
AOptions.MaximumFractionDigits, AOptions.RoundingMode, AOptions.SignDisplay,
AOptions.Currency) and the ICU wrapper functions
(IntlFunctions.UnumSetAttribute, IntlFunctions.UnumSetTextAttribute) so the
formatter honors all provided fields before formatting.
source/shared/IntlICU.pas-768-830 (1)

768-830: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Component-based DateTimeFormat options are dropped on the ICU path.

When dateStyle and timeStyle are both unset, this falls back to UDAT_MEDIUM for both instead of honoring component options like year, month, day, hour, minute, second, and timeZoneName. Calls such as { year: "numeric" } will therefore format as a medium date+time string rather than the requested field set.

To fix this, use the ICU DateTimePatternGenerator API: convert ECMA-402 component options to a skeleton string (e.g., year="numeric""y", month="short""MMM", day="numeric""d"), then call getBestPattern(skeleton) to generate a localized pattern for SimpleDateFormat.

🤖 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 `@source/shared/IntlICU.pas` around lines 768 - 830, TryICUFormatDateTime
currently falls back to UDAT_MEDIUM when both AOptions.DateStyle and
AOptions.TimeStyle are unset, losing component-based options
(year/month/day/hour/...). Instead, when dateStyle/timeStyle are idtsNone build
an ICU skeleton from AOptions component fields (e.g., year="numeric"→"y",
month="short"→"MMM", day="numeric"→"d", hour/minute/second and timeZoneName) and
use the ICU DateTimePatternGenerator APIs to call getBestPattern(skeleton) to
produce a localized SimpleDateFormat pattern, then create the formatter with
that pattern (via the IntlFunctions pattern/generator wrappers) and format;
update TryICUFormatDateTime to branch: if both styles are unset use the
skeleton→getBestPattern path, otherwise keep the existing DateTimeStyleToICU
path, and ensure timezone handling (TzPtr/TzLen) is passed into the
pattern-based formatter creation and closing (IntlFunctions.*).
source/shared/IntlICU.pas-741-752 (1)

741-752: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Implement semantic field extraction using ICU's FormattedValue APIs for formatToParts compliance.

Both TryICUFormatNumberToParts (lines 741–752) and TryICUFormatDateTimeToParts (lines 833–844) collapse the formatted result into a single { type: "literal", value: ... } part, losing all semantic information (integer, group, currency, year, month, etc.). ECMA-402 formatToParts implementations require field-by-field semantic breakdown.

ICU exposes this capability through modern FormattedValue APIs (FormattedNumber, FormattedDateTime) via nextPosition(ConstrainedFieldPosition) iteration (available since ICU 64.0). The current codebase only binds legacy ICU C functions (unum_formatDouble, udat_format) with FieldPosition, which are insufficient for complete field extraction.

To fix: Add Pascal bindings for modern ICU formatting APIs and iterate field positions to populate the semantic part types.

🤖 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 `@source/shared/IntlICU.pas` around lines 741 - 752, The current
TryICUFormatNumberToParts and TryICUFormatDateTimeToParts return a single
"literal" part; replace this with real field extraction by adding Pascal
bindings for ICU's modern FormattedValue APIs (FormattedNumber /
FormattedDateTime and ConstrainedFieldPosition / nextPosition) and using them to
iterate semantic field ranges; for TryICUFormatNumberToParts call the
FormattedNumber formatter, loop nextPosition to get each field name and
substring, map ICU field identifiers to our TIntlFormatPartArray.PartType values
(integer, group, fraction, currency, etc.), and append parts with correct Value
and PartType; do the analogous binding+iteration for TryICUFormatDateTimeToParts
to extract year/month/day/hour/minute/second and literal separators instead of
collapsing into one literal.
tests/built-ins/Intl/nul-character-validation.js-8-157 (1)

8-157: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add per-constructor guards for optional Intl APIs.

This suite only checks typeof Intl !== "undefined" but directly references optional constructors like Intl.DisplayNames, Intl.Segmenter, Intl.DurationFormat, Intl.ListFormat, Intl.RelativeTimeFormat, and Intl.Locale. These are not guaranteed to exist on all Intl-supporting runtimes. Other Intl test files in this repository use per-constructor guards (e.g., describe.runIf(isIntl && typeof Intl.DisplayNames !== "undefined")) for exactly these optional APIs. Without similar guards, this file will crash with a runtime error before reaching the NUL-validation assertions if any optional constructor is missing. Split tests into per-constructor guarded blocks to skip cleanly instead of failing the entire suite.

🤖 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 `@tests/built-ins/Intl/nul-character-validation.js` around lines 8 - 157, The
test suite currently only checks isIntl but directly calls optional APIs
(Intl.DisplayNames, Intl.Segmenter, Intl.DurationFormat, Intl.ListFormat,
Intl.RelativeTimeFormat, Intl.Locale and methods like
Intl.Collator.supportedLocalesOf and Intl.getCanonicalLocales) which can be
undefined; split or wrap the tests that reference each optional
constructor/method in per-constructor guards using describe.runIf(isIntl &&
typeof Intl.DisplayNames !== "undefined") (and similarly for Intl.Segmenter,
Intl.DurationFormat, Intl.ListFormat, Intl.RelativeTimeFormat, Intl.Locale) and
guard calls to Intl.Collator.supportedLocalesOf and Intl.getCanonicalLocales
with typeof checks so each group is skipped if the specific API is missing
rather than crashing the whole suite.
source/units/Goccia.Values.IntlCollator.pas-111-124 (1)

111-124: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Forward numeric, caseFirst, collation, and usage options to the comparison function.

These options are parsed from the constructor (lines 111-124) and returned in resolvedOptions() (lines 232-237), but the two compare paths—BoundCompare (lines 198-199) and IntlCollatorCompare (lines 217-218)—only pass sensitivity and ignorePunctuation to TryICUCompareStrings. This means numeric, caseFirst, collation, and usage have no effect on sort order, making calls like { numeric: true } or { caseFirst: "upper" } observably incorrect and non-compliant with ECMA-402.

🤖 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 `@source/units/Goccia.Values.IntlCollator.pas` around lines 111 - 124, The
compare paths are only passing FSensitivity and FIgnorePunctuation to
TryICUCompareStrings, so FNumeric, FCaseFirst, FCollation and FUsage are never
used; update the calls in BoundCompare and IntlCollatorCompare to forward
FNumeric, FCaseFirst, FCollation and FUsage along with FSensitivity and
FIgnorePunctuation to TryICUCompareStrings, and if necessary extend
TryICUCompareStrings' signature/implementation to accept and apply these four
options when invoking ICU string comparison; reference the parsed fields
FSensitivity, FIgnorePunctuation, FNumeric, FCaseFirst, FCollation and FUsage
and the functions BoundCompare, IntlCollatorCompare, and TryICUCompareStrings.
source/units/Goccia.Values.IntlNumberFormat.pas-247-254 (1)

247-254: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CLDR fallback depends on the process decimal separator.

FormatFloat uses the current FormatSettings, but the code later searches for '.' unconditionally. On hosts whose decimal separator is ',', the fallback misparses fractional output when ICU is unavailable.

Based on learnings: locale-independent numeric formatting sites in this repo must pin FS.DecimalSeparator := '.' right after FS := DefaultFormatSettings.

🤖 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 `@source/units/Goccia.Values.IntlNumberFormat.pas` around lines 247 - 254, The
code builds RawStr via FormatFloat(FormatSpec, AbsValue) but then looks for '.'
unconditionally (DotPos := Pos('.', RawStr)), which fails when the current
DefaultFormatSettings.DecimalSeparator is ','; to fix, create and use a
locale-independent FormatSettings instance (e.g. FS := DefaultFormatSettings;
FS.DecimalSeparator := '.'); pass that FS into FormatFloat (use
FormatFloat(FormatSpec, AbsValue, FS)) so RawStr always uses '.' and the
subsequent DotPos/FracPart/IntPart parsing works correctly; update usages in
IntlNumberFormat-related functions (FormatSpec, RawStr, DotPos handling)
accordingly.
source/units/Goccia.Values.IntlNumberFormat.pas-271-272 (1)

271-272: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

useGrouping is stored but never applied in the fallback formatter.

The CLDR path always inserts grouping separators, so resolvedOptions().useGrouping can disagree with format() output on non-ICU builds.

🤖 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 `@source/units/Goccia.Values.IntlNumberFormat.pas` around lines 271 - 272, The
fallback formatter always applies grouping by calling
InsertGroupingSeparator(IntPart, GroupSep), causing
resolvedOptions().useGrouping to be inaccurate; update the formatting logic (the
routine that builds IntPart — e.g., where IntPart :=
InsertGroupingSeparator(IntPart, GroupSep) is called) to only insert GroupSep
when the instance's useGrouping flag is true (check the formatter's stored
useGrouping property) and ensure resolvedOptions().useGrouping reflects that
same flag so format() output and resolvedOptions() remain consistent.
source/units/Goccia.Values.IntlListFormat.pas-92-105 (1)

92-105: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Only arrays are consumed; other iterables format as an empty list.

ExtractStringArray returns [] for every non-TGocciaArrayValue, so Set, generators, and custom iterables silently format as '' instead of going through the spec’s iterable consumption path.

Also applies to: 280-287

🤖 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 `@source/units/Goccia.Values.IntlListFormat.pas` around lines 92 - 105, The
ExtractStringArray function currently treats only TGocciaArrayValue as iterable
and returns an empty array for other iterable types; modify ExtractStringArray
(and the duplicate logic around the other occurrence) to detect and consume any
iterable TGocciaValue (e.g., types implementing the iterable interface or a
TGocciaIterableValue) by enumerating its elements rather than only
TGocciaArrayValue, converting each element to its string literal
(Element.ToStringLiteral.Value) and building the TStringArray from that
enumeration so Sets, generators and custom iterables are handled the same as
arrays.
source/units/Goccia.Builtins.Intl.pas-644-665 (1)

644-665: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

supportedLocalesOf() becomes empty on CLDR-only builds.

The available-locale set is sourced only from ICU. If ICU loading fails but the embedded CLDR fallback still powers the constructors, every supportedLocalesOf() call returns [], which no longer matches actual Intl support.

🤖 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 `@source/units/Goccia.Builtins.Intl.pas` around lines 644 - 665, The code only
populates AvailableTags when TryICUGetAvailableLocales succeeds, so when ICU
fails supportedLocalesOf returns empty even though CLDR-backed constructors
(Canonical) exist; change the logic in the block around
TryICUGetAvailableLocales/AvailableTags so that if TryICUGetAvailableLocales
returns false you populate AvailableTags from the embedded CLDR canonical list
(use the existing Canonical array or a CLDR fallback source, canonicalizing as
needed with TryICUCanonicalizeLocale or equivalent) before calling
BestAvailableLocale; ensure AvailableTags ends up containing CLDR-derived tags
so BestAvailableLocale(Canonical[I]) can find matches when ICU is absent.
source/units/Goccia.Values.IntlListFormat.pas-311-324 (1)

311-324: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

formatToParts() hardcodes comma literals instead of the locale pattern.

This builder discards the actual ICU/CLDR formatting pattern and always emits ', ' between elements. That makes formatToParts() disagree with format() for conjunction/disjunction and for locales whose list separators are not commas.

🤖 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 `@source/units/Goccia.Values.IntlListFormat.pas` around lines 311 - 324, The
loop in formatToParts currently inserts a hardcoded ', ' literal (creating
TGocciaStringLiteralValue with ', ') which breaks locale-specific list
formatting; replace that hardcoded literal with the locale-derived
separator/pattern from the list formatter used by Intl.ListFormat (do not create
a constant ', '). Locate the list formatting code (the method implementing
formatToParts / the class that builds parts) and, instead of inserting
TGocciaStringLiteralValue.Create(', '), call the existing list/ICU pattern
helper (or add a small helper) that returns the correct literal for the current
position (e.g., GetListLiteralForPosition or the formatter instance used by
format()) and pass that string into TGocciaStringLiteralValue.Create; keep
creating the 'literal' PartObj but use the locale pattern value so formatToParts
matches format() for conjunctions/disjunctions and non-comma locales.
source/units/Goccia.Values.IntlPluralRules.pas-262-271 (1)

262-271: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

selectRange() currently ignores the start category.

This path computes StartCat, then discards it and always returns EndCat. Plural-range resolution is locale-specific, so ranges will be misclassified whenever ICU range support is unavailable.

🤖 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 `@source/units/Goccia.Values.IntlPluralRules.pas` around lines 262 - 271,
selectRange currently computes StartCat then ignores it and always returns
EndCat; update selectRange to perform ECMA-402 plural-range resolution when ICU
range support is unavailable by combining StartCat and EndCat instead of
returning EndCat directly. Specifically, after obtaining StartCat and EndCat via
TryICUSelectPlural/SelectPluralWithCLDR, call or implement a plural-range
resolution helper (e.g., SelectPluralRangeWithCLDR or similar) that takes
StartCat, EndCat and PR.FLocale (and PluralTypeStringToEnum(PR.FType) as needed)
and use its returned category when creating the TGocciaStringLiteralValue result
instead of always using EndCat. Ensure symbols referenced: selectRange,
TryICUSelectPlural, SelectPluralWithCLDR, StartCat, EndCat,
TGocciaStringLiteralValue.Create.
source/units/Goccia.Values.IntlDisplayNames.pas-72-85 (1)

72-85: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unknown type values silently fall back to language names.

DisplayTypeStringToEnum defaults to idntLanguage, and the constructor never rejects unsupported type strings. Invalid input should throw, not succeed and route lookups through the language code path.

Also applies to: 121-126

🤖 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 `@source/units/Goccia.Values.IntlDisplayNames.pas` around lines 72 - 85,
DisplayTypeStringToEnum currently maps any unknown AValue to idntLanguage,
allowing invalid "type" strings to silently succeed; change
DisplayTypeStringToEnum to raise an exception (e.g. raise
EArgumentException.CreateFmt(...)) when AValue is not one of the known tokens
('region','script','calendar','currency','dateTimeField','language'), and update
the TIntlDisplayNames constructor (the Create method that accepts the type
string) to call DisplayTypeStringToEnum and propagate the exception so
unsupported type strings are rejected at construction; also apply the same
validation/error behavior to the similar conversion routine found around lines
121-126 so no unknown values fall back silently.
source/units/Goccia.Values.IntlRelativeTimeFormat.pas-106-142 (1)

106-142: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Fallback formatting ignores both numeric and style.

The CLDR path only keys on locale + unit, so when ICU is unavailable numeric: 'auto' never produces lexical forms and style: 'short'/'narrow' still formats like the default long style. resolvedOptions() reports these options, but the output cannot reflect them.

Also applies to: 260-265

🤖 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 `@source/units/Goccia.Values.IntlRelativeTimeFormat.pas` around lines 106 -
142, FormatRelativeTimeWithCLDR currently only looks up CLDR patterns by
locale+unit so it ignores numeric and style; update the lookup flow so
FormatRelativeTimeWithCLDR (and TryGetRelativeTimePattern) first attempt to
retrieve a pattern keyed by locale+unit+style+numeric (e.g., long/short/narrow
and numeric=auto/always), if that fails then for numeric='auto' try lexical
forms (yesterday/today/tomorrow) before falling back, and if style is
short/narrow try those pattern variants (Pattern.Short / Pattern.Narrow or
equivalent) before using Pattern.Long; ensure you pass the style and numeric
flags into TryGetRelativeTimePattern (or add parameters) and only fallback to
FloatToStr(AValue) after all style/numeric-aware lookups are exhausted.
source/units/Goccia.Values.IntlDateTimeFormat.pas-259-299 (1)

259-299: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The non-ICU path does not implement DateTimeFormat behavior.

When ICU formatting is unavailable, format() returns raw milliseconds, formatToParts() returns [], and formatRange() just concatenates the two numbers. That breaks the advertised CLDR fallback path on platforms without ICU.

🤖 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 `@source/units/Goccia.Values.IntlDateTimeFormat.pas` around lines 259 - 299,
The three methods IntlDateTimeFormatFormat, IntlDateTimeFormatFormatToParts, and
IntlDateTimeFormatFormatRange currently return raw millis/empty
arrays/concatenated numbers when TryICUFormatDateTime* fails; instead implement
the CLDR fallback path: when TryICUFormatDateTime fails call the library's
CLDR-based formatter (using DTF.FResolvedOptions and DTF.FLocale) to produce the
locale-aware formatted string in IntlDateTimeFormatFormat, produce the correct
sequence of parts in IntlDateTimeFormatFormatToParts (use the same
part-generation routine used by the ICU path or a new CLDR parts generator and
then return FormatPartsToArray(Parts)), and implement FormatRange to follow the
Range formatting algorithm (format each end via the CLDR fallback and apply the
range bridging rules) rather than concatenating numbers; update code around
TryICUFormatDateTime, TryICUFormatDateTimeToParts, FormatPartsToArray, and the
constructors TGocciaStringLiteralValue/TGocciaArrayValue to return the proper
locale-aware outputs.
source/units/Goccia.Intl.CLDRData.pas-482-553 (1)

482-553: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add parent-locale fallback when resolving plural rules.

This path only probes exact keys like ALocale + '/one'. In the embedded CLDR fallback, locale variants that inherit plural rules from a parent locale will return False even when the parent data exists, so PluralRules becomes unnecessarily unsupported for tags like en-US or pt-BR.

🤖 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 `@source/units/Goccia.Intl.CLDRData.pas` around lines 482 - 553,
TryGetPluralRules currently only queries exact keys for ALocale (e.g., ALocale +
'/one'), so implement a parent-locale fallback loop inside TryGetPluralRules:
set a localeToTry variable initialized to ALocale and in a loop attempt
TryGetKeyValue(Resource, DataOffset, DataLength, localeToTry +
'/zero'|'one'|'two'|'few'|'many'|'other') to populate ARules; if any category is
found set FoundAny and break out of the loop; if nothing is found and
localeToTry contains a '-' remove the last subtag (e.g., change 'en-US' -> 'en')
and retry, stopping when no '-' remains (or you reach a defined root). Keep
using TryGetSectionData and TryGetKeyValue as the lookup functions and preserve
existing ARules assignment logic when a parent provides the rules.
source/units/Goccia.Intl.CLDRData.pas-576-581 (1)

576-581: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Implement the embedded date-pattern lookup before merging.

TryGetDatePattern is part of the public fallback surface but currently always returns False. That means any DateTimeFormat path that lands on embedded CLDR cannot obtain patterns at all.

🤖 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 `@source/units/Goccia.Intl.CLDRData.pas` around lines 576 - 581,
TryGetDatePattern currently always returns False; implement it to look up the
embedded CLDR date/time pattern for ALocale given ADateStyle and ATimeStyle, set
APattern to the found pattern and return True, otherwise return False. Map the
TIntlDateTimeStyle combination to the CLDR pattern key(s) used in this unit,
search the embedded CLDR data/fallbacks for ALocale (including parent/fallback
locales), populate APattern with the matched pattern string and return True; if
no pattern is found leave APattern empty and return False. Ensure the
implementation uses the same locale lookup/fallback logic as other functions in
this unit so the public fallback surface behaves consistently.
source/units/Goccia.Values.IntlDurationFormat.pas-144-199 (1)

144-199: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The *Display options are parsed but never affect formatting.

ReadOptions() stores yearsDisplay/monthsDisplay/… and resolvedOptions() exposes them, but neither format() nor formatToParts() ever reads those fields. Right now instances with { hoursDisplay: 'always' } and { hoursDisplay: 'auto' } format identically, so the object advertises option support it does not implement.

Also applies to: 369-408

🤖 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 `@source/units/Goccia.Values.IntlDurationFormat.pas` around lines 144 - 199,
FormatDurationFallback currently ignores the per-unit display options
(yearsDisplay, monthsDisplay, etc.) that ReadOptions/resolvedOptions parsed;
update FormatDurationFallback (and its nested AddPart) to consult the
corresponding display option for each unit and only skip zero-valued units when
the unit's display option is not 'always' (i.e., if display is 'always' include
zeros, if 'auto' or unspecified skip zeros), by passing the unit's display
setting into AddPart (or reading it from a supplied options record) and using
that to determine whether to exit on AValue = 0; apply the same change to the
analogous code segment at lines ~369-408 so format() / formatToParts() honor the
stored yearsDisplay/monthsDisplay/... options.
source/units/Goccia.Values.IntlLocale.pas-101-138 (1)

101-138: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Recompute FTag and FBaseName from the resolved locale components.

FBaseName is frozen to Canonical before extensions are stripped, and the later option overrides only mutate FLanguage/FRegion/FCalendar/… without rebuilding the serialized tag. That leaves the object internally inconsistent: e.g. baseName can still include -u-... extensions, and toString() can keep returning the pre-override tag while the getters expose the overridden components.

🤖 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 `@source/units/Goccia.Values.IntlLocale.pas` around lines 101 - 138, The object
leaves FTag/FBaseName unchanged after option overrides, causing mismatch between
serialized tag and component getters; after applying ReadValidatedStringOption
changes (affecting FLanguage, FScript, FRegion, FCalendar, FCaseFirst,
FCollation, FHourCycle, FNumberingSystem, FNumeric) you must rebuild the locale
identifier and base name from those resolved components: assemble the core
subtags (FLanguage, FScript, FRegion, any variants) into a baseName without
Unicode extensions, assemble a -u- extension chunk from
calendar/caseFirst/collation/hourCycle/numberingSystem/numeric when present,
concatenate them, then pass the result through CanonicalizeUnicodeLocaleId (or
the existing BCP47/CANONICAL helper) and assign the result to both FBaseName and
FTag so toString()/getters are consistent with the overrides; update the code
around ParseBcp47Tag, ReadValidatedStringOption and the option-handling block to
perform this rebuild after options are applied.
source/units/Goccia.Values.IntlDurationFormat.pas-221-260 (1)

221-260: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unsupported option values instead of storing them verbatim.

FStyle and the per-unit display fields accept any string here, but the formatter only special-cases 'digital'. A typo like { style: 'digitial' } leaves the instance in an invalid state, resolvedOptions() echoes that invalid value, and formatting silently falls back to the generic text path.

🤖 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 `@source/units/Goccia.Values.IntlDurationFormat.pas` around lines 221 - 260,
The code currently stores any string values for FStyle and per-unit display
fields (FYearsDisplay, FMonthsDisplay, FWeeksDisplay, FDaysDisplay,
FHoursDisplay, FMinutesDisplay, FSecondsDisplay, FMillisecondsDisplay,
FMicrosecondsDisplay, FNanosecondsDisplay, and FNumberingSystem) directly from
AOptions, which allows invalid typos to persist; instead, validate each incoming
option against the allowed set for that option (per the Intl.DurationFormat
spec) and reject unknown values by calling ThrowRangeError (use the existing
SErrorIntlInvalidOption message format) rather than assigning them; reuse the
same pattern already used for FStyle’s nul check and use the
GetProperty/ToStringLiteral extraction you have, but add an allowed-values
lookup (or case/if checks) for each property before assigning to the
corresponding field.
source/units/Goccia.Values.IntlSegmenter.pas-146-153 (1)

146-153: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Populate input with the original segmented string, not the current segment.

Every segment object currently reports input === segment, so callers lose the original source text entirely. That breaks segment().containing(...) and iterator results for any multi-segment input.

Possible fix
-function CreateSegmentObject(const ASeg: TIntlSegment; const AIsWordGranularity: Boolean): TGocciaObjectValue;
+function CreateSegmentObject(const ASeg: TIntlSegment; const AInput: string;
+  const AIsWordGranularity: Boolean): TGocciaObjectValue;
 begin
   Result := TGocciaObjectValue.Create(TGocciaObjectValue.SharedObjectPrototype);
   Result.AssignProperty('segment', TGocciaStringLiteralValue.Create(ASeg.Segment));
   Result.AssignProperty('index', TGocciaNumberLiteralValue.Create(ASeg.Index));
-  Result.AssignProperty('input', TGocciaStringLiteralValue.Create(ASeg.Segment));
+  Result.AssignProperty('input', TGocciaStringLiteralValue.Create(AInput));
   if AIsWordGranularity then
     Result.AssignProperty('isWordLike', TGocciaBooleanLiteralValue.Create(ASeg.IsWordLike));
 end;
🤖 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 `@source/units/Goccia.Values.IntlSegmenter.pas` around lines 146 - 153, The
segment object currently sets its 'input' property to ASeg.Segment (the single
segment) — change CreateSegmentObject so 'input' contains the original source
string instead: replace the AssignProperty call that creates 'input' using
ASeg.Segment with one that uses the ASeg property that holds the full original
input (e.g. ASeg.Input or ASeg.OriginalText depending on the TIntlSegment field
name), i.e. Result.AssignProperty('input',
TGocciaStringLiteralValue.Create(ASeg.Input)); thereby preserving the original
segmented string for methods like segment().containing and iterators.
source/units/Goccia.Values.IntlSegmenter.pas-324-336 (1)

324-336: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Free the temporary iterator in containing().

Iter is class-typed here, so it will not be auto-released. Each Segments.prototype.containing() call currently leaks a TGocciaIntlSegmentIteratorValue.

Possible fix
   Iter := TGocciaIntlSegmentIteratorValue.Create(Segs.FLocale, Segs.FGranularity,
     UnicodeString(Segs.FText));
-  for I := 0 to Length(Iter.FSegments) - 1 do
-  begin
-    Seg := Iter.FSegments[I];
-    if (Idx >= Seg.Index) and (Idx < Seg.Index + Length(Seg.Segment)) then
+  try
+    for I := 0 to Length(Iter.FSegments) - 1 do
     begin
-      Result := CreateSegmentObject(Seg, Segs.FGranularity = 'word');
-      Exit;
+      Seg := Iter.FSegments[I];
+      if (Idx >= Seg.Index) and (Idx < Seg.Index + Length(Seg.Segment)) then
+      begin
+        Result := CreateSegmentObject(Seg, Segs.FText, Segs.FGranularity = 'word');
+        Exit;
+      end;
     end;
+  finally
+    Iter.Free;
   end;

Based on learnings: class-typed owners in this codebase are not auto-released and need explicit cleanup via try..finally + Free.

🤖 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 `@source/units/Goccia.Values.IntlSegmenter.pas` around lines 324 - 336, The
temporary TGocciaIntlSegmentIteratorValue instance (Iter) created in
containing() is not freed and leaks; wrap the creation and loop in a
try..finally and call Iter.Free in the finally block so Iter is always released,
keeping the same logic that uses Iter.FSegments and CreateSegmentObject and
returning TGocciaUndefinedLiteralValue.UndefinedValue when not found.
source/units/Goccia.Values.IntlSegmenter.pas-551-556 (1)

551-556: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

[Symbol.iterator] on the iterator prototype must return the iterator itself, not call next().

Currently, [Symbol.iterator] is bound to IntlSegmentIteratorNext, which returns a { value, done } record and advances the iterator. This violates the iterator protocol—iterator[Symbol.iterator]() should return the iterator itself to maintain self-iterable behavior.

Fix
+function TGocciaIntlSegmentIteratorValue.IntlSegmentIteratorSymbolIterator(
+  const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
+begin
+  if not (AThisValue is TGocciaIntlSegmentIteratorValue) then
+    ThrowTypeError('Segment Iterator [Symbol.iterator] called on non-iterator');
+  Result := AThisValue;
+end;
+
       Members.AddSymbolMethod(
         TGocciaSymbolValue.WellKnownIterator,
         '[Symbol.iterator]',
-        IntlSegmentIteratorNext,
+        IntlSegmentIteratorSymbolIterator,
         0,
         [pfConfigurable, pfWritable]);
🤖 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 `@source/units/Goccia.Values.IntlSegmenter.pas` around lines 551 - 556, The
[Symbol.iterator] binding on the iterator prototype is wrong: it currently
points to IntlSegmentIteratorNext (which advances and returns {value, done}) but
must instead return the iterator itself; replace the AddSymbolMethod call that
binds TGocciaSymbolValue.WellKnownIterator/'[Symbol.iterator]' to
IntlSegmentIteratorNext with a new method (e.g.
IntlSegmentIteratorSymbolIterator) that simply returns the iterator object
(this/self) without advancing, and register that new method with the same
attributes (0 args, [pfConfigurable, pfWritable]); update references to
TGocciaSymbolValue.WellKnownIterator/'[Symbol.iterator]' to use the new function
name so the iterator is self-iterable.
🟡 Minor comments (3)
tests/built-ins/Intl/Collator/constructor.js-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard this suite on Intl.Collator availability too.

Line 8 only checks Intl; in partial-Intl environments this can hard-fail instead of skipping.

Suggested patch
- describe.runIf(isIntl)("Intl.Collator constructor", () => {
+ describe.runIf(isIntl && typeof Intl.Collator !== "undefined")("Intl.Collator constructor", () => {
🤖 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 `@tests/built-ins/Intl/Collator/constructor.js` at line 8, The test suite is
only guarded by isIntl and can fail in partial-Intl environments; update the
guard used on describe.runIf to ensure Intl.Collator exists (e.g., require
isIntl && typeof Intl.Collator === "function" or a small helper like
isIntlCollator) so the "Intl.Collator constructor" suite is skipped when
Collator is unavailable; modify the call at describe.runIf(isIntl) to use this
combined predicate referencing Intl.Collator.
tests/built-ins/Intl/NumberFormat/prototype/format.js-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Gate this suite on Intl.NumberFormat existence.

Line 8 currently gates only on Intl; if NumberFormat is missing, the suite fails instead of being skipped.

Suggested patch
- describe.runIf(isIntl)("Intl.NumberFormat.prototype.format", () => {
+ describe.runIf(isIntl && typeof Intl.NumberFormat !== "undefined")("Intl.NumberFormat.prototype.format", () => {
🤖 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 `@tests/built-ins/Intl/NumberFormat/prototype/format.js` at line 8, The suite
is gated only by isIntl but should also require Intl.NumberFormat; update the
runIf predicate on the describe (the describe.runIf(...) call wrapping
"Intl.NumberFormat.prototype.format") to check both Intl and NumberFormat
existence (e.g., use isIntl && typeof Intl.NumberFormat === "function" or a
small helper like hasIntlNumberFormat) so the suite is skipped when
Intl.NumberFormat is missing rather than run and fail.
source/units/Goccia.Values.IntlCollator.pas-193-196 (1)

193-196: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the arity check that rejects fewer than 2 arguments.

ECMA-402 specifies that Intl.Collator.prototype.compare should convert arguments to strings via ToString(), with missing arguments defaulting to undefined (which converts to the string "undefined"). The explicit check at lines 193–196 and 212–215 that throws TypeError for fewer than 2 arguments violates this specification. Remove these checks and allow arguments to pass through the normal string-conversion path so that collator.compare("a") succeeds rather than failing.

🤖 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 `@source/units/Goccia.Values.IntlCollator.pas` around lines 193 - 196, Remove
the explicit arity check that throws via ThrowTypeError when AArgs.Length < 2 in
Intl.Collator.prototype.compare; instead allow the normal ToString conversion
path for missing args. Concretely, delete the conditional that checks
AArgs.Length and the ThrowTypeError call, and keep the subsequent calls that
obtain values via AArgs.GetElement(0).ToStringLiteral.Value and
AArgs.GetElement(1).ToStringLiteral.Value so missing elements are converted to
"undefined" per ToString semantics (ensure Str1 and Str2 are produced solely
from those GetElement(...)→ToStringLiteral.Value calls). Also remove the
equivalent arity-check/ThrowTypeError elsewhere in the same compare
implementation so both checks no longer reject fewer-than-2-argument calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48f9c0ac-9de4-435c-8741-ab9dae208180

📥 Commits

Reviewing files that changed from the base of the PR and between cd11813 and a3401ec.

⛔ Files ignored due to path filters (2)
  • source/generated/Generated.IntlData.pas is excluded by !**/generated/**
  • source/generated/Generated.IntlData.res is excluded by !**/generated/**
📒 Files selected for processing (54)
  • docs/build-system.md
  • docs/built-ins-intl.md
  • docs/built-ins.md
  • docs/decision-log.md
  • docs/language-tables.md
  • docs/test262.md
  • scripts/generate-intl-data.js
  • scripts/test262_compatibility_roadmap.json
  • source/shared/BCP47.pas
  • source/shared/EmbeddedResourceReader.pas
  • source/shared/ICU.pas
  • source/shared/IntlICU.pas
  • source/shared/IntlLocaleResolver.pas
  • source/shared/IntlTypes.pas
  • source/units/Goccia.Builtins.Intl.pas
  • source/units/Goccia.Builtins.TestingLibrary.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Error.Messages.pas
  • source/units/Goccia.Intl.CLDRData.pas
  • source/units/Goccia.Intl.Helpers.pas
  • source/units/Goccia.Temporal.TimeZone.pas
  • source/units/Goccia.Temporal.TimeZoneData.pas
  • source/units/Goccia.Values.IntlCollator.pas
  • source/units/Goccia.Values.IntlDateTimeFormat.pas
  • source/units/Goccia.Values.IntlDisplayNames.pas
  • source/units/Goccia.Values.IntlDurationFormat.pas
  • source/units/Goccia.Values.IntlListFormat.pas
  • source/units/Goccia.Values.IntlLocale.pas
  • source/units/Goccia.Values.IntlNumberFormat.pas
  • source/units/Goccia.Values.IntlPluralRules.pas
  • source/units/Goccia.Values.IntlRelativeTimeFormat.pas
  • source/units/Goccia.Values.IntlSegmenter.pas
  • source/units/Goccia.Values.StringObjectValue.pas
  • source/units/Goccia.inc
  • tests/built-ins/Intl/Collator/constructor.js
  • tests/built-ins/Intl/Collator/prototype/compare.js
  • tests/built-ins/Intl/DateTimeFormat/constructor.js
  • tests/built-ins/Intl/DisplayNames/constructor.js
  • tests/built-ins/Intl/DurationFormat/constructor.js
  • tests/built-ins/Intl/ListFormat/constructor.js
  • tests/built-ins/Intl/ListFormat/prototype/format.js
  • tests/built-ins/Intl/Locale/constructor.js
  • tests/built-ins/Intl/NumberFormat/constructor.js
  • tests/built-ins/Intl/NumberFormat/prototype/format.js
  • tests/built-ins/Intl/PluralRules/constructor.js
  • tests/built-ins/Intl/PluralRules/prototype/select.js
  • tests/built-ins/Intl/RelativeTimeFormat/constructor.js
  • tests/built-ins/Intl/Segmenter/constructor.js
  • tests/built-ins/Intl/Segmenter/prototype/segment.js
  • tests/built-ins/Intl/getCanonicalLocales.js
  • tests/built-ins/Intl/nul-character-validation.js
  • tests/built-ins/Intl/supportedValuesOf.js
  • tests/built-ins/Intl/toStringTag.js
  • tests/built-ins/String/prototype/toLocaleLowerCase.js

Comment thread source/shared/IntlICU.pas
Comment on lines +1105 to +1139
function TryICUFormatRelativeTime(const ALocale: string; AValue: Double;
AUnit: TIntlRelativeTimeUnit; ANumeric: TIntlRelativeTimeNumeric;
out AFormatted: string): Boolean;
var
Status: TICUErrorCode;
Reldatefmt: Pointer;
Buffer: array[0..FORMAT_BUFFER_CAPACITY - 1] of WideChar;
ResultLen: LongInt;
LocaleAnsi: AnsiString;
begin
Result := False;
AFormatted := '';

if not EnsureLoaded then
Exit;

if not Assigned(IntlFunctions.UreldatefmtOpen) or
not Assigned(IntlFunctions.UreldatefmtClose) or
not Assigned(IntlFunctions.UreldatefmtFormatNumeric) then
Exit;

LocaleAnsi := AnsiString(ALocale);
Status := ICU_SUCCESS;
Reldatefmt := IntlFunctions.UreldatefmtOpen(PAnsiChar(LocaleAnsi),
nil, URELDATEFMT_STYLE_LONG, 0, Status);
if not ICUSucceeded(Status) or not Assigned(Reldatefmt) then
Exit;

try
FillChar(Buffer, SizeOf(Buffer), 0);
Status := ICU_SUCCESS;
ResultLen := IntlFunctions.UreldatefmtFormatNumeric(Reldatefmt,
AValue, RelativeTimeUnitToICU(AUnit),
@Buffer[0], FORMAT_BUFFER_CAPACITY, Status);
if not ICUSucceeded(Status) or (ResultLen <= 0) then
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Which ICU relative-time API should be used for numeric: "auto" values such as "yesterday"/"tomorrow", and how are short/narrow widths selected?

💡 Result:

The ICU API for handling relative-time formatting with numeric: "auto" values like "yesterday" and "tomorrow" is RelativeDateTimeFormatter.[1][2][3][4] This class provides methods to format relative dates using text styles when possible (e.g., format(1.0, RelativeUnit.DAYS) produces "tomorrow"), falling back to numeric styles otherwise, which corresponds to the "auto" behavior in Intl.RelativeTimeFormat.[1][3] For short and narrow widths, use the Style parameter when constructing the formatter: RelativeDateTimeFormatter.Style.SHORT (abbreviations when possible, e.g., "3 sec. ago") or Style.NARROW (single letters when possible, e.g., "3s ago"). Pass the desired Style to factory methods like getInstance(locale, style).[4][5][3] These styles apply to both numeric patterns and absolute relative units like yesterday/tomorrow, with data sourced from CLDR relativeTimePatterns distinguished by style (long/short/narrow).[6][3]

Citations:


🏁 Script executed:

# Check the full function implementation to see if ANumeric is used
fd "IntlICU.pas" --type f -x cat -n {} | sed -n '1105,1139p'

Repository: frostney/GocciaScript

Length of output: 1454


🏁 Script executed:

# Search for ANumeric usage in the entire function and related code
rg "ANumeric" source/shared/IntlICU.pas -B 5 -A 15

Repository: frostney/GocciaScript

Length of output: 1246


🏁 Script executed:

# Check for other relative time formatting functions in the file
rg "TryICUFormatRelativeTime|UreldatefmtFormat|Ureldatefmt" source/shared/IntlICU.pas -A 2

Repository: frostney/GocciaScript

Length of output: 2049


🏁 Script executed:

# Check the enum/constant definitions for RelativeTimeNumeric and styles
rg "TIntlRelativeTimeNumeric|RelativeTimeNumeric|URELDATEFMT_STYLE" source/shared/IntlICU.pas -B 2 -A 5

Repository: frostney/GocciaScript

Length of output: 1240


🏁 Script executed:

# Search for any references to non-numeric relative time formatting
rg "ureldatefmt_format[^N]|Ureldatefmt(?!Numeric)" source/shared/IntlICU.pas

Repository: frostney/GocciaScript

Length of output: 348


🏁 Script executed:

# Check the entire variable/constant declarations for relative time
rg "TIntlRelativeTimeNumeric|relativeTime|Relative" source/shared/IntlICU.pas -B 2 -A 2

Repository: frostney/GocciaScript

Length of output: 822


🏁 Script executed:

# Search for ureldatefmt function declarations more broadly
rg "Ureldatefmt" source/shared/IntlICU.pas | head -20

Repository: frostney/GocciaScript

Length of output: 946


Use ureldatefmt_format() instead of ureldatefmt_formatNumeric() to support the numeric parameter and enable text output like "yesterday".

The current implementation calls ureldatefmt_formatNumeric(), which always produces numeric output regardless of the numeric parameter setting (e.g., "in 1 day" instead of "tomorrow"). To support numeric: "auto" (which produces text for common relative units), add a binding for ureldatefmt_format() and use it to pass the ANumeric parameter as the numeric mode (URELDATEFMT_NUMERIC_AUTO or URELDATEFMT_NUMERIC_ALWAYS).

Additionally, the width style is hard-coded to URELDATEFMT_STYLE_LONG in the ureldatefmt_open() call. To support short/narrow styles, parameterize the width based on a style input parameter.

🤖 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 `@source/shared/IntlICU.pas` around lines 1105 - 1139, The code currently calls
IntlFunctions.UreldatefmtFormatNumeric which forces numeric output; change the
implementation of TryICUFormatRelativeTime to call a new binding
IntlFunctions.UreldatefmtFormat (binding must be added) and pass the ANumeric
value mapped to ICU numeric modes (URELDATEFMT_NUMERIC_AUTO /
URELDATEFMT_NUMERIC_ALWAYS) so the numeric parameter controls auto/text vs
numeric output; also stop hard-coding URELDATEFMT_STYLE_LONG in the
UreldatefmtOpen call—add a style parameter (or map an existing style input) and
pass that into IntlFunctions.UreldatefmtOpen instead of URELDATEFMT_STYLE_LONG.
Reference the existing symbols TryICUFormatRelativeTime,
IntlFunctions.UreldatefmtOpen, IntlFunctions.UreldatefmtFormatNumeric,
IntlFunctions.UreldatefmtFormat and RelativeTimeUnitToICU when making these
changes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Tracked in #599. The ureldatefmt_formatNumeric vs ureldatefmt_format gap is a known incomplete feature — the current implementation always uses numeric output. Adding ureldatefmt_format for numeric: "auto" and parameterizing the width style are part of the RelativeTimeFormat conformance work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@frostney, understood — thanks for the context. Acknowledged that the ureldatefmt_format / numeric: "auto" gap and the width-style parameterization are intentionally deferred conformance work tracked in #599. I'll avoid re-flagging these in future reviews.


✏️ Learnings added
Learnt from: frostney
Repo: frostney/GocciaScript PR: 590
File: source/shared/IntlICU.pas:1105-1139
Timestamp: 2026-05-10T13:54:57.343Z
Learning: In frostney/GocciaScript, `TryICUFormatRelativeTime` in `source/shared/IntlICU.pas` intentionally always calls `ureldatefmt_formatNumeric` (always numeric output) and hard-codes `URELDATEFMT_STYLE_LONG`. Supporting `numeric: "auto"` (via `ureldatefmt_format`) and parameterizing the width style are known gaps tracked in issue `#599` (RelativeTimeFormat conformance follow-up). Do not flag these as bugs during review of this file.

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.


🧠 Learnings used
Learnt from: frostney
Repo: frostney/GocciaScript PR: 53
File: souffle/Souffle.Wasm.Emitter.pas:1467-1477
Timestamp: 2026-03-09T17:21:38.193Z
Learning: In this codebase (Frostney/GocciaScript), for Pascal/Delphi (FPC) files using AnsiString with {$mode delphi} {H+}, treat String as byte-indexed. Length(str) returns byte count and Byte(str[I]) is a no-op cast. Do not replace per-byte copy loops like Data[I-1] := Byte(AStr[I]) with TEncoding.GetBytes, since TEncoding is a Delphi RTL feature unavailable in FPC's SysUtils and would break the build. Apply this guidance to all .pas files under the project; ensure manual per-byte copying logic remains correct for UTF-8 content encoded in AnsiString, and avoid introducing RTL dependencies not supported by FPC.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 63
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-11T01:50:11.071Z
Learning: Replace hardcoded string literals 'now', 'toJSON', and 'timeOrigin' with the new constants from Goccia.Constants.PropertyNames (PROP_NOW, PROP_TO_JSON, PROP_TIME_ORIGIN). Apply this refactor across the repository wherever these literals are used, including but not limited to units/Goccia.Runtime.Operations.pas and units/Goccia.Builtins.Performance.pas. Use a project-wide search (e.g., regex or code navigation) to identify all occurrences, update to the respective constants, and adjust imports or uses accordingly. Ensure all references compile and behavior remains unchanged.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 110
File: units/Goccia.Runtime.Operations.pas:0-0
Timestamp: 2026-03-23T11:25:00.381Z
Learning: In this repo’s GocciaScript Pascal code, when implementing thenable detection/callability checks (e.g., around Await/unwrap logic), always read the thenable’s `then` property using `Goccia.Constants.PropertyNames.PROP_THEN` rather than a string literal. If the value being inspected is a `TSouffleRecord`, unwrap the retrieved `then` property via `UnwrapToGocciaValue` before testing whether it is callable (e.g., in `TGocciaRuntimeOperations.AwaitValue`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 200
File: REPL.dpr:0-0
Timestamp: 2026-04-07T17:12:07.324Z
Learning: When reviewing timing/metrics code in the GocciaScript repo (e.g., REPL.dpr, ScriptLoader, TestRunner, BenchmarkRunner, and TGocciaEngine.Execute), follow the established timing convention: capture `StartTime` before the JSX pre-pass (`TGocciaJSXTransformer.Transform`) and `StringListToLFText`, then capture `LexEnd` after `ScanTokens`. The `Lex` duration (`LexEnd - StartTime`) is intended to include the JSX pre-pass because it is treated as part of the source→tokens pipeline. Do not flag this ordering as a bug and do not recommend moving `StartTime` to after the JSX transform, as it would break the bucket semantics.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 209
File: units/Goccia.FFI.DynamicLibrary.pas:7-21
Timestamp: 2026-04-08T13:56:59.262Z
Learning: In Free Pascal (FPC), `TLibHandle` is defined in the `System` unit (`TLibHandle = PtrInt`), not in `Dynlibs`. Since `System` is implicitly available in all Pascal units, referencing `TLibHandle` in the interface section (e.g., as a private field type of a class declared in the interface/`interface` section) does not require `Dynlibs` to be listed in the interface `uses` clause. Only ensure `Dynlibs` is included in the implementation `uses` clause when you actually use `LoadLibrary`, `GetProcAddress`, or `FreeLibrary`. Therefore, do not flag a missing `Dynlibs` entry in the interface `uses` clause when the interface section references only `TLibHandle`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 242
File: AGENTS.md:396-400
Timestamp: 2026-04-09T11:50:01.452Z
Learning: In .pas code, avoid unsafe Int64→Double conversions/workarounds that trigger FPC 3.2.2 miscompilations. Specifically, do not rely on `Double(Int64Var)` when it performs bit reinterpretation (Delphi-mode front-end bug on affected versions/platforms). Also do not use arithmetic patterns like `Int64Var * 1.0` / `Int64Var * Double` that can produce incorrect results near ±2^31 on AArch64. Prefer value-safe conversions via implicit assignment (e.g., `var D: Double; D := SomeInt64`) or implicit promotion through function/constructor parameter passing (e.g., `TGocciaNumberLiteralValue.Create(SomeInt64)`).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 253
File: units/Goccia.Values.Iterator.Concat.pas:84-88
Timestamp: 2026-04-10T12:44:59.067Z
Learning: In frostney/GocciaScript (GocciaScript), `TGocciaFunctionBase(callable).Call(args, thisValue)` is the project-standard way to invoke callable values. During code review, do not flag `TGocciaFunctionBase(...)` casts at call sites as unsafe and do not recommend replacing them with an alternative “generic callable path”—for this codebase, this cast-and-`Call` pattern is the intended generic callable invocation mechanism.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 285
File: units/Goccia.Lexer.pas:207-213
Timestamp: 2026-04-12T15:46:28.683Z
Learning: In this Frostney/GocciaScript project (Free Pascal / FPC, including files under units/), do not recommend adding explicit `{$B-}` directives (either inside units or in `Goccia.inc`) to “fix” short-circuit evaluation safety. `{$B-}` is already FPC’s default in all compiler modes (`fpc`, `delphi`, `objfpc`). Treat missing `{$B-}` as expected and do not flag it as a maintainability or safety issue. Only note `{$B-}` overrides if an explicit `{$B+}` is present in the file or via a highly visible project-level `fpc.cfg` setting.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 296
File: units/Goccia.REPL.Formatter.pas:15-29
Timestamp: 2026-04-13T16:53:02.191Z
Learning: For Pascal units in frostney/GocciaScript, enforce `uses`-clause grouping order as follows (with blank lines between groups): (1) System units (e.g., `SysUtils`), (2) Third-party units, then (3) Project units (e.g., `Goccia.*`). Specifically, `StringBuffer` (from `StringBuffer.pas`) must be treated as a Third-party unit and placed in the Third-party group. Do not report a `uses` ordering violation when `StringBuffer` appears between the System group and the `Goccia.*` group, since `./format.pas` and the Lefthook pre-commit hook enforce this grouping.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 302
File: docs/contributing/code-style.md:31-37
Timestamp: 2026-04-14T13:36:54.107Z
Learning: When reviewing Free Pascal (FPC) sources in this project, do not flag the compiler directive `{H+}` as a syntax error or as an inconsistency caused by a missing `$`. In FPC, `{H+}` (short-form) is valid and equivalent to `{$H+}` for enabling long/AnsiStrings. This project’s own `Goccia.inc` and documentation intentionally use `{H+}`, so the reviewer should only consider it an issue if it’s not valid FPC syntax for the surrounding context.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 308
File: units/Goccia.VM.pas:0-0
Timestamp: 2026-04-15T15:41:55.194Z
Learning: In this repo’s Pascal code, when handling “assigning to a read-only” errors, use the exact suggestion type `SSuggestReadOnlyProperty`, paired with `SErrorCannotAssignReadOnly`. Keep this mapping consistent everywhere it applies (e.g., for `ObjectValue`, `ClassValue`, and similar value/descriptor assignments), so the same read-only assignment diagnostic always yields the same suggestion/supported error combination.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 333
File: source/shared/OrderedMap.Test.pas:387-401
Timestamp: 2026-04-18T03:14:07.382Z
Learning: For Free Pascal (FPC) in Delphi mode (`{$mode delphi}`) used in this repo (Pascal `.pas` files), do not treat a bare indexed-property read used as a standalone statement (e.g., `Map[999];`) as a compile error. In this mode, the getter call is executed and its result is silently discarded; if the getter raises an exception, it still propagates to the surrounding `except` handler as expected. For readability/portability, assigning the result to a dummy local (e.g., `Dummy := Map[999];`) is preferred, but bare indexed-property reads should remain unflagged as long as they are valid under FPC Delphi mode.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 402
File: source/units/Goccia.Values.TemporalDuration.pas:176-178
Timestamp: 2026-04-25T22:50:52.352Z
Learning: In this Free Pascal (FPC) codebase (GocciaScript), do not use `Abs(Int64Var)` in range-guard comparisons when the input could be `Low(Int64)` (-2^63), because `Abs(Low(Int64))` overflows in FPC and wraps back to a negative value, causing range checks to silently pass. Instead, use explicit signed-bound comparisons, e.g. `(AVal <= -Int64(BOUND)) or (AVal >= BOUND)`. When forming `-BOUND` and `BOUND` might be implicitly typed narrower than `Int64`, cast to `Int64` before negation (e.g., `-Int64(BOUND)`) so the negation stays in range. Apply this pattern everywhere you see `Abs(Int64)` used inside a comparison guard.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 402
File: source/units/Goccia.Values.ArrayValue.pas:755-767
Timestamp: 2026-04-26T14:02:20.855Z
Learning: In frostney/GocciaScript, do not re-flag calls to `Trunc()` for missing finite-number guards (`IsNaN`/`IsInfinity`) when the code runs under `TGocciaEngine.Create` where `exInvalidOp` is masked (engine `Create`). Under this masking, `Trunc()` on `NaN` or `±Infinity` is safe and yields IEEE indeterminate `Low(Int64)` instead of raising `EInvalidOp`. Also, rely on the existing guard pattern `Len <> Trunc(Len)` to catch non-finite inputs: `NaN <> <anything>` evaluates to `true`, and `±Infinity <> Low(Int64)` evaluates to `true`, which triggers `ThrowRangeError`. Therefore, in this codebase, `Trunc()` calls are considered already protected when paired with the `Len <> Trunc(Len)`-style check; only flag missing guards if that combined protection (or equivalent non-finite handling) is not present.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 415
File: source/units/Goccia.Values.Primitives.pas:242-306
Timestamp: 2026-04-27T11:24:27.552Z
Learning: In the frostney/GocciaScript codebase (Free Pascal 3.2.2), `FormatSettings`/`DefaultFormatSettings` is declared as `Absolute DefaultFormatSettings` and therefore shares the same memory. For every new `TFormatSettings` construction site that creates a locale-independent formatter (i.e., any code that sets up `TFormatSettings` for JSON/TOML/YAML/etc. parsing/serialization), immediately pin `FS.DecimalSeparator := '.'` right after `FS := DefaultFormatSettings`. This pin is required for consistency: `Str()` is a compiler intrinsic that always uses `'.'`, but `TryStrToFloat(..., FS)` and `FloatToStr(..., FS)` use the provided `FS` record—so you must explicitly set `FS.DecimalSeparator` rather than relying on defaults. Do not treat this assignment as redundant; enforce it at every `TFormatSettings` usage site that relies on locale-independent decimal parsing/formatting.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 471
File: source/units/Goccia.VM.pas:0-0
Timestamp: 2026-04-30T12:40:36.331Z
Learning: When implementing iterator-to-array (or similar consumption) in Pascal units, wrap both consumption paths (e.g., typed iterator values and any raw iterator object) in try/except so that on abrupt completion you close iterators using CloseIteratorPreservingError / CloseRawIteratorPreservingError. After closing, re-raise the original error so the thrown exception matches ES §7.4.10 step 5 semantics (i.e., close must not replace the original error).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 493
File: source/units/Goccia.ScriptLoader.Globals.Test.pas:159-172
Timestamp: 2026-05-02T00:42:01.543Z
Learning: When reviewing code that uses `TGocciaRuntime` in `frostney/GocciaScript`, account for its engine-ownership semantics. The default `TGocciaRuntime.Create(AEngine, ARuntimeGlobals)` overload is **non-owning**: the caller retains ownership of `AEngine` and may (and must) free it separately after `TGocciaRuntime` is freed. Treat a “double-free” as a false positive if the code calls the non-owning overload and frees both `Runtime` and `Engine` independently. Engine ownership is transferred only when an overload explicitly includes `AOwnsEngine: Boolean` with `True` (e.g., `Create(AEngine, AOwnsEngine)` or `Create(AEngine, ARuntimeGlobals, AOwnsEngine)` where `AOwnsEngine=True`); in that case, the caller must not free `AEngine` separately.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 490
File: source/units/Goccia.VM.pas:4867-4939
Timestamp: 2026-05-02T12:47:39.953Z
Learning: In frostney/GocciaScript (Goccia VM), all VM private-key helper APIs must be called with an explicit brand token to prevent cross-class private-name aliasing. Use the tokenized forms only:
- Call `NormalizeBytecodePrivateKey(AName, PrivateBrandToken)` and `BytecodePrivateBrandKey(AKey, PrivateBrandToken)`.
- Derive `PrivateBrandToken` via `BytecodePrivateTokenForKey(AKey, Fallback)` or from the owning class/receiver value (e.g., `AClassValue.PrivateBrandToken` / `BytecodePrivateReceiverBrandToken`).
Tokenless forms were removed (commit 99994d2c), so any call sites passing only keys/names without the brand token should be rejected during review.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 506
File: source/app/GocciaScriptLoaderBare.dpr:0-0
Timestamp: 2026-05-03T18:43:29.776Z
Learning: In the GocciaScript codebase (Free Pascal/Delphi), follow the canonical “acquire-then-protect” pattern for resource management: (1) assign the result of a constructor/creation call to the resource variable first, then (2) immediately open the surrounding `try..finally`, and only call `Resource.Free` in the `finally`. Don’t require a pre-nil guard like `Resource := nil` *when* the `try` block opens only after the assignment has completed. Rationale: if the constructor raises, execution skips the inner `try..finally` that contains the `Free`, so `Resource.Free` is never reached before the variable has been assigned. Only add pre-nil/initialization when the `try` block is entered before the assignment (i.e., `try` wraps the assignment).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 527
File: source/units/Goccia.Values.FunctionValue.Test.pas:110-123
Timestamp: 2026-05-05T23:39:55.120Z
Learning: In frostney/GocciaScript's Pascal tests, TGocciaThrowValue carries only Value: TGocciaValue and has no Kind or TGocciaThrowValueKind. To assert a thrown error is a TypeError, read E.Value as TGocciaObjectValue, call GetProperty(PROP_NAME) to obtain the error's name, cast to TGocciaStringLiteralValue, and compare .Value against TYPE_ERROR_NAME. Do not use E.Kind or similar enum-based checks since they do not exist on TGocciaThrowValue.

Comment thread source/units/Goccia.Temporal.TimeZone.pas
frostney and others added 2 commits May 10, 2026 14:54
WindowsICU.Handle now comes from the shared ICU.pas loader, which
already frees the library in its own finalization. Removed the
redundant UnloadLibrary call that would double-release the DLL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ocale safety

Segmenter: segment objects now carry the full original input string
instead of the individual segment text. Iterator [Symbol.iterator]
returns self instead of calling next(). containing() frees the
temporary iterator via try/finally.

DisplayNames: unknown type strings now throw RangeError instead of
silently defaulting to language lookups.

NumberFormat CLDR fallback: uses locale-invariant FormatSettings for
FormatFloat so decimal separator is always '.' regardless of host
locale. Respects useGrouping option — skips grouping when false.

Collator: compare no longer rejects <2 arguments. Missing args convert
to "undefined" per ECMA-402 ToString semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney merged commit 1d232d0 into main May 10, 2026
14 checks passed
@frostney frostney deleted the issue-542-intl-namespace branch May 10, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation internal Refactoring, CI, tooling, cleanup new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Engine: ECMA-402 / Intl namespace not implemented (242 test262 tests)

1 participant