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: Optional pre-type won't cause a crash anymore #5442

Merged
merged 9 commits into from
May 17, 2024
7 changes: 5 additions & 2 deletions Source/DafnyCore/Resolver/PreType/PreType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,13 @@ public class PreTypePlaceholderModule : PreTypePlaceholder {
public class PreTypePlaceholderType : PreTypePlaceholder {
}

public class UnusedPreType : PreTypePlaceholder {
/// Used for assigning a pre-type to MemberSelect expressions, such as "obj.method",
/// which is not considered an expression. This indicates that resolution has occurred,
Copy link
Member

Choose a reason for hiding this comment

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

What do you mean by MemberSelect not being considered an expression? It inherits from Expression

Copy link
Member Author

Choose a reason for hiding this comment

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

It weirdly is an Expression, but not a true Dafny expression: you cannot use it in a regular Dafny expression. Consider the following:

class A {
  method Test() {
  }
}
method Main() {
  var a := new A();
  var f := a.Test; // Error, expected a Dafny expression on the RHS
  f();
}

Copy link
Member

Choose a reason for hiding this comment

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

It is also used for fields, in which case it is an expression, right?

Copy link
Member

@keyboardDrummer keyboardDrummer May 16, 2024

Choose a reason for hiding this comment

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

It seems nice to conceptually consider a MemberSelectExpr that references a method still an expression, even if we don't allow it in an expression context. If we do that, then I would expect to assign some sort of callable type to it. Could we rename UnusedPreType to Method[Pre]Type?

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, it's used for fields and in that case it's indeed an expression.

I did the renaming, hoping it won't break any future development.

/// even though the pre-type itself is not useful.
public class MethodPreType : PreTypePlaceholder {
public readonly string Why;

public UnusedPreType(string why) {
public MethodPreType(string why) {
Why = why;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,7 @@ public partial class PreTypeResolver {
if (!allowMethodCall) {
// it's a method and method calls are not allowed in the given resolutionContext
ReportError(tok, "expression is not allowed to invoke a {0} ({1})", member.WhatKind, member.Name);
return null;
}
int suppliedTypeArguments = optTypeArguments == null ? 0 : optTypeArguments.Count;
if (optTypeArguments != null) {
Expand All @@ -1608,7 +1609,7 @@ public partial class PreTypeResolver {
rr.PreTypeApplication_JustMember.Add(ta);
subst.Add(method.TypeArgs[i], ta);
}
rr.PreType = new UnusedPreType($"call to {method.WhatKind} {method.Name}"); // fill in this field, in order to make "rr" resolved
rr.PreType = new MethodPreType($"call to {method.WhatKind} {method.Name}"); // fill in this field, in order to make "rr" resolved
}
return rr;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/Resolver/PreType/PreTypeResolve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1590,7 +1590,7 @@ class PreTypeSanityChecker : BottomUpVisitor {
preTypeResolver.ReportError(expr.tok, $"SANITY CHECK FAILED: .PreType is '{expr.PreType}' but .Type is null");
} else if (PreType.Same(expr.PreType, preTypeResolver.Type2PreType(expr.Type))) {
// all is cool
} else if (expr.PreType is UnusedPreType && expr.Type is TypeProxy) {
} else if (expr.PreType is MethodPreType && expr.Type is TypeProxy) {
// this is expected
} else {
preTypeResolver.ReportError(expr.tok, $"SANITY CHECK FAILED: pre-type '{expr.PreType}' does not correspond to type '{expr.Type}'");
Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/Resolver/PreType/PreTypeToType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class PreTypeToTypeVisitor : ASTVisitor<IASTVisitorContext> {
return;
}

if (expr.PreType is UnusedPreType) {
if (expr.PreType is MethodPreType) {
expr.Type = new InferredTypeProxy();
return;
}
Expand Down
2 changes: 1 addition & 1 deletion Source/DafnyCore/Resolver/ResolutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public ResolutionContext(ICodeContext codeContext, bool isTwoState)
if (CodeContext.IsGhost == isGhost) {
return this;
}
return new ResolutionContext(new CodeContextWrapper(CodeContext, isGhost), IsTwoState, InOld, InReveal, InFunctionPostcondition, InFirstPhaseConstructor);
return this with { CodeContext = new CodeContextWrapper(CodeContext, isGhost) };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// RUN: %exits-with 2 %baredafny verify %args --type-system-refresh "%s" > "%t"
// RUN: %diff "%s.expect" "%t"

ghost function f2(items: set<nat>): (r:nat)
requires |items| > 0 {
var item :| item in items;
item
}

function f(items: set<nat>) : (r:nat)
requires |items| > 0 {
//assume exists item :: item in items && forall other <- items :: item == other || item < other;
//assert exists item Smallest()
var item :| IsSmallest(items, item);
item
}

predicate IsSmallest(s: set<nat>, item: nat)
requires item in s {
m in s && forall y: nat :: y in s ==> m <= y
}

lemma Smallest(s: set<nat>) returns (m: nat)
requires s != {}
ensures m in s && IsSmallest(s, m)
decreases s
{
m :| m in s;
if y: nat :| y in s && y < m {
ghost var s' := s - {m};
assert y in s';
m := ThereIsASmallest(s');
}
}

function smallest(items: set<nat>): (r: nat)
requires |items| > 0

method m(items: set<nat>) returns (r:nat)
requires |items| > 0 {
var item :| item in items;
return item;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
git-issue-5369.dfy(20,2): Error: expression is not allowed to invoke a method (m)
git-issue-5369.dfy(20,40): Error: expression is not allowed to invoke a method (m)
git-issue-5369.dfy(32,9): Error: unresolved identifier: ThereIsASmallest
3 resolution/type errors detected in git-issue-5369.dfy
1 change: 1 addition & 0 deletions docs/dev/news/5369.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Optional pre-type won't cause a crash anymore