Skip to content

Refactor built-in object model registration#136

Merged
frostney merged 4 commits intomainfrom
refactor/unified-object-model
Mar 27, 2026
Merged

Refactor built-in object model registration#136
frostney merged 4 commits intomainfrom
refactor/unified-object-model

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 27, 2026

Summary

  • add a shared object-model definition and engine registration layer for built-in constructors, prototypes, statics, and accessors
  • migrate the built-in and wrapper registration paths onto , including RTTI-backed method/property helpers
  • replace the remaining raw construction sites with and update the docs to match

Summary by CodeRabbit

  • Refactor

    • Centralized registration for built-in constructors, prototypes and static members via a new object-model registration API; many prototypes now use cached member-definition tables and adjusted visibility for prototype/constructor members. No public API or runtime behavior changes.
  • Documentation

    • Architecture and style docs updated to prefer the new object-model helpers for prototype/static setup; GC guidance adjusted to reference shared member-definition tables while keeping existing callback requirements.
  • Tests

    • Added tests validating declared arities/lengths for several built-ins.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fb1e085-cf1e-49f0-bb21-7a194c7155ce

📥 Commits

Reviewing files that changed from the base of the PR and between a5068ff and ef64cfb.

📒 Files selected for processing (2)
  • tests/built-ins/Array/prototype/shift-unshift.js
  • units/Goccia.Values.ArrayValue.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • units/Goccia.Values.ArrayValue.pas

📝 Walkthrough

Walkthrough

Adds a new object-model registration framework (units: Goccia.ObjectModel.Types, Goccia.ObjectModel, Goccia.ObjectModel.Engine) and migrates many built-ins and value types to bulk member-definition registration and centralized type-definition wiring. Documentation and tests updated to prefer the new APIs and validate declared arities.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, docs/adding-built-in-types.md, docs/architecture.md, docs/code-style.md
Docs updated to prefer Goccia.ObjectModel member-definition tables and RegisterTypeDefinition/RegisterMemberDefinitions over ad-hoc RegisterNativeMethod/DefineProperty; examples and prototype/constructor wiring descriptions adjusted.
Object-model framework (new units)
units/Goccia.ObjectModel.Types.pas, units/Goccia.ObjectModel.pas, units/Goccia.ObjectModel.Engine.pas
Added member-definition types/enums, TGocciaMemberCollection builder and helpers, RTTI-hosted getters, validation, RegisterMemberDefinitions, property copying, ExposeSharedPrototypeOnConstructor, and TGocciaTypeDefinition + RegisterTypeDefinition.
Engine
units/Goccia.Engine.pas
Refactored RegisterBuiltinConstructors to use TGocciaTypeDefinition + RegisterTypeDefinition; added prototype-provider/expose helpers and removed manual prototype/constructor wiring and static-copy loops.
Built-ins (static members)
units/Goccia.Builtins.* (Console, GlobalArray, GlobalArrayBuffer, GlobalNumber, GlobalObject, GlobalPromise, GlobalString, GlobalSymbol, Globals, JSON, Math, Performance, Temporal)
Replaced per-method inline registrations with TGocciaMemberCollectionToDefinitionsRegisterMemberDefinitions; added cached FStaticMembers; moved many method declarations to published; adjusted unit uses.
Values / prototypes
units/Goccia.Values.* (Array*, ArrayBuffer*, BooleanObjectValue, NumberObjectValue, StringObjectValue, SymbolValue, EnumValue, ObjectValue, PromiseValue, IteratorValue, FunctionBase, MapValue, SetValue, TypedArrayValue, SharedArrayBufferValue, Temporal* files, etc.)
Prototype initialization now uses cached FPrototypeMembers built by TGocciaMemberCollection and registered via RegisterMemberDefinitions; prototype exposure uses ExposeSharedPrototypeOnConstructor; many prototype methods moved to published; removed direct native-function wrappers.
Tests
tests/built-ins/* (Array/constructor, Array/prototype/, Number/, Object/*)
Added tests asserting function/property arities and shared-prototype data property presence to match migrated registrations.
Misc / config
units/Goccia.inc, various units
Enabled {$M+} unconditionally; updated many uses lists to include Goccia.ObjectModel / Goccia.ObjectModel.Types and removed now-unused Goccia.Values.NativeFunction imports.

Sequence Diagram(s)

sequenceDiagram
    participant Engine as TGocciaEngine
    participant Registrar as RegisterTypeDefinition
    participant Scope as TGocciaScope
    participant Constructor as TGocciaClassValue
    participant Prototype as PrototypeProvider / SharedPrototype
    participant StaticSrc as StaticSource (TGocciaObjectValue)

    Engine->>Registrar: Build TGocciaTypeDefinition (name, kind, ClassValueClass, PrototypeProvider/ExposePrototype, StaticSource, PrototypeParent, AddSpeciesGetter)
    Registrar->>Scope: Create TGocciaClassValue in Scope
    alt ExposePrototype provided
        Registrar->>Prototype: Call ExposePrototype(Constructor)
    else PrototypeProvider provided
        Registrar->>Prototype: Prototype := PrototypeProvider()
        Registrar->>Constructor: Set prototype from Prototype
    end
    Registrar->>Constructor: Optionally set PrototypeParent
    alt StaticSource provided
        Registrar->>StaticSrc: CopyOwnProperties(StaticSource -> Constructor)
    end
    alt AddSpeciesGetter
        Registrar->>Constructor: Define [Symbol.species] accessor
    end
    Registrar->>Scope: Register Constructor as lexical const (ConstructorName)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hop through code with whiskers bright,
I gather members, stack them right.
One collection now binds scattered song,
Constructors hum and prototypes long.
RTTI carrots, functions in a row — hop, deploy, delight! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Refactor built-in object model registration' directly and clearly summarizes the main change: refactoring how built-in objects register their members using a unified object-model layer.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/unified-object-model

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 27, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3521 3521
Tests Passed 3480 ✅ 3521 ✅
Tests Skipped 41 0
Tests Execution 160.2ms 167.3ms
Tests Engine 314.3ms 672.6ms
Benchmarks Total 263 263
Benchmarks Duration 7.12min 12.68min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 27, 2026

Benchmark Results

263 benchmarks

Interpreted: 🟢 5 improved · 258 unchanged · avg +0.6%
Bytecode: 🟢 3 improved · 🔴 12 regressed · 248 unchanged · avg -0.3%

arraybuffer.js — Interp: 🟢 1, 13 unch. · avg +2.2% · Bytecode: 14 unch. · avg +1.5%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 458,863 → 475,223 +3.6% 162,091 → 162,550 +0.3%
create ArrayBuffer(64) 447,784 → 464,186 +3.7% 161,094 → 161,438 +0.2%
create ArrayBuffer(1024) 337,342 → 353,638 +4.8% 143,218 → 145,436 +1.5%
create ArrayBuffer(8192) 143,176 → 155,657 🟢 +8.7% 84,718 → 89,824 +6.0%
slice full buffer (64 bytes) 526,609 → 531,917 +1.0% 393,011 → 393,484 +0.1%
slice half buffer (512 of 1024 bytes) 448,763 → 458,142 +2.1% 345,544 → 358,057 +3.6%
slice with negative indices 458,711 → 461,115 +0.5% 372,763 → 383,534 +2.9%
slice empty range 519,347 → 524,172 +0.9% 388,655 → 395,659 +1.8%
byteLength access 1,625,275 → 1,666,511 +2.5% 1,194,252 → 1,193,506 -0.1%
Symbol.toStringTag access 1,217,596 → 1,236,133 +1.5% 685,600 → 679,818 -0.8%
ArrayBuffer.isView 820,635 → 830,187 +1.2% 570,568 → 562,983 -1.3%
clone ArrayBuffer(64) 433,865 → 433,821 -0.0% 393,773 → 404,222 +2.7%
clone ArrayBuffer(1024) 328,966 → 336,226 +2.2% 288,792 → 295,715 +2.4%
clone ArrayBuffer inside object 274,207 → 268,896 -1.9% 194,189 → 196,367 +1.1%
arrays.js — Interp: 19 unch. · avg +1.3% · Bytecode: 🟢 1, 18 unch. · avg +3.2%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 14,020 → 14,390 +2.6% 14,364 → 14,590 +1.6%
Array.from 10 elements 235,898 → 249,823 +5.9% 181,510 → 182,354 +0.5%
Array.of 10 elements 331,264 → 326,659 -1.4% 251,016 → 253,595 +1.0%
spread into new array 340,184 → 357,888 +5.2% 854,552 → 880,132 +3.0%
map over 50 elements 29,018 → 29,567 +1.9% 24,101 → 25,480 +5.7%
filter over 50 elements 24,110 → 24,322 +0.9% 23,308 → 24,988 🟢 +7.2%
reduce sum 50 elements 28,181 → 28,334 +0.5% 20,932 → 21,367 +2.1%
forEach over 50 elements 24,384 → 24,191 -0.8% 26,288 → 27,853 +6.0%
find in 50 elements 36,097 → 36,345 +0.7% 30,896 → 32,413 +4.9%
sort 20 elements 12,664 → 12,867 +1.6% 12,485 → 12,681 +1.6%
flat nested array 118,559 → 120,843 +1.9% 489,974 → 492,191 +0.5%
flatMap 74,341 → 76,179 +2.5% 332,606 → 332,410 -0.1%
map inside map (5x5) 22,324 → 22,805 +2.2% 103,991 → 108,875 +4.7%
filter inside map (5x10) 16,589 → 16,708 +0.7% 14,411 → 15,112 +4.9%
reduce inside map (5x10) 20,179 → 20,477 +1.5% 15,006 → 15,624 +4.1%
forEach inside forEach (5x10) 17,823 → 17,529 -1.7% 16,625 → 17,515 +5.4%
find inside some (10x10) 14,521 → 14,525 +0.0% 11,696 → 12,356 +5.6%
map+filter chain nested (5x20) 5,657 → 5,698 +0.7% 4,840 → 5,089 +5.1%
reduce flatten (10x5) 39,882 → 39,414 -1.2% 6,713 → 6,456 -3.8%
async-await.js — Interp: 6 unch. · avg +1.4% · Bytecode: 6 unch. · avg +1.9%
Benchmark Interpreted Δ Bytecode Δ
single await 381,478 → 384,145 +0.7% 292,820 → 297,497 +1.6%
multiple awaits 169,211 → 172,688 +2.1% 125,367 → 127,825 +2.0%
await non-Promise value 859,740 → 869,514 +1.1% 1,028,245 → 1,018,643 -0.9%
await with try/catch 370,649 → 374,457 +1.0% 286,445 → 294,142 +2.7%
await Promise.all 50,854 → 51,627 +1.5% 46,655 → 47,828 +2.5%
nested async function call 191,181 → 195,467 +2.2% 212,870 → 220,729 +3.7%
classes.js — Interp: 🟢 1, 30 unch. · avg +1.5% · Bytecode: 31 unch. · avg +1.1%
Benchmark Interpreted Δ Bytecode Δ
simple class new 136,081 → 141,197 +3.8% 402,690 → 407,080 +1.1%
class with defaults 106,634 → 114,288 🟢 +7.2% 285,649 → 296,878 +3.9%
50 instances via Array.from 5,651 → 5,694 +0.8% 7,454 → 7,469 +0.2%
instance method call 66,565 → 69,307 +4.1% 183,699 → 178,333 -2.9%
static method call 112,664 → 116,063 +3.0% 424,349 → 418,732 -1.3%
single-level inheritance 53,884 → 55,980 +3.9% 176,001 → 170,848 -2.9%
two-level inheritance 46,868 → 47,968 +2.3% 145,140 → 144,708 -0.3%
private field access 72,323 → 73,022 +1.0% 192,816 → 189,782 -1.6%
private methods 78,802 → 80,378 +2.0% 252,287 → 240,876 -4.5%
getter/setter access 75,577 → 77,496 +2.5% 194,115 → 196,970 +1.5%
class decorator (identity) 91,733 → 93,101 +1.5% 61,013 → 59,849 -1.9%
class decorator (wrapping) 54,226 → 54,988 +1.4% 43,809 → 44,342 +1.2%
identity method decorator 65,131 → 64,668 -0.7% 46,294 → 46,938 +1.4%
wrapping method decorator 51,944 → 52,879 +1.8% 44,379 → 45,468 +2.5%
stacked method decorators (x3) 34,607 → 34,982 +1.1% 29,826 → 30,402 +1.9%
identity field decorator 70,136 → 70,731 +0.8% 50,392 → 52,307 +3.8%
field initializer decorator 59,091 → 59,868 +1.3% 45,416 → 46,395 +2.2%
getter decorator (identity) 65,153 → 67,238 +3.2% 45,336 → 46,725 +3.1%
setter decorator (identity) 55,153 → 55,871 +1.3% 39,346 → 40,306 +2.4%
static method decorator 70,619 → 70,780 +0.2% 71,738 → 73,509 +2.5%
static field decorator 81,322 → 83,002 +2.1% 73,601 → 75,466 +2.5%
private method decorator 53,623 → 53,946 +0.6% 41,796 → 42,629 +2.0%
private field decorator 58,219 → 59,084 +1.5% 42,298 → 43,374 +2.5%
plain auto-accessor (no decorator) 102,392 → 103,094 +0.7% 60,822 → 62,369 +2.5%
auto-accessor with decorator 55,168 → 53,895 -2.3% 40,516 → 41,434 +2.3%
decorator writing metadata 44,655 → 44,677 +0.1% 44,588 → 46,102 +3.4%
static getter read 128,627 → 129,843 +0.9% 436,714 → 441,995 +1.2%
static getter/setter pair 103,240 → 102,505 -0.7% 230,222 → 231,476 +0.5%
inherited static getter 77,548 → 77,707 +0.2% 301,376 → 303,304 +0.6%
inherited static setter 84,609 → 84,866 +0.3% 225,592 → 231,675 +2.7%
inherited static getter with this binding 71,975 → 72,207 +0.3% 178,958 → 180,844 +1.1%
closures.js — Interp: 11 unch. · avg +0.8% · Bytecode: 11 unch. · avg +0.5%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 135,578 → 138,852 +2.4% 781,406 → 785,534 +0.5%
closure over multiple variables 124,963 → 125,490 +0.4% 483,279 → 455,909 -5.7%
nested closures 131,319 → 132,226 +0.7% 682,010 → 690,046 +1.2%
function as argument 100,361 → 99,381 -1.0% 699,256 → 699,877 +0.1%
function returning function 124,425 → 124,741 +0.3% 757,408 → 782,725 +3.3%
compose two functions 74,041 → 74,703 +0.9% 463,143 → 464,874 +0.4%
fn.call 154,424 → 156,633 +1.4% 144,684 → 148,782 +2.8%
fn.apply 117,557 → 116,072 -1.3% 100,500 → 99,626 -0.9%
fn.bind 141,888 → 143,185 +0.9% 147,341 → 148,320 +0.7%
recursive sum to 50 12,102 → 12,341 +2.0% 51,691 → 52,538 +1.6%
recursive tree traversal 20,055 → 20,475 +2.1% 77,472 → 78,947 +1.9%
collections.js — Interp: 12 unch. · avg +0.5% · Bytecode: 🔴 1, 11 unch. · avg -2.8%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,404 → 7,352 -0.7% 6,015 → 5,826 -3.1%
has lookup (50 elements) 92,768 → 92,107 -0.7% 89,877 → 86,453 -3.8%
delete elements 48,974 → 48,815 -0.3% 38,125 → 37,870 -0.7%
forEach iteration 16,313 → 16,916 +3.7% 17,327 → 17,650 +1.9%
spread to array 28,284 → 27,832 -1.6% 141,061 → 137,676 -2.4%
deduplicate array 39,922 → 39,874 -0.1% 49,863 → 49,397 -0.9%
set 50 entries 5,381 → 5,489 +2.0% 6,130 → 5,916 -3.5%
get lookup (50 entries) 88,519 → 89,090 +0.6% 97,674 → 94,050 -3.7%
has check 133,492 → 133,632 +0.1% 153,044 → 149,181 -2.5%
delete entries 46,390 → 46,583 +0.4% 36,316 → 34,949 -3.8%
forEach iteration 16,593 → 17,002 +2.5% 17,472 → 17,579 +0.6%
keys/values/entries 7,567 → 7,558 -0.1% 22,484 → 19,784 🔴 -12.0%
destructuring.js — Interp: 🟢 1, 21 unch. · avg +2.6% · Bytecode: 🔴 1, 21 unch. · avg -1.1%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 432,616 → 427,614 -1.2% 1,039,019 → 1,019,118 -1.9%
with rest element 286,848 → 289,634 +1.0% 781,073 → 765,672 -2.0%
with defaults 425,122 → 449,490 +5.7% 965,450 → 977,531 +1.3%
skip elements 452,754 → 450,502 -0.5% 1,181,530 → 1,159,828 -1.8%
nested array destructuring 177,428 → 183,365 +3.3% 518,333 → 493,749 -4.7%
swap variables 516,541 → 540,641 +4.7% 1,371,207 → 1,356,323 -1.1%
simple object destructuring 314,238 → 319,461 +1.7% 650,724 → 635,252 -2.4%
with defaults 361,392 → 375,049 +3.8% 329,599 → 330,408 +0.2%
with renaming 328,626 → 338,117 +2.9% 766,046 → 746,555 -2.5%
nested object destructuring 146,175 → 153,866 +5.3% 297,562 → 297,400 -0.1%
rest properties 186,887 → 195,228 +4.5% 231,751 → 233,009 +0.5%
object parameter 94,482 → 97,081 +2.8% 212,565 → 211,736 -0.4%
array parameter 127,161 → 126,925 -0.2% 429,312 → 424,663 -1.1%
mixed destructuring in map 36,212 → 36,109 -0.3% 38,642 → 37,852 -2.0%
forEach with array destructuring 67,421 → 67,529 +0.2% 199,752 → 184,176 🔴 -7.8%
map with array destructuring 68,266 → 69,114 +1.2% 254,298 → 256,528 +0.9%
filter with array destructuring 71,933 → 73,043 +1.5% 289,464 → 273,369 -5.6%
reduce with array destructuring 76,152 → 77,351 +1.6% 269,207 → 277,242 +3.0%
map with object destructuring 80,673 → 82,613 +2.4% 77,319 → 79,650 +3.0%
map with nested destructuring 66,614 → 67,792 +1.8% 66,656 → 66,630 -0.0%
map with rest in destructuring 39,398 → 44,596 🟢 +13.2% 23,734 → 23,419 -1.3%
map with defaults in destructuring 60,516 → 61,209 +1.1% 40,161 → 40,681 +1.3%
fibonacci.js — Interp: 8 unch. · avg -0.9% · Bytecode: 🔴 1, 7 unch. · avg -1.7%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 327 → 335 +2.6% 1,454 → 1,426 -1.9%
recursive fib(20) 29 → 30 +2.0% 132 → 128 -2.6%
recursive fib(15) typed 332 → 343 +3.3% 1,905 → 1,952 +2.4%
recursive fib(20) typed 29 → 30 +0.8% 172 → 176 +2.2%
iterative fib(20) via reduce 12,468 → 12,119 -2.8% 9,838 → 9,139 🔴 -7.1%
iterator fib(20) 9,739 → 9,272 -4.8% 16,909 → 16,533 -2.2%
iterator fib(20) via Iterator.from + take 15,495 → 14,741 -4.9% 18,666 → 17,595 -5.7%
iterator fib(20) last value via reduce 11,900 → 11,534 -3.1% 13,589 → 13,762 +1.3%
for-of.js — Interp: 7 unch. · avg -1.7% · Bytecode: 🔴 1, 6 unch. · avg -2.2%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 46,491 → 45,938 -1.2% 162,368 → 162,449 +0.1%
for...of with 100-element array 5,324 → 5,195 -2.4% 23,546 → 23,792 +1.0%
for...of with string (10 chars) 34,329 → 33,381 -2.8% 125,795 → 122,859 -2.3%
for...of with Set (10 elements) 46,699 → 45,895 -1.7% 169,612 → 169,730 +0.1%
for...of with Map entries (10 entries) 29,435 → 29,209 -0.8% 46,006 → 42,597 🔴 -7.4%
for...of with destructuring 40,121 → 39,687 -1.1% 76,662 → 75,327 -1.7%
for-await-of with sync array 44,710 → 43,802 -2.0% 142,010 → 135,126 -4.8%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 20 unch. · avg -0.4% · Bytecode: 🔴 2, 18 unch. · avg -3.2%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 15,579 → 15,290 -1.9% 20,078 → 17,893 🔴 -10.9%
Iterator.from({next}).toArray() — 50 elements 6,838 → 6,664 -2.5% 8,082 → 7,670 -5.1%
spread pre-wrapped iterator — 20 elements 11,562 → 11,455 -0.9% 17,872 → 16,282 🔴 -8.9%
Iterator.from({next}).forEach — 50 elements 4,844 → 4,813 -0.6% 5,887 → 5,583 -5.2%
Iterator.from({next}).reduce — 50 elements 4,834 → 4,806 -0.6% 5,564 → 5,489 -1.3%
wrap array iterator 174,232 → 179,257 +2.9% 117,575 → 110,587 -5.9%
wrap plain {next()} object 10,865 → 10,646 -2.0% 14,803 → 14,462 -2.3%
map + toArray (50 elements) 4,916 → 4,895 -0.4% 6,070 → 6,018 -0.9%
filter + toArray (50 elements) 4,804 → 4,729 -1.6% 5,956 → 5,893 -1.0%
take(10) + toArray (50 element source) 28,533 → 27,655 -3.1% 32,567 → 31,243 -4.1%
drop(40) + toArray (50 element source) 6,877 → 6,664 -3.1% 9,948 → 9,306 -6.5%
chained map + filter + take (100 element source) 8,926 → 8,875 -0.6% 10,490 → 10,379 -1.1%
some + every (50 elements) 2,806 → 2,780 -0.9% 3,701 → 3,720 +0.5%
find (50 elements) 6,158 → 6,068 -1.5% 8,329 → 8,237 -1.1%
array.values().map().filter().toArray() 9,461 → 9,684 +2.4% 10,866 → 10,672 -1.8%
array.values().take(5).toArray() 212,524 → 216,572 +1.9% 151,749 → 147,479 -2.8%
array.values().drop(45).toArray() 198,607 → 202,084 +1.8% 145,274 → 143,310 -1.4%
map.entries() chained helpers 10,891 → 11,043 +1.4% 5,764 → 5,728 -0.6%
set.values() chained helpers 19,263 → 19,742 +2.5% 22,591 → 22,434 -0.7%
string iterator map + toArray 14,343 → 14,048 -2.1% 23,047 → 22,452 -2.6%
json.js — Interp: 20 unch. · avg -0.2% · Bytecode: 🔴 3, 17 unch. · avg -0.8%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 173,948 → 177,317 +1.9% 150,710 → 144,642 -4.0%
parse nested object 105,993 → 106,430 +0.4% 99,728 → 98,266 -1.5%
parse array of objects 62,623 → 62,511 -0.2% 60,131 → 60,870 +1.2%
parse large flat object 69,355 → 69,627 +0.4% 60,653 → 61,694 +1.7%
parse mixed types 79,829 → 80,004 +0.2% 69,178 → 67,919 -1.8%
stringify simple object 200,548 → 201,425 +0.4% 174,653 → 165,617 -5.2%
stringify nested object 111,399 → 110,983 -0.4% 92,661 → 87,389 -5.7%
stringify array of objects 62,438 → 62,142 -0.5% 54,356 → 51,979 -4.4%
stringify mixed types 88,184 → 88,357 +0.2% 76,854 → 71,394 🔴 -7.1%
reviver doubles numbers 48,802 → 48,853 +0.1% 54,760 → 50,665 🔴 -7.5%
reviver filters properties 41,550 → 40,689 -2.1% 53,383 → 47,949 🔴 -10.2%
reviver on nested object 52,354 → 51,540 -1.6% 55,318 → 55,224 -0.2%
reviver on array 33,949 → 33,033 -2.7% 33,499 → 34,648 +3.4%
replacer function doubles numbers 48,536 → 48,409 -0.3% 53,172 → 55,006 +3.4%
replacer function excludes properties 62,053 → 61,805 -0.4% 65,884 → 68,502 +4.0%
array replacer (allowlist) 114,277 → 117,085 +2.5% 102,574 → 107,685 +5.0%
stringify with 2-space indent 97,563 → 97,259 -0.3% 82,365 → 85,627 +4.0%
stringify with tab indent 98,228 → 96,206 -2.1% 87,399 → 90,197 +3.2%
parse then stringify 56,815 → 57,075 +0.5% 53,498 → 55,544 +3.8%
stringify then parse 34,959 → 34,663 -0.8% 33,475 → 34,011 +1.6%
jsx.jsx — Interp: 21 unch. · avg -2.1% · Bytecode: 21 unch. · avg +0.2%
Benchmark Interpreted Δ Bytecode Δ
simple element 211,888 → 210,190 -0.8% 795,028 → 811,011 +2.0%
self-closing element 220,436 → 219,532 -0.4% 839,131 → 838,214 -0.1%
element with string attribute 180,983 → 173,598 -4.1% 592,915 → 587,035 -1.0%
element with multiple attributes 160,332 → 154,626 -3.6% 508,009 → 509,583 +0.3%
element with expression attribute 170,406 → 165,088 -3.1% 574,458 → 574,719 +0.0%
text child 220,193 → 208,824 -5.2% 816,989 → 813,438 -0.4%
expression child 207,978 → 204,810 -1.5% 794,593 → 802,483 +1.0%
mixed text and expression 197,368 → 193,405 -2.0% 741,029 → 729,792 -1.5%
nested elements (3 levels) 79,065 → 78,640 -0.5% 319,135 → 325,387 +2.0%
sibling children 58,362 → 58,213 -0.3% 236,572 → 241,785 +2.2%
component element 151,031 → 146,465 -3.0% 551,331 → 543,887 -1.4%
component with children 93,162 → 91,071 -2.2% 353,724 → 350,797 -0.8%
dotted component 125,683 → 123,173 -2.0% 418,545 → 416,825 -0.4%
empty fragment 218,089 → 219,256 +0.5% 838,847 → 829,124 -1.2%
fragment with children 59,373 → 57,771 -2.7% 237,922 → 239,562 +0.7%
spread attributes 110,990 → 110,750 -0.2% 127,272 → 127,504 +0.2%
spread with overrides 95,168 → 94,355 -0.9% 99,927 → 98,481 -1.4%
shorthand props 165,352 → 159,597 -3.5% 529,676 → 530,483 +0.2%
nav bar structure 27,978 → 27,233 -2.7% 99,169 → 100,169 +1.0%
card component tree 32,676 → 31,766 -2.8% 110,200 → 110,394 +0.2%
10 list items via Array.from 15,184 → 14,720 -3.1% 26,098 → 27,019 +3.5%
modules.js — Interp: 🟢 1, 8 unch. · avg +4.2% · Bytecode: 9 unch. · avg -2.5%
Benchmark Interpreted Δ Bytecode Δ
call imported function 507,750 → 510,555 +0.6% 1,874,276 → 1,779,703 -5.0%
call two imported functions 286,402 → 289,205 +1.0% 1,348,519 → 1,269,249 -5.9%
read imported constant 1,695,542 → 1,773,944 +4.6% 3,942,771 → 3,862,034 -2.0%
read imported string 1,688,570 → 1,769,544 +4.8% 3,945,635 → 3,882,852 -1.6%
read JSON string property 1,707,675 → 1,785,299 +4.5% 3,940,219 → 3,877,999 -1.6%
read JSON number property 1,684,000 → 1,750,967 +4.0% 3,940,955 → 3,879,344 -1.6%
read JSON boolean property 1,689,982 → 1,765,341 +4.5% 3,929,522 → 3,881,705 -1.2%
read JSON array property 1,696,036 → 1,763,217 +4.0% 3,931,777 → 3,881,569 -1.3%
read multiple JSON properties 922,803 → 1,015,187 🟢 +10.0% 3,390,004 → 3,313,133 -2.3%
numbers.js — Interp: 11 unch. · avg -0.2% · Bytecode: 11 unch. · avg -1.6%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 557,263 → 564,185 +1.2% 2,114,490 → 2,070,121 -2.1%
floating point arithmetic 558,633 → 586,902 +5.1% 2,343,524 → 2,305,129 -1.6%
number coercion 191,307 → 191,886 +0.3% 164,850 → 171,050 +3.8%
toFixed 103,219 → 102,330 -0.9% 225,668 → 225,133 -0.2%
toString 157,402 → 154,760 -1.7% 870,562 → 843,223 -3.1%
valueOf 227,173 → 219,772 -3.3% 1,343,750 → 1,283,755 -4.5%
toPrecision 145,091 → 142,603 -1.7% 464,404 → 455,541 -1.9%
Number.isNaN 328,035 → 330,266 +0.7% 213,372 → 209,997 -1.6%
Number.isFinite 334,114 → 322,928 -3.3% 213,141 → 209,807 -1.6%
Number.isInteger 326,135 → 332,946 +2.1% 226,664 → 221,257 -2.4%
Number.parseInt and parseFloat 261,789 → 258,594 -1.2% 177,379 → 173,559 -2.2%
objects.js — Interp: 7 unch. · avg -0.4% · Bytecode: 🟢 1, 6 unch. · avg +2.5%
Benchmark Interpreted Δ Bytecode Δ
create simple object 502,113 → 486,388 -3.1% 993,352 → 1,055,279 +6.2%
create nested object 226,786 → 225,435 -0.6% 445,199 → 473,394 +6.3%
create 50 objects via Array.from 9,566 → 9,303 -2.8% 9,156 → 9,457 +3.3%
property read 619,003 → 629,679 +1.7% 874,924 → 839,231 -4.1%
Object.keys 293,510 → 294,623 +0.4% 200,829 → 200,060 -0.4%
Object.entries 102,064 → 104,652 +2.5% 69,866 → 67,038 -4.0%
spread operator 181,325 → 179,482 -1.0% 192,668 → 212,240 🟢 +10.2%
promises.js — Interp: 12 unch. · avg +0.0% · Bytecode: 🟢 1, 🔴 2, 9 unch. · avg -2.5%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 570,605 → 576,989 +1.1% 369,700 → 383,110 +3.6%
new Promise(resolve => resolve(value)) 193,914 → 195,214 +0.7% 163,279 → 154,202 -5.6%
Promise.reject(reason) 573,852 → 572,743 -0.2% 366,735 → 348,265 -5.0%
resolve + then (1 handler) 179,012 → 180,862 +1.0% 153,603 → 150,553 -2.0%
resolve + then chain (3 deep) 70,247 → 70,434 +0.3% 62,923 → 68,219 🟢 +8.4%
resolve + then chain (10 deep) 22,774 → 22,621 -0.7% 21,964 → 20,617 -6.1%
reject + catch + then 101,602 → 100,892 -0.7% 94,533 → 86,538 🔴 -8.5%
resolve + finally + then 85,077 → 85,246 +0.2% 79,204 → 75,986 -4.1%
Promise.all (5 resolved) 31,873 → 31,636 -0.7% 26,547 → 25,752 -3.0%
Promise.race (5 resolved) 33,743 → 33,589 -0.5% 28,313 → 28,621 +1.1%
Promise.allSettled (5 mixed) 26,571 → 26,368 -0.8% 22,901 → 23,177 +1.2%
Promise.any (5 mixed) 31,485 → 31,730 +0.8% 29,202 → 26,307 🔴 -9.9%
strings.js — Interp: 🟢 1, 10 unch. · avg -0.6% · Bytecode: 🔴 1, 10 unch. · avg -2.8%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 408,813 → 414,439 +1.4% 415,234 → 379,963 🔴 -8.5%
template literal 653,639 → 752,982 🟢 +15.2% 733,726 → 731,002 -0.4%
string repeat 416,804 → 406,910 -2.4% 1,145,571 → 1,072,012 -6.4%
split and join 139,527 → 136,476 -2.2% 344,866 → 333,128 -3.4%
indexOf and includes 170,323 → 164,734 -3.3% 773,299 → 776,650 +0.4%
toUpperCase and toLowerCase 254,561 → 249,596 -2.0% 767,957 → 748,302 -2.6%
slice and substring 160,724 → 157,574 -2.0% 896,937 → 855,096 -4.7%
trim operations 189,816 → 182,028 -4.1% 690,739 → 669,324 -3.1%
replace and replaceAll 211,599 → 208,637 -1.4% 635,956 → 621,791 -2.2%
startsWith and endsWith 136,901 → 131,445 -4.0% 652,665 → 668,812 +2.5%
padStart and padEnd 199,100 → 196,264 -1.4% 700,251 → 682,826 -2.5%
typed-arrays.js — Interp: 22 unch. · avg +1.1% · Bytecode: 22 unch. · avg +1.1%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 331,610 → 338,251 +2.0% 137,912 → 140,670 +2.0%
new Int32Array(100) 309,344 → 322,384 +4.2% 131,705 → 134,944 +2.5%
new Int32Array(1000) 174,421 → 186,145 +6.7% 81,216 → 80,210 -1.2%
new Float64Array(100) 274,589 → 284,398 +3.6% 118,446 → 116,778 -1.4%
Int32Array.from([...]) 180,288 → 186,091 +3.2% 162,097 → 164,402 +1.4%
Int32Array.of(1, 2, 3, 4, 5) 322,751 → 320,777 -0.6% 262,799 → 266,128 +1.3%
sequential write 100 elements 3,605 → 3,578 -0.7% 14,738 → 14,473 -1.8%
sequential read 100 elements 3,685 → 3,641 -1.2% 10,502 → 10,731 +2.2%
Float64Array write 100 elements 3,303 → 3,315 +0.4% 13,963 → 14,049 +0.6%
fill(42) 47,282 → 44,795 -5.3% 45,394 → 43,100 -5.1%
slice() 207,380 → 211,711 +2.1% 183,354 → 190,068 +3.7%
map(x => x * 2) 8,494 → 8,461 -0.4% 7,770 → 8,008 +3.1%
filter(x => x > 50) 8,678 → 8,877 +2.3% 8,701 → 8,755 +0.6%
reduce (sum) 8,185 → 8,227 +0.5% 7,358 → 7,534 +2.4%
sort() 178,546 → 179,185 +0.4% 150,538 → 153,167 +1.7%
indexOf() 452,602 → 464,728 +2.7% 357,723 → 375,249 +4.9%
reverse() 343,068 → 347,338 +1.2% 274,939 → 277,125 +0.8%
create view over existing buffer 425,453 → 431,831 +1.5% 154,068 → 155,059 +0.6%
subarray() 466,292 → 475,130 +1.9% 359,753 → 362,813 +0.9%
set() from array 609,958 → 618,636 +1.4% 259,719 → 268,899 +3.5%
for-of loop 4,943 → 4,917 -0.5% 20,189 → 20,427 +1.2%
spread into array 16,419 → 16,057 -2.2% 54,122 → 54,485 +0.7%

Measured on ubuntu-latest x64. Changes within ±7% are considered insignificant.

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

Caution

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

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

59-78: ⚠️ Potential issue | 🟠 Major

Pin the shared Boolean prototype before registering members.

FSharedBooleanPrototype and FPrototypeMethodHost are only held in class vars until the final PinObject calls. If GC runs during member collection/registration, both objects are still collectible and the registered callbacks can end up bound to a freed host.

Suggested fix
   FSharedBooleanPrototype := TGocciaObjectValue.Create;
   FPrototypeMethodHost := Self;
+  if Assigned(TGarbageCollector.Instance) then
+  begin
+    TGarbageCollector.Instance.PinObject(FSharedBooleanPrototype);
+    TGarbageCollector.Instance.PinObject(FPrototypeMethodHost);
+  end;
   if Length(FPrototypeMembers) = 0 then
   begin
     Members := TGocciaMemberCollection.Create;
     try
       Members.AddMethod(BooleanValueOf, 0);
@@
   end;
   RegisterMemberDefinitions(FSharedBooleanPrototype, FPrototypeMembers);
-
-  if Assigned(TGarbageCollector.Instance) then
-  begin
-    TGarbageCollector.Instance.PinObject(FSharedBooleanPrototype);
-    TGarbageCollector.Instance.PinObject(FPrototypeMethodHost);
-  end;
 end;

As per coding guidelines, "Values held only by Pascal code (not in any GocciaScript scope) must be protected with AddTempRoot/RemoveTempRoot for the duration they are needed" and "shared prototype singletons must be pinned in their InitializePrototype method".

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

In `@units/Goccia.Values.BooleanObjectValue.pas` around lines 59 - 78, Pin the
shared prototype and method host before you build/register members to prevent GC
from collecting them during member collection: in the InitializePrototype flow
for FSharedBooleanPrototype/FPrototypeMethodHost, call the GC pin (e.g.
TGarbageCollector.Instance.PinObject or AddTempRoot) immediately after creating
FSharedBooleanPrototype and setting FPrototypeMethodHost, then perform
Members.AddMethod(...) and RegisterMemberDefinitions(...), and finally
unpin/remove the temp root only if you used AddTempRoot/RemoveTempRoot; ensure
the PinObject calls happen before any Members.AddMethod or
RegisterMemberDefinitions calls so callbacks cannot be bound to a freed host.
🧹 Nitpick comments (8)
units/Goccia.Values.TemporalPlainTime.pas (1)

156-161: Use property-name constants for the new accessors.

The new registrations reintroduce raw property strings ('hour', 'minute', etc.) even though this unit already imports Goccia.Constants.PropertyNames. Please register these via the split constants instead of literals.

As per coding guidelines, "Use constant names from split constant units instead of hardcoded string literals: Goccia.Constants.PropertyNames for property names".

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

In `@units/Goccia.Values.TemporalPlainTime.pas` around lines 156 - 161, Replace
the hardcoded property name strings used when registering accessors with the
appropriate constants from Goccia.Constants.PropertyNames: update the
Members.AddAccessor calls that reference
'hour','minute','second','millisecond','microsecond','nanosecond' (the lines
registering GetHour, GetMinute, GetSecond, GetMillisecond, GetMicrosecond,
GetNanosecond) to use the corresponding PropertyNames constants instead of
string literals so the unit follows the split-constants guideline.
units/Goccia.Values.FunctionBase.pas (1)

156-158: Use property constants for the Function prototype registrations.

The new member definitions hardcode 'call', 'apply', and 'bind' instead of going through Goccia.Constants.PropertyNames. Keeping these names centralized matters here because they are looked up across multiple runtime paths.

As per coding guidelines, "Use constant names from split constant units instead of hardcoded string literals: Goccia.Constants.PropertyNames for property names".

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

In `@units/Goccia.Values.FunctionBase.pas` around lines 156 - 158, Replace the
hardcoded property name strings when registering Function prototype members with
the central constants from Goccia.Constants.PropertyNames: use the
PropertyNames.Call, PropertyNames.Apply, and PropertyNames.Bind constants
instead of 'call', 'apply', 'bind' in the Members[...] assignments that call
DefineNamedMethod (the registrations using DefineNamedMethod with handlers
FunctionCall, FunctionApply, FunctionBind) so look up and use those constant
symbols rather than string literals.
units/Goccia.Builtins.GlobalString.pas (1)

17-18: Make FStaticMembers actually static, or keep it local.

The new class var reads like a cache, but Lines 40-44 overwrite it on every TGocciaGlobalString construction. Either guard the build with Length(FStaticMembers) = 0 or drop the class var so the lifetime/ownership is explicit.

Also applies to: 40-48

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

In `@units/Goccia.Builtins.GlobalString.pas` around lines 17 - 18, FStaticMembers
is declared as a class var on TGocciaGlobalString but is being rebuilt on every
construction; either make it a true shared cache by populating it only once
(guard the population with Length(FStaticMembers)=0 inside the
constructor/initializer that currently sets it — e.g., the TGocciaGlobalString
constructor block that fills members), or remove the class var and use a
local/instance variable so lifetime/ownership is explicit; update the code paths
that currently write FStaticMembers (the block around lines 40–48) to follow one
of these two approaches.
units/Goccia.Values.StringObjectValue.pas (1)

183-184: Use shared property-name constants for these explicit method names.

replace and replaceAll are now hardcoded in a table that otherwise comes from shared runtime metadata. Please route them through Goccia.Constants.PropertyNames here (adding constants if needed) so this registration block stays aligned with the rest of the runtime.
As per coding guidelines, Use constant names from split constant units instead of hardcoded string literals: Goccia.Constants.PropertyNames for property names...

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

In `@units/Goccia.Values.StringObjectValue.pas` around lines 183 - 184, Replace
the hardcoded 'replace' and 'replaceAll' literal strings passed to
Members.AddNamedMethod with shared constants from Goccia.Constants.PropertyNames
(add two constants there if missing, e.g., Replace and ReplaceAll or similarly
named identifiers used throughout the runtime). Update the calls
Members.AddNamedMethod('replace', StringReplaceMethod, ...) and
Members.AddNamedMethod('replaceAll', StringReplaceAllMethod, ...) to use
Goccia.Constants.PropertyNames.<YourReplaceConstant> and
Goccia.Constants.PropertyNames.<YourReplaceAllConstant> so the registration uses
the shared property-name constants.
units/Goccia.Values.TemporalPlainDateTime.pas (1)

192-224: Replace the new member-name literals with shared constants.

This block adds a large batch of raw runtime names ('calendarId', 'monthCode', 'toJSON', etc.) even though the unit already imports Goccia.Constants.PropertyNames. Please move these names into the constant units before the same literals start diverging across other Temporal types.

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

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

In `@units/Goccia.Values.TemporalPlainDateTime.pas` around lines 192 - 224, The
large block in the constructor/register code uses raw string literals for
runtime property names in calls like Members.AddAccessor('calendarId',
GetCalendarId, ...) and should use the shared constants from
Goccia.Constants.PropertyNames instead; replace each literal
('calendarId','year','month','monthCode','day',...,'nanosecond') with the
corresponding PropertyNames.* constant (e.g. PropertyNames.CalendarId,
PropertyNames.Year, PropertyNames.MonthCode, etc.), keeping the same accessor
methods (GetCalendarId, GetYear, GetMonthCode, GetNanosecond) and existing
flags, and ensure any property-like names used elsewhere in this unit follow the
same pattern so names stay consistent across Temporal types.
units/Goccia.Values.IteratorValue.pas (1)

147-147: Use the existing property-name constants here.

PROP_NEXT and PROP_FROM are already imported and used later in this unit. Hardcoding those names in the new registration path makes this file drift from the repo’s constant-name convention.

♻️ Suggested cleanup
-      Members.AddNamedMethod('next', IteratorNext, 0, gmkPrototypeMethod, [gmfNoFunctionPrototype]);
+      Members.AddNamedMethod(PROP_NEXT, IteratorNext, 0, gmkPrototypeMethod, [gmfNoFunctionPrototype]);

-      Members.AddNamedMethod('from', FPrototypeMethodHost.IteratorFrom, 1, gmkStaticMethod, [gmfNoFunctionPrototype]);
+      Members.AddNamedMethod(PROP_FROM, FPrototypeMethodHost.IteratorFrom, 1, gmkStaticMethod, [gmfNoFunctionPrototype]);

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

Also applies to: 187-187

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

In `@units/Goccia.Values.IteratorValue.pas` at line 147, Replace the hardcoded
property-name string literals in the iterator registration with the shared
property-name constants: change the AddNamedMethod call that uses 'next'
(Members.AddNamedMethod('next', IteratorNext, ...)) to use PROP_NEXT, and
likewise replace any other hardcoded property names like 'from' with PROP_FROM
(these constants are already imported in this unit), ensuring the
AddNamedMethod/registration paths use PROP_NEXT and PROP_FROM instead of string
literals.
units/Goccia.ObjectModel.pas (2)

157-158: Consider adding a named type alias for the global list.

Per coding guidelines, TObjectList<T> should use named type aliases. This could be:

type
  TGocciaPublishedGetterHostList = TObjectList<TGocciaPublishedGetterHost>;
var
  GPublishedGetterHosts: TGocciaPublishedGetterHostList;

This improves type safety and clarity, though the current implementation works correctly.

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

In `@units/Goccia.ObjectModel.pas` around lines 157 - 158, Introduce a named type
alias for the global list instead of declaring GPublishedGetterHosts as
TObjectList<TObject>; add a type like TGocciaPublishedGetterHostList =
TObjectList<TGocciaPublishedGetterHost> and change the declaration of
GPublishedGetterHosts to use TGocciaPublishedGetterHostList so the global
variable references the concrete element type (refer to GPublishedGetterHosts
and the use of TObjectList<TObject> to find where to update). Ensure the new
type is declared in the same unit scope before the var section and update any
code that constructs or frees GPublishedGetterHosts to use the new alias.

624-629: Consider validating Def.Symbol is a TGocciaSymbolValue in ValidateMemberDefinition.

The validation checks that Symbol is assigned but doesn't verify it's actually a TGocciaSymbolValue. If a caller mistakenly passes a non-symbol value, the unchecked cast on line 672 (TGocciaSymbolValue(Def.Symbol)) would produce incorrect behavior.

🛡️ Defensive validation
     gmkSymbolMethod, gmkSymbolProperty, gmkSymbolAccessor:
+    begin
       if not Assigned(ADefinition.Symbol) then
         raise EGocciaObjectModelError.CreateFmt(
           'Invalid %s "%s": symbol members require a symbol value',
           [KindToString(ADefinition.Kind), ADefinition.ExposedName]);
+      if not (ADefinition.Symbol is TGocciaSymbolValue) then
+        raise EGocciaObjectModelError.CreateFmt(
+          'Invalid %s "%s": symbol members require a TGocciaSymbolValue, got %s',
+          [KindToString(ADefinition.Kind), ADefinition.ExposedName,
+           ADefinition.Symbol.ClassName]);
+    end;

Also applies to: 671-676

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

In `@units/Goccia.ObjectModel.pas` around lines 624 - 629, In
ValidateMemberDefinition, the code only checks Assigned(ADefinition.Symbol) but
not its runtime type; add a check that ADefinition.Symbol is actually a
TGocciaSymbolValue (e.g. using the "is" operator) and raise
EGocciaObjectModelError with a clear message if not, so the later cast
TGocciaSymbolValue(Def.Symbol) is safe; reference ValidateMemberDefinition,
ADefinition.Symbol and TGocciaSymbolValue when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/adding-built-in-types.md`:
- Around line 105-133: Replace the deprecated early-exit guard in
TGocciaYourValue.InitializePrototype by inverting the condition: instead of "if
Assigned(FShared) then Exit" use "if not Assigned(FShared) then" to wrap the
prototype creation and registration logic (including the FShared :=
TGocciaSharedPrototype.Create(Self), the FPrototypeMembers initialization block,
and RegisterMemberDefinitions). Ensure you still check and populate
FPrototypeMembers only when its Length = 0 and preserve the Members try/finally
logic and the gmfNoFunctionPrototype flag on FPrototypeMembers[0]; remove the
bare early return so the singleton prototype is created exactly once.

In `@units/Goccia.Builtins.GlobalNumber.pas`:
- Around line 60-65: The Number.* methods are registered with incorrect .length
values; update the Members.AddMethod calls so NumberParseInt is registered with
length 2 (not 1) and the NumberIsFinite, NumberIsNaN, NumberIsInteger,
NumberIsSafeInteger methods are registered with length 1 (not 0). Locate the
Members.AddMethod invocations for NumberParseInt, NumberParseFloat,
NumberIsFinite, NumberIsNaN, NumberIsInteger, and NumberIsSafeInteger and change
their arity arguments to reflect the actual signatures (NumberParseInt -> 2;
NumberParseFloat stays 1; NumberIs* helpers -> 1). Ensure only the numeric arity
parameter (the middle integer) is updated so .length exposes the correct values.

In `@units/Goccia.Builtins.GlobalObject.pas`:
- Around line 79-80: The Object.create and Object.hasOwn methods are registered
with incorrect arity; update the Members.AddMethod calls that register
ObjectCreate and ObjectHasOwn to use length 2 instead of 1 so Function.length
metadata matches the ECMAScript spec. Locate the Members.AddMethod calls that
reference ObjectCreate and ObjectHasOwn and change their second argument from 1
to 2 (keeping gmkStaticMethod unchanged); this will align with ObjectCreate's
optional Properties parameter and ObjectHasOwn's RequireExactly(AArgs, 2, ...)
requirement.

In `@units/Goccia.ObjectModel.Engine.pas`:
- Around line 55-79: The loop currently copies static values via
ATypeDefinition.StaticSource.GetProperty into AConstructor.SetProperty which
flattens accessors/flags; instead, iterate
ATypeDefinition.StaticSource.GetAllPropertyNames and for each Key fetch the full
property descriptor (e.g. StaticSource.GetPropertyDescriptor or equivalent) and
define that descriptor on the constructor (e.g. AConstructor.DefineProperty /
AssignPropertyDescriptor) so getters/setters and attributes
(writable/enumerable/configurable) are preserved; update the code paths around
AConstructor.SetProperty and ATypeDefinition.StaticSource.GetProperty to use the
descriptor-based API to copy descriptors intact.

In `@units/Goccia.ObjectModel.pas`:
- Around line 433-448: Replace the Pascal exception raised for runtime type
mismatches in Invoke with a JS-catchable error: add Goccia.Values.ErrorHelper to
the implementation uses clause and replace the raise
EGocciaObjectModelError.CreateFmt(...) that checks (AThisValue is FTargetClass)
with a call to ThrowTypeError (include the same formatted message), preserving
assignment to TargetObject when valid; leave the property-not-found raise for
GetPropInfo untouched since it represents engine misconfiguration.

In `@units/Goccia.Values.ArrayValue.pas`:
- Around line 260-263: Replace the accessor registration for PROP_LENGTH with a
data property on the shared array prototype: stop calling
Members.AddAccessor(PROP_LENGTH, GetLengthProperty, ...) and instead define
PROP_LENGTH as a data property with numeric value 0 and attributes
writable=true, enumerable=false, configurable=false (use the equivalent
Members.AddDataProperty / Members.AddProperty method available in
TGocciaMemberCollection). Remove reliance on GetLengthProperty for the prototype
(it casts the receiver to TGocciaArrayValue and will fail for
FSharedArrayPrototype which is a TGocciaObjectValue); keep GetLengthProperty for
actual array instances only. Leave ArrayMap registration unchanged.
- Around line 284-285: The registered arities for the Array prototype methods
are wrong: change the Members.AddMethod calls that register ArrayToSorted and
ArrayToSpliced so their length metadata match the spec — call
Members.AddMethod(ArrayToSorted, 1, gmkPrototypeMethod,
[gmfNoFunctionPrototype]) and Members.AddMethod(ArrayToSpliced, 2,
gmkPrototypeMethod, [gmfNoFunctionPrototype]) so Array.prototype.toSorted.length
=== 1 and Array.prototype.toSpliced.length === 2.

In `@units/Goccia.Values.EnumValue.pas`:
- Around line 68-74: The TGocciaStringLiteralValue created for
Symbol.toStringTag (via TGocciaStringLiteralValue.Create(AEnum.Name)) is only
temporarily held in the local Members array before
RegisterMemberDefinitions(AEnum, Members) and can be collected; protect it by
calling AddTempRoot on the created string value immediately after creation and
call RemoveTempRoot after RegisterMemberDefinitions returns (or alternatively
call DefineSymbolDataProperty on AEnum immediately after creating the string
literal so it becomes reachable without relying on Pascal-only references).
Ensure you reference the created value passed to DefineSymbolDataProperty and
use AddTempRoot/RemoveTempRoot around the registration window to satisfy the GC
rooting guideline.

---

Outside diff comments:
In `@units/Goccia.Values.BooleanObjectValue.pas`:
- Around line 59-78: Pin the shared prototype and method host before you
build/register members to prevent GC from collecting them during member
collection: in the InitializePrototype flow for
FSharedBooleanPrototype/FPrototypeMethodHost, call the GC pin (e.g.
TGarbageCollector.Instance.PinObject or AddTempRoot) immediately after creating
FSharedBooleanPrototype and setting FPrototypeMethodHost, then perform
Members.AddMethod(...) and RegisterMemberDefinitions(...), and finally
unpin/remove the temp root only if you used AddTempRoot/RemoveTempRoot; ensure
the PinObject calls happen before any Members.AddMethod or
RegisterMemberDefinitions calls so callbacks cannot be bound to a freed host.

---

Nitpick comments:
In `@units/Goccia.Builtins.GlobalString.pas`:
- Around line 17-18: FStaticMembers is declared as a class var on
TGocciaGlobalString but is being rebuilt on every construction; either make it a
true shared cache by populating it only once (guard the population with
Length(FStaticMembers)=0 inside the constructor/initializer that currently sets
it — e.g., the TGocciaGlobalString constructor block that fills members), or
remove the class var and use a local/instance variable so lifetime/ownership is
explicit; update the code paths that currently write FStaticMembers (the block
around lines 40–48) to follow one of these two approaches.

In `@units/Goccia.ObjectModel.pas`:
- Around line 157-158: Introduce a named type alias for the global list instead
of declaring GPublishedGetterHosts as TObjectList<TObject>; add a type like
TGocciaPublishedGetterHostList = TObjectList<TGocciaPublishedGetterHost> and
change the declaration of GPublishedGetterHosts to use
TGocciaPublishedGetterHostList so the global variable references the concrete
element type (refer to GPublishedGetterHosts and the use of TObjectList<TObject>
to find where to update). Ensure the new type is declared in the same unit scope
before the var section and update any code that constructs or frees
GPublishedGetterHosts to use the new alias.
- Around line 624-629: In ValidateMemberDefinition, the code only checks
Assigned(ADefinition.Symbol) but not its runtime type; add a check that
ADefinition.Symbol is actually a TGocciaSymbolValue (e.g. using the "is"
operator) and raise EGocciaObjectModelError with a clear message if not, so the
later cast TGocciaSymbolValue(Def.Symbol) is safe; reference
ValidateMemberDefinition, ADefinition.Symbol and TGocciaSymbolValue when making
this change.

In `@units/Goccia.Values.FunctionBase.pas`:
- Around line 156-158: Replace the hardcoded property name strings when
registering Function prototype members with the central constants from
Goccia.Constants.PropertyNames: use the PropertyNames.Call, PropertyNames.Apply,
and PropertyNames.Bind constants instead of 'call', 'apply', 'bind' in the
Members[...] assignments that call DefineNamedMethod (the registrations using
DefineNamedMethod with handlers FunctionCall, FunctionApply, FunctionBind) so
look up and use those constant symbols rather than string literals.

In `@units/Goccia.Values.IteratorValue.pas`:
- Line 147: Replace the hardcoded property-name string literals in the iterator
registration with the shared property-name constants: change the AddNamedMethod
call that uses 'next' (Members.AddNamedMethod('next', IteratorNext, ...)) to use
PROP_NEXT, and likewise replace any other hardcoded property names like 'from'
with PROP_FROM (these constants are already imported in this unit), ensuring the
AddNamedMethod/registration paths use PROP_NEXT and PROP_FROM instead of string
literals.

In `@units/Goccia.Values.StringObjectValue.pas`:
- Around line 183-184: Replace the hardcoded 'replace' and 'replaceAll' literal
strings passed to Members.AddNamedMethod with shared constants from
Goccia.Constants.PropertyNames (add two constants there if missing, e.g.,
Replace and ReplaceAll or similarly named identifiers used throughout the
runtime). Update the calls Members.AddNamedMethod('replace',
StringReplaceMethod, ...) and Members.AddNamedMethod('replaceAll',
StringReplaceAllMethod, ...) to use
Goccia.Constants.PropertyNames.<YourReplaceConstant> and
Goccia.Constants.PropertyNames.<YourReplaceAllConstant> so the registration uses
the shared property-name constants.

In `@units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 192-224: The large block in the constructor/register code uses raw
string literals for runtime property names in calls like
Members.AddAccessor('calendarId', GetCalendarId, ...) and should use the shared
constants from Goccia.Constants.PropertyNames instead; replace each literal
('calendarId','year','month','monthCode','day',...,'nanosecond') with the
corresponding PropertyNames.* constant (e.g. PropertyNames.CalendarId,
PropertyNames.Year, PropertyNames.MonthCode, etc.), keeping the same accessor
methods (GetCalendarId, GetYear, GetMonthCode, GetNanosecond) and existing
flags, and ensure any property-like names used elsewhere in this unit follow the
same pattern so names stay consistent across Temporal types.

In `@units/Goccia.Values.TemporalPlainTime.pas`:
- Around line 156-161: Replace the hardcoded property name strings used when
registering accessors with the appropriate constants from
Goccia.Constants.PropertyNames: update the Members.AddAccessor calls that
reference 'hour','minute','second','millisecond','microsecond','nanosecond' (the
lines registering GetHour, GetMinute, GetSecond, GetMillisecond, GetMicrosecond,
GetNanosecond) to use the corresponding PropertyNames constants instead of
string literals so the unit follows the split-constants guideline.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 609c25f5-dbe5-4433-9ffe-4210cf4b67fc

📥 Commits

Reviewing files that changed from the base of the PR and between a46d89f and 90aa5c8.

📒 Files selected for processing (42)
  • AGENTS.md
  • docs/adding-built-in-types.md
  • docs/architecture.md
  • docs/code-style.md
  • units/Goccia.Builtins.Console.pas
  • units/Goccia.Builtins.GlobalArray.pas
  • units/Goccia.Builtins.GlobalArrayBuffer.pas
  • units/Goccia.Builtins.GlobalNumber.pas
  • units/Goccia.Builtins.GlobalObject.pas
  • units/Goccia.Builtins.GlobalPromise.pas
  • units/Goccia.Builtins.GlobalString.pas
  • units/Goccia.Builtins.GlobalSymbol.pas
  • units/Goccia.Builtins.Globals.pas
  • units/Goccia.Builtins.JSON.pas
  • units/Goccia.Builtins.Math.pas
  • units/Goccia.Builtins.Performance.pas
  • units/Goccia.Builtins.Temporal.pas
  • units/Goccia.Engine.pas
  • units/Goccia.ObjectModel.Engine.pas
  • units/Goccia.ObjectModel.Types.pas
  • units/Goccia.ObjectModel.pas
  • units/Goccia.Values.ArrayBufferValue.pas
  • units/Goccia.Values.ArrayValue.pas
  • units/Goccia.Values.BooleanObjectValue.pas
  • units/Goccia.Values.EnumValue.pas
  • units/Goccia.Values.FunctionBase.pas
  • units/Goccia.Values.IteratorValue.pas
  • units/Goccia.Values.MapValue.pas
  • units/Goccia.Values.NumberObjectValue.pas
  • units/Goccia.Values.ObjectValue.pas
  • units/Goccia.Values.PromiseValue.pas
  • units/Goccia.Values.SetValue.pas
  • units/Goccia.Values.SharedArrayBufferValue.pas
  • units/Goccia.Values.StringObjectValue.pas
  • units/Goccia.Values.SymbolValue.pas
  • units/Goccia.Values.TemporalDuration.pas
  • units/Goccia.Values.TemporalInstant.pas
  • units/Goccia.Values.TemporalPlainDate.pas
  • units/Goccia.Values.TemporalPlainDateTime.pas
  • units/Goccia.Values.TemporalPlainTime.pas
  • units/Goccia.Values.TypedArrayValue.pas
  • units/Goccia.inc
👮 Files not reviewed due to content moderation or server errors (7)
  • units/Goccia.inc
  • docs/architecture.md
  • units/Goccia.Builtins.Temporal.pas
  • docs/code-style.md
  • AGENTS.md
  • units/Goccia.Values.SharedArrayBufferValue.pas
  • units/Goccia.Values.NumberObjectValue.pas

Comment thread docs/adding-built-in-types.md
Comment thread units/Goccia.Builtins.GlobalNumber.pas Outdated
Comment thread units/Goccia.Builtins.GlobalObject.pas Outdated
Comment thread units/Goccia.ObjectModel.Engine.pas Outdated
Comment thread units/Goccia.ObjectModel.pas
Comment thread units/Goccia.ObjectModel.pas
Comment thread units/Goccia.Values.ArrayValue.pas
Comment thread units/Goccia.Values.ArrayValue.pas Outdated
Comment thread units/Goccia.Values.EnumValue.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: 1

Caution

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

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

385-393: ⚠️ Potential issue | 🟠 Major

ThrowError methods bypass ErrorHelper and create uncatchable errors.

These private helper methods use TGocciaError.Create directly instead of the Goccia.Values.ErrorHelper functions. Per coding guidelines, all runtime errors must use ThrowTypeError, ThrowRangeError, etc., to ensure errors are proper JS Error objects with correct prototype chains, catchable from JavaScript try...catch.

The current implementation creates errors that may not be catchable from JavaScript code.

Suggested refactor

Replace calls to ThrowError(...) throughout the file with the appropriate ThrowTypeError(...) calls from Goccia.Values.ErrorHelper, then remove these private methods:

-procedure TGocciaArrayValue.ThrowError(const AMessage: string; const AArgs: array of const);
-begin
-  raise TGocciaError.Create(Format(AMessage, AArgs), 0, 0, '', nil);
-end;
-
-procedure TGocciaArrayValue.ThrowError(const AMessage: string);
-begin
-  ThrowError(AMessage, []);
-end;

Then update call sites like:

-    ThrowError('Array.%s called on non-array', [AMethodName]);
+    ThrowTypeError(Format('Array.%s called on non-array', [AMethodName]));

As per coding guidelines: "Always call error construction through Goccia.Values.ErrorHelper functions to ensure proper error type prototype chain and catchability from JavaScript."

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

In `@units/Goccia.Values.ArrayValue.pas` around lines 385 - 393, The ThrowError
overloads in TGocciaArrayValue create TGocciaError directly and must be replaced
by calls to the ErrorHelper functions (e.g., ThrowTypeError, ThrowRangeError) so
JS errors are proper and catchable; update all call sites that use
TGocciaArrayValue.ThrowError to call the appropriate Goccia.Values.ErrorHelper
function (choose ThrowTypeError/ThrowRangeError based on context) and then
remove the private ThrowError methods from TGocciaArrayValue; ensure
Format(AMessage, AArgs) is passed into the chosen ErrorHelper so the message
formatting is preserved.
🧹 Nitpick comments (1)
units/Goccia.Values.ArrayValue.pas (1)

98-98: GetLengthProperty appears to be dead code.

This method was previously used as the accessor getter for Array.prototype.length, but now that length is registered as a data property via AddDataProperty (line 262), this method is no longer referenced. Consider removing it.

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

In `@units/Goccia.Values.ArrayValue.pas` at line 98, Remove the now-unused
GetLengthProperty function as it is dead code: locate the
declaration/implementation of function GetLengthProperty(const AArgs:
TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue and
delete it (and any related forward declaration) because length is registered as
a data property via AddDataProperty and the getter is no longer referenced;
ensure no other code references GetLengthProperty before removing, and run a
build to confirm no unresolved symbols remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Values.ArrayValue.pas`:
- Line 289: The Array.prototype.unshift method is registered with length -1
(variadic convention) but per spec its length must be 1; update the
Members.AddMethod call that registers ArrayUnshift so its length argument is 1
(i.e., replace the -1 with 1) while keeping the same flags (gmkPrototypeMethod
and [gmfNoFunctionPrototype]) so ArrayUnshift.length reports 1 as required by
ECMAScript §23.1.3.37.

---

Outside diff comments:
In `@units/Goccia.Values.ArrayValue.pas`:
- Around line 385-393: The ThrowError overloads in TGocciaArrayValue create
TGocciaError directly and must be replaced by calls to the ErrorHelper functions
(e.g., ThrowTypeError, ThrowRangeError) so JS errors are proper and catchable;
update all call sites that use TGocciaArrayValue.ThrowError to call the
appropriate Goccia.Values.ErrorHelper function (choose
ThrowTypeError/ThrowRangeError based on context) and then remove the private
ThrowError methods from TGocciaArrayValue; ensure Format(AMessage, AArgs) is
passed into the chosen ErrorHelper so the message formatting is preserved.

---

Nitpick comments:
In `@units/Goccia.Values.ArrayValue.pas`:
- Line 98: Remove the now-unused GetLengthProperty function as it is dead code:
locate the declaration/implementation of function GetLengthProperty(const AArgs:
TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue and
delete it (and any related forward declaration) because length is registered as
a data property via AddDataProperty and the getter is no longer referenced;
ensure no other code references GetLengthProperty before removing, and run a
build to confirm no unresolved symbols remain.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8321010-d79d-49a8-8b72-3edab2146d0e

📥 Commits

Reviewing files that changed from the base of the PR and between da47998 and a5068ff.

📒 Files selected for processing (16)
  • tests/built-ins/Array/constructor.js
  • tests/built-ins/Array/prototype/toSorted.js
  • tests/built-ins/Array/prototype/toSpliced.js
  • tests/built-ins/Number/isFinite.js
  • tests/built-ins/Number/isInteger.js
  • tests/built-ins/Number/isNaN.js
  • tests/built-ins/Number/isSafeInteger.js
  • tests/built-ins/Number/parseInt.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Object/hasOwn.js
  • units/Goccia.Builtins.GlobalNumber.pas
  • units/Goccia.Builtins.GlobalObject.pas
  • units/Goccia.ObjectModel.Engine.pas
  • units/Goccia.ObjectModel.pas
  • units/Goccia.Values.ArrayValue.pas
  • units/Goccia.Values.EnumValue.pas
✅ Files skipped from review due to trivial changes (10)
  • tests/built-ins/Number/parseInt.js
  • tests/built-ins/Array/constructor.js
  • tests/built-ins/Array/prototype/toSorted.js
  • tests/built-ins/Object/create.js
  • tests/built-ins/Number/isFinite.js
  • tests/built-ins/Object/hasOwn.js
  • tests/built-ins/Number/isSafeInteger.js
  • tests/built-ins/Array/prototype/toSpliced.js
  • tests/built-ins/Number/isInteger.js
  • tests/built-ins/Number/isNaN.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • units/Goccia.Builtins.GlobalNumber.pas
  • units/Goccia.ObjectModel.Engine.pas
  • units/Goccia.Builtins.GlobalObject.pas
  • units/Goccia.ObjectModel.pas

Comment thread units/Goccia.Values.ArrayValue.pas Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal Refactoring, CI, tooling, cleanup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant