-
Notifications
You must be signed in to change notification settings - Fork 789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Breakpoints on pipeline stages #11957
Conversation
Some unresolved questions:
|
As far as the names generated for locals in pipelines, IIRC the C# expression evaluator has issues with shadowed names, is it possible to generate unique names (or close-to-unique?) for the intermediate values to sidestep this? |
@dsyme you can debug creating a completion list as a decent test:
|
It does, though as things stand the names aren't actually usable in expression evaluation (
We could in theory generate unique names ( |
Great point. In VSCode at least (unsure about VS) method returns do have a local that's rendered something like:
so there might be an option there for us:
|
If the expression evaluator can support F#, then we can use ``pipe@input`` because |
Folks, could you choose between these as names for generated locals please? I need some guidance here: Option 1 - succinct
Option 2 - usable in C# expression evaluator but confusing because they look like F# values
Option 3 - uppercase start, spaces with text
Option 4 - something else, please spec |
Can we generate usable names in a similar way to how type parameter names are generated, i.e. keep track of used names? Or, say, similar to function parameter names. All local names are known at this stage of a compilation, aren't they? |
I mean, we could. But I think a bigger problem is cognitive - people look at "pipe1_input" and think it's an F# identifier and wonder what the heck it is. So I suspect that, like return values from functions, it's better to produce text like "Pipe #1 input" or "pipe1-input" |
I agree, I think the names should unambiguously not be F# identifiers if possible. So Option 3 would be my ideal of the ones presented so far. |
Can this technique also be used in other expressions with implicit "parameters"? If yes, it'd be great to take cases like |
Has it been problem for On the other hand, I'd prefer being able to use these identifiers in evaluating expressions in debugger over anything else, so I think I'm slightly biased. I really think evaluating expressions is an important part here. 🙂 |
Yes, any of those identifiers are sloppy and poor tooling I think. I don't want to repeat that |
If we leave the option for future F# support in expression evaluators, I'd say
Because |
This symbol isn't reserved for identifier names |
Sounds a bit like "security through obscurity" to me, and if the engineering efforts are similar to other operators, I'd find it preferable to enable similar debugging experience, even if the operator is not favored. Improving debugging experience, especially in messy codebases is a different concern from "code should be nice at all times".
Despite the shadowing is indeed confusing in that context, I think the generated names won't help, and it will reduce the incentive for debugger infrastructure to ever improve to support shadowed names. IIRC, in the "locals" tab, it is possible to see all the different symbols, despite they share the same identifier, so I'd be in favour of keeping status quo and tracking the issue upstream on the debugger side, even if things stay as they are. |
Part of the problem is that in expr1 <| expr2 The evaluation order is
This doesn't lead to a great natural stepping/breakpoint sequence. Presumably the steps would cover the following ranges:
This will result in a lot of leaping back and forth, and when placing a breakpoint it will be somewhat random what you get In contrast, for expr1 |> expr2 The evaluation order is
This gives a simplfied two-step natural stepping/breakpoint sequence down the pipeline:
This is because As an aside, in debug code the separation of stages down the pipeline may potentially create more closures, I'm not sure, I will check. It doesn't necessarily have to but it is important to check. |
These identifiers are already seen in lots of places in functions/patterns when debugging F# code, and I assumed people learn what they are or simply ignore them. Having a similar naming would at least be consistent (which I believe helps in learning a language) and, arguably, won't make things worse than they already are. |
I want to go over the whole lot and make them better :) That is, I'm very open to discussing what "better" is! :) |
Not that serious: in debug mode, we could wrap these values into structs that would have a good debugger presentation (explaining what they are) and a name that would be accessible in evaluations. 🙂 |
I wonder if the pipe symbol itself could work for generated locals, |
@dsyme What do you think about having the pipe start line number instead of or in addition to the pipe number? The line where a pipe starts might be a bit more helpful than the number of the pipe. |
And to be consistent with stack traces for lambdas as well as for future usage from F# support in the debugger evaluator, we can use |
This seems reasonable
Perhaps, it seems ok, though we could consider changing that to something clearer, or use C#-like naming for closure classes |
Co-authored-by: Hadrian Tang <hadrianwttang@outlook.com>
Here's a short gif video of vscode debugging stepping through this feature at the current commit: The major takeaway is that things mostly Just Work, with some oddities around how putting breakpoints in the lambdas work. You can see the breakpoints at the start of the line bind to the first expression in the lambda instead of the fancy new points, but that is likelyt o be out of our control in Ionide. Luckily when you step out the lambda you get the good pipeline locals again. |
Is Ionide using FCS ValidateBreakpointLocation for the language service? If not:
VBPL should be exposed as part of the language server protocol and AKAIK should be a standard sort of thing for languages to implement in VS Code. |
There's a separate specification for debugging in VSCode and it does have a request for setting a breakpoint, but in Ionide we are completely reliant on the executable provided by microsoft that implements this protocol. This is shipped and activated in Omnisharp and is closed source, so I personally have zero visibility into its capabilities or extensibility. |
I've added systematic tests for ValidateBreakpointLocation and this is ready. |
Yes, ultimately we will need our own F# implementation of this - otherwise the debug experience (before launching debug) will be messed up. It won't be a complex thing - presumably we can look at the TypeScript or other implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a big change, but it's relatively small with a large amount of impact. :) - Most of the changes in the PR are updating the baseline tests.
I only had a minor nit on the name menv
.
This adds breakpoints and stepping on stages in F# forward piping in debug code.
Spec (debug codegen only)
Breakpoint validity
For
expr |> f1 |> f2 ... |> fLast
, a breakpoint is valid on the expression that is an input to a pipeline, and any "stage" of a pipeline.Similarly, for
(expr1, expr2) ||> f1 |> f2 ... |> fLast
and (expr1, expr2, expr3) |||> f1 |> f2 ... |> fLast` , a breakpoint is valid on each of the input expressions, and any "stage" of a pipeline.Sequence point emit
A corresponding sequence point is emitted in .NET IL before the evaluation of
expr
and before applying each off1
,f2
etc.Locals
A local value
Pipe #m input #n @ line
is shown within f1, f2, f3, ...for the corresponding value ofexpr
. The pipe number is the occurrence of piping within the method. Then
it the input number (elided if only one input). Theline
is the line number where the input expression occurs.A local value
Pipe #m stage 1 @ line
is shown within f2, f3, ... for the corresponding value ofexpr |> f1
A local value
Pipe #m stage 2 @ line
is shown within f3, ... for the corresponding value ofexpr |> f1 |> f2
etc.
The names of the locals are emitted in IL code and are not localized (as that would make compiler output dependent on localization).
Sample screen capture below (stepping) - the local values names differ slightly from the above spec
a.Debugging.-.Microsoft.Visual.Studio.Administrator.2021-08-12.15-17-03.mp4
And with breakpoints:
a.-.Microsoft.Visual.Studio.Administrator.2021-08-12.15-54-23.mp4