Skip to content

Fix F# computation expression breakpoints resolving to wrong method#222

Open
bryancostanich wants to merge 2 commits intoSamsung:masterfrom
bryancostanich:fix/fsharp-ce-breakpoints
Open

Fix F# computation expression breakpoints resolving to wrong method#222
bryancostanich wants to merge 2 commits intoSamsung:masterfrom
bryancostanich:fix/fsharp-ce-breakpoints

Conversation

@bryancostanich
Copy link
Copy Markdown

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), because ResolveBreakPoints unconditionally 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 .cctor sequence 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 if sourceLine >= 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 in NestedInto() 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).

# Before fix:
Line  8 (regular function): HIT
Line 17 (CE block body):    MISSED — slid to line 15 (.cctor)

# After fix:
Line  8 (regular function): HIT
Line 17 (CE block body):    HIT — ceTests@17.Invoke()

Test plan

  • Minimal F# repro (Expecto CE block) — breakpoint fires in DAP mode
  • Minimal F# repro — breakpoint fires in CLI mode
  • Regular (non-CE) F# breakpoints still work
  • C# breakpoints regression (test suite blocked by netcoreapp3.1 arch issues on arm64)

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
Comment on lines +70 to +73
if (startLine == other.startLine && startColumn == other.startColumn)
return false;
if (endLine == other.endLine && endColumn == other.endColumn)
return false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this really works with C# constructors?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean all C# tests are passed?

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.

F# computation expression line breakpoints don't fire (closure class methods not searched)

2 participants