Skip to content

Improve Temporal conformance and enable BigInt in test262#354

Merged
frostney merged 5 commits intomainfrom
t3code/test262-bigint-temporal
Apr 20, 2026
Merged

Improve Temporal conformance and enable BigInt in test262#354
frostney merged 5 commits intomainfrom
t3code/test262-bigint-temporal

Conversation

@frostney
Copy link
Copy Markdown
Owner

Summary

  • Add shared ISO string coercion primitives (CoerceToISO*) and toString options (ResolveTemporalToStringOptions, RoundTimeForToString, GetCalendarDisplay) to eliminate duplicated parsing logic across all Temporal types
  • Fill missing API surface: toLocaleString, Symbol.toStringTag, toZonedDateTimeISO, withCalendar, expand rounding mode
  • Fix property descriptors (non-enumerable constructor statics), timezone offset IDs, leap second handling, annotation validation
  • Enable BigInt in test262 filter

test262: 6,612 → 6,837 passed (+225), Temporal 1,787 → 2,012 (+225, 42.5% → 47.9%)

Test plan

  • ./build.pas testrunner && ./build/GocciaTestRunner tests --asi --unsafe-ffi — 5,739 pass, 5 fail (pre-existing FFI fixture)
  • ./format.pas --check — 274 files, all clean
  • Zero compiler warnings
  • test262 suite run confirms +225 net improvement

🤖 Generated with Claude Code

- Accept BigInt syntax in Test262 filtering
- Coerce Temporal string inputs with offsets and annotations
- Allow offset-based time zone IDs and improve Temporal constructor properties
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

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: 7b3ed295-5d56-40ff-ae81-2d5dcb7bb5f2

📥 Commits

Reviewing files that changed from the base of the PR and between a20c055 and 1ffb039.

📒 Files selected for processing (1)
  • source/units/Goccia.Values.TemporalInstant.pas

📝 Walkthrough

Walkthrough

Centralizes Temporal ISO coercion and annotation handling, adds option-driven toString/toLocaleString rounding and calendar annotations, accepts offset-form timezone IDs, registers @@toStringTag and toLocaleString on Temporal prototypes, and enables BigInt literals in the test syntax filter.

Changes

Cohort / File(s) Summary
Test Configuration
scripts/test262_syntax_filter.py
Enable BigInt feature and remove BigInt-literal unsupported pattern so BigInt literal tests are allowed.
Temporal Utilities & Parsing
source/units/Goccia.Temporal.Utils.pas
Add annotation/offset stripping, annotation validation, normalized leap-second handling, and new coercion helpers (CoerceToISODate/Time/DateTime/Instant/YearMonth/MonthDay); update parsing routines to use these helpers and enforce annotation/timezone rules.
Temporal Options & Formatting
source/units/Goccia.Temporal.Options.pas
Add rmExpand rounding mode and public helpers (ResolveTemporalToStringOptions, GetCalendarDisplay, RoundTimeForToString, FormatCalendarAnnotation) to centralize option resolution, rounding, and calendar-annotation formatting.
Timezone Handling
source/units/Goccia.Temporal.TimeZone.pas
Treat offset-form timezone IDs as valid and return parsed offset seconds directly from UTC offset lookup (bypassing zone-data when an offset string is supplied).
Builtin Registration & Coercion
source/units/Goccia.Builtins.Temporal.pas
Switch property registrations to DefineProperty with configurable/writable descriptors; migrate constructors/compare/coerce paths to use CoerceToISO* helpers and add object-coercion branches for several Temporal types.
Temporal Value Types — API & Formatting
source/units/Goccia.Values.Temporal*.pas
source/units/Goccia.Values.TemporalDuration.pas, ...TemporalInstant.pas, ...PlainDate.pas, ...PlainDateTime.pas, ...PlainTime.pas, ...PlainMonthDay.pas, ...PlainYearMonth.pas, ...TemporalZonedDateTime.pas
Register toLocaleString prototype methods and well-known Symbol.toStringTag data properties; change many string coercions to use CoerceToISO*; implement option-driven toString formatting, rounding (including minute-special-case), calendar display handling, and auxiliary methods (toZonedDateTimeISO, WithCalendar, etc.).
Temporal Duration Value
source/units/Goccia.Values.TemporalDuration.pas
Add Duration.prototype.toLocaleString delegating to existing stringification and simplify ParseRelativeTo to use CoerceToISODate.

Sequence Diagram(s)

sequenceDiagram
    participant Input as rgba(200,200,255,0.5) String/Input/Args
    participant Utils as rgba(200,255,200,0.5) TemporalUtils
    participant Coerce as rgba(255,220,200,0.5) CoercionLayer
    participant TZ as rgba(255,200,200,0.5) TimeZoneResolver
    participant Value as rgba(200,255,255,0.5) TemporalValue
    Input->>Utils: StripAnnotations / StripOffsetAndAnnotations
    Utils-->>Coerce: Cleaned string
    Coerce->>TZ: Parse offset-form ID if present
    TZ-->>Coerce: Offset seconds (or none)
    Coerce->>Value: Return Date/Time/Instant/YearMonth/MonthDay records
    Value-->>Value: Construct/compare using object or coerced record
Loading
sequenceDiagram
    participant Value as rgba(200,200,255,0.5) TemporalValue
    participant Options as rgba(200,255,200,0.5) ResolveTemporalToStringOptions
    participant Rounder as rgba(255,220,200,0.5) RoundTimeForToString
    participant Formatter as rgba(255,200,200,0.5) Formatter/Annotation
    Value->>Options: Resolve options (fractionalDigits, roundingMode, calendarDisplay)
    Options-->>Value: FracDigits, RoundingMode, CalendarDisplay
    Value->>Rounder: Round time fields (may produce ExtraDays)
    Rounder-->>Value: Rounded fields + ExtraDays
    Value->>Formatter: Format time (precision or HH:MM) and append calendar annotation
    Formatter-->>Value: Formatted string
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the two main changes: improving Temporal API conformance and enabling BigInt support in test262.
Description check ✅ Passed The description covers the main changes (shared coercion primitives, API surface expansion, bug fixes, BigInt enablement), provides test results showing clear improvement (+225 tests), and documents verification steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification labels Apr 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 5819 5819
Tests Passed 5778 ✅ 5819 ✅
Tests Skipped 41 0
Tests Workers 4 4
Tests Test Duration 1.30s 1.40s
Tests Lex (cumulative) 175.4ms 109.1ms
Tests Parse (cumulative) 275.1ms 259.3ms
Tests Compile (cumulative) 154.4ms
Tests Execute (cumulative) 1.37s 1.33s
Tests Engine Total (cumulative) 1.82s 1.85s
Tests Lex (avg/worker) 43.9ms 27.3ms
Tests Parse (avg/worker) 68.8ms 64.8ms
Tests Compile (avg/worker) 38.6ms
Tests Execute (avg/worker) 342.3ms 331.9ms
Tests Engine Total (avg/worker) 455.0ms 462.6ms
Benchmarks Total 386 386
Benchmarks Workers 4 4
Benchmarks Duration 3.23min 2.89min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Benchmark Results

386 benchmarks

Interpreted: 🟢 265 improved · 🔴 19 regressed · 102 unchanged · avg +6.1%
Bytecode: 🟢 385 improved · 1 unchanged · avg +38.3%

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

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.

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: 7

Caution

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

⚠️ Outside diff range comments (3)
source/units/Goccia.Values.TemporalZonedDateTime.pas (1)

1111-1151: ⚠️ Potential issue | 🟠 Major

Recompute the offset after rounding.

OffsetSeconds is captured from the original instant before RoundTimeForToString mutates the displayed wall-clock fields. If rounding rolls into a different offset boundary, this can emit a rounded local date/time paired with the old offset, i.e. a string that never actually occurred in that zone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalZonedDateTime.pas` around lines 1111 -
1151, Recompute and use the zone offset after rounding: currently OffsetSeconds
is obtained before calling RoundTimeForToString, so if rounding changes the
wall-clock instant across an offset boundary the formatted string will show a
stale offset. Move or repeat the call to GetUtcOffsetSeconds after
RoundTimeForToString (and after adjusting DateRec when ExtraDays != 0) using
Zdt.FTimeZone and the possibly adjusted epoch/instant (derived from the rounded
local fields or by computing the shifted epoch milliseconds corresponding to
DateRec and LHour/LMinute/etc.), then use that recomputed OffsetSeconds in
FormatOffsetString; ensure the new OffsetSeconds replaces the original variable
used when building S.
source/units/Goccia.Builtins.Temporal.pas (2)

884-898: ⚠️ Potential issue | 🟠 Major

Missing required-field validation for object input.

Unlike PlainDateFrom (lines 554-574) which throws TypeError when year, month, or day are missing/undefined, PlainDateTimeFrom silently defaults them to 0. This creates invalid dates (month 0, day 0). Per TC39 Temporal spec, PlainDateTime.from(propertyBag) requires year/month/day for the ISO calendar.

🐛 Proposed fix to validate required fields
 else if Arg is TGocciaObjectValue then
 begin
   Obj := TGocciaObjectValue(Arg);
-  Y := 0; Mo := 0; D := 0; H := 0; Mi := 0; S := 0; Ms := 0; Us := 0; Ns := 0;
-  V := Obj.GetProperty(PROP_YEAR); if Assigned(V) and not (V is TGocciaUndefinedLiteralValue) then Y := Trunc(V.ToNumberLiteral.Value);
-  V := Obj.GetProperty(PROP_MONTH); if Assigned(V) and not (V is TGocciaUndefinedLiteralValue) then Mo := Trunc(V.ToNumberLiteral.Value);
-  V := Obj.GetProperty(PROP_DAY); if Assigned(V) and not (V is TGocciaUndefinedLiteralValue) then D := Trunc(V.ToNumberLiteral.Value);
+  V := Obj.GetProperty(PROP_YEAR);
+  if (V = nil) or (V is TGocciaUndefinedLiteralValue) then
+    ThrowTypeError(SErrorTemporalPlainDateTimeFromProps, SSuggestTemporalFromArg);
+  Y := Trunc(V.ToNumberLiteral.Value);
+  V := Obj.GetProperty(PROP_MONTH);
+  if (V = nil) or (V is TGocciaUndefinedLiteralValue) then
+    ThrowTypeError(SErrorTemporalPlainDateTimeFromProps, SSuggestTemporalFromArg);
+  Mo := Trunc(V.ToNumberLiteral.Value);
+  V := Obj.GetProperty(PROP_DAY);
+  if (V = nil) or (V is TGocciaUndefinedLiteralValue) then
+    ThrowTypeError(SErrorTemporalPlainDateTimeFromProps, SSuggestTemporalFromArg);
+  D := Trunc(V.ToNumberLiteral.Value);
+  H := 0; Mi := 0; S := 0; Ms := 0; Us := 0; Ns := 0;
   V := Obj.GetProperty(PROP_HOUR); if Assigned(V) and not (V is TGocciaUndefinedLiteralValue) then H := Trunc(V.ToNumberLiteral.Value);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Builtins.Temporal.pas` around lines 884 - 898, The
PlainDateTimeFrom branch is missing required-field validation: when Arg is a
TGocciaObjectValue you must ensure PROP_YEAR, PROP_MONTH and PROP_DAY are
present and not TGocciaUndefinedLiteralValue and raise a TypeError if any are
missing (same behavior as PlainDateFrom), before extracting and Trunc()-ing
values and creating TGocciaTemporalPlainDateTimeValue; update the code that
reads V := Obj.GetProperty(PROP_YEAR)/PROP_MONTH/PROP_DAY to check for
Assigned(V) AND not (V is TGocciaUndefinedLiteralValue) and if that check fails
for year, month or day call the runtime TypeError throw path used elsewhere.

310-316: ⚠️ Potential issue | 🔴 Critical

Duration.compare violates TC39 Temporal spec for calendar units.

Per TC39 Temporal specification, Duration.compare(one, two) must throw RangeError if either duration has nonzero years or months components without a relativeTo option. The current implementation (1) accepts only 2 arguments and lacks support for a relativeTo parameter, (2) silently ignores years and months fields in the nanoseconds calculation, and (3) produces incorrect comparison results for durations with calendar units.

The function must be updated to:

  • Accept an optional third argument for { relativeTo } options
  • Validate and throw RangeError if either duration has nonzero years or months and relativeTo is absent or invalid
  • For ISO calendar (the only supported calendar), weeks do not require relativeTo (fixed 7 days), but years/months do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Builtins.Temporal.pas` around lines 310 - 316, The
Duration.compare implementation must be updated: change the signature of
Duration.compare to accept an optional third parameter (options) carrying
relativeTo, validate options.relativeTo (or its absence), and if either input
duration (D1 or D2) has nonzero years or months and no valid relativeTo is
provided, raise a RangeError; do not silently ignore years/months in the TotalNs
calculation—if relativeTo is provided and uses the ISO calendar, convert
years/months to days/nanoseconds using the calendar-aware conversion logic
before computing TotalNs1/TotalNs2 (weeks remain fixed as 7 days and do not
require relativeTo), and then compare the computed totals. Ensure the RangeError
logic and relativeTo handling are implemented in the Duration.compare function
that computes TotalNs1/TotalNs2 so comparisons of calendar-unit durations follow
the TC39 Temporal spec.
🧹 Nitpick comments (2)
source/units/Goccia.Builtins.Temporal.pas (2)

243-247: Consider adding property name constants for Duration's plural field names.

Duration uses plural property names ('years', 'months', etc.) per TC39 spec, distinct from the singular PROP_YEAR, PROP_MONTH constants used by other Temporal types. For consistency with the project's property-name constant pattern, consider adding PROP_YEARS, PROP_MONTHS, PROP_WEEKS, etc. to Goccia.Constants.PropertyNames.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Builtins.Temporal.pas` around lines 243 - 247, Add plural
property-name constants for Duration to Goccia.Constants.PropertyNames (e.g.,
PROP_YEARS, PROP_MONTHS, PROP_WEEKS, PROP_DAYS, PROP_HOURS, PROP_MINUTES,
PROP_SECONDS, PROP_MILLISECONDS, PROP_MICROSECONDS, PROP_NANOSECONDS) and update
usages in duration construction sites to use those constants instead of
hard-coded strings; specifically replace the string keys passed to GetFieldOr in
TGocciaTemporalDurationValue.Create with the new PROP_* constants and ensure the
constants follow the same naming and visibility pattern as existing
PROP_YEAR/PROP_MONTH so other code can reuse them.

1096-1117: Consider adding object coercion to PlainYearMonthCompare for consistency.

PlainDateCompare, PlainTimeCompare, and PlainDateTimeCompare now accept object inputs with property-bag coercion, but PlainYearMonthCompare only accepts TGocciaTemporalPlainYearMonthValue or string inputs. For API consistency, consider adding object coercion that reads year and month properties.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Builtins.Temporal.pas` around lines 1096 - 1117,
CoerceYearMonth currently only handles TGocciaTemporalPlainYearMonthValue and
string inputs; add object coercion like other comparators by detecting
TGocciaObjectValue in CoerceYearMonth, extracting the "year" and "month"
properties (accept number or string), convert them to integers (or use existing
CoerceToISOYearMonth helper/validation), and construct
TGocciaTemporalPlainYearMonthValue.Create(Y, M) on success; on bad/missing props
call ThrowRangeError(SErrorInvalidISOYearMonth, SSuggestTemporalISOFormat) or
ThrowTypeError(SErrorTemporalPlainYearMonthCompareArg,
SSuggestTemporalCompareArg) consistent with existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/units/Goccia.Temporal.Options.pas`:
- Around line 559-594: ResolveTemporalToStringOptions currently silently accepts
primitive options by treating them like nil; change the initial validation so
that AOptions = nil (undefined) still returns, but if AOptions is a primitive
(not an object) the routine raises a TypeError (use ThrowTypeError with a
message like "options must be an object or undefined"); keep the existing
precedence where smallestUnit (handled by
GetOptionString/GetTemporalUnitFromString) overrides fractionalSecondDigits
(GetFractionalSecondDigits) and retain ARoundingMode via GetRoundingMode, and
update the comment to remove the incorrect "mutually exclusive" wording.

In `@source/units/Goccia.Temporal.Utils.pas`:
- Around line 938-957: The loop that extracts bracket annotations currently
assigns the first non-calendar annotation to ATimeZone and silently drops any
subsequent zone-style annotations; change it to reject a second zone-style
annotation by detecting when AnnotContent (after stripping a leading '!') is a
non-'u-ca=' annotation and ATimeZone is already non-empty, then raise/return an
error (or set an invalid flag) instead of overwriting or ignoring it; update the
logic inside the while loop that processes AnnotContent/ATimeZone/Rest so the
parser fails on a second zone annotation rather than silently accepting only the
first.
- Around line 1101-1125: CoerceToISOInstant currently accepts offsetless
datetimes by falling back to TryParseISODateTime and forcing AOffsetSeconds :=
0; remove that fallback so only inputs parsed by TryParseISODateTimeWithOffset
succeed. In practice, delete the block that calls TryParseISODateTime (lines
using TryParseISODateTime, setting AOffsetSeconds := 0 and returning True), and
ensure the function returns False when TryParseISODateTimeWithOffset fails so
offsetless strings cause failure; keep the existing annotation stripping and
TimeZone-aware parsing in CoerceToISOInstant intact.

In `@source/units/Goccia.Values.TemporalPlainDate.pas`:
- Around line 657-668: In TGocciaTemporalPlainDateValue.DateWithCalendar guard
access to Arg before calling Arg.ToStringLiteral.Value: after Arg :=
AArgs.GetElement(0) check that Arg is not nil (or represents undefined/missing
in your runtime) and if it is, raise a JS-visible error (e.g. call
ThrowTypeError with an appropriate message) instead of dereferencing; then
proceed to read CalId and the existing calendar validation and result creation.

In `@source/units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 871-881: In
TGocciaTemporalPlainDateTimeValue.DateTimeWithCalendar, guard the argument
returned by AArgs.GetElement(0) before calling Arg.ToStringLiteral: check if Arg
is nil (or represents undefined) and throw the appropriate JS-visible error (use
the same throw helper pattern as other methods, e.g. ThrowTypeError or
ThrowRangeError with SSuggestTemporalFromArg) instead of dereferencing it, then
only call Arg.ToStringLiteral.Value and validate CalId.

In `@source/units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 292-307: MonthDayToString currently emits only "MM-DD" then
FormatCalendarAnnotation, which drops the instance's FReferenceYear and breaks
round-tripping when a calendar annotation is present; modify
TGocciaTemporalPlainMonthDayValue.MonthDayToString so that if
GetCalendarDisplay(OptionsObj) indicates a calendar annotation should be shown
(i.e., CalDisp is not the value meaning "none"), the string uses the full
"YYYY-MM-DD" form by including PadFourOrSigned(FReferenceYear) (or otherwise
formatting FReferenceYear as a four-digit year) before '-' + PadTwo(FMonth) +
'-' + PadTwo(FDay) and then append FormatCalendarAnnotation(CalDisp) when
creating the TGocciaStringLiteralValue; leave the existing "MM-DD" behavior only
when no calendar annotation is requested.

In `@source/units/Goccia.Values.TemporalPlainYearMonth.pas`:
- Around line 475-490: The YearMonthToString implementation
(TGocciaTemporalPlainYearMonthValue.YearMonthToString) must include the
reference day when the calendar is non-ISO; update the code that builds the
string (currently using FormatYearMonthString(YM.FYear, YM.FMonth) +
FormatCalendarAnnotation(CalDisp)) to insert a "-DD" reference day before the
calendar annotation for non-ISO calendars—obtain the day from the PlainYearMonth
instance (YM) via its stored reference day field or accessor (e.g.,
YM.ReferenceDay or the existing field) and format it as two digits; keep using
GetCalendarDisplay(OptionsObj) to decide when to include the calendar annotation
and only append the reference day when the calendar is not ISO so the result
becomes "YYYY-MM-DD" (plus FormatCalendarAnnotation(CalDisp) when required).

---

Outside diff comments:
In `@source/units/Goccia.Builtins.Temporal.pas`:
- Around line 884-898: The PlainDateTimeFrom branch is missing required-field
validation: when Arg is a TGocciaObjectValue you must ensure PROP_YEAR,
PROP_MONTH and PROP_DAY are present and not TGocciaUndefinedLiteralValue and
raise a TypeError if any are missing (same behavior as PlainDateFrom), before
extracting and Trunc()-ing values and creating
TGocciaTemporalPlainDateTimeValue; update the code that reads V :=
Obj.GetProperty(PROP_YEAR)/PROP_MONTH/PROP_DAY to check for Assigned(V) AND not
(V is TGocciaUndefinedLiteralValue) and if that check fails for year, month or
day call the runtime TypeError throw path used elsewhere.
- Around line 310-316: The Duration.compare implementation must be updated:
change the signature of Duration.compare to accept an optional third parameter
(options) carrying relativeTo, validate options.relativeTo (or its absence), and
if either input duration (D1 or D2) has nonzero years or months and no valid
relativeTo is provided, raise a RangeError; do not silently ignore years/months
in the TotalNs calculation—if relativeTo is provided and uses the ISO calendar,
convert years/months to days/nanoseconds using the calendar-aware conversion
logic before computing TotalNs1/TotalNs2 (weeks remain fixed as 7 days and do
not require relativeTo), and then compare the computed totals. Ensure the
RangeError logic and relativeTo handling are implemented in the Duration.compare
function that computes TotalNs1/TotalNs2 so comparisons of calendar-unit
durations follow the TC39 Temporal spec.

In `@source/units/Goccia.Values.TemporalZonedDateTime.pas`:
- Around line 1111-1151: Recompute and use the zone offset after rounding:
currently OffsetSeconds is obtained before calling RoundTimeForToString, so if
rounding changes the wall-clock instant across an offset boundary the formatted
string will show a stale offset. Move or repeat the call to GetUtcOffsetSeconds
after RoundTimeForToString (and after adjusting DateRec when ExtraDays != 0)
using Zdt.FTimeZone and the possibly adjusted epoch/instant (derived from the
rounded local fields or by computing the shifted epoch milliseconds
corresponding to DateRec and LHour/LMinute/etc.), then use that recomputed
OffsetSeconds in FormatOffsetString; ensure the new OffsetSeconds replaces the
original variable used when building S.

---

Nitpick comments:
In `@source/units/Goccia.Builtins.Temporal.pas`:
- Around line 243-247: Add plural property-name constants for Duration to
Goccia.Constants.PropertyNames (e.g., PROP_YEARS, PROP_MONTHS, PROP_WEEKS,
PROP_DAYS, PROP_HOURS, PROP_MINUTES, PROP_SECONDS, PROP_MILLISECONDS,
PROP_MICROSECONDS, PROP_NANOSECONDS) and update usages in duration construction
sites to use those constants instead of hard-coded strings; specifically replace
the string keys passed to GetFieldOr in TGocciaTemporalDurationValue.Create with
the new PROP_* constants and ensure the constants follow the same naming and
visibility pattern as existing PROP_YEAR/PROP_MONTH so other code can reuse
them.
- Around line 1096-1117: CoerceYearMonth currently only handles
TGocciaTemporalPlainYearMonthValue and string inputs; add object coercion like
other comparators by detecting TGocciaObjectValue in CoerceYearMonth, extracting
the "year" and "month" properties (accept number or string), convert them to
integers (or use existing CoerceToISOYearMonth helper/validation), and construct
TGocciaTemporalPlainYearMonthValue.Create(Y, M) on success; on bad/missing props
call ThrowRangeError(SErrorInvalidISOYearMonth, SSuggestTemporalISOFormat) or
ThrowTypeError(SErrorTemporalPlainYearMonthCompareArg,
SSuggestTemporalCompareArg) consistent with existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5376652e-4d07-4de6-8af0-4d96244e4608

📥 Commits

Reviewing files that changed from the base of the PR and between b3a596a and c889a15.

📒 Files selected for processing (13)
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.Builtins.Temporal.pas
  • source/units/Goccia.Temporal.Options.pas
  • source/units/Goccia.Temporal.TimeZone.pas
  • source/units/Goccia.Temporal.Utils.pas
  • source/units/Goccia.Values.TemporalDuration.pas
  • source/units/Goccia.Values.TemporalInstant.pas
  • source/units/Goccia.Values.TemporalPlainDate.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Values.TemporalPlainTime.pas
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
  • source/units/Goccia.Values.TemporalZonedDateTime.pas

Comment thread source/units/Goccia.Temporal.Options.pas
Comment thread source/units/Goccia.Temporal.Utils.pas
Comment thread source/units/Goccia.Temporal.Utils.pas Outdated
Comment thread source/units/Goccia.Values.TemporalPlainDate.pas
Comment thread source/units/Goccia.Values.TemporalPlainDateTime.pas
Comment thread source/units/Goccia.Values.TemporalPlainMonthDay.pas
Comment thread source/units/Goccia.Values.TemporalPlainYearMonth.pas
- Fix misleading "mutually exclusive" comment in ResolveTemporalToStringOptions
  (smallestUnit takes precedence, not mutually exclusive)
- Reject duplicate timezone annotations in TryParseISODateTimeWithOffset
- Remove offsetless datetime fallback from CoerceToISOInstant (spec requires
  explicit Z or ±HH:MM offset for Instant coercion)
- Guard withCalendar() against nil/undefined argument in PlainDate and
  PlainDateTime to prevent access violation
- Include reference year in PlainMonthDay.toString() when calendar annotation
  is present (required for round-tripping per spec)
- Include reference day in PlainYearMonth.toString() when calendar annotation
  is present (required for round-tripping per spec)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the documentation Improvements or additions to documentation label Apr 19, 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: 3

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.Utils.pas (1)

989-1009: ⚠️ Potential issue | 🟠 Major

Require the UTC offset token to be fully consumed.

Once the hour/minute fields parse, any trailing bytes in the offset token are ignored. 2020-01-01T00:00+01:00junk currently succeeds instead of being rejected as an invalid Temporal string, violating the TC39 Temporal ISO8601 parsing specification.

🐛 Minimal fix
       else if ParsePos + 1 <= Length(OffsetPart) then
       begin
         if not TryParseDigits(OffsetPart, ParsePos, 2, OffsetM) then Exit;
       end;
+      if ParsePos <= Length(OffsetPart) then Exit;
       // Validate ranges
       if (OffsetH > 23) or (OffsetM > 59) then Exit;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Temporal.Utils.pas` around lines 989 - 1009, The offset
parsing currently allows trailing characters because ParsePos isn't checked
after parsing hours/minutes; inside the block that handles OffsetPart (use
symbols OffsetPart, ParsePos, TryParseDigits, OffsetH, OffsetM, OffsetSign,
AOffsetSeconds, ATimeZone), after validating ranges and before setting
AOffsetSeconds/ATimeZone add a check that ParsePos > Length(OffsetPart) (or
equivalently that ParsePos is past the last character) and Exit if there are
leftover characters so tokens like " +01:00junk " are rejected; keep the
existing range checks and assignments otherwise.
♻️ Duplicate comments (1)
source/units/Goccia.Temporal.Utils.pas (1)

1108-1116: ⚠️ Potential issue | 🟠 Major

CoerceToISOInstant still accepts offsetless date-times.

The old fallback block is gone, but TryParseISODateTimeWithOffset itself still succeeds on YYYY-MM-DDTHH:mm without Z / ±HH:MM, leaving this path to coerce an invalid Instant string as UTC.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Temporal.Utils.pas` around lines 1108 - 1116,
CoerceToISOInstant currently accepts offsetless strings because
TryParseISODateTimeWithOffset still succeeds on them; update CoerceToISOInstant
to enforce an explicit UTC offset by checking the input AStr for a timezone
designator before calling or accepting TryParseISODateTimeWithOffset (e.g.
require trailing 'Z' or a '+'/'-' offset after the time portion), and if none is
present set Result := False and Exit; alternatively, modify
TryParseISODateTimeWithOffset to return a flag indicating whether an offset was
present and make CoerceToISOInstant reject parses with no offset; reference
AStr, StripAnnotations, TryParseISODateTimeWithOffset and CoerceToISOInstant
when applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/units/Goccia.Temporal.Utils.pas`:
- Around line 449-452: In both StripAnnotations and
TryParseISODateTimeWithOffset, after extracting AnnotContent and stripping the
critical '!' prefix (variables AnnotContent and IsCritical), add a check that
rejects empty annotation content (Length(AnnotContent) = 0) so that inputs like
"2020-01-01[]" or "2020-01-01T00:00[!]" are treated as invalid; implement this
by returning failure/false from TryParseISODateTimeWithOffset and by not
treating the annotation as a timezone-annotation in StripAnnotations (or
otherwise signalling an invalid annotation).

In `@source/units/Goccia.Values.TemporalPlainDate.pas`:
- Around line 529-533: The toString implementation currently treats any
non-object argument as undefined; before calling GetCalendarDisplay you must
validate the first argument (Arg := AArgs.GetElement(0)): if an argument is
present and is not TGocciaObjectValue and is not an explicit undefined, raise a
TypeError (use the existing SSuggestTemporalFromArg message or equivalent)
instead of silently treating it as nil; only set OptionsObj when Arg is an
object and then call GetCalendarDisplay(OptionsObj).

In `@source/units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 749-752: Replace the current loose type-check with a ToObject
coercion: after fetching Arg := AArgs.GetElement(0), call the value's ToObject
coercion and assign/convert that result to OptionsObj (e.g. Arg := Arg.ToObject
or OptionsObj := TGocciaObjectValue(Arg.ToObject)); this will cause primitives
to raise the required TypeError per TC39 Temporal and ensure only proper objects
reach the TGocciaObjectValue cast used by the toString('auto') path. Update the
same pattern wherever similar OptionsObj/Arg handling occurs in other Temporal
types.

---

Outside diff comments:
In `@source/units/Goccia.Temporal.Utils.pas`:
- Around line 989-1009: The offset parsing currently allows trailing characters
because ParsePos isn't checked after parsing hours/minutes; inside the block
that handles OffsetPart (use symbols OffsetPart, ParsePos, TryParseDigits,
OffsetH, OffsetM, OffsetSign, AOffsetSeconds, ATimeZone), after validating
ranges and before setting AOffsetSeconds/ATimeZone add a check that ParsePos >
Length(OffsetPart) (or equivalently that ParsePos is past the last character)
and Exit if there are leftover characters so tokens like " +01:00junk " are
rejected; keep the existing range checks and assignments otherwise.

---

Duplicate comments:
In `@source/units/Goccia.Temporal.Utils.pas`:
- Around line 1108-1116: CoerceToISOInstant currently accepts offsetless strings
because TryParseISODateTimeWithOffset still succeeds on them; update
CoerceToISOInstant to enforce an explicit UTC offset by checking the input AStr
for a timezone designator before calling or accepting
TryParseISODateTimeWithOffset (e.g. require trailing 'Z' or a '+'/'-' offset
after the time portion), and if none is present set Result := False and Exit;
alternatively, modify TryParseISODateTimeWithOffset to return a flag indicating
whether an offset was present and make CoerceToISOInstant reject parses with no
offset; reference AStr, StripAnnotations, TryParseISODateTimeWithOffset and
CoerceToISOInstant when applying the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bf796e17-17c6-43df-893e-d871d8ee62d5

📥 Commits

Reviewing files that changed from the base of the PR and between c889a15 and 054fa52.

📒 Files selected for processing (6)
  • source/units/Goccia.Temporal.Options.pas
  • source/units/Goccia.Temporal.Utils.pas
  • source/units/Goccia.Values.TemporalPlainDate.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
✅ Files skipped from review due to trivial changes (1)
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
🚧 Files skipped from review as they are similar to previous changes (2)
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Temporal.Options.pas

Comment thread source/units/Goccia.Temporal.Utils.pas
Comment thread source/units/Goccia.Values.TemporalPlainDate.pas
Comment thread source/units/Goccia.Values.TemporalPlainDateTime.pas
- Reject empty bracket annotations ([] and [!]) in both StripAnnotations
  and TryParseISODateTimeWithOffset
- Throw TypeError when toString options is a primitive (not object or
  undefined) across all 7 Temporal types with toString options

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 6

🧹 Nitpick comments (4)
source/units/Goccia.Values.TemporalPlainTime.pas (1)

502-550: Please add spec annotations above the updated PlainTime stringification methods.

TimeToString now has a new option-resolution/rounding flow, and PlainTimeToLocaleString is a new Temporal entry point, but neither changed body has the required spec-section comment immediately above it.

Based on learnings, “Annotate each function implementing ECMAScript-specified behavior with // ESYYYY §X.Y.Z SpecMethodName(specParams) immediately above the function body.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainTime.pas` around lines 502 - 550, Add
the required ECMAScript spec annotations as single-line comments immediately
above each updated Temporal PlainTime method: place lines like "// ES2023 §X.Y.Z
TimeToString(this, options)" above function TimeToString, "// ES2023 §X.Y.Z
toJSON" above TimeToJSON, "// ES2023 §X.Y.Z valueOf" above TimeValueOf, and "//
ES2023 §X.Y.Z PlainTimeToLocaleString(this, locales, options)" above
PlainTimeToLocaleString (use the actual spec section numbers if known); ensure
the comment format matches the project's convention exactly and is placed
immediately above each function header.
source/units/Goccia.Values.TemporalPlainDate.pas (1)

521-675: Add spec-section comments for the changed Temporal methods.

The modified DateToString implementation and the new DateToLocaleString / DateWithCalendar bodies are ECMAScript-defined entry points, but this changed surface still has no spec-section comments above the functions. Please add them while touching these methods.

Based on learnings, “Annotate each function implementing ECMAScript-specified behavior with // ESYYYY §X.Y.Z SpecMethodName(specParams) immediately above the function body.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainDate.pas` around lines 521 - 675, Add
ES spec-section comments immediately above each ECMAScript entry-point function
added/modified: DateToString, DateToLocaleString, DateWithCalendar (and any
other Temporal methods in this diff such as DateToJSON, DateValueOf,
DateToPlainDateTime, DateToPlainYearMonth, DateToPlainMonthDay,
DateToZonedDateTime if applicable). For each function insert a single-line
comment in the form "// ESYYYY §X.Y.Z SpecMethodName(specParams)" (use the
correct year/section and the exact spec method name like
PlainDate.prototype.toString, PlainDate.prototype.toLocaleString,
PlainDate.prototype.withCalendar) directly above the function declaration (e.g.
above "function TGocciaTemporalPlainDateValue.DateToString(...)" etc.). Ensure
formatting matches existing project convention and update only the comment
lines.
source/units/Goccia.Values.TemporalPlainYearMonth.pas (1)

474-543: Keep the Temporal spec annotations in sync with the new method surface.

Line 475 now implements an options-taking toString, but the adjacent spec comment still documents toString(), and the new YearMonthToLocaleString body has no spec-section comment at all. Please add/update the annotations here so the changed Temporal entry points keep the required spec traceability.

Based on learnings, “Annotate each function implementing ECMAScript-specified behavior with // ESYYYY §X.Y.Z SpecMethodName(specParams) immediately above the function body.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainYearMonth.pas` around lines 474 -
543, Update the spec-comment annotations above the Temporal PlainYearMonth
methods to the required ES-style lines: replace the TC39 comments for
YearMonthToString, YearMonthToJSON, YearMonthValueOf, YearMonthToPlainDate with
lines of the form "// ESYYYY §X.Y.Z YearMonthToString(options)" (and the correct
spec method names/params) immediately above each corresponding function
(YearMonthToString, YearMonthToJSON, YearMonthValueOf, YearMonthToPlainDate) and
add a new spec annotation line for YearMonthToLocaleString immediately above its
function; ensure the annotation text matches the new method signatures (e.g.,
include "options" for toString and the correct SpecMethodName for
toLocaleString) so each implementation has an ESYYYY-style spec traceability
comment.
source/units/Goccia.Values.TemporalPlainDateTime.pas (1)

875-890: LGTM with minor suggestion: consider using error message constants.

The implementation is correct—the nil/undefined guard is in place, and rejecting non-iso8601 calendars matches the engine's ISO-only calendar support.

The hardcoded error messages on lines 884 and 887 could be moved to Goccia.Error.Messages for consistency with other Temporal units, but this is low priority since the messages are clear and the method works correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainDateTime.pas` around lines 875 - 890,
Move the two hardcoded error strings in
TGocciaTemporalPlainDateTimeValue.DateTimeWithCalendar into message constants in
Goccia.Error.Messages and use those constants in the ThrowTypeError and
ThrowRangeError calls; specifically, add descriptive constant identifiers (e.g.,
PlainDateTimeWithCalendar_MissingCalendar and
PlainDateTimeWithCalendar_UnknownCalendar) to Goccia.Error.Messages, replace the
literal 'PlainDateTime.prototype.withCalendar requires a calendar argument' and
'Unknown calendar: ' + CalId with those constants (concatenating CalId to the
unknown-calendar constant if needed), and leave the existing
SSuggestTemporalFromArg parameter and ThrowTypeError/ThrowRangeError call sites
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/units/Goccia.Values.TemporalInstant.pas`:
- Around line 525-528: TGocciaTemporalInstantValue.InstantToLocaleString
currently forwards the incoming AArgs (which may contain locales/options) into
InstantToString; instead, implement an adapter that discards locales/options and
calls the normal toString path. Change InstantToLocaleString to ignore/clear the
TGocciaArgumentsCollection parameters (do not forward locales/options) and call
InstantToString in its no-locale form (i.e., pass an empty/default
TGocciaArgumentsCollection or the overload that only uses AThisValue), and apply
the same pattern to the other Temporal type methods (PlainDate, PlainDateTime,
PlainTime, YearMonth, MonthDay, ZonedDateTime) so toLocaleString always returns
toString() without using locales/options.
- Around line 530-549: In InstantToZonedDateTimeISO, accept a
TGocciaTemporalZonedDateTimeValue in addition to TGocciaStringLiteralValue: when
Arg is TGocciaStringLiteralValue use its Value as TZ, and when Arg is
TGocciaTemporalZonedDateTimeValue extract the time zone via its TimeZone
property and assign to TZ; otherwise keep throwing the existing type error
(ThrowTypeError(..., SSuggestTemporalTimezone)); then construct the
TGocciaTemporalZonedDateTimeValue result using Inst.FEpochMilliseconds,
Inst.FSubMillisecondNanoseconds and the extracted TZ.

In `@source/units/Goccia.Values.TemporalPlainDate.pas`:
- Around line 661-673: In TGocciaTemporalPlainDateValue.DateWithCalendar, Arg is
coerced via Arg.ToStringLiteral.Value without rejecting Symbol values; add a
pre-check for TGocciaSymbolValue and call ThrowTypeError('Cannot convert symbol
to string', SSuggestTemporalFromArg) if Arg is a symbol before performing the
ToStringLiteral conversion so Symbol inputs produce a TypeError instead of being
stringified and causing a RangeError.

In `@source/units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 356-359: The MonthDayToLocaleString implementation incorrectly
forwards AArgs to MonthDayToString causing locale string to be treated as
options; change TGocciaTemporalPlainMonthDayValue.MonthDayToLocaleString to call
MonthDayToString with an empty TGocciaArgumentsCollection (or strip out the
locales/options parameters) so that the fallback toString ignores both locale
and options. Apply the same fix pattern to the equivalent methods on
PlainMonthDay, PlainDate, PlainDateTime, PlainTime, PlainYearMonth,
ZonedDateTime, Instant and Duration types (use the corresponding ToLocaleString
methods and their ToString helpers like MonthDayToString) so locales/options are
discarded in non-Intl fallback.

In `@source/units/Goccia.Values.TemporalZonedDateTime.pas`:
- Around line 1116-1155: The code computes OffsetSeconds before calling
RoundTimeForToString, causing the final formatted string to use a stale offset
after rounding; after RoundTimeForToString (which may set ExtraDays) compute a
new rounded instant by adding ExtraDays * MILLISECONDS_PER_DAY to
Zdt.FEpochMilliseconds, then call ComputeLocalComponents on that rounded instant
and recompute OffsetSeconds via GetUtcOffsetSeconds(using roundedEpoch div
MILLISECONDS_PER_SECOND) before building TimeStr and S so both the local fields
and the offset reflect the rounded instant (update references around
OffsetSeconds, RoundTimeForToString, ComputeLocalComponents and
Zdt.FEpochMilliseconds).
- Around line 1184-1187: The
TGocciaTemporalZonedDateTimeValue.ZonedDateTimeToLocaleString implementation is
incorrectly forwarding AArgs to ZonedDateTimeToString (treating the first
locales param as options); change it to discard both locales and options by
creating an empty TGocciaArgumentsCollection and pass that to
ZonedDateTimeToString so the non-Intl fallback returns the same value as
toString() while ignoring parameters; ensure you reference ZonedDateTimeToString
and use a new/empty local arguments collection instead of AArgs inside
ZonedDateTimeToLocaleString.

---

Nitpick comments:
In `@source/units/Goccia.Values.TemporalPlainDate.pas`:
- Around line 521-675: Add ES spec-section comments immediately above each
ECMAScript entry-point function added/modified: DateToString,
DateToLocaleString, DateWithCalendar (and any other Temporal methods in this
diff such as DateToJSON, DateValueOf, DateToPlainDateTime, DateToPlainYearMonth,
DateToPlainMonthDay, DateToZonedDateTime if applicable). For each function
insert a single-line comment in the form "// ESYYYY §X.Y.Z
SpecMethodName(specParams)" (use the correct year/section and the exact spec
method name like PlainDate.prototype.toString,
PlainDate.prototype.toLocaleString, PlainDate.prototype.withCalendar) directly
above the function declaration (e.g. above "function
TGocciaTemporalPlainDateValue.DateToString(...)" etc.). Ensure formatting
matches existing project convention and update only the comment lines.

In `@source/units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 875-890: Move the two hardcoded error strings in
TGocciaTemporalPlainDateTimeValue.DateTimeWithCalendar into message constants in
Goccia.Error.Messages and use those constants in the ThrowTypeError and
ThrowRangeError calls; specifically, add descriptive constant identifiers (e.g.,
PlainDateTimeWithCalendar_MissingCalendar and
PlainDateTimeWithCalendar_UnknownCalendar) to Goccia.Error.Messages, replace the
literal 'PlainDateTime.prototype.withCalendar requires a calendar argument' and
'Unknown calendar: ' + CalId with those constants (concatenating CalId to the
unknown-calendar constant if needed), and leave the existing
SSuggestTemporalFromArg parameter and ThrowTypeError/ThrowRangeError call sites
unchanged.

In `@source/units/Goccia.Values.TemporalPlainTime.pas`:
- Around line 502-550: Add the required ECMAScript spec annotations as
single-line comments immediately above each updated Temporal PlainTime method:
place lines like "// ES2023 §X.Y.Z TimeToString(this, options)" above function
TimeToString, "// ES2023 §X.Y.Z toJSON" above TimeToJSON, "// ES2023 §X.Y.Z
valueOf" above TimeValueOf, and "// ES2023 §X.Y.Z PlainTimeToLocaleString(this,
locales, options)" above PlainTimeToLocaleString (use the actual spec section
numbers if known); ensure the comment format matches the project's convention
exactly and is placed immediately above each function header.

In `@source/units/Goccia.Values.TemporalPlainYearMonth.pas`:
- Around line 474-543: Update the spec-comment annotations above the Temporal
PlainYearMonth methods to the required ES-style lines: replace the TC39 comments
for YearMonthToString, YearMonthToJSON, YearMonthValueOf, YearMonthToPlainDate
with lines of the form "// ESYYYY §X.Y.Z YearMonthToString(options)" (and the
correct spec method names/params) immediately above each corresponding function
(YearMonthToString, YearMonthToJSON, YearMonthValueOf, YearMonthToPlainDate) and
add a new spec annotation line for YearMonthToLocaleString immediately above its
function; ensure the annotation text matches the new method signatures (e.g.,
include "options" for toString and the correct SpecMethodName for
toLocaleString) so each implementation has an ESYYYY-style spec traceability
comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37fcab77-419b-47c2-865f-de3b6497c55b

📥 Commits

Reviewing files that changed from the base of the PR and between 054fa52 and 50e9628.

📒 Files selected for processing (8)
  • source/units/Goccia.Temporal.Utils.pas
  • source/units/Goccia.Values.TemporalInstant.pas
  • source/units/Goccia.Values.TemporalPlainDate.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Values.TemporalPlainTime.pas
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
  • source/units/Goccia.Values.TemporalZonedDateTime.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/units/Goccia.Temporal.Utils.pas

Comment thread source/units/Goccia.Values.TemporalInstant.pas
Comment thread source/units/Goccia.Values.TemporalInstant.pas
Comment thread source/units/Goccia.Values.TemporalPlainDate.pas
Comment thread source/units/Goccia.Values.TemporalPlainMonthDay.pas
Comment on lines +1116 to +1155
Arg: TGocciaValue;
OptionsObj: TGocciaObjectValue;
FracDigits, ExtraDays: Integer;
Mode: TTemporalRoundingMode;
CalDisp: TTemporalCalendarDisplay;
DateRec: TTemporalDateRecord;
TimeStr, S: string;
begin
Zdt := AsZonedDateTime(AThisValue, 'ZonedDateTime.prototype.toString');
ComputeLocalComponents(Zdt, LYear, LMonth, LDay, LHour, LMinute, LSecond, LMs, LUs, LNs);
OffsetSeconds := GetUtcOffsetSeconds(Zdt.FTimeZone, Zdt.FEpochMilliseconds div MILLISECONDS_PER_SECOND);

S := FormatDateString(LYear, LMonth, LDay) + 'T' +
FormatTimeString(LHour, LMinute, LSecond, LMs, LUs, LNs) +
FormatOffsetString(OffsetSeconds) +
'[' + Zdt.FTimeZone + ']';
OptionsObj := nil;
Arg := AArgs.GetElement(0);
if Assigned(Arg) and not (Arg is TGocciaUndefinedLiteralValue) then
begin
if not (Arg is TGocciaObjectValue) then
ThrowTypeError('options must be an object or undefined', SSuggestTemporalFromArg);
OptionsObj := TGocciaObjectValue(Arg);
end;
ResolveTemporalToStringOptions(OptionsObj, FracDigits, Mode);
CalDisp := GetCalendarDisplay(OptionsObj);
ExtraDays := 0;
RoundTimeForToString(LHour, LMinute, LSecond, LMs, LUs, LNs, ExtraDays, FracDigits, Mode);
if ExtraDays <> 0 then
DateRec := AddDaysToDate(LYear, LMonth, LDay, ExtraDays)
else
begin
DateRec.Year := LYear;
DateRec.Month := LMonth;
DateRec.Day := LDay;
end;
if FracDigits = -2 then // smallestUnit: minute
TimeStr := PadTwo(LHour) + ':' + PadTwo(LMinute)
else
TimeStr := FormatTimeWithPrecision(LHour, LMinute, LSecond, LMs, LUs, LNs, FracDigits);

S := FormatDateString(DateRec.Year, DateRec.Month, DateRec.Day) + 'T' +
TimeStr + FormatOffsetString(OffsetSeconds) +
'[' + Zdt.FTimeZone + ']' + FormatCalendarAnnotation(CalDisp);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 19, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Recompute the offset after rounding.

OffsetSeconds is captured before RoundTimeForToString, but the rounded wall time can carry into another date or across a zone transition. The final string then combines the rounded local date/time with the old offset, which can emit impossible combinations around DST boundaries. Build the rounded instant first, then derive both the local fields and the offset from that rounded instant before formatting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalZonedDateTime.pas` around lines 1116 -
1155, The code computes OffsetSeconds before calling RoundTimeForToString,
causing the final formatted string to use a stale offset after rounding; after
RoundTimeForToString (which may set ExtraDays) compute a new rounded instant by
adding ExtraDays * MILLISECONDS_PER_DAY to Zdt.FEpochMilliseconds, then call
ComputeLocalComponents on that rounded instant and recompute OffsetSeconds via
GetUtcOffsetSeconds(using roundedEpoch div MILLISECONDS_PER_SECOND) before
building TimeStr and S so both the local fields and the offset reflect the
rounded instant (update references around OffsetSeconds, RoundTimeForToString,
ComputeLocalComponents and Zdt.FEpochMilliseconds).

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.

Acknowledged — the offset recomputation after rounding is a valid concern for DST boundaries. Currently our timezone implementation uses a fixed offset per-call, but this should be revisited when we add full DST transition support. Tracked for follow-up.

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. The stale-offset issue is a real DST edge case, but if the current implementation uses a fixed offset per-call, the risk is limited to transition zones and won't affect the majority of test262 cases passing today. Deferring this alongside full DST transition support makes sense.

Would you like me to open a follow-up issue to track the offset recomputation work so it doesn't get lost?


✏️ Learnings added
Learnt from: frostney
Repo: frostney/GocciaScript PR: 354
File: source/units/Goccia.Values.TemporalZonedDateTime.pas:1116-1155
Timestamp: 2026-04-19T18:53:34.108Z
Learning: In `source/units/Goccia.Values.TemporalZonedDateTime.pas` (frostney/GocciaScript, PR `#354`), `ZonedDateTimeToString` computes `OffsetSeconds` via `GetUtcOffsetSeconds` **before** calling `RoundTimeForToString`. After rounding, `ExtraDays` may push the instant across a DST boundary, making the captured offset stale. The fix (recompute `OffsetSeconds` from the rounded epoch after applying `ExtraDays`) is intentionally deferred to the follow-up PR that adds full DST transition support. Do not re-flag the pre-rounding offset capture in `ZonedDateTimeToString` until that DST PR lands.

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: 346
File: source/units/Goccia.Values.TemporalZonedDateTime.pas:622-628
Timestamp: 2026-04-18T22:41:49.426Z
Learning: In `source/units/Goccia.Values.TemporalZonedDateTime.pas` (frostney/GocciaScript), `ZonedDateTimeUntil` and `ZonedDateTimeSince` were fixed in commit 70a89a69 (PR `#346`). They no longer collapse the delta into a total `DiffNs: Int64`. Instead they use the same two-field `{DiffMs: Int64, DiffSubMs: Integer}` decomposition as `Instant`: normalize sign between the two fields, then decompose `DiffMs` into hours/minutes/seconds/milliseconds and `DiffSubMs` into microseconds/nanoseconds directly. Do not flag `Int64` overflow risk in these methods — the total-nanosecond collapse has been removed.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 308
File: units/Goccia.Values.TemporalPlainMonthDay.pas:0-0
Timestamp: 2026-04-15T20:56:34.721Z
Learning: In frostney/GocciaScript Temporal value units (e.g., units/Goccia.Values.TemporalPlainMonthDay.pas), use `SSuggestTemporalThisType` when throwing a TypeError because `AThisValue` (or the first arg) is not the expected Temporal type (wrong-receiver / wrong-type-for-helper checks). Use `SSuggestTemporalToPlainDateYear` when throwing because a `toPlainDate` call is missing or receives a non-object/undefined `year` property. Do NOT use the broader `SSuggestTemporalFromArg` for these two specific cases.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:630-656
Timestamp: 2026-04-17T14:39:13.057Z
Learning: In frostney/GocciaScript, `ParseRelativeTo` in `units/Goccia.Values.TemporalDuration.pas` intentionally supports only `TGocciaTemporalPlainDateValue` and ISO date/date-time string inputs for `relativeTo` in `Duration.prototype.total()`. Support for `TGocciaTemporalZonedDateTimeValue` and property-bag/object inputs is deferred to a dedicated follow-up PR, as it requires importing the ZonedDateTime unit, extracting the plain date, and handling DST-aware day lengths. Do not re-flag missing ZonedDateTime/property-bag handling in `ParseRelativeTo` until that follow-up PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 308
File: units/Goccia.Values.TemporalPlainDateTime.pas:144-145
Timestamp: 2026-04-16T06:33:07.065Z
Learning: In frostney/GocciaScript Temporal unit implementations (e.g., units/Goccia.Values.TemporalPlainDateTime.pas, units/Goccia.Values.TemporalPlainDate.pas, units/Goccia.Values.TemporalPlainTime.pas, units/Goccia.Values.TemporalPlainYearMonth.pas, units/Goccia.Values.TemporalPlainMonthDay.pas, units/Goccia.Values.TemporalInstant.pas, units/Goccia.Values.TemporalZonedDateTime.pas, units/Goccia.Builtins.Temporal.pas), the correction of ThrowTypeError → ThrowRangeError for invalid ISO/time/duration string parsing failures (per TC39 Temporal spec) is intentionally deferred to a dedicated follow-up PR. PRs that only centralize error message constants and add suggestion metadata must not change error types (TypeError vs RangeError). Do not re-flag the ThrowTypeError usage for ISO/time/duration parse failures in these Temporal units until the dedicated behavioral-change PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:891-920
Timestamp: 2026-04-17T14:39:20.659Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), the engine supports only the ISO 8601 calendar. Weeks are always exactly 7 days in this calendar, so Duration.prototype.total does NOT require `relativeTo` for the 'weeks' unit or for durations with non-zero FWeeks components. The fixed `weeks * 7 * NANOSECONDS_PER_DAY` calculation is intentional and correct. Do not flag missing relativeTo guards for week units/components in this file. If/when non-ISO calendar support is added, this guard should be revisited.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 346
File: source/units/Goccia.Values.TemporalInstant.pas:178-184
Timestamp: 2026-04-18T22:29:31.789Z
Learning: In `source/units/Goccia.Values.TemporalInstant.pas` (frostney/GocciaScript), the `GetEpochNanoseconds` getter is safe from overflow. `FEpochMilliseconds` is `Int64` (max ≈ ±9.2×10¹⁸ ms) and the valid Temporal instant range is ±8.64×10¹⁵ ms, so `TBigInteger.FromInt64(Inst.FEpochMilliseconds)` is always lossless. Do NOT flag `TBigInteger.FromInt64` on `FEpochMilliseconds` in this getter as a potential overflow.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 327
File: units/Goccia.Values.TemporalDuration.pas:541-561
Timestamp: 2026-04-17T16:31:38.698Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), `DurationRound` intentionally parses and validates the `relativeTo` option eagerly (before `NeedCalendar` is computed) because the TC39 Temporal spec calls `ToRelativeTemporalObject` as an early step of `Duration.prototype.round` — before any check for whether calendar math is needed. An invalid `relativeTo` must throw even for a pure time/day round. Do NOT flag the eager parsing in `DurationRound` as incorrect or suggest deferring it until `NeedCalendar` is known. If `DurationTotal` uses lazy parsing, that is the less spec-compliant path, not `DurationRound`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 277
File: units/Goccia.Builtins.Temporal.pas:0-0
Timestamp: 2026-04-12T13:19:32.780Z
Learning: For all Temporal-related units in GocciaScript (e.g., files like Goccia.Builtins.Temporal.pas and Goccia.Values.TemporalPlainTime.pas), use the shared Temporal property-name constants defined in units/Goccia.Constants.PropertyNames.pas (PROP_YEAR, PROP_MONTH, PROP_MONTH_CODE, PROP_DAY, PROP_CALENDAR_ID, plus a full set of time unit constants: PROP_HOUR, PROP_MINUTE, PROP_SECOND, PROP_MILLISECOND, PROP_MICROSECOND, PROP_NANOSECOND) when accessing Temporal properties and when constructing any related error messages. Do not use hardcoded string literals for these Temporal property names in Temporal units.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:691-693
Timestamp: 2026-04-17T14:59:54.438Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), TC39 Temporal Duration magnitude validation (calendar units < 2^32, normalized seconds < 2^53) is intentionally deferred to a follow-up PR. The validation belongs in the `TGocciaTemporalDurationValue` constructor and `DurationFromObject`, not in `DurationEndDate`, `AddMonthsToDate`, or `DurationTotal`. Do not re-flag missing magnitude validation in `DurationEndDate` or `total()` path — the arithmetic there is safe for spec-compliant inputs; the gap is upstream validation not yet implemented.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 327
File: units/Goccia.Values.TemporalDuration.pas:667-671
Timestamp: 2026-04-17T16:42:55.749Z
Learning: In `units/Goccia.Values.TemporalDuration.pas` (`DurationRound`, `tuYear`/`tuMonth` calendar-rounding path), when aligning a fractional `WholeUnits` count to an `Increment`-sized bucket for **negative** durations, use `WholeUnits - 1` as the floor-division base instead of `WholeUnits`. For negative durations the walk stops at `WholeUnits` (e.g. -2) but the actual fractional position is further negative (e.g. -3), so `floor(-2 / Increment) * Increment` selects the wrong (too-high) bucket. Subtracting 1 before the floor division (i.e. `floor((WholeUnits - 1) / Increment) * Increment`) selects the bucket whose interval actually contains the fractional position. Do not re-flag this alignment logic and do not suggest adjusting `RoundWithMode` inputs as a sign-preservation fix — the correct fix is the bucket-base adjustment. Regression test: `-P3M` from 2021-07-31 with `roundingIncrement: 2, roundingMode: "halfExpand"` must round to `-P4M`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 337
File: source/units/Goccia.Builtins.TSV.pas:315-319
Timestamp: 2026-04-18T13:58:01.524Z
Learning: In `source/units/Goccia.Builtins.TSV.pas` (and the parallel `source/units/Goccia.Builtins.CSV.pas`), `startOffset`/`endOffset` numeric arguments passed to `parseChunk` are intentionally truncated with `Trunc()` without a prior NaN/Infinity guard. The resulting integer (however large or negative) is immediately passed to `ClampOffset` in `TGocciaTSVParser`/`TGocciaCSVParser`, which clamps negative values to 0 and values beyond `TextLength` to `TextLength`. This makes a separate finite-number validation unnecessary. Do not re-flag missing NaN/Infinity guards on `Trunc()` calls for offset arguments in these `parseChunk` methods.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 351
File: source/units/Goccia.Engine.pas:1388-1402
Timestamp: 2026-04-19T03:29:53.111Z
Learning: In frostney/GocciaScript (PR `#351`), `Goccia.gc.bytesAllocated` (implemented as `TGocciaEngine.GocciaGCBytesAllocatedGetter` in `source/units/Goccia.Engine.pas`) returns a freshly allocated `TGocciaNumberLiteralValue`, which itself goes through `AfterConstruction` → `RegisterObject` → `Inc(FBytesAllocated, InstanceSize)`. This means each read of `bytesAllocated` bumps the counter by ~48 bytes (one `TGocciaNumberLiteralValue` InstanceSize). This is a known, accepted limitation: (1) there is no untracked/unregistered allocation path in the codebase, (2) the metric is explicitly documented as "approximate", and (3) the ~48-byte per-read noise is negligible against any realistic heap size. Do NOT re-flag this self-inflation as a correctness or accuracy issue until a broader architectural decision is made about whether to add an untracked value path.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 277
File: units/Goccia.Values.TemporalPlainMonthDay.pas:0-0
Timestamp: 2026-04-12T10:23:58.234Z
Learning: In frostney/GocciaScript, Temporal-specific property name constants `PROP_YEAR`, `PROP_MONTH`, `PROP_MONTH_CODE`, `PROP_DAY`, and `PROP_CALENDAR_ID` were added to `Goccia.Constants.PropertyNames` (as of commit d59dd03, PR `#277`). All Temporal value units (e.g. `Goccia.Values.TemporalPlainMonthDay.pas`, `Goccia.Values.TemporalPlainYearMonth.pas`, etc.) must use these constants instead of hardcoded string literals for property accesses (`GetProperty`, `SetProperty`, accessor registration, `ThrowTypeError` messages referencing property names, etc.).

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: CR
Repo: frostney/GocciaScript PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T15:16:53.061Z
Learning: Applies to {**/Goccia.Values.*.pas,**/Goccia.Evaluator.pas,**/Goccia.Runtime.*.pas} : Annotate each function implementing ECMAScript-specified behavior with `// ESYYYY §X.Y.Z SpecMethodName(specParams)` immediately above the function body, using the spec's pseudo-code method name and parameters exactly as written

Learnt from: frostney
Repo: frostney/GocciaScript PR: 343
File: source/units/Goccia.Evaluator.Arithmetic.pas:49-55
Timestamp: 2026-04-18T21:08:03.166Z
Learning: In frostney/GocciaScript (commit 3344dede, PR `#343`), `TGocciaBigIntObjectValue` was added to support BigInt boxing via `Object(1n)`. `Box` in `ClassHelper.pas` now returns a `TGocciaBigIntObjectValue` for `TGocciaBigIntValue` primitives. `ToPrimitive` calls were added to `EvaluateSubtraction`, `EvaluateMultiplication`, `EvaluateDivision`, `EvaluateModulo`, and `EvaluateExponentiation` in `source/units/Goccia.Evaluator.Arithmetic.pas` so that boxed BigInts (e.g. `Object(1n) - 1n`) are correctly unboxed via `valueOf()` before BigInt type checks. `Object()` was also fixed to call `Box` for primitive arguments per ES2026 §20.1.1.1. Future reviews of arithmetic operators SHOULD expect `ToPrimitive` before BigInt checks.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 343
File: source/units/Goccia.Builtins.GlobalBigInt.pas:213-223
Timestamp: 2026-04-18T20:48:02.918Z
Learning: In `source/units/Goccia.Builtins.GlobalBigInt.pas` (frostney/GocciaScript, landed in commit 477b5609 on PR `#343`), `BigInt.asIntN` and `BigInt.asUintN` use proper `ToIndex` abstract-op coercion for the `bits` parameter: `undefined`/`NaN` → 0, `Infinity`/negative/oversized → `RangeError`. This is fully implemented and tested. Do not flag missing `ToIndex` coercion for these methods.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 343
File: source/units/Goccia.JSON.pas:841-844
Timestamp: 2026-04-18T20:08:00.723Z
Learning: In frostney/GocciaScript (source/units/Goccia.JSON.pas), `ApplyToJSON` intentionally only probes `toJSON` when the value is a `TGocciaObjectValue`. For BigInt primitives (`TGocciaBigIntValue`), `JSON.stringify(1n)` correctly throws TypeError per ES2026 §25.5.2.5 step 10 — this is the expected default behavior. Extending `ApplyToJSON` to call `GetProperty('toJSON')` directly on BigInt primitives (per ES2026 §25.5.2.2) is a known gap deferred to a follow-up PR. Do not re-flag the missing `toJSON` probe for BigInt in `ApplyToJSON` until that follow-up is scoped.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 346
File: source/shared/BigInteger.Test.pas:0-0
Timestamp: 2026-04-18T23:32:30.735Z
Learning: In `source/shared/BigInteger.Test.pas` (frostney/GocciaScript PR `#346`), `TestFromDoubleMaxSafeInteger` and `TestFromDoubleNegMaxSafeInteger` intentionally exercise `2^53 = 9007199254740992.0` (and its negation), not `Number.MAX_SAFE_INTEGER = 9007199254740991`. The value `2^53` is the upper boundary of the `FromDouble` Int64 fast-path added in `source/shared/BigInteger.pas` (commit c3fee5b0). The test labels were renamed to "2^53 (Int64 fast-path boundary)" to reflect this intent. Do not re-flag these test values as off-by-one for MAX_SAFE_INTEGER.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 109
File: units/Goccia.Runtime.Operations.pas:2797-2828
Timestamp: 2026-03-23T15:57:21.755Z
Learning: Repo frostney/GocciaScript — PR `#109`: Native Map/Set scaffolding
- Current behavior: ToSouffleValue wraps TGocciaMapValue/TGocciaSetValue as TGocciaWrappedValue; TGocciaSouffleMap/TGocciaSouffleSet instances are not produced in normal execution.
- Pre-wired fast paths: GetProperty, GetIterator, and IteratorNext recognize TGocciaSouffleMap/TGocciaSouffleSet and their iterator types; these are staged for future activation.
- Deferred work: When Construct is updated to produce native collection types, add corresponding fast paths in HasProperty, SetProperty, and DeleteProperty.

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: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Shared/utility units under the repository (e.g., units/BaseMap.pas, HashMap.pas, OrderedMap.pas, etc.) that are used across multiple parts of the project should not be required to use the Goccia.* prefix. A separate naming convention will be defined in a follow-up issue. Do not flag missing Goccia.* prefix for these shared infrastructure units in reviews.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Do not flag missing Goccia.* prefixes for shared/utility units that are used across multiple parts of the project or can be extracted as standalone infrastructure (e.g., BaseMap.pas, HashMap.pas, OrderedMap.pas, OrderedStringMap.pas, GC, test assertion units). A separate naming convention (to be defined in issue `#104`) should apply to these targets. When reviewing, apply this guideline to all similar shared utility units under the units directory; for one-off implementations outside this scope, continue with the existing naming expectations.

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: 337
File: source/units/Goccia.Builtins.TSV.pas:382-388
Timestamp: 2026-04-18T13:58:20.551Z
Learning: When reviewing GocciaScript Pascal code, do not flag missing `Args.Free`/`try/finally` around `TGocciaArgumentsCollection` instances. `TGocciaArgumentsCollection` (in `source/units/Goccia.Arguments.Collection.pas`) descends from `TInterfacedObject` and is reference-counted, so objects created via `CreateWithCapacity` or `Create` are automatically freed when the last reference goes out of scope. An explicit `Free` call (or wrapping in `try/finally`) is not appropriate for this type.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 351
File: source/units/Goccia.Engine.pas:1364-1368
Timestamp: 2026-04-19T15:42:49.575Z
Learning: In GocciaScript (e.g., source/units/Goccia.Engine.pas), treat `Goccia.gc()` / `TGocciaEngine.GocciaGC` as intentionally thread-safe: it should be a no-op on worker threads by guarding the manual `GC.Collect` call with `not GIsWorkerThread`. This prevents races on shared singleton state that uses a single, non-thread-safe `FGCMark` field during mark-sweep. Reviewers should not flag the `GIsWorkerThread` guard as a regression or as a missing manual-GC path; only the full/manual collection triggered by `Goccia.gc()` is skipped on workers, while other worker-thread protections (e.g., the memory-limit ceiling check in `TGocciaValue.AfterConstruction`) should still apply.

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.

Please create an issue for this

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo frostney/GocciaScript \
  --title "ZonedDateTimeToString: recompute UTC offset after rounding (DST boundary correctness)" \
  --body "## Summary

In \`source/units/Goccia.Values.TemporalZonedDateTime.pas\`, \`ZonedDateTimeToString\` captures \`OffsetSeconds\` via \`GetUtcOffsetSeconds\` **before** calling \`RoundTimeForToString\`. When rounding produces a non-zero \`ExtraDays\` carry, the rounded instant may cross a DST boundary, making the captured offset stale. The formatted string can then contain an impossible local-time/offset combination.

## Affected code

File: \`source/units/Goccia.Values.TemporalZonedDateTime.pas\`, around lines 1116–1155 (\`ZonedDateTimeToString\`).

## Expected fix

After \`RoundTimeForToString\` (and applying \`ExtraDays\` to the date), compute a new rounded epoch milliseconds value and recompute \`OffsetSeconds\` via \`GetUtcOffsetSeconds\` using the rounded epoch before building \`TimeStr\` and \`S\`.

## Context

Identified in PR `#354` (comment: https://github.com/frostney/GocciaScript/pull/354#discussion_r3107322466). The fix is intentionally deferred until full DST transition support is added, as the current timezone implementation uses a fixed offset per-call and the risk is limited to transition zones.

Requested by `@frostney`." \
  --assignee frostney

Length of output: 51


@frostney, the follow-up issue has been created: #358

It tracks the stale-offset recomputation in ZonedDateTimeToString and links back to this PR comment for context.


🧠 Learnings used
Learnt from: frostney
Repo: frostney/GocciaScript PR: 354
File: source/units/Goccia.Values.TemporalZonedDateTime.pas:1116-1155
Timestamp: 2026-04-19T18:53:34.108Z
Learning: In `source/units/Goccia.Values.TemporalZonedDateTime.pas` (frostney/GocciaScript, PR `#354`), `ZonedDateTimeToString` computes `OffsetSeconds` via `GetUtcOffsetSeconds` **before** calling `RoundTimeForToString`. After rounding, `ExtraDays` may push the instant across a DST boundary, making the captured offset stale. The fix (recompute `OffsetSeconds` from the rounded epoch after applying `ExtraDays`) is intentionally deferred to the follow-up PR that adds full DST transition support. Do not re-flag the pre-rounding offset capture in `ZonedDateTimeToString` until that DST PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 354
File: source/units/Goccia.Values.TemporalPlainDate.pas:668-680
Timestamp: 2026-04-19T18:53:31.289Z
Learning: For this project (frostney/GocciaScript), do not raise review findings at individual call sites that use `Arg.ToStringLiteral.Value` on user-supplied arguments for missing Symbol guards (e.g., ensuring `Arg is TGocciaSymbolValue`). This implicit Symbol→string coercion gap is intentionally deferred to a project-wide fix that should be implemented in `ToStringLiteral` itself (or a shared coercion helper). Until that `ToStringLiteral` fix lands, suppress/skip re-flagging the same issue in methods like `DateWithCalendar` and other `withCalendar`/`withTimeZone`-style methods in `source/units`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 346
File: source/units/Goccia.Values.TemporalZonedDateTime.pas:622-628
Timestamp: 2026-04-18T22:41:49.426Z
Learning: In `source/units/Goccia.Values.TemporalZonedDateTime.pas` (frostney/GocciaScript), `ZonedDateTimeUntil` and `ZonedDateTimeSince` were fixed in commit 70a89a69 (PR `#346`). They no longer collapse the delta into a total `DiffNs: Int64`. Instead they use the same two-field `{DiffMs: Int64, DiffSubMs: Integer}` decomposition as `Instant`: normalize sign between the two fields, then decompose `DiffMs` into hours/minutes/seconds/milliseconds and `DiffSubMs` into microseconds/nanoseconds directly. Do not flag `Int64` overflow risk in these methods — the total-nanosecond collapse has been removed.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 308
File: units/Goccia.Values.TemporalPlainMonthDay.pas:0-0
Timestamp: 2026-04-15T20:56:34.721Z
Learning: In frostney/GocciaScript Temporal value units (e.g., units/Goccia.Values.TemporalPlainMonthDay.pas), use `SSuggestTemporalThisType` when throwing a TypeError because `AThisValue` (or the first arg) is not the expected Temporal type (wrong-receiver / wrong-type-for-helper checks). Use `SSuggestTemporalToPlainDateYear` when throwing because a `toPlainDate` call is missing or receives a non-object/undefined `year` property. Do NOT use the broader `SSuggestTemporalFromArg` for these two specific cases.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:630-656
Timestamp: 2026-04-17T14:39:13.057Z
Learning: In frostney/GocciaScript, `ParseRelativeTo` in `units/Goccia.Values.TemporalDuration.pas` intentionally supports only `TGocciaTemporalPlainDateValue` and ISO date/date-time string inputs for `relativeTo` in `Duration.prototype.total()`. Support for `TGocciaTemporalZonedDateTimeValue` and property-bag/object inputs is deferred to a dedicated follow-up PR, as it requires importing the ZonedDateTime unit, extracting the plain date, and handling DST-aware day lengths. Do not re-flag missing ZonedDateTime/property-bag handling in `ParseRelativeTo` until that follow-up PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 308
File: units/Goccia.Values.TemporalPlainDateTime.pas:144-145
Timestamp: 2026-04-16T06:33:07.065Z
Learning: In frostney/GocciaScript Temporal unit implementations (e.g., units/Goccia.Values.TemporalPlainDateTime.pas, units/Goccia.Values.TemporalPlainDate.pas, units/Goccia.Values.TemporalPlainTime.pas, units/Goccia.Values.TemporalPlainYearMonth.pas, units/Goccia.Values.TemporalPlainMonthDay.pas, units/Goccia.Values.TemporalInstant.pas, units/Goccia.Values.TemporalZonedDateTime.pas, units/Goccia.Builtins.Temporal.pas), the correction of ThrowTypeError → ThrowRangeError for invalid ISO/time/duration string parsing failures (per TC39 Temporal spec) is intentionally deferred to a dedicated follow-up PR. PRs that only centralize error message constants and add suggestion metadata must not change error types (TypeError vs RangeError). Do not re-flag the ThrowTypeError usage for ISO/time/duration parse failures in these Temporal units until the dedicated behavioral-change PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:891-920
Timestamp: 2026-04-17T14:39:20.659Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), the engine supports only the ISO 8601 calendar. Weeks are always exactly 7 days in this calendar, so Duration.prototype.total does NOT require `relativeTo` for the 'weeks' unit or for durations with non-zero FWeeks components. The fixed `weeks * 7 * NANOSECONDS_PER_DAY` calculation is intentional and correct. Do not flag missing relativeTo guards for week units/components in this file. If/when non-ISO calendar support is added, this guard should be revisited.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 346
File: source/units/Goccia.Values.TemporalInstant.pas:178-184
Timestamp: 2026-04-18T22:29:31.789Z
Learning: In `source/units/Goccia.Values.TemporalInstant.pas` (frostney/GocciaScript), the `GetEpochNanoseconds` getter is safe from overflow. `FEpochMilliseconds` is `Int64` (max ≈ ±9.2×10¹⁸ ms) and the valid Temporal instant range is ±8.64×10¹⁵ ms, so `TBigInteger.FromInt64(Inst.FEpochMilliseconds)` is always lossless. Do NOT flag `TBigInteger.FromInt64` on `FEpochMilliseconds` in this getter as a potential overflow.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 327
File: units/Goccia.Values.TemporalDuration.pas:541-561
Timestamp: 2026-04-17T16:31:38.698Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), `DurationRound` intentionally parses and validates the `relativeTo` option eagerly (before `NeedCalendar` is computed) because the TC39 Temporal spec calls `ToRelativeTemporalObject` as an early step of `Duration.prototype.round` — before any check for whether calendar math is needed. An invalid `relativeTo` must throw even for a pure time/day round. Do NOT flag the eager parsing in `DurationRound` as incorrect or suggest deferring it until `NeedCalendar` is known. If `DurationTotal` uses lazy parsing, that is the less spec-compliant path, not `DurationRound`.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 277
File: units/Goccia.Builtins.Temporal.pas:0-0
Timestamp: 2026-04-12T13:19:32.780Z
Learning: For all Temporal-related units in GocciaScript (e.g., files like Goccia.Builtins.Temporal.pas and Goccia.Values.TemporalPlainTime.pas), use the shared Temporal property-name constants defined in units/Goccia.Constants.PropertyNames.pas (PROP_YEAR, PROP_MONTH, PROP_MONTH_CODE, PROP_DAY, PROP_CALENDAR_ID, plus a full set of time unit constants: PROP_HOUR, PROP_MINUTE, PROP_SECOND, PROP_MILLISECOND, PROP_MICROSECOND, PROP_NANOSECOND) when accessing Temporal properties and when constructing any related error messages. Do not use hardcoded string literals for these Temporal property names in Temporal units.

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: 343
File: source/units/Goccia.Lexer.pas:1429-1437
Timestamp: 2026-04-18T20:08:21.207Z
Learning: In `source/units/Goccia.Lexer.pas` (frostney/GocciaScript PR `#343`), the `gttBigInt` lexeme emitted by the lexer preserves the radix prefix (e.g., `0xFF`, `0b1010`, `0o77`) after stripping separators and the `n` suffix. The parser's private helper `ConvertBigIntLiteral` (source/units/Goccia.Parser.pas, line ~4339) is radix-aware: it detects `0x`/`0X`, `0b`/`0B`, `0o`/`0O` prefixes and dispatches to `TBigInteger.FromHexString`, `FromBinaryString`, or `FromOctalString` respectively, falling back to `FromDecimalString` for plain decimal literals. The lexer/parser contract is correct and intentional. Do NOT flag the preserved prefix in `gttBigInt` lexemes as inconsistent with the parser's BigInt construction.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 343
File: source/units/Goccia.JSON.pas:841-844
Timestamp: 2026-04-18T20:08:00.723Z
Learning: In frostney/GocciaScript (source/units/Goccia.JSON.pas), `ApplyToJSON` intentionally only probes `toJSON` when the value is a `TGocciaObjectValue`. For BigInt primitives (`TGocciaBigIntValue`), `JSON.stringify(1n)` correctly throws TypeError per ES2026 §25.5.2.5 step 10 — this is the expected default behavior. Extending `ApplyToJSON` to call `GetProperty('toJSON')` directly on BigInt primitives (per ES2026 §25.5.2.2) is a known gap deferred to a follow-up PR. Do not re-flag the missing `toJSON` probe for BigInt in `ApplyToJSON` until that follow-up is scoped.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 306
File: TestRunner.dpr:634-636
Timestamp: 2026-04-15T06:27:56.026Z
Learning: In frostney/GocciaScript (PR `#306`, TestRunner.dpr), the `WriteLn` calls inside the error-handling paths of `RunGocciaScriptInterpreted` and `RunGocciaScriptBytecode` (e.g. on EStreamError, TGocciaError, TGocciaThrowValue, and generic Exception) execute on worker threads during parallel directory runs, causing potentially interleaved stdout. This is an accepted trade-off: these paths are rare (script-load failures / uncaught exceptions only), messages include filenames for identification, and a proper output-capture refactor (routing error text through AConsoleOutput/AErrorMessage and printing on the main thread) is deferred to a follow-up. Do NOT re-flag these WriteLn calls in TestRunner.dpr parallel worker context until the output-capture refactor lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 336
File: source/units/Goccia.Engine.StrictTypes.Test.pas:45-47
Timestamp: 2026-04-18T11:56:12.474Z
Learning: In frostney/GocciaScript Pascal test files (e.g. `Goccia.Lexer.Test.pas`, `Goccia.Error.Test.pas`, `Goccia.OrderedStringMap.Test.pas`, `Goccia.TextFiles.Test.pas`, `Goccia.Engine.StrictTypes.Test.pas`), the construct-before-try pattern (allocating objects before the `try` block without pre-initializing refs to `nil`) is intentional and is the established project-wide convention for test code. The constructors used are simple and the leak risk is considered negligible in the test context. Do NOT flag this pattern as an exception-safety issue or suggest the nil-init / try-before-construct alternative in these test files.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 209
File: units/Goccia.Values.FFILibrary.pas:73-82
Timestamp: 2026-04-08T13:55:31.119Z
Learning: In frostney/GocciaScript (units/Goccia.Values.FFILibrary.pas, PR `#209`), `TGocciaFFIBoundFunction` intentionally holds only a raw `CodePointer` with no back-reference to the owning `TGocciaFFILibraryHandle`. Calling `close()` while bound functions or raw symbol pointers still exist is documented UB — the same contract as Deno/Bun FFI. This is an explicit v1 design decision, gated behind the `ggFFI` opt-in flag so only embedders who accept the contract enable it. A ref-counted handle guard is planned as a v2 enhancement. Do NOT re-flag the missing library back-reference or "bind() leaks" in future reviews of this unit.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 299
File: TestRunner.dpr:132-139
Timestamp: 2026-04-14T06:46:13.250Z
Learning: In frostney/GocciaScript, `EscapeJSONString` (escaping `\`, `"`, `\n`, `\r`, `\t` only) is intentionally duplicated across ~5 CLI tool files (e.g., TestRunner.dpr, BenchmarkRunner.dpr). The incomplete control-character coverage (missing U+0000–U+001F beyond the five named characters) is a known pre-existing issue tracked for fix as part of a future consolidation into a shared helper. Do NOT re-flag missing control-character escaping in individual per-tool copies of `EscapeJSONString` until that consolidation PR lands.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 306
File: ScriptLoader.dpr:692-707
Timestamp: 2026-04-15T06:26:13.084Z
Learning: In frostney/GocciaScript (ScriptLoader.dpr), `TScriptLoaderApp.ScriptWorkerProc` intentionally calls `RunScriptFromFile` directly in worker threads, which causes interleaved/nondeterministic stdout in parallel directory runs. This is an accepted trade-off for the initial parallel implementation — ScriptLoader parallel mode is only used for directory inputs where output ordering is less critical. A proper fix (refactoring `RunSource`/`ExecuteInterpreted` to support output capture and returning text via `AConsoleOutput`/`AErrorMessage`) has been deferred to a follow-up. Do not re-flag the direct `RunScriptFromFile` call in `ScriptWorkerProc` as a regression or issue until the output-capture refactor lands.

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: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Shared/utility units under the repository (e.g., units/BaseMap.pas, HashMap.pas, OrderedMap.pas, etc.) that are used across multiple parts of the project should not be required to use the Goccia.* prefix. A separate naming convention will be defined in a follow-up issue. Do not flag missing Goccia.* prefix for these shared infrastructure units in reviews.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 66
File: units/BaseMap.pas:22-22
Timestamp: 2026-03-18T08:12:24.165Z
Learning: Do not flag missing Goccia.* prefixes for shared/utility units that are used across multiple parts of the project or can be extracted as standalone infrastructure (e.g., BaseMap.pas, HashMap.pas, OrderedMap.pas, OrderedStringMap.pas, GC, test assertion units). A separate naming convention (to be defined in issue `#104`) should apply to these targets. When reviewing, apply this guideline to all similar shared utility units under the units directory; for one-off implementations outside this scope, continue with the existing naming expectations.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 107
File: units/Goccia.Runtime.Operations.pas:211-212
Timestamp: 2026-03-23T12:37:30.936Z
Learning: Repo: frostney/GocciaScript — units/Goccia.Runtime.Operations.pas (PR `#107`)
Learning: In TGocciaRuntimeOperations.MarkExternalRoots, entries in FModuleCache (cached module namespace/JSON records) must be traversed and their TSouffleValue references marked as external roots to survive GC. This behavior was added in commit 36f69cc; do not re‑flag missing FModuleCache rooting unless the logic regresses.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 302
File: docs/contributing/code-style.md:43-46
Timestamp: 2026-04-14T13:31:59.106Z
Learning: In frostney/GocciaScript (docs/contributing/code-style.md and related docs), units like `BaseMap.pas`, `HashMap.pas`, `OrderedMap.pas`, `OrderedStringMap.pas` are described as "shared infrastructure units that are not GocciaScript-specific" and intentionally live outside the `Goccia.*` namespace. They are NOT "legacy units." Only project-specific units follow the `Goccia.<Category>.<Name>.pas` convention. The naming convention for these shared infrastructure units is tracked in issue `#104`. Do not describe these units as "legacy" in review comments.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 306
File: units/Goccia.Builtins.GlobalPromise.pas:56-57
Timestamp: 2026-04-15T06:26:35.760Z
Learning: In frostney/GocciaScript (PR `#306`), ~30 builtin units (e.g., Goccia.Builtins.GlobalPromise.pas, Goccia.Builtins.GlobalArray.pas, Goccia.Builtins.Math.pas, and ~27 others) use `threadvar FStaticMembers: TArray<TGocciaMemberDefinition>` in their `implementation` sections. FPC does NOT finalize managed threadvar contents on thread exit, so the TArray shell (~few hundred bytes per unit) leaks per worker thread. This is an accepted/deferred trade-off: (1) threads are short-lived spawn-and-join workers, (2) the GC-managed TGocciaMemberDefinition objects are cleaned up by ShutdownThreadRuntime, (3) OS reclaims all memory on process exit, (4) a proper fix requires a registration-based cleanup system or per-unit finalization hooks since the threadvars are in implementation sections. Do NOT re-flag this managed-threadvar leak until long-lived thread pools are introduced and a cleanup registry is in place.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 147
File: units/Goccia.VM.pas:3042-3084
Timestamp: 2026-03-31T18:38:23.367Z
Learning: Repo: frostney/GocciaScript — units/Goccia.VM.pas (PR `#147`)
As of commit 457e712: (1) TGocciaVM.GetPropertyValue performs own-only private static accessor lookup on '#…' for TGocciaClassValue and throws TypeError on class-brand misses; (2) TGocciaVM.SetPropertyValue avoids inherited private accessor resolution, but still allows raw private static writes via SetRawPrivateValue to support current class initialization that emits OP_SET_PROP_CONST for private static fields. Planned follow-up is to add dedicated opcodes to define private static fields at class-declaration time and then make SetPropertyValue strict (throw on brand miss with no fallback). Do not re-flag the current fallback until those opcodes exist.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 136
File: docs/adding-built-in-types.md:105-133
Timestamp: 2026-03-27T14:09:32.826Z
Learning: Applies to units/**/*.pas and docs/**/*.md (frostney/GocciaScript): The documented and enforced convention for shared prototype singleton initialization IS the early-exit guard `if Assigned(FShared) then Exit` at the top of `InitializePrototype`. Do NOT flag this pattern or suggest replacing it with `if not Assigned(FShared) then` wrapping. The project docs (adding-built-in-types.md, code-style.md) explicitly use the early-exit form as the canonical template.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 60
File: units/Goccia.Runtime.Operations.pas:8694-8696
Timestamp: 2026-03-10T18:05:46.926Z
Learning: Repo: frostney/GocciaScript PR: 60 — units/Goccia.Runtime.Operations.pas: The GC rooting of FArrayBridgeReverse (marking values in MarkExternalRoots and keys in MarkWrappedGocciaValues) is intentional to preserve array identity across async/microtask boundaries (e.g., Promise.resolve(arr).then(v => v === arr)). Do not prune this map during normal clearing; per-file GC.Collect in runners bounds cross-file memory growth.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 277
File: units/Goccia.Values.TemporalPlainMonthDay.pas:0-0
Timestamp: 2026-04-12T10:23:58.234Z
Learning: In frostney/GocciaScript, Temporal-specific property name constants `PROP_YEAR`, `PROP_MONTH`, `PROP_MONTH_CODE`, `PROP_DAY`, and `PROP_CALENDAR_ID` were added to `Goccia.Constants.PropertyNames` (as of commit d59dd03, PR `#277`). All Temporal value units (e.g. `Goccia.Values.TemporalPlainMonthDay.pas`, `Goccia.Values.TemporalPlainYearMonth.pas`, etc.) must use these constants instead of hardcoded string literals for property accesses (`GetProperty`, `SetProperty`, accessor registration, `ThrowTypeError` messages referencing property names, etc.).

Learnt from: frostney
Repo: frostney/GocciaScript PR: 324
File: units/Goccia.Values.TemporalDuration.pas:691-693
Timestamp: 2026-04-17T14:59:54.438Z
Learning: In frostney/GocciaScript (units/Goccia.Values.TemporalDuration.pas), TC39 Temporal Duration magnitude validation (calendar units < 2^32, normalized seconds < 2^53) is intentionally deferred to a follow-up PR. The validation belongs in the `TGocciaTemporalDurationValue` constructor and `DurationFromObject`, not in `DurationEndDate`, `AddMonthsToDate`, or `DurationTotal`. Do not re-flag missing magnitude validation in `DurationEndDate` or `total()` path — the arithmetic there is safe for spec-compliant inputs; the gap is upstream validation not yet implemented.

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: 343
File: source/units/Goccia.Evaluator.Arithmetic.pas:49-55
Timestamp: 2026-04-18T21:08:03.166Z
Learning: In frostney/GocciaScript (commit 3344dede, PR `#343`), `TGocciaBigIntObjectValue` was added to support BigInt boxing via `Object(1n)`. `Box` in `ClassHelper.pas` now returns a `TGocciaBigIntObjectValue` for `TGocciaBigIntValue` primitives. `ToPrimitive` calls were added to `EvaluateSubtraction`, `EvaluateMultiplication`, `EvaluateDivision`, `EvaluateModulo`, and `EvaluateExponentiation` in `source/units/Goccia.Evaluator.Arithmetic.pas` so that boxed BigInts (e.g. `Object(1n) - 1n`) are correctly unboxed via `valueOf()` before BigInt type checks. `Object()` was also fixed to call `Box` for primitive arguments per ES2026 §20.1.1.1. Future reviews of arithmetic operators SHOULD expect `ToPrimitive` before BigInt checks.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 343
File: source/units/Goccia.Builtins.GlobalBigInt.pas:213-223
Timestamp: 2026-04-18T20:48:02.918Z
Learning: In `source/units/Goccia.Builtins.GlobalBigInt.pas` (frostney/GocciaScript, landed in commit 477b5609 on PR `#343`), `BigInt.asIntN` and `BigInt.asUintN` use proper `ToIndex` abstract-op coercion for the `bits` parameter: `undefined`/`NaN` → 0, `Infinity`/negative/oversized → `RangeError`. This is fully implemented and tested. Do not flag missing `ToIndex` coercion for these methods.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 346
File: source/shared/BigInteger.Test.pas:0-0
Timestamp: 2026-04-18T23:32:30.735Z
Learning: In `source/shared/BigInteger.Test.pas` (frostney/GocciaScript PR `#346`), `TestFromDoubleMaxSafeInteger` and `TestFromDoubleNegMaxSafeInteger` intentionally exercise `2^53 = 9007199254740992.0` (and its negation), not `Number.MAX_SAFE_INTEGER = 9007199254740991`. The value `2^53` is the upper boundary of the `FromDouble` Int64 fast-path added in `source/shared/BigInteger.pas` (commit c3fee5b0). The test labels were renamed to "2^53 (Int64 fast-path boundary)" to reflect this intent. Do not re-flag these test values as off-by-one for MAX_SAFE_INTEGER.

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: 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: 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: 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: 337
File: source/units/Goccia.Builtins.TSV.pas:382-388
Timestamp: 2026-04-18T13:58:20.551Z
Learning: When reviewing GocciaScript Pascal code, do not flag missing `Args.Free`/`try/finally` around `TGocciaArgumentsCollection` instances. `TGocciaArgumentsCollection` (in `source/units/Goccia.Arguments.Collection.pas`) descends from `TInterfacedObject` and is reference-counted, so objects created via `CreateWithCapacity` or `Create` are automatically freed when the last reference goes out of scope. An explicit `Free` call (or wrapping in `try/finally`) is not appropriate for this type.

Learnt from: frostney
Repo: frostney/GocciaScript PR: 351
File: source/units/Goccia.Engine.pas:1364-1368
Timestamp: 2026-04-19T15:42:49.575Z
Learning: In GocciaScript (e.g., source/units/Goccia.Engine.pas), treat `Goccia.gc()` / `TGocciaEngine.GocciaGC` as intentionally thread-safe: it should be a no-op on worker threads by guarding the manual `GC.Collect` call with `not GIsWorkerThread`. This prevents races on shared singleton state that uses a single, non-thread-safe `FGCMark` field during mark-sweep. Reviewers should not flag the `GIsWorkerThread` guard as a regression or as a missing manual-GC path; only the full/manual collection triggered by `Goccia.gc()` is skipped on workers, while other worker-thread protections (e.g., the memory-limit ceiling check in `TGocciaValue.AfterConstruction`) should still apply.

Comment thread source/units/Goccia.Values.TemporalZonedDateTime.pas
- toLocaleString across all 8 Temporal types now creates empty args
  instead of forwarding AArgs to toString, preventing locale strings
  from being misinterpreted as toString options
- Instant.prototype.toZonedDateTimeISO now accepts ZonedDateTime
  objects in addition to strings, extracting the timezone

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

♻️ Duplicate comments (1)
source/units/Goccia.Values.TemporalInstant.pas (1)

551-554: ⚠️ Potential issue | 🟡 Minor

Update the error text to match the broadened input contract.

This method now accepts TGocciaTemporalZonedDateTimeValue, but the thrown message still says “string time zone”. That will point callers in the wrong direction when they pass an unsupported non-string value.

🛠️ Suggested wording fix
-    ThrowTypeError('Instant.prototype.toZonedDateTimeISO requires a string time zone', SSuggestTemporalTimezone);
+    ThrowTypeError('Instant.prototype.toZonedDateTimeISO requires a time zone or ZonedDateTime', SSuggestTemporalTimezone);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalInstant.pas` around lines 551 - 554, The
error message thrown in Instant.prototype.toZonedDateTimeISO is stale (it says
"requires a string time zone") even though the method now accepts
TGocciaTemporalZonedDateTimeValue; update the ThrowTypeError call (the line
invoking ThrowTypeError with SSuggestTemporalTimezone) to use a clearer message
that reflects the broadened input contract (e.g., require "a time zone (string
or TGocciaTemporalZonedDateTimeValue)" or similar), keeping the same
ThrowTypeError invocation and SSuggestTemporalTimezone usage and leaving TZ
handling unchanged.
🧹 Nitpick comments (3)
source/units/Goccia.Values.TemporalPlainMonthDay.pas (2)

173-177: Avoid duplicating the Temporal type-name literal here.

This adds a second hardcoded 'Temporal.PlainMonthDay' in the unit, which can drift from ToStringTag. Prefer a shared type-name constant and reuse it from both registration sites.

As per coding guidelines, "Use split constant units for runtime constants instead of hardcoded string literals: Goccia.Constants.PropertyNames for property names, Goccia.Constants.TypeNames for type names, Goccia.Constants.ErrorNames for error names, Goccia.Constants.ConstructorNames for constructor names, and Goccia.Constants.SymbolNames for symbol names."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainMonthDay.pas` around lines 173 - 177,
The literal 'Temporal.PlainMonthDay' is duplicated here; replace the hardcoded
string passed to TGocciaStringLiteralValue.Create with the shared type-name
constant from the split constants unit (e.g. use the appropriate identifier in
Goccia.Constants.TypeNames) so both the ToStringTag registration and any other
registrations reuse the same constant; update the code around
Members.AddSymbolDataProperty and keep MonthDayToLocaleString unchanged,
referencing the TypeNames constant instead of the literal.

356-365: Drop the manual Free on TGocciaArgumentsCollection.

TGocciaArgumentsCollection is ref-counted in this repo, so the explicit Free/try...finally here is the wrong lifetime pattern. Let the local reference go out of scope instead.

♻️ Proposed cleanup
 function TGocciaTemporalPlainMonthDayValue.MonthDayToLocaleString(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
 var
   EmptyArgs: TGocciaArgumentsCollection;
 begin
   EmptyArgs := TGocciaArgumentsCollection.Create([]);
-  try
-    Result := MonthDayToString(EmptyArgs, AThisValue);
-  finally
-    EmptyArgs.Free;
-  end;
+  Result := MonthDayToString(EmptyArgs, AThisValue);
 end;

Based on learnings, "TGocciaArgumentsCollection (in source/units/Goccia.Arguments.Collection.pas) descends from TInterfacedObject and is reference-counted, so objects created via CreateWithCapacity or Create are automatically freed when the last reference goes out of scope. An explicit Free call (or wrapping in try/finally) is not appropriate for this type."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalPlainMonthDay.pas` around lines 356 - 365,
The method TGocciaTemporalPlainMonthDayValue.MonthDayToLocaleString currently
creates a TGocciaArgumentsCollection and manually calls Free in a try/finally;
since TGocciaArgumentsCollection is ref-counted (descends from
TInterfacedObject) remove the try..finally and the explicit EmptyArgs.Free,
simply create the EmptyArgs local (via TGocciaArgumentsCollection.Create([]))
and pass it to MonthDayToString so the reference-counting releases it when the
variable goes out of scope.
source/units/Goccia.Values.TemporalDuration.pas (1)

1233-1242: Drop the manual Free / try...finally around TGocciaArgumentsCollection here.

TGocciaArgumentsCollection is reference-counted in this codebase, so this adapter does not need explicit lifetime management. The same pattern was copied into the other new Temporal toLocaleString adapters in this PR, so it’s worth normalizing all of them the same way.

♻️ Suggested simplification
 function TGocciaTemporalDurationValue.DurationToLocaleString(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
 var
   EmptyArgs: TGocciaArgumentsCollection;
 begin
   EmptyArgs := TGocciaArgumentsCollection.Create([]);
-  try
-    Result := DurationToString(EmptyArgs, AThisValue);
-  finally
-    EmptyArgs.Free;
-  end;
+  Result := DurationToString(EmptyArgs, AThisValue);
 end;

Based on learnings, TGocciaArgumentsCollection descends from TInterfacedObject, so explicit Free / try...finally is not appropriate for this type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalDuration.pas` around lines 1233 - 1242, In
TGocciaTemporalDurationValue.DurationToLocaleString replace the try/finally
manual lifetime handling for the TGocciaArgumentsCollection with simple
reference-counted usage: create EmptyArgs via
TGocciaArgumentsCollection.Create([]) and pass it to DurationToString(AArgs,
AThisValue) (or DurationToString(EmptyArgs, AThisValue)) without calling
EmptyArgs.Free or wrapping in try..finally, since TGocciaArgumentsCollection is
reference-counted (descends from TInterfacedObject); do the same normalization
for the other Temporal toLocaleString adapters following this pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@source/units/Goccia.Values.TemporalInstant.pas`:
- Around line 551-554: The error message thrown in
Instant.prototype.toZonedDateTimeISO is stale (it says "requires a string time
zone") even though the method now accepts TGocciaTemporalZonedDateTimeValue;
update the ThrowTypeError call (the line invoking ThrowTypeError with
SSuggestTemporalTimezone) to use a clearer message that reflects the broadened
input contract (e.g., require "a time zone (string or
TGocciaTemporalZonedDateTimeValue)" or similar), keeping the same ThrowTypeError
invocation and SSuggestTemporalTimezone usage and leaving TZ handling unchanged.

---

Nitpick comments:
In `@source/units/Goccia.Values.TemporalDuration.pas`:
- Around line 1233-1242: In TGocciaTemporalDurationValue.DurationToLocaleString
replace the try/finally manual lifetime handling for the
TGocciaArgumentsCollection with simple reference-counted usage: create EmptyArgs
via TGocciaArgumentsCollection.Create([]) and pass it to DurationToString(AArgs,
AThisValue) (or DurationToString(EmptyArgs, AThisValue)) without calling
EmptyArgs.Free or wrapping in try..finally, since TGocciaArgumentsCollection is
reference-counted (descends from TInterfacedObject); do the same normalization
for the other Temporal toLocaleString adapters following this pattern.

In `@source/units/Goccia.Values.TemporalPlainMonthDay.pas`:
- Around line 173-177: The literal 'Temporal.PlainMonthDay' is duplicated here;
replace the hardcoded string passed to TGocciaStringLiteralValue.Create with the
shared type-name constant from the split constants unit (e.g. use the
appropriate identifier in Goccia.Constants.TypeNames) so both the ToStringTag
registration and any other registrations reuse the same constant; update the
code around Members.AddSymbolDataProperty and keep MonthDayToLocaleString
unchanged, referencing the TypeNames constant instead of the literal.
- Around line 356-365: The method
TGocciaTemporalPlainMonthDayValue.MonthDayToLocaleString currently creates a
TGocciaArgumentsCollection and manually calls Free in a try/finally; since
TGocciaArgumentsCollection is ref-counted (descends from TInterfacedObject)
remove the try..finally and the explicit EmptyArgs.Free, simply create the
EmptyArgs local (via TGocciaArgumentsCollection.Create([])) and pass it to
MonthDayToString so the reference-counting releases it when the variable goes
out of scope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6102a5f1-29c4-4500-b01a-826b3162141d

📥 Commits

Reviewing files that changed from the base of the PR and between 50e9628 and a20c055.

📒 Files selected for processing (8)
  • source/units/Goccia.Values.TemporalDuration.pas
  • source/units/Goccia.Values.TemporalInstant.pas
  • source/units/Goccia.Values.TemporalPlainDate.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Values.TemporalPlainTime.pas
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
  • source/units/Goccia.Values.TemporalZonedDateTime.pas
🚧 Files skipped from review as they are similar to previous changes (3)
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
  • source/units/Goccia.Values.TemporalPlainDate.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas

The TypeError message now correctly reflects that the method accepts
both string time zones and ZonedDateTime objects.

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

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

🧹 Nitpick comments (1)
source/units/Goccia.Values.TemporalInstant.pas (1)

525-535: Remove explicit Free call on TGocciaArgumentsCollection.

TGocciaArgumentsCollection descends from TInterfacedObject and is reference-counted. The explicit Free call is not appropriate per project conventions — the object will be automatically freed when the reference goes out of scope.

♻️ Suggested fix
 function TGocciaTemporalInstantValue.InstantToLocaleString(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue;
 var
   EmptyArgs: TGocciaArgumentsCollection;
 begin
   EmptyArgs := TGocciaArgumentsCollection.Create([]);
-  try
-    Result := InstantToString(EmptyArgs, AThisValue);
-  finally
-    EmptyArgs.Free;
-  end;
+  Result := InstantToString(EmptyArgs, AThisValue);
 end;

Based on learnings: "TGocciaArgumentsCollection... descends from TInterfacedObject and is reference-counted, so objects created via CreateWithCapacity or Create are automatically freed when the last reference goes out of scope. An explicit Free call (or wrapping in try/finally) is not appropriate for this type."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.TemporalInstant.pas` around lines 525 - 535, In
TGocciaTemporalInstantValue.InstantToLocaleString replace the manual creation +
try/finally + EmptyArgs.Free pattern with a simple local interfaced variable for
TGocciaArgumentsCollection so the reference-counting frees it automatically:
remove the try/finally and the explicit EmptyArgs.Free, just assign EmptyArgs :=
TGocciaArgumentsCollection.Create([]) and call InstantToString(EmptyArgs,
AThisValue) returning the result; this uses the interface semantics of
TGocciaArgumentsCollection and avoids manual freeing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@source/units/Goccia.Values.TemporalInstant.pas`:
- Around line 525-535: In TGocciaTemporalInstantValue.InstantToLocaleString
replace the manual creation + try/finally + EmptyArgs.Free pattern with a simple
local interfaced variable for TGocciaArgumentsCollection so the
reference-counting frees it automatically: remove the try/finally and the
explicit EmptyArgs.Free, just assign EmptyArgs :=
TGocciaArgumentsCollection.Create([]) and call InstantToString(EmptyArgs,
AThisValue) returning the result; this uses the interface semantics of
TGocciaArgumentsCollection and avoids manual freeing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7df226a-7a05-400b-8d90-01fd798f7ce0

📥 Commits

Reviewing files that changed from the base of the PR and between a20c055 and 1ffb039.

📒 Files selected for processing (1)
  • source/units/Goccia.Values.TemporalInstant.pas

@frostney frostney merged commit 8c534f5 into main Apr 20, 2026
10 checks passed
@frostney frostney deleted the t3code/test262-bigint-temporal branch April 20, 2026 08:45
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 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.

1 participant