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

Resolve match before compile #2734

Merged

Conversation

keyboardDrummer
Copy link
Member

@keyboardDrummer keyboardDrummer commented Sep 13, 2022

Changes

A nested match is one where a single case may match on multiple destructors, organised in a tree. A nested match case may also match on a constant value. A simple match case may only match on a single constructor. The process of compiling/denesting/flattening nested matches, transforms nested matches into if statements and simple matches.

Previously the flattening was done during resolution, but this PR moves it further back in the pipeline so the resolved AST does not have the flattening applied to it. The flattening is still computed during resolution, but it is stored in a new field instead of being applied to the resolved AST. That way, the IDE can derive code navigation from the unflattened AST, but still emit warnings produced by the flattening. Using the flattened results is done in verification and compilation by traversing the new field.

I think more idiomatically would be not to have the newly added field flattened inside NestedMatchExpr/Stmt, and instead fully replace the NestedMatchExpr/Stmt node, since that would prevent having to add code in the backends. However, we don't have a good mechanism for replacing AST nodes yet. Also, doing that would require being able to set up a transformation that runs after resolution, so it doesn't affect the IDE's code navigation, but before verification and code generation, so both of those can benefit from it, and we don't have a good mechanism for that either. I also wonder whether that would require cloning the AST right after resolution so it's preserved for code navigation. However, I think such mechanisms could benefit code generators that produce warnings after doing other transformations, so it's not a ton of work just for nested matches.

Code related to the flattening of nested matches has been moved out of Resolver.cs and into a new file MatchFlattener.cs. Code related to resolving nested matches has been moved out of resolver and into the related AST nodes. Simple matches no longer have resolution code since they're instantiated already resolved. Resolution of nested matches is currently done in two phases, the 'Check' and 'Resolve' phase, that can be merged, but I'll leave that for a follow-up PR.

The was a small transformation that was done during match flattening, which transforms explicit type annotations on bound case variables, by replacing them with a let statement around the body of the case, where the bound let variable carries the explicit type annotation. This transformation has been moved to the resolution phase of nested matches. Possibly we don't need this transformation, but I leave removing or moving it for a follow-up PR.


Fix a bug related to matches and ghost constructors. In the following program, D0 is non-ghost, but previously the match was still considered to be ghost because XY has constructors other than D0 that are ghost.

  method P0(xy: XY) returns (r: int) {
-    // the following match statement is ghost, because it indirectly mentions a ghost constructor of type XY
+   // the following match statement is non-ghost, because it does not mention a ghost constructor of type XY
    match xy
-    case D0(_) => r := 0; // error: assignment to r in a ghost context
+   case D0(_) => r := 0;
     case any =>
  }

Testing

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

@keyboardDrummer keyboardDrummer self-assigned this Sep 15, 2022
@RustanLeino
Copy link
Collaborator

This is great! This will make many things better. For example, it will help my new type inference and will aid in producing good linter warnings about the user's given syntax.

@keyboardDrummer keyboardDrummer requested review from robin-aws and atomb and removed request for robin-aws January 3, 2023 17:26
Copy link
Member

@atomb atomb left a comment

Choose a reason for hiding this comment

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

Just a few cleanup changes and questions. Otherwise I think this looks good, and makes the treatment of nested matches easier to understand.


namespace Microsoft.Dafny;

class ResolverBottomUpVisitor : BottomUpVisitor {
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this class still also exists inside Resolver.cs. Was that intentional?

public override IEnumerable<Statement> SubStatements =>
ResolvedStatement == null ? Cases.SelectMany(c => c.Body) : base.SubStatements;
public override IEnumerable<Statement> SubStatements {
get {
Copy link
Member

Choose a reason for hiding this comment

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

Why make this use a get block instead of => like before?

}
}

public abstract record LinkedList<T> : IEnumerable<T> {
Copy link
Member

Choose a reason for hiding this comment

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

Why can't you use System.Collections.Generic.LinkedList?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a doubly linked list and this is a single one, which allows easily grabbing just the tail. I will rename this one to make it more clear.

@@ -8558,6 +7362,14 @@ public CollectFriendlyCallsInSpec_Visitor(Resolver resolver, ISet<Expression> fr
}
}

abstract class ResolverTopDownVisitor<T> : TopDownVisitor<T> {
Copy link
Member

Choose a reason for hiding this comment

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

This class appears to be duplicated.

/// it gets "replaced" by the statement in "ResolvedStatement".
/// Adapted from ConcreteSyntaxStatement
/// </summary>
public abstract class ConcreteSyntaxStatement : Statement {
Copy link
Member

Choose a reason for hiding this comment

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

I'm happy to see this go away!

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 but NestedMatchStmt.Flattened came in its place so I'm not sure if it's an improvement 😅 I considered changing ConcreteSyntaxStatement and ConcreteSyntaxExpression so they would have not only a resolvedX but also a compiledX field, instead of the current Flattened property. That would reduce the code added to Translator.cs and SinglePassCompiler.cs, and add opportunities for other post resolution compilation passes.

Copy link
Member

Choose a reason for hiding this comment

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

Good point. But one advantage to NestedMatchStmt.Flattened is that it's clear that it's used for one thing, whereas ConcreteSyntaxStatement sounds like it could cover many instances of concrete syntax, even if it didn't, in practice.

* 4 - An IdPattern at datatype type with no arguments representing a bound variable
*/
public void CheckLinearExtendedPattern(Type type, ResolutionContext resolutionContext, Resolver resolver) {
if (type == null) {
Copy link
Member

Choose a reason for hiding this comment

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

Under what circumstances might this happen?

Copy link
Member Author

Choose a reason for hiding this comment

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

That doesn't seem possible to me. Note this is an existing check, this method was moved from Resolver.cs. In a follow-up PR, I will merge CheckLinearExtendedPattern and ExtendedPattern.Resolve and maybe this can go then.


// Get the datatype of the matchee
var currMatcheeType = currMatchee.Type
// resolver.PartiallyResolveTypeForMemberSelection(currMatchee.tok, currMatchee.Type)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps remove this commented-out code?

.NormalizeExpand();
// if (currMatcheeType is TypeProxy) {
// resolver.PartiallySolveTypeConstraints(true);
// }
Copy link
Member

Choose a reason for hiding this comment

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

Remove this, as well?

if (dtd == null) {
ctors = null;
} else {
ctors = dtd.Ctors.ToDictionary(c => c.Name, c => c); //resolver.datatypeCtors[dtd];
Copy link
Member

Choose a reason for hiding this comment

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

Obsolete comment?

}
}

// TODO use a record.
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason not to do this right away?

@keyboardDrummer keyboardDrummer enabled auto-merge (squash) January 4, 2023 13:45
Copy link
Member

@atomb atomb 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!

@keyboardDrummer keyboardDrummer merged commit a8afc97 into dafny-lang:master Jan 4, 2023
@keyboardDrummer keyboardDrummer deleted the resolveMatchBeforeCompile branch January 4, 2023 17:57
keyboardDrummer added a commit that referenced this pull request Jan 6, 2023
Fixes #3333

The actual fix was already accidentally made in
#2734, this PR is just adding a
test case and a release note, and removing some obsolete code.

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
atomb added a commit to atomb/dafny that referenced this pull request Jan 10, 2023
During PR dafny-lang#2734, a refactoring of the cloner mistakenly omitted a clone
call for one sub-expression of TypeRHS, leading to issue dafny-lang#3343.

This PR reinstates that clone.

Fixes dafny-lang#3343.
robin-aws pushed a commit that referenced this pull request Jan 10, 2023
During PR #2734, a refactoring of the cloner mistakenly omitted a clone
call for one sub-expression of TypeRHS, leading to issue #3343.

This PR reinstates that clone.

Fixes #3343.
atomb added a commit to atomb/dafny that referenced this pull request Jan 10, 2023
During PR dafny-lang#2734, a refactoring of the cloner mistakenly omitted a clone
call for one sub-expression of TypeRHS, leading to issue dafny-lang#3343.

This PR reinstates that clone.

Fixes dafny-lang#3343.
davidcok pushed a commit to davidcok/dafny that referenced this pull request Jan 20, 2023
During PR dafny-lang#2734, a refactoring of the cloner mistakenly omitted a clone
call for one sub-expression of TypeRHS, leading to issue dafny-lang#3343.

This PR reinstates that clone.

Fixes dafny-lang#3343.
@stefan-aws stefan-aws mentioned this pull request Feb 1, 2023
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