Collapse the call-site debug stop that duplicates its own statement - #82
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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,3never 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.checkDebuggains an internalcallSiteargument.DebugHookFn's signature is untouched, so no consumer or test lambda changes.Verification
CallSiteOnItsOwnLineStillStopspinning the alternation.[3, 4, 3, 4, 3, 4, 3, 4]— alternation intact.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