Skip to content

Commit 4362fb1

Browse files
authored
Print effects (#1353)
This PR adds a way to control print effects. It follows the suggestion described in #1346, except that it names the command-line option `/trackPrintEffects`. Fixes #1346 Also: * Document {:axiom} attribute * Add Attributes.Find method
1 parent fe0ec94 commit 4362fb1

File tree

10 files changed

+341
-8
lines changed

10 files changed

+341
-8
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- feat: `continue` statements. Like Dafny's `break` statements, these come in two forms: one that uses a label to name the continue target and one that specifies the continue target by nesting level. See section [19.2](https://dafny-lang.github.io/dafny/DafnyRef/DafnyRef#sec-break-continue) of the Reference Manual. (https://github.com/dafny-lang/dafny/pull/1839)
44
- feat: The keyword syntax for functions will change in Dafny version 4. The new command-line option `/functionSyntax` (see `/help`) allows early adoption of the new syntax. (https://github.com/dafny-lang/dafny/pull/1832)
5+
- feat: Attribute `{:print}` declares that a method may have print effects. Print effects are enforced only with `/trackPrintEffects:1`.
56
- fix: No warning "File contains no code" if a file only contains a submodule (https://github.com/dafny-lang/dafny/pull/1840)
67
- fix: Stuck in verifying in VSCode - support for verification cancellation (https://github.com/dafny-lang/dafny/pull/1771)
78
- fix: export-reveals of function-by-method now allows the function body to be ghost (https://github.com/dafny-lang/dafny/pull/1855)

Source/Dafny/AST/DafnyAst.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,16 @@ public static bool Contains(Attributes attrs, string nm) {
426426
return attrs.AsEnumerable().Any(aa => aa.Name == nm);
427427
}
428428

429+
/// <summary>
430+
/// Returns first occurrence of an attribute named <c>nm</c>, or <c>null</c> if there is no such
431+
/// attribute.
432+
/// </summary>
433+
[Pure]
434+
public static Attributes/*?*/ Find(Attributes attrs, string nm) {
435+
Contract.Requires(nm != null);
436+
return attrs.AsEnumerable().FirstOrDefault(attr => attr.Name == nm);
437+
}
438+
429439
/// <summary>
430440
/// Returns true if "nm" is a specified attribute. If it is, then:
431441
/// - if the attribute is {:nm true}, then value==true

Source/Dafny/DafnyOptions.cs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public enum CompilationTarget {
8989

9090
public CompilationTarget CompileTarget = CompilationTarget.Csharp;
9191
public bool CompileVerbose = true;
92+
public bool EnforcePrintEffects = false;
9293
public string DafnyPrintCompiledFile = null;
9394
public string CoverageLegendFile = null;
9495
public string MainMethod = null;
@@ -267,6 +268,14 @@ protected override bool ParseOption(string name, Bpl.CommandLineOptionEngine.Com
267268
return true;
268269
}
269270

271+
case "trackPrintEffects": {
272+
int printEffects = 0;
273+
if (ps.GetNumericArgument(ref printEffects, 2)) {
274+
EnforcePrintEffects = printEffects == 1;
275+
}
276+
return true;
277+
}
278+
270279
case "Main":
271280
case "main": {
272281
if (ps.ConfirmArgumentCount(1)) {
@@ -631,9 +640,6 @@ The ReferenceName is the name used to refer to the entity in the target language
631640
Dafny does not perform sanity checks on the arguments---it is the user's responsibility not to generate
632641
malformed target code.
633642
634-
{:axiom}
635-
TODO
636-
637643
{:handle}
638644
TODO
639645
@@ -647,11 +653,25 @@ malformed target code.
647653
TODO
648654
649655
{:axiom}
650-
TODO
656+
Ordinarily, the compiler gives an error for every function or
657+
method without a body. If the function or method is ghost, then
658+
marking it with {:axiom} suppresses the error. The {:axiom}
659+
attribute says you're taking responsibility for the existence
660+
of a body for the function or method.
651661
652662
{:abstemious}
653663
TODO
654664
665+
{:print}
666+
This attributes declares that a method may have print effects,
667+
that is, it may use 'print' statements and may call other methods
668+
that have print effects. The attribute can be applied to compiled
669+
methods, constructors, and iterators, and it gives an error if
670+
applied to functions or ghost methods. An overriding method is
671+
allowed to use a {:print} attribute only if the overridden method
672+
does.
673+
Print effects are enforced only with /trackPrintEffects:1.
674+
655675
{:nativeType}
656676
Can be applied to newtype declarations for integer types and
657677
indicates an expectation of what native type (or not) the
@@ -916,7 +936,7 @@ It can also extend Microsoft.Dafny.Plugins.PluginConfiguration to receive argume
916936
https://github.com/dafny-lang/dafny/blob/master/Source/DafnyLanguageServer/README.md#about-plugins
917937
/Main:<name>
918938
The (fully-qualified) name of the method to use as the executable entry point.
919-
Default is the method with the {{:main}} atrribute, or else the method named 'Main'.
939+
Default is the method with the {{:main}} attribute, or else the method named 'Main'.
920940
/compileVerbose:<n>
921941
0 - don't print status of compilation to the console
922942
1 (default) - print information such as files being written by
@@ -942,6 +962,11 @@ The compiler emits branch-coverage calls and outputs into
942962
<file> a legend that gives a description of each
943963
source-location identifier used in the branch-coverage calls.
944964
(use - as <file> to print to console)
965+
/trackPrintEffects:<n>
966+
0 (default) - Every compiled method, constructor, and iterator, whether or not
967+
it bears a {{:print}} attribute, may have print effects.
968+
1 - A compiled method, constructor, or iterator is allowed to have print effects
969+
only if it is marked with {{:print}}.
945970
/noCheating:<n>
946971
0 (default) - allow assume statements and free invariants
947972
1 - treat all assumptions as asserts, and drop free.

Source/Dafny/Resolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ public void ResolveProgram(Program prog) {
438438
}
439439

440440
rewriters.Add(new InductionRewriter(reporter));
441+
rewriters.Add(new PrintEffectEnforcement(reporter));
441442

442443
foreach (var plugin in DafnyOptions.O.Plugins) {
443444
rewriters.AddRange(plugin.GetRewriters(reporter));
@@ -2046,7 +2047,7 @@ ModuleSignature RegisterTopLevelDecls(ModuleDefinition moduleDef, bool useImport
20462047
new Specification<FrameExpression>(new List<FrameExpression>(), null),
20472048
new List<AttributedExpression>(),
20482049
new Specification<Expression>(new List<Expression>(), null),
2049-
null, null, null);
2050+
null, Attributes.Find(iter.Attributes, "print"), null);
20502051
// add these implicit members to the class
20512052
init.EnclosingClass = iter;
20522053
init.InheritVisibility(iter);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright by the contributors to the Dafny Project
2+
// SPDX-License-Identifier: MIT
3+
4+
using System.Diagnostics.Contracts;
5+
using Microsoft.Boogie;
6+
using Bpl = Microsoft.Boogie;
7+
8+
namespace Microsoft.Dafny {
9+
// ------------------- PrintEffectEnforcement -------------------
10+
11+
public class PrintEffectEnforcement : IRewriter {
12+
internal PrintEffectEnforcement(ErrorReporter reporter) : base(reporter) {
13+
Contract.Requires(reporter != null);
14+
}
15+
16+
internal override void PostDecreasesResolve(ModuleDefinition m) {
17+
foreach (var decl in m.TopLevelDecls) {
18+
if (decl is IteratorDecl iter) {
19+
var hasPrintAttribute = HasPrintAttribute(iter.Attributes);
20+
if (!hasPrintAttribute && iter.Body != null) {
21+
if (DafnyOptions.O.EnforcePrintEffects) {
22+
iter.Body.Body.Iter(stmt => CheckNoPrintEffects(stmt, iter));
23+
}
24+
}
25+
} else if (decl is TopLevelDeclWithMembers cl) {
26+
foreach (var member in cl.Members) {
27+
var hasPrintAttribute = HasPrintAttribute(member.Attributes);
28+
if (member is Function f) {
29+
if (hasPrintAttribute) {
30+
Reporter.Error(MessageSource.Rewriter, member.tok, ":print attribute is not allowed on functions");
31+
}
32+
if (f.ByMethodDecl != null && DafnyOptions.O.EnforcePrintEffects) {
33+
f.ByMethodDecl.Body.Body.Iter(stmt => CheckNoPrintEffects(stmt, f.ByMethodDecl));
34+
}
35+
} else if (member is Method method) {
36+
if (hasPrintAttribute) {
37+
if (member.IsGhost) {
38+
Reporter.Error(MessageSource.Rewriter, member.tok, ":print attribute is not allowed on ghost methods");
39+
} else if (method.OverriddenMethod != null && !HasPrintAttribute(method.OverriddenMethod.Attributes, false)) {
40+
Reporter.Error(MessageSource.Rewriter, member.tok,
41+
"not allowed to override a non-printing method with a possibly printing method ('{0}')", method.Name);
42+
}
43+
} else if (!member.IsGhost && method.Body != null) {
44+
if (DafnyOptions.O.EnforcePrintEffects) {
45+
method.Body.Body.Iter(stmt => CheckNoPrintEffects(stmt, method));
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
bool HasPrintAttribute(Attributes attrs, bool checkParameters = true) {
55+
var printAttribute = Attributes.Find(attrs, "print");
56+
if (checkParameters && printAttribute != null && printAttribute.Args.Count != 0) {
57+
Reporter.Error(MessageSource.Rewriter, printAttribute.Args[0].tok, ":print attribute does not take any arguments");
58+
}
59+
return printAttribute != null;
60+
}
61+
62+
private void CheckNoPrintEffects(Statement stmt, IMethodCodeContext codeContext) {
63+
if (stmt is PrintStmt) {
64+
var method = codeContext as Method;
65+
if (method != null && method.IsByMethod) {
66+
Reporter.Error(MessageSource.Rewriter, stmt.Tok, "a function-by-method is not allowed to use print statements");
67+
} else {
68+
Reporter.Error(MessageSource.Rewriter, stmt.Tok,
69+
"to use a print statement, the enclosing {0} must be marked with {{:print}}", method?.WhatKind ?? ((IteratorDecl)codeContext).WhatKind);
70+
}
71+
} else if (stmt is CallStmt call) {
72+
if (HasPrintAttribute(call.Method.Attributes, false)) {
73+
var method = codeContext as Method;
74+
if (method != null && method.IsByMethod) {
75+
Reporter.Error(MessageSource.Rewriter, stmt.Tok, "a function-by-method is not allowed to call a method with print effects");
76+
} else {
77+
Reporter.Error(MessageSource.Rewriter, stmt.Tok,
78+
"to call a method with print effects, the enclosing {0} must be marked with {{:print}}",
79+
method?.WhatKind ?? ((IteratorDecl)codeContext).WhatKind);
80+
}
81+
}
82+
}
83+
stmt.SubStatements.Iter(s => CheckNoPrintEffects(s, codeContext));
84+
}
85+
}
86+
}

Source/Dafny/Rewriter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics.Contracts;
7+
using Microsoft.Boogie;
78
using Bpl = Microsoft.Boogie;
89
using IToken = Microsoft.Boogie.IToken;
910

@@ -2058,5 +2059,3 @@ internal override void PostResolve(Program program) {
20582059
}
20592060
}
20602061
}
2061-
2062-

Test/dafny0/PrintEffects.dfy

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// RUN: %dafny /compile:3 "%s" > "%t"
2+
// RUN: %dafny /compile:3 /trackPrintEffects:1 "%s" >> "%t"
3+
// RUN: %diff "%s.expect" "%t"
4+
5+
method Main() {
6+
print "hello, Main\n"; // error: cannot print from this method
7+
P(); // error: cannot call printing method
8+
var iter0 := new NoPrintIter(3);
9+
var iter1 := new PrintIter(3);
10+
var _ := iter0.MoveNext();
11+
var _ := iter1.MoveNext(); // error: cannot call printing method
12+
var cl0 := new Cl.NoPrint();
13+
var cl1 := new Cl.Print(); // error: cannot call printing constructor
14+
}
15+
16+
method {:print} P() {
17+
print "method P here\n";
18+
M();
19+
var iter0 := new NoPrintIter(3);
20+
var iter1 := new PrintIter(3);
21+
print "calling MoveNexts\n";
22+
MoveNexts(iter0, iter1);
23+
var cl := new Cl.NoPrint();
24+
cl := new Cl.Print();
25+
26+
TestOverrides();
27+
}
28+
29+
method MoveNexts(iter0: NoPrintIter, iter1: PrintIter)
30+
requires iter0.Valid() && iter1.Valid()
31+
requires iter0._modifies == iter0._new == iter0._reads == {}
32+
requires iter1._modifies == iter1._new == iter1._reads == {}
33+
modifies iter0, iter1
34+
{
35+
var more0 := iter0.MoveNext();
36+
var more1 := iter1.MoveNext(); // error: cannot print from this method
37+
}
38+
39+
method M() {
40+
var x := F(3);
41+
print "bye, from M\n"; // error: cannot print from this method
42+
}
43+
44+
function F(x: int): int {
45+
10
46+
} by method {
47+
print "function-by-method F\n"; // error: cannot print from this method
48+
return 10;
49+
}
50+
51+
iterator NoPrintIter(a: int) yields (x: int)
52+
{
53+
print "Start of Iter 0\n"; // error: cannot print from this method
54+
yield 3 + a;
55+
print "End of Iter 0\n"; // error: cannot print from this method
56+
}
57+
58+
iterator {:print} PrintIter(a: int) yields (x: int)
59+
{
60+
print "Start of Iter 1\n";
61+
yield 3 + a;
62+
print "End of Iter 1\n";
63+
}
64+
65+
class Cl {
66+
constructor NoPrint() {
67+
print "Cl.NoPrint ctor\n"; // error: cannot print from this method
68+
}
69+
constructor {:print} Print() {
70+
print "Cl.Print ctor\n";
71+
}
72+
}
73+
74+
trait Trait {
75+
method {:print} MayPrint()
76+
method {:print} AlwaysPrints()
77+
}
78+
79+
class Overrides extends Trait {
80+
method MayPrint() { // allowed to drop {:print} attribute
81+
print "Override X"; // error: cannot print without a {:print} attribute
82+
}
83+
method {:print} AlwaysPrints() {
84+
print " Y\n";
85+
}
86+
}
87+
88+
method {:print} TestOverrides() {
89+
var c: Overrides := new Overrides;
90+
var t: Trait := c;
91+
92+
t.MayPrint();
93+
t.AlwaysPrints();
94+
95+
c.MayPrint();
96+
c.AlwaysPrints();
97+
98+
TestOverridesNoPrint(c, t);
99+
}
100+
101+
method TestOverridesNoPrint(c: Overrides, t: Trait) {
102+
t.MayPrint(); // error: cannot call printing method
103+
t.AlwaysPrints(); // error: cannot call printing method
104+
105+
c.MayPrint();
106+
c.AlwaysPrints(); // error: cannot call printing method
107+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
Dafny program verifier finished with 13 verified, 0 errors
3+
hello, Main
4+
method P here
5+
function-by-method F
6+
bye, from M
7+
calling MoveNexts
8+
Start of Iter 0
9+
Start of Iter 1
10+
Cl.NoPrint ctor
11+
Cl.Print ctor
12+
Override X Y
13+
Override X Y
14+
Override X Y
15+
Override X Y
16+
Start of Iter 0
17+
Start of Iter 1
18+
Cl.NoPrint ctor
19+
Cl.Print ctor
20+
PrintEffects.dfy(53,2): Error: to use a print statement, the enclosing iterator must be marked with {:print}
21+
PrintEffects.dfy(55,2): Error: to use a print statement, the enclosing iterator must be marked with {:print}
22+
PrintEffects.dfy(67,4): Error: to use a print statement, the enclosing constructor must be marked with {:print}
23+
PrintEffects.dfy(81,4): Error: to use a print statement, the enclosing method must be marked with {:print}
24+
PrintEffects.dfy(6,2): Error: to use a print statement, the enclosing method must be marked with {:print}
25+
PrintEffects.dfy(7,3): Error: to call a method with print effects, the enclosing method must be marked with {:print}
26+
PrintEffects.dfy(11,25): Error: to call a method with print effects, the enclosing method must be marked with {:print}
27+
PrintEffects.dfy(13,20): Error: to call a method with print effects, the enclosing method must be marked with {:print}
28+
PrintEffects.dfy(36,29): Error: to call a method with print effects, the enclosing method must be marked with {:print}
29+
PrintEffects.dfy(41,2): Error: to use a print statement, the enclosing method must be marked with {:print}
30+
PrintEffects.dfy(47,2): Error: a function-by-method is not allowed to use print statements
31+
PrintEffects.dfy(102,12): Error: to call a method with print effects, the enclosing method must be marked with {:print}
32+
PrintEffects.dfy(103,16): Error: to call a method with print effects, the enclosing method must be marked with {:print}
33+
PrintEffects.dfy(106,16): Error: to call a method with print effects, the enclosing method must be marked with {:print}
34+
14 resolution/type errors detected in PrintEffects.dfy

0 commit comments

Comments
 (0)