Skip to content
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

Correctly colorize parameterized CE builders #16052

Merged
merged 6 commits into from
Oct 10, 2023

Conversation

brianrourkeboll
Copy link
Contributor

@brianrourkeboll brianrourkeboll commented Sep 26, 2023

Change

  • Enable correct colorization of parameterized custom computation expression builders, e.g.:
    • builder<…> { … } (type application).
    • builder () { … } (function application).1

Edit: It turns out that there was a very old issue for this, which might as well be linked: #110.

Example

In the following example, builder is now correctly colorized as a keyword (like async and any other custom builder that doesn't have generic type parameters).

type Builder<'T> () =
    member _.Return x = x
    member _.Run x = x

let builder<'T> = Builder<'T> ()

let x = builder { return 3 }
let y = builder<int> { return 3 }
let z = builder<_> { return 3 }

Before

image

After

image

Tests

SyntacticClassificationServiceTests seems to be too low-level to catch this change. I have added tests for FSharpSymbolUse.IsFromComputationExpression under Tests.Service.Symbols.ComputationExpressions.

Footnotes

  1. This does not apply to direct invocations of constructors, e.g., Builder () { … }. I think this behavior makes sense, since a Pascal-case constructor doesn't "feel" like it should get keyword coloring, even in this context.

    I can think of even funkier valid cases where keyword coloring still does not apply and almost certainly (?) shouldn't…

    type System.Int32 with
        member _.Return x = x
        member _.Run x = x
    
    let uh = 99 { return 3 }
    
    type List<'T> with
        member _.Return x = x
        member _.Run x = x
    
    let yeah = [1..10] { return 3 }
    

* This change enables correct colorization of custom computation
  expression builders with generic type parameters, e.g., `builder<…>`.
@brianrourkeboll brianrourkeboll requested a review from a team as a code owner September 26, 2023 23:17
@psfinaki
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@psfinaki
Copy link
Member

Ohh thanks - that looks useful!

As for testing - I haven't done much in the colorization space but to me SyntacticClassificationServiceTests look about right. Why do you think they won't be enough?

@auduchinok
Copy link
Member

I'm realizing after the fact that this will also apply to functions that return builders, e.g.,
I believe this should be fine, unless there's a compelling reason to keep distinct coloring for such builder functions.

Hm, this doesn't sound right to me. We only colorize the builder values as keywords when they are used in an actual sequence expression application. In some of the tools this is done via checking symbolUse.IsFromComputationExpression. There're other features that depend on this flag: code completion may look at it for sorting lookup items, for instance. Changing this behaviour for unrelated symbols (i.e. a function that happen to return a builder type) is something we should try to avoid, as it'll change/break the behaviour of unrelated features.

@vzarytovskii
Copy link
Member

I'm realizing after the fact that this will also apply to functions that return builders, e.g.,
I believe this should be fine, unless there's a compelling reason to keep distinct coloring for such builder functions.

Hm, this doesn't sound right to me. We only colorize the builder values as keywords when they are used in an actual sequence expression application. In some of the tools this is done via checking symbolUse.IsFromComputationExpression. There're other features that depend on this flag: code completion may look at it for sorting lookup items, for instance. Changing this behaviour for unrelated symbols (i.e. a function that happen to return a builder type) is something we should try to avoid, as it'll change/break the behaviour of unrelated features.

Tbh, I'm not horribly against it, however this should be done in tooling instead (in semantic highlighting, I presume).

@auduchinok
Copy link
Member

Tbh, I'm not horribly against it, however this should be done in tooling instead (in semantic highlighting, I presume).

Yes, and we should have tests that show that symbolUse.IsFromComputationExpression is not changed if we decide to change the highlighting.

@brianrourkeboll
Copy link
Contributor Author

brianrourkeboll commented Sep 27, 2023

@auduchinok

I'm realizing after the fact that this will also apply to functions that return builders, e.g.,
I believe this should be fine, unless there's a compelling reason to keep distinct coloring for such builder functions.

Hm, this doesn't sound right to me. We only colorize the builder values as keywords when they are used in an actual sequence expression application. In some of the tools this is done via checking symbolUse.IsFromComputationExpression. There're other features that depend on this flag: code completion may look at it for sorting lookup items, for instance. Changing this behaviour for unrelated symbols (i.e. a function that happen to return a builder type) is something we should try to avoid, as it'll change/break the behaviour of unrelated features.

This change gives such functions keyword coloring (and IsFromComputationExpression returns true) only in a context like builder () { … }, not in any other scenario. See:

image

However, if we still think keyword colorization shouldn't apply in such cases, I can make this change:

- | Expr.App (funcExpr = Expr.Val (vref, _, m))
+ | Expr.App (funcExpr = Expr.Val (vref, _, m); args = [])

args = [] means that it will match for type applications (like builder<…>) but not for function applications (like builder ()).

@auduchinok
Copy link
Member

This change only gives such functions keyword coloring (and IsFromComputationExpression returns true) only in a context like builder () { … }, not in any other scenario.

This looks nice! Would be good to have the tests anyway 🙂

@brianrourkeboll
Copy link
Contributor Author

brianrourkeboll commented Sep 27, 2023

@psfinaki

Ohh thanks - that looks useful!

As for testing - I haven't done much in the colorization space but to me SyntacticClassificationServiceTests look about right. Why do you think they won't be enough?

If I add a test like this:

[<Theory>]
[<InlineData("builder")>]
[<InlineData("builder<int>")>]
[<InlineData("builder<_>")>]
member this.CustomBuilder_GenericTypeParameters builder =
    this.VerifyColorizerAtEndOfMarker(
        fileContents = $"
type Builder<'T> () =
    member _.Return x = x
    member _.Run x = x

let builder<'T> = Builder<'T> ()

let x = (*marker*)%s{builder} {{ return 3 }}
",
        marker = "(*marker*)b",
        defines = [],
        classificationType = ClassificationTypeNames.Keyword)

…it fails because builder is classified as an identifier, not a keyword. I think that's because those tests are relying only on the tokenizer, which doesn't classify computation expression builder values differently, as far as I can tell.

* Test that FSharpSymbolUse.IsFromComputationExpression only returns
  true in `builder { … }`, `builder () { … }`, `builder<…> { … }`.
@brianrourkeboll
Copy link
Contributor Author

@auduchinok

This change only gives such functions keyword coloring (and IsFromComputationExpression returns true) only in a context like builder () { … }, not in any other scenario.

This looks nice! Would be good to have the tests anyway 🙂

Are these similar to what you were thinking? 9b7579e

@brianrourkeboll brianrourkeboll changed the title Correctly colorize custom CE builders with generic type parameters Correctly colorize parameterized CE builders Sep 27, 2023
* For a computation expression builder type `Builder`, we don't expect
  keyword highlighting (or `FSharpSymbolUse.IsFromComputationExpression`
  to return true) in `type Builder () = …` or `Builder () { … }`.
@T-Gro T-Gro enabled auto-merge (squash) October 10, 2023 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

5 participants