Fix F# computation expression breakpoints resolving to wrong method#222
Open
bryancostanich wants to merge 2 commits intoSamsung:masterfrom
Open
Fix F# computation expression breakpoints resolving to wrong method#222bryancostanich wants to merge 2 commits intoSamsung:masterfrom
bryancostanich wants to merge 2 commits intoSamsung:masterfrom
Conversation
Minimal reproduction case for Samsung#221: line breakpoints inside F# computation expression blocks don't fire because GetMethodTokensByLineNumber() doesn't search closure class methods. Includes automated DAP and CLI test scripts plus PDB analysis.
…nside
When a nested method (e.g., F# computation expression closure) is fully
contained within an outer method's sequence point range, the breakpoint
resolver unconditionally picked the outer method. This caused breakpoints
on lines inside F# CE blocks like `test "name" { ... }` to slide to the
module initializer (.cctor) instead of the closure body.
The fix checks whether sourceLine falls within the nested method's range.
If so, the breakpoint is set on the nested method. If sourceLine is before
the nested range (e.g., C# await with inline lambda), the outer method is
used as before.
Also replaces assertions in NestedInto() that crash on F# compiler-
generated methods sharing start/end positions, returning false instead.
Repro: repro/fsharp-ce-breakpoints/
Issue: Samsung#221
viewizard
reviewed
Apr 3, 2026
Comment on lines
+70
to
+73
| if (startLine == other.startLine && startColumn == other.startColumn) | ||
| return false; | ||
| if (endLine == other.endLine && endColumn == other.endColumn) | ||
| return false; |
Member
There was a problem hiding this comment.
Is this really works with C# constructors?
Member
There was a problem hiding this comment.
I mean all C# tests are passed?
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.
Summary
Fixes #221. Line breakpoints inside F# computation expression (CE) blocks resolve to the wrong method when the CE closure is fully contained within an outer method's sequence point range.
Before: A breakpoint on line 17 inside
test "name" { let a = 100 ... }slides to line 15 (the module static constructor), becauseResolveBreakPointsunconditionally picks the outer method when the nested closure is fully contained.After: The breakpoint correctly resolves to the closure method (
ceTests@17.Invoke) at line 17.Root cause
In
SymbolReader.ResolveBreakPoints(), when a nested method is fully contained within an outer method's sequence point range, the code always chose the outer method. This was designed for C#await Parallel.ForEachAsync(... async () => ...)patterns where the breakpoint line is the outer call. But for F# CEs, the outer.cctorsequence point spans the entire expression block, and the user's breakpoint line is inside the closure body.Changes
src/managed/SymbolReader.cs: When a nested method is fully contained in the outer SP, check ifsourceLine >= nested_start_p.StartLine. If so, resolve to the nested method (user wants the closure body). Otherwise, resolve to the outer method (original behavior for C# lambdas).src/metadata/modules_sources.h: Replace assertions inNestedInto()with graceful returns. The F# compiler generates closure classes that can share start/end positions with other methods, which triggers the assertion in Debug builds. The assertions are not load-bearing — returning false for identical positions is the correct semantic.Reproduction
Repro project in
repro/fsharp-ce-breakpoints/with automated DAP test script (test-dap.py).Test plan