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

feat: Provide formals and declarations type highlighting. #1630

Merged
merged 32 commits into from
Dec 20, 2021

Conversation

MikaelMayer
Copy link
Member

@MikaelMayer MikaelMayer commented Dec 4, 2021

I added hovering type annotations to formals (in parameters)
image

and out parameters
image

Local variable declarations.
image

Bound variables in set comprehensions
image

Bound variables in forall expressions
image

Bound variables in exists expressions
image

Bound variables in map comprehensions
image

Bound variables in lambdas
image

By method bodies in functions
image

Let expressions:
image
image

Note that not only this adds hovering annotations, but it also adds the ability to jump to the definition for all variables declared in set and map comprehensions, forall and exists expressions, and let expressions, using either the right-click menu or CTRL+click on the variable.

Previously, hovering such variables either at declaration sites or even on their use site (for comprehensions) did not show any hint about their type, even in specifications.
The reason why this is useful is that it removes the need to investigate a method call's signature to know what is the type of a variable that is declared and assigned at the same time, and for formals for consistency.

Copy link
Member

@camrein camrein left a comment

Choose a reason for hiding this comment

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

I like this improvement since it's sometimes useful to see what type the compiler infers.

I prefer that we stick to SingleOrDefault since for the lookup there must not be any overlapping ranges because a designator should not have multiple lookup entries.
Moreover, please add a new test for the now supported Formal lookup.

var symbolsAtPosition = LookupTree.Query(position);
// TODO: Figure out why duplication crashes SingleOrDefault before using FirstOrDefault
// Use case: function f(a: int) {}, and hover over a.
symbol = symbolsAtPosition.FirstOrDefault();
Copy link
Member

Choose a reason for hiding this comment

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

Since there should only be one LookUp entry per designator, one designator is registered twice or overlaps with the range of another.
This should be fixed as this probably hides another error.

@MikaelMayer
Copy link
Member Author

I like this improvement since it's sometimes useful to see what type the compiler infers.

I prefer that we stick to SingleOrDefault since for the lookup there must not be any overlapping ranges because a designator should not have multiple lookup entries. Moreover, please add a new test for the now supported Formal lookup.

Thank you for letting me know how much it was important to keep SingleOrDefault. I will definitely sort this out and when this works, I'll confirm the behavior using a test and un-draft the PR.

@MikaelMayer
Copy link
Member Author

I did not find documentation about how to run the tests in DafnyLanguageServer.Test
How to run them?

@camrein
Copy link
Member

camrein commented Dec 7, 2021

I did not find documentation about how to run the tests in DafnyLanguageServer.Test
How to run them?

I'm sorry that I haven't documented this yet. I tend not to document things that are standard for the utilized platform. That was the same for the VSCode extension until Robin asked for documentation.

You can either test it through the IDE. For example, in Visual Studio you can open the Test Explorer view:
image
This view allows you to run (and debug) the whole test suite using the Play button or individual tests by right-clicking them.

Alternatively, you can execute the tests through the command line:

dotnet test .\Source\DafnyLanguageServer.Test\DafnyLanguageServer.Test.csproj

It's important to note that you may have to place z3 in the build directory of the tests. That's DafnyLanguageServer.Test/bin/Debug/net5.0 and DafnyLanguageServer.Test/bin/Release/net5.0, depending on your build settings. Well, I guess that at least this special setup requirement should be documented.

@MikaelMayer
Copy link
Member Author

Thanks for these instructions. I think they are relevant to any new user so that I added them in the wiki
https://github.com/dafny-lang/dafny/wiki/INSTALL#running-dafnylanguageserver-and-dafnypipeline-tests

@MikaelMayer MikaelMayer marked this pull request as ready for review December 7, 2021 16:11
@MikaelMayer MikaelMayer self-assigned this Dec 7, 2021
@MikaelMayer MikaelMayer marked this pull request as draft December 7, 2021 17:23
MikaelMayer and others added 4 commits December 7, 2021 12:41
Fixed function body hovering.
ComprehensionSymbol is now a ScopeSymbol
Support for scopes in method and function specifications.
@MikaelMayer MikaelMayer marked this pull request as ready for review December 8, 2021 22:40
@MikaelMayer
Copy link
Member Author

This PR is ready for review @camrein . Nothing new specific on my agenda. I did many upgrades in the highlighting system (see updated PR message)

Copy link
Member

@camrein camrein left a comment

Choose a reason for hiding this comment

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

That's a great improvement.
I haven't fully reviewed it since I have the feeling some changes might be irrelevant.

In general, the language server does not have any abbreviations so I'd prefer to keep this style. I have seen some things that would need improvement (probably in another PR) to make the design choices a bit more clear.

Feel free to tag me once you've included my suggested changes (note: I haven't marked everything since there are some repetitions of the same pattern).

- Groupped LetExpr and ComprehensionExpr in a single interface for accessing bound variables
- Added test case for result variable in functions + implemented it
- New overloads for every type of comprehension expression + simplifications.
- Fixed CI warnings about potential null initalization
- Removed abbreviations
- Moved AsEnumerable from Symbol to SymbolExtensions
- Removed obsolete TODO
@MikaelMayer
Copy link
Member Author

@camrein These were great improvement suggestions. I carefully reviewed each of them and did my best to handle repetitions of the same patterns.
If needed, here are the changes since your last review
7de6a53

@MikaelMayer MikaelMayer enabled auto-merge (squash) December 9, 2021 20:20
…rn type which cause symbol building to crash.

Fixed potential errors with null tokens
Copy link
Member

@camrein camrein left a comment

Choose a reason for hiding this comment

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

Thanks a lot for these improvements. This looks way better now.

MikaelMayer and others added 2 commits December 15, 2021 10:39
- Use of pattern matching
- Warning logging if duplicate symbol in lookup table
- Adding to list of symbol instead of replacing the initialized list
- Better naming of start and end token for scope symbol
- Removed a canceled refactoring artefact (IToken? => IToken)
- Removed extra whitespace
-
Copy link
Member

@camrein camrein left a comment

Choose a reason for hiding this comment

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

This looks great so far. I've only had some minor input now. Thanks for taking your time.

Copy link
Member

@camrein camrein left a comment

Choose a reason for hiding this comment

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

Looks great, thanks!
Since I might be biased by now, @keyboardDrummer would you mind giving a second look?

@@ -265,8 +266,9 @@ public class BuiltIns {
/// the built-in total-arrow type (if "total", in which case "member" is expected to denote the "requires" member).
/// The given "id" is expected to be already resolved.
/// </summary>
private Expression ArrowSubtypeConstraint(IToken tok, BoundVar id, Function member, List<TypeParameter> tps, bool total) {
private Expression ArrowSubtypeConstraint(IToken tok, IToken endTok, BoundVar id, Function member, List<TypeParameter> tps, bool total) {
Copy link
Member

Choose a reason for hiding this comment

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

Should we not use the IRegion interface here so we only need a single parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

The two places in which this ArrowSubtypeConstraint is being called actually give Token.NoToken as argument, so there is nothing close to IRegion to provide, so although this is a good suggestion, it's better to keep it this way until there is a compelling reason to change it.

Copy link
Member

@keyboardDrummer keyboardDrummer Dec 17, 2021

Choose a reason for hiding this comment

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

Why not wrap the Tokens in a class Region : IRegion? I think it's common for AST nodes to know their start and end token, and to store both in a single object. Why have two parameters when you can have one?

Copy link
Member Author

Choose a reason for hiding this comment

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

After 1-1 discussion, we agreed that, since Expressions already use tok for the start token, it's better to leave this huge refactoring for an independent PR later.

Copy link
Member Author

Choose a reason for hiding this comment

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

We will eventually add start and end positions for every expression in Dafny's grammar, so it's not useful to do this change now in a way that would be half-baked.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

@cpitclaudel cpitclaudel Dec 20, 2021

Choose a reason for hiding this comment

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

Thanks for the ping, I had a quick look. I'm not sure when the right time is to change the way end tokes are passed, but here's my 2¢:

  • Are these tokens used for anything else than position reporting? If not, they shouldn't even be passed into the various AST constructors, and only ranges instead. It's OK to implement a range variant that's based on a pair of tokens if we really want to keep the tokens around for some reason.
  • Exposing a pair of tokens directly is a risky business, because it's unclear (at least to me) what the semantics of end tokens are. For example when you convert to a range you use GetLSPPosition on both tokens, but that means the range ends on the beginning of the last token. If the last token is part of the range, the range should end at the end of that token instead. If the current code is right, then I expect others will make the same mistake as I did when proofreading this, so I think there's value in a level of indirection that encapsulates pairs of tokens and exposes a range.
  • I have no thoughts on when that change should be done. It would be OK to me to put a FIXME in there, but I don't know the context of this change.

HTH!

(Also, I props to you on completing that change; it's impressive me how complicated this conceptually simple change ended up being, so nice job on pushing it through — and the final feature is very cool.)

Copy link
Member

Choose a reason for hiding this comment

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

I have no thoughts on when that change should be done. It would be OK to me to put a FIXME in there, but I don't know the context of this change.

Do we all to agree that the desirable end state is for every expression AST Node to have a Range/Region field instead of a token field? I don't think there is a reason to work with tokens after parsing.

Given that end state, why make a detour by first introducing a second token field to expression AST nodes?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we all agree on the final state.

It's not a detour, it's actually reusing the same field name and interface as the statements, i.e. being consistent with the system already in place.

Copy link
Member

Choose a reason for hiding this comment

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

it's actually reusing the same field name and interface as the statements, i.e. being consistent with the system already in place.

Okay, let's settle with that then.

@camrein
Copy link
Member

camrein commented Dec 20, 2021

@MikaelMayer thanks for applying my suggested changes and explaining to me some of your decisions.
For me, it looks fine for now. If @keyboardDrummer doesn't have any other feedback a merge should be fine.

@MikaelMayer MikaelMayer enabled auto-merge (squash) December 20, 2021 16:54
@MikaelMayer MikaelMayer merged commit 40db911 into master Dec 20, 2021
@MikaelMayer MikaelMayer deleted the ide-highlight-formals-and-declarations branch December 20, 2021 17:09
new Range(scopeSymbol.Declaration.Tok.GetLspPosition(), scopeSymbol.Declaration.EndTok.GetLspPosition())
scopeSymbol.BodyStartToken,
scopeSymbol.BodyStartToken.GetLspRange(),
new Range(scopeSymbol.BodyStartToken.GetLspPosition(), endToken.GetLspPosition())
Copy link
Member

Choose a reason for hiding this comment

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

Don't you want ToLspPosition(token.line, token.col + token.val.Length) here? (as in GetLspRange?)

Copy link
Member

Choose a reason for hiding this comment

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

(I've added details about this above; copied here for context:)

It's unclear to me what the semantics of end tokens are. For example when you convert to a range you use GetLSPPosition on both tokens, but that means the range ends on the beginning of the last token. If the last token is part of the range, the range should end at the end of that token instead.

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.

None yet

4 participants