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

fix: Fill in missing type substitutions #2984

Merged
merged 17 commits into from
Nov 28, 2022

Conversation

RustanLeino
Copy link
Collaborator

Fixes #2947

This PR fixes some missing type substitutions that affected the Boogie translation of extreme predicates.

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

@RustanLeino RustanLeino marked this pull request as ready for review November 4, 2022 00:01
/// Yields all functions and methods that are members of some type in the given list of
/// declarations, including prefix lemmas and prefix predicates.
/// </summary>
public static IEnumerable<ICallable> AllCallablesIncludingPrefixDeclarations(List<TopLevelDecl> declarations) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd like to have each language construct be as independent as possible. For extreme lemmas and predicates this would also mean they have mostly outwards references. Would it be possible to achieve this?

Would it be possible to translate extremes to their prefix versions and then forget about the originals? Or maybe create the prefix versions next to the originals?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need both. A program needs the extremes, since that's what you'll ultimately will use for the property you want. The prefix predicates/lemmas give a way to prove properties about the extremes, so they are also needed.

Actually, the more surprising thing (to me) is that AllCallables (which excludes the prefix versions) is used so many times.

Copy link
Member

@keyboardDrummer keyboardDrummer Nov 6, 2022

Choose a reason for hiding this comment

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

We need both. A program needs the extremes, since that's what you'll ultimately will use for the property you want. The prefix predicates/lemmas give a way to prove properties about the extremes, so they are also needed.

Sure, but can we place both side by side instead of introducing AllCallablesIncludingPrefixDeclarations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't understand. The AllCallablesIncludingPrefixDeclarations and AllCallables properties are declared side by side. Are you perhaps suggesting that the prefix predicates/lemmas be placed in the list of the enclosing module's top-level-decls (rather than being linked off the corresponding extreme predicate/lemma)? (And if so, do you think that will improve the organization for other parts of the implementation?)

Copy link
Member

Choose a reason for hiding this comment

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

Are you perhaps suggesting that the prefix predicates/lemmas be placed in the list of the enclosing module's top-level-decls

Yes that's what I meant.

And if so, do you think that will improve the organization for other parts of the implementation?

I think so. If I understand extreme lemma's correctly, they behave similarly to as if you'd written the prefix lemma manually next to them. Also, looking at usages of ExtremeLemma.PrefixLemma gives the impression there's a handful of snippets that handle the PrefixLemma as if it was a top level method.

Code like this:

    internal override void PostResolveIntermediate(ModuleDefinition m) {
      var forallvisiter = new ForAllStmtVisitor(Reporter);
      foreach (var decl in ModuleDefinition.AllCallables(m.TopLevelDecls)) {
        forallvisiter.Visit(decl, true);
        if (decl is ExtremeLemma) {
          var prefixLemma = ((ExtremeLemma)decl).PrefixLemma;
          if (prefixLemma != null) {
            forallvisiter.Visit(prefixLemma, true);
          }
        }
      }

    }

or this:

              if (member is ExtremeLemma) {
                var method = (ExtremeLemma)member;
                ProcessMethodExpressions(method);
                ComputeLemmaInduction(method.PrefixLemma);
                ProcessMethodExpressions(method.PrefixLemma);
              } else if (member is Method) {
                var method = (Method)member;
                ComputeLemmaInduction(method);
                ProcessMethodExpressions(method);
              } 

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I looked into this in detail for .PrefixLemma and .PrefixPredicate. In places where the code looked like your first example, I change AllCallables to AllCallablesIncludingPrefixDeclarations. I also improved code in the various other visitors, making use of newer C# constructs. And I improved the iteration over declarations in IsEssentiallyEmptyModuleBody(), to be more exact than before.

In the code base, what happens with extreme lemmas/predicates and prefix lemmas/predicates is quite varied. In some places, the extreme declaration is used and the prefix version ignored. In other places, it's the other way around. And in some places, the two are handled in different ways. Therefore, I suggest that any further improvements be done separately from this PR.

keyboardDrummer
keyboardDrummer previously approved these changes Nov 4, 2022
@@ -38,13 +38,19 @@ public class Substituter {
return new StaticReceiverExpr(e.tok, ty, e.IsImplicit) { Type = ty };
} else if (expr is LiteralExpr literalExpr) {
if (literalExpr.Value == null) {
return new LiteralExpr(literalExpr.tok) { Type = Resolver.SubstType(literalExpr.Type, typeMap) };
var ty = Resolver.SubstType(literalExpr.Type, typeMap);
Copy link
Member

Choose a reason for hiding this comment

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

What's the effect of this change? I see you're changing the object pointer less often, but does that have an effect?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's a (possibly misguided) optimization in the Substitute(Expression expr) method, which is that if the result is pointer-equal to expr if the substitution has no effect. An illustrative example of this is:

      } else if (expr is MultiSetFormingExpr) {
        var e = (MultiSetFormingExpr)expr;
        var se = Substitute(e.E);
        if (se != e.E) {
          newExpr = new MultiSetFormingExpr(expr.tok, se);
        }

This optimization is error prone. For LiteralExpr, the optimization was missing, so I added it. In other places (like DatatypeValue, the optimization was too aggressive (meaning, "wrong") because it returned expr even when it should have returned a new DatatypeValue with a different InferredTypeArgs field.

The optimization is intended to save memory, but this may not make any difference, and the optimization makes the code significantly more complicated. If you suggest, I'd be happy to remove this optimization from Substitute(Expression.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I built a local version with a simplified Substitute (that is, I removed the optimization). It passes. I'd be happy to add it to this PR, if you think, or to file a separate PR for it.

Copy link
Member

Choose a reason for hiding this comment

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

The optimization is intended to save memory, but this may not make any difference, and the optimization makes the code significantly more complicated.

Without a measurement about the performance difference I'd prefer to leave it like it is, unless you're confident the optimisation doesn't have a significant effect. I'm not sure whether comparing the difference in the test-suite runtime is a good test.

I'm hoping we can at some point generate the code for various behaviors such as Substitutor, Cloner, SubExpressions, SubStatements, and likely some others.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I'll leave it as is. And yes, I agree that generating code for these sorts of routines would be far better than the manual upkeep we have to do today.

Copy link
Member

@keyboardDrummer keyboardDrummer left a comment

Choose a reason for hiding this comment

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

Approving but I expect fresh bugs related to this to be added in the future. How should a caller decide whether to call AllCallablesIncludingPrefixDeclarations or AllCallables without having to dive into the semantics of extreme lemma's? I feel that as long as there's these two options there's a source for bugs.

@keyboardDrummer keyboardDrummer enabled auto-merge (squash) November 24, 2022 09:31
@keyboardDrummer keyboardDrummer merged commit 10ce903 into dafny-lang:master Nov 28, 2022
@RustanLeino RustanLeino deleted the issue-2947 branch November 28, 2022 21:17
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.

Declaration of generic extreme predicate causes crash
2 participants