Skip to content

First draft of re-ordering maps functionality#30

Merged
frostney merged 3 commits into
mainfrom
feat-maps
Feb 22, 2026
Merged

First draft of re-ordering maps functionality#30
frostney merged 3 commits into
mainfrom
feat-maps

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Feb 22, 2026

Summary by CodeRabbit

  • Refactor
    • Consolidated object property storage into a single ordered map for stable, predictable property enumeration and iteration order.
    • Exposed a read-only Properties view on objects for clearer inspection of property state.
    • Property enumeration APIs now return array-based results for simpler consumption.
    • Streamlined scope/binding management for improved reliability and performance.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds a generic insertion-ordered map (TOrderedMap) and refactors scope binding and object property storage to use it, replacing several custom map/list patterns and updating related iteration and property-access code.

Changes

Cohort / File(s) Summary
New OrderedMap implementation
units/OrderedMap.pas
Adds generic TOrderedMap<TValue> with insertion-order flat entries, open-addressing hash table, tombstone handling, resizing/compaction, and full public API (Add, TryGetValue, Remove, Keys, Values, ToArray, Count, etc.).
Scope binding storage refactor
units/Goccia.Scope.BindingMap.pas, units/Goccia.Scope.pas
Replaces class TGocciaBindingMap with TGocciaScopeBindingMap = TOrderedMap<TLexicalBinding>; adds OrderedMap dependency; TLexicalBinding gains private IsWritable/public Writable; code updated to use map Keys/Values and map iteration APIs.
Object property descriptor & storage refactor
units/Goccia.Values.ObjectPropertyDescriptor.pas, units/Goccia.Values.ObjectValue.pas
Adds TGocciaPropertyMap = TOrderedMap<TGocciaPropertyDescriptor>; replaces FPropertyDescriptors/insertion-order list with FProperties; rewrites property accessors, enumerators, Freeze/Seal/IsExtensible flows, and changes several APIs to return arrays (TArray<string>). Exposes Properties.
Instance property lookup update
units/Goccia.Values.ClassValue.pas
TGocciaInstanceValue.GetProperty now checks FProperties.ContainsKey(AName) (map) instead of the old descriptor collection.
Binding cloning minor change
units/Goccia.Builtins.Globals.pas
CloneObject uses TArray<string> for Keys, adjusts loop to Length(Keys)-1, and replaces an empty-branch with Continue when a descriptor is missing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through keys and descriptors bright,
Replaced spun lists with orderly light,
Buckets and flat arrays now hum in tune,
Bindings and props march under one moon—
A little rabbit cheers "new map, hooray!" 🥕✨

🚥 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 title 'First draft of re-ordering maps functionality' accurately captures the main change: introducing the OrderedMap data structure and refactoring existing code to use it for insertion-order preservation.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-maps

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 22, 2026

Benchmark Results

179 benchmarks · 🟢 142 improved · 37 unchanged · avg +20.8%

arrays.js — 🟢 5 improved, 14 unchanged · avg +3.2%
Benchmark Base (ops/sec) PR (ops/sec) Change
Array.from length 100 12,980 13,803 +6.3%
Array.from 10 elements 146,829 161,019 🟢 +9.7%
Array.of 10 elements 187,981 193,160 +2.8%
spread into new array 215,004 231,084 🟢 +7.5%
map over 50 elements 11,348 11,227 -1.1%
filter over 50 elements 9,659 9,648 -0.1%
reduce sum 50 elements 12,215 12,104 -0.9%
forEach over 50 elements 9,898 9,642 -2.6%
find in 50 elements 13,058 12,917 -1.1%
sort 20 elements 10,779 10,225 -5.1%
flat nested array 79,140 89,258 🟢 +12.8%
flatMap 52,952 57,978 🟢 +9.5%
map inside map (5x5) 15,840 16,542 +4.4%
filter inside map (5x10) 5,011 5,271 +5.2%
reduce inside map (5x10) 5,902 6,127 +3.8%
forEach inside forEach (5x10) 5,515 5,588 +1.3%
find inside some (10x10) 3,339 3,382 +1.3%
map+filter chain nested (5x20) 2,359 2,282 -3.3%
reduce flatten (10x5) 5,548 6,170 🟢 +11.2%
classes.js — 🟢 15 improved · avg +20.1%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple class new 84,145 101,519 🟢 +20.6%
class with defaults 61,491 78,909 🟢 +28.3%
50 instances via Array.from 3,671 4,791 🟢 +30.5%
instance method call 38,121 46,679 🟢 +22.4%
static method call 74,280 80,997 🟢 +9.0%
single-level inheritance 30,567 39,569 🟢 +29.5%
two-level inheritance 28,126 35,057 🟢 +24.6%
private field access 41,153 46,193 🟢 +12.2%
private methods 46,426 53,983 🟢 +16.3%
getter/setter access 43,467 53,578 🟢 +23.3%
static getter read 82,390 97,296 🟢 +18.1%
static getter/setter pair 59,367 68,939 🟢 +16.1%
inherited static getter 45,760 54,565 🟢 +19.2%
inherited static setter 50,091 58,363 🟢 +16.5%
inherited static getter with this binding 40,639 46,823 🟢 +15.2%
closures.js — 🟢 10 improved, 1 unchanged · avg +14.4%
Benchmark Base (ops/sec) PR (ops/sec) Change
closure over single variable 76,253 84,727 🟢 +11.1%
closure over multiple variables 86,494 97,047 🟢 +12.2%
nested closures 87,653 107,530 🟢 +22.7%
function as argument 65,956 76,353 🟢 +15.8%
function returning function 81,712 97,130 🟢 +18.9%
compose two functions 49,305 59,239 🟢 +20.1%
fn.call 118,900 138,194 🟢 +16.2%
fn.apply 79,572 92,697 🟢 +16.5%
fn.bind 101,283 115,791 🟢 +14.3%
recursive sum to 50 8,041 7,931 -1.4%
recursive tree traversal 12,454 13,953 🟢 +12.0%
collections.js — 🟢 9 improved, 3 unchanged · avg +23.3%
Benchmark Base (ops/sec) PR (ops/sec) Change
add 50 elements 5,223 5,804 🟢 +11.1%
has lookup (50 elements) 8,744 8,985 +2.8%
delete elements 22,897 25,837 🟢 +12.8%
forEach iteration 5,599 5,778 +3.2%
spread to array 8,031 12,926 🟢 +60.9%
deduplicate array 25,762 40,659 🟢 +57.8%
set 50 entries 4,133 4,330 +4.8%
get lookup (50 entries) 3,990 4,505 🟢 +12.9%
has check 3,983 4,541 🟢 +14.0%
delete entries 9,967 12,334 🟢 +23.8%
forEach iteration 3,138 3,458 🟢 +10.2%
keys/values/entries 2,705 4,463 🟢 +65.0%
destructuring.js — 🟢 14 improved · avg +26.3%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple array destructuring 242,772 287,406 🟢 +18.4%
with rest element 167,615 184,317 🟢 +10.0%
with defaults 245,188 291,182 🟢 +18.8%
skip elements 269,549 295,637 🟢 +9.7%
nested array destructuring 118,897 145,916 🟢 +22.7%
swap variables 304,780 349,753 🟢 +14.8%
simple object destructuring 169,974 248,218 🟢 +46.0%
with defaults 218,431 289,116 🟢 +32.4%
with renaming 220,842 290,372 🟢 +31.5%
nested object destructuring 97,867 148,273 🟢 +51.5%
rest properties 105,683 160,727 🟢 +52.1%
object parameter 69,858 84,521 🟢 +21.0%
array parameter 91,869 98,554 🟢 +7.3%
mixed destructuring in map 8,911 11,722 🟢 +31.5%
fibonacci.js — 🟢 4 improved, 2 unchanged · avg +28.5%
Benchmark Base (ops/sec) PR (ops/sec) Change
recursive fib(15) 210 217 +3.4%
recursive fib(20) 19 20 +2.6%
iterative fib(20) via reduce 8,605 9,671 🟢 +12.4%
iterator fib(20) 5,240 7,265 🟢 +38.6%
iterator fib(20) via Iterator.from + take 5,035 8,049 🟢 +59.8%
iterator fib(20) last value via reduce 4,522 6,977 🟢 +54.3%
iterators.js — 🟢 20 improved · avg +52.0%
Benchmark Base (ops/sec) PR (ops/sec) Change
Iterator.from({next}).toArray() — 20 elements 6,147 9,253 🟢 +50.5%
Iterator.from({next}).toArray() — 50 elements 2,612 3,959 🟢 +51.6%
spread pre-wrapped iterator — 20 elements 6,077 9,247 🟢 +52.2%
Iterator.from({next}).forEach — 50 elements 2,136 3,028 🟢 +41.7%
Iterator.from({next}).reduce — 50 elements 2,182 3,094 🟢 +41.8%
wrap array iterator 33,448 62,708 🟢 +87.5%
wrap plain {next()} object 4,100 6,358 🟢 +55.1%
map + toArray (50 elements) 1,780 2,721 🟢 +52.9%
filter + toArray (50 elements) 1,832 2,735 🟢 +49.3%
take(10) + toArray (50 element source) 9,525 15,186 🟢 +59.4%
drop(40) + toArray (50 element source) 2,554 3,803 🟢 +48.9%
chained map + filter + take (100 element source) 3,255 4,854 🟢 +49.1%
some + every (50 elements) 1,257 1,734 🟢 +37.9%
find (50 elements) 2,563 3,721 🟢 +45.2%
array.values().map().filter().toArray() 2,099 3,409 🟢 +62.4%
array.values().take(5).toArray() 13,116 18,069 🟢 +37.8%
array.values().drop(45).toArray() 6,471 11,143 🟢 +72.2%
map.entries() chained helpers 2,446 3,433 🟢 +40.4%
set.values() chained helpers 4,229 5,832 🟢 +37.9%
string iterator map + toArray 5,084 8,473 🟢 +66.7%
json.js — 🟢 19 improved, 1 unchanged · avg +21.3%
Benchmark Base (ops/sec) PR (ops/sec) Change
parse simple object 135,247 171,397 🟢 +26.7%
parse nested object 79,621 111,493 🟢 +40.0%
parse array of objects 43,720 59,712 🟢 +36.6%
parse large flat object 38,969 50,905 🟢 +30.6%
parse mixed types 59,823 74,115 🟢 +23.9%
stringify simple object 119,297 146,941 🟢 +23.2%
stringify nested object 65,080 80,442 🟢 +23.6%
stringify array of objects 13,085 16,620 🟢 +27.0%
stringify mixed types 52,836 65,020 🟢 +23.1%
reviver doubles numbers 30,140 34,383 🟢 +14.1%
reviver filters properties 26,143 32,002 🟢 +22.4%
reviver on nested object 34,893 41,413 🟢 +18.7%
reviver on array 21,893 21,223 -3.1%
replacer function doubles numbers 28,570 34,331 🟢 +20.2%
replacer function excludes properties 40,110 45,211 🟢 +12.7%
array replacer (allowlist) 68,738 92,280 🟢 +34.2%
stringify with 2-space indent 64,169 72,261 🟢 +12.6%
stringify with tab indent 64,296 73,275 🟢 +14.0%
parse then stringify 41,090 44,994 🟢 +9.5%
stringify then parse 14,549 16,797 🟢 +15.5%
jsx.jsx — 🟢 21 improved · avg +17.0%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple element 158,352 187,635 🟢 +18.5%
self-closing element 162,039 192,192 🟢 +18.6%
element with string attribute 127,764 156,999 🟢 +22.9%
element with multiple attributes 107,341 132,910 🟢 +23.8%
element with expression attribute 116,337 140,239 🟢 +20.5%
text child 156,567 181,835 🟢 +16.1%
expression child 151,741 176,987 🟢 +16.6%
mixed text and expression 143,863 167,531 🟢 +16.5%
nested elements (3 levels) 58,066 66,072 🟢 +13.8%
sibling children 42,996 48,411 🟢 +12.6%
component element 105,117 123,270 🟢 +17.3%
component with children 65,158 75,323 🟢 +15.6%
dotted component 85,744 102,597 🟢 +19.7%
empty fragment 155,693 180,101 🟢 +15.7%
fragment with children 43,194 48,256 🟢 +11.7%
spread attributes 78,991 96,266 🟢 +21.9%
spread with overrides 67,741 83,205 🟢 +22.8%
shorthand props 118,753 133,048 🟢 +12.0%
nav bar structure 20,712 23,046 🟢 +11.3%
card component tree 23,870 26,831 🟢 +12.4%
10 list items via Array.from 10,984 12,750 🟢 +16.1%
numbers.js — 🟢 4 improved, 7 unchanged · avg +4.9%
Benchmark Base (ops/sec) PR (ops/sec) Change
integer arithmetic 317,425 316,219 -0.4%
floating point arithmetic 351,550 348,394 -0.9%
number coercion 134,421 147,864 🟢 +10.0%
toFixed 85,536 89,990 +5.2%
toString 119,813 132,305 🟢 +10.4%
valueOf 157,516 182,208 🟢 +15.7%
toPrecision 110,845 120,741 🟢 +8.9%
Number.isNaN 225,038 228,795 +1.7%
Number.isFinite 225,386 229,391 +1.8%
Number.isInteger 210,282 210,598 +0.2%
Number.parseInt and parseFloat 189,405 191,506 +1.1%
objects.js — 🟢 7 improved · avg +36.8%
Benchmark Base (ops/sec) PR (ops/sec) Change
create simple object 286,762 364,611 🟢 +27.1%
create nested object 136,421 189,730 🟢 +39.1%
create 50 objects via Array.from 6,482 9,188 🟢 +41.8%
property read 126,438 169,819 🟢 +34.3%
Object.keys 87,838 121,416 🟢 +38.2%
Object.entries 52,447 73,014 🟢 +39.2%
spread operator 111,528 153,875 🟢 +38.0%
promises.js — 🟢 11 improved, 1 unchanged · avg +15.7%
Benchmark Base (ops/sec) PR (ops/sec) Change
Promise.resolve(value) 351,972 418,834 🟢 +19.0%
new Promise(resolve => resolve(value)) 146,717 160,663 🟢 +9.5%
Promise.reject(reason) 365,250 444,430 🟢 +21.7%
resolve + then (1 handler) 122,345 139,869 🟢 +14.3%
resolve + then chain (3 deep) 50,697 55,912 🟢 +10.3%
resolve + then chain (10 deep) 16,822 17,895 +6.4%
reject + catch + then 73,939 81,758 🟢 +10.6%
resolve + finally + then 59,385 68,547 🟢 +15.4%
Promise.all (5 resolved) 22,748 27,334 🟢 +20.2%
Promise.race (5 resolved) 23,696 28,261 🟢 +19.3%
Promise.allSettled (5 mixed) 18,803 23,245 🟢 +23.6%
Promise.any (5 mixed) 22,686 26,694 🟢 +17.7%
strings.js — 🟢 3 improved, 8 unchanged · avg +5.7%
Benchmark Base (ops/sec) PR (ops/sec) Change
string concatenation 355,376 364,174 +2.5%
template literal 400,934 387,482 -3.4%
string repeat 299,989 356,631 🟢 +18.9%
split and join 117,899 120,724 +2.4%
indexOf and includes 127,541 143,715 🟢 +12.7%
toUpperCase and toLowerCase 206,264 210,016 +1.8%
slice and substring 117,141 116,995 -0.1%
trim operations 145,865 155,291 +6.5%
replace and replaceAll 170,696 177,460 +4.0%
startsWith and endsWith 98,235 115,221 🟢 +17.3%
padStart and padEnd 158,422 159,560 +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: 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.ObjectValue.pas (1)

115-132: ⚠️ Potential issue | 🟠 Major

Free old descriptors before replacement in DefineProperty, Freeze, and Seal.

TOrderedMap does not own its values. When FProperties.Add() overwrites an existing key, the old TGocciaPropertyDescriptor instance is not freed. Since descriptor classes hold no destructors, their contained TGocciaValue references (in TGocciaPropertyDescriptorData.FValue and TGocciaPropertyDescriptorAccessor.FGetter/FSetter) also leak.

This occurs in:

  • DefineProperty (line 316): replaces existing descriptor without freeing
  • Freeze (lines 659, 706): creates new descriptors for all properties, discards old ones
  • Seal (line 705): same pattern as Freeze

Retrieve and free the old descriptor before calling Add(), or add destructors to descriptor classes to handle cleanup.

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

In `@units/Goccia.Values.ObjectValue.pas` around lines 115 - 132, DefineProperty,
Freeze, and Seal currently overwrite TGocciaPropertyDescriptor instances without
freeing the old ones; before calling FProperties.Add (and before replacing
entries in FSymbolDescriptors), retrieve the existing descriptor (e.g., via
TryGetValue/GetValue or the map's lookup method), Free it, then add the new
descriptor; alternatively implement destructors on TGocciaPropertyDescriptor /
TGocciaPropertyDescriptorData / TGocciaPropertyDescriptorAccessor to release
held TGocciaValue references and call Free on old descriptor whenever it would
be replaced (apply this change in the DefineProperty, Freeze and Seal code paths
where new descriptors replace existing ones).
🧹 Nitpick comments (1)
units/OrderedMap.pas (1)

53-80: Prefer const for non-mutated parameters.

AHash, ANewBucketCount, and AIndex are not modified and should be const for consistency and clarity.

♻️ Proposed updates
-    function FindBucket(const AKey: string; AHash: Cardinal; out ABucketIdx: Integer): Boolean;
+    function FindBucket(const AKey: string; const AHash: Cardinal; out ABucketIdx: Integer): Boolean;
...
-    procedure Rehash(ANewBucketCount: Integer);
+    procedure Rehash(const ANewBucketCount: Integer);
...
-    function EntryAt(AIndex: Integer): TKeyValuePair;
+    function EntryAt(const AIndex: Integer): TKeyValuePair;
-function TOrderedMap<TValue>.FindBucket(const AKey: string; AHash: Cardinal;
+function TOrderedMap<TValue>.FindBucket(const AKey: string; const AHash: Cardinal;
   out ABucketIdx: Integer): Boolean;
...
-procedure TOrderedMap<TValue>.Rehash(ANewBucketCount: Integer);
+procedure TOrderedMap<TValue>.Rehash(const ANewBucketCount: Integer);
...
-function TOrderedMap<TValue>.EntryAt(AIndex: Integer): TKeyValuePair;
+function TOrderedMap<TValue>.EntryAt(const AIndex: Integer): TKeyValuePair;
As per coding guidelines: “Prefer `const` parameters for all parameters that are not modified in the function body.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/OrderedMap.pas` around lines 53 - 80, Parameters that are never
modified should be declared const: update the signatures for FindBucket to make
AHash const, Rehash to make ANewBucketCount const, and EntryAt to make AIndex
const so they reflect immutability and follow the project's guideline; locate
the methods by name (FindBucket, Rehash, EntryAt) in OrderedMap.pas and change
those parameter declarations to use const, then rebuild to ensure no call sites
require non-const parameters.
🤖 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/OrderedMap.pas`:
- Around line 19-21: Rename the TOrderedMap generic class to TGocciaOrderedMap
to follow the TGoccia<Name> class naming convention: update the type declaration
(TOrderedMap<TValue> -> TGocciaOrderedMap<TValue>), adjust any
constructor/destructor and class references (uses, variable declarations,
typecasts, and unit exports) that reference TOrderedMap, and ensure any
unit/interface/implementation identifiers that expose the class are updated to
the new TGocciaOrderedMap symbol so all references compile.

---

Outside diff comments:
In `@units/Goccia.Values.ObjectValue.pas`:
- Around line 115-132: DefineProperty, Freeze, and Seal currently overwrite
TGocciaPropertyDescriptor instances without freeing the old ones; before calling
FProperties.Add (and before replacing entries in FSymbolDescriptors), retrieve
the existing descriptor (e.g., via TryGetValue/GetValue or the map's lookup
method), Free it, then add the new descriptor; alternatively implement
destructors on TGocciaPropertyDescriptor / TGocciaPropertyDescriptorData /
TGocciaPropertyDescriptorAccessor to release held TGocciaValue references and
call Free on old descriptor whenever it would be replaced (apply this change in
the DefineProperty, Freeze and Seal code paths where new descriptors replace
existing ones).

---

Nitpick comments:
In `@units/OrderedMap.pas`:
- Around line 53-80: Parameters that are never modified should be declared
const: update the signatures for FindBucket to make AHash const, Rehash to make
ANewBucketCount const, and EntryAt to make AIndex const so they reflect
immutability and follow the project's guideline; locate the methods by name
(FindBucket, Rehash, EntryAt) in OrderedMap.pas and change those parameter
declarations to use const, then rebuild to ensure no call sites require
non-const parameters.

Comment thread units/OrderedMap.pas
Comment on lines +19 to +21
type
TOrderedMap<TValue> = class
public type
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rename TOrderedMap to TGocciaOrderedMap to match naming conventions.

The class name doesn’t follow the required TGoccia<Name> prefix convention.

As per coding guidelines: “Class naming: Use TGoccia<Name> prefix for all class names.”

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

In `@units/OrderedMap.pas` around lines 19 - 21, Rename the TOrderedMap generic
class to TGocciaOrderedMap to follow the TGoccia<Name> class naming convention:
update the type declaration (TOrderedMap<TValue> -> TGocciaOrderedMap<TValue>),
adjust any constructor/destructor and class references (uses, variable
declarations, typecasts, and unit exports) that reference TOrderedMap, and
ensure any unit/interface/implementation identifiers that expose the class are
updated to the new TGocciaOrderedMap symbol so all references compile.

@frostney frostney changed the title First draft of re-ordering maps First draft of re-ordering maps functionality Feb 22, 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.

🧹 Nitpick comments (4)
units/Goccia.Values.ObjectValue.pas (2)

332-340: Identical implementations for GetOwnPropertyNames and GetOwnPropertyKeys.

Both methods return FProperties.Keys. If they serve distinct semantic purposes (e.g., future symbol key support for GetOwnPropertyKeys), this is fine. Otherwise, one could delegate to the other.

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

In `@units/Goccia.Values.ObjectValue.pas` around lines 332 - 340,
TGocciaObjectValue.GetOwnPropertyNames and TGocciaObjectValue.GetOwnPropertyKeys
both return FProperties.Keys; if they are intended to be semantically identical,
make one delegate to the other to avoid duplicate logic (e.g., have
GetOwnPropertyKeys call GetOwnPropertyNames or vice versa), otherwise implement
the intended difference (for example, change GetOwnPropertyKeys to include
symbol keys or a different source than FProperties.Keys). Update the
implementations of GetOwnPropertyNames and GetOwnPropertyKeys accordingly,
keeping FProperties.Keys as the canonical source unless symbol/key differences
are required.

544-558: Consistency note: Symbol properties still use dictionary pattern.

String properties now use TGocciaPropertyMap while symbol properties retain TDictionary + TList for insertion order. Consider aligning these implementations in a follow-up for consistency.

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

In `@units/Goccia.Values.ObjectValue.pas` around lines 544 - 558, The symbol
property storage is inconsistent: replace the TDictionary + TList pattern
(FSymbolDescriptors, FSymbolInsertionOrder) with the same TGocciaPropertyMap
used for string properties so insertion order and descriptor management are
unified; update TGocciaObjectValue.DefineSymbolProperty to use the
TGocciaPropertyMap APIs (lookup/update/remove/iterate) instead of
TryGetValue/AddOrSetValue and remove manual insertion-order handling, and adjust
any other symbol-related methods to use the new map field and its methods to
preserve insertion order and configurable checks.
units/OrderedMap.pas (2)

10-10: Unit naming should follow Goccia.<Category>.<Name>.pas convention.

The unit is named OrderedMap but project conventions require the Goccia.<Category>.<Name>.pas naming pattern. Consider renaming to Goccia.Collections.OrderedMap.pas or similar.

As per coding guidelines: "Unit naming convention: Goccia.<Category>.<Name>.pas"

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

In `@units/OrderedMap.pas` at line 10, The unit declaration and filename should
follow the project's naming convention: rename the unit from "OrderedMap" to a
Goccia-style name such as "Goccia.Collections.OrderedMap" and update the
filename accordingly (e.g., Goccia.Collections.OrderedMap.pas); then update any
references/usages of the unit in uses clauses and project files to the new unit
identifier (search for "unit OrderedMap;" and occurrences of "OrderedMap" in
uses lists) so compilation and unit resolution remain correct.

395-396: Minor edge case in error message.

When FCount is 0, the error message will display "index 0 out of range [0..-1]", which is slightly confusing. Consider a clearer message for the empty map case.

💡 Suggested improvement
   if (AIndex < 0) or (AIndex >= FCount) then
-    raise ERangeError.CreateFmt('EntryAt index %d out of range [0..%d]', [AIndex, FCount - 1]);
+    if FCount = 0 then
+      raise ERangeError.Create('EntryAt called on empty map')
+    else
+      raise ERangeError.CreateFmt('EntryAt index %d out of range [0..%d]', [AIndex, FCount - 1]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/OrderedMap.pas` around lines 395 - 396, The range-check in the EntryAt
(or similar index-access) branch constructs "0..-1" when FCount = 0; update the
check to produce a clearer message for empty maps by testing FCount = 0 first
and raising ERangeError with a message like "EntryAt index %d out of range: map
is empty" (or otherwise omit the upper bound), otherwise keep the existing
CreateFmt('EntryAt index %d out of range [0..%d]', [AIndex, FCount - 1]) for
FCount > 0 so the error is human-readable in both cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@units/Goccia.Values.ObjectValue.pas`:
- Around line 332-340: TGocciaObjectValue.GetOwnPropertyNames and
TGocciaObjectValue.GetOwnPropertyKeys both return FProperties.Keys; if they are
intended to be semantically identical, make one delegate to the other to avoid
duplicate logic (e.g., have GetOwnPropertyKeys call GetOwnPropertyNames or vice
versa), otherwise implement the intended difference (for example, change
GetOwnPropertyKeys to include symbol keys or a different source than
FProperties.Keys). Update the implementations of GetOwnPropertyNames and
GetOwnPropertyKeys accordingly, keeping FProperties.Keys as the canonical source
unless symbol/key differences are required.
- Around line 544-558: The symbol property storage is inconsistent: replace the
TDictionary + TList pattern (FSymbolDescriptors, FSymbolInsertionOrder) with the
same TGocciaPropertyMap used for string properties so insertion order and
descriptor management are unified; update
TGocciaObjectValue.DefineSymbolProperty to use the TGocciaPropertyMap APIs
(lookup/update/remove/iterate) instead of TryGetValue/AddOrSetValue and remove
manual insertion-order handling, and adjust any other symbol-related methods to
use the new map field and its methods to preserve insertion order and
configurable checks.

In `@units/OrderedMap.pas`:
- Line 10: The unit declaration and filename should follow the project's naming
convention: rename the unit from "OrderedMap" to a Goccia-style name such as
"Goccia.Collections.OrderedMap" and update the filename accordingly (e.g.,
Goccia.Collections.OrderedMap.pas); then update any references/usages of the
unit in uses clauses and project files to the new unit identifier (search for
"unit OrderedMap;" and occurrences of "OrderedMap" in uses lists) so compilation
and unit resolution remain correct.
- Around line 395-396: The range-check in the EntryAt (or similar index-access)
branch constructs "0..-1" when FCount = 0; update the check to produce a clearer
message for empty maps by testing FCount = 0 first and raising ERangeError with
a message like "EntryAt index %d out of range: map is empty" (or otherwise omit
the upper bound), otherwise keep the existing CreateFmt('EntryAt index %d out of
range [0..%d]', [AIndex, FCount - 1]) for FCount > 0 so the error is
human-readable in both 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.

🧹 Nitpick comments (2)
units/OrderedMap.pas (2)

318-322: Consider using a more specific exception type.

EArgumentException or a custom EKeyNotFound exception would provide clearer diagnostics than the generic Exception class.

🔧 Suggested improvement
 function TOrderedMap<TValue>.GetValue(const AKey: string): TValue;
 begin
   if not TryGetValue(AKey, Result) then
-    raise Exception.Create('Key not found in ordered map');
+    raise EArgumentException.CreateFmt('Key ''%s'' not found in ordered map', [AKey]);
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/OrderedMap.pas` around lines 318 - 322, Change the generic Exception
raised in TOrderedMap<TValue>.GetValue to a more specific exception type; either
raise EArgumentException (e.g., using EArgumentException.CreateFmt to include
the missing AKey in the message) or define and raise a custom EKeyNotFound =
class(Exception) and use EKeyNotFound.CreateFmt to include the key for clearer
diagnostics; update the raise statement in GetValue accordingly so callers can
catch a specific exception.

391-412: O(n) complexity for index-based access.

EntryAt scans entries linearly to find the nth active element. For sequential iteration, prefer ForEach or ToArray which are more efficient. This is fine for occasional single-element access but worth documenting.

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

In `@units/OrderedMap.pas` around lines 391 - 412, Add a short API comment above
TOrderedMap<TValue>.EntryAt noting that EntryAt performs a linear scan over
FEntries (O(n) time) to find the nth active element and is intended for
occasional single-element access; recommend using ForEach or ToArray for
sequential iteration or bulk retrieval instead, and mention that complexity
stems from scanning FEntryCount/FEntries for Active entries. This guidance
should reference the EntryAt method name and the ForEach/ToArray helpers so
callers know the alternatives.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@units/OrderedMap.pas`:
- Around line 19-21: Rename the class symbol TOrderedMap to follow the
TGoccia<Name> convention: update the class declaration TOrderedMap<TValue> to
TGocciaOrderedMap<TValue> and update all internal references/usages (type
declarations, variables, method declarations, constructors/destructors, and any
casts) that reference TOrderedMap to use TGocciaOrderedMap instead so the
identifier is consistent across the unit.

---

Nitpick comments:
In `@units/OrderedMap.pas`:
- Around line 318-322: Change the generic Exception raised in
TOrderedMap<TValue>.GetValue to a more specific exception type; either raise
EArgumentException (e.g., using EArgumentException.CreateFmt to include the
missing AKey in the message) or define and raise a custom EKeyNotFound =
class(Exception) and use EKeyNotFound.CreateFmt to include the key for clearer
diagnostics; update the raise statement in GetValue accordingly so callers can
catch a specific exception.
- Around line 391-412: Add a short API comment above TOrderedMap<TValue>.EntryAt
noting that EntryAt performs a linear scan over FEntries (O(n) time) to find the
nth active element and is intended for occasional single-element access;
recommend using ForEach or ToArray for sequential iteration or bulk retrieval
instead, and mention that complexity stems from scanning FEntryCount/FEntries
for Active entries. This guidance should reference the EntryAt method name and
the ForEach/ToArray helpers so callers know the alternatives.

@frostney frostney merged commit 7ef9c97 into main Feb 22, 2026
4 checks passed
@frostney frostney deleted the feat-maps branch February 22, 2026 16:35
@frostney frostney added the new feature New feature or request label Apr 9, 2026
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant