Skip to content

Add stack property for errors#23

Merged
frostney merged 2 commits into
mainfrom
feat-add-stacktrace
Feb 20, 2026
Merged

Add stack property for errors#23
frostney merged 2 commits into
mainfrom
feat-add-stacktrace

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Feb 20, 2026

Summary by CodeRabbit

  • New Features

    • Error objects now include formatted stack traces showing the error header and call-site lines; stacks are preserved for thrown and runtime errors.
    • Symbol and iterator semantics clarified: Symbol.iterator behavior, lazy well-known symbols, and Symbol.species support for controlling derived constructors.
    • Microtask/Promise execution model and microtask queue behavior documented.
  • Documentation

    • Expanded docs describing call-stack and microtask behaviors and public-facing symbol/iterator semantics.
  • Tests

    • Added comprehensive tests validating error stack contents and formatting across error types and call patterns.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Adds a process-global CallStack singleton and integrates call-frame push/pop into the evaluator so Error objects receive a formatted stack property; documentation expanded (including microtask queue notes) and tests added to validate stack trace behavior.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, docs/architecture.md, docs/built-ins.md
Expanded docs describing CallStack, microtask queue design, Error.stack format/semantics, Symbol and iterator behavior; no functional changes in docs.
Call Stack Implementation
units/Goccia.CallStack.pas
New unit adding TGocciaCallFrame and TGocciaCallStack singleton with Push/Pop, dynamic storage, and CaptureStackTrace formatting with skip-frame support.
Engine Init
units/Goccia.Engine.pas
Imports CallStack and initializes TGocciaCallStack during engine startup (after GC initialization).
Error Construction
units/Goccia.Builtins.Globals.pas, units/Goccia.Values.ErrorHelper.pas
CreateErrorObject signature extended with ASkipTop; error construction now attaches a formatted stack when CallStack is available; globals now pass skip argument when building errors.
Evaluator Call Tracking
units/Goccia.Evaluator.pas
Adds callee-name capture and balanced CallStack.Push/Pop around function and constructor invocations (EvaluateCall, EvaluateNewExpression), with try/finally to ensure pop on exit.
Tests
tests/built-ins/Error/error-stack.js
New comprehensive test suite verifying presence, format, and content of Error.stack across error types, nested calls, thrown/caught errors, and AggregateError.

Sequence Diagram(s)

sequenceDiagram
    participant Evaluator
    participant CallStack as CallStack (singleton)
    participant ErrorHelper as ErrorHelper
    participant ErrorObj as Error (object)

    Evaluator->>CallStack: Push(calleeName, file, line)
    Evaluator->>ErrorHelper: CreateErrorObject(name, message, skipTop)
    ErrorHelper->>CallStack: CaptureStackTrace(name, message, skipTop)
    CallStack-->>ErrorHelper: formattedStack
    ErrorHelper->>ErrorObj: attach stack property
    ErrorObj-->>Evaluator: return Error object
    Evaluator->>CallStack: Pop()
Loading
sequenceDiagram
    participant User
    participant Evaluator
    participant CallStack as CallStack (singleton)
    participant Function as Callee

    User->>Evaluator: invoke function
    Evaluator->>CallStack: Push(calleeName, file, line)
    Evaluator->>Function: call
    alt throws
        Function-->>Evaluator: raises error
        Evaluator->>ErrorHelper: CreateErrorObject(...)
        ErrorHelper->>CallStack: CaptureStackTrace(...)
    else returns
        Function-->>Evaluator: result
    end
    Evaluator->>CallStack: Pop()
    Evaluator-->>User: result or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Remove the usage of interfaces #21: Modifies evaluator call/constructor dispatch (EvaluateCall/EvaluateNewExpression); likely to interact with the new CallStack push/pop integration.

Poem

🐰 I hopped through frames both near and far,

Pushed my name upon the call-stack bar,
Now errors hum the trail they keep,
A breadcrumb path for code to peep,
Hooray — my hops are in the stack!

🚥 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 'Add stack property for errors' clearly and concisely summarizes the main change: implementing a stack property for error objects throughout the codebase.
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-add-stacktrace

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 20, 2026

Benchmark Results

172 benchmarks · 🔴 1 regressed · 171 unchanged · avg -2.8%

arrays.js — 12 unchanged · avg -2.0%
Benchmark Base (ops/sec) PR (ops/sec) Change
Array.from length 100 5,223 4,993 -4.4%
Array.from 10 elements 127,747 125,324 -1.9%
Array.of 10 elements 156,386 153,564 -1.8%
spread into new array 165,348 157,197 -4.9%
map over 50 elements 5,001 4,927 -1.5%
filter over 50 elements 4,623 4,560 -1.3%
reduce sum 50 elements 4,842 4,793 -1.0%
forEach over 50 elements 4,377 4,324 -1.2%
find in 50 elements 5,092 5,026 -1.3%
sort 20 elements 4,426 4,339 -2.0%
flat nested array 71,365 69,969 -2.0%
flatMap 37,385 36,934 -1.2%
classes.js — 15 unchanged · avg -2.5%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple class new 69,336 68,334 -1.4%
class with defaults 53,372 52,574 -1.5%
50 instances via Array.from 2,566 2,489 -3.0%
instance method call 33,701 32,674 -3.0%
static method call 56,266 54,983 -2.3%
single-level inheritance 27,621 27,121 -1.8%
two-level inheritance 25,068 24,675 -1.6%
private field access 35,726 34,658 -3.0%
private methods 41,455 39,666 -4.3%
getter/setter access 37,718 36,695 -2.7%
static getter read 70,379 67,616 -3.9%
static getter/setter pair 49,268 47,644 -3.3%
inherited static getter 41,356 40,517 -2.0%
inherited static setter 44,109 43,151 -2.2%
inherited static getter with this binding 34,918 34,206 -2.0%
closures.js — 11 unchanged · avg -3.9%
Benchmark Base (ops/sec) PR (ops/sec) Change
closure over single variable 59,145 56,343 -4.7%
closure over multiple variables 58,680 56,664 -3.4%
nested closures 63,882 61,674 -3.5%
function as argument 44,164 42,829 -3.0%
function returning function 58,974 56,641 -4.0%
compose two functions 36,163 34,606 -4.3%
fn.call 88,852 86,492 -2.7%
fn.apply 62,529 60,916 -2.6%
fn.bind 74,742 71,753 -4.0%
recursive sum to 50 5,124 4,913 -4.1%
recursive tree traversal 9,228 8,644 -6.3%
collections.js — 12 unchanged · avg -1.8%
Benchmark Base (ops/sec) PR (ops/sec) Change
add 50 elements 3,314 3,220 -2.8%
has lookup (50 elements) 5,716 5,574 -2.5%
delete elements 14,882 14,495 -2.6%
forEach iteration 3,257 3,209 -1.4%
spread to array 6,406 6,288 -1.8%
deduplicate array 24,538 24,084 -1.9%
set 50 entries 2,717 2,629 -3.2%
get lookup (50 entries) 3,194 3,169 -0.8%
has check 3,232 3,210 -0.7%
delete entries 8,324 8,276 -0.6%
forEach iteration 2,240 2,218 -1.0%
keys/values/entries 2,515 2,454 -2.4%
destructuring.js — 14 unchanged · avg -2.5%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple array destructuring 184,540 180,112 -2.4%
with rest element 136,782 131,969 -3.5%
with defaults 185,373 181,885 -1.9%
skip elements 198,444 192,496 -3.0%
nested array destructuring 105,218 105,025 -0.2%
swap variables 214,200 209,482 -2.2%
simple object destructuring 140,362 137,244 -2.2%
with defaults 169,520 166,292 -1.9%
with renaming 166,621 163,324 -2.0%
nested object destructuring 84,630 80,323 -5.1%
rest properties 87,621 84,904 -3.1%
object parameter 52,566 51,546 -1.9%
array parameter 66,943 64,520 -3.6%
mixed destructuring in map 6,110 5,986 -2.0%
fibonacci.js — 6 unchanged · avg -2.7%
Benchmark Base (ops/sec) PR (ops/sec) Change
recursive fib(15) 144 138 -4.3%
recursive fib(20) 13 12 -2.9%
iterative fib(20) via reduce 7,094 6,954 -2.0%
iterator fib(20) 3,937 3,844 -2.4%
iterator fib(20) via Iterator.from + take 4,034 3,950 -2.1%
iterator fib(20) last value via reduce 3,384 3,301 -2.5%
iterators.js — 20 unchanged · avg -1.4%
Benchmark Base (ops/sec) PR (ops/sec) Change
Iterator.from({next}).toArray() — 20 elements 5,307 5,187 -2.3%
Iterator.from({next}).toArray() — 50 elements 2,276 2,230 -2.0%
spread pre-wrapped iterator — 20 elements 5,376 5,149 -4.2%
Iterator.from({next}).forEach — 50 elements 1,716 1,671 -2.6%
Iterator.from({next}).reduce — 50 elements 1,731 1,695 -2.1%
wrap array iterator 31,258 30,718 -1.7%
wrap plain {next()} object 3,693 3,574 -3.2%
map + toArray (50 elements) 1,481 1,454 -1.8%
filter + toArray (50 elements) 1,590 1,541 -3.1%
take(10) + toArray (50 element source) 8,391 8,499 +1.3%
drop(40) + toArray (50 element source) 2,167 2,135 -1.5%
chained map + filter + take (100 element source) 2,626 2,684 +2.2%
some + every (50 elements) 980 1,019 +4.0%
find (50 elements) 2,151 2,126 -1.2%
array.values().map().filter().toArray() 1,550 1,572 +1.4%
array.values().take(5).toArray() 7,688 7,724 +0.5%
array.values().drop(45).toArray() 4,639 4,514 -2.7%
map.entries() chained helpers 2,021 1,933 -4.3%
set.values() chained helpers 3,139 3,071 -2.1%
string iterator map + toArray 4,340 4,223 -2.7%
json.js — 20 unchanged · avg -2.0%
Benchmark Base (ops/sec) PR (ops/sec) Change
parse simple object 117,707 114,219 -3.0%
parse nested object 73,895 73,250 -0.9%
parse array of objects 42,450 41,475 -2.3%
parse large flat object 36,471 35,929 -1.5%
parse mixed types 53,313 53,069 -0.5%
stringify simple object 100,474 99,854 -0.6%
stringify nested object 57,076 57,319 +0.4%
stringify array of objects 11,214 11,025 -1.7%
stringify mixed types 48,020 46,803 -2.5%
reviver doubles numbers 22,968 22,836 -0.6%
reviver filters properties 21,474 21,152 -1.5%
reviver on nested object 27,610 26,914 -2.5%
reviver on array 16,184 15,946 -1.5%
replacer function doubles numbers 22,147 21,490 -3.0%
replacer function excludes properties 29,082 28,403 -2.3%
array replacer (allowlist) 59,963 57,360 -4.3%
stringify with 2-space indent 56,313 55,064 -2.2%
stringify with tab indent 57,048 54,809 -3.9%
parse then stringify 36,290 35,440 -2.3%
stringify then parse 12,222 11,920 -2.5%
jsx.jsx — 21 unchanged · avg -3.7%
Benchmark Base (ops/sec) PR (ops/sec) Change
simple element 116,654 113,686 -2.5%
self-closing element 119,238 113,286 -5.0%
element with string attribute 98,925 95,853 -3.1%
element with multiple attributes 86,171 83,652 -2.9%
element with expression attribute 89,652 86,991 -3.0%
text child 114,767 112,205 -2.2%
expression child 109,194 105,975 -2.9%
mixed text and expression 106,554 101,999 -4.3%
nested elements (3 levels) 43,968 42,219 -4.0%
sibling children 33,192 32,099 -3.3%
component element 82,357 79,622 -3.3%
component with children 52,004 49,528 -4.8%
dotted component 70,388 67,267 -4.4%
empty fragment 112,313 107,212 -4.5%
fragment with children 32,725 31,640 -3.3%
spread attributes 63,451 60,660 -4.4%
spread with overrides 54,042 52,355 -3.1%
shorthand props 85,303 80,891 -5.2%
nav bar structure 16,011 14,940 -6.7%
card component tree 18,525 17,768 -4.1%
10 list items via Array.from 7,860 7,782 -1.0%
numbers.js — 🔴 1 regressed, 10 unchanged · avg -3.9%
Benchmark Base (ops/sec) PR (ops/sec) Change
integer arithmetic 189,116 191,523 +1.3%
floating point arithmetic 212,318 212,686 +0.2%
number coercion 108,179 104,274 -3.6%
toFixed 72,592 71,292 -1.8%
toString 99,734 94,553 -5.2%
valueOf 131,955 121,672 🔴 -7.8%
toPrecision 94,570 89,491 -5.4%
Number.isNaN 160,786 153,272 -4.7%
Number.isFinite 153,464 147,168 -4.1%
Number.isInteger 158,118 150,245 -5.0%
Number.parseInt and parseFloat 149,088 139,645 -6.3%
objects.js — 7 unchanged · avg -3.1%
Benchmark Base (ops/sec) PR (ops/sec) Change
create simple object 227,446 218,353 -4.0%
create nested object 120,720 116,160 -3.8%
create 50 objects via Array.from 4,654 4,616 -0.8%
property read 105,140 103,009 -2.0%
Object.keys 75,117 74,068 -1.4%
Object.entries 49,045 47,326 -3.5%
spread operator 95,789 89,619 -6.4%
promises.js — 12 unchanged · avg -3.7%
Benchmark Base (ops/sec) PR (ops/sec) Change
Promise.resolve(value) 315,290 303,860 -3.6%
new Promise(resolve => resolve(value)) 122,748 118,744 -3.3%
Promise.reject(reason) 333,338 314,709 -5.6%
resolve + then (1 handler) 105,679 101,823 -3.6%
resolve + then chain (3 deep) 44,552 42,760 -4.0%
resolve + then chain (10 deep) 14,839 14,490 -2.4%
reject + catch + then 65,903 63,131 -4.2%
resolve + finally + then 57,517 56,029 -2.6%
Promise.all (5 resolved) 23,150 22,467 -3.0%
Promise.race (5 resolved) 24,370 23,272 -4.5%
Promise.allSettled (5 mixed) 18,757 18,130 -3.3%
Promise.any (5 mixed) 23,226 22,229 -4.3%
strings.js — 11 unchanged · avg -4.2%
Benchmark Base (ops/sec) PR (ops/sec) Change
string concatenation 290,078 282,289 -2.7%
template literal 264,933 258,036 -2.6%
string repeat 243,273 238,678 -1.9%
split and join 102,589 101,690 -0.9%
indexOf and includes 107,577 102,621 -4.6%
toUpperCase and toLowerCase 158,411 149,512 -5.6%
slice and substring 98,871 93,555 -5.4%
trim operations 116,913 108,925 -6.8%
replace and replaceAll 138,837 131,956 -5.0%
startsWith and endsWith 85,755 80,403 -6.2%
padStart and padEnd 126,427 121,000 -4.3%

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

🧹 Nitpick comments (2)
units/Goccia.Builtins.Globals.pas (1)

145-154: Avoid double stack capture in BuildErrorObject.
CreateErrorObject now attaches stack, and this block captures again with SkipTop=1, so error constructors compute the stack twice. Consider adding an optional ASkipTop (or CaptureStack flag) to CreateErrorObject and call it with 1 here (and 0 elsewhere). This also keeps AggregateError consistent.

♻️ Proposed refactor
-  Result := CreateErrorObject(AName, Message);
-  Result.Prototype := AProto;
-
-  { Capture stack trace at construction site, skipping the Error constructor frame }
-  if Assigned(TGocciaCallStack.Instance) then
-    Result.AssignProperty('stack',
-      TGocciaStringLiteralValue.Create(
-        TGocciaCallStack.Instance.CaptureStackTrace(AName, Message, 1)));
+  Result := CreateErrorObject(AName, Message, 1);
+  Result.Prototype := AProto;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.Globals.pas` around lines 145 - 154, BuildErrorObject
is double-capturing the stack because CreateErrorObject already sets the 'stack'
property; add an optional parameter to CreateErrorObject (e.g., ASkipTop:
Integer = 0 or ACaptureStack: Boolean = True) so callers can control stack
capture, update this call-site in BuildErrorObject to call CreateErrorObject
with ASkipTop = 1 (or ACaptureStack = True and ASkipTop = 1) and remove the
subsequent manual Result.AssignProperty('stack', ...) block, and update other
callers (including AggregateError creation paths) to pass ASkipTop = 0 (or leave
default) so only one stack capture occurs per error construction.
units/Goccia.CallStack.pas (1)

51-54: Consider defensive nil check or lazy initialization.

Instance returns FInstance directly, which could be nil if Initialize hasn't been called. Callers that assume a non-nil result risk null pointer dereferences.

If intentional (relying on engine startup order), consider adding an assertion or at minimum documenting this precondition. Alternatively, lazy initialization could make this more robust:

♻️ Optional: Lazy initialization approach
 class function TGocciaCallStack.Instance: TGocciaCallStack;
 begin
+  if not Assigned(FInstance) then
+    FInstance := TGocciaCallStack.Create;
   Result := FInstance;
 end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.CallStack.pas` around lines 51 - 54, The
TGocciaCallStack.Instance currently returns FInstance directly which may be nil;
update Instance to guard against that by either (a) performing lazy
initialization (if FInstance is nil call TGocciaCallStack.Initialize or create a
new TGocciaCallStack instance and assign FInstance before returning) or (b)
adding a defensive check that raises/asserts with a clear message when FInstance
is nil (use Assert or raise an exception) so callers won't silently dereference
nil; reference TGocciaCallStack.Instance, FInstance and Initialize when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/built-ins.md`:
- Line 322: The inline code span for the `stack` property contains leading
spaces (the string showing lines like "    at functionName
(filePath:line:col)"), which violates MD038; remove the leading indentation
inside the inline code span or replace that example with a fenced code block
showing the multi-line stack trace. Update the text around the `stack` property
in the sentence that describes the formatted string so it either uses a properly
trimmed inline code span (no leading spaces) or a fenced code block
demonstrating the first line plus the "at functionName (filePath:line:col)"
frames.

In `@units/Goccia.CallStack.pas`:
- Around line 108-110: Clamp negative ASkipTop before computing EffectiveCount
to avoid out-of-bounds reads: ensure ASkipTop is treated as at least 0 (e.g.,
set LocalSkipTop := Max(0, ASkipTop) or explicitly if ASkipTop < 0 then
LocalSkipTop := 0), then compute EffectiveCount := FCount - LocalSkipTop and
clamp EffectiveCount to >= 0; update the block referencing
EffectiveCount/FCount/ASkipTop so any negative ASkipTop cannot cause
EffectiveCount to exceed FCount or produce negative indices.

In `@units/Goccia.Evaluator.pas`:
- Around line 934-953: The evaluator currently mutates the global singleton
TGocciaCallStack via TGocciaCallStack.Instance.Push/Pop inside EvaluateCall (and
EvaluateNewExpression), causing side effects; instead add a CallStack (or
ICallStack) reference to TGocciaEvaluationContext and remove any
TGocciaCallStack.Instance usage from evaluator units—have EvaluateCall and
EvaluateNewExpression accept/use AContext.CallStack to Push/Pop (or better,
expose PushCall/PopCall on TGocciaEvaluationContext) and move the responsibility
for initializing/owning the call stack to the interpreter (which instantiates
and supplies the CallStack on the context), ensuring evaluators remain pure and
no longer touch the singleton.

---

Nitpick comments:
In `@units/Goccia.Builtins.Globals.pas`:
- Around line 145-154: BuildErrorObject is double-capturing the stack because
CreateErrorObject already sets the 'stack' property; add an optional parameter
to CreateErrorObject (e.g., ASkipTop: Integer = 0 or ACaptureStack: Boolean =
True) so callers can control stack capture, update this call-site in
BuildErrorObject to call CreateErrorObject with ASkipTop = 1 (or ACaptureStack =
True and ASkipTop = 1) and remove the subsequent manual
Result.AssignProperty('stack', ...) block, and update other callers (including
AggregateError creation paths) to pass ASkipTop = 0 (or leave default) so only
one stack capture occurs per error construction.

In `@units/Goccia.CallStack.pas`:
- Around line 51-54: The TGocciaCallStack.Instance currently returns FInstance
directly which may be nil; update Instance to guard against that by either (a)
performing lazy initialization (if FInstance is nil call
TGocciaCallStack.Initialize or create a new TGocciaCallStack instance and assign
FInstance before returning) or (b) adding a defensive check that raises/asserts
with a clear message when FInstance is nil (use Assert or raise an exception) so
callers won't silently dereference nil; reference TGocciaCallStack.Instance,
FInstance and Initialize when making the change.

Comment thread docs/built-ins.md Outdated
Comment thread units/Goccia.CallStack.pas Outdated
Comment on lines +934 to +953
if Assigned(TGocciaCallStack.Instance) then
TGocciaCallStack.Instance.Push(CalleeName, AContext.CurrentFilePath,
ACallExpression.Line, ACallExpression.Column);
try
if Callee is TGocciaNativeFunctionValue then
Result := TGocciaNativeFunctionValue(Callee).Call(Arguments, ThisValue)
else if Callee is TGocciaFunctionValue then
Result := TGocciaFunctionValue(Callee).Call(Arguments, ThisValue)
else if Callee is TGocciaBoundFunctionValue then
Result := TGocciaBoundFunctionValue(Callee).Call(Arguments, ThisValue)
else if Callee is TGocciaClassValue then
Result := TGocciaClassValue(Callee).Call(Arguments, ThisValue)
else
begin
SafeOnError(AContext, Format('%s is not a function', [Callee.TypeName]), ACallExpression.Line, ACallExpression.Column);
Result := TGocciaUndefinedLiteralValue.UndefinedValue;
end;
finally
if Assigned(TGocciaCallStack.Instance) then
TGocciaCallStack.Instance.Pop;
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 | 🟠 Major

Evaluator purity breach: CallStack mutations inside evaluator functions.
Pushing/popping the singleton introduces global side effects in EvaluateCall/EvaluateNewExpression, which conflicts with the evaluator purity rule. Please move stack tracking to the interpreter or a value/context-owned object (e.g., pass a CallStack instance via TGocciaEvaluationContext) so evaluator logic remains side-effect free.

Based on learnings: Evaluator functions in Goccia.Evaluator.pas and its sub-modules must be pure functions with no side effects. State changes happen through the scope and value objects passed via TGocciaEvaluationContext, never through evaluator-internal state.

Also applies to: 1592-1684

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

In `@units/Goccia.Evaluator.pas` around lines 934 - 953, The evaluator currently
mutates the global singleton TGocciaCallStack via
TGocciaCallStack.Instance.Push/Pop inside EvaluateCall (and
EvaluateNewExpression), causing side effects; instead add a CallStack (or
ICallStack) reference to TGocciaEvaluationContext and remove any
TGocciaCallStack.Instance usage from evaluator units—have EvaluateCall and
EvaluateNewExpression accept/use AContext.CallStack to Push/Pop (or better,
expose PushCall/PopCall on TGocciaEvaluationContext) and move the responsibility
for initializing/owning the call stack to the interpreter (which instantiates
and supplies the CallStack on the context), ensuring evaluators remain pure and
no longer touch the singleton.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

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

241-241: ⚠️ Potential issue | 🟡 Minor

Inconsistent ASkipTop for AggregateError.

BuildErrorObject passes ASkipTop=1 to exclude the constructor frame, but AggregateErrorConstructor calls CreateErrorObject directly without this parameter. This means AggregateError stack traces will include an extra constructor frame compared to other error types.

🔧 Proposed fix for consistency
   { Step 2: Let O = OrdinaryCreateFromConstructor }
-  ErrorObj := CreateErrorObject('AggregateError', Message);
+  ErrorObj := CreateErrorObject('AggregateError', Message, 1);
   ErrorObj.Prototype := FAggregateErrorProto;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.Globals.pas` at line 241, AggregateErrorConstructor
currently calls CreateErrorObject without the ASkipTop parameter, causing
inconsistent stack traces vs. BuildErrorObject which uses ASkipTop=1; update
AggregateErrorConstructor to call CreateErrorObject with ASkipTop=1 (or
otherwise propagate the same skip behavior) so the constructor frame is excluded
and AggregateError stack traces match other error types.
🧹 Nitpick comments (1)
docs/built-ins.md (1)

324-328: Add language identifier to fenced code block.

The fenced code block lacks a language identifier, triggering MD040. Since this is a plain text example, adding text or plaintext would satisfy the linter.

📝 Proposed fix
-```
+```text
 ErrorName: message
     at functionName (filePath:line:col)
     at outerFunction (filePath:line:col)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/built-ins.md around lines 324 - 328, The fenced code block in
docs/built-ins.md showing the stack trace lacks a language identifier which
triggers MD040; update that fenced block (the triple-backtick block that
contains "ErrorName: message" and the subsequent "at functionName..." lines) to
include a plain text language identifier such as text or plaintext (e.g.,
replace withtext) so the linter recognizes it as plain text.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @units/Goccia.Builtins.Globals.pas:

  • Line 241: AggregateErrorConstructor currently calls CreateErrorObject without
    the ASkipTop parameter, causing inconsistent stack traces vs. BuildErrorObject
    which uses ASkipTop=1; update AggregateErrorConstructor to call
    CreateErrorObject with ASkipTop=1 (or otherwise propagate the same skip
    behavior) so the constructor frame is excluded and AggregateError stack traces
    match other error types.

Nitpick comments:
In @docs/built-ins.md:

  • Around line 324-328: The fenced code block in docs/built-ins.md showing the
    stack trace lacks a language identifier which triggers MD040; update that fenced
    block (the triple-backtick block that contains "ErrorName: message" and the
    subsequent "at functionName..." lines) to include a plain text language
    identifier such as text or plaintext (e.g., replace withtext) so the
    linter recognizes it as plain text.

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@frostney frostney merged commit 6b8e647 into main Feb 20, 2026
4 checks passed
@frostney frostney deleted the feat-add-stacktrace branch February 20, 2026 23:49
@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