Skip to content

Expand Test262 support for classes, functions, and typed arrays#378

Merged
frostney merged 14 commits intomainfrom
t3code/test262-feature-gaps
Apr 23, 2026
Merged

Expand Test262 support for classes, functions, and typed arrays#378
frostney merged 14 commits intomainfrom
t3code/test262-feature-gaps

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Apr 21, 2026

Summary

  • Added missing Test262 harness support for --compat-var, BigInt typed array helpers, and additional syntax filtering for unsupported feature paths.
  • Reworked class and function prototype wiring so constructors, Function.prototype, and class method/static property descriptors match expected enumerability and inheritance behavior more closely.
  • Added %TypedArray% intrinsic wiring and connected typed array constructors to the shared typed array prototype.
  • Tightened built-in object, error, array, and RegExp prototype/property definitions to use proper property descriptors.
  • Added focused regression tests for classes, function prototype methods, Object.create, Object.getPrototypeOf, array helpers, maps/sets, typed arrays, and error constructors.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 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: 3475c283-27f2-412e-8299-d47835519326

📥 Commits

Reviewing files that changed from the base of the PR and between bd65d7d and 7856e66.

📒 Files selected for processing (1)
  • source/units/Goccia.Builtins.GlobalObject.pas

📝 Walkthrough

Walkthrough

Multiple runtime and VM changes redefine how prototypes and constructor properties are installed (using explicit DefineProperty descriptors), introduce a non-global %TypedArray% intrinsic and wired shared typed-array prototype, extend call/apply/bind dispatch to support class constructors and callable proxies, and update the Test262 runner/filter with a --compat-var flag and BigInt typed-array mapping.

Changes

Cohort / File(s) Summary
Test262 runner & filter
scripts/run_test262_suite.py, scripts/test262_syntax_filter.py
Add --compat-var CLI flag (default true) and thread compat_var through mainevaluate_suiterun_negative_parse_test; map testBigIntTypedArray.js to testTypedArray.js; treat BigInt64Array/BigUint64Array as supported; expand skip-path segments.
Object creation & define-properties
source/units/Goccia.Builtins.GlobalObject.pas
Refactor ObjectCreate to build/return a local object then apply optional Properties; extend ObjectDefineProperties to enumerate and define symbol-keyed descriptors.
Prototype/constructor descriptor refactors
source/units/Goccia.Builtins.GlobalRegExp.pas, source/units/Goccia.Builtins.Globals.pas, source/units/Goccia.SharedPrototype.pas, source/units/Goccia.Values.ArrayValue.pas, source/units/Goccia.Values.TypedArrayValue.pas
Replace AssignProperty value assignments with DefineProperty using TGocciaPropertyDescriptorData for constructor/name/message/prototype installs; add typed-array shared-prototype accessor.
Engine / runtime bootstrap & %TypedArray% intrinsic
source/units/Goccia.Engine.pas, source/units/Goccia.Runtime.Bootstrap.pas
Introduce retained non-global FTypedArrayIntrinsic (%TypedArray%) with name/length, link its .prototype to the shared typed-array prototype, capture TextEncoder/TextDecoder constructors, and adjust Function.prototype wiring to use shared prototype + explicit descriptor installs.
Class value, object-model & VM opcode changes
source/units/Goccia.Values.ClassValue.pas, source/units/Goccia.ObjectModel.Engine.pas, source/units/Goccia.VM.pas
Refactor TGocciaClassValue (separate class prototype field and new helpers), install constructor/methods via DefineProperty with explicit flags, copy static members preserving descriptors, and update VM opcodes to use DefineProperty for class-related installs.
Function dispatch / bound-call behavior
source/units/Goccia.Values.FunctionBase.pas
Add shared DispatchCall to route invocations to TGocciaFunctionBase, TGocciaClassValue, or callable TGocciaProxyValue; refactor Function.prototype.call/apply/bind and loosen bound-function fast-paths to IsCallable.
Tests — descriptors, classes, call/apply/bind, typed arrays
tests/... (e.g., tests/built-ins/*, tests/language/classes/*, tests/built-ins/Proxy/apply.js)
Add many tests asserting property descriptor attributes across built-ins, class metadata and method enumerability, %TypedArray% intrinsic behavior, and Function.prototype call/apply/bind coverage (including class constructors and callable proxies).

Sequence Diagram(s)

sequenceDiagram
    participant Engine as Engine/Bootstrap
    participant Intrinsic as %TypedArray%<br/>Intrinsic
    participant Prototype as Shared<br/>TypedArray Prototype
    participant Registry as Constructor<br/>Registry
    participant Constructors as TypedArray<br/>Constructors

    Engine->>Intrinsic: create %TypedArray% (name/length)
    Engine->>Prototype: create shared typed-array prototype
    Intrinsic->>Prototype: link intrinsic.prototype -> shared prototype
    Engine->>Registry: register typed-array constructors using FTypedArrayIntrinsic
    Registry->>Constructors: create each constructor with %TypedArray% intrinsic
    Constructors->>Prototype: set constructor.prototype -> shared prototype
    Engine->>Constructors: patch default prototypes for related constructors
Loading
sequenceDiagram
    participant Caller as Caller Code
    participant FnProto as Function.prototype.call/apply
    participant Dispatcher as DispatchCall
    participant FuncBase as TGocciaFunctionBase
    participant ClassVal as TGocciaClassValue
    participant ProxyVal as TGocciaProxyValue<br/>(callable)

    Caller->>FnProto: invoke call/apply on callee
    FnProto->>Dispatcher: DispatchCall(callee, thisArg, args)
    alt callee is TGocciaFunctionBase
        Dispatcher->>FuncBase: Call(...)
        FuncBase-->>Dispatcher: result
    else callee is TGocciaClassValue
        Dispatcher->>ClassVal: Call(...)
        ClassVal-->>Dispatcher: result
    else callee is callable TGocciaProxyValue
        Dispatcher->>ProxyVal: ApplyTrap(...)
        ProxyVal-->>Dispatcher: result
    end
    Dispatcher-->>FnProto: result
    FnProto-->>Caller: result
Loading
sequenceDiagram
    participant VM as VM Executor
    participant Opcode as OP_NEW_CLASS / OP_CLASS_ADD_METHOD_CONST
    participant Proto as Class Prototype
    participant Desc as TGocciaPropertyDescriptorData

    VM->>Opcode: execute class creation opcode
    Opcode->>Proto: create prototype object
    VM->>Desc: construct descriptor with pfConfigurable, pfWritable
    VM->>Proto: DefineProperty("constructor", Desc)
    VM->>Proto: DefineProperty(method_name, Desc_non_enumerable)
    Proto-->>VM: prototype and methods installed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • PR #260 — TypedArray intrinsic/prototype wiring changes; overlaps %TypedArray% intrinsic and bootstrap/engine prototype plumbing.
  • PR #244 — Modifications to Function.prototype.apply/call paths; relates to DispatchCall and call/apply/bind refactor.
  • PR #341 — Test262 harness and syntax-filter edits; overlaps run_test262_suite.py and test262_syntax_filter.py changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Expand Test262 support for classes, functions, and typed arrays' accurately summarizes the main changes in the changeset, highlighting the primary focus on expanding Test262 support and fixing related implementation issues.
Description check ✅ Passed The pull request description provides comprehensive coverage of all major changes including Test262 support additions, prototype wiring rework, %TypedArray% intrinsic introduction, property descriptor tightening, and focused regression tests. While the testing checklist boxes are not marked, the description covers the substantive requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 internal Refactoring, CI, tooling, cleanup labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 7687 7687
Tests Passed 7646 ✅ 7687 ✅
Tests Skipped 41 0
Tests Workers 4 4
Tests Test Duration 1.60s 1.50s
Tests Lex (cumulative) 195.3ms 116.3ms
Tests Parse (cumulative) 273.1ms 283.4ms
Tests Compile (cumulative) 177.9ms
Tests Execute (cumulative) 1.78s 1.55s
Tests Engine Total (cumulative) 2.25s 2.13s
Tests Lex (avg/worker) 48.8ms 29.1ms
Tests Parse (avg/worker) 68.3ms 70.9ms
Tests Compile (avg/worker) 44.5ms
Tests Execute (avg/worker) 445.7ms 387.2ms
Tests Engine Total (avg/worker) 562.8ms 531.6ms
Benchmarks Total 386 386
Benchmarks Workers 4 4
Benchmarks Duration 3.02min 3.06min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Benchmark Results

386 benchmarks

Interpreted: 🟢 13 improved · 🔴 283 regressed · 90 unchanged · avg -5.8%
Bytecode: 🟢 378 improved · 🔴 5 regressed · 3 unchanged · avg +25.1%

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

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

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

468-512: ⚠️ Potential issue | 🟡 Minor

Accessor descriptors include pfEnumerable but class accessors should be non-enumerable.

Per ES §14.3.7, class element definitions (including accessors) have enumerable: false. The AddMethod implementation correctly omits pfEnumerable, but AddGetter and AddSetter include it.

🔧 Proposed fix to remove pfEnumerable from accessor descriptors
-    FClassPrototype.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(AGetter, ExistingSetter, [pfEnumerable, pfConfigurable, pfWritable]));
+    FClassPrototype.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(AGetter, ExistingSetter, [pfConfigurable, pfWritable]));

Apply similar changes to lines 487, 505, and 510.

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

In `@source/units/Goccia.Values.ClassValue.pas` around lines 468 - 512, The
accessor descriptors created in TGocciaClassValue.AddGetter and AddSetter
wrongly include pfEnumerable; update the FClassPrototype.DefineProperty calls in
both AddGetter and AddSetter (the TGocciaPropertyDescriptorAccessor.Create
invocations) to omit pfEnumerable so class accessors are non-enumerable (i.e.,
use the same flags as AddMethod, removing pfEnumerable from the flags list).
🤖 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.Builtins.GlobalObject.pas`:
- Around line 355-364: Object.create's second-argument path passes PropsArg to
ObjectDefineProperties, but ObjectDefineProperties currently only iterates
PropertiesDescriptor.GetEnumerablePropertyEntries so symbol-keyed property
descriptors are ignored; update ObjectDefineProperties (the routine called from
the Object.create branch) to also collect/iterate symbol-keyed descriptor
entries (or use a descriptor API that returns both string and symbol entries)
instead of solely GetEnumerablePropertyEntries so descriptors for symbol keys
(e.g. PropertiesDescriptor.GetEnumerableSymbolEntries or equivalent) are
processed and applied to the new object.

In `@source/units/Goccia.Engine.pas`:
- Around line 1035-1047: The backfill calls to
TGocciaClassValue.PatchDefaultPrototype only patch the constructors listed (and
GenericConstructor only references the most recent registration), leaving
earlier-registered constructors like TextEncoder, TextDecoder, URL,
URLSearchParams, Headers, and Response unpatched and still detached from
Function.prototype; fix this by ensuring PatchDefaultPrototype is invoked for
every constructor registration (not just the latest GenericConstructor) — either
call TGocciaClassValue.PatchDefaultPrototype for each missing constructor
(TextEncoder, TextDecoder, URL, URLSearchParams, Headers, Response) in the
initialization block or update the registration code so that the
registry/registration routine iterates and calls PatchDefaultPrototype for all
entries when they are created, so all constructors end up with the correct
[[Prototype]] (ensure references to GenericConstructor, FunctionConstructor and
the named constructors are used to locate where to apply the changes).

In `@source/units/Goccia.Runtime.Bootstrap.pas`:
- Around line 505-517: PatchDefaultPrototype is only being applied to the
specific constructors listed and misses earlier-created constructors
(TextEncoder, TextDecoder, URL, URLSearchParams), so those globals won't inherit
from Function.prototype; update the bootstrap to call
TGocciaClassValue.PatchDefaultPrototype for TextEncoderConstructor,
TextDecoderConstructor, URLConstructor and URLSearchParamsConstructor (or
alternatively iterate over the registry of created constructors and call
TGocciaClassValue.PatchDefaultPrototype for each) instead of assuming
GenericConstructor covers earlier ones.

In `@source/units/Goccia.Values.FunctionBase.pas`:
- Around line 598-599: The bound-class call paths in TGocciaClassValue (where
FOriginalFunction is TGocciaClassValue and the call uses
TGocciaClassValue(...).Call with CombinedArgs and FBoundThis) execute correctly
but TGocciaBoundFunctionValue.GetFunctionName and GetFunctionLength still read
metadata only from TGocciaFunctionBase, causing bound constructors to report
fallback "bound " and length 0; update GetFunctionName and GetFunctionLength in
TGocciaBoundFunctionValue to detect when FOriginalFunction is a
TGocciaClassValue (or any class-derived callable) and return the original
class/constructor name and length by delegating to the underlying
TGocciaClassValue (or underlying callable) metadata rather than always reading
TGocciaFunctionBase, ensuring the bound wrapper preserves the original
constructor’s name and length for all call paths (also apply same fix for the
other similar branches around the 610–676 region).

---

Outside diff comments:
In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 468-512: The accessor descriptors created in
TGocciaClassValue.AddGetter and AddSetter wrongly include pfEnumerable; update
the FClassPrototype.DefineProperty calls in both AddGetter and AddSetter (the
TGocciaPropertyDescriptorAccessor.Create invocations) to omit pfEnumerable so
class accessors are non-enumerable (i.e., use the same flags as AddMethod,
removing pfEnumerable from the flags list).
🪄 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: 043e6da2-d4a8-4835-b905-e4a1520b1f96

📥 Commits

Reviewing files that changed from the base of the PR and between 267b593 and f8df5da.

📒 Files selected for processing (32)
  • scripts/run_test262_suite.py
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.AST.Statements.pas
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • source/units/Goccia.Values.TypedArrayValue.pas
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/TypedArray/constructors.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js

Comment thread source/units/Goccia.Builtins.GlobalObject.pas
Comment thread source/units/Goccia.Engine.pas
Comment thread source/units/Goccia.Runtime.Bootstrap.pas
Comment thread source/units/Goccia.Values.FunctionBase.pas
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: 4

🧹 Nitpick comments (1)
scripts/test-cli-parser.ts (1)

15-25: Align the first test block with the rest of the file by using Bun.spawnSync with direct stdin piping instead of shell printf and redirection.

Lines 17–18 depend on shell piping and 2>&1 redirection, while the JSON test blocks below use Bun.spawnSync with explicit stdin encoding. For cross-platform consistency, refactor the first block to use the same stdin-piping pattern:

Suggested change
 console.log("Error display (SyntaxError with caret and suggestion)...");
 {
-  const res = await $`printf '%s\n' 'const x = 1' 'const y = x +' | ${LOADER} 2>&1`.nothrow();
-  const out = res.text();
-  if (res.exitCode === 0) throw new Error("Expected syntax error exit code");
+  const proc = Bun.spawnSync([LOADER], {
+    stdin: new TextEncoder().encode("const x = 1\nconst y = x +\n"),
+    stdout: "pipe",
+    stderr: "pipe",
+  });
+  const out = proc.stdout.toString() + proc.stderr.toString();
+  if (proc.exitCode === 0) throw new Error("Expected syntax error exit code");
   if (!out.includes("SyntaxError")) throw new Error(`Expected SyntaxError, got: ${out}`);
   if (!out.includes("^")) throw new Error(`Expected caret in error display, got: ${out}`);
   const lower = out.toLowerCase();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-cli-parser.ts` around lines 15 - 25, Replace the shell-based
invocation that uses printf and redirection with a Bun.spawnSync call that runs
LOADER and supplies the test stdin directly; specifically, change the const res
= await $`printf ... | ${LOADER} 2>&1`.nothrow() pattern to a synchronous
Bun.spawnSync invocation of LOADER providing the input string "const x =
1\nconst y = x +" (or equivalent) and then build out from res.stdout and
res.stderr (or combined output) into the same out variable used by the existing
checks; keep the existing asserts that inspect res.exitCode,
out.includes("SyntaxError"), out.includes("^"), and the suggestion hint checks
unchanged but read output from the Bun.spawnSync result instead of res.text().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/test-cli-apps.ts`:
- Around line 279-284: The test currently only inspects stdinRes.text() for
errors when invoking the loader with --source-map; update the block that runs
${LOADER} (variables LOADER, stdinRes, stdinOut) to first assert
stdinRes.exitCode is non-zero (fail the test if exitCode === 0) before checking
the output text, so we reject successful exit with a warning; keep the existing
output assertions after the exitCode check.

In `@scripts/test-cli-config.ts`:
- Around line 403-480: The current test block (in scripts/test-cli-config.ts
using tmp, test.js, test-runner.js, bench.js and commands
LOADER/TESTRUNNER/BUNDLER/BENCHRUNNER) only verifies behavior under a single
goccia.json and thus would pass if compat-var were enabled globally; add a
sibling directory (e.g., tmpNoConfig or a subdir of tmp without a goccia.json)
and create a no-config file that uses var, then invoke the same runners (LOADER
and TESTRUNNER at minimum) against that file and assert they fail or do not
exhibit compat-var behavior (e.g., expect loader output not to be "Result: 10"
or test runner not to pass), ensuring the test asserts the feature is scoped
only to files covered by goccia.json.

In `@scripts/test-cli.ts`:
- Around line 103-132: Add a baseline "no-flag" assertion before the existing
--compat-var checks: run the same inputs against LOADER, BUNDLER and TESTRUNNER
without the --compat-var flag (use the same src "use-var.js" and test
"test-var.js") and assert the negative expectations (Loader output does NOT
include "Result: 10", Bundler does NOT emit use-var.gbc, TestRunner does NOT
report "Passed: 1" / exits with failure). Place these checks immediately before
the current flagged checks so regressions that accept var unconditionally are
caught; reference LOADER, BUNDLER, TESTRUNNER, src (use-var.js) and testSrc
(test-var.js) to locate where to insert them.

In `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 560-563: The code currently assigns Info.Name into
TGocciaClassExpression(Info.Initializer).ClassDefinition.FName which converts an
anonymous class into a named class (affecting CompileClassExpression's
HasNameBinding behavior); instead remove the assignment to ClassDefinition.FName
and instead apply the inferred name to the resulting class value using the same
approach as the AST execution path (call SetInferredName on the produced class
value or equivalent helper) so the class definition remains anonymous while the
runtime value carries Info.Name; update the handling around Info.Initializer
(TGocciaClassExpression) to invoke SetInferredName( <produced class value>,
Info.Name ) at the point the value is created rather than setting
ClassDefinition.FName.

---

Nitpick comments:
In `@scripts/test-cli-parser.ts`:
- Around line 15-25: Replace the shell-based invocation that uses printf and
redirection with a Bun.spawnSync call that runs LOADER and supplies the test
stdin directly; specifically, change the const res = await $`printf ... |
${LOADER} 2>&1`.nothrow() pattern to a synchronous Bun.spawnSync invocation of
LOADER providing the input string "const x = 1\nconst y = x +" (or equivalent)
and then build out from res.stdout and res.stderr (or combined output) into the
same out variable used by the existing checks; keep the existing asserts that
inspect res.exitCode, out.includes("SyntaxError"), out.includes("^"), and the
suggestion hint checks unchanged but read output from the Bun.spawnSync result
instead of res.text().
🪄 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: 07f8dc79-aadc-4f3e-b674-f7bf0655855d

📥 Commits

Reviewing files that changed from the base of the PR and between f8df5da and 8a2f169.

📒 Files selected for processing (8)
  • scripts/test-cli-apps.ts
  • scripts/test-cli-config.ts
  • scripts/test-cli-lexer.ts
  • scripts/test-cli-parser.ts
  • scripts/test-cli.ts
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.VM.pas
  • tests/built-ins/Object/getPrototypeOf.js
✅ Files skipped from review due to trivial changes (1)
  • tests/built-ins/Object/getPrototypeOf.js

Comment thread scripts/test-cli-apps.ts
Comment on lines +279 to +284
console.log("Loader: stdin --source-map rejection...");
const stdinRes = await $`echo 'const x = 1;' | ${LOADER} --source-map 2>&1`.nothrow();
const stdinOut = stdinRes.text().toLowerCase();
if (!stdinOut.includes("error") && !stdinOut.includes("cannot") && !stdinOut.includes("require")) {
throw new Error(`Stdin --source-map should produce an error, got: ${stdinRes.text()}`);
}
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.

⚠️ Potential issue | 🟡 Minor

Assert non-zero exit for the stdin --source-map rejection too.

Right now this only checks the text. If the loader starts ignoring --source-map on stdin and returns success with a warning, this block can still pass. Please assert the rejection via exitCode before inspecting the message.

Suggested change
     console.log("Loader: stdin --source-map rejection...");
     const stdinRes = await $`echo 'const x = 1;' | ${LOADER} --source-map 2>&1`.nothrow();
+    if (stdinRes.exitCode === 0) {
+      throw new Error("Stdin --source-map should exit non-zero");
+    }
     const stdinOut = stdinRes.text().toLowerCase();
     if (!stdinOut.includes("error") && !stdinOut.includes("cannot") && !stdinOut.includes("require")) {
       throw new Error(`Stdin --source-map should produce an error, got: ${stdinRes.text()}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Loader: stdin --source-map rejection...");
const stdinRes = await $`echo 'const x = 1;' | ${LOADER} --source-map 2>&1`.nothrow();
const stdinOut = stdinRes.text().toLowerCase();
if (!stdinOut.includes("error") && !stdinOut.includes("cannot") && !stdinOut.includes("require")) {
throw new Error(`Stdin --source-map should produce an error, got: ${stdinRes.text()}`);
}
console.log("Loader: stdin --source-map rejection...");
const stdinRes = await $`echo 'const x = 1;' | ${LOADER} --source-map 2>&1`.nothrow();
if (stdinRes.exitCode === 0) {
throw new Error("Stdin --source-map should exit non-zero");
}
const stdinOut = stdinRes.text().toLowerCase();
if (!stdinOut.includes("error") && !stdinOut.includes("cannot") && !stdinOut.includes("require")) {
throw new Error(`Stdin --source-map should produce an error, got: ${stdinRes.text()}`);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-cli-apps.ts` around lines 279 - 284, The test currently only
inspects stdinRes.text() for errors when invoking the loader with --source-map;
update the block that runs ${LOADER} (variables LOADER, stdinRes, stdinOut) to
first assert stdinRes.exitCode is non-zero (fail the test if exitCode === 0)
before checking the output text, so we reject successful exit with a warning;
keep the existing output assertions after the exitCode check.

Comment thread scripts/test-cli-config.ts
Comment thread scripts/test-cli.ts
Comment on lines +103 to +132
console.log("--compat-var (Loader + Bundler + TestRunner)...");
{
const tmp = mkdtempSync(join(tmpdir(), "goccia-var-"));
try {
const src = join(tmp, "use-var.js");
writeFileSync(src, "var x = 10;\nx;\n");

// Loader with --compat-var
const loaderOut = await $`${LOADER} ${src} --compat-var 2>&1`.text();
if (!loaderOut.includes("Result: 10")) throw new Error(`Loader --compat-var expected Result: 10, got: ${loaderOut}`);

// Bundler with --compat-var
await $`${BUNDLER} ${src} --compat-var`.quiet();
if (!existsSync(join(tmp, "use-var.gbc"))) throw new Error("Bundler --compat-var should produce .gbc");

// TestRunner with --compat-var
const testSrc = join(tmp, "test-var.js");
writeFileSync(
testSrc,
[
"var y = 20;",
'describe("var", () => {',
' test("works", () => {',
" expect(y).toBe(20);",
" });",
"});",
].join("\n") + "\n",
);
const trOut = await $`${TESTRUNNER} ${testSrc} --compat-var --no-progress 2>&1`.text();
if (!trOut.includes("Passed: 1")) throw new Error(`TestRunner --compat-var expected Passed: 1, got: ${trOut}`);
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.

⚠️ Potential issue | 🟡 Minor

Add one no-flag assertion for --compat-var.

This block only proves the positive path. If var becomes accepted unconditionally, all three checks still pass. A single baseline failure before enabling the flag would catch that regression.

Suggested change
   try {
     const src = join(tmp, "use-var.js");
     writeFileSync(src, "var x = 10;\nx;\n");

+    const loaderNoCompat = await $`${LOADER} ${src} 2>&1`.nothrow();
+    if (loaderNoCompat.exitCode === 0) {
+      throw new Error("Loader should reject `var` without --compat-var");
+    }
+
     // Loader with --compat-var
     const loaderOut = await $`${LOADER} ${src} --compat-var 2>&1`.text();
     if (!loaderOut.includes("Result: 10")) throw new Error(`Loader --compat-var expected Result: 10, got: ${loaderOut}`);

Based on learnings: JavaScript tests should cover happy paths, edge cases, and error cases. Keep tests isolated and grouped by feature/filename.

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

In `@scripts/test-cli.ts` around lines 103 - 132, Add a baseline "no-flag"
assertion before the existing --compat-var checks: run the same inputs against
LOADER, BUNDLER and TESTRUNNER without the --compat-var flag (use the same src
"use-var.js" and test "test-var.js") and assert the negative expectations
(Loader output does NOT include "Result: 10", Bundler does NOT emit use-var.gbc,
TestRunner does NOT report "Passed: 1" / exits with failure). Place these checks
immediately before the current flagged checks so regressions that accept var
unconditionally are caught; reference LOADER, BUNDLER, TESTRUNNER, src
(use-var.js) and testSrc (test-var.js) to locate where to insert them.

Comment thread source/units/Goccia.Compiler.Statements.pas Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.VM.pas`:
- Around line 4465-4475: Computed writes to class constructors are routed
through TGocciaClassValue.SetProperty (via
KeyToPropertyNameRegister/RegisterToValue) which creates missing static
properties without pfEnumerable; instead, treat computed writes like
symbol-keyed assignments and call TGocciaClassValue.AssignSymbolProperty so the
created static uses the same descriptor flags (including pfEnumerable) as normal
static fields. Locate the branch using KeyToPropertyNameRegister and
RegisterToValue and replace the SetProperty call with an AssignSymbolProperty
call that converts the computed key to a TGocciaSymbolValue (mirroring the
existing symbol branch) or otherwise ensure the property is created with
pfEnumerable when falling back from computed-key writes.
- Around line 4407-4427: OP_GET_INDEX must use the same class-first dispatch as
the OP_GET_PROPERTY code path: when handling index lookups check
FRegisters[B].Kind and whether FRegisters[B].ObjectValue is a TGocciaClassValue
before checking for TGocciaObjectValue so that
TGocciaClassValue.GetProperty/GetSymbolProperty (and its static getter/method
and superclass logic) run for computed reads; update the OP_GET_INDEX handler to
call SetRegister(A, TGocciaClassValue(...).GetSymbolProperty(...)) or
SetRegister(A,
TGocciaClassValue(...).GetProperty(KeyToPropertyNameRegister(...))) when B holds
a TGocciaClassValue, and only fall back to
TGocciaObjectValue.GetSymbolProperty/GetProperty afterwards.
🪄 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: 21789ee5-fe63-46aa-9d05-b869271de6ea

📥 Commits

Reviewing files that changed from the base of the PR and between 8a2f169 and c276675.

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

Comment thread source/units/Goccia.VM.pas
Comment thread source/units/Goccia.VM.pas
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.

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

670-684: ⚠️ Potential issue | 🟠 Major

AddAutoAccessor ignores the AIsStatic parameter.

The TC39 proposal supports static accessor a = 1 syntax, meaning auto-accessors can be either instance or static. For instance auto-accessors, the getter/setter should be on the prototype (FClassPrototype). For static auto-accessors, they should be on the class itself (Self).

Currently, the method always defines on FClassPrototype regardless of AIsStatic, which means static auto-accessors would incorrectly be placed on the prototype instead of the class.

🐛 Proposed fix to handle static vs instance auto-accessors
 procedure TGocciaClassValue.AddAutoAccessor(const AName, ABackingName: string; const AIsStatic: Boolean);
 var
   GetterHelper: TGocciaAutoAccessorGetter;
   SetterHelper: TGocciaAutoAccessorSetter;
   GetterFn, SetterFn: TGocciaNativeFunctionValue;
+  Target: TGocciaObjectValue;
 begin
   GetterHelper := TGocciaAutoAccessorGetter.Create(ABackingName);
   SetterHelper := TGocciaAutoAccessorSetter.Create(ABackingName);
 
   GetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(GetterHelper.Get, 'get ' + AName, 0);
   SetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(SetterHelper.SetValue, 'set ' + AName, 1);
 
-  FClassPrototype.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(
+  if AIsStatic then
+    Target := Self
+  else
+    Target := FClassPrototype;
+
+  Target.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(
     GetterFn, SetterFn, [pfEnumerable, pfConfigurable, pfWritable]));
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.ClassValue.pas` around lines 670 - 684, The
AddAutoAccessor method (TGocciaClassValue.AddAutoAccessor) currently always
defines the accessor on FClassPrototype and ignores the AIsStatic flag; update
it to choose the target object based on AIsStatic (use Self for static
accessors, FClassPrototype for instance accessors) and call DefineProperty on
that chosen target with the created GetterFn and SetterFn
(TGocciaPropertyDescriptorAccessor), preserving the existing flags and names so
static accessors are attached to the class and instance accessors to the
prototype.
🧹 Nitpick comments (1)
source/units/Goccia.Values.FunctionBase.pas (1)

695-701: Double property read in GetFunctionLength.

GetProperty(PROP_LENGTH) is called twice: once in the type check and again to extract the value. This is inefficient and could produce inconsistent results if the getter has side effects.

♻️ Proposed fix to store the result in a local
 function TGocciaBoundFunctionValue.GetFunctionLength: Integer;
 var
   OrigLength: Integer;
+  LengthVal: TGocciaValue;
 begin
   // ECMAScript: bound function length = max(0, originalLength - boundArgs.length)
   if FOriginalFunction is TGocciaFunctionBase then
     OrigLength := TGocciaFunctionBase(FOriginalFunction).GetFunctionLength
   else if FOriginalFunction is TGocciaClassValue then
   begin
     OrigLength := 0;
-    if TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH) is TGocciaNumberLiteralValue then
-      OrigLength := Trunc(TGocciaNumberLiteralValue(
-        TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH)).Value);
+    LengthVal := TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH);
+    if LengthVal is TGocciaNumberLiteralValue then
+      OrigLength := Trunc(TGocciaNumberLiteralValue(LengthVal).Value);
   end
   else
     OrigLength := 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.FunctionBase.pas` around lines 695 - 701, In
GetFunctionLength, avoid calling
TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH) twice: fetch the
result once into a local variable (e.g., LProp :=
TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH)), check if LProp
is a TGocciaNumberLiteralValue and only then extract and Trunc its .Value into
OrigLength; ensure you handle LProp = nil safely. Update references in the
TGocciaClassValue branch to use LProp instead of repeated GetProperty calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 670-684: The AddAutoAccessor method
(TGocciaClassValue.AddAutoAccessor) currently always defines the accessor on
FClassPrototype and ignores the AIsStatic flag; update it to choose the target
object based on AIsStatic (use Self for static accessors, FClassPrototype for
instance accessors) and call DefineProperty on that chosen target with the
created GetterFn and SetterFn (TGocciaPropertyDescriptorAccessor), preserving
the existing flags and names so static accessors are attached to the class and
instance accessors to the prototype.

---

Nitpick comments:
In `@source/units/Goccia.Values.FunctionBase.pas`:
- Around line 695-701: In GetFunctionLength, avoid calling
TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH) twice: fetch the
result once into a local variable (e.g., LProp :=
TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH)), check if LProp
is a TGocciaNumberLiteralValue and only then extract and Trunc its .Value into
OrigLength; ensure you handle LProp = nil safely. Update references in the
TGocciaClassValue branch to use LProp instead of repeated GetProperty calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8d75474-0304-4cac-b1d1-c4f18408379d

📥 Commits

Reviewing files that changed from the base of the PR and between c276675 and ad398ac.

📒 Files selected for processing (5)
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
🚧 Files skipped from review as they are similar to previous changes (2)
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Engine.pas

frostney and others added 4 commits April 22, 2026 20:14
- Add compat-var and BigInt typed array handling to the Test262 runner
- Fix constructor/prototype property descriptors and class name inference
- Update built-in and language tests for the newly supported semantics
- cover loader, bundler, benchmark runner, and REPL app behaviors
- add config discovery, per-file overrides, source maps, and coverage cases
- fix statement and VM handling for Test262 object prototype checks
- Swap TGocciaClassValue/TGocciaObjectValue checks in VM property ops
- Link derived class constructors to superclass via [[Prototype]]
…essor flags

- Reorder TGocciaClassValue/TGocciaObjectValue type checks in VM ops
  (OP_ARRAY_GET, OP_ARRAY_SET) so the more specific class check runs
  first — fixes Symbol.species, static computed symbol accessors, and
  static property inheritance in bytecode mode
- Set [[Prototype]] on derived class in OP_CLASS_SET_SUPER for static
  property inheritance via prototype chain
- PatchDefaultPrototype for TextEncoder, TextDecoder, URL, and
  URLSearchParams constructors in both Engine and Bootstrap
- ObjectDefineProperties: process symbol-keyed descriptor entries
- BoundFunction name/length: delegate to TGocciaClassValue metadata
  when the original function is a class constructor
- Class accessors (AddGetter/AddSetter): omit pfEnumerable per ES
  §14.3.8/§14.3.9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney force-pushed the t3code/test262-feature-gaps branch from ad398ac to 570fe7d Compare April 22, 2026 19:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
source/units/Goccia.VM.pas (1)

4510-4555: ⚠️ Potential issue | 🟠 Major

Bytecode class accessors are still created as enumerable.

Lines 4514-4555 fix constructor and prototype methods, but Lines 5843-5886 still route class accessors through DefineGetterProperty* / DefineSetterProperty*, and those helpers build descriptors with pfEnumerable. That means bytecode-compiled get/set class elements still expose the wrong enumerability even after this method fix.

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

In `@source/units/Goccia.VM.pas` around lines 4510 - 4555, Bytecode-compiled class
accessors are being created with pfEnumerable because the helpers
DefineGetterProperty*/DefineSetterProperty* always add pfEnumerable; update
those helpers (DefineGetterProperty, DefineSetterProperty) to either accept an
explicit enumerability parameter or detect when the target object is a
class/prototype (e.g., TGocciaVMClassValue.Prototype or
TGocciaClassValue.Prototype) and create the property descriptor without
pfEnumerable (use [pfConfigurable, pfWritable] like in OP_CLASS_ADD_METHOD_CONST
for prototype methods/constructor), so get/set class elements are
non-enumerable.
source/units/Goccia.Values.ClassValue.pas (1)

918-927: ⚠️ Potential issue | 🟠 Major

Add 'prototype' as an own property in TGocciaClassValue.

GetProperty('prototype') returns FClassPrototype, but HasOwnProperty and GetOwnPropertyDescriptor don't recognize it because 'prototype' is not in the property table. Either override these methods to handle 'prototype' specially, or call DefineProperty(PROP_PROTOTYPE, ...) during construction like native constructors do, to ensure Object.getOwnPropertyDescriptor(C, 'prototype') and C.hasOwnProperty('prototype') return correct values matching ordinary reads.

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

In `@source/units/Goccia.Values.ClassValue.pas` around lines 918 - 927,
TGocciaClassValue currently returns FClassPrototype from GetProperty when AName
= PROP_PROTOTYPE but does not expose it as an "own" property; update
TGocciaClassValue to define 'prototype' as an own property during construction
(or initialization) by calling DefineProperty(PROP_PROTOTYPE, FClassPrototype,
<appropriate attributes>) so that HasOwnProperty and GetOwnPropertyDescriptor
recognize it, or alternatively override HasOwnProperty and
GetOwnPropertyDescriptor in TGocciaClassValue to special-case PROP_PROTOTYPE and
return the descriptor/value tied to FClassPrototype; reference
TGocciaClassValue.GetProperty, TGocciaClassValue.HasOwnProperty,
TGocciaClassValue.GetOwnPropertyDescriptor, PROP_PROTOTYPE, FClassPrototype, and
the class constructor/initializer when applying the change.
♻️ Duplicate comments (1)
source/units/Goccia.VM.pas (1)

4407-4427: ⚠️ Potential issue | 🟠 Major

OP_GET_INDEX is still object-first, so C[key] can bypass class static lookup.

This branch is fixed, but Line 4701 still checks TGocciaObjectValue before TGocciaClassValue in OP_GET_INDEX. Since class values are object-like here, bracket reads in bytecode mode can still miss TGocciaClassValue.GetProperty / GetSymbolProperty and skip superclass walking for static members. Based on learnings: In ExecuteClosureRegistersInternal, property/index ops must dispatch "class-first" so static lookup and superclass walking happen.

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

In `@source/units/Goccia.VM.pas` around lines 4407 - 4427, The OP_GET_INDEX
handling is dispatching object-first so class static lookups and superclass
walking are bypassed; fix ExecuteClosureRegistersInternal by checking for
TGocciaClassValue before TGocciaObjectValue when inspecting FRegisters[B] (and
keeping the existing TGocciaSymbolValue check for FRegisters[C] inside that
branch), i.e., move or duplicate the TGocciaClassValue branch so the code tests
"(FRegisters[B].ObjectValue is TGocciaClassValue)" prior to the "(... is
TGocciaObjectValue)" branch and call
TGocciaClassValue.GetProperty/GetSymbolProperty there to ensure static member
resolution and superclass traversal occur.
🤖 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.Runtime.Bootstrap.pas`:
- Around line 443-447: The property creation for the %TypedArray% intrinsic uses
SetProperty which makes writable/configurable properties; replace those
SetProperty calls for PROP_NAME and PROP_LENGTH on FTypedArrayIntrinsic with
DefineProperty calls that use TGocciaStringLiteralValue('TypedArray') and
TGocciaNumberLiteralValue(0) respectively and set the descriptor flags to make
them non-writable (and non-enumerable/non-configurable per built-in constructor
semantics). In other words, instead of FTypedArrayIntrinsic.SetProperty(...),
call FTypedArrayIntrinsic.DefineProperty(PROP_NAME, <string value>, writable:
false, enumerable: false, configurable: false) and similarly for PROP_LENGTH
with the number value.

In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 682-683: The class auto-accessor is being defined with
pfEnumerable which makes it enumerable; update the AddAutoAccessor path so
FClassPrototype.DefineProperty uses TGocciaPropertyDescriptorAccessor with flags
[pfConfigurable, pfWritable] (same as AddGetter/AddSetter) instead of including
pfEnumerable; locate the code that calls FClassPrototype.DefineProperty for
auto-accessors (the AddAutoAccessor implementation) and remove pfEnumerable from
the descriptor flags to make the auto-accessor non-enumerable per ECMAScript
class accessor semantics.

---

Outside diff comments:
In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 918-927: TGocciaClassValue currently returns FClassPrototype from
GetProperty when AName = PROP_PROTOTYPE but does not expose it as an "own"
property; update TGocciaClassValue to define 'prototype' as an own property
during construction (or initialization) by calling
DefineProperty(PROP_PROTOTYPE, FClassPrototype, <appropriate attributes>) so
that HasOwnProperty and GetOwnPropertyDescriptor recognize it, or alternatively
override HasOwnProperty and GetOwnPropertyDescriptor in TGocciaClassValue to
special-case PROP_PROTOTYPE and return the descriptor/value tied to
FClassPrototype; reference TGocciaClassValue.GetProperty,
TGocciaClassValue.HasOwnProperty, TGocciaClassValue.GetOwnPropertyDescriptor,
PROP_PROTOTYPE, FClassPrototype, and the class constructor/initializer when
applying the change.

In `@source/units/Goccia.VM.pas`:
- Around line 4510-4555: Bytecode-compiled class accessors are being created
with pfEnumerable because the helpers
DefineGetterProperty*/DefineSetterProperty* always add pfEnumerable; update
those helpers (DefineGetterProperty, DefineSetterProperty) to either accept an
explicit enumerability parameter or detect when the target object is a
class/prototype (e.g., TGocciaVMClassValue.Prototype or
TGocciaClassValue.Prototype) and create the property descriptor without
pfEnumerable (use [pfConfigurable, pfWritable] like in OP_CLASS_ADD_METHOD_CONST
for prototype methods/constructor), so get/set class elements are
non-enumerable.

---

Duplicate comments:
In `@source/units/Goccia.VM.pas`:
- Around line 4407-4427: The OP_GET_INDEX handling is dispatching object-first
so class static lookups and superclass walking are bypassed; fix
ExecuteClosureRegistersInternal by checking for TGocciaClassValue before
TGocciaObjectValue when inspecting FRegisters[B] (and keeping the existing
TGocciaSymbolValue check for FRegisters[C] inside that branch), i.e., move or
duplicate the TGocciaClassValue branch so the code tests
"(FRegisters[B].ObjectValue is TGocciaClassValue)" prior to the "(... is
TGocciaObjectValue)" branch and call
TGocciaClassValue.GetProperty/GetSymbolProperty there to ensure static member
resolution and superclass traversal occur.
🪄 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: 6e22cefe-a1d0-44fe-92df-eddb901738d5

📥 Commits

Reviewing files that changed from the base of the PR and between ad398ac and 570fe7d.

📒 Files selected for processing (34)
  • scripts/run_test262_suite.py
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.AST.Statements.pas
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.VM.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • source/units/Goccia.Values.TypedArrayValue.pas
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/TypedArray/constructors.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js
✅ Files skipped from review due to trivial changes (14)
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Array/isArray.js
  • tests/language/classes/method-enumerable.js
  • source/units/Goccia.Compiler.Statements.pas
  • tests/built-ins/TypedArray/constructors.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
🚧 Files skipped from review as they are similar to previous changes (12)
  • tests/built-ins/Function/prototype/apply.js
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.SharedPrototype.pas
  • tests/language/classes/basic-class-declaration.js
  • tests/built-ins/Object/create.js
  • source/units/Goccia.Values.TypedArrayValue.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.AST.Statements.pas
  • scripts/run_test262_suite.py
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Engine.pas

Comment thread source/units/Goccia.Runtime.Bootstrap.pas Outdated
Comment thread source/units/Goccia.Values.ClassValue.pas Outdated
…lity

- %TypedArray%.name and .length: use DefineProperty with [pfConfigurable]
  instead of SetProperty, making them non-writable per ES §17
- Auto-accessor (AddAutoAccessor): remove pfEnumerable to match class
  accessor semantics (AddGetter/AddSetter already fixed)

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

♻️ Duplicate comments (2)
source/units/Goccia.Compiler.Statements.pas (1)

560-563: ⚠️ Potential issue | 🟠 Major

Do not turn anonymous class expressions into named class expressions for inferred names.

At Line 563, writing ClassDefinition.FName := Info.Name changes semantics: CompileClassExpression will treat it as named (HasNameBinding at Line 2558), creating inner class-name binding behavior instead of just inferring .name.

Suggested direction
-      if (Info.Initializer is TGocciaClassExpression) and
-         (TGocciaClassExpression(Info.Initializer).ClassDefinition.Name = '') then
-        TGocciaClassExpression(Info.Initializer).ClassDefinition.FName := Info.Name;
+      // Keep class definition anonymous here.
+      // Apply inferred class name on the produced class value instead.
#!/bin/bash
# Verify the mutation site and its semantic coupling to name-binding logic.
rg -n -A6 -B6 'ClassDefinition\.FName := Info\.Name' source/units/Goccia.Compiler.Statements.pas
rg -n -A8 -B6 'HasNameBinding := ClassDef\.Name <> ' source/units/Goccia.Compiler.Statements.pas
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Compiler.Statements.pas` around lines 560 - 563, The code
assigns Info.Name to ClassDefinition.FName which turns an anonymous class into a
named class (affecting CompileClassExpression's HasNameBinding logic); remove
that assignment and instead record the inferred name without setting FName
(e.g., introduce/use a separate property such as ClassDefinition.InferredName or
a runtime-only name field) so the class keeps Name = '' for binding checks while
still exposing the inferred .name; update TGocciaClassExpression/ClassDefinition
to hold and use that inferred-name slot where needed, and ensure
CompileClassExpression/HasNameBinding continue to rely on ClassDefinition.Name.
source/units/Goccia.Engine.pas (1)

936-955: ⚠️ Potential issue | 🟠 Major

Backfill Headers and Response too.

PatchDefaultPrototype only fixes the exact constructor instance you pass in. Headers and Response are still registered before FunctionConstructor, but unlike TextEncoder/URL they are never saved and never patched here, so both globals stay detached from Function.prototype.

Suggested fix
   URLConstructor: TGocciaURLClassValue;
   URLSearchParamsConstructor: TGocciaURLSearchParamsClassValue;
-  TextEncoderConstructor, TextDecoderConstructor: TGocciaClassValue;
+  TextEncoderConstructor, TextDecoderConstructor: TGocciaClassValue;
+  HeadersConstructor, ResponseConstructor: TGocciaClassValue;
   TypeDef: TGocciaTypeDefinition;
@@
   TypeDef.AddSpeciesGetter := False;
   RegisterTypeDefinition(FInterpreter.GlobalScope, TypeDef, SpeciesGetter, GenericConstructor);
+  HeadersConstructor := GenericConstructor;
@@
   TypeDef.AddSpeciesGetter := False;
   RegisterTypeDefinition(FInterpreter.GlobalScope, TypeDef, SpeciesGetter, GenericConstructor);
+  ResponseConstructor := GenericConstructor;
@@
   TGocciaClassValue.PatchDefaultPrototype(TextEncoderConstructor);
   TGocciaClassValue.PatchDefaultPrototype(TextDecoderConstructor);
   TGocciaClassValue.PatchDefaultPrototype(URLConstructor);
   TGocciaClassValue.PatchDefaultPrototype(URLSearchParamsConstructor);
+  TGocciaClassValue.PatchDefaultPrototype(HeadersConstructor);
+  TGocciaClassValue.PatchDefaultPrototype(ResponseConstructor);
   TGocciaClassValue.PatchDefaultPrototype(FunctionConstructor);

Also applies to: 1041-1057

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

In `@source/units/Goccia.Engine.pas` around lines 936 - 955, TypeDef instances for
CONSTRUCTOR_HEADERS and CONSTRUCTOR_RESPONSE are registered before
FunctionConstructor and thus never patched by PatchDefaultPrototype; after
FunctionConstructor is created and patched, locate where PatchDefaultPrototype
is invoked for TextEncoder/URL and call the same for the Headers and Response
constructors (the TypeDef entries with ConstructorName = CONSTRUCTOR_HEADERS and
CONSTRUCTOR_RESPONSE or the concrete
TGocciaHeadersClassValue/TGocciaResponseClassValue constructor objects) so their
prototype chains are attached to Function.prototype; in short, ensure you
backfill/patch the Headers and Response constructors after FunctionConstructor
initialization by invoking PatchDefaultPrototype (or the equivalent patching
routine) for those two constructors just like was done for TextEncoder/URL.
🧹 Nitpick comments (1)
tests/built-ins/Object/getPrototypeOf.js (1)

54-64: Test name says "interpreted" but describes cross-mode behavior.

The test name includes "(interpreted)" but the comment explicitly states it's testing "cross-mode compatibility." Consider renaming for clarity.

✏️ Suggested rename
-  test("returns superclass for a class with extends (interpreted)", () => {
+  test("returns superclass for a class with extends", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Object/getPrototypeOf.js` around lines 54 - 64, Rename the
test case whose title is "returns superclass for a class with extends
(interpreted)" to reflect cross-mode behavior: update the test name string in
the test containing classes A and B and the call Object.getPrototypeOf(B) to
something like "returns superclass for a class with extends (cross-mode
compatible)" or "returns superclass for a class with extends (cross-mode)"; keep
the test body (A, B, Object.getPrototypeOf(B), and the prototype assertions)
unchanged so the intent and assertions remain identical while the title matches
the comment.
🤖 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.ClassValue.pas`:
- Around line 302-306: The code currently skips creating the class .name own
property when FName = '' which breaks anonymous-class own-property semantics;
always call DefineProperty for PROP_NAME using TGocciaPropertyDescriptorData
with TGocciaStringLiteralValue.Create(FName) (allowing empty string) and the
same descriptor flags ([pfConfigurable]) instead of conditionally gating on
FName, so update the logic in the class constructor/initialization (references:
FName, PROP_NAME, DefineProperty, TGocciaPropertyDescriptorData,
TGocciaStringLiteralValue.Create) to unconditionally define the .name property.

In `@source/units/Goccia.Values.FunctionBase.pas`:
- Around line 314-325: DispatchCall currently only handles TGocciaFunctionBase
and TGocciaClassValue, so callable proxies (where ACallee.IsCallable = True)
fall through and raise 'not callable' instead of invoking their apply trap;
change DispatchCall to check the value's IsCallable property and, when true,
invoke the general call path on the value itself (use the value's Call method)
instead of only casting to TGocciaFunctionBase/TGocciaClassValue, e.g. first
test ACallee.IsCallable and call ACallee.Call(AArgs, AThisValue), otherwise keep
the existing checks for TGocciaFunctionBase/TGocciaClassValue and finally raise
the error.

---

Duplicate comments:
In `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 560-563: The code assigns Info.Name to ClassDefinition.FName which
turns an anonymous class into a named class (affecting CompileClassExpression's
HasNameBinding logic); remove that assignment and instead record the inferred
name without setting FName (e.g., introduce/use a separate property such as
ClassDefinition.InferredName or a runtime-only name field) so the class keeps
Name = '' for binding checks while still exposing the inferred .name; update
TGocciaClassExpression/ClassDefinition to hold and use that inferred-name slot
where needed, and ensure CompileClassExpression/HasNameBinding continue to rely
on ClassDefinition.Name.

In `@source/units/Goccia.Engine.pas`:
- Around line 936-955: TypeDef instances for CONSTRUCTOR_HEADERS and
CONSTRUCTOR_RESPONSE are registered before FunctionConstructor and thus never
patched by PatchDefaultPrototype; after FunctionConstructor is created and
patched, locate where PatchDefaultPrototype is invoked for TextEncoder/URL and
call the same for the Headers and Response constructors (the TypeDef entries
with ConstructorName = CONSTRUCTOR_HEADERS and CONSTRUCTOR_RESPONSE or the
concrete TGocciaHeadersClassValue/TGocciaResponseClassValue constructor objects)
so their prototype chains are attached to Function.prototype; in short, ensure
you backfill/patch the Headers and Response constructors after
FunctionConstructor initialization by invoking PatchDefaultPrototype (or the
equivalent patching routine) for those two constructors just like was done for
TextEncoder/URL.

---

Nitpick comments:
In `@tests/built-ins/Object/getPrototypeOf.js`:
- Around line 54-64: Rename the test case whose title is "returns superclass for
a class with extends (interpreted)" to reflect cross-mode behavior: update the
test name string in the test containing classes A and B and the call
Object.getPrototypeOf(B) to something like "returns superclass for a class with
extends (cross-mode compatible)" or "returns superclass for a class with extends
(cross-mode)"; keep the test body (A, B, Object.getPrototypeOf(B), and the
prototype assertions) unchanged so the intent and assertions remain identical
while the title matches the 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: f7a716f6-fad7-486c-8bdb-d84625adc0da

📥 Commits

Reviewing files that changed from the base of the PR and between ad398ac and 98650d1.

📒 Files selected for processing (34)
  • scripts/run_test262_suite.py
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.AST.Statements.pas
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.VM.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • source/units/Goccia.Values.TypedArrayValue.pas
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/TypedArray/constructors.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js
✅ Files skipped from review due to trivial changes (15)
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js
  • tests/built-ins/Function/prototype/call.js
  • source/units/Goccia.Builtins.GlobalObject.pas
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Map/constructor.js
  • source/units/Goccia.VM.pas
🚧 Files skipped from review as they are similar to previous changes (7)
  • source/units/Goccia.Values.ArrayValue.pas
  • tests/built-ins/Object/create.js
  • source/units/Goccia.SharedPrototype.pas
  • tests/built-ins/TypedArray/constructors.js
  • tests/built-ins/Function/prototype/apply.js
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Builtins.Globals.pas

Comment thread source/units/Goccia.Values.ClassValue.pas Outdated
Comment thread source/units/Goccia.Values.FunctionBase.pas Outdated
- ClassValue constructor: unconditionally define .name own property
  (anonymous classes get empty string per ES spec)
- Compiler: emit OP_ARRAY_SET for inferred class name instead of
  mutating AST FName, which broke HasNameBinding scope logic
- DispatchCall: add comment clarifying callable proxy handling
- Engine: save Headers/Response constructor refs and add
  PatchDefaultPrototype calls for both
- Rename test case to drop "(interpreted)" qualifier

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

Caution

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

⚠️ Outside diff range comments (2)
source/units/Goccia.Values.ClassValue.pas (2)

669-683: ⚠️ Potential issue | 🟠 Major

Respect AIsStatic when installing auto-accessors.

AIsStatic is ignored here, so static accessor x is always defined on FClassPrototype. That puts the accessor on instances instead of the constructor and breaks static decorator output.

Suggested fix
 procedure TGocciaClassValue.AddAutoAccessor(const AName, ABackingName: string; const AIsStatic: Boolean);
 var
   GetterHelper: TGocciaAutoAccessorGetter;
   SetterHelper: TGocciaAutoAccessorSetter;
   GetterFn, SetterFn: TGocciaNativeFunctionValue;
+  Target: TGocciaObjectValue;
 begin
   GetterHelper := TGocciaAutoAccessorGetter.Create(ABackingName);
   SetterHelper := TGocciaAutoAccessorSetter.Create(ABackingName);

   GetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(GetterHelper.Get, 'get ' + AName, 0);
   SetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(SetterHelper.SetValue, 'set ' + AName, 1);

-  FClassPrototype.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(
+  if AIsStatic then
+    Target := Self
+  else
+    Target := FClassPrototype;
+
+  Target.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create(
     GetterFn, SetterFn, [pfConfigurable, pfWritable]));
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.ClassValue.pas` around lines 669 - 683, The
AddAutoAccessor method ignores AIsStatic and always installs accessors on
FClassPrototype; update TGocciaClassValue.AddAutoAccessor to choose the target
object based on AIsStatic (use FClassPrototype when AIsStatic = False and the
class constructor object—e.g. FClassConstructor or whatever field holds the
constructor—when AIsStatic = True) and call DefineProperty on that target with
the existing GetterFn/SetterFn and
TGocciaPropertyDescriptorAccessor.Create(...). Keep creation of
GetterHelper/SetterHelper and GetterFn/SetterFn unchanged but branch on
AIsStatic before the DefineProperty call so static accessors are attached to the
constructor instead of the prototype.

954-979: ⚠️ Potential issue | 🟠 Major

Preserve ordinary assignment semantics for runtime static writes.

After the setter lookup, this turns every C.x = ... write into a non-enumerable define. That is correct for class-body statics and builtin slots, but not for ordinary userland assignment on constructor objects — Object.keys(C) should still see a property added via C.x = 1.

Suggested fix
 procedure TGocciaClassValue.SetProperty(const AName: string; const AValue: TGocciaValue);
 var
   Setter: TGocciaFunctionBase;
   Args: TGocciaArgumentsCollection;
   Current: TGocciaClassValue;
 begin
   Current := Self;
   repeat
     if Current.FStaticSetters.TryGetValue(AName, Setter) then
     begin
       Args := TGocciaArgumentsCollection.CreateWithCapacity(1);
       try
         Args.Add(AValue);
         Setter.Call(Args, Self);
       finally
         Args.Free;
       end;
       Exit;
     end;
     Current := Current.FSuperClass;
   until not Assigned(Current);

-  inherited DefineProperty(AName,
-    TGocciaPropertyDescriptorData.Create(AValue, [pfConfigurable, pfWritable]));
+  inherited SetProperty(AName, AValue);
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.Values.ClassValue.pas` around lines 954 - 979, The
fallback path in TGocciaClassValue.SetProperty currently calls inherited
DefineProperty with a descriptor that omits pfEnumerable, making every runtime
static write (e.g. C.x = 1) non-enumerable; update the fallback to define the
property as enumerable for ordinary userland assignment by adding pfEnumerable
to the TGocciaPropertyDescriptorData flags (i.e. use [pfConfigurable,
pfWritable, pfEnumerable]) so constructor-object assignments become visible to
Object.keys; keep this change localized to the fallback path in
TGocciaClassValue.SetProperty where inherited DefineProperty is invoked.
🧹 Nitpick comments (1)
source/units/Goccia.Values.FunctionBase.pas (1)

696-702: Consider caching GetProperty result to avoid double call.

GetProperty(PROP_LENGTH) is called twice—once for the type check and once for value extraction. Storing the result in a local variable improves clarity and avoids redundant work.

♻️ Suggested refactor
   else if FOriginalFunction is TGocciaClassValue then
   begin
     OrigLength := 0;
-    if TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH) is TGocciaNumberLiteralValue then
-      OrigLength := Trunc(TGocciaNumberLiteralValue(
-        TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH)).Value);
+    LengthVal := TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH);
+    if LengthVal is TGocciaNumberLiteralValue then
+      OrigLength := Trunc(TGocciaNumberLiteralValue(LengthVal).Value);
   end

(Declare LengthVal: TGocciaValue at the top of the function.)

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

In `@source/units/Goccia.Values.FunctionBase.pas` around lines 696 - 702, Cache
the result of TGocciaClassValue(...).GetProperty(PROP_LENGTH) into a local
variable (e.g., LengthVal: TGocciaValue) instead of calling GetProperty twice:
first check "if LengthVal is TGocciaNumberLiteralValue" and then cast LengthVal
to TGocciaNumberLiteralValue to set OrigLength (keep OrigLength assignment logic
intact). Declare LengthVal at the top of the routine, replace both
GetProperty(PROP_LENGTH) calls with LengthVal, and ensure the type cast uses
LengthVal when extracting .Value.
🤖 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.ClassValue.pas`:
- Around line 809-812: GetProperty virtualizes the prototype but
ReplacePrototype (and the constructor Create code at ~918-927) only updates
FClassPrototype, leaving the PROP_PROTOTYPE descriptor out of sync with
own-property reads; modify TGocciaClassValue.ReplacePrototype and the
constructor initialization to also write a real own property entry into
FProperties under PROP_PROTOTYPE (create/update a property descriptor with the
new prototype value and appropriate attributes consistent with JS prototype
property semantics) so Object.getOwnPropertyDescriptor/hasOwnProperty reflect
the same value returned by GetProperty.

---

Outside diff comments:
In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 669-683: The AddAutoAccessor method ignores AIsStatic and always
installs accessors on FClassPrototype; update TGocciaClassValue.AddAutoAccessor
to choose the target object based on AIsStatic (use FClassPrototype when
AIsStatic = False and the class constructor object—e.g. FClassConstructor or
whatever field holds the constructor—when AIsStatic = True) and call
DefineProperty on that target with the existing GetterFn/SetterFn and
TGocciaPropertyDescriptorAccessor.Create(...). Keep creation of
GetterHelper/SetterHelper and GetterFn/SetterFn unchanged but branch on
AIsStatic before the DefineProperty call so static accessors are attached to the
constructor instead of the prototype.
- Around line 954-979: The fallback path in TGocciaClassValue.SetProperty
currently calls inherited DefineProperty with a descriptor that omits
pfEnumerable, making every runtime static write (e.g. C.x = 1) non-enumerable;
update the fallback to define the property as enumerable for ordinary userland
assignment by adding pfEnumerable to the TGocciaPropertyDescriptorData flags
(i.e. use [pfConfigurable, pfWritable, pfEnumerable]) so constructor-object
assignments become visible to Object.keys; keep this change localized to the
fallback path in TGocciaClassValue.SetProperty where inherited DefineProperty is
invoked.

---

Nitpick comments:
In `@source/units/Goccia.Values.FunctionBase.pas`:
- Around line 696-702: Cache the result of
TGocciaClassValue(...).GetProperty(PROP_LENGTH) into a local variable (e.g.,
LengthVal: TGocciaValue) instead of calling GetProperty twice: first check "if
LengthVal is TGocciaNumberLiteralValue" and then cast LengthVal to
TGocciaNumberLiteralValue to set OrigLength (keep OrigLength assignment logic
intact). Declare LengthVal at the top of the routine, replace both
GetProperty(PROP_LENGTH) calls with LengthVal, and ensure the type cast uses
LengthVal when extracting .Value.
🪄 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: 78c7dbd3-85ad-4c9a-8215-f71dbbec9573

📥 Commits

Reviewing files that changed from the base of the PR and between 98650d1 and e34c921.

📒 Files selected for processing (5)
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • tests/built-ins/Object/getPrototypeOf.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/units/Goccia.Compiler.Statements.pas

Comment thread source/units/Goccia.Values.ClassValue.pas
…lity

- ClassValue: override GetOwnPropertyDescriptor/HasOwnProperty for
  .prototype so Object.getOwnPropertyDescriptor reflects FClassPrototype
- ClassValue: AddAutoAccessor respects AIsStatic (static accessors go
  on the constructor, not prototype)
- ClassValue: SetProperty fallback uses inherited SetProperty (creates
  enumerable properties for runtime assignment C.x = val)
- ClassValue: .name property is writable for anonymous classes to allow
  bytecode name inference, non-writable for named classes
- CopyStaticMembersToConstructor: use DefineProperty to preserve
  non-enumerable flags from source descriptors
- FunctionBase: cache GetProperty(PROP_LENGTH) result in
  GetFunctionLength; add callable proxy support to DispatchCall and
  BoundFunction.Call via TGocciaProxyValue.ApplyTrap
- Add tests for callable proxy with call/apply/bind

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

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

696-723: ⚠️ Potential issue | 🟠 Major

Bound proxy metadata still collapses to 0 / "bound ".

Once bind() accepts proxy targets, GetFunctionLength and GetFunctionName also need to read length / name from FOriginalFunction as a normal callable object. Right now Function.prototype.bind.call(new Proxy((a, b) => {}, {}), null, 1) will report fallback metadata instead of preserving the proxied callable’s values.

Based on learnings, all values inherit from TGocciaValue; prefer VMT methods like GetProperty(Name) over type-gated branches for this kind of callable metadata lookup.

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

In `@source/units/Goccia.Values.FunctionBase.pas` around lines 696 - 723, Update
TGocciaBoundFunctionValue.GetFunctionLength and GetFunctionName to treat
FOriginalFunction as a generic TGocciaValue when it is not a TGocciaFunctionBase
or TGocciaClassValue: call FOriginalFunction.GetProperty(PROP_LENGTH) and, if
that returns a TGocciaNumberLiteralValue, use its Value (truncate) to compute
OrigLength; likewise call FOriginalFunction.GetProperty(PROP_NAME) and, if that
returns a TGocciaStringLiteralValue, use its Value to build the bound name. Keep
the existing special-case branches for TGocciaFunctionBase and TGocciaClassValue
but add the fallback that queries properties via GetProperty on
FOriginalFunction to preserve proxied callable metadata.
🧹 Nitpick comments (1)
source/units/Goccia.ObjectModel.Engine.pas (1)

67-73: Consider creating new descriptor objects for consistency with symbol handling.

The change correctly preserves non-enumerable flags for built-in static methods. However, the same Descriptor object is reused (line 69) while symbol properties (lines 82-92) create fresh descriptor objects. This inconsistency is harmless for immutable built-in method values, but for uniformity and defensive coding, consider creating a new descriptor:

♻️ Optional: Create fresh descriptor for consistency
     // Copy with original descriptor flags (built-in static methods are non-enumerable)
     if Descriptor is TGocciaPropertyDescriptorData then
-      TGocciaObjectValue(AConstructor).DefineProperty(Key, Descriptor)
+      TGocciaObjectValue(AConstructor).DefineProperty(Key,
+        TGocciaPropertyDescriptorData.Create(
+          TGocciaPropertyDescriptorData(Descriptor).Value,
+          Descriptor.Flags))
     else
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/units/Goccia.ObjectModel.Engine.pas` around lines 67 - 73, The code
currently passes the existing Descriptor object directly into
TGocciaObjectValue(AConstructor).DefineProperty for built-in static methods
(using TGocciaPropertyDescriptorData), while symbol handling creates new
descriptor instances; to make this consistent and defensive, construct a new
TGocciaPropertyDescriptorData (or copy) that preserves Descriptor's attributes
(value/getter/setter, configurable/writable/enumerable flags, etc.) and pass
that new descriptor to DefineProperty for the AConstructor/Key case instead of
reusing Descriptor.
🤖 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.FunctionBase.pas`:
- Around line 615-620: The current bound-proxy call logic incorrectly rejects
callable targets via explicit type checks (FOriginalFunction is
TGocciaFunctionBase / TGocciaClassValue) causing fast-paths
(CallNoArgs/Call1/Call2) to throw; instead, use the VMT predicate IsCallable to
allow any callable TGocciaValue to proceed and route non-TGocciaFunctionBase
targets through the generic Call(...) path so ApplyTrap is reached. Replace the
type-check gates around FOriginalFunction in the BoundFunction.Call overrides
(and the similar blocks at the other two locations) with checks like "if not
FOriginalFunction.IsCallable then raise ..." and, when FOriginalFunction is
callable but not a TGocciaFunctionBase, call
Exit(Call(TGocciaArgumentsCollection.Create, AThisValue)) (or the appropriate
generic Call(...) invocation) rather than raising; keep special-casing only for
actual TGocciaFunctionBase instances so fast paths remain valid.

---

Outside diff comments:
In `@source/units/Goccia.Values.FunctionBase.pas`:
- Around line 696-723: Update TGocciaBoundFunctionValue.GetFunctionLength and
GetFunctionName to treat FOriginalFunction as a generic TGocciaValue when it is
not a TGocciaFunctionBase or TGocciaClassValue: call
FOriginalFunction.GetProperty(PROP_LENGTH) and, if that returns a
TGocciaNumberLiteralValue, use its Value (truncate) to compute OrigLength;
likewise call FOriginalFunction.GetProperty(PROP_NAME) and, if that returns a
TGocciaStringLiteralValue, use its Value to build the bound name. Keep the
existing special-case branches for TGocciaFunctionBase and TGocciaClassValue but
add the fallback that queries properties via GetProperty on FOriginalFunction to
preserve proxied callable metadata.

---

Nitpick comments:
In `@source/units/Goccia.ObjectModel.Engine.pas`:
- Around line 67-73: The code currently passes the existing Descriptor object
directly into TGocciaObjectValue(AConstructor).DefineProperty for built-in
static methods (using TGocciaPropertyDescriptorData), while symbol handling
creates new descriptor instances; to make this consistent and defensive,
construct a new TGocciaPropertyDescriptorData (or copy) that preserves
Descriptor's attributes (value/getter/setter, configurable/writable/enumerable
flags, etc.) and pass that new descriptor to DefineProperty for the
AConstructor/Key case instead of reusing Descriptor.
🪄 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: 1e3413e4-e0ac-464c-a908-b0b3dfd153f6

📥 Commits

Reviewing files that changed from the base of the PR and between e34c921 and 84c9c82.

📒 Files selected for processing (4)
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • tests/built-ins/Proxy/apply.js

Comment thread source/units/Goccia.Values.FunctionBase.pas Outdated
@frostney frostney force-pushed the t3code/test262-feature-gaps branch from 84c9c82 to f93b352 Compare April 23, 2026 10:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.Builtins.GlobalObject.pas`:
- Around line 653-664: The symbol-key loop is assigning descriptor objects
directly (via Obj.AssignSymbolProperty using
TGocciaPropertyDescriptorData.Value) and bypasses ToPropertyDescriptor, causing
wrong values and ignoring accessors; change the loop that iterates SymbolKeys
(from PropertiesDescriptor.GetOwnSymbols and GetOwnSymbolPropertyDescriptor) to
call ObjectDefineProperty(Obj, SymbolKeys[I], SymbolDesc) for each assigned
SymbolDesc (only when Assigned(SymbolDesc)), mirroring the string-key path so
descriptor objects are converted via ToPropertyDescriptor and accessors are
handled correctly instead of using Obj.AssignSymbolProperty or special-casing
TGocciaPropertyDescriptorData.

In `@source/units/Goccia.Runtime.Bootstrap.pas`:
- Around line 465-467: After replacing the prototype with the shared object in
the TypedArray block, the shared prototype’s own "constructor" property is not
set, leaving %TypedArray%.prototype.constructor missing; after calling
FTypedArrayIntrinsic.ReplacePrototype(TGocciaTypedArrayValue.GetSharedPrototypeObject)
set the shared prototype object's own "constructor" property to the TypedArray
constructor function (the FTypedArrayIntrinsic function object) — i.e. get
TGocciaTypedArrayValue.GetSharedPrototypeObject and define its "constructor"
property to reference the FTypedArrayIntrinsic function value with the same
attributes used in the Function block so %TypedArray%.prototype.constructor
exists on the shared prototype.
🪄 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: 47b60a63-f9e5-4efe-8c3c-300894d64a6c

📥 Commits

Reviewing files that changed from the base of the PR and between 84c9c82 and f93b352.

📒 Files selected for processing (33)
  • scripts/run_test262_suite.py
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.VM.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • source/units/Goccia.Values.TypedArrayValue.pas
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Proxy/apply.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/TypedArray/constructors.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js
✅ Files skipped from review due to trivial changes (13)
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Map/prototype/get.js
  • tests/language/classes/method-enumerable.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/language/classes/basic-class-declaration.js
  • tests/built-ins/Array/prototype/map.js
  • source/units/Goccia.Values.ClassValue.pas
🚧 Files skipped from review as they are similar to previous changes (9)
  • tests/built-ins/Proxy/apply.js
  • tests/built-ins/Array/isArray.js
  • source/units/Goccia.SharedPrototype.pas
  • tests/built-ins/TypedArray/constructors.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Object/create.js
  • scripts/run_test262_suite.py
  • source/units/Goccia.Engine.pas

Comment thread source/units/Goccia.Builtins.GlobalObject.pas
Comment thread source/units/Goccia.Runtime.Bootstrap.pas
frostney and others added 4 commits April 23, 2026 12:28
…e-gaps

# Conflicts:
#	source/units/Goccia.AST.Statements.pas
…lity

- ClassValue: synthesize .name and .prototype descriptors in
  GetOwnPropertyDescriptor/HasOwnProperty overrides instead of storing
  in FProperties — avoids non-writable conflicts with static field
  overrides and bootstrap ReplacePrototype calls
- ClassValue: SetProperty handles .name via DefineProperty to override
  the synthesized non-writable descriptor (configurable allows this)
- ClassValue: SetInferredName simplified to just update FName
- ClassValue: AddAutoAccessor respects AIsStatic parameter
- CopyStaticMembersToConstructor: preserve descriptor flags from source
- Remove redundant OP_ARRAY_SET for class name inference (main's
  CompileClassExpression handles it via InferredName parameter)
- Fix duplicate SetInferredName declaration/implementation from rebase

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BoundFunction.CallNoArgs/CallOneArg/CallTwoArgs used explicit type
checks (TGocciaFunctionBase/TGocciaClassValue) that rejected callable
proxies. Replace with IsCallable guard and route non-TGocciaFunctionBase
targets through the generic Call path so ApplyTrap is reached.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney force-pushed the t3code/test262-feature-gaps branch from 860de19 to 2b43bf8 Compare April 23, 2026 11:34
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: 1

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

693-723: ⚠️ Potential issue | 🟡 Minor

Preserve bind metadata for callable proxies too.

bind now accepts proxy callables, but GetFunctionLength and GetFunctionName still special-case only TGocciaFunctionBase and TGocciaClassValue. A bound proxy therefore falls back to 0 and "bound " instead of reflecting the proxy-exposed length/name.

♻️ Possible fix
 function TGocciaBoundFunctionValue.GetFunctionLength: Integer;
 var
   OrigLength: Integer;
   LengthVal: TGocciaValue;
 begin
   // ECMAScript: bound function length = max(0, originalLength - boundArgs.length)
   if FOriginalFunction is TGocciaFunctionBase then
     OrigLength := TGocciaFunctionBase(FOriginalFunction).GetFunctionLength
-  else if FOriginalFunction is TGocciaClassValue then
+  else if FOriginalFunction.IsCallable then
   begin
     OrigLength := 0;
-    LengthVal := TGocciaClassValue(FOriginalFunction).GetProperty(PROP_LENGTH);
+    LengthVal := FOriginalFunction.GetProperty(PROP_LENGTH);
     if LengthVal is TGocciaNumberLiteralValue then
       OrigLength := Trunc(TGocciaNumberLiteralValue(LengthVal).Value);
   end
   else
     OrigLength := 0;
@@
-function TGocciaBoundFunctionValue.GetFunctionName: string;
+function TGocciaBoundFunctionValue.GetFunctionName: string;
+var
+  NameVal: TGocciaValue;
 begin
   // ECMAScript: bound function name = "bound " + originalName
   if FOriginalFunction is TGocciaFunctionBase then
     Result := 'bound ' + TGocciaFunctionBase(FOriginalFunction).GetFunctionName
-  else if FOriginalFunction is TGocciaClassValue then
-    Result := 'bound ' + TGocciaClassValue(FOriginalFunction).Name
+  else if FOriginalFunction.IsCallable then
+  begin
+    NameVal := FOriginalFunction.GetProperty(PROP_NAME);
+    if NameVal is TGocciaStringLiteralValue then
+      Result := 'bound ' + TGocciaStringLiteralValue(NameVal).Value
+    else
+      Result := 'bound ';
+  end
   else
     Result := 'bound ';
 end;
source/units/Goccia.Values.ClassValue.pas (2)

513-520: ⚠️ Potential issue | 🟠 Major

Install static accessors as real own properties.

AddStaticGetter and AddStaticSetter only populate the side maps, so C.x/C.x = ... work through the manual lookup path, but own-property reflection still cannot see the accessor. Object.getOwnPropertyDescriptor(C, 'x'), hasOwnProperty, and delete/redefine behavior will all be out of sync until the accessor descriptor is also defined on the constructor object itself. Mirroring AddGetter / AddSetter with DefineProperty on Self would fix that.

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

In `@source/units/Goccia.Values.ClassValue.pas` around lines 513 - 520,
AddStaticGetter and AddStaticSetter currently only add entries to
FStaticGetters/FStaticSetters causing static accessors to be invisible to
own-property reflection; mirror the pattern used by AddGetter/AddSetter by also
calling DefineProperty on Self to create an accessor descriptor for the property
name (using the same AGetter/ASetter and appropriate attributes) so
Object.getOwnPropertyDescriptor(Self, AName), hasOwnProperty and delete/redefine
behavior are correct; update TGocciaClassValue.AddStaticGetter and
AddStaticSetter to both populate the side maps (FStaticGetters/FStaticSetters)
and call DefineProperty(Self, AName, Descriptor) with the accessor descriptor
constructed from AGetter/ASetter.

955-961: ⚠️ Potential issue | 🟠 Major

The synthesized .name path is now out of sync with real descriptor semantics.

GetProperty returns FName before consulting any real own name descriptor, SetProperty redefines name via DefineProperty, and HasOwnProperty hard-codes True. That means cases like static name = 123, Object.defineProperty(C, 'name', ...), delete C.name, and plain C.name = ... no longer follow the built-in non-writable-but-configurable name property rules consistently. The FName fallback should only apply when no own name descriptor exists, and ordinary assignment should stay on the normal write path.

Also applies to: 1017-1025, 1032-1060

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

In `@source/units/Goccia.Values.ClassValue.pas` around lines 955 - 961,
GetProperty, SetProperty and HasOwnProperty are overriding real descriptor
semantics by always using FName/forcing an own name descriptor; update
GetProperty (check for an existing own property descriptor for PROP_NAME first,
returning that descriptor's value/attributes if present, and only fall back to
FName when no own descriptor exists), change SetProperty so it follows ordinary
write semantics (do not unconditionally call DefineProperty to redefine name;
instead perform normal assignment/update which respects
non-writable/configurable flags of any existing descriptor), and make
HasOwnProperty return whether an actual own descriptor for PROP_NAME exists
(don’t hard-code True). Keep references to FName and PROP_NAME and ensure
delete/define/assign paths now operate against real descriptors rather than
always binding FName.
♻️ Duplicate comments (1)
source/units/Goccia.Runtime.Bootstrap.pas (1)

465-467: ⚠️ Potential issue | 🟠 Major

Restore %TypedArray%.prototype.constructor after swapping in the shared prototype.

After Line 467, %TypedArray%.prototype points at the shared typed-array prototype, but this block never backfills that object's own constructor property. That leaves the shared %TypedArray% prototype incomplete even though the Function.prototype block below does this wiring explicitly.

🛠️ Minimal fix
     // Wire %TypedArray%.prototype to the shared prototype (which has all methods)
     if Assigned(TGocciaTypedArrayValue.GetSharedPrototypeObject) then
-      FTypedArrayIntrinsic.ReplacePrototype(TGocciaTypedArrayValue.GetSharedPrototypeObject);
+    begin
+      FTypedArrayIntrinsic.ReplacePrototype(TGocciaTypedArrayValue.GetSharedPrototypeObject);
+      FTypedArrayIntrinsic.Prototype.DefineProperty(PROP_CONSTRUCTOR,
+        TGocciaPropertyDescriptorData.Create(FTypedArrayIntrinsic, [pfConfigurable, pfWritable]));
+    end;

Based on learnings: TGocciaClassValue.prototype is backed by FClassPrototype, so this should be fixed by defining constructor on FTypedArrayIntrinsic.Prototype, not by materializing a separate .prototype descriptor.

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

In `@source/units/Goccia.Runtime.Bootstrap.pas` around lines 465 - 467, After
swapping in the shared typed-array prototype via
TGocciaTypedArrayValue.GetSharedPrototypeObject and
FTypedArrayIntrinsic.ReplacePrototype, restore the constructor reference on the
shared object by defining the "constructor" property on
FTypedArrayIntrinsic.Prototype to point to the typed-array class function (the
same value used by TGocciaClassValue.prototype / FClassPrototype wiring), rather
than creating a separate .prototype descriptor; in short, after calling
FTypedArrayIntrinsic.ReplacePrototype(...), set FTypedArrayIntrinsic.Prototype's
"constructor" to the appropriate class function so the shared prototype has a
correct constructor property.
🧹 Nitpick comments (2)
tests/built-ins/TypedArray/constructors.js (1)

306-319: Consider adding Float16Array to the prototype identity test.

The test at line 10 verifies Float16Array is defined, but it's not included in the shared [[Prototype]] identity check. For completeness:

   test("all typed array constructors share the same [[Prototype]]", () => {
     const TA = Object.getPrototypeOf(Int8Array);
     expect(Object.getPrototypeOf(Uint8Array)).toBe(TA);
     expect(Object.getPrototypeOf(Int16Array)).toBe(TA);
     expect(Object.getPrototypeOf(Uint16Array)).toBe(TA);
     expect(Object.getPrototypeOf(Int32Array)).toBe(TA);
     expect(Object.getPrototypeOf(Uint32Array)).toBe(TA);
+    expect(Object.getPrototypeOf(Float16Array)).toBe(TA);
     expect(Object.getPrototypeOf(Float32Array)).toBe(TA);
     expect(Object.getPrototypeOf(Float64Array)).toBe(TA);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/TypedArray/constructors.js` around lines 306 - 319, The
prototype-identity test "all typed array constructors share the same
[[Prototype]]" omits Float16Array; add an assertion to verify Float16Array
shares the same [[Prototype]] as the others by calling
Object.getPrototypeOf(Float16Array) and expecting it toBe(TA) (TA is the
variable assigned from Object.getPrototypeOf(Int8Array)); insert this new expect
alongside the other expect(...) lines in the same test block.
tests/built-ins/Error/error-constructor.js (1)

107-115: Consider adding AggregateError to the NativeError descriptor test.

The test covers TypeError, RangeError, ReferenceError, SyntaxError, and URIError, but omits AggregateError (and SuppressedError if supported). Since these also inherit from Error and have similar prototype descriptors, consider adding them for completeness.

 test("NativeError.prototype.name is non-enumerable", () => {
-  const types = [TypeError, RangeError, ReferenceError, SyntaxError, URIError];
+  const types = [TypeError, RangeError, ReferenceError, SyntaxError, URIError, AggregateError];
   types.forEach((ErrorType) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Error/error-constructor.js` around lines 107 - 115, The test
"NativeError.prototype.name is non-enumerable" currently checks descriptor
properties for TypeError, RangeError, ReferenceError, SyntaxError, and URIError;
update the types array in that test to also include AggregateError (and
optionally SuppressedError if the environment supports it) so the descriptor
assertions (obtained via Object.getOwnPropertyDescriptor(ErrorType.prototype,
"name")) run for those native error classes as well. Ensure you only add the
class names to the existing types array used in the forEach and keep the same
assertions (enumerable false, writable true, configurable true).
🤖 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.ObjectModel.Engine.pas`:
- Around line 67-69: The copy loop currently reuses the original Descriptor
instance when copying static string properties (the branch that calls
TGocciaObjectValue(AConstructor).DefineProperty(Key, Descriptor)), which can
couple state between ASource and the new object; instead, create/clone a new
TGocciaPropertyDescriptorData instance populated from Descriptor before calling
TGocciaObjectValue(AConstructor).DefineProperty so the target gets an
independent descriptor (mirror how the symbol branch clones descriptors),
ensuring ownership/lifetime safety for TGocciaPropertyDescriptorData.

---

Outside diff comments:
In `@source/units/Goccia.Values.ClassValue.pas`:
- Around line 513-520: AddStaticGetter and AddStaticSetter currently only add
entries to FStaticGetters/FStaticSetters causing static accessors to be
invisible to own-property reflection; mirror the pattern used by
AddGetter/AddSetter by also calling DefineProperty on Self to create an accessor
descriptor for the property name (using the same AGetter/ASetter and appropriate
attributes) so Object.getOwnPropertyDescriptor(Self, AName), hasOwnProperty and
delete/redefine behavior are correct; update TGocciaClassValue.AddStaticGetter
and AddStaticSetter to both populate the side maps
(FStaticGetters/FStaticSetters) and call DefineProperty(Self, AName, Descriptor)
with the accessor descriptor constructed from AGetter/ASetter.
- Around line 955-961: GetProperty, SetProperty and HasOwnProperty are
overriding real descriptor semantics by always using FName/forcing an own name
descriptor; update GetProperty (check for an existing own property descriptor
for PROP_NAME first, returning that descriptor's value/attributes if present,
and only fall back to FName when no own descriptor exists), change SetProperty
so it follows ordinary write semantics (do not unconditionally call
DefineProperty to redefine name; instead perform normal assignment/update which
respects non-writable/configurable flags of any existing descriptor), and make
HasOwnProperty return whether an actual own descriptor for PROP_NAME exists
(don’t hard-code True). Keep references to FName and PROP_NAME and ensure
delete/define/assign paths now operate against real descriptors rather than
always binding FName.

---

Duplicate comments:
In `@source/units/Goccia.Runtime.Bootstrap.pas`:
- Around line 465-467: After swapping in the shared typed-array prototype via
TGocciaTypedArrayValue.GetSharedPrototypeObject and
FTypedArrayIntrinsic.ReplacePrototype, restore the constructor reference on the
shared object by defining the "constructor" property on
FTypedArrayIntrinsic.Prototype to point to the typed-array class function (the
same value used by TGocciaClassValue.prototype / FClassPrototype wiring), rather
than creating a separate .prototype descriptor; in short, after calling
FTypedArrayIntrinsic.ReplacePrototype(...), set FTypedArrayIntrinsic.Prototype's
"constructor" to the appropriate class function so the shared prototype has a
correct constructor property.

---

Nitpick comments:
In `@tests/built-ins/Error/error-constructor.js`:
- Around line 107-115: The test "NativeError.prototype.name is non-enumerable"
currently checks descriptor properties for TypeError, RangeError,
ReferenceError, SyntaxError, and URIError; update the types array in that test
to also include AggregateError (and optionally SuppressedError if the
environment supports it) so the descriptor assertions (obtained via
Object.getOwnPropertyDescriptor(ErrorType.prototype, "name")) run for those
native error classes as well. Ensure you only add the class names to the
existing types array used in the forEach and keep the same assertions
(enumerable false, writable true, configurable true).

In `@tests/built-ins/TypedArray/constructors.js`:
- Around line 306-319: The prototype-identity test "all typed array constructors
share the same [[Prototype]]" omits Float16Array; add an assertion to verify
Float16Array shares the same [[Prototype]] as the others by calling
Object.getPrototypeOf(Float16Array) and expecting it toBe(TA) (TA is the
variable assigned from Object.getPrototypeOf(Int8Array)); insert this new expect
alongside the other expect(...) lines in the same test block.
🪄 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: a7c05717-304c-493d-ae66-ab00865ff544

📥 Commits

Reviewing files that changed from the base of the PR and between f93b352 and 2b43bf8.

📒 Files selected for processing (33)
  • scripts/run_test262_suite.py
  • scripts/test262_syntax_filter.py
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Builtins.Globals.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.VM.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.ClassValue.pas
  • source/units/Goccia.Values.FunctionBase.pas
  • source/units/Goccia.Values.TypedArrayValue.pas
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Error/error-constructor.js
  • tests/built-ins/Function/prototype/apply.js
  • tests/built-ins/Function/prototype/bind.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Proxy/apply.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/TypedArray/constructors.js
  • tests/language/classes/basic-class-declaration.js
  • tests/language/classes/method-enumerable.js
✅ Files skipped from review due to trivial changes (11)
  • tests/built-ins/Map/prototype/get.js
  • tests/built-ins/Set/constructor.js
  • tests/built-ins/Set/prototype/has.js
  • tests/built-ins/Array/isArray.js
  • tests/built-ins/Object/keys.js
  • tests/language/classes/method-enumerable.js
  • tests/built-ins/Function/prototype/constructor.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Function/prototype/call.js
  • tests/built-ins/Function/prototype/bind.js
  • source/units/Goccia.Values.TypedArrayValue.pas
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/built-ins/Array/prototype/map.js
  • tests/built-ins/Map/constructor.js
  • tests/built-ins/Proxy/apply.js
  • source/units/Goccia.Values.ArrayValue.pas
  • tests/language/classes/basic-class-declaration.js
  • tests/built-ins/Object/create.js
  • scripts/run_test262_suite.py

Comment thread source/units/Goccia.ObjectModel.Engine.pas Outdated
frostney and others added 2 commits April 23, 2026 13:01
CopyStaticMembersToConstructor was sharing TGocciaPropertyDescriptorData
instances between the source builtin object and the target class
constructor. When the GC swept the source during collection, its
Destroy freed the shared descriptors, leaving dangling pointers in the
constructor's FProperties. The next GC mark cycle hit use-after-free
via MarkValues on the freed descriptors, causing EInvalidCast.

Fix: clone descriptors with TGocciaPropertyDescriptorData.Create using
the original value and flags, so each object owns its own descriptor
instance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ObjectDefineProperties: route symbol-keyed descriptors through
  ObjectDefineProperty for proper ToPropertyDescriptor handling
- Set %TypedArray%.prototype.constructor to %TypedArray% intrinsic
  per ES §23.2.3 (was missing, defaulted to Object)
- Add test: Object.create with symbol-keyed property descriptor
- Add test: instance auto-accessor on prototype not constructor
- Add test: runtime class property assignment is enumerable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread source/units/Goccia.Builtins.GlobalObject.pas
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: 1

🤖 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.Builtins.GlobalObject.pas`:
- Around line 627-648: The loop currently skips symbol-keyed entries when the
descriptor in PropertiesDescriptor is an accessor (the code only handles
TGocciaPropertyDescriptorData), but per spec we must invoke the property's
getter (Get(props, key)) to obtain the descriptor object; modify the loop that
iterates SymbolKeys so that for non-data symbol descriptors you call
PropertiesDescriptor.Get(SymbolKeys[I]) (or the equivalent getter API) to
retrieve the descriptor value, then pass that value as the third argument to
ObjectDefineProperty (same as TGocciaPropertyDescriptorData.Value is passed
now), preserving the Enumerable check and exception behavior; reference Symbols:
PropertiesDescriptor.GetOwnSymbols,
PropertiesDescriptor.GetOwnSymbolPropertyDescriptor,
TGocciaPropertyDescriptorData, ObjectDefineProperty, and CallArgs. Ensure you
free CallArgs and handle nil/non-object returns consistently
(ObjectDefineProperty will perform ToPropertyDescriptor), and catch/propagate
any exceptions from the getter as before.
🪄 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: d280870e-4061-467f-bc06-ede4e1a0d063

📥 Commits

Reviewing files that changed from the base of the PR and between 2b43bf8 and bd65d7d.

📒 Files selected for processing (6)
  • source/units/Goccia.Builtins.GlobalObject.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.ObjectModel.Engine.pas
  • source/units/Goccia.Runtime.Bootstrap.pas
  • tests/built-ins/Object/create.js
  • tests/language/classes/basic-class-declaration.js
✅ Files skipped from review due to trivial changes (1)
  • tests/built-ins/Object/create.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/units/Goccia.Engine.pas

Comment thread source/units/Goccia.Builtins.GlobalObject.pas
Use GetSymbolProperty (Get semantics) instead of reading
TGocciaPropertyDescriptorData.Value directly, so accessor descriptors
on the properties object invoke their getter to obtain the descriptor
value before passing to ObjectDefineProperty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney merged commit 5f17a1a into main Apr 23, 2026
10 checks passed
@frostney frostney deleted the t3code/test262-feature-gaps branch April 23, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant