dotnet / dotnet/fsharp

Request for debug team - always stepping in to a call

Open
#13,342 6 comments 0 reactions 0 assignees View on GitHub
Area-Debug Feature Request
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 22h
Merged PRs (30d)
144

Description

I've been looking at debugging issues for computation expressions as part of #13339 . It's ultimately a request for the .NET debugger team but I'll write it up here first. cc @gregg-miskelly who the .NET team suggested as a contact in the Visual Studio debugger.

I believe we should look into a new attribute [DebuggerStepIntoAttribute ](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debuggerstepthroughattribute?view=net-6.0) (or something similar) to partner DebuggerStepThroughAttribute, where putting the new attribute on a method has the effect of changing a F10 "Step Over" into a "Step Into".

This would have use in debugging for F# computation expressions, for expressing the "calling the continuation that gives the rest of the computation" part of a bind operation. It would also have many other uses in F# code, where "the rest of the method" is sometimes compiled as a separate method (this is an optimization F# sometimes performs for large methods).

We actually also need this to somehow apply to an indirect call. For example, the typical situation is that

```fsharp
let f() =
cancellable {
print "hello"
let! res1 = SomeOtherCancellable() <--- F10 Step Over here
print "world"
}
```
From the user's point of view the sequence points are:
```fsharp
let f() =
cancellable {
// Sequence Point 1
print "hello"
// Sequence Point 2 <--- F10 Step Over here
let! res1 = SomeOtherCancellable()
// Sequence Point 3
print "world"
}
```
And "Step Over" should go through these three sequence points. However the code gets compiled as follows - each chunk becomes a function that gets called, and these are then stitched together.

```fsharp
let f() =
cancellable.Delay(fun () ->
print "hello"
cancellable.Bind( SomeOtherCancellable(), fun() ->
print "world"
)
)
```
After inlining and some flattening of `cancellable.Bind` becomes
```fsharp
let f() =
Cancellable(fun ct ->
// Sequence Point 1
print "hello"
// Sequence Point 2
let computation1 = SomeOtherCancellable()
let res1 = computation1.Invoke(ct)
let computation2 =
Cancellable(fun ct ->
// Sequence Point 3
print "world")
computation2.Invoke(ct) <--- need to step into here to reach Sequence Point 3
)
```

The effect we want is that a Step Over at Sequence Point 2 would step over al the invocations and then actially Step **Into** `computation2.Invoke(ct)`. Exactly how we achieve that I'm not sure. Somehow we need to mark that call as "always step into".

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.