Skip to content

Extend FSharpDiagnostic with a property bag for structured relevant data #1094

@baronfel

Description

@baronfel

I propose we augment the current definition of the FSharpDiagnostic structure with a property bag for carrying extra data relevant to each particular diagnostic. The intent would be to provide contextually-relevant data for things like code fixers, replacing potentially error-prone logic that must be implemented separately for each consumer of the compiler services with accurate data provided directly by the compiler at the point the diagnostic is raised. This data would be initially useful for implementing code fixes, but could also feasibly be used to drive more detailed command-line or editor-focused displays of diagnostics, as seen in example like this one from the Rust compiler, where useful data points like expressions and lifetime scopes are displayed to the user in the context of their code.

A worked example

I was working on an fsautocomplete codefix to automatically address diagnostic 3368 and 3369 by inserting a space between the expressions as a minimal fix. The current implementation of this fix is a) not exhaustive and b) error prone because it relies on imperfect heuristics like text comparisons. While I could code a more robust parse tree walk to find the actual sub-ranges of the left and right expression in these particular cases, implementing this codefix would be made much easier if there was a property bag with something like leftExprRange: Range and rightExprRange: Range data in it. At that point the codefix would be very simple in almost any editor:

  • convert the FCS range to the editor-appropriate range
  • insert a space at the end of the left-most range

Another example

The suggest another identifier name codefix is similarly gross. It looks for error message with a particular format - in this case the list of suggested identifier names that @forki contributed so long ago - and parses those names out of the message itself to present suggestions to the user. While this is an excellent user-facing message, making this machine-handleable would require having a field like suggestions: string [] in the property bag of a diagnostic and generating the replacements for the diagnostic range from that.

Proposed Technical Plan

Broadly, if an IDictionary<string, obj> was added to the FSharpDiagnostic type, the next question would be 'where would the data come from'. My proposal is to allow the introduction of various contextually-specific exception types in a progressive manner to the code base, and in the primary constructors of those exceptions add the named, structured data to the underlying Data property bag on the System.Exception type. Later, when FSharpDiagnostic.CreateFromException and friends are passed these exceptions, the Data bag can be added to the diagnostic.

I've plumbed a proof of concept of this style of work through FCS and done a sample end-to-end using FSAC, and the concept seems to work well in execution. I'd like to collect feedback on this approach, and talk about potential points of conflict and integration with the various tooling consumers. I consider this a Tooling suggestion and not a full language RFC, as many of the changes would be internal to the compiler itself, with the only public API changes being directly related to adding the property bag to FSharpDiagnostic and the associated creation members on that type.

Status Quo

The existing way of approaching this problem in F# is to either re-construct the compiler logic with our own syntax/typed tree analysis or to use crude heuristics about the error code and message to recreate the desired fix behavior.

Pros and Cons

The advantages of making this adjustment to F# are potentially more detailed and actionable error messages, as well as easier implementation of code fixes related to diagnostics for editor and tooling authors.

The disadvantages of making this adjustment to F# are mildly increased surface area, a potentially new kind of API contract that would need tracking to prevent breakages, and a risk of internal compiler data types being leaked out via the data bag.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S/M

Related suggestions: None that I am aware of.

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design (NB: It is technically a breaking API change, but FCS does not have a stable API guarantee at the moment anyway)
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions