Skip to content

Collapse the call-site debug stop that duplicates its own statement - #82

Merged
revarbat merged 3 commits into
mainfrom
call-site-expr-level
Aug 8, 2026
Merged

Collapse the call-site debug stop that duplicates its own statement#82
revarbat merged 3 commits into
mainfrom
call-site-expr-level

Conversation

@revarbat

@revarbat revarbat commented Aug 8, 2026

Copy link
Copy Markdown
Member

A breakpoint on x = f(y); fired twice per execution. Measured on a loop calling a module four times, a breakpoint on the assignment line stopped eight times: n=0, n=0, n=1, n=1, n=2, n=2, n=3, n=3.

Two statement-level checkpoints land on that line at the same depth, back to back — the assignment, then the call site before descending into the callee.

Consumers cannot collapse this themselves. Fast-continue skips the checkpoints in between, so from outside the duplicate is indistinguishable from a genuine second visit. An attempt to suppress it in BelfrySCAD silently dropped real loop iterations — n=1,2,3 never stopped at all.

Only the duplicate is dropped

The first version of this branch marked every call-site stop expression-level. That fixed the breakpoint but cost something worth keeping: stepping stopped pausing at a call inside an expression, so a list comprehension no longer alternated for-line/call-line.

The two are separable. A call-site stop is a duplicate only when it lands on the same line and depth as the statement checkpoint already in progress there. A call on its own line still stops.

Tracked per call depth, not as a single last-checkpoint slot: a = [f(1), f(2), f(3)]; runs each callee's body checkpoints in between, and treating those as the caller moving to a new statement let the second and third call sites fire again — three stops instead of one.

checkDebug gains an internal callSite argument. DebugHookFn's signature is untouched, so no consumer or test lambda changes.

Verification

  • 800 C++ tests pass, including a new CallSiteOnItsOwnLineStillStops pinning the alternation.
  • Built into BelfrySCAD: breakpoint fires 4 times for 4 calls, and the list-comp step sequence is [3, 4, 3, 4, 3, 4, 3, 4] — alternation intact.
  • BelfrySCAD: 653 tests pass. Its list-comp alternation test passes unchanged; only the three same-line cases needed updating.

Four bytecode-compiler tests count hook stops as a compiled-vs-interpreted fingerprint rather than for their debug meaning; each drops by exactly one and still discriminates (2 compiled vs 4 interpreted).

🤖 Generated with Claude Code

revarbat and others added 3 commits August 8, 2026 12:32
A breakpoint on `x = f(y);` fired twice per execution. Two statement-level
checkpoints land on that line at the same depth, back to back: the
assignment, then the call site before descending into the callee. Every
consumer that treats a statement-level stop as a breakpoint hit therefore
pauses twice -- BelfrySCAD's debugger stuttered on Continue, and stepping
into such a call took two `into`s because the first consumed the call-site
stop without moving.

Consumers cannot collapse this themselves. Fast-continue skips the
checkpoints in between, so from outside, the duplicate is indistinguishable
from a genuine second visit to the same line -- an attempt to suppress it
in BelfrySCAD silently dropped real loop iterations.

The stop is kept, since stepping into a call still needs it; it is simply
labelled as the sub-expression it is. A call inside a larger statement is
not a statement of its own.

This is a deliberate divergence from the Python reference, which reports
the stop as statement-level. The parity tests carried that behaviour
verbatim from a recorded run, so they are updated with the reason rather
than quietly adjusted. One consequence is intended and worth knowing:
stepping no longer pauses at a call site inside an expression, so a list
comprehension stops once per iteration at the `for` rather than
alternating for-line/call-line. The callee's own body-entry stop is
untouched, so stepping into the function still works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Supersedes the previous commit on this branch, which marked every
call-site stop expression-level. That fixed the double breakpoint but
cost something worth keeping: stepping stopped pausing at a call inside
an expression, so a list comprehension no longer alternated for-line/
call-line.

The two are separable. A call-site stop is a duplicate only when it lands
on the same line and depth as the statement checkpoint already in
progress there -- `a = double(5);`, where the assignment and the call are
one line. A call on its own line, as in a list comprehension body, is not
a duplicate and still stops.

Tracked per call depth rather than as a single last-checkpoint slot:
`a = [f(1), f(2), f(3)];` runs each callee's body checkpoints in between,
and treating those as the caller moving on to a new statement let the
second and third call sites fire again -- three stops on the line instead
of one.

checkDebug gains an internal callSite argument; DebugHookFn's signature is
untouched, so no consumer or test lambda changes.

Four bytecode-compiler tests count hook stops as a compiled-vs-interpreted
fingerprint rather than for their debug meaning; each drops by exactly one
and still discriminates (2 compiled vs 4 interpreted).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat revarbat changed the title Report a function call site as an expression-level debug stop Collapse the call-site debug stop that duplicates its own statement Aug 8, 2026
@revarbat
revarbat merged commit ea25a62 into main Aug 8, 2026
3 checks passed
@revarbat
revarbat deleted the call-site-expr-level branch August 8, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant