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: Added ranges to expression #1985

Merged
merged 7 commits into from
Apr 12, 2022
Merged

Feat: Added ranges to expression #1985

merged 7 commits into from
Apr 12, 2022

Conversation

MikaelMayer
Copy link
Member

Before (possibly failing assertions are barely visible):
image

After (assertions are clearly visible)
image

Although it cannot be seen yet, related positions on postconditions also now cover an entire expression instead of just a token.

This PR:

  • Adds a way for expression to expose their start token and their end token
  • Offers a way to create a "RangeToken" from any expression that represents the entire range of an expression
  • Also changes the tokens we use for Boogie so that they use RangeToken if an translator option ``ReportRange` is activated
  • activates this option for the language server only for now.

In the translator, the method GetTok replaces .tok to perform the switch.

After searching for 30 minutes, I didn't find what else to change to get an entire function or method call to be entirely highlighted, any hint @RustanLeino ?

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

@MikaelMayer MikaelMayer self-assigned this Apr 7, 2022
@MikaelMayer MikaelMayer added kind: enhancement Enhancements, feature requests, etc. These are NOT bugs, but ways we can improve Dafny area: error-reporting Clarity of the error reporting labels Apr 7, 2022
cpitclaudel
cpitclaudel previously approved these changes Apr 7, 2022
Copy link
Member

@cpitclaudel cpitclaudel left a comment

Choose a reason for hiding this comment

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

👍 lovely

private void RecomputeStartEnd() {
startTok = tok;
endTok = tok;
foreach (var e in SubExpressions) {
Copy link
Member

Choose a reason for hiding this comment

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

Possibly out of scope: it would help if SubExpressions was a IReadOnlyList so you could retrieve the first and last element without having to iterate.

Copy link
Member Author

Choose a reason for hiding this comment

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

You mean, we could cache the SubExpressions? That's a good point, but yes, let's leave it out of scope for this PR.

endTok = tok;
foreach (var e in SubExpressions) {
if (e.tok.pos < startTok.pos) {
startTok = e.tok;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be e.StartTok ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes that would be even better, thanks. I'm also adding a check that the filename should be the same.

public IToken RangeToken => new RangeToken(StartTok, EndTok);

// Computes the start and end token of this expression
private void RecomputeStartEnd() {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this called Re compute, isn't it only called once?

Copy link
Member Author

@MikaelMayer MikaelMayer Apr 8, 2022

Choose a reason for hiding this comment

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

I imagine it could be called multiple times if we modify the AST, e.g. by adding elements in a set comprehension. But we can always clone and expression, modify it and then access the new range so that's not useful. Will remove this method.

if (e.tok.pos < startTok.pos) {
startTok = e.tok;
} else if (e.tok.pos > endTok.pos) {
endTok = e.tok;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be e.EndTok ?

@@ -9070,6 +9086,48 @@ public abstract class Expression {
get { yield break; }
}

private IToken startTok;
Copy link
Member

Choose a reason for hiding this comment

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

Why not store the RangeToken instead of two ITokens?


// Creates a token on the entire range of the expression.
// Used only for error reporting.
public IToken RangeToken => new RangeToken(StartTok, EndTok);
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we could move all the new code to a class

class SyntaxNode {
  public readonly IToken tok;

  public abstract IEnumerable<SyntaxNode> ChildNodes {get;}

  public IToken RangeToken =>

  ..
}

and then have

class Expression : SyntaxNode {
  public IEnumerable<SyntaxNode> ChildNodes => SubExpressions;
}

Or do you feel that's too early for this PR ?

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 could as well use the bounded polymorphism pattern:

class SyntaxNode<T> where T: SyntaxNode {
  public readonly IToken tok;

  public abstract IEnumerable<T> Children{get;}

  public IToken RangeToken =>
  ..
}
class Expression : SyntaxNode<Expression> {
}

However, we should do this refactoring later, when I see a compelling reason to do so. We might as well replace the "tok" by a Format or trait to keep track of concrete syntax tree elements.

Copy link
Member

Choose a reason for hiding this comment

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

However, we should do this refactoring later

Alright. Indeed, I think it only makes sense to do this if we have multiple subclasses of SyntaxNode.

@@ -110,6 +111,10 @@ public ExpressionTranslator(ExpressionTranslator etran, string modifiesFrame)
Contract.Requires(modifiesFrame != null);
}

internal IToken GetTok(Expression e) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not name it GetToken ? Also, I'm not a fan of e even though this is such as short method ^^

@@ -44,6 +44,7 @@ public partial class Translator {
public class TranslatorFlags {
public bool InsertChecksums = 0 < DafnyOptions.O.VerifySnapshots;
public string UniqueIdPrefix = null;
public bool ReportRanges = false;
Copy link
Member

Choose a reason for hiding this comment

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

When do we not want to report ranges? Looks like it's turned on for the IDE but not the CLI. Why the difference?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, because the IDE will highlight ranges. However, in the CLI, there is a format to report positions only, so that would mean 1) to change that format to support ranges of positions, which would be cluttering 2) or we could also decide so activate this option when -showSnipptes is enabled, but it's not the option by default yet and it will require to change the way we display errors anyway.

I though we should first add these ranges in the Dafny Language Server, where they are the most useful, and then we can always do this later.

Copy link
Member

Choose a reason for hiding this comment

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

Alright

@@ -582,7 +582,7 @@ modifies this
Assert.AreEqual("This is the postcondition that might not hold.", relatedInformation[0].Message);
Assert.AreEqual(new Range((14, 16), (14, 21)), relatedInformation[0].Location.Range);
Assert.AreEqual("Related location", relatedInformation[1].Message);
Assert.AreEqual(new Range((9, 13), (9, 14)), relatedInformation[1].Location.Range);
Assert.AreEqual(new Range((9, 11), (9, 16)), relatedInformation[1].Location.Range);
Copy link
Member

Choose a reason for hiding this comment

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

Could we have tests with more nested expressions as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

tests added

@keyboardDrummer
Copy link
Member

Nice change!

MikaelMayer and others added 2 commits April 11, 2022 11:47
Failed function call highlight
Other cases of extended range highlighting
@MikaelMayer MikaelMayer enabled auto-merge (squash) April 11, 2022 18:49
@@ -8997,6 +9026,38 @@ public abstract class Expression {
get { yield break; }
}

private RangeToken rangeToken;

// Creates a token on the entire range of the expression.
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: I'd prefer documentation comments /// above members.

@MikaelMayer MikaelMayer merged commit 7918c70 into master Apr 12, 2022
@MikaelMayer MikaelMayer deleted the feat-ranges branch April 12, 2022 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: error-reporting Clarity of the error reporting kind: enhancement Enhancements, feature requests, etc. These are NOT bugs, but ways we can improve Dafny
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants