Skip to content

Add logical assignment operators#259

Merged
frostney merged 2 commits intomainfrom
t3code/logical-assignment-operators
Apr 10, 2026
Merged

Add logical assignment operators#259
frostney merged 2 commits intomainfrom
t3code/logical-assignment-operators

Conversation

@frostney
Copy link
Copy Markdown
Owner

Summary

  • Adds support for logical assignment operators &&= and ||= across lexer, parser, evaluator, and bytecode compiler.
  • Preserves short-circuit behavior for identifiers, properties, computed properties, and private fields.
  • Adds end-to-end language coverage for truthy/falsy cases, short-circuiting, unresolved identifiers, and const binding behavior.
  • Updates README and language restrictions docs to reflect the expanded operator set.

Testing

  • Not run.
  • New JS coverage added under tests/language/expressions/logical/ for &&= and ||=.
  • Compiler and evaluator paths updated to use short-circuit jumps for both operators.

- Parse, compile, and evaluate `&&=` and `||=`
- Add end-to-end tests for assignments, short-circuiting, and errors
- Update docs to list logical assignment support
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds ES logical assignment operators &&= and ||= across lexer, parser, AST evaluator, compiler, and tests, implementing short-circuit semantics and bytecode generation updates for these operators.

Changes

Cohort / File(s) Summary
Documentation
README.md, docs/language-restrictions.md
Documented support for logical assignment operators &&= and `
Lexer & Tokens
units/Goccia.Token.pas, units/Goccia.Lexer.pas
Added gttLogicalAndAssign and gttLogicalOrAssign token kinds; lexer now distinguishes &&/`
Parser
units/Goccia.Parser.pas
Recognizes the new logical-assignment tokens in assignment parsing and in type-annotation/Comparison token collection.
Compiler Context
units/Goccia.Compiler.Context.pas
Added IsShortCircuitAssignment and ShortCircuitJumpOp helpers to identify short-circuit assignments and map them to appropriate jump opcodes.
Compiler Expressions
units/Goccia.Compiler.Expressions.pas
Compound-assignment compilation updated to use short-circuit helpers and emit the condition-specific jump opcode (replacing hardcoded nullish jump). Applies to locals, upvalues, globals, and property variants.
AST Evaluation
units/Goccia.AST.Expressions.pas
Compound-assignment evaluation updated to treat &&= and `
Runtime Evaluator
units/Goccia.Evaluator.pas
Private-property and instance compound-assignment evaluation updated for ??=, &&=, `
Tests
tests/language/expressions/logical/logical-and-assignment.js, tests/language/expressions/logical/logical-or-assignment.js
Added comprehensive test suites for &&= and `

Sequence Diagram

sequenceDiagram
    autonumber
    participant Source as Source Code
    participant Lexer
    participant Parser
    participant Compiler
    participant VM as Runtime/Evaluator

    Source->>Lexer: Tokenize `a &&= b`
    Lexer->>Lexer: match '&&', check for '='
    Lexer->>Parser: emit `gttLogicalAndAssign`
    
    Parser->>Parser: parse Assignment -> CompoundAssignment AST
    Parser->>Compiler: send AST

    Compiler->>Compiler: IsShortCircuitAssignment(op)
    Compiler->>Compiler: ShortCircuitJumpOp(op)
    Compiler->>Compiler: emit LOAD LHS
    Compiler->>Compiler: emit JUMP_IF_FALSE (patchable)
    Compiler->>Compiler: compile RHS
    Compiler->>Compiler: emit ASSIGN target
    Compiler->>VM: load bytecode

    VM->>VM: evaluate LHS
    VM->>VM: ToBoolean(LHS)
    alt LHS falsy
        VM->>VM: short-circuit return LHS
    else LHS truthy
        VM->>VM: evaluate RHS
        VM->>VM: assign RHS to target
        VM->>VM: return assigned value
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the main implementation details, testing approach, and documentation updates, but the Testing section indicates tests were not run, which is incomplete. Confirm whether end-to-end tests were actually executed to verify the feature works correctly, or clarify the testing status in the description.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the primary change: adding support for logical assignment operators &&= and ||=.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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

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

github-actions Bot commented Apr 10, 2026

Benchmark Results

306 benchmarks

Interpreted: 🟢 155 improved · 🔴 88 regressed · 63 unchanged · avg +2.4%
Bytecode: 🟢 287 improved · 🔴 1 regressed · 18 unchanged · avg +10.0%

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 10, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 4296 4296
Tests Passed 4255 ✅ 4296 ✅
Tests Skipped 41 0
Tests Test Duration 312.4ms 299.0ms
Tests Lex 82.6ms 57.8ms
Tests Parse 105.8ms 108.8ms
Tests Compile 69.1ms
Tests Execute 328.6ms 330.1ms
Tests Engine Total 517.0ms 565.8ms
Benchmarks Total 306 306
Benchmarks Duration 8.26min 7.20min

Measured on ubuntu-latest x64.

@coderabbitai coderabbitai Bot added the new feature New feature or request label Apr 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
units/Goccia.Evaluator.pas (1)

2684-2699: ⚠️ Potential issue | 🟠 Major

Preserve getter-only private accessor errors in the short-circuit write path.

When ??=, &&=, or ||= actually needs to assign to an instance private member, this branch skips the HasPrivateGetter check that still exists in the normal compound-assignment path at Lines 2728-2729. A getter-only private accessor will therefore be written via SetPrivateProperty(...) instead of throwing the expected TypeError.

💡 Proposed fix
     if ObjectValue is TGocciaInstanceValue then
     begin
       if AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName) then
       begin
         SetterFn := AccessClass.PrivatePropertySetter[APrivatePropertyCompoundAssignmentExpression.PrivateName];
         SetterArgs := TGocciaArgumentsCollection.Create;
         try
           SetterArgs.Add(Value);
           SetterFn.Call(SetterArgs, Instance);
         finally
           SetterArgs.Free;
         end;
       end
+      else if AccessClass.HasPrivateGetter(APrivatePropertyCompoundAssignmentExpression.PrivateName) then
+        ThrowPrivateSetterMissingError(APrivatePropertyCompoundAssignmentExpression.PrivateName)
       else
         Instance.SetPrivateProperty(APrivatePropertyCompoundAssignmentExpression.PrivateName, Result, AccessClass);
     end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Evaluator.pas` around lines 2684 - 2699, The short-circuit write
path handling TGocciaInstanceValue skips the HasPrivateGetter check and can call
Instance.SetPrivateProperty for getter-only private accessors; update the branch
that currently tests
AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName)
(and then either calls SetterFn.Call or Instance.SetPrivateProperty) to first
check AccessClass.HasPrivateGetter for the same PrivateName and, if a
getter-only accessor exists (HasPrivateGetter true and HasPrivateSetter false),
raise the same TypeError the normal compound-assignment path uses; only call the
setter or Instance.SetPrivateProperty when the getter check permits writing.
🤖 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 `@units/Goccia.Evaluator.pas`:
- Around line 2684-2699: The short-circuit write path handling
TGocciaInstanceValue skips the HasPrivateGetter check and can call
Instance.SetPrivateProperty for getter-only private accessors; update the branch
that currently tests
AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName)
(and then either calls SetterFn.Call or Instance.SetPrivateProperty) to first
check AccessClass.HasPrivateGetter for the same PrivateName and, if a
getter-only accessor exists (HasPrivateGetter true and HasPrivateSetter false),
raise the same TypeError the normal compound-assignment path uses; only call the
setter or Instance.SetPrivateProperty when the getter check permits writing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9f6747f-7ace-4a15-8826-59f2db2ccb11

📥 Commits

Reviewing files that changed from the base of the PR and between 010f8ec and 115f5df.

📒 Files selected for processing (11)
  • README.md
  • docs/language-restrictions.md
  • tests/language/expressions/logical/logical-and-assignment.js
  • tests/language/expressions/logical/logical-or-assignment.js
  • units/Goccia.AST.Expressions.pas
  • units/Goccia.Compiler.Context.pas
  • units/Goccia.Compiler.Expressions.pas
  • units/Goccia.Evaluator.pas
  • units/Goccia.Lexer.pas
  • units/Goccia.Parser.pas
  • units/Goccia.Token.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)
units/Goccia.Evaluator.pas (1)

2684-2699: ⚠️ Potential issue | 🟠 Major

Preserve getter-only private accessor errors in the short-circuit write path.

When #x ||= ... / #x &&= ... / #x ??= ... actually needs to assign on an instance, this branch skips the getter-only check and falls through to Instance.SetPrivateProperty(...). That diverges from both EvaluatePrivatePropertyAssignment (Lines 2581-2598) and the non-short-circuit compound path (Lines 2716-2729), which correctly throw ThrowPrivateSetterMissingError for getter-only private accessors.

💡 Proposed fix
     if ObjectValue is TGocciaInstanceValue then
     begin
       if AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName) then
       begin
         SetterFn := AccessClass.PrivatePropertySetter[APrivatePropertyCompoundAssignmentExpression.PrivateName];
         SetterArgs := TGocciaArgumentsCollection.Create;
         try
           SetterArgs.Add(Value);
           SetterFn.Call(SetterArgs, Instance);
         finally
           SetterArgs.Free;
         end;
       end
+      else if AccessClass.HasPrivateGetter(APrivatePropertyCompoundAssignmentExpression.PrivateName) then
+        ThrowPrivateSetterMissingError(
+          APrivatePropertyCompoundAssignmentExpression.PrivateName)
       else
         Instance.SetPrivateProperty(APrivatePropertyCompoundAssignmentExpression.PrivateName, Result, AccessClass);
     end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Evaluator.pas` around lines 2684 - 2699, The short-circuit
instance assignment path bypasses the getter-only check and directly calls
Instance.SetPrivateProperty, so replicate the same checks as
EvaluatePrivatePropertyAssignment: when ObjectValue is TGocciaInstanceValue,
first test
AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName)
and call the private setter (SetterFn.Call) if present; otherwise if the
property has a private getter but no setter (e.g., AccessClass.HasPrivateGetter
for APrivatePropertyCompoundAssignmentExpression.PrivateName) then invoke
ThrowPrivateSetterMissingError with the PrivateName to preserve the getter-only
error; only when neither private getter/setter metadata indicates an accessor
should you fall back to Instance.SetPrivateProperty. Ensure you reference
TGocciaInstanceValue, AccessClass.HasPrivateSetter/HasPrivateGetter,
APrivatePropertyCompoundAssignmentExpression.PrivateName, SetterFn,
Instance.SetPrivateProperty and ThrowPrivateSetterMissingError so the logic
mirrors EvaluatePrivatePropertyAssignment and the non-short-circuit compound
path.
🤖 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 `@units/Goccia.Evaluator.pas`:
- Around line 2684-2699: The short-circuit instance assignment path bypasses the
getter-only check and directly calls Instance.SetPrivateProperty, so replicate
the same checks as EvaluatePrivatePropertyAssignment: when ObjectValue is
TGocciaInstanceValue, first test
AccessClass.HasPrivateSetter(APrivatePropertyCompoundAssignmentExpression.PrivateName)
and call the private setter (SetterFn.Call) if present; otherwise if the
property has a private getter but no setter (e.g., AccessClass.HasPrivateGetter
for APrivatePropertyCompoundAssignmentExpression.PrivateName) then invoke
ThrowPrivateSetterMissingError with the PrivateName to preserve the getter-only
error; only when neither private getter/setter metadata indicates an accessor
should you fall back to Instance.SetPrivateProperty. Ensure you reference
TGocciaInstanceValue, AccessClass.HasPrivateSetter/HasPrivateGetter,
APrivatePropertyCompoundAssignmentExpression.PrivateName, SetterFn,
Instance.SetPrivateProperty and ThrowPrivateSetterMissingError so the logic
mirrors EvaluatePrivatePropertyAssignment and the non-short-circuit compound
path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56b25227-f4d5-49b3-b352-385fce1153c9

📥 Commits

Reviewing files that changed from the base of the PR and between 010f8ec and 115f5df.

📒 Files selected for processing (11)
  • README.md
  • docs/language-restrictions.md
  • tests/language/expressions/logical/logical-and-assignment.js
  • tests/language/expressions/logical/logical-or-assignment.js
  • units/Goccia.AST.Expressions.pas
  • units/Goccia.Compiler.Context.pas
  • units/Goccia.Compiler.Expressions.pas
  • units/Goccia.Evaluator.pas
  • units/Goccia.Lexer.pas
  • units/Goccia.Parser.pas
  • units/Goccia.Token.pas

- Throw when `&&=`/`||=` target a private accessor without a setter
- Add regression coverage for short-circuit and TypeError cases
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)
units/Goccia.Evaluator.pas (1)

2655-2705: ⚠️ Potential issue | 🟠 Major

Temp-root the private receiver before evaluating the RHS.

Line 2681 can allocate or trigger GC while the target instance/class is only kept in ObjectValue / Instance / ClassValue locals here. A temporary receiver can be reclaimed before the later SetPrivateProperty / AssignPrivateMemberOnClass write-back runs.

Please guard the full read/short-circuit/write window with AddTempRoot / RemoveTempRoot for the receiver, ideally once for the whole function since the non-short-circuit path below has the same lifetime shape.

As per coding guidelines, "Values held only by Pascal code (not in any GocciaScript scope) must be protected with AddTempRoot/RemoveTempRoot".

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

In `@units/Goccia.Evaluator.pas` around lines 2655 - 2705, The code must protect
the receiver across the evaluate/short-circuit/write window: wrap the receiver
value(s) (ObjectValue / Instance / ClassValue) with AddTempRoot before calling
EvaluateExpression and other GC-triggering ops and RemoveTempRoot after the
eventual write-back (SetPrivateProperty or AssignPrivateMemberOnClass); ideally
call AddTempRoot once at the start of the function handling
APrivatePropertyCompoundAssignmentExpression and RemoveTempRoot at all exit
points (including short-circuit returns) so the receiver cannot be reclaimed
while running EvaluateExpression, calling private setters
(PrivatePropertySetter.Call) or performing Instance.SetPrivateProperty /
AssignPrivateMemberOnClass.
🤖 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 `@units/Goccia.Evaluator.pas`:
- Around line 2655-2705: The code must protect the receiver across the
evaluate/short-circuit/write window: wrap the receiver value(s) (ObjectValue /
Instance / ClassValue) with AddTempRoot before calling EvaluateExpression and
other GC-triggering ops and RemoveTempRoot after the eventual write-back
(SetPrivateProperty or AssignPrivateMemberOnClass); ideally call AddTempRoot
once at the start of the function handling
APrivatePropertyCompoundAssignmentExpression and RemoveTempRoot at all exit
points (including short-circuit returns) so the receiver cannot be reclaimed
while running EvaluateExpression, calling private setters
(PrivatePropertySetter.Call) or performing Instance.SetPrivateProperty /
AssignPrivateMemberOnClass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 201c5bea-66cb-49ce-9264-864b9bb3a452

📥 Commits

Reviewing files that changed from the base of the PR and between 115f5df and d56b039.

📒 Files selected for processing (3)
  • tests/language/expressions/logical/logical-and-assignment.js
  • tests/language/expressions/logical/logical-or-assignment.js
  • units/Goccia.Evaluator.pas
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/language/expressions/logical/logical-or-assignment.js
  • tests/language/expressions/logical/logical-and-assignment.js

@frostney frostney merged commit 259e26b into main Apr 10, 2026
9 checks passed
@frostney frostney deleted the t3code/logical-assignment-operators branch April 10, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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