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

Implement decreases to expressions #5367

Merged
merged 40 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
53a1a9d
Barebones end-to-end pipeline
atomb Apr 17, 2024
14c8e49
Reasonable old and new expressions
atomb Apr 23, 2024
e20e735
Undo spurious changes
atomb Apr 24, 2024
23eabb4
More reasonable `decreases to` syntax
atomb Apr 24, 2024
cd7b8d9
Update error messages
atomb Apr 25, 2024
5fe0e36
Add initial test
atomb Apr 25, 2024
3aa0609
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 13, 2024
30f81df
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 14, 2024
ee6059e
Adjust parser for `decreases to`
atomb May 14, 2024
1757a58
Remove ICanFormat from DecreasesToExpr
atomb May 14, 2024
bccfb40
Support `decreases to` only in parentheses
atomb May 15, 2024
59f74d0
Add release notes
atomb May 15, 2024
341f8e8
Add documentation for `decreases to`
atomb May 15, 2024
bd4a58c
Update parse error message capitalization
atomb May 15, 2024
7b2a9f1
Update news file
atomb May 15, 2024
4ad5ae1
More tests
atomb May 15, 2024
cc89ec1
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 15, 2024
ad26c21
Fix parsing of the unit value
atomb May 15, 2024
bbd3828
Fix numbering in reference manual
atomb May 15, 2024
7c4672e
Error message discrepancies
atomb May 15, 2024
31dbe3d
Create error IDs for new error messages
atomb May 15, 2024
1faa65d
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 17, 2024
54e1b00
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 21, 2024
6f86ce9
Make new GetExtraExplanation methods proper overrides
atomb May 22, 2024
cd93069
Support allowNoChange in DecreasesToExpr
atomb May 22, 2024
2c1a505
Add test for `decreases to` in non-ghost context
atomb May 22, 2024
e87c990
Fix `nonincreases to`
atomb May 22, 2024
e69bbfb
Fix cloning of `DecreasesToExpr`
atomb May 22, 2024
ca3fab5
Fix token on some DecreasesToExpr translations
atomb May 22, 2024
76a7896
Fix type checking of `decreases to`
atomb May 23, 2024
a1a3dcb
Fix checking of `decreases to` reference examples
atomb May 23, 2024
1856361
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 24, 2024
aefe775
Fix tests
atomb May 24, 2024
1c6f182
Remove unnecessary grammar complexity
atomb May 24, 2024
e2700c3
Remove unused lines
atomb May 24, 2024
a4c910a
Cross-reference `decreases` and `decreases to`
atomb May 24, 2024
089811a
Fix tests
atomb May 24, 2024
5e84606
Merge remote-tracking branch 'upstream/master' into decreasesto
atomb May 24, 2024
a4181a4
Merge branch 'master' into decreasesto
atomb May 28, 2024
15d21a4
Merge branch 'master' into decreasesto
atomb May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Source/DafnyCore/AST/Expressions/Operators/DecreasesToExpr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Dafny;

public class DecreasesToExpr : Expression, ICloneable<DecreasesToExpr> {
public IEnumerable<Expression> OldExpressions { get; }
public IEnumerable<Expression> NewExpressions { get; }

public bool AllowNoChange { get; }

public DecreasesToExpr(IToken tok, IEnumerable<Expression> oldExpressions, IEnumerable<Expression> newExpressions, bool allowNoChange) : base(tok) {
OldExpressions = oldExpressions;
NewExpressions = newExpressions;
AllowNoChange = allowNoChange;
}

public DecreasesToExpr(Cloner cloner, DecreasesToExpr original) : base(cloner, original) {
OldExpressions = original.OldExpressions.Select(cloner.CloneExpr);
NewExpressions = original.NewExpressions.Select(cloner.CloneExpr);
AllowNoChange = original.AllowNoChange;
}

public DecreasesToExpr Clone(Cloner cloner) {
return new DecreasesToExpr(cloner, this);
}

public override IEnumerable<Expression> SubExpressions {
get {
foreach (var oldExpr in OldExpressions) {
yield return oldExpr;
}
foreach (var newExpr in NewExpressions) {
yield return newExpr;
}
}
}
}
2 changes: 2 additions & 0 deletions Source/DafnyCore/AST/Grammar/ParseErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ public enum ErrorId {
p_file_has_no_code,
p_general_traits_datatype,
p_general_traits_full,
p_decreases_without_to,
p_binding_in_decreases_to,
}

static ParseErrors() {
Expand Down
38 changes: 37 additions & 1 deletion Source/DafnyCore/AST/Grammar/ParserNonGeneratedPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ bool IsParenStar() {
bool IsExpliesOp() => IsExpliesOp(la);
bool IsAndOp() => IsAndOp(la);
bool IsOrOp() => IsOrOp(la);
bool IsComma() {
return la.val == ",";
}
static bool IsEquivOp(IToken la) {
return la.val == "<==>";
}
Expand Down Expand Up @@ -682,6 +685,39 @@ bool IsAssumeTypeKeyword(IToken la) {
return la.kind == _assume || la.kind == _assert || la.kind == _expect;
}

Expression ProcessTupleArgs(List<ActualBinding> args, IToken lp) {
if (args.Count == 1 && !args[0].IsGhost) {
if (args[0].FormalParameterName != null) {
SemErr(ErrorId.p_no_parenthesized_binding, lp, "binding not allowed in parenthesized expression");
}
return args[0].Actual;
} else {
// Compute the actual position of ghost arguments
var ghostness = new bool[args.Count];
for (var i = 0; i < args.Count; i++) {
ghostness[i] = false;
}
for (var i = 0; i < args.Count; i++) {
var arg = args[i];
if (arg.IsGhost) {
if (arg.FormalParameterName == null) {
ghostness[i] = true;
} else {
var success = int.TryParse(arg.FormalParameterName.val, out var index);
if (success && 0 <= index && index < args.Count) {
ghostness[index] = true;
}
}
}
}
var argumentGhostness = ghostness.ToList();
// make sure the corresponding tuple type exists
SystemModuleModifiers.Add(b => b.TupleType(lp, args.Count, true, argumentGhostness));
return new DatatypeValue(lp, SystemModuleManager.TupleTypeName(argumentGhostness), SystemModuleManager.TupleTypeCtorName(args.Count), args);
}
}


public void ApplyOptionsFromAttributes(Attributes attrs) {
var overrides = attrs.AsEnumerable().Where(a => a.Name == "options")
.Reverse().Select(a =>
Expand Down Expand Up @@ -764,4 +800,4 @@ void CheckDeclModifiers(ref DeclModifierData dmod, string declCaption, AllowedDe
}
}

}
}
22 changes: 22 additions & 0 deletions Source/DafnyCore/AST/Grammar/Printer/Printer.Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,28 @@ void PrintExpr(Expression expr, int contextBindingStrength, bool fragileContext,
wr.Write("[BoogieFunctionCall]"); // this prevents debugger watch window crash
} else if (expr is Resolver_IdentifierExpr) {
wr.Write("[Resolver_IdentifierExpr]"); // we can get here in the middle of a debugging session
} else if (expr is DecreasesToExpr decreasesToExpr) {
var comma = false;
foreach (var oldExpr in decreasesToExpr.OldExpressions) {
if (comma) {
wr.Write(", ");
}
PrintExpression(oldExpr, false);
comma = true;
}
if (decreasesToExpr.AllowNoChange) {
wr.Write(" nonincreases to ");
} else {
wr.Write(" decreases to ");
}
comma = false;
foreach (var newExpr in decreasesToExpr.NewExpressions) {
if (comma) {
wr.Write(", ");
}
PrintExpression(newExpr, false);
comma = true;
}
Comment on lines +1196 to +1217
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we test this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. See the Asserted expression: lines in the added tests.

} else {
Contract.Assert(false); throw new cce.UnreachableException(); // unexpected expression
}
Expand Down
7 changes: 7 additions & 0 deletions Source/DafnyCore/AST/Substituter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,13 @@ ExtendedPattern SubstituteForPattern(ExtendedPattern pattern) {
newExpr = new BoogieGenerator.BoogieFunctionCall(e.tok, e.FunctionName, e.UsesHeap, e.UsesOldHeap, e.HeapAtLabels, newArgs, newTyArgs);
}

} else if (expr is DecreasesToExpr decreasesToExpr) {
List<Expression> oldExpressionsSubst = SubstituteExprList(decreasesToExpr.OldExpressions.ToList());
List<Expression> newExpressionsSubst = SubstituteExprList(decreasesToExpr.NewExpressions.ToList());
newExpr = new DecreasesToExpr(decreasesToExpr.tok, oldExpressionsSubst, newExpressionsSubst, decreasesToExpr.AllowNoChange) {
Type = decreasesToExpr.Type
};

} else {
Contract.Assume(false); // unexpected Expression
}
Expand Down
94 changes: 58 additions & 36 deletions Source/DafnyCore/Dafny.atg
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ TOKENS
by = "by".
in = "in".
decreases = "decreases".
nonincreases = "nonincreases".
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that it's there, we might as well keep it, but was there a strong reason to add it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sometimes the check that Dafny needs to perform is, roughly speaking, <= instead of <. To be able to express those conditions, it seemed most natural to have a form of the expression that encoded <= instead of <. It would be possible, alternatively, to have (x decreases to y) || x == y, but it seems a little more awkward, and I think would be more difficult to implement, as well (though I'm no longer sure of that).

invariant = "invariant".
function = "function".
predicate = "predicate".
Expand Down Expand Up @@ -2970,7 +2971,7 @@ AssumeStmt<out Statement/*!*/ s>
.)
"assume" (. x = t; startToken = t; .)
{ Attribute<ref attrs> }
( Expression<out e, false, true>
( Expression<out e, false, false>
| ellipsis (. dotdotdot = t;
errors.Deprecated(ErrorId.p_deprecated_statement_refinement, t, "the ... refinement feature in statements is deprecated");
.)
Expand Down Expand Up @@ -3202,6 +3203,47 @@ Forall = "forall".
Exists = "exists".
QSep = "::".

MaybeDecreasesToExpression<out Expression e, IToken lp>
= (. List<ActualBinding> args = new();
List<Expression> newExprs = new();
Expression tmp = null;
IToken x = null;
bool allowNoChange = false;
.)
TupleArgs<args>
[ ( "decreases" (. allowNoChange = false; .)
| "nonincreases" (. allowNoChange = true; .)
) (. x = t; .)
ident
(. if (t.val != "to") {
SemErr(ErrorId.p_decreases_without_to, t, "expected 'to'");
}
.)
Expression<out tmp, false, false> (. newExprs.Add(tmp); .)
{ ","
Expression<out tmp, false, false> (. newExprs.Add(tmp); .)
}
]
(.
if (newExprs.Count() == 0) {
e = ProcessTupleArgs(args, lp);
} else if (args.Count() > 0 && newExprs.Count() > 0) {
var formals = args.Where(arg => arg.FormalParameterName != null).ToList();
if (formals.Any()) {
SemErr(ErrorId.p_binding_in_decreases_to,
formals[0].FormalParameterName,
"bindings are not allowed in `decreases to` expressions.");
}
var oldExprs = args.Select(arg => arg.Actual).ToList();
e = new DecreasesToExpr(x, oldExprs, newExprs, allowNoChange);
e.RangeToken = new RangeToken(oldExprs[0].tok, newExprs[newExprs.Count() - 1].tok);
} else {
// Unreachable
e = null;
}
.)
.

/* The "allowLemma" argument says whether or not the expression
* to be parsed is allowed to have the form S;E where S is a call to a lemma.
* "allowLemma" should be passed in as "false" whenever the expression to
Expand Down Expand Up @@ -3718,43 +3760,23 @@ LambdaSpec<.ref List<FrameExpression> reads, ref Attributes readsAttrs, ref Expr

/*------------------------------------------------------------------------*/
ParensExpression<out Expression e>
= (. IToken lp; IToken rp;
= (. IToken lp = null; IToken rp = null;
var args = new List<ActualBinding>();
e = dummyExpr;
.)
"(" (. lp = t; .)
[ TupleArgs<args> ]
")" (. rp = t; .)
(. if (args.Count == 1 && !args[0].IsGhost) {
if (args[0].FormalParameterName != null) {
SemErr(ErrorId.p_no_parenthesized_binding, new RangeToken(lp,rp), "binding not allowed in parenthesized expression");
}
e = new ParensExpression(lp, args[0].Actual);
} else {
// Compute the actual position of ghost arguments
var ghostness = new bool[args.Count];
for (var i = 0; i < args.Count; i++) {
ghostness[i] = false;
}
for (var i = 0; i < args.Count; i++) {
var arg = args[i];
if (arg.IsGhost) {
if (arg.FormalParameterName == null) {
ghostness[i] = true;
} else {
var success = int.TryParse(arg.FormalParameterName.val, out var index);
if (success && 0 <= index && index < args.Count) {
ghostness[index] = true;
}
}
}
}
var argumentGhostness = ghostness.ToList();
// make sure the corresponding tuple type exists
SystemModuleModifiers.Add(b => b.TupleType(lp, args.Count, true, argumentGhostness));
e = new DatatypeValue(lp, SystemModuleManager.TupleTypeName(argumentGhostness), SystemModuleManager.TupleTypeCtorName(args.Count), args);
}
e.RangeToken = new RangeToken(lp, rp);
.)
"(" (. lp = t; .)
(
MaybeDecreasesToExpression<out e, lp>
")" (. rp = t;
if (e is not DatatypeValue) {
e = new ParensExpression(lp, e);
}
.)
|
")" (. rp = t;
e = ProcessTupleArgs(new List<ActualBinding>(), lp);
.)
) (. e.RangeToken = new RangeToken(lp, rp); .)
.

/*------------------------------------------------------------------------*/
Expand Down
13 changes: 12 additions & 1 deletion Source/DafnyCore/Pipeline/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,19 @@ private static void AddAssertedExprToCounterExampleErrorInfo(

if (boogieProofObligationDesc is ProofObligationDescription.ProofObligationDescription dafnyProofObligationDesc) {
var expr = dafnyProofObligationDesc.GetAssertedExpr(options);
string? msg = null;
if (expr != null) {
errorInformation.AddAuxInfo(errorInformation.Tok, expr.ToString(), ErrorReporterExtensions.AssertedExprCategory);
msg = expr.ToString();
}

var extra = dafnyProofObligationDesc.GetExtraExplanation();
if (extra != null) {
msg = (msg ?? "") + extra;
}

if (msg != null) {
errorInformation.AddAuxInfo(errorInformation.Tok, msg,
ErrorReporterExtensions.AssertedExprCategory);
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Source/DafnyCore/Resolver/ExpressionTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ bool onlyGhostParametersChange(Expression ee) {
// other conditions are checked below
subexpressionsAreInsideBranchesOnlyExcept = matchExpr.Source;

} else if (expr is DecreasesToExpr _) {
ReportError(ErrorId.r_decreases_to_only_in_specification,
expr, "a `decreases to` expression is allowed only in specification and ghost contexts");
return false;

} else if (expr is ConcreteSyntaxExpression concreteSyntaxExpression) {
return CheckIsCompilable(concreteSyntaxExpression.ResolvedExpression, codeContext, insideBranchesOnly);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,12 @@ public void ResolveExpressionX(Expression expr, ResolutionContext resolutionCont

} else if (expr is NestedMatchExpr nestedMatchExpr) {
ResolveNestedMatchExpr(nestedMatchExpr, resolutionContext);
} else if (expr is DecreasesToExpr decreasesToExpr) {
foreach (var subexpr in decreasesToExpr.SubExpressions) {
ResolveExpression(subexpr, resolutionContext);
}

decreasesToExpr.Type = Type.Bool;
} else {
Contract.Assert(false); throw new cce.UnreachableException(); // unexpected expression
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,13 @@ resolutionContext.CodeContext is ConstantField ||
var e = (NestedMatchExpr)expr;
ResolveNestedMatchExpr(e, resolutionContext);

} else if (expr is DecreasesToExpr decreasesToExpr) {
foreach (var e in decreasesToExpr.SubExpressions) {
ResolveExpression(e, resolutionContext);
}

decreasesToExpr.PreType = ConstrainResultToBoolFamilyOperator(decreasesToExpr.tok, "decreasesto");

} else if (expr is MatchExpr) {
Contract.Assert(false); // this case is always handled via NestedMatchExpr

Expand Down
3 changes: 2 additions & 1 deletion Source/DafnyCore/Resolver/ResolutionErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public enum ErrorId {
r_failure_methods_deprecated,
r_member_only_assumes_other,
r_member_only_has_no_before_after,
r_empty_cyclic_datatype
r_empty_cyclic_datatype,
r_decreases_to_only_in_specification
}

static ResolutionErrors() {
Expand Down
11 changes: 8 additions & 3 deletions Source/DafnyCore/Verifier/BoogieGenerator.Decreases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public partial class BoogieGenerator {
void CheckCallTermination(IToken tok, List<Expression> contextDecreases, List<Expression> calleeDecreases,
Expression allowance,
Expression receiverReplacement, Dictionary<IVariable, Expression> substMap,
Dictionary<IVariable, Expression> directSubstMap,
Dictionary<TypeParameter, Type> typeMap,
ExpressionTranslator etranCurrent, bool oldCaller, BoogieStmtListBuilder builder, bool inferredDecreases, string hint) {
Contract.Requires(tok != null);
Expand Down Expand Up @@ -69,6 +70,7 @@ void CheckCallTermination(IToken tok, List<Expression> contextDecreases, List<Ex
}
for (int i = 0; i < N; i++) {
Expression e0 = Substitute(calleeDecreases[i], receiverReplacement, substMap, typeMap);
Expression e0direct = Substitute(calleeDecreases[i], receiverReplacement, directSubstMap, typeMap);
Expression e1 = contextDecreases[i];
if (oldCaller) {
e1 = new OldExpr(e1.tok, e1) {
Expand All @@ -80,7 +82,7 @@ void CheckCallTermination(IToken tok, List<Expression> contextDecreases, List<Ex
break;
}
oldExpressions.Add(e1);
newExpressions.Add(e0);
newExpressions.Add(e0direct);
toks.Add(new NestedToken(tok, e1.tok));
types0.Add(e0.Type.NormalizeExpand());
types1.Add(e1.Type.NormalizeExpand());
Expand All @@ -94,7 +96,7 @@ void CheckCallTermination(IToken tok, List<Expression> contextDecreases, List<Ex
}
builder.Add(Assert(tok, decrExpr, new
PODesc.Terminates(inferredDecreases, null, allowance,
oldExpressions, newExpressions, hint)));
oldExpressions, newExpressions, endsWithWinningTopComparison, hint)));
}

/// <summary>
Expand Down Expand Up @@ -173,7 +175,7 @@ Bpl.Expr DecreasesCheck(List<IToken> toks, List<Type> types0, List<Type> types1,
return decrCheck;
}

bool CompatibleDecreasesTypes(Type t, Type u) {
static bool CompatibleDecreasesTypes(Type t, Type u) {
Contract.Requires(t != null);
Contract.Requires(u != null);
t = t.NormalizeToAncestorType();
Expand Down Expand Up @@ -356,5 +358,8 @@ void ComputeLessEq(IToken tok, Type ty0, Type ty1, Bpl.Expr e0, Bpl.Expr e1, out
less = BplAnd(Bpl.Expr.Not(b0), b1);
atmost = BplImp(b0, b1);
}

less.tok = tok;
atmost.tok = tok;
}
}
Loading