The dafny-lang community
Latest release snapshot
Abstract:This is the Dafny reference manual; it describes the Dafny programminglanguage and how to use the Dafny verification system.Parts of this manual are more tutorial in nature in order to help theuser understand how to do proofs with Dafny.
(Link to current document as html)
- 1. Introduction
- 2. Lexical and Low Level Grammar
- 3. Programs (grammar)
- 4. Modules (grammar)
- 5. Types
- 5.1. Kinds of types
- 5.2. Basic types
- 5.3. Type parameters (grammar)
- 5.4. Generic Instantiation (grammar)
- 5.5. Collection types
- 5.6. Types that stand for other types (grammar)
- 5.7. Newtypes (grammar)
- 5.8. Class types (grammar)
- 5.9. Trait types (grammar)
- 5.10. Array types (grammar)
- 5.11. Iterator types (grammar)
- 5.12. Arrow types (grammar)
- 5.13. Tuple types
- 5.14. Algebraic Datatypes (grammar)
- 5.14.1. Inductive datatypes
- 5.14.2. Coinductive datatypes
- 5.14.3. Coinduction
- 6. Member declarations
- 7. Specifications
- 8. Statements (grammar)
- 8.1. Labeled Statement (grammar)
- 8.2. Block Statement (grammar)
- 8.3. Return Statement (grammar)
- 8.4. Yield Statement (grammar)
- 8.5. Update and Call Statements (grammar)
- 8.6. Update with Failure Statement (
:-
) (grammar)- 8.6.1. Failure compatible types
- 8.6.2. Simple status return with no other outputs
- 8.6.3. Status return with additional outputs
- 8.6.4. Failure-returns with additional data
- 8.6.5. RHS with expression list
- 8.6.6. Failure with initialized declaration.
- 8.6.7. Keyword alternative
- 8.6.8. Key points
- 8.6.9. Failure returns and exceptions
- 8.7. Variable Declaration Statement (grammar)
- 8.8. Guards (grammar)
- 8.9. Binding Guards (grammar)
- 8.10. If Statement (grammar)
- 8.11. Match Statement (grammar)
- 8.12. While Statement (grammar)
- 8.13. For Loops (grammar)
- 8.14. Break and Continue Statements (grammar)
- 8.15. Loop Specifications
- 8.16. Print Statement (grammar)
- 8.17. Assert statement (grammar)
- 8.18. Assume Statement (grammar)
- 8.19. Expect Statement (grammar)
- 8.20. Reveal Statement (grammar)
- 8.21. Forall Statement (grammar)
- 8.22. Modify Statement (grammar)
- 8.23. Calc Statement (grammar)
- 8.24. Opaque Block (grammar)
- 9. Expressions
- 9.1. Lemma-call expressions (grammar)
- 9.2. Equivalence Expressions (grammar)
- 9.3. Implies or Explies Expressions (grammar)
- 9.4. Logical Expressions (grammar)
- 9.5. Relational Expressions (grammar)
- 9.6. Bit Shifts (grammar)
- 9.7. Terms (grammar)
- 9.8. Factors (grammar)
- 9.9. Bit-vector Operations (grammar)
- 9.10. As (Conversion) and Is (type test) Expressions (grammar)
- 9.11. Unary Expressions (grammar)
- 9.12. Primary Expressions (grammar)
- 9.13. Lambda expressions (grammar)
- 9.14. Left-Hand-Side Expressions (grammar)
- 9.15. Right-Hand-Side Expressions (grammar)
- 9.16. Array Allocation (grammar)
- 9.17. Object Allocation (grammar)
- 9.18. Havoc Right-Hand-Side (grammar)
- 9.19. Constant Or Atomic Expressions (grammar)
- 9.20. Literal Expressions (grammar}
- 9.21.
this
Expression (grammar) - 9.22. Old and Old@ Expressions (grammar)
- 9.23. Fresh Expressions (grammar)
- 9.24. Allocated Expressions (grammar)
- 9.25. Unchanged Expressions (grammar)
- 9.26. Cardinality Expressions (grammar)
- 9.27. Parenthesized Expressions (grammar)
- 9.28. Sequence Display Expression (grammar)
- 9.29. Set Display Expression (grammar)
- 9.30. Map Display Expression (grammar)
- 9.31. Endless Expression (grammar)
- 9.31.1. If Expression (grammar)
- 9.31.2. Case and Extended Patterns (grammar)
- 9.31.3. Match Expression (grammar)
- 9.31.4. Quantifier Expression (grammar)
- 9.31.5. Set Comprehension Expressions (grammar)
- 9.31.6. Statements in an Expression (grammar)
- 9.31.7. Let and Let or Fail Expression (grammar)
- 9.31.8. Map Comprehension Expression (grammar)
- 9.32. Name Segment (grammar)
- 9.33. Hash call (grammar)
- 9.34. Suffix (grammar)
- 9.35. Expression Lists (grammar)
- 9.36. Parameter Bindings (grammar)
- 9.37. Assigned Expressions
- 9.38. Termination Ordering Expressions
- 9.39. Compile-Time Constants
- 9.40. List of specification expressions
- 10. Refinement
- 11. Attributes
- 11.1. Attributes on top-level declarations
- 11.2. Attributes on functions and methods
- 11.2.1.
{:abstemious}
- 11.2.2.
{:autoReq}
- 11.2.3.
{:autoRevealDependenciesk}
- 11.2.4.
{:axiom}
- 11.2.5.
{:compile}
- 11.2.6.
{:concurrent}
- 11.2.7.
{:extern<name>}
- 11.2.8.
{:fuelX}
- 11.2.9.
{:id<string>}
- 11.2.10.
{:induction}
- 11.2.11.
{:inductionTrigger}
- 11.2.12.
{:only}
- 11.2.13.
{:print}
- 11.2.14.
{:priority}
- 11.2.15.
{:resource_limit}
and{:rlimit}
- 11.2.16.
{:selective_checking}
- 11.2.17.
{:tailrecursion}
- 11.2.18.
{:test}
- 11.2.19.
{:timeLimitN}
- 11.2.20.
{:timeLimitMultiplierX}
- 11.2.21.
{:transparent}
- 11.2.22.
{:verifyfalse}
- 11.2.23.
{:vcs_max_costN}
- 11.2.24.
{:vcs_max_keep_going_splitsN}
- 11.2.25.
{:vcs_max_splitsN}
- 11.2.26.
{:isolate_assertions}
- 11.2.27.
{:synthesize}
- 11.2.28.
{:optionsOPT0,OPT1,...}
- 11.2.1.
- 11.3. Attributes on reads and modifies clauses
- 11.4. Attributes on assertions, preconditions and postconditions
- 11.5. Attributes on variable declarations
- 11.6. Attributes on quantifier expressions (forall, exists)
- 11.7. Deprecated attributes
- 11.8. Other undocumented verification attributes
- 11.9. New attribute syntax
- 12. Advanced Topics
- 13. Dafny User’s Guide
- 13.1. Introduction
- 13.2. Installing Dafny
- 13.3. Dafny Programs and Files
- 13.4. Dafny Standard Libraries
- 13.5. Dafny Code Style
- 13.6. Using Dafny From the Command Line
- 13.6.1. dafny commands
- 13.6.1.1. Options that are not associated with a command
- 13.6.1.2.
dafnyresolve
- 13.6.1.3.
dafnyverify
- 13.6.1.4.
dafnytranslate<language>
- 13.6.1.5.
dafnybuild
- 13.6.1.6.
dafnyrun
- 13.6.1.7.
dafnyserver
- 13.6.1.8.
dafnyaudit
- 13.6.1.9.
dafnyformat
- 13.6.1.10.
dafnytest
- 13.6.1.11.
dafnydoc
[Experimental] - 13.6.1.12.
dafnygenerate-tests
- 13.6.1.13.
Inlining
- 13.6.1.14.
CommandLineOptions
- 13.6.1.15.
dafnyfind-dead-code
- 13.6.1.16.
dafnymeasure-complexity
- 13.6.1.17. Plugins
- 13.6.1.18. Legacy operation
- 13.6.2. In-tool help
- 13.6.3. dafny exit codes
- 13.6.4. dafny output
- 13.6.5. Project files
- 13.6.1. dafny commands
- 13.7. Verification
- 13.7.1. Verification debugging when verification fails
- 13.7.2. Verification debugging when verification is slow
- 13.7.3. Assertion batches, well-formedness, correctness
- 13.7.4. Command-line options and other attributes to control verification
- 13.7.5. Analyzing proof dependencies
- 13.7.6. Debugging brittle verification
- 13.8. Compilation
- 13.9. Dafny Command Line Options
- 13.9.1. Help and version information
- 13.9.2. Controlling input
- 13.9.3. Controlling plugins
- 13.9.4. Controlling output
- 13.9.5. Controlling language features
- 13.9.6. Controlling warnings
- 13.9.7. Controlling verification
- 13.9.8. Controlling compilation
- 13.9.9. Controlling Boogie
- 13.9.10. Controlling the prover
- 13.9.11. Controlling test generation
- 14. Dafny VSCode extension and the Dafny Language Server
- 15. Plugins to Dafny
- 16. Full list of legacy command-line options {#sec-full-command-line-options}
- 17. Dafny Grammar
- 17.1. Dafny Syntax
- 17.2. Dafny Grammar productions
- 17.2.1. Programs
- 17.2.2. Modules
- 17.2.3. Types
- 17.2.4. Type member declarations
- 17.2.5. Specifications
- 17.2.5.1. Method specifications
- 17.2.5.2. Function specifications
- 17.2.5.3. Lambda function specifications
- 17.2.5.4. Iterator specifications
- 17.2.5.5. Loop specifications
- 17.2.5.6. Requires clauses
- 17.2.5.7. Ensures clauses
- 17.2.5.8. Decreases clauses
- 17.2.5.9. Modifies clauses
- 17.2.5.10. Invariant clauses
- 17.2.5.11. Reads clauses
- 17.2.5.12. Frame expressions
- 17.2.6. Statements
- 17.2.6.1. Labeled statement
- 17.2.6.2. Non-Labeled statement
- 17.2.6.3. Break and continue statements
- 17.2.6.4. Block statement
- 17.2.6.5. Return statement
- 17.2.6.6. Yield statement
- 17.2.6.7. Update and call statement
- 17.2.6.8. Update with failure statement
- 17.2.6.9. Variable declaration statement
- 17.2.6.10. Guards
- 17.2.6.11. Binding guards
- 17.2.6.12. If statement
- 17.2.6.13. While Statement
- 17.2.6.14. For statement
- 17.2.6.15. Match statement
- 17.2.6.16. Assert statement
- 17.2.6.17. Assume statement
- 17.2.6.18. Expect statement
- 17.2.6.19. Print statement
- 17.2.6.20. Reveal statement
- 17.2.6.21. Forall statement
- 17.2.6.22. Modify statement
- 17.2.6.23. Calc statement
- 17.2.6.24. Opaque block
- 17.2.7. Expressions
- 17.2.7.1. Top-level expression
- 17.2.7.2. Equivalence expression
- 17.2.7.3. Implies expression
- 17.2.7.4. Logical expression
- 17.2.7.5. Relational expression
- 17.2.7.6. Bit-shift expression
- 17.2.7.7. Term (addition operations)
- 17.2.7.8. Factor (multiplication operations)
- 17.2.7.9. Bit-vector expression
- 17.2.7.10. As/Is expression
- 17.2.7.11. Unary expression
- 17.2.7.12. Primary expression
- 17.2.7.13. Lambda expression
- 17.2.7.14. Left-hand-side expression
- 17.2.7.15. Right-hand-side expression
- 17.2.7.16. Array allocation right-hand-side expression
- 17.2.7.17. Object allocation right-hand-side expression
- 17.2.7.18. Havoc right-hand-side expression
- 17.2.7.19. Atomic expressions
- 17.2.7.20. Literal expressions
- 17.2.7.21. This expression
- 17.2.7.22. Old and Old@ Expressions
- 17.2.7.23. Fresh Expressions
- 17.2.7.24. Allocated Expressions
- 17.2.7.25. Unchanged Expressions
- 17.2.7.26. Cardinality Expressions
- 17.2.7.27. Parenthesized Expression
- 17.2.7.28. Sequence Display Expression
- 17.2.7.29. Set Display Expression
- 17.2.7.30. Map Display Expression
- 17.2.7.31. Endless Expression
- 17.2.7.32. If expression
- 17.2.7.33. Match Expression
- 17.2.7.34. Case and Extended Patterns
- 17.2.7.35. Quantifier expression
- 17.2.7.36. Set Comprehension Expressions
- 17.2.7.37. Map Comprehension Expression
- 17.2.7.38. Statements in an Expression
- 17.2.7.39. Let and Let or Fail Expression
- 17.2.7.40. Name Segment
- 17.2.7.41. Hash Call
- 17.2.7.42. Suffix
- 17.2.7.43. Augmented Dot Suffix
- 17.2.7.44. Datatype Update Suffix
- 17.2.7.45. Subsequence Suffix
- 17.2.7.46. Subsequence Slices Suffix
- 17.2.7.47. Sequence Update Suffix
- 17.2.7.48. Selection Suffix
- 17.2.7.49. Argument List Suffix
- 17.2.7.50. Expression Lists
- 17.2.7.51. Parameter Bindings
- 17.2.7.52. Quantifier domains
- 17.2.7.53. Basic name and type combinations
- 18. Testing syntax rendering
- 19. References
1. Introduction
Dafny [@Leino:Dafny:LPAR16] is a programming language with built-in specification constructs,so that verifying a program’s correctness with respect to those specificationsis a natural part of writing software.The Dafny static program verifier can be used to verify the functionalcorrectness of programs.This document is a reference manual for the programming language and a user guidefor thedafny
tool that performs verification and compilation to anexecutable form.
The Dafny programming language is designed to support the staticverification of programs. It is imperative, sequential, supports genericclasses, inheritance and abstraction, methods and functions, dynamic allocation, inductive andcoinductive datatypes, and specification constructs. Thespecifications include pre- and postconditions, frame specifications(read and write sets), and termination metrics. To further supportspecifications, the language also offers updatable ghost variables,recursive functions, and types like sets and sequences. Specificationsand ghost constructs are used only during verification; the compileromits them from the executable code.
Thedafny
verifier is run as part of the compiler. As such, a programmerinteracts with it in much the same way as with the static typechecker—when the tool produces errors, the programmer responds bychanging the program’s type declarations, specifications, and statements.
(This document typically uses “Dafny” to refer to the programming languageanddafny
to refer to the software tool that verifies and compiles programsin the Dafny language.)
The easiest way to try out the Dafny language is todownload the supporting tools and documentation andrundafny
on your machine as you follow along with theDafny tutorial.Thedafny
tool can be run from the command line (on Linux, MacOS, Windows or other platforms) or from IDEssuch as emacs and VSCode, which can provide syntax highlighting and code manipulation capabilities.
The verifier is poweredbyBoogie[@Boogie:Architecture;@Leino:Boogie2-RefMan;@LeinoRuemmer:Boogie2]andZ3 [@deMouraBjorner:Z3:overview].
From verified programs, thedafny
compiler can produce code for a numberof different backends:the .NET platform via intermediate C# files, Java, Javascript, Go, and C++.Each language provides a basic Foreign Function Interface (through uses of:extern
)and a supporting runtime library.
This reference manual for the Dafny verification system isbased on the following references:[@Leino:Dafny:LPAR16],[@MSR:dafny:main],[@LEINO:Dafny:Calc],[@LEINO:Dafny:Coinduction],Co-induction Simply.
The main part of the reference manual is in top down order except for aninitial section that deals with the lowest level constructs.
The details of using (and contributing to) the dafny tool are described in the User Guide (Section 13).
1.1. Dafny 4.0
The most recent major version of the Dafny language is Dafny 4.0, released in February 2023.It has some backwards incompatibilities with Dafny 3, as decribed in themigration guide.
The user documentation has been expanded with more examples, a FAQ, and an error explanation catalog.There is even a new book,Program Proofs by Dafny designer Rustan Leino.
The IDE now has a framework for showing error explanation information and corresponding quick fixes arebeing added, with refactoring operations on the horizon.
More details of 4.0 functionality are described in the release notes.
1.2. Dafny Example
To give a flavor of Dafny, here is the solution to a competition problem.
// VSComp 2010, problem 3, find a 0 in a linked list and return// how many nodes were skipped until the first 0 (or end-of-list)// was found.// Rustan Leino, 18 August 2010.//// The difficulty in this problem lies in specifying what the// return value 'r' denotes and in proving that the program// terminates. Both of these are addressed by declaring a ghost// field 'List' in each linked-list node, abstractly representing// the linked-list elements from the node to the end of the linked// list. The specification can now talk about that sequence of// elements and can use 'r' as an index into the sequence, and// termination can be proved from the fact that all sequences in// Dafny are finite.//// We only want to deal with linked lists whose 'List' field is// properly filled in (which can only happen in an acyclic list,// for example). To that end, the standard idiom in Dafny is to// declare a predicate 'Valid()' that is true of an object when// the data structure representing that object's abstract value// is properly formed. The definition of 'Valid()' is what one// intuitively would think of as the ''object invariant'', and// it is mentioned explicitly in method pre- and postconditions.//// As part of this standard idiom, one also declares a ghost// variable 'Repr' that is maintained as the set of objects that// make up the representation of the aggregate object--in this// case, the Node itself and all its successors.module{:options"--function-syntax:4"}M{classNode{ghostvarList:seq<int>ghostvarRepr:set<Node>varhead:intvarnext:Node?// Node? means a Node value or nullghostpredicateValid()readsthis,Repr{thisinRepr&&1<=|List|&&List[0]==head&&(next==null==>|List|==1)&&(next!=null==>nextinRepr&&next.Repr<=Repr&&this!innext.Repr&&next.Valid()&&next.List==List[1..])}staticmethodCons(x:int,tail:Node?)returns(n:Node)requirestail==null||tail.Valid()ensuresn.Valid()ensuresiftail==nullthenn.List==[x]elsen.List==[x]+tail.List{n:=newNode;n.head,n.next:=x,tail;if(tail==null){n.List:=[x];n.Repr:={n};}else{n.List:=[x]+tail.List;n.Repr:={n}+tail.Repr;}}}methodSearch(ll:Node?)returns(r:int)requiresll==null||ll.Valid()ensuresll==null==>r==0ensuresll!=null==>0<=r&&r<=|ll.List|&&(r<|ll.List|==>ll.List[r]==0&&0!inll.List[..r])&&(r==|ll.List|==>0!inll.List){if(ll==null){r:=0;}else{varjj,i:=ll,0;while(jj!=null&&jj.head!=0)invariantjj!=null==>jj.Valid()&&i+|jj.List|==|ll.List|&&ll.List[i..]==jj.Listinvariantjj==null==>i==|ll.List|invariant0!inll.List[..i]decreases|ll.List|-i{jj:=jj.next;i:=i+1;}r:=i;}}methodMain(){varlist:Node?:=null;list:=list.Cons(0,list);list:=list.Cons(5,list);list:=list.Cons(0,list);list:=list.Cons(8,list);varr:=Search(list);print"Search returns ",r,"\n";assertr==1;}}
2. Lexical and Low Level Grammar
As with most languages, Dafny syntax is defined in two levels. First the streamof input characters is broken up intotokens. Then these tokens are parsedusing the Dafny grammar.
The Dafny grammar is designed as anattributed grammar, which is a conventional BNF-style set of productions, but in which the productions canhave arguments. The arguments control some alternatives withinthe productions, such as whether an alternative is allowed or not in a specific context.These arguments allow for a more compact and understandable grammar.
The precise, technical details of the grammar are presented together inSection 17.The expository parts of this manual present the language structure less formally.Throughout this document there are embedded hyperlinks to relevant grammar sections, marked asgrammar.
2.1. Dafny Input
Dafny source code files are readable text encoded in UTF-8.All program text other than the contents of comments, character, string and verbatim string literalsconsists of printable and white-space ASCII characters,that is, ASCII characters in the range!
to~
, plus space, tab, carriage return and newline (ASCII 9, 10, 13, 32) characters.(In some past versions of Dafny, non-ASCII, unicode representations of some mathematical symbols werepermitted in Dafny source text; these are no longer recognized.)
String and character literals and comments may contain any unicode character,either directly or as an escape sequence.
2.2. Tokens and whitespace
The characters used in a Dafny program fall into four groups:
- White space characters: space, tab, carriage return and newline
- alphanumerics: letters, digits, underscore (
_
), apostrophe ('
), and question mark (?
) - punctuation:
(){}[],.`;
- operator characters (the other printable characters)
Except for string and character literals, each Dafny token consists of a sequence of consecutive characters from just one of thesegroups, excluding white-space. White-space is ignored except that itseparates tokens and except in the bodies of character and string literals.
A sequence of alphanumeric characters (with no preceding or following additionalalphanumeric characters) is asingle token. This is true even if the tokenis syntactically or semantically invalid and the sequence could be separated intomore than one valid token. For example,assert56
is one identifier token,not a keywordassert
followed by a number;ifb!=0
begins with the tokenifb
and not with the keywordif
and tokenb
;0xFFFFZZ
is an illegaltoken, not a valid hex number0xFFFF
followed by an identifierZZ
.White-space must be used to separate two such tokens in a program.
Somewhat differently, operator tokens need not be separated.Only specific sequences of operator characters are recognized and theseare somewhat context-sensitive. For example, inseq<set<int>>
, the grammarknows that>>
is two individual>
tokens terminating the nestedtype parameter lists; the right shift operator>>
would never be valid here. Similarly, thesequence==>
is always one token; even if it were invalid in its context,separating it into==
and>
would always still be invalid.
In summary, except for required white space between alphanumeric tokens,adding or removing white space between tokens can never result in changing the meaning of a Dafny program.For most of this document, we consider Dafny programs as sequences of tokens.
2.3. Character Classes
This section defines character classes used later in the token definitions.In this section
- a backslash is used to start an escape sequence (so for example
'\n'
denotes the single linefeed character) - double quotesenclose the set of characters constituting a character class
- enclosing singlequotes are used when there is just one character in the class(perhaps expressed with a
\
escape character) +
indicatesthe union of two character classes-
is the set-difference between thetwo classesANY
designates allunicode characters.
name | description |
---|---|
letter | ASCII upper or lower case letter; no unicode characters |
digit | base-ten digit (“0123456789”) |
posDigit | digits, excluding 0 (“123456789”) |
posDigitFrom2 | digits excluding 0 and 1 (“23456789”) |
hexdigit | a normal hex digit (“0123456789abcdefABCDEF”) |
special | `?_” |
cr | carriage return character (ASCII 10) |
lf | line feed character (ASCII 13) |
tab | tab character (ASCII 9) |
space | space character (ASCII 32) |
nondigitIdChar | characters allowed in an identifier, except digits (letter + special) |
idchar | characters allowed in an identifier (nondigitIdChar + digits) |
nonidchar | characters not in identifiers (ANY - idchar) |
charChar | characters allowed in a character constant (ANY - ‘'’ - ‘\’ - cr - lf) |
stringChar | characters allowed in a string constant (ANY - ‘”’ - ‘\’ - cr - lf) |
verbatimStringChar | characters allowed in a verbatim string constant (ANY - ‘”’) |
Thespecial characters are the characters in addition to alphanumeric charactersthat are allowed to appear in a Dafny identifier. These are
'
because mathematicians like to put primes on identifiers and some MLprogrammers like to start names of type parameters with a'
,_
because computer scientists expect to be able to have underscores in identifiers, and?
because it is useful to have?
at the end of names of predicates,e.g.,Cons?
.
Anonidchar
is any character except those that can be used in an identifier.Here the scanner generator will interpretANY
as any unicode character.However,nonidchar
is used only to mark the end of the!in
token;in this context any character other thanwhitespace or printable ASCIIwill trigger a subsequent scanning or parsing error.
2.4. Comments
Comments are in two forms.
- They may go from
/*
to*/
. - They may go from
//
to the end of the line.
A comment is identified as a token during the tokenization of input text and is then discarded for the purpose of interpreting the Dafny program. (It is retained to enable auto-formattingand provide accurate source locations for error messages.)Thus comments are token separators:a/*x*/b
becomes two tokensa
andb
.
Comments may be nested,but note that the nesting of multi-line comments is behavior that is differentfrom most programming languages. In Dafny,
methodm(){/* comment /* nested comment */ rest of outer comment */}
is permitted; this feature is convenient for commenting out blocks ofprogram statements that already have multi-line comments within them.Other than looking for end-of-comment delimiters,the contents of a comment are not interpreted.Comments may contain any characters.
Note that the nesting is not fool-proof. In
methodm(){/* var i: int; // */linecommentvarj:int;*/}
and
methodm(){/* var i: int; var s: string := "a*/b";varj:int;*/}
the*/
inside the line comment and the string are seen as the end of the outercomment, leaving trailing text that will provoke parsing errors.
2.5. Documentation comments
Like many other languages, Dafny permitsdocumentation comments in a program file.Such comments contain natural language descriptions of program elements and may be usedby IDEs and documentation generation tools to present information to users.
In Dafny programs.
- Documentation comments (a) either begin with
/**
or (b) begin with//
or /*` in specific locations - Documentation comments may be associated with any declaration, including type definitions, export declarations, and datatype constructors.
- They may be placed before the declaration, between the declaration and the definition or after the definition. The priority of these comments is the following:
- If there is a comment starting with
/**
right before the definition, it’s the documentation comment. - Otherwise, if one or more comments appear between a declaration and its definition — which is uncommon in other programming languages — they are treated as documentation comments, regardless of whether they start with //, /*, or /. This is because there is no other plausible reason for a comment to be placed in this position.
- Otherwise, if there is a single comment starting with
//
or/**
or/*
at the end of the definition, it’s the documentation comment. In this case, multi-line documentation comments must be starting with/*
- If there is a comment starting with
- The extraction of the doc-string from a documentation comment follows the following rules
- On the first line, an optional
*
right after/*
and an optional space are removed, if present - On other lines, the indentation space (with possibly one star in it) is removed, as if the content was supposed to align with A if the comment started with
/** A
for example.
- On the first line, an optional
- The documentation string is interpreted as plain text, but it is possible to provide a user-writtenplugin that provides other interpretations. VSCode as used by Dafny interprets any markdownsyntax in the doc-string.
Here are examples:
/* Just a comment */constc0:int:=8// docstring about c0// Just a comment/** docstring about c1 */newtypec1=x:int|x>8// Just a comment/* Just a comment */functionc2():(r:int)// Docstring about c2ensuresr>0{8}// Just a comment// just a commentconstc5:=8
Datatype constructors may also have comments:
datatypeT// Docstring for T=|A(x:int,y:int)// Docstring for A|B()/* Docstring for B */|C()// Docstring for C/** Docstring for T0 */datatypeT0=|/** Docstring for A */A(x:int,y:int)|/** Docstring for B */B()|/** Docstring for C */C()
As canexport
declarations:
moduleM{constA:intconstB:intconstC:intconstD:intexport// This is the docstring of the eponymous export set intended for most clientsprovidesA,B,C/** This is the docstring */exportABprovidesA,BexportFriendsextendsAB// This is the docstring of the export set is for clients who need to know more of the// details of the module's definitions.revealsAprovidesD}
2.6. Tokens (grammar)
The Dafny tokens are defined in this section.
2.6.1. Reserved Words
Dafny has a set of reserved words that may notbe used as identifiers of user-defined entities.These are listedhere.
In particular note that
array
,array2
,array3
, etc. are reserved words, denoting array types of given rank.However,array1
andarray0
are ordinary identifiers.array?
,array2?
,array3?
, etc. are reserved words, denoting possibly-null array types of given rank,but notarray1?
orarray0?
.bv0
,bv1
,bv2
, etc. are reserved words that denote the types ofbitvectors of given length.The sequence of digits after ‘array’ or ‘bv’ may not have leading zeros: for example,bv02
is an ordinary identifier.
2.6.2. Identifiers
In general, anident
token (an identifier) is a sequence ofidchar
characters wherethe first character is anondigitIdChar
. However tokens that fit this patternare not identifiers if they look like a character literalor a reserved word (including array or bit-vector type tokens).Also,ident
tokens that begin with an_
are not permitted as user identifiers.
2.6.3. Digits
Adigits
token is a sequence of decimal digits (digit
), possibly interspersed with underscores for readability (but not beginning or ending with an underscore).Example:1_234_567
.
Ahexdigits
token denotes a hexadecimal constant, and is a sequence of hexadecimal digits (hexdigit
)prefaced by0x
andpossibly interspersed with underscores for readability (but not beginning or ending with an underscore).Example:0xffff_ffff
.
Adecimaldigits
token is a decimal fraction constant, possibly interspersed with underscores for readability (but not beginning or ending with an underscore).It has digits both before and after a single period (.
) character. There is no syntax for floating point numbers with exponents.Example:123_456.789_123
.
2.6.4. Escaped Character
TheescapedChar
token is a multi-character sequence that denotes a non-printable or non-ASCII character.Such tokens begin with a backslash characcter (\
) and denote a single- or double-quote character, backslash,null, new line, carriage return, tab, or aUnicode character with given hexadecimal representation.Which Unicode escape form is allowed depends on the value of the--unicode-char
option.
If--unicode-char:false
is stipulated,\uXXXX
escapes can be used to specify any UTF-16 code unit.
If--unicode-char:true
is stipulated,\U{X..X}
escapes can be used to specify any Unicode scalar value.There must be at least one hex digit in between the braces, and at most six.Surrogate code points are not allowed.The hex digits may be interspersed with underscores for readability (but not beginning or ending with an underscore), as in\U{1_F680}
.The braces are part of the required character sequence.
Note that although Unicodeletters are not allowed in Dafny identifiers, Dafny does supportUnicodein its character, string, and verbatim strings constants and in its comments.
2.6.5. Character Constant Token
ThecharToken
token denotes a character constant.It is either acharChar
or anescapedChar
enclosed in single quotes.
2.6.6. String Constant Token
AstringToken
denotes a string constant.It consists of a sequence ofstringChar
andescapedChar
characters enclosed in double quotes.
AverbatimStringToken
token also denotes a string constant.It is a sequence of anyverbatimStringChar
characters (which includes newline characters),enclosed between@"
and"
, except that twosuccessive double quotes represent one quote character insidethe string. This is the mechanism for escaping a double quote character,which is the only character needing escaping in a verbatim string.Within a verbatim string constant, a backslash character represents itself and is not the first character of anescapedChar
.
2.6.7. Ellipsis
TheellipsisToken
is the character sequence...
and is typically used to designate something missing that willlater be inserted through refinement or is already present in a parent declaration.
2.7. Low Level Grammar Productions
2.7.1. Identifier Variations
2.7.1.1. Identifier
A basic ordinary identifier is just anident
token.
It may be followed by a sequence of suffixes to denote compound entities.Each suffix is a dot (.
) and another token, which may be
- another
ident
token - a
digits
token - the
requires
reserved word - the
reads
reserved word
Note that
- Digits can be used to name fields of classes and destructors ofdatatypes. For example, the built-in tuple datatypes have destructorsnamed 0, 1, 2, etc. Note that as a field or destructor name, a digit sequenceis treated as a string, not a number: internalunderscores matter, so
10
is different from1_0
and from010
. m.requires
is used to denote theprecondition for methodm
.m.reads
is used to denote the things that methodm
mayread.
2.7.1.2. No-underscore-identifier
ANoUSIdent
is an identifier except that identifiers with aleadingunderscore are not allowed. The names of user-defined entities arerequired to beNoUSIdent
s or, in some contexts, adigits
. We introduce more mnemonic namesfor these below (e.g.ClassName
).
A no-underscore-identifier is required for the following:
- module name
- class or trait name
- datatype name
- newtype name
- synonym (and subset) type name
- iterator name
- type variable name
- attribute name
A variation, a no-underscore-identifier or adigits
, is allowed for
- datatype member name
- method or function or constructor name
- label name
- export id
- suffix that is a typename or constructor
Alluser-declared names do not start with underscores, but there areinternally generated names that a user program mightuse that beginwith an underscore or are just an underscore.
2.7.1.3. Wild identifier
A wild identifier is a no-underscore-identifier except that the singleton_
is allowed. The_
is replaced conceptually by a uniqueidentifier distinct from all other identifiers in the program.A_
is used when an identifier is needed, but its content is discarded.Such identifiers are not used in expressions.
Wild identifiers may be used in these contexts:
- formal parameters of a lambda expression
- the local formal parameter of a quantifier
- the local formal parameter of a subset type or newtype declaration
- a variable declaration
- a case pattern formal parameter
- binding guard parameter
- for loop parameter
- LHS of update statements
2.7.2. Qualified Names
A qualified name starts with the name of a top-level entity and then is followed byzero or moreDotSuffix
s which denote a component. Examples:
Module.MyType1
MyTuple.1
MyMethod.requires
A.B.C.D
The identifiers and dots are separate tokens and so may optionally beseparated by whitespace.
2.7.3. Identifier-Type Combinations
Identifiers are typically declared in combination with a type, as in
vari:int
However, Dafny infers types in many circumstances, and in those, the type can be omitted. The type is requiredfor field declarations and formal parameters of methods, functions and constructors (because there is no initializer).It may be omitted (if the type can be inferred) for local variable declarations, pattern matching variables, quantifiers,
Similarly, there are circumstances in which the identifier name is not needed, because it is not used.This is allowed in defining algebraic datatypes.
In some other situations a wild identifier can be used, as describedabove.
2.7.4. Quantifier Domains (grammar)
Several Dafny constructs bind one or more variables to a range of possible values.For example, the quantifierforallx:nat|x<=5::x*x<=25
has the meaning“for all integers x between 0 and 5 inclusive, the square of x is at most 25”.Similarly, the set comprehensionsetx:nat|x<=5::f(x)
can be read as“the set containing the result of applying f to x, for each integer x from 0 to 5 inclusive”.The common syntax that specifies the bound variables and what values they take onis known as thequantifier domain; in the previous examples this isx:nat|x<=5
, which binds the variablex
to the values0
,1
,2
,3
,4
, and5
.
Here are some more examples.
x:byte
(where a value of typebyte
is an int-based numberx
in the range0<=x<256
)x:nat|x<=5
x<-integerSet
x:nat<-integerSet
x:nat<-integerSet|x%2==0
x:nat,y:nat|x<2&&y<2
x:nat|x<2,y:nat|y<x
i|0<=i<|s|,y<-s[i]|i<y
A quantifier domain declares one or morequantified variables, separated by commas.Each variable declaration can be nothing more than a variable name, but it may also include any of three optional elements:
The optional syntax
:T
declares the type of the quantified variable.If not provided, it will be inferred from context.The optional syntax
<-C
attaches a collection expressionC
as aquantified variable domain.Here a collection is any value of a type that supports thein
operator, namely sets, multisets, maps, and sequences.The domain restricts the bindings to the elements of the collection:x<-C
impliesxinC
.The example above can also be expressed asvarc:=[0,1,2,3,4,5];forallx<-c::x*x<=25
.The optional syntax
|E
attaches a boolean expressionE
as aquantified variable range,which restricts the bindings to values that satisfy this expression.In the example abovex<=5
is the range attached to thex
variable declaration.
Note that a variable’s domain expression may reference any variable declared before it,and a variable’s range expression may reference the attached variable (and usually does) and any variable declared before it.For example, in the quantifier domaini|0<=i<|s|,y<-s[i]|i<y
, the expressions[i]
is alwayswell-formedbecause the range attached toi
ensuresi
is a valid index in the sequences
.
Allowing per-variable ranges is not fully backwards compatible, and so it is not yet allowed by default;the--quantifier-syntax:4
option needs to be provided to enable this feature (SeeSection 13.9.5).
2.7.5. Numeric Literals (grammar)
Integer and bitvector literals may be expressed in either decimal or hexadecimal (digits
orhexdigits
).
Real number literals are written as decimal fractions (decimaldigits
).
3. Programs (grammar)
At the top level, a Dafny program (stored as files with extension.dfy
)is a set of declarations. The declarations introduce (module-level)constants, methods, functions, lemmas, types (classes, traits, inductive andcoinductive datatypes, newtypes, type synonyms, abstract types, anditerators) and modules, where the order of introduction is irrelevant. Some types, notably classes, also may contain a set of declarations, introducing fields, methods,and functions.
When asked to compile a program, Dafny looks for the existence of aMain()
method. If a legalMain()
method is found, the compiler will emitan executable appropriate to the target language; otherwise it will emita library or individual files.The conditions for a legalMain()
method are described in the User Guide(Section 3.4).If there is more than oneMain()
, Dafny will emit an error message.
An invocation of Dafny may specify a number of source files.Each Dafny file follows the grammar of theDafny
non-terminal.
A file consists of
- a sequence of optionalinclude directives, followed by
- top level declarations, followed by
- the end of the file.
3.1. Include Directives (grammar)
Examples:
include"MyProgram.dfy"include@"/home/me/MyFile.dfy"
Include directives have the form"include"stringToken
wherethe string token is either a normal string token or averbatim string token. ThestringToken
is interpreted as the name ofa file that will be included in the Dafny source. These includedfiles also obey theDafny
grammar. Dafny parses and processes thetransitive closure of the original source files and all the included files,but will not invoke the verifier on the included files unless they have been listedexplicitly on the command line or the--verify-included-files
option isspecified.
The file name may be a path using the customary/
,.
, and..
punctuation.The interpretation of the name (e.g., case-sensitivity) will depend on theunderlying operating system. A path not beginning with/
is looked up inthe underlying file system relative to thelocation of the file in which the include directive is stated. Paths beginning with a devicedesignator (e.g.,C:
) are only permitted on Windows systems.Better style advocates using relative paths in include directives so thatgroups of files may be moved as a whole to a new location.
Paths of files on the command-line or named in--library
options are relative the the current working directory.
3.2. Top Level Declarations (grammar)
Examples:
abstractmoduleM{}traitR{}classC{}datatypeD=A|Bnewtypepos=i:int|i>=0typeT=i:int|0<=i<100methodm(){}functionf():intconstc:bool
Top-level declarations may appear either at the top level of a Dafny file,or within a (sub)module declaration. A top-level declaration is one ofvarious kinds of declarations described later. Top-level declarations areimplicitly members of a default (unnamed) top-level module.
Declarations within a module or at the top-level all begin with reserved keywords and do not end with semicolons.
These declarations are one of these kinds:
- methods and functions, encapsulating computations or actions
- const declarations, which are names (of a given type) initialized to an unchanging value;declarations of variables and mutable fields are not allowed at the module level
- type declarations of various kinds (Section 5 and the following sections)
Methods, functions and const declarations are placed in an implicit class declarationthat is in the top-level implicit module. These declarations are all implicitlystatic
(and may not be declared explicitly static).
3.3. Declaration Modifiers (grammar)
Examples:
abstractmoduleM{classC{staticmethodm(){}}}ghostopaqueconstc:int
Top level declarations may be preceded by zero or more declarationmodifiers. Not all of these are allowed in all contexts.
Theabstract
modifier may only be used for module declarations.An abstract module can leave some entities underspecified.Abstract modules are not compiled.
Theghost
modifier is used to mark entities as being used forspecification only, not for compilation to code.
Theopaque
modifier may be used on const declarations and functions.
Thestatic
modifier is used for class members thatare associated with the class as a whole rather than withan instance of the class. This modifier may not be used withdeclarations that are implicitly static, as are members of the top-level, unnamed implicit class.
The following table shows modifiers that are availablefor each of the kinds of declaration. In the tablewe use already-ghost (already-non-ghost) to denote that the item is notallowed to have the ghost modifier because it is alreadyimplicitly ghost (non-ghost).
Declaration | allowed modifiers |
---|---|
module | abstract |
class | - |
trait | - |
datatype or codatatype | - |
field (const) | ghost opaque |
newtype | - |
synonym types | - |
iterators | - |
method | ghost static |
lemma | already-ghost static |
least lemma | already-ghost static |
greatest lemma | already-ghost static |
constructor | ghost |
function | ghost static opaque (Dafny 4) |
function method | already-non-ghost static opaque (Dafny 3) |
function (non-method) | already-ghost static opaque (Dafny 3) |
predicate | ghost static opaque (Dafny 4) |
predicate method | already-non-ghost static opaque (Dafny 3) |
predicate (non-method) | already-ghost static opaque (Dafny 3) |
least predicate | already-ghost static opaque |
greatest predicate | already-ghost static opaque |
3.4. Executable programs
Dafny programs have an important emphasis on verification, but the programs may also be executable.
To be executable, the program must have exactly oneMain
method and that method must be a legal main entry point.
- The program is searched for a method with the attribute
{:main}
.If exactly one is found, that method is used as the entry point; if morethan one method has the{:main}
attribute, an error message is issued. - Otherwise, the program is searched for a method with the name
Main
.If more than one is found an error message is issued.
Any abstract modules are not searched for candidate entry points,but otherwise the entry point may be in any module or type. In addition,an entry-point candidate must satisfy the following conditions:
- The method has no type parameters and either has no parameters or one non-ghost parameter of type
seq<string>
. - The method has no non-ghost out-parameters.
- The method is not a ghost method.
- The method has no requires or modifies clauses, unless it is marked
{:main}
. - If the method is an instance (that is, non-static) method and theenclosing type is a class,then that class must not declare any constructor.In this case, the runtime system willallocate an object of the enclosing class and will invokethe entry-point method on it.
- If the method is an instance (that is, non-static) method and theenclosing type is not a class,then the enclosing type must, when instantiated with auto-initializingtype parameters, be an auto-initializing type.In this case, the runtime system willinvoke the entry-point method on a value of the enclosing type.
Note, however, that the following are allowed:
- The method is allowed to have
ensures
clauses - The method is allowed to have
decreases
clauses, including adecreases*
. (IfMain()
has adecreases*
, then its execution maygo on forever, but in the absence of adecreases*
onMain()
,dafny
will have verified that the entire execution will eventuallyterminate.)
If no legal candidate entry point is identified,dafny
will still produce executable output files, butthey will need to be linked with some other code in the target language thatprovides amain
entry point.
If theMain
method takes an argument (of typeseq<string>
), the value of that input argument is the sequenceof command-line arguments, with the first entry of the sequence (at index 0) being a system-determined name for the executable being run.
The exit code of the program, when executed, is not yet specified.
4. Modules (grammar)
Examples:
moduleN{}importAexportArevealsf
Structuring a program by breaking it into parts is an important part ofcreating large programs. In Dafny, this is accomplished viamodules.Modules provide a way to group together related types, classes, methods,functions, and other modules, as well as to control the scope ofdeclarations. Modules may import each other for code reuse, and it ispossible to abstract over modules to separate an implementation from aninterface.
Module declarations are of three types:
- a module definition
- a module import
- a module export definition
Module definitions and imports each declare a submoduleof its enclosing module, which may be theimplicit, undeclared, top-level module.
4.1. Declaring New Modules (grammar)
Examples:
moduleP{consti:int}abstractmoduleA.Q{methodm(){}}moduleM{moduleN{}}
Amodule definition
- has an optional modifier (only
abstract
is allowed) - followed by the keyword “module”
- followed by a name (a sequence of dot-separated identifiers)
- followed by a body enclosed in curly braces
A module body consists of any declarations that are allowed at the toplevel: classes, datatypes, types, methods, functions, etc.
moduleMod{classC{varf:intmethodm()}datatypeOption=A(int)|B(int)typeTmethodm()functionf():int}
You can also put a module inside another, in a nested fashion:
moduleMod{moduleHelpers{classC{methoddoIt()varf:int}}}
Then you can refer to the members of theHelpers
module within theMod
module by prefixing them with “Helpers.”. For example:
moduleMod{moduleHelpers{classC{constructor(){f:=0;}methoddoIt()varf:int}}methodm(){varx:=newHelpers.C();x.doIt();x.f:=4;}}
Methods and functions defined at the module level are available likeclasses, with just the module name prefixing them. They are alsoavailable in the methods and functions of the classes in the samemodule.
moduleMod{moduleHelpers{functionaddOne(n:nat):nat{n+1}}methodm(){varx:=5;x:=Helpers.addOne(x);// x is now 6}}
Note that everything declared at the top-level(in all the files constituting the program) is implicitly partof a single implicit unnamed global module.
4.2. Declaring nested modules standalone
As described in the previous section, module declarations can be nested.It is also permitted to declare a nested moduleoutside of its“containing” module. So instead of
moduleA{moduleB{}}
one can write
moduleA{}moduleA.B{}
The second module is completely separate; for example, it can be ina different file.This feature provides flexibility in writing and maintenance;for example, it can reduce the size of moduleA
by extracting moduleA.B
into a separate body of text.
However, it can also lead to confusion, and program authors need to take care.It may not be apparent to a reader of moduleA
that moduleA.B
exists;the existence ofA.B
might cause names to be resolved differently andthe semantics of the program might be (silently) different ifA.B
ispresent or absent.
4.3. Importing Modules (grammar)
Examples:
importAimportopenedBimportA=BimportA:BimportA.BimportA`EimportX=A.B`{E,F}
Sometimes you want to refer tothings from an existing module, such as a library. In this case, youcanimport one module into another. This is done via theimport
keyword, which has two forms with different meanings.The simplest form is the concrete import, which hasthe formimportA=B
. This declaration creates a reference to themoduleB
(which must already exist), and binds it to the new local nameA
. This form can also be used to create a reference to a nestedmodule, as inimportA=B.C
. The other form, using a:
, isdescribed inSection 4.6.
As modules in the same scope must have different names, this abilityto bind a module to a new name allows disambiguating separately developedexternal modules that have the same name.Note that the new name is only bound in the scope containingthe import declaration; it does not create a global alias. Forexample, ifHelpers
was defined outside ofMod
, then we could importit:
moduleHelpers{functionaddOne(n:nat):nat{n+1}}moduleMod{importA=Helpersmethodm(){assertA.addOne(5)==6;}}
Note that insidem()
, we have to useA
instead ofHelpers
, as we boundit to a different name. The nameHelpers
is not available insidem()
(or anywhere else insideMod
),as only names that have been bound insideMod
are available. In orderto use the members from another module, that other module either has to be declaredthere withmodule
or imported withimport
. (As described below, theresolution of theModuleQualifiedName
that follows the=
in theimport
statement or therefines
in a module declaration uses slightlydifferent rules.)
We don’t have to giveHelpers
a new name, though, if we don’t wantto. We can writeimportHelpers=Helpers
to import the module underits own name; Dafnyeven provides the shorthandimportHelpers
for this behavior. Youcan’t bind two modules with the same name at the same time, sosometimes you have to use the = version to ensure the names do notclash. When importing nested modules,importB.C
meansimportC=B.C
;the implicit name is always the last name segment of the module designation.
The first identifier in the dot-separated sequence of identifers that constitutethe qualified name of the module being imported is resolved as (in order)
- a submodule of the importing module,
- or a sibling module of the importing module,
- or a sibling module of some containing module, traversing outward. There is no way to refer to a containing module, onlysibling modules (and their submodules).
Import statements may occur at the top-level of a program(that is, in the implicit top-level module of the program) as well.There they serve as a way to give a new name, perhaps ashorthand name, to a module. For example,
moduleMyModule{}// declare MyModuleimportMyModule// error: cannot add a module named MyModule// because there already is oneimportM=MyModule// OK. M and MyModule are equivalent
4.4. Opening Modules
Sometimes, prefixing the members of the module you imported with itsname is tedious and ugly, even if you select a short name whenimporting it. In this case, you can import the module asopened
,which causes all of its members to be available without adding themodule name. Theopened
keyword, if present, must immediately followimport
.For example, we could write the previous example as:
moduleHelpers{functionaddOne(n:nat):nat{n+1}}moduleMod{importopenedHelpersmethodm(){assertaddOne(5)==6;}}
When opening modules, the newly bound members have lower prioritythan local definitions. This means if you definea local function calledaddOne
, the function fromHelpers
will nolonger be available under that name. When modules are opened, theoriginal name binding is still present however, so you can always usethe name that was bound to get to anything that is hidden.
moduleHelpers{functionaddOne(n:nat):nat{n+1}}moduleMod{importopenedH=HelpersfunctionaddOne(n:nat):nat{n-1}methodm(){assertaddOne(5)==6;// this is now false,// as this is the function just definedassertH.addOne(5)==6;// this is still true}}
If you open two modules that both declare members with the same name,then neither member can be referred to without a module prefix, as itwould be ambiguous which one was meant. Just opening the two modulesis not an error, however, as long as you don’t attempt to use memberswith common names. However, if the ambiguous references actuallyrefer to the same declaration, then they are permitted.Theopened
keyword may be used with any kind ofimport
declaration, including the module abstraction form.
Animportopened
may occur at the top-level as well. For example,
moduleMyModule{}// declares MyModuleimportopenedMyModule// does not declare a new module, but does// make all names in MyModule available in// the current scope, without needing// qualificationimportopenedM=MyModule// names in MyModule are available in// the current scope without qualification// or qualified with either M (because of this// import) or MyModule (because of the original// module definition)
The Dafny style guidelines suggest using opened imports sparingly.They are best used when the names being imported have obviousand unambiguous meanings and when using qualified names would beverbose enough to impede understanding.
There is a special case in which the behavior described above is altered.If a moduleM
declares a typeM
andM
isimportopened
without renaming inside another moduleX
, then the rules above would have, withinX
,M
mean the module andM.M
mean the type. This is verbose. So in this somewhat common case, the typeM
is effectively made a local declaration withinX
so that it has precedence over the module name. NowM
refers to the type.If one needs to refer to the module, it will have to be renamed as part oftheimportopened
statement.
This special-case behavior does give rise to a source of ambiguity. Considerthe example
moduleOption{consta:=1datatypeOption=A|B{staticconsta:=2}}moduleX{importopenedOptionmethodM(){printOption.a;}}
Option.a
now means thea
in the datatype instead of thea
in the module.To avoid confusion in such cases, it is an ambiguity error if a namethat is declared in both the datatype and the module is usedwhen there is animportopen
ofthe module (without renaming).
4.5. Export Sets and Access Control (grammar)
Examples:
exportEextendsFrevealsf,gprovidesg,hexportEreveals*exportrevealsf,gprovidesg,hexportEexportE...revealsf
In some programming languages, keywords such aspublic
,private
, andprotected
are used to control access to (that is, visibility of) declared program entities.In Dafny, modules and export sets provide that capability.Modules combine declarations into logically related groups.Export sets then permit selectively exposing subsets of a module’s declarations;another module can import the export set appropriate to its needs.A user can define as many export sets as are needed to provide differentkinds of access to the module’s declarations.Each export set designates a list of names, which must benames that are declared in the module (or in a refinement parent).
By default (in the absence of any export set declarations)all the names declared in a module are available outside themodule using theimport
mechanism.Anexport set enables a module to disallow theuse of some declarations outside the module.
An export set has an optional name used to disambiguatein case of multiple export sets;If specified, such names are used inimport
statementsto designate which export set of a module is being imported.If a moduleM
has export setsE1
andE2
,we can writeimportA=M`E1
to create a module aliasA
that contains only the names inE1
.Or we can writeimportA=M`{E1,E2}
to import the unionof names inE1
andE2
as module aliasA
.As before,importM`E1
is an abbreviation ofimportM=M`E1
.
If no export set is given in an importstatement, the default export set of the module is used.
There are variousdefaults that apply differently in different cases.The following description is with respect to an example moduleM
:
M
has no export sets declared. Then another module may simplyimportZ=M
to obtain access to all of M’s declarations.
M
has one or more named export sets (e.g.,E
,F
). Then another module canwriteimportZ=M`E
orimportZ=M`{E,F}
to obtain access to thenames that are listed in export setE
or to the union of those in export setsE
andF
, respectively. If no export set has the same name as the module,then an export set designator must be used: in that case you cannot writesimplyimportZ=M
.
M
has an unnamed export set, along with other export sets (e.g., namedE
). The unnamedexport set is the default export set and implicitly has the same name asthe module. Because there is a default export set, another module may writeeitherimportZ=M
orimportZ=M`M
to import the names in thatdefault export set. You can also still use the other export sets with theexplicit designator:importZ=M`E
M
declares an export set with the same name as the module. This is equivalentto declaring an export set without a name.importM
andimportM`M
perform the same function in either case; the export set with or withoutthe name of the module is the default export set for the module.
Note that names of module aliases (declared by import statements) arejust like other names in a module; they can be included or omitted fromexport sets.Names brought into a module byrefinement are treated the same aslocally declared names and can be listed in export set declarations.However, names brought into a module byimportopened
(either into a moduleor a refinement parent of a module) maynot be further exported. For example,
moduleA{consta:=10constz:=10}moduleB{importopenedZ=A// includes a, declares Zconstb:=Z.a// OK}moduleC{importopenedB// includes b, Z, but not amethodm(){//assert b == a; // error: a is not known//assert b == B.a; // error: B.a is not valid//assert b == A.a; // error: A is not knownassertb==Z.a;// OK: module Z is known and includes a}}
However, in the above example,
- if
A
has one export setexportYrevealsa
then the import in moduleB
is invalid becauseA
has no defaultexport set; - if
A
has one export setexportYrevealsa
andB
hasimportZ=A`Y
thenB
’s import is OK. So is the use ofZ.a
in the assert becauseB
declaresZ
andC
brings inZ
through theimportopened
andZ
containsa
by virtue of its declaration. (The aliasZ
is not able tohave export sets; all of its names are visible.) - if
A
has one export setexportprovidesz
thenA
does have adefault export set, so the import inB
is OK, but neither the use ofa
inB
nor asZ.a
in C would be valid, becausea
is not inZ
.
The default export set is important in the resolution of qualifiednames, as described inSection 4.8.
There are a few unusual cases to be noted:
- an export set can be completely empty, as in
exportNothing
- an eponymous export set can be completely empty, as in
export
, which by default has the same name as the enclosing module; this is a way to make the module completely private - an export set declaration followed by an extreme predicate declaration looks like this:
exportleastpredicateP(){true}
In this case, theleast
(orgreatest
) is the identifier naming the export set.Consequently,exportleastpredicateP[nat](){true}
is illegal because[nat]
cannot be part of a non-extreme predicate.So, it is not possible to declare an eponymous, empty export set by omitting the export id immediately prior to a declaration of an extreme predicate,because theleast
orgreatest
token is parsed as the export set identifier. The workaround for this situation is to either put the name of the module in explicitly as the export ID (not leaving it to the default) or reorder the declarations. - To avoid confusion, the code
moduleM{exportleastpredicateP(){true}}
provokes a warning telling the user that the
least
goes with theexport
.
4.5.1. Provided and revealed names
Names can be exported from modules in two ways, designated byprovides
andreveals
in the export set declaration.
When a name is exported asprovided, then inside a module that hasimported the name only the name is known, not the details of thename’s declaration.
For example, in the following code the constanta
is exported as provided.
moduleA{exportprovidesaconsta:=10constb:=20}moduleB{importAmethodm(){assertA.a==10;// a is known, but not its value// assert A.b == 20; // b is not known through A`A}}
Sincea
is imported into moduleB
through the default export setA`A
,it can be referenced in the assert statement. The constantb
is notexported, so it is not available. But the assert abouta
is not provablebecause the value ofa
is not known in moduleB
.
In contrast, ifa
is exported asrevealed, as shown in the next example,its value is known and the assertion can be proved.
moduleA{exportrevealsaconsta:=10constb:=20}moduleB{importAmethodm(){assertA.a==10;// a and its value are known// assert A.b == 20; // b is not known through A`A}}
The following table shows which parts of a declaration are exported by anexport set thatprovides
orreveals
the declaration.
declaration | what is exported | what is exported | with provides | with reveals---------------------|---------------------|--------------------- const x: X := E | const x: X | const x: X := E---------------------|---------------------|--------------------- var x: X | var x: X | not allowed---------------------|---------------------|--------------------- function F(x: X): Y | function F(x: X): Y | function F(x: X): Y specification... | specification... | specification... { | | { Body | | Body } | | }---------------------|---------------------|--------------------- method M(x: X) | method M(x: X) | not allowed returns (y: Y) | returns (y: Y) | specification... | specification... | { | | Body; | | } | |---------------------|---------------------|--------------------- type Opaque | type Opaque | type Opaque { | | // members... | | } | |---------------------|---------------------|--------------------- type Synonym = T | type Synonym | type Synonym = T---------------------|---------------------|--------------------- type S = x: X | type S | type S = x: X | P witness E | | | P witness E---------------------|---------------------|--------------------- newtype N = x: X | type N | newtype N = x: X | P witness E | | | P witness E { | | // members... | | } | |
---------------------|---------------------|--------------------- datatype D = | type D | datatype D = Ctor0(x0: X0) | | Ctor0(x0: X0) | Ctor1(x1: X1) | | | Ctor1(x1: X1) | ... | | | ... { | | // members... | | } | |---------------------|---------------------|--------------------- class Cl | type Cl | class Cl extends T0, ... | | extends T0, ... { | | { constructor () | | constructor () spec... | | spec... { | | Body; | | } | | // members... | | } | | }---------------------|---------------------|--------------------- trait Tr | type Tr | trait Tr extends T0, ... | | extends T0, ... { | | // members... | | } | |---------------------|---------------------|--------------------- iterator Iter(x: X) | type Iter | iterator Iter(x: X) yields (y: Y) | | yields (y: Y) specification... | | specification... { | | Body; | | } | |---------------------|---------------------|--------------------- module SubModule | module SubModule | not allowed ... | ... | { | { | export SubModule | export SubModule | ... | ... | export A ... | | // decls... | // decls... | } | } |---------------------|---------------------|--------------------- import L = MS | import L = MS | not allowed---------------------|---------------------|---------------------
Variations of functions (e.g.,predicate
,twostatefunction
) arehandled likefunction
above, and variations of methods (e.g.,lemma
andtwostatelemma
) are treated likemethod
above. Sincethe whole signature is exported, a function or method is exported tobe of the same kind, even throughprovides
. For example, an exportedtwostatelemma
is exported as atwostatelemma
(and thus is knownby importers to have two implicit heap parameters), and an exportedleastpredicateP
is exported as aleastpredicateP
(and thusimporters can use bothP
and its prefix predicateP#
).
IfC
is aclass
,trait
, oriterator
, thenprovidesC
exportsthe non-null reference typeC
as an abstract type. This does not revealthatC
is a reference type, nor does it export the nullable typeC?
.
In most cases, exporting aclass
,trait
,datatype
,codatatype
, orabstract type does not automatically export its members. Instead, any memberto be exported must be listed explicitly. For example, consider the typedeclaration
traitTr{functionF(x:int):int{10}functionG(x:int):int{12}functionH(x:int):int{14}}
An export set that contains onlyrevealsTr
has the effect of exporting
traitTr{}
and an export set that contains onlyprovidesTr,Tr.FrevealsTr.H
hasthe effect of exporting
typeTr{functionF(x:int):intfunctionH(x:int):int{14}}
There is no syntax (for example,Tr.*
) to export all members of a type.
Some members are exported automatically when the type is revealed.Specifically:
- Revealing a
datatype
orcodatatype
automatically exports the type’sdiscriminators and destructors. - Revealing an
iterator
automatically exports the iterator’s members. - Revealing a class automatically exports the class’s anonymous constructor, if any.
For aclass
, aconstructor
member can be exported only if the class is revealed.For aclass
ortrait
, avar
member can be exported only if the class or trait is revealed(but aconst
member can be exported even if the enclosing class or trait is only provided).
When exporting a sub-module, only the sub-module’s eponymous export set is exported.There is no way for a parent module to export any other export set of a sub-module, unlessit is done via animport
declaration of the parent module.
The effect of declaring an import asopened
is confined to the importing module. Thatis, the ability of use such imported names as unqualified is not passed on to furtherimports, as the following example illustrates:
moduleLibrary{constxyz:=16}moduleM{exportprovidesLibprovidesxyz// error: 'xyz' is not declared locallyimportopenedLib=Libraryconstk0:=Lib.xyzconstk1:=xyz}moduleClient{importopenedMconsta0:=M.Lib.xyzconsta1:=Lib.xyzconsta2:=M.xyz// error: M does not have a declaration 'xyz'consta3:=xyz// error: unresolved identifier 'xyz'}
As highlighted in this example, moduleM
can usexyz
as if it were a localname (see declarationk1
), but the unqualified namexyz
is not made availableto importers ofM
(see declarationsa2
anda3
), nor is it possible forM
to export the namexyz
.
A few other notes:
- A
provides
list can mention*
, which means that all local names(except export set names) in the module are exported asprovides
. - A
reveals
list can mention*
, which means that all local names(except export set names) in the module are exported asreveals
, ifthe declaration is allowed to appear in areveals
clause, or asprovides
, if the declaration is not allowed to appear in areveals
clause. - If no export sets are declared, then the implicitexport set is
exportreveals*
. - A refinement module acquires all the export sets from its refinement parent.
- Names acquired by a module from its refinement parent are also subject toexport lists. (These are local names just like those declared directly.)
4.5.2. Extends list
An export set declaration may include anextends list, which is a list ofone or more export set names from the same module containing the declaration(including export set names obtained from a refinement parent).The effect is to include in the declaration the union of all the names inthe export sets in the extends list, along with any other names explicitlyincluded in the declaration. So for example in
moduleM{consta:=10constb:=10constc:=10exportArevealsaexportBrevealsbexportCextendsA,Brevealsc}
export setC
will contain the namesa
,b
, andc
.
4.6. Module Abstraction
Sometimes, using a specific implementation is unnecessary; instead,all that is needed is a module that implements some interface. Inthat case, you can use anabstract module import. In Dafny, this iswrittenimportA:B
. This means bind the nameA
as before, butinstead of getting the exact moduleB
, you get any module whichadheres toB
. Typically, the moduleB
may have abstract typedefinitions, classes with bodiless methods, or otherwise be unsuitableto use directly. Because of the way refinement is defined, anyrefinement ofB
can be used safely. For example, suppose we start withthese declarations:
abstractmoduleInterface{functionaddSome(n:nat):natensuresaddSome(n)>n}abstractmoduleMod{importA:Interfacemethodm(){assert6<=A.addSome(5);}}
We can be more precise if we know thataddSome
actually addsexactly one. The following module has this behavior. Further, thepostcondition is stronger, so this is actually a refinement of theInterface module.
moduleImplementation{functionaddSome(n:nat):natensuresaddSome(n)==n+1{n+1}}
We can then substituteImplementation
forA
in a new module, bydeclaring a refinement ofMod
which definesA
to beImplementation
.
abstractmoduleInterface{functionaddSome(n:nat):natensuresaddSome(n)>n}abstractmoduleMod{importA:Interfacemethodm(){assert6<=A.addSome(5);}}moduleImplementation{functionaddSome(n:nat):natensuresaddSome(n)==n+1{n+1}}moduleMod2refinesMod{importA=Implementation...}
When you refine an abstract import into a concrete oneDafny checks that the concrete module is arefinement of the abstract one. This means that the methods must havecompatible signatures, all the classes and datatypes with theirconstructors and fields in the abstract one must be present in theconcrete one, the specifications must be compatible, etc.
A module that includes an abstract import must be declaredabstract
.
4.7. Module Ordering and Dependencies
Dafny isn’t particular about the textual order in which modules aredeclared, butthey must follow some rules to be well formed. In particular,there must be a way to order the modules in a program such that eachonly refers to things definedbefore it in the ordering. Thatdoesn’t mean the modules have to be given textually in that order inthe source text. Dafny willfigure out that order for you, assuming you haven’t made any circularreferences. For example, this is pretty clearly meaningless:
importA=BimportB=A// error: circular
You can have import statements at the toplevel and you can importmodules defined at the same level:
importA=Bmethodm(){A.whatever();}moduleB{methodwhatever(){}}
In this case, everything is well defined because we can putB
first,followed by theA
import, and then finallym()
. If there is nopermitted ordering, then Dafny will give an error, complaining about a cyclicdependency.
Note that when rearranging modules and imports, they have to be keptin the same containing module, which disallows some pathologicalmodule structures. Also, the imports and submodules are alwaysconsidered to be before their containing module, even at the toplevel. This means that thefollowing is not well formed:
methoddoIt(){}moduleM{methodm(){doIt();// error: M precedes doIt}}
because the moduleM
must come before any other kind of members, suchas methods. To define global functions like this, you can put them ina module (calledGlobals
, say) and open it into any module that needsits functionality. Finally, if you import via a path, such asimportA=B.C
, then this creates a dependency ofA
onB
, andB
itselfdepends on its own nested moduleB.C
.
4.8. Name Resolution
When Dafny sees something likeA<T>.B<U>.C<V>
, how does it know what each partrefers to? The process Dafny uses to determine what identifiersequences like this refer to is name resolution. Though the rules mayseem complex, usually they do what you would expect. Dafny first looksup the initial identifier. Depending on what the first identifierrefers to, the rest of the identifier is looked up in the appropriatecontext.
In terms of the grammar, sequences like the above are represented asaNameSegment
followed by 0 or moreSuffix
es.The form shown above contains three instances ofAugmentedDotSuffix_
.
The resolution is different depending on whether it is ina module context, an expression context or a type context.
4.8.1. Modules and name spaces
A module is a collection of declarations, each of which has a name.These names are held in two namespaces.
- The names of export sets
- The names of all other declarations, including submodules and aliased modules
In addition names can be classified aslocal orimported.
- Local declarations of a module are the declarations that are explicit in the module and thelocal declarations of the refinement parent. This includes, forexample, the
N
ofimportN=
in the refinement parent, recursively. - Imported names of a module are those brought in by
importopened
plusthe imported names in the refinement parent.
Within each namespace, the local names are unique. Thus a module maynot reuse a name that a refinement parent has declared (unless it is arefining declaration, which replaces both declarations, as describedinSection 10).
Local names take precedence over imported names. If a name is used more thanonce among imported names (coming from different imports), then it isambiguous touse the name without qualification.
4.8.2. Module Id Context Name Resolution
A qualified name may be used to refer to a module in an import statement or a refines clause of a module declaration.Such a qualified name is resolved as follows, with respect to its syntacticlocation within a moduleZ
:
The leading identifier of the qualified name is resolved as a local or imported module name of
Z
, if thereis one with a matching name. The target of arefines
clause does notconsider local names, that is, inmoduleZrefinesA.B.C
, any contents ofZ
are not considered in findingA
.Otherwise, it is resolved as a local or imported module name of the most enclosing module of
Z
,iterating outward to each successive enclosing module until a match isfound or the default toplevel module is reached without a match.No consideration of export sets, default or otherwise, is used in this step.However, if at any stage a matching name is found that is not a moduledeclaration, the resolution fails. See the examples below.
3a. Once the leading identifier is resolved as say moduleM
, the next identifier in the quallified name is resolved as a local or imported module name withinM
. The resolution is restricted to the default export set ofM
.
3b. If the resolved module name is a module alias (from animport
statement) then the target of the alias is resolved as a new qualified name with respect to its syntactic context (independent of any resolutions ormodules so far). SinceZ
depends onM
, any such alias target willalready have been resolved, because modules are resolved in order ofdependency.
- Step 3 is iterated for each identifier in the qualified module id,resulting in a module that is the final resolution of the completequalified id.
Ordinarily a module must beimported in order for its constituentdeclarations to be visible inside a given moduleM
. However, for theresolution of qualified names this is not the case.
This example shows that the resolution of the refinement parent does notuse any local names:
moduleA{consta:=10}moduleBrefinesA{// the top-level A, not the submodule AmoduleA{consta:=30}methodm(){asserta==10;}// true}
In the example, theA
inrefinesA
refers to the globalA
, not the submoduleA
.
4.8.3. Expression Context Name Resolution
The leading identifier is resolved using the first followingrule that succeeds.
Local variables, parameters and bound variables. These are things like
x
,y
, andi
invarx;,...returns(y:int)
, andforalli::....
The declaration chosen is the match from theinnermost matching scope.If in a class, try to match a member of the class. If the member thatis found is not static an implicit
this
is inserted. This works forfields, functions, and methods of the current class (if in a staticcontext, then only static methods and functions are allowed). You canrefer to fields of the current class either asthis.f
orf
,assuming of course thatf
is not hidden by one of the above. Youcan always prefixthis
if needed, which cannot be hidden. (Note that afield whose name is a string of digits must always have some prefix.)If there is no
Suffix
, then look for a datatype constructor, ifunambiguous. Any datatypes that don’t need qualification (so thedatatype name itself doesn’t need a prefix) and also have a uniquelynamed constructor can be referred to just by name. So ifdatatypeList=Cons(List)|Nil
is the only datatype that declaresCons
andNil
constructors, then you can writeCons(Cons(Nil))
.If the constructor name is not unique, then you need to prefix it withthe name of the datatype (for exampleList.Cons(List.Nil)))
. This isdone per constructor, not per datatype.Look for a member of the enclosing module.
Module-level (static) functions and methods
In each module, names from opened modules are also potential matches, butonly after names declared in the module.If an ambiguous name is found or a name of the wrong kind (e.g. a moduleinstead of an expression identifier), an error is generated, rather than continuingdown the list.
After the first identifier, the rules are basically thesame, except in the new context. For example, if the first identifier isa module, then the next identifier looks into that module. Opened modulesonly apply within the module it is opened into. When looking up intoanother module, only things explicitly declared in that module areconsidered.
To resolve expressionE.id
:
First resolve expression E and any type arguments.
- If
E
resolved to a moduleM
:- If
E.id<T>
is not followed by any further suffixes, look forunambiguous datatype constructor. - Member of module M: a sub-module (including submodules of imports),class, datatype, etc.
- Static function or method.
- If
- If
E
denotes a type:- Look up id as a member of that type
- If
E
denotes an expression:- Let T be the type of E. Look up id in T.
4.8.4. Type Context Name Resolution
In a type context the priority of identifier resolution is:
Type parameters.
Member of enclosing module (type name or the name of a module).
To resolve expressionE.id
:
- If
E
resolved to a moduleM
:- Member of module M: a sub-module (including submodules of imports),class, datatype, etc.
- If
E
denotes a type:- Then the validity and meaning of
id
depends on the type andmust be a user-declared or pre-defined member of the type.
- Then the validity and meaning of
5. Types
A Dafny type is a (possibly-empty) set of values or heap data-structures,together with allowed operations on those values.Types are classified as mutable reference types or immutable value types,depending on whether their values are stored in the heap or are (mathematical) values independent of the heap.
Dafny supports the following kinds of types,all described in later sections of this manual:
- builtin scalar types,
- builtin collection types,
- reference types (classes, traits, iterators),
- tuple types (including as a special case a parenthesized type),
- inductive andcoinductive datatypes,
- function (arrow) types, and
- types, such as subset types, derived from other types.
5.1. Kinds of types
5.1.1. Value Types
The value types are those whose values do not lie in the program heap.These are:
- The basic scalar types:
bool
,char
,int
,real
,ORDINAL
, bitvector types - The built-in collection types:
set
,iset
,multiset
,seq
,string
,map
,imap
- Tuple Types
- Inductive and coinductive types
- Function (arrow) types
- Subset and newtypes that are based on value types
Data items having value types are passed by value. Since they are notconsidered to occupymemory, framing expressions do not reference them.
Thenat
type is a pre-definedsubset type ofint
.
Dafny does not include types themselves as values, nor is there a type of types.
5.1.2. Reference Types
Dafny offers a host ofreference types. These representreferences to objects allocated dynamically in the program heap. Toaccess the members of an object, a reference to (that is, apointerto orobject identity of) the object isdereferenced.
The reference types are class types, traits and array types.Dafny supports both reference types that contain the specialnull
value(nullable types) and reference types that do not (non-null types).
5.1.3. Named Types (grammar)
ANamed Type is used to specify a user-defined type by a (possibly module- or class-qualified) name.Named types are introduced byclass, trait, inductive, coinductive, synonym and abstracttype declarations. They are also used to refer to type variables.A Named Type is denoted by a dot-separated sequence of name segments (Section 9.32).
A name segment (for a type) is a type name optionally followed by ageneric instantiation, which supplies type parameters to a generictype, if needed.
The following sections describe each of these kinds of types in more detail.
5.2. Basic types
Dafny offers these basic types:bool
for booleans,char
forcharacters,int
andnat
for integers,real
for reals,ORDINAL
, and bit-vector types.
5.2.1. Booleans (grammar)
There are two boolean values and each has a corresponding literal inthe language:false
andtrue
.
Typebool
supports the following operations:
operator | precedence | description |
---|---|---|
<==> | 1 | equivalence (if and only if) |
==> | 2 | implication (implies) |
<== | 2 | reverse implication (follows from) |
&& | 3 | conjunction (and) |
|| | 3 | disjunction (or) |
== | 4 | equality |
!= | 4 | disequality |
! | 10 | negation (not) |
Negation is unary; the others are binary. The table shows the operatorsin groups of increasing binding power, with equality binding strongerthan conjunction and disjunction, and weaker than negation. Withineach group, different operators do not associate, so parentheses needto be used. For example,
A&&B||C// error
would be ambiguous and instead has to be written as either
(A&&B)||C
or
A&&(B||C)
depending on the intended meaning.
5.2.1.1. Equivalence Operator
The expressionsA<==>B
andA==B
give the same value, but notethat<==>
isassociative whereas==
ischaining and they havedifferent precedence. So,
A<==>B<==>C
is the same as
A<==>(B<==>C)
and
(A<==>B)<==>C
whereas
A==B==C
is simply a shorthand for
A==B&&B==C
Also,
A<==>B==C<==>D
is
A<==>(B==C)<==>D
5.2.1.2. Conjunction and Disjunction
Conjunction and disjunction are associative. These operators areshort circuiting (from left to right), meaning that their secondargument is evaluated only if the evaluation of the first operand doesnot determine the value of the expression. Logically speaking, theexpressionA&&B
is defined whenA
is defined and eitherA
evaluates tofalse
orB
is defined. WhenA&&B
is defined, itsmeaning is the same as the ordinary, symmetric mathematicalconjunction&
. The same holds for||
and|
.
5.2.1.3. Implication and Reverse Implication
Implication isright associative and is short-circuiting from leftto right. Reverse implicationB<==A
is exactly the same asA==>B
, but gives the ability to write the operands in the oppositeorder. Consequently, reverse implication isleft associative and isshort-circuiting fromright to left. To illustrate theassociativity rules, each of the following four lines expresses thesame property, for anyA
,B
, andC
of typebool
:
A==>B==>CA==>(B==>C)// parentheses redundant, ==> is right associativeC<==B<==A(C<==B)<==A// parentheses redundant, <== is left associative
To illustrate the short-circuiting rules, note that the expressiona.Length
is defined for an arraya
only ifa
is notnull
(seeSection 5.1.2), which means the following twoexpressions arewell-formed:
a!=null==>0<=a.Length0<=a.Length<==a!=null
The contrapositives of these two expressions would be:
a.Length<0==>a==null// not well-formeda==null<==a.Length<0// not well-formed
but these expressions might not necessarily bewell-formed, since well-formednessrequires the left (and right, respectively) operand,a.Length<0
,to bewell-formed in their context.
ImplicationA==>B
is equivalent to the disjunction!A||B
, butis sometimes (especially in specifications) clearer to read. Since,||
is short-circuiting from left to right, note that
a==null||0<=a.Length
iswell-formed by itself, whereas
0<=a.Length||a==null// not well-formed
is not if the context cannot prove thata!=null
.
In addition, booleans supportlogical quantifiers (forall andexists), described inSection 9.31.4.
5.2.2. Numeric Types (grammar)
Dafny supportsnumeric types of two kinds,integer-based, whichincludes the basic typeint
of all integers, andreal-based, whichincludes the basic typereal
of all real numbers. User-definednumeric types based onint
andreal
, eithersubset types ornewtypes,are described inSection 5.6.3 andSection 5.7.
There is one built-insubset type,nat
, representing the non-negative subrange ofint
.
The language includes a literal for each integer, like0
,13
, and1985
. Integers can also be written in hexadecimalusing the prefix “0x
”, as in0x0
,0xD
, and0x7c1
(always witha lower casex
, but the hexadecimal digits themselves are caseinsensitive). Leading zeros are allowed. To form negative literals,use the unary minus operator, as in-12
, but not-(12)
.
There are also literals for some of the reals. These arewritten as a decimal point with a nonempty sequence of decimal digitson both sides, optionally prefixed by a-
character.For example,1.0
,1609.344
,-12.5
, and0.5772156649
.Real literals using exponents are not supported in Dafny. For now, you’d have to write your own function for that, e.g.
// realExp(2.37, 100) computes 2.37e100functionrealExp(r:real,e:int):realdecreasesife>0theneelse-e{ife==0thenrelseife<0thenrealExp(r/10.0,e+1)elserealExp(r*10.0,e-1)}
For integers (in both decimal and hexadecimal form) and reals,any two digits in a literal may be separated by an underscore in orderto improve human readability of the literals. For example:
constc1:=1_000_000// easier to read than 1000000constc2:=0_12_345_6789// strange but legal formatting of 123456789constc3:=0x8000_0000// same as 0x80000000 -- hex digits are// often placed in groups of 4constc4:=0.000_000_000_1// same as 0.0000000001 -- 1 Angstrom
In addition to equality and disequality, numeric typessupport the following relational operations, which have thesame precedence as equality:
operator | description |
---|---|
< | less than |
<= | at most |
>= | at least |
> | greater than |
Like equality and disequality, these operators are chaining, as longas they are chained in the “same direction”. That is,
A<=B<C==D<=E
is simply a shorthand for
A<=B&&B<C&&C==D&&D<=E
whereas
A<B>C
is not allowed.
There are also operators on each numeric type:
operator | precedence | description |
---|---|---|
+ | 6 | addition (plus) |
- | 6 | subtraction (minus) |
* | 7 | multiplication (times) |
/ | 7 | division (divided by) |
% | 7 | modulus (mod) – int only |
- | 10 | negation (unary minus) |
The binary operators are left associative, and they associate witheach other in the two groups.The groups are listed in order ofincreasing binding power, with equality binding less strongly than any of these operators.There is no implicit conversion betweenint
andreal
: useasint
orasreal
conversions to write an explicit conversion (cf.Section 9.10).
Modulus is supported only for integer-based numeric types. Integerdivision and modulus are theEuclidean division and modulus. Thismeans that modulus always returns a non-negative value, regardless of thesigns of the two operands. More precisely, for any integera
andnon-zero integerb
,
a==a/b*b+a%b0<=a%b<B
whereB
denotes the absolute value ofb
.
Real-based numeric types have a memberFloor
that returns thefloor of the real value (as an int value), that is, the largest integer not exceedingthe real value. For example, the following properties hold, for anyr
andr'
of typereal
:
methodm(r:real,r':real){assert3.14.Floor==3;assert(-2.5).Floor==-3;assert-2.5.Floor==-2;// This is -(2.5.Floor)assertr.Floorasreal<=r;assertr<=r'==>r.Floor<=r'.Floor;}
Note in the third line that member access (like.Floor
) bindsstronger than unary minus. The fourth line uses the conversionfunctionasreal
fromint
toreal
, as described inSection 9.10.
5.2.3. Bit-vector Types (grammar)
Dafny includes a family of bit-vector types, each type having a specific,constant length, the number of bits in its values.Each such type isdistinct and is designated by the prefixbv
followed (without white space) bya positive integer without leading zeros or zero, stating the number of bits. For example,bv1
,bv8
, andbv32
are legal bit-vector type names.The typebv0
is also legal; it is a bit-vector type with no bits and just one value,0x0
.
Constant literals of bit-vector types are given by integer literals converted automaticallyto the designated type, either by an implicit or explicit conversion operation or by initialization in a declaration.Dafny checks that the constant literal is in the correct range. For example,
consti:bv1:=1constj:bv8:=195constk:bv2:=5// error - out of rangeconstm:=(194asbv8)|(7asbv8)
Bit-vector values can be converted to and fromint
and other bit-vector types, as long asthe values are in range for the target type. Bit-vector values are always considered unsigned.
Bit-vector operations include bit-wise operators and arithmetic operators(as well as equality, disequality, and comparisons).The arithmetic operationstruncate the high-order bits from the results; that is, they performunsigned arithmetic modulo 2^{number of bits}, like 2’s-complement machine arithmetic.
operator | precedence | description |
---|---|---|
<< | 5 | bit-limited bit-shift left |
>> | 5 | unsigned bit-shift right |
+ | 6 | bit-limited addition |
- | 6 | bit-limited subtraction |
* | 7 | bit-limited multiplication |
& | 8 | bit-wise and |
| | 8 | bit-wise or |
^ | 8 | bit-wise exclusive-or |
- | 10 | bit-limited negation (unary minus) |
! | 10 | bit-wise complement |
.RotateLeft(n) | 11 | rotates bits left by n bit positions |
.RotateRight(n) | 11 | rotates bits right by n bit positions |
The groups of operators lower in the table above bind more tightly.1All operators bind more tightly than equality, disequality, and comparisons.All binary operators are left-associative, but the bit-wise&
,|
, and^
do not associate together (parentheses are required to disambiguate).The+
,|
,^
, and&
operators are commutative.
The right-hand operand of bit-shift operations is anint
value,must be non-negative, andno more than the number of bits in the type.There is no signed right shift as all bit-vector values correspond tonon-negative integers.
Bit-vector negation returns an unsigned value in the correct range for the type.It has the propertiesx+(-x)==0
and(!x)+1==-x
, for a bitvector valuex
of at least one bit.
The argument of theRotateLeft
andRotateRight
operations is anon-negativeint
that is no larger than the bit-width of the value being rotated.RotateLeft
moves bits to higher bit positions (e.g.,(2asbv4).RotateLeft(1)==(4asbv4)
and(8asbv4).RotateLeft(1)==(1asbv4)
);RotateRight
moves bits to lower bit positions, sob.RotateLeft(n).RotateRight(n)==b
.
Here are examples of the various operations (all the assertions are true except where indicated):
consti:bv4:=9constj:bv4:=3methodm(){assert(i&j)==(1asbv4);assert(i|j)==(11asbv4);assert(i^j)==(10asbv4);assert!i==(6asbv4);assert-i==(7asbv4);assert(i+i)==(2asbv4);assert(j-i)==(10asbv4);assert(i*j)==(11asbv4);assert(iasint)/(jasint)==3;assert(j<<1)==(6asbv4);assert(i<<1)==(2asbv4);assert(i>>1)==(4asbv4);asserti==9;// auto conversion of literal to bv4asserti*4==j+8+9;// arithmetic is modulo 16asserti+j>>1==(i+j)>>1;// + - bind tigher than << >>asserti+j^2==i+(j^2);asserti*j&1==i*(j&1);// & | ^ bind tighter than + - *}
The following are incorrectly formed:
consti:bv4:=9constj:bv4:=3methodm(){asserti&4|j==0;// parentheses required}
constk:bv4:=9methodp(){assertkasbv5==9asbv6;// error: mismatched types}
These produce assertion errors:
consti:bv4:=9methodm(){assertiasbv3==1;// error: i is out of range for bv3}
constj:bv4:=9methodn(){assertj==25;// error: 25 is out of range for bv4}
Bit-vector constants (like all constants) can be initialized using expressions, but pay attentionto how type inference applies to such expressions. For example,
consta:bv3:=-1
is legal because Dafny interprets-1
as abv3
expression, becausea
has typebv3
.Consequently the-
isbv3
negation and the1
is abv3
literal; the value of the expression-1
isthebv3
value7
, which is then the value ofa
.
On the other hand,
constb:bv3:=6&11
is illegal because, again, the&
isbv3
bit-wise-and and the numbers must be validbv3
literals.But11
is not a validbv3
literal.
5.2.4. Ordinal type (grammar)
Values of typeORDINAL
behave likenat
s in many ways, with one important difference:there areORDINAL
values that are larger than anynat
. The smallest of these non-nat ordinals isrepresented as $\omega$ in mathematics, though there is no literal expression in Dafny that represents this value.
The natural numbers are ordinals.Any ordinal has a successor ordinal (equivalent to adding1
).Some ordinals arelimit ordinals, meaning they are not a successor of any other ordinal;the natural number0
and $\omega$ are limit ordinals.
Theoffset of an ordinal is the number of successor operations it takes to reach it from a limit ordinal.
The Dafny typeORDINAL
has these member functions:
o.IsLimit
– true ifo
is a limit ordinal (including0
)o.IsSucc
– true ifo
is a successor to something, soo.IsSucc<==>!o.IsLimit
o.IsNat
– true ifo
represents anat
value, so forn
anat
,(nasORDINAL).IsNat
is trueand ifo.IsNat
is true then(oasnat)
is well-definedo.Offset
– is thenat
value giving the offset of the ordinal
In addition,
- non-negative numeric literals may be considered
ORDINAL
literals, soo+1
is allowed ORDINAL
s may be compared, using==!=<<=>>=
- two
ORDINAL
s may be added and the result is>=
either one of them; addition is associative but not commutative *
,/
and%
are not defined forORDINAL
s- two
ORDINAL
s may be subtracted if the RHS satisfies.IsNat
and the offset of the LHS is not smaller than the offset of the RHS
In Dafny,ORDINAL
s are used primarily in conjunction withextreme functions and lemmas.
5.2.5. Characters (grammar)
Dafny supports a typechar
ofcharacters.
Its exact meaning is controlled by the command-line switch--unicode-char:true|false
.
If--unicode-char
is disabled, thenchar
represents anyUTF-16 code unit.This includes surrogate code points.
If--unicode-char
is enabled, thenchar
represents anyUnicode scalar value.This excludes surrogate code points.
Character literals are enclosed in single quotes, as in'D'
. To write a single quote as acharacter literal, it is necessary to use anescape sequence.Escape sequences can also be used to write other characters. Thesupported escape sequences are the following:
escape sequence | meaning |
---|---|
\' | the character' |
\" | the character" |
\\ | the character\ |
\0 | the null character, same as\u0000 or\U{0} |
\n | line feed |
\r | carriage return |
\t | horizontal tab |
\u xxxx | UTF-16 code unit whose hexadecimal code isxxxx, where eachx is a hexadecimal digit |
\U{ x..x} | Unicode scalar value whose hexadecimal code isx..x, where eachx is a hexadecimal digit |
The escape sequence for a double quote is redundant, because'"'
and'\"'
denote the samecharacter—both forms are provided in order to support the sameescape sequences in string literals (Section 5.5.3.5).
In the form\u
xxxx, which is only allowed if--unicode-char
is disabled,theu
is always lower case, but the fourhexadecimal digits are case insensitive.
In the form\U{
x..x}
, which is only allowed if--unicode-char
is enabled,theU
is always upper case,but the hexadecimal digits are case insensitive, and there mustbe at least one and at most six digits.Surrogate code points are not allowed.The hex digits may be interspersed with underscores for readability (but not beginning or ending with an underscore), as in\U{1_F680}
.
Character values are ordered and can be compared using the standardrelational operators:
operator | description |
---|---|
< | less than |
<= | at most |
>= | at least |
> | greater than |
Sequences of characters representstrings, as described inSection 5.5.3.5.
Character values can be converted to and fromint
values using theasint
andaschar
conversion operations. The result is what wouldbe expected in other programming languages, namely, theint
value of achar
is the ASCII or Unicode numeric value.
The only other operations on characters are obtaining a characterby indexing into a string, and the implicit conversion to stringwhen used as a parameter of aprint
statement.
5.3. Type parameters (grammar)
Examples:
typeG1<T>typeG2<T(0)>typeG3<+T(==),-U>
Many of the types, functions, and methods in Dafny can beparameterized by types. Thesetype parameters are declared inside angle brackets and can stand for any type.
Dafny has some inference support that makes certain signatures lesscluttered (described inSection 12.2).
5.3.1. Declaring restrictions on type parameters
It is sometimes necessary to restrict type parameters so thatthey can only be instantiated by certain families of types, that is,by types that have certain properties. These properties are known astype characteristics. The following subsectionsdescribe the type characteristics that Dafny supports.
In some cases, type inference will infer that a type-parametermust be restricted in a particular way, in which case Dafnywill add the appropriate suffix, such as(==)
, automatically.
If more than one restriction is needed, they are eitherlisted comma-separated,inside the parentheses or as multiple parenthesized elements:T(==,0)
orT(==)(0)
.
When an actual type is substituted for a type parameter in a generic type instantiation,the actual type must have the declared or inferred type characteristics of the type parameter.These characteristics might also be inferred for the actual type. For example, a numeric-basedsubset or newtype automatically has the==
relationship of its base type. Similarly, type synonyms have the characteristics of the type they represent.
An abstract type has no known characteristics. If it is intended to be defined only as typesthat have certain characteristics, then those characteristics must be declared.For example,
classA<T(00)>{}typeQconsta:A<Q>
will give an error because it is not known whether the typeQ
is non-empty (00
).Instead, one needs to write
classA<T(00)>{}typeQ(00)consta:A?<Q>:=null
5.3.1.1. Equality-supporting type parameters:T(==)
Designating a type parameter with the(==)
suffix indicates thatthe parameter may only be replaced in non-ghost contextswith types that are known tosupport run-time equality comparisons (==
and!=
).All types support equality in ghost contexts,as if, for some types, the equality function is ghost.
For example,
methodCompare<T(==)>(a:T,b:T)returns(eq:bool){ifa==b{eq:=true;}else{eq:=false;}}
is a method whose type parameter is restricted to equality-supportingtypes when used in a non-ghost context.Again, note thatall types support equality inghostcontexts; the difference is only for non-ghost (that is, compiled)code. Coinductive datatypes, arrow types, and inductivedatatypes with ghost parameters are examples of types that are notequality supporting.
5.3.1.2. Auto-initializable types:T(0)
At every access of a variablex
of a typeT
, Dafny ensures thatx
holds a legal value of typeT
.If no explicit initialization is given, then an arbitrary value isassumed by the verifier and supplied by the compiler,that is, the variable isauto-initialized, but to an arbitrary value.For example,
classExample<A(0),X>{varn:natvari:intvara:Avarx:Xconstructor(){new;// error: field 'x' has not been given a value`assertn>=0;// true, regardless of the value of 'n'asserti>=0;// possibly false, since an arbitrary 'int' may be negative// 'a' does not require an explicit initialization, since 'A' is auto-init}}
In the example above, the class fields do not need to be explicitly initializedin the constructor because they are auto-initialized to an arbitrary value.
Local variables and out-parameters are however, subject to definite assignmentrules. The following example requires--relax-definite-assignment
,which is not the default.
methodm(){varn:nat;// Auto-initialized to an arbitrary value of type `nat`assertn>=0;// true, regardless of the value of nvari:int;asserti>=0;// possibly false, arbitrary ints may be negative}
With the default behavior of definite assignment,n
andi
need to be initializedto an explicit value of their type or to an arbitrary value using, for example,varn:nat:=*;
.
For some types (known asauto-init types), the compiler can choose aninitial value, but for others it does not.Variables and fields whose type the compiler does not auto-initializeare subject todefinite-assignment rules. These ensure that the programexplicitly assigns a value to a variable before it is used.For more details seeSection 12.6 and the--relax-definite-assignment
command-line option.More detail on auto-initializing is inthis document.
Dafny supports auto-init as a type characteristic.To restrict a type parameter to auto-init types, mark it with the(0)
suffix. For example,
methodAutoInitExamples<A(0),X>()returns(a:A,x:X){// 'a' does not require an explicit initialization, since A is auto-init// error: out-parameter 'x' has not been given a value}
In this example, an error is reported because out-parameterx
has notbeen assigned—since nothing is known about typeX
, variables oftypeX
are subject to definite-assignment rules. In contrast, sincetype parameterA
is declared to be restricted to auto-init types,the program does not need to explicitly assign any value to theout-parametera
.
5.3.1.3. Nonempty types:T(00)
Auto-init types are important in compiled contexts. In ghost contexts, itmay still be important to know that a type is nonempty. Dafny supportsa type characteristic for nonempty types, written with the suffix(00)
.For example, with--relax-definite-assignment
, the following example happens:
methodNonemptyExamples<B(00),X>()returns(b:B,ghostg:B,ghosth:X){// error: non-ghost out-parameter 'b' has not been given a value// ghost out-parameter 'g' is fine, since its type is nonempty// error: 'h' has not been given a value}
Because ofB
’s nonempty type characteristic, ghost parameterg
does notneed to be explicitly assigned. However, Dafny reports an error for thenon-ghostb
, sinceB
is not an auto-init type, and reports an errorforh
, since the typeX
could be empty.
Note that every auto-init type is nonempty.
In the default definite-assignment mode (that is, without--relax-definite-assignment
)there will be errors for all three formal parameters in the example just given.
For more details seeSection 12.6.
5.3.1.4. Non-heap based:T(!new)
Dafny makes a distinction between types whose values are on the heap,i.e. references, likeclasses and arrays, and those that are strictly value-based, like basictypes and datatypes.The practical implication is that references depend on allocation state(e.g., are affected by theold
operation) whereas non-reference valuesare not.Thus it can be relevant to know whether the values of a type parameterare heap-based or not. This is indicated by the mode suffix(!new)
.
A type parameter characterized by(!new)
isrecursively independentof the allocation state. For example, a datatype is not a reference, but fora parameterized data type such as
datatypeResult<T>=Failure(error:string)|Success(value:T)
the instantiationResult<int>
satisfies(!new)
, whereasResult<array<int>>
does not.
Note that this characteristic of a type parameter is operative for bothverification and compilation.Also, abstract types at the topmost scope are always implicitly(!new)
.
Here are some examples:
datatypeResult<T>=Failure(error:string)|Success(v:T)datatypeResultN<T(!new)>=Failure(error:string)|Success(v:T)classC{}methodm(){varx1:Result<int>;varx2:ResultN<int>;varx3:Result<C>;varx4:ResultN<C>;// errorvarx5:Result<array<int>>;varx6:ResultN<array<int>>;// error}
5.3.2. Type parameter variance
Type parameters have several different variance and cardinality properties.These properties of type parameters are designated in a generic type definition.For instance, intypeA<+T>=...
, the+
indicates that theT
positionis co-variant. These properties are indicated by the following notation:
notation | variance | cardinality-preserving |
---|---|---|
(nothing) | non-variant | yes |
+ | co-variant | yes |
- | contra-variant | not necessarily |
* | co-variant | not necessarily |
! | non-variant | not necessarily |
- co-variance (
A<+T>
orA<*T>
) means that ifU
is a subtype ofV
thenA<U>
is a subtype ofA<V>
- contra-variance (
A<-T>
) means that ifU
is a subtype ofV
thenA<V>
is a subtype ofA<U>
- non-variance (
A<T>
orA<!T>
) means that ifU
is a different type thanV
then there is no subtyping relationship betweenA<U>
andA<V>
Cardinality preserving means that the cardinality of the type being defined never exceeds the cardinality of any of its type parameters.For exampletypeT<X>=X->bool
is illegal and returns the error messageformaltypeparameter'X'isnotusedaccordingtoitsvariancespecification(itisusedleftofanarrow)(perhapstrydeclaring'X'as'-X'or'!X')
The typeX->bool
has strictly more values than the typeX
. This affects certain uses of the type, so Dafny requires the declaration ofT
to explicitly say so. Marking the type parameterX
with-
or!
announces that the cardinality ofT<X>
may by larger than that ofX
. If you use-
, you’re also declaringT
to be contravariant in its type argument, and if you use!
, you’re declaring thatT
is non-variant in its type argument.
To fix it, we use the variance!
:
typeT<!X>=X->bool
This states thatT
does not preserve the cardinality ofX
, meaning there could be strictly more values of typeT<E>
than values of typeE
for anyE
.
A more detailed explanation of these topics ishere.
5.4. Generic Instantiation (grammar)
A generic instantiation consists of a comma-separated list of 1 or more Types,enclosed in angle brackets (<
>
),providing actual types to be used in place of the type parameters of the declaration of the generic type.If there is no instantion for a generic type, type inference will tryto fill these in (cf.Section 12.2).
5.5. Collection types
Dafny offers several built-in collection types.
5.5.1. Sets (grammar)
For any typeT
, each value of typeset<T>
is a finite set ofT
values.
Set membership is determined by equality in the typeT
,soset<T>
can be used in a non-ghost context only ifT
isequality supporting.
For any typeT
, each value of typeiset<T>
is a potentially infiniteset ofT
values.
A set can be formed using aset display expression, which is apossibly empty, unordered, duplicate-insensitive list of expressionsenclosed in curly braces. To illustrate,
{}{2,7,5,3}{4+2,1+5,a*b}
are three examples of set displays. There is also aset comprehensionexpression (with a binder, like in logical quantifications), described inSection 9.31.5.
In addition to equality and disequality, set typessupport the following relational operations:
operator | precedence | description |
---|---|---|
< | 4 | proper subset |
<= | 4 | subset |
>= | 4 | superset |
> | 4 | proper superset |
Like the arithmetic relational operators, these operators arechaining.
Sets support the following binary operators, listed in order ofincreasing binding power:
operator | precedence | description |
---|---|---|
!! | 4 | disjointness |
+ | 6 | set union |
- | 6 | set difference |
* | 7 | set intersection |
The associativity rules of+
,-
, and*
are like those of thearithmetic operators with the same names. The expressionA!!B
,whose binding power is the same as equality (but which neitherassociates nor chains with equality), says that setsA
andB
haveno elements in common, that is, it is equivalent to
A*B=={}
However, the disjointness operator is chaining though in a slightly different way than other chaining operators:A!!B!!C!!D
means thatA
,B
,C
andD
are all mutually disjoint, that is
A*B=={}&&(A+B)*C=={}&&(A+B+C)*D=={}
In addition, for any sets
of typeset<T>
oriset<T>
and anyexpressione
of typeT
, sets support the following operations:
expression | precedence | result type | description |
---|---|---|---|
eins | 4 | bool | set membership |
e!ins | 4 | bool | set non-membership |
|s| | 11 | nat | set cardinality (not foriset ) |
The expressione!ins
is a syntactic shorthand for!(eins)
.
(No white space is permitted between!
andin
, making!in
effectivelythe one example of a mixed-character-class token in Dafny.)
5.5.2. Multisets (grammar)
Amultiset is similar to a set, but keeps track of the multiplicityof each element, not just its presence or absence. For any typeT
,each value of typemultiset<T>
is a map fromT
values to naturalnumbers denoting each element’s multiplicity. Multisets in Dafnyare finite, that is, they contain a finite number of each of a finiteset of elements. Stated differently, a multiset maps only a finitenumber of elements to non-zero (finite) multiplicities.
Like sets, multiset membership is determined by equality in the typeT
, somultiset<T>
can be used in a non-ghost context only ifT
isequality supporting.
A multiset can be formed using amultiset display expression, whichis a possibly empty, unordered list of expressions enclosed in curlybraces after the keywordmultiset
. To illustrate,
multiset{}multiset{0,1,1,2,3,5}multiset{4+2,1+5,a*b}
are three examples of multiset displays. There is no multisetcomprehension expression.
In addition to equality and disequality, multiset typessupport the following relational operations:
operator | precedence | description |
---|---|---|
< | 4 | proper multiset subset |
<= | 4 | multiset subset |
>= | 4 | multiset superset |
> | 4 | proper multiset superset |
Like the arithmetic relational operators, these operators arechaining.
Multisets support the following binary operators, listed in order ofincreasing binding power:
operator | precedence | description |
---|---|---|
!! | 4 | multiset disjointness |
+ | 6 | multiset sum |
- | 6 | multiset difference |
* | 7 | multiset intersection |
The associativity rules of+
,-
, and*
are like those of thearithmetic operators with the same names. The+
operatoradds the multiplicity of corresponding elements, the-
operatorsubtracts them (but 0 is the minimum multiplicity),and the*
has multiplicity that is the minimum of themultiplicity of the operands. There is no operator for multisetunion, which would compute the maximum of the multiplicities of the operands.
The expressionA!!B
says that multisetsA
andB
have no elements in common, that is,it is equivalent to
A*B==multiset{}
Like the analogous set operator,!!
is chaining and means mutual disjointness.
In addition, for any multisets
of typemultiset<T>
,expressione
of typeT
, and non-negative integer-based numericn
, multisets support the following operations:
expression | precedence | result type | description |
---|---|---|---|
eins | 4 | bool | multiset membership |
e!ins | 4 | bool | multiset non-membership |
|s| | 11 | nat | multiset cardinality |
s[e] | 11 | nat | multiplicity ofe ins |
s[e:=n] | 11 | multiset<T> | multiset update (change of multiplicity) |
The expressioneins
returnstrue
if and only ifs[e]!=0
.The expressione!ins
is a syntactic shorthand for!(eins)
.The expressions[e:=n]
denotes a multiset likes
, but where the multiplicity of elemente
isn
. Note thatthe multiset updates[e:=0]
results in a multiset likes
butwithout any occurrences ofe
(whether or nots
has occurrences ofe
in the first place). As another example, note thats-multiset{e}
is equivalent to:
ifeinsthens[e:=s[e]-1]elses
5.5.3. Sequences (grammar)
For any typeT
, a value of typeseq<T>
denotes asequence ofT
elements, that is, a mapping from a finite downward-closed set of naturalnumbers (calledindices) toT
values.
5.5.3.1. Sequence Displays
A sequence can be formed using asequence display expression, whichis a possibly empty, ordered list of expressions enclosed in squarebrackets. To illustrate,
[][3,1,4,1,5,9,3][4+2,1+5,a*b]
are three examples of sequence displays.
There is also a sequencecomprehension expression (Section 9.28):
seq(5,i=>i*i)
is equivalent to[0,1,4,9,16]
.
5.5.3.2. Sequence Relational Operators
In addition to equality and disequality, sequence typessupport the following relational operations:
operator | precedence | description |
---|---|---|
< | 4 | proper prefix |
<= | 4 | prefix |
Like the arithmetic relational operators, these operators arechaining. Note the absence of>
and>=
.
5.5.3.3. Sequence Concatenation
Sequences support the following binary operator:
operator | precedence | description |
---|---|---|
+ | 6 | concatenation |
Operator+
is associative, like the arithmetic operator with thesame name.
5.5.3.4. Other Sequence Expressions
In addition, for any sequences
of typeseq<T>
, expressione
of typeT
, integer-based numeric indexi
satisfying0<=i<|s|
, andinteger-based numeric boundslo
andhi
satisfying0<=lo<=hi<=|s|
, noting that bounds can equal the length of the sequence,sequences support the following operations:
expression | precedence | result type | description |
---|---|---|---|
eins | 4 | bool | sequence membership |
e!ins | 4 | bool | sequence non-membership |
|s| | 11 | nat | sequence length |
s[i] | 11 | T | sequence selection |
s[i:=e] | 11 | seq<T> | sequence update |
s[lo..hi] | 11 | seq<T> | subsequence |
s[lo..] | 11 | seq<T> | drop |
s[..hi] | 11 | seq<T> | take |
s[ slices] | 11 | seq<seq<T>> | slice |
multiset(s) | 11 | multiset<T> | sequence conversion to amultiset<T> |
Expressions[i:=e]
returns a sequence likes
, except that theelement at indexi
ise
. The expressioneins
says thereexists an indexi
such thats[i]==e
. It is allowed in non-ghostcontexts only if the element typeT
isequality supporting.The expressione!ins
is a syntactic shorthand for!(eins)
.
Expressions[lo..hi]
yields a sequence formed by taking the firsthi
elements and then dropping the firstlo
elements. Theresulting sequence thus has lengthhi-lo
. Note thats[0..|s|]
equalss
. If the upper bound is omitted, itdefaults to|s|
, sos[lo..]
yields the sequence formed by droppingthe firstlo
elements ofs
. If the lower bound is omitted, itdefaults to0
, sos[..hi]
yields the sequence formed by taking thefirsthi
elements ofs
.
In the sequence slice operation,slices is a nonempty list oflength designators separated and optionally terminated by a colon, andthere is at least one colon. Each length designator is a non-negativeinteger-based numeric; the sum of the length designators is no greater than|s|
. If therearek colons, the operation producesk + 1 consecutive subsequencesfroms
, with the length of each indicated by the corresponding lengthdesignator, and returns these as a sequence ofsequences.Ifslices is terminated by acolon, then the length of the last slice extends until the end ofs
,that is, its length is|s|
minus the sum of the given lengthdesignators. For example, the following equalities hold, for anysequences
of length at least10
:
methodm(s:seq<int>){vart:=[3.14,2.7,1.41,1985.44,100.0,37.2][1:0:3];assert|t|==3&&t[0]==[3.14]&&t[1]==[];assertt[2]==[2.7,1.41,1985.44];varu:=[true,false,false,true][1:1:];assert|u|==3&&u[0][0]&&!u[1][0]&&u[2]==[false,true];assume|s|>10;asserts[10:][0]==s[..10];asserts[10:][1]==s[10..];}
The operationmultiset(s)
yields the multiset of elements ofsequences
. It is allowed in non-ghost contexts only if the elementtypeT
isequality supporting.
5.5.3.5. Strings (grammar)
A special case of a sequence type isseq<char>
, for which Dafnyprovides a synonym:string
. Strings are like other sequences, butprovide additional syntax for sequence display expressions, namelystring literals. There are two forms of the syntax for stringliterals: thestandard form and theverbatim form.
String literals of the standard form are enclosed in double quotes, asin"Dafny"
. To include a double quote in such a string literal,it is necessary to use an escape sequence. Escape sequences can alsobe used to include other characters. The supported escape sequencesare the same as those for character literals (Section 5.2.5).For example, the Dafny expression"say \"yes\""
represents thestring'say "yes"'
.The escape sequence for a single quote is redundant, because"\'"
and"\'"
denote the samestring—both forms are provided in order to support the sameescape sequences as do character literals.
String literals of the verbatim form are bracketed by@"
and"
, as in@"Dafny"
. To includea double quote in such a string literal, it is necessary to use theescape sequence""
, that is, to write the charactertwice. In the verbatim form, there are no other escape sequences.Even characters like newline can be written inside the string literal(hence spanning more than one line in the program text).
For example, the following three expressions denote the same string:
"C:\\tmp.txt"@"C:\tmp.txt"['C',':','\\','t','m','p','.','t','x','t']
Since strings are sequences, the relational operators<
and<=
are defined on them. Note, however, that these operatorsstill denote proper prefix and prefix, respectively, not some kind ofalphabetic comparison as might be desirable, for example, whensorting strings.
5.5.4. Finite and Infinite Maps (grammar)
For any typesT
andU
, a value of typemap<T,U>
denotes a(finite) mapfromT
toU
. In other words, it is a look-up table indexed byT
. Thedomain of the map is a finite set ofT
values that haveassociatedU
values. Since the keys in the domain are comparedusing equality in the typeT
, typemap<T,U>
can be used in anon-ghost context only ifT
isequality supporting.
Similarly, for any typesT
andU
, a value of typeimap<T,U>
denotes a(possibly) infinite map. In most regards,imap<T,U>
islikemap<T,U>
, but a map of typeimap<T,U>
is allowed to have aninfinite domain.
A map can be formed using amap display expression (seeSection 9.30),which is a possibly empty, ordered list ofmaplets, each maplet having theformt:=u
wheret
is an expression of typeT
andu
is anexpression of typeU
, enclosed in square brackets after the keywordmap
. To illustrate,
map[]map[20:=true,3:=false,20:=false]map[a+b:=c+d]
are three examples of map displays. By using the keywordimap
instead ofmap
, the map produced will be of typeimap<T,U>
instead ofmap<T,U>
. Note that an infinite map (imap
) is allowedto have a finite domain, whereas a finite map (map
) is not allowedto have an infinite domain.If the same key occurs more thanonce in a map display expression, only the last occurrence appears in the resultingmap.2 There is also amap comprehension expression,explained inSection 9.31.8.
For any mapfm
of typemap<T,U>
,any mapm
of typemap<T,U>
orimap<T,U>
,any expressiont
of typeT
,any expressionu
of typeU
, and anyd
in the domain ofm
(thatis, satisfyingdinm
), maps support the following operations:
expression | precedence | result type | description |
---|---|---|---|
tinm | 4 | bool | map domain membership |
t!inm | 4 | bool | map domain non-membership |
|fm| | 11 | nat | map cardinality |
m[d] | 11 | U | map selection |
m[t:=u] | 11 | map<T,U> | map update |
m.Keys | 11 | (i)set<T> | the domain ofm |
m.Values | 11 | (i)set<U> | the range ofm |
m.Items | 11 | (i)set<(T,U)> | set of pairs (t,u) inm |
|fm|
denotes the number of mappings infm
, that is, thecardinality of the domain offm
. Note that the cardinality operatoris not supported for infinite maps.Expressionm[d]
returns theU
value thatm
associates withd
.Expressionm[t:=u]
is a map likem
, except that theelement at keyt
isu
. The expressiontinm
sayst
is in thedomain ofm
andt!inm
is a syntactic shorthand for!(tinm)
.3
The expressionsm.Keys
,m.Values
, andm.Items
return, as sets,the domain, the range, and the 2-tuples holding the key-valueassociations in the map. Note thatm.Values
will have a differentcardinality thanm.Keys
andm.Items
if different keys areassociated with the same value. Ifm
is animap
, then theseexpressions returniset
values. Ifm
is a map,m.Values
andm.Items
require the type of the rangeU
to support equality.
Here is a small example, where a mapcache
of typemap<int,real>
is used to cache computed values of Joule-Thomson coefficients forsome fixed gas at a given temperature:
ifKincache{// check if temperature is in domain of cachecoeff:=cache[K];// read result in cache}else{coeff:=ComputeJTCoefficient(K);// do expensive computationcache:=cache[K:=coeff];// update the cache}
Dafny also overloads the+
and-
binary operators for maps.The+
operator merges two maps or imaps of the same type, as if each(key,value) pair of the RHS is added in turn to the LHS (i)map.In this use,+
is not commutative; if a key exists in both(i)maps, it is the value from the RHS (i)map that is present in the result.
The-
operator implements a map difference operator. Here the LHSis amap<K,V>
orimap<K,V>
and the RHS is aset<K>
(but not aniset
); the operation removesfrom the LHS all the (key,value) pairs whose key is a member of the RHS set.
To avoid causing circular reasoning chains or providing too much information that mightcomplicate Dafny’s prover finding proofs, not all properties of maps are known by the prover by default.For example, the following does not prove:
methodmmm<K(==),V(==)>(m:map<K,V>,k:K,v:V){varmm:=m[k:=v];assertvinmm.Values;}
Rather, one must provide an intermediate step, which is not entirely obvious:
methodmmm<K(==),V(==)>(m:map<K,V>,k:K,v:V){varmm:=m[k:=v];assertkinmm.Keys;assertvinmm.Values;}
5.5.5. Iterating over collections
Collections are very commonly used in programming and one frequentlyneeds to iterate over the elements of a collection. Dafny does not havebuilt-in iterator methods, but the idioms by which to do so are straightforward.The subsections below give some introductory examples; moredetail can be found in thispower user note.
5.5.5.1. Sequences and arrays
Sequences and arrays are indexable and have a length. So the idiom toiterate over the contents is well-known. For an array:
methodm(a:array<int>){vari:=0;varsum:=0;whilei<a.Length{sum:=sum+a[i];i:=i+1;}}
For a sequence, the only difference is the length operator:
methodm(s:seq<int>){vari:=0;varsum:=0;whilei<|s|{sum:=sum+s[i];i:=i+1;}}
Theforall
statement (Section 8.21) can also be usedwith arrays where parallel assignment is needed:
methodm(s:array<int>){varrev:=newint[s.Length];foralli|0<=i<s.Length{rev[i]:=s[s.Length-i-1];}}
SeeSection 5.10.2 on how to convert an array to a sequence.
5.5.5.2. Sets
There is no intrinsic order to the elements of a set. Nevertheless, we canextract an arbitrary element of a nonempty set, performing an iterationas follows:
methodm(s:set<int>){varss:=s;whiless!={}decreases|ss|{vari:int:|iinss;ss:=ss-{i};printi,"\n";}}
Becauseiset
s may be infinite, Dafny does not permit iteration over aniset
.
5.5.5.3. Maps
Iterating over the contents of amap
uses the component sets:Keys
,Values
, andItems
. The iteration loop follows the same patterns as for sets:
methodm<T(==),U(==)>(m:map<T,U>){varitems:=m.Items;whileitems!={}decreases|items|{varitem:|iteminitems;items:=items-{item};printitem.0," ",item.1,"\n";}}
There are no mechanisms currently defined in Dafny for iterating overimap
s.
5.6. Types that stand for other types (grammar)
It is sometimes useful to know a type by several names or to treat atype abstractly. There are several mechanisms in Dafny to do this:
- (Section 5.6.1) A typicalsynonym type, in which a type name is a synonym for another type
- (Section 5.6.2) Anabstract type, in which a new type name is declared as an uninterpreted type
- (Section 5.6.3) Asubset type, in which a new type name is given to a subset of the values of a given type
- (Section 5.7) Anewtype, in which a subset type is declared, but with restrictions on converting to and from its base type
5.6.1. Type synonyms (grammar)
typeT=inttypeSS<T>=set<set<T>>
Atype synonym declaration:
typeY<T>=G
declaresY<T>
to be a synonym for the typeG
.If the=G
is omitted then the declaration just declares a name as an uninterpretedabstract type, as described inSection 5.6.2. Such types may begiven a definition elsewhere in the Dafny program.
Here,T
is anonempty list of type parameters (each of which optionallyhas atype characteristics suffix), which can be used as free typevariables inG
. If the synonym has no type parameters, the “<T>
”is dropped. In all cases, a type synonym is just a synonym. That is,there is never a difference, other than possibly in error messagesproduced, betweenY<T>
andG
.
For example, the names of the following type synonyms may improve thereadability of a program:
typeReplacements<T>=map<T,T>typeVertex=int
The new type name itself may havetype characteristics declared, and may need to if there is no definition.If there is a definition, the type characteristics are typically inferred from the definition. The syntax is like this:
typeZ(==)<T(0)>
As already described inSection 5.5.3.5,string
is a built-intype synonym forseq<char>
, as if it would have been declared asfollows:
typestring_(==,0,!new)=seq<char>
If the implicit declaration did not include the type characteristics, they would be inferred in any case.
Note that although a type synonym can be declared and used in place of a type name, that does not affect the names of datatype or class constructors.For example, consider
datatypePair<T>=Pair(first:T,second:T)typeIntPair=Pair<int>constp:IntPair:=Pair(1,2)// OKconstq:IntPair:=IntPair(3,4)// Error
In the declaration ofq
,IntPair
is the name of a type, not the name of a function or datatype constructor.
5.6.2. Abstract types (grammar)
Examples:
typeTtypeQ{functiontoString(t:T):string}
An abstract type is a special case of a type synonym that is underspecified. Sucha type is declared simply by:
typeY<T>
Its definition can be stated in arefining module. The nameY
can be immediately followed bya type characteristics suffix (Section 5.3.1).Because there is no defining RHS, the type characteristics cannot be inferred and somust be stated. If, in some refining module, a definition of the type is given, thetype characteristics must match those of the new definition.
For example, the declarations
typeTfunctionF(t:T):T
can be used to model an uninterpreted functionF
on somearbitrary typeT
. As another example,
typeMonad<T>
can be used abstractly to represent an arbitrary parameterized monad.
Even as an abstract type, the typemay be given members such as constants, methods or functions.For example,
abstractmoduleP{typeT{functionToString():string}}moduleXrefinesP{newtypeT=i|0<=i<10{functionToString():string{""}}}
The abstract typeP.T
has a declared memberToString
, which can be called whereverP.T
may be used.In the refining moduleX
,T
is declared to be anewtype
, in whichToString
now has a body.
It would be an error to refineP.T
as a simple type synonym or subset type inX
, saytypeT=int
, becausetype synonyms may not have members.
5.6.3. Subset types (grammar)
Examples:
typePos=i:int|i>0witness1typePosReal=r|r>0.0witness1.0typeEmpty=n:nat|n<0witness*typeBig=n:nat|n>1000ghostwitness10000
Asubset type is a restricted use of an existing type, calledthebase type of the subset type. A subset type is like acombined use of the base type and a predicate on the basetype.
An assignment from a subset type to its base type is alwaysallowed. An assignment in the other direction, from the base type toa subset type, is allowed provided the value assigned does indeedsatisfy the predicate of the subset type. This condition is checkedby the verifier, not by the type checker. Similarly, assignments fromone subset type to another (both with the same base type) are alsopermitted, as long as it can be established that the value being assignedsatisfies the predicate defining the receiving subset type.(Note, in contrast, assignments between a newtype and its base typeare never allowed, even if the value assigned is a value of the targettype. For such assignments, an explicit conversion must be used, seeSection 9.10.)
The declaration of a subset type permits an optionalwitness
clause, to declare that there isa value that satisfies the subset type’s predicate; that is, the witness clause establishes that the definedtype is not empty. The compiler may, but is not obligated to, use this value when auto-initializing anewly declared variable of the subset type.
Dafny builds in three families of subset types, as described next.
5.6.3.1. Typenat
The built-in typenat
, which represents the non-negative integers(that is, the natural numbers), is a subset type:
typenat=n:int|0<=n
A simple example thatputs subset typenat
to good use is the standard Fibonaccifunction:
functionFib(n:nat):nat{ifn<2thennelseFib(n-2)+Fib(n-1)}
An equivalent, but clumsy, formulation of this function (modulo thewording of any error messages produced at call sites) would be to usetypeint
and to write the restricting predicate in pre- andpostconditions:
functionFib(n:int):intrequires0<=n// the function argument must be non-negativeensures0<=Fib(n)// the function result is non-negative{ifn<2thennelseFib(n-2)+Fib(n-1)}
5.6.3.2. Non-null types
Every class, trait, and iterator declarationC
gives rise to two types.
One type has the nameC?
(that is, the name of the class, trait,or iterator declaration with a?
character appended to the end).The values ofC?
are the references toC
objects, and alsothe valuenull
.In other words,C?
is the type ofpossibly null references(aka,nullable references) toC
objects.
The other type has the nameC
(that is, the same name as theclass, trait, or iterator declaration).Its values are the references toC
objects, and does not containthe valuenull
.In other words,C
is the type ofnon-null references toC
objects.
The typeC
is a subset type ofC?
:
typeC=c:C?|c!=null
(It may be natural to think of the typeC?
as the union oftypeC
and the valuenull
, but, technically, Dafny definesC
as a subset type with base typeC?
.)
From being a subset type, we get thatC
is a subtype ofC?
.Moreover, if a class or traitC
extends a traitB
, thentypeC
is a subtype ofB
and typeC?
is a subtype ofB?
.
Every possibly-null reference type is a subtype of thebuilt-in possibly-null trait typeobject?
, andevery non-null reference type is a subtype of thebuilt-in non-null trait typeobject
. (And, from the factthatobject
is a subset type ofobject?
, we also have thatobject
is a subtype ofobject?
.)
Arrays are references and array types also come in these two flavors.For example,array?
andarray2?
are possibly-null (1- and 2-dimensional) array types, andarray
andarray2
are their respective non-null types.
Note that?
is not an operator. Instead, it is simply the lastcharacter of the name of these various possibly-null types.
5.6.3.3. Arrow types:->
,-->
, and~>
For more information about arrow types (function types), seeSection 5.12.This section is a preview to point out the subset-type relationships among the kindsof function types.
The built-in type
->
stands for total functions,-->
stands for partial functions (that is, functions with possiblerequires
clauses),and~>
stands for all functions.
More precisely, type constructorsexist for any arity (()->X
,A->X
,(A,B)->X
,(A,B,C)->X
,etc.).
For a list of typesTT
and a typeU
, the values of the arrow type(TT)~>U
are functions fromTT
toU
. This includes functions that may read theheap and functions that are not defined on all inputs. It is not commonto need this generality (and working with such general functions isdifficult). Therefore, Dafny defines two subset types that are more common(and much easier to work with).
The type(TT)-->U
denotes the subset of(TT)~>U
where the functionsdo not read the (mutable parts of the) heap.Values of type(TT)-->U
are calledpartial functions,and the subset type(TT)-->U
is called thepartial arrow type.(As a mnemonic to help you remember that this is the partial arrow, you maythink of the little gap between the two hyphens in-->
as showing a brokenarrow.)
Intuitively, the built-in partial arrow type is defined as follows (here shownfor arrows with arity 1):
typeA-->B=f:A~>B|foralla::f.reads(a)=={}
(except that what is shown here left of the=
is not legal Dafny syntaxand that the restriction could not be verified as is).That is, the partial arrow type is defined as those functionsf
whose reads frame is empty for all inputs.More precisely, taking variance into account, the partial arrow typeis defined as
type-A-->+B=f:A~>B|foralla::f.reads(a)=={}
The type(TT)->U
is, in turn, a subset type of(TT)-->U
, adding therestriction that the functions must not impose any precondition. That is,values of type(TT)->U
aretotal functions, and the subset type(TT)->U
is called thetotal arrow type.
The built-in total arrow type is defined as follows (here shownfor arrows with arity 1):
type-A->+B=f:A-->B|foralla::f.requires(a)
That is, the total arrow type is defined as those partial functionsf
whose precondition evaluates totrue
for all inputs.
Among these types, the most commonly used are the total arrow types.They are also the easiest to work with. Because they are common, theyhave the simplest syntax (->
).
Note, informally, we tend to speak of all three of these types as arrow types,even though, technically, the~>
types are the arrow types and the-->
and->
types are subset types thereof. The one place where you may need toremember that-->
and->
are subset types is in some error messages.For example, if you try to assign a partial function to a variable whosetype is a total arrow type and the verifier is not able to prove that thepartial function really is total, then you’ll get an error saying that the subset-typeconstraint may not be satisfied.
For more information about arrow types, seeSection 5.12.
5.6.3.4. Witness clauses
The declaration of a subset type permits an optionalwitness
clause.Types in Dafny are generally expected to be non-empty, in part becausevariables of any type are expected to have some value when they are used.In many cases, Dafny can determine that a newly declared type has some value. For example, in the absence of a witness clause,a numeric type that includes 0 is known by Dafnyto be non-empty.However, Dafny cannot always make this determination.If it cannot, awitness
clause is required. The value given inthewitness
clause must be a valid value for the type and assures Dafnythat the type is non-empty. (The variationwitness*
is described below.)
For example,
typeOddInt=x:int|x%2==1
will give an error message, but
typeOddInt=x:int|x%2==1witness73
does not. Here is another example:
typeNonEmptySeq=x:seq<int>||x|>0witness[0]
If the witness is only available in ghost code, you can declare the witnessas aghostwitness
. In this case, the Dafny verifier knows that the typeis non-empty, but it will not be able to auto-initialize a variable of thattype in compiled code.
There is even room to do the following:
typeBaseTypepredicateRHS(x:BaseType)typeMySubset=x:BaseType|RHS(x)ghostwitnessMySubsetWitness()function{:axiom}MySubsetWitness():BaseTypeensuresRHS(MySubsetWitness())
Here the type is given a ghost witness: the result of the expressionMySubsetWitness()
, which is a call of a (ghost) function.Now that function has a postcondition saying that the returned value is indeed a candidate value for the declared type, so the verifier issatisfied regarding the non-emptiness of the type. However, the functionhas no body, so there is still no proof that there is indeed such a witness.You can either supply a, perhaps complicated, body to generate a viablecandidate or you can be very sure, without proof, that there is indeed such a value.If you are wrong, you have introduced an unsoundness into your program.
In addition though, types are allowed to be empty or possibly empty.This is indicated by the clausewitness*
, which tells the verifier not to check for a satisfying witness.A declaration like this produces an empty type:
typeReallyEmpty=x:int|falsewitness*
The type can be used in code like
methodM(x:ReallyEmpty)returns(seven:int)ensuresseven==7{seven:=10;}
which does verify. But the method can never be called because there is no value thatcan be supplied as the argument. Even this code
methodP()returns(seven:int)ensuresseven==7{varx:ReallyEmpty;seven:=10;}
does not complain aboutx
unlessx
is actually used, in which case it must have a value.The postcondition inP
does not verify, but not because of the empty type.
5.7. Newtypes (grammar)
Examples:
newtypeI=intnewtypeD=i:int|0<=i<10newtypeuint8=i|0<=i<256
A newtype is like a type synonym or subset type except that it declares a wholly new typename that is distinct from its base type. It also accepts an optionalwitness
clause.
A new type can be declared with thenewtypedeclaration, for example:
newtypeN=x:M|Q
whereM
is a type andQ
is a boolean expression that canusex
as a free variable. IfM
is an integer-based numeric type,then so isN
; ifM
is real-based, then so isN
. If the typeM
can be inferred fromQ
, the “:M
” can be omitted. IfQ
is justtrue
, then the declaration can be given simply as:
newtypeN=M
TypeM
is known as thebase type ofN
. At present, Dafny only supportsint
andreal
as base types of newtypes.
A newtype is a type that supports the same operations as itsbase type. The newtype is distinct from and incompatible with othertypes; in particular, it is not assignable to its base typewithout an explicit conversion. An important difference between theoperations on a newtype and the operations on its base type is thatthe newtype operations are defined only if the result satisfies thepredicateQ
, and likewise for the literals of thenewtype.
For example, supposelo
andhi
are integer-based numeric bounds thatsatisfy0<=lo<=hi
and consider the following code fragment:
varmid:=(lo+hi)/2;
Iflo
andhi
have typeint
, then the code fragment is legal; inparticular, it never overflows, sinceint
has no upper bound. Incontrast, iflo
andhi
are variables of a newtypeint32
declaredas follows:
newtypeint32=x|-0x8000_0000<=x<0x8000_0000
then the code fragment is erroneous, since the result of the additionmay fail to satisfy the predicate in the definition ofint32
. Thecode fragment can be rewritten as
varmid:=lo+(hi-lo)/2;
in which case it is legal for bothint
andint32
.
An additional point with respect to arithmetic overflow is that for (signed)int32
valueshi
andlo
constrained only bylo<=hi
, the differencehi-lo
can also overflow the bounds of theint32
type. So you could also write:
varmid:=lo+(hi/2-lo/2);
Since a newtype is incompatible with its base type and since allresults of the newtype’s operations are members of the newtype, acompiler for Dafny is free to specialize the run-time representationof the newtype. For example, by scrutinizing the definition ofint32
above, a compiler may decide to storeint32
values usingsigned 32-bit integers in the target hardware.
The incompatibility of a newtype and its basetype is intentional,as newtypes are meant to be used as distinct types from the basetype.If numeric types are desired that mix more readily with the basetype,the subset types described inSection 5.6.3 may be more appropriate.
Note that the bound variablex
inQ
has typeM
, notN
.Consequently, it may not be possible to stateQ
about theN
value. For example, consider the following type of 8-bit 2’scomplement integers:
newtypeint8=x:int|-128<=x<128
and consider a variablec
of typeint8
. The expression
-128<=c<128
is not well-defined, because the comparisons require each operand tohave typeint8
, which means the literal128
is checked to be oftypeint8
, which it is not. A proper way to write this expressionis to use a conversion operation, described inSection 5.7.1, onc
toconvert it to the base type:
-128<=casint<128
If possible, Dafny compilers will represent values of the newtype usinga native type for the sake of efficiency. This action canbe inhibited or a specific native data type selected byusing the{:nativeType}
attribute, as explained inSection 11.1.2.
Furthermore, for the compiler to be able to make an appropriate choice ofrepresentation, the constants in the defining expression as shown above must beknown constants at compile-time. They need not be numeric literals; combinationsof basic operations and symbolic constants are also allowed as describedinSection 9.39.
5.7.1. Conversion operations
For every typeN
, there is a conversion operation with thenameasN
, described more fully inSection 9.10.It is a partial function defined when thegiven value, which can be of any type, is a member of the typeconverted to. When the conversion is from a real-based numeric typeto an integer-based numeric type, the operation requires that thereal-based argument have no fractional part. (To round a real-basednumeric value down to the nearest integer, use the.Floor
member,seeSection 5.2.2.)
To illustrate using the example from above, iflo
andhi
have typeint32
, then the code fragment can legally be written as follows:
varmid:=(loasint+hiasint)/2;
where the type ofmid
is inferred to beint
. Since the resultvalue of the division is a member of typeint32
, one can introduceyet another conversion operation to make the type ofmid
beint32
:
varmid:=((loasint+hiasint)/2)asint32;
If the compiler does specialize the run-time representation forint32
, then these statements come at the expense of two,respectively three, run-time conversions.
TheasN
conversion operation is grammatically a suffix operation like.
field and array indexing, but binds less tightly than unary operations:-xasint
is(-x)asint
;a+basint
isa+(basint)
.
TheasN
conversion can also be used with reference types. For example,ifC
is a class,c
is an expression of typeC
, ando
is an expressionof typeobject
, thencasobject
andcasobject?
are upcastsandoisC
is a downcast. A downcast requires the LHS expression tohave the RHS type, as is enforced by the verifier.
For some types (in particular, reference types), there is also acorrespondingis
operation (Section 9.10) thattests whether a value is valid for a given type.
5.8. Class types (grammar)
Examples:
traitT{}classA{}classBextendsT{constb:B?varv:intconstructor(vv:int){v:=vv;b:=null;}functiontoString():string{"a B"}methodm(i:int){varx:=newB(0);}staticmethodq(){}}
Declarations within a class all begin with keywords and do not end with semicolons.
AclassC
is a reference type declared as follows:
classC<T>extendsJ1,...,Jn{_members_}
where the <>-enclosed list of one-or-more type parametersT
is optional. The text“extendsJ1,...,Jn
” is also optional and says that the class extends traitsJ1
…Jn
.The members of a class arefields,constant fields,functions, andmethods. These are accessed or invoked by dereferencing a referenceto aC
instance.
A function or method is invoked on aninstanceofC
, unless the function or method is declaredstatic
.A function or method that is notstatic
is called aninstance function or method.
An instance function or method takes an implicitreceiverparameter, namely, the instance used to access the member. In thespecification and body of an instance function or method, the receiverparameter can be referred to explicitly by the keywordthis
.However, in such places, members ofthis
can also be mentionedwithout any qualification. To illustrate, the qualifiedthis.f
andthe unqualifiedf
refer to the same field of the same object in thefollowing example:
classC{varf:intvarx:intmethodExample()returns(b:bool){varx:int;b:=f==this.f;}}
so the method body always assignstrue
to the out-parameterb
.However, in this example,x
andthis.x
are different becausethe fieldx
is shadowed by the declaration of the local variablex
.There is no semantic difference between qualified andunqualified accesses to the same receiver and member.
AC
instance is created usingnew
. There are three forms ofnew
,depending on whether or not the class declares anyconstructors(seeSection 6.3.2):
c:=newC;c:=newC.Init(args);c:=newC(args);
For a class with no constructors, the first two forms can be used.The first form simply allocates a new instance of aC
object, initializingits fields to values of their respective types (and initializing eachconst
fieldwith a RHS to its specified value). The second form additionally invokesaninitialization method (here, namedInit
) on the newly allocated objectand the given arguments. It is therefore a shorthand for
c:=newC;c.Init(args);
An initialization method is an ordinary method that has no out-parameters andthat modifies no more thanthis
.
For a class that declares one or more constructors, the second and third formsofnew
can be used. For such a class, the second form invokes the indicatedconstructor (here, namedInit
), which allocates and initializes the object.The third form is the same as the second, but invokes theanonymous constructorof the class (that is, a constructor declared with the empty-string name).
The details of constructors and other class members are described inSection 6.3.2.
5.9. Trait types (grammar)
Atrait is an abstract superclass, similar to an “interface” or“mixin”. A trait can beextended only by another trait orby a class (and in the latter case we say that the classimplementsthe trait). More specifically, algebraic datatypes cannot extend traits.4
The declaration of a trait is much like that of a class:
traitJ{_members_}
wheremembers can include fields, constant fields, functions, methods and declarations of nested traits, butno constructor methods. The functions and methods are allowed to bedeclaredstatic
.
A reference typeC
that extends a traitJ
is assignable to a variable oftypeJ
;a value of typeJ
is assignable to a variable of a reference typeC
thatextendsJ
only if the verifier can prove that the reference doesindeed refer to an object of allocated typeC
.The members ofJ
are available as membersofC
. A member inJ
is not allowed to be redeclared inC
,except if the member is a non-static
function or method without abody inJ
. By doing so, typeC
can supply a strongerspecification and a body for the member. There is further discussion onthis point inSection 5.9.2.
new
is not allowed to be used with traits. Therefore, there is noobject whose allocated type is a trait. But there can of course beobjects of a classC
that implement a traitJ
, and a reference tosuch aC
object can be used as a value of typeJ
.
5.9.1. Typeobject
(grammar)
There is a built-in traitobject
that is implicitly extended by all classes and traits.It produces two types: the typeobject?
that is a supertype of allreference types and a subset typeobject
that is a supertype of all non-null reference types.This includes reference types like arrays and iterators that do not permitexplicit extending of traits. The purpose of typeobject
is to enable a uniform treatment ofdynamic frames. In particular, itis useful to keep a ghost field (typically namedRepr
for“representation”) of typeset<object>
.
It serves no purpose (but does no harm) to explicitly list the traitobject
asan extendee in a class or trait declaration.
Traitsobject?
andobject
contain no members.
The dynamic allocation of objects is done usingnewC
…, whereC
is the name of a class. The nameC
is not allowed to be a trait, except that it is allowed to beobject
. The constructionnewobject
allocates a new object (of an unspecified class type). The construction can be used to create unique references, where no other properties of those references are needed.(newobject?
makes no sense; always usenewobject
instead because the result ofnew
is always non-null.)
5.9.2. Inheritance
The purpose of traits is to be able to express abstraction: a traitencapsulates a set of behaviors; classes and traits that extend itinherit those behaviors, perhaps specializing them.
A trait or class may extend multiple other traits.The traits syntactically listed in a trait or class’sextends
clauseare called itsdirect parents; thetransitive parents of a trait or classare its direct parents, the transitive parents of its direct parents, andtheobject
trait (if it is not itselfobject
).These are sets of traits, in that it does not matter ifthere are repetitions of a given trait in a class or trait’s direct ortransitive parents. However, if a trait with type parameters is repeated,it must have the same actual type parameters in each instance.Furthermore, a trait may not be in its own set of transitive parents; that is,the graph of traits connected by the directedextends relationship may nothave any cycles.
A class or trait inherits (as if they are copied) all the instance membersof its transitive parents. However, since names may not be overloaded inDafny, different members (that is, members with different type signatures)within the set of transitive parents and the class or trait itself must have different names.5This restriction does mean that traits from different sources thatcoincidentally use the same name for different purposes cannot be combinedby being part of the set of transitive parents for some new trait or class.
A declaration of memberC.M
in a class or traitoverrides any other declarationsof the same name (and signature) in a transitive parent.C.M
is then called anoverride; a declaration thatdoes not override anything is called anoriginal declaration.
Static members of a trait may not be redeclared;thus, if there is a body it must be declared in the trait;the compiler will require a body, though the verifier will not.
Where traits within an extension hierarchy do declare instance members with the samename (and thus the same signature), some rules apply. Recall that, for methods,every declaration includes a specification; if no specification is givenexplicitly, a default specification applies. Instance method declarations in traits,however, need not have a body, as a body can be declared in an override.
For a given non-static method M,
- A trait or class may not redeclare M if it has a transitive parent that declares M and provides a body.
- A trait may but need not provide a body if all its transitive parents that declare M do not declare a body.
- A trait or class may not have more than one transitive parent that declares M with a body.
- A class that has one or more transitive parents that declare M without a bodyand no transitive parent that declares M with a body must itself redeclare Mwith a body if it is compiled. (The verifier alone does not require a body.)
- Currently (and under debate), the following restriction applies:if
M
overrides two (or more) declarations,P.M
andQ.M
, then eitherP.M
must overrideQ.M
orQ.M
must overrideP.M
.
The last restriction above is the current implementation. It effectively limitsinheritance of a method M to a single “chain” of declarations and does notpermit mixins.
Each of any method declarations explicitly or implicitlyincludes a specification. In simple cases, those syntactically separatespecifications will be copies of each other (up to renaming to take accountof differing formal parameter names). However they need not be. The rule isthat the specifications of M in a given class or trait must beas strong asM’s specifications in a transitive parent.Hereas strong as means that itmust be permitted to call the subtype’s M in the context of the supertype’s M.Stated differently, where P and C are a parent trait and a child class or trait,respectively, then, under the precondition ofP.M
,
- C.M’s
requires
clause must be implied by P.M’srequires
clause - C.M’s
ensures
clause must imply P.M’sensures
clause - C.M’s
reads
set must be a subset of P.M’sreads
set - C.M’s
modifies
set must be a subset of P.M’smodifies
set - C.M’s
decreases
expression must be smaller than or equal to P.M’sdecreases
expression
Non-static const and field declarations are also inherited from parent traits.These may not be redeclared in extending traits and classes.However, a trait need not initialize a const field with a value.The class that extends a trait that declares such a const field without aninitializer can initialize the field in a constructor.If the declaring trait does givean initial value in the declaration, the extending class or trait may not eitherredeclare the field or give it a value in a constructor.
When names are inherited from multiple traits, they must be different.If two traits declare a common name (even with the same signature),they cannot both be extendees of the same class or trait.
5.9.3. Example of traits
As an example, the following trait represents movable geometric shapes:
traitShape{functionWidth():realreadsthisdecreases1methodMove(dx:real,dy:real)modifiesthismethodMoveH(dx:real)modifiesthis{Move(dx,0.0);}}
MembersWidth
andMove
areabstract (that is, body-less) and canbe implemented differently by different classes that extend the trait.The implementation of methodMoveH
is given in the trait and thusis used by all classes that extendShape
. Here are two classesthat each extendShape
:
classUnitSquareextendsShape{varx:real,y:realfunctionWidth():realdecreases0{// note the empty reads clause1.0}methodMove(dx:real,dy:real)modifiesthis{x,y:=x+dx,y+dy;}}classLowerRightTriangleextendsShape{varxNW:real,yNW:real,xSE:real,ySE:realfunctionWidth():realreadsthisdecreases0{xSE-xNW}methodMove(dx:real,dy:real)modifiesthis{xNW,yNW,xSE,ySE:=xNW+dx,yNW+dy,xSE+dx,ySE+dy;}}
Note that the classes can declare additional members, that they supplyimplementations for the abstract members of the trait,that they repeat the member signatures, and that they are responsiblefor providing their own member specifications that both strengthen thecorresponding specification in the trait and are satisfied by theprovided body.Finally, here is some code that creates two class instances and usesthem together as shapes:
methodm(){varmyShapes:seq<Shape>;varA:=newUnitSquare;myShapes:=[A];vartri:=newLowerRightTriangle;// myShapes contains two Shape values, of different classesmyShapes:=myShapes+[tri];// move shape 1 to the right by the width of shape 0myShapes[1].MoveH(myShapes[0].Width());}
5.10. Array types (grammar)
Dafny supports mutable fixed-lengtharray types of any positivedimension. Array types are (heap-based) reference types.
arrayToken
is a kind ofreserved token,such asarray
,array?
,array2
,array2?
,array3
, and so on (but notarray1
).The type parameter suffix giving the element type can be omitted if the element type can be inferred, though in that case it is likely that thearrayToken
itself is alsoinferrable and can be omitted.
5.10.1. One-dimensional arrays
A one-dimensional array ofn
T
elements may be initialized byany expression that returns a value of the desired type.Commonly,array allocation expressions are used.Some examples are shown here:
typeT(0)methodm(n:nat){vara:=newT[n];varb:array<int>:=newint[8];varc:array:=newT[9];}
The initial values of the array elements are arbitrary values of typeT
. A one-dimensional array value can also be assigned using an ordered list of expressions enclosed in square brackets, as follows:
a:=newT[][t1,t2,t3,t4];
The initialization can also use an expression that returns a function of typenat->T
:
a:=newint[5](i=>i*i);
In fact, the initializer can simply be a function name for the right type of function:
a:=newint[5](Square);
The length of an array is retrieved using the immutableLength
member. For example, the array allocated witha:=newT[n];
satisfies:
a.Length==n
Once an array is allocated, its length cannot be changed.
For any integer-based numerici
in the range0<=i<a.Length
,thearray selection expressiona[i]
retrieves elementi
(thatis, the element preceded byi
elements in the array). Theelement stored ati
can be changed to a valuet
using the arrayupdate statement:
a[i]:=t;
Caveat: The type of the array created bynewT[n]
isarray<T>
. A mistake that is simple to make and that can lead tobefuddlement is to writearray<T>
instead ofT
afternew
.For example, consider the following:
typeT(0)methodm(n:nat){vara:=newarray<T>;varb:=newarray<T>[n];varc:=newarray<T>(n);// resolution errorvard:=newarray(n);// resolution error}
The first statement allocates an array of typearray<T>
, but ofunknown length. The second allocates an array of typearray<array<T>>
of lengthn
, that is, an array that holdsn
values of typearray<T>
. The third statement allocates anarray of typearray<T>
and then attempts to invoke an anonymousconstructor on this array, passing argumentn
. Sincearray
has noconstructors, let alone an anonymous constructor, this statementgives rise to an error. If the type-parameter list is omitted for atype that expects type parameters, Dafny will attempt to fill thesein, so as long as thearray
type parameter can be inferred, it isokay to leave off the “<T>
” in the fourth statement above. However,as with the third statement,array
has no anonymous constructor, soan error message is generated.
5.10.2. Converting arrays to sequences
One-dimensional arrays support operations that convert a stretch ofconsecutive elements into a sequence. For any arraya
of typearray<T>
, integer-based numeric boundslo
andhi
satisfying0<=lo<=hi<=a.Length
, noting that bounds can equal the array’s length,the following operations each yields aseq<T>
:
expression | description |
---|---|
a[lo..hi] | subarray conversion to sequence |
a[lo..] | drop |
a[..hi] | take |
a[..] | array conversion to sequence |
The expressiona[lo..hi]
takes the firsthi
elements of the array,then drops the firstlo
elements thereof and returns what remains asa sequence, with lengthhi-lo
.The other operations are special instances of the first. Iflo
isomitted, it defaults to0
and ifhi
is omitted, it defaults toa.Length
.In the last operation, bothlo
andhi
have been omitted, thusa[..]
returns the sequence consisting of all the array elements ofa
.
The subarray operations are especially useful in specifications. Forexample, the loop invariant of a binary search algorithm that usesvariableslo
andhi
to delimit the subarray where the searchkey
may still be found can be expressed as follows:
key!ina[..lo]&&key!ina[hi..]
Another use is to say that a certain range of array elements have notbeen changed since the beginning of a method:
a[lo..hi]==old(a[lo..hi])
or since the beginning of a loop:
ghostvarprevElements:=a[..];while// ...invarianta[lo..hi]==prevElements[lo..hi]{// ...}
Note that the type ofprevElements
in this example isseq<T>
, ifa
has typearray<T>
.
A final example of the subarray operation lies in expressing that anarray’s elements are a permutation of the array’s elements at thebeginning of a method, as would be done in most sorting algorithms.Here, the subarray operation is combined with the sequence-to-multisetconversion:
multiset(a[..])==multiset(old(a[..]))
5.10.3. Multi-dimensional arrays
An array of 2 or more dimensions is mostly like a one-dimensionalarray, except thatnew
takes more length arguments (one for eachdimension), and the array selection expression and the array updatestatement take more indices. For example:
matrix:=newT[m,n];matrix[i,j],matrix[x,y]:=matrix[x,y],matrix[i,j];
create a 2-dimensional array whose dimensions have lengthsm
andn
, respectively, and then swaps the elements ati,j
andx,y
.The type ofmatrix
isarray2<T>
, and similarly forhigher-dimensional arrays (array3<T>
,array4<T>
, etc.). Note,however, that there is no typearray0<T>
, and what could have beenarray1<T>
is actually named justarray<T>
. (Accordingly,array0
andarray1
are justnormal identifiers, not type names.)
Thenew
operation above requiresm
andn
to be non-negativeinteger-based numerics. These lengths can be retrieved using theimmutable fieldsLength0
andLength1
. For example, the followingholds for the array created above:
matrix.Length0==m&&matrix.Length1==n
Higher-dimensional arrays are similar (Length0
,Length1
,Length2
, …). The array selection expression and array updatestatement require that the indices are in bounds. For example, theswap statement above iswell-formed only if:
0<=i<matrix.Length0&&0<=j<matrix.Length1&&0<=x<matrix.Length0&&0<=y<matrix.Length1
In contrast to one-dimensional arrays, there is no operation toconvert stretches of elements from a multi-dimensional array to asequence.
There is however syntax to create a multi-dimensional array valueusing a function: seeSection 9.16.
5.11. Iterator types (grammar)
SeeSection 7.5 for a description of iterator specifications.
Aniterator provides a programming abstraction for writing code thatiteratively returns elements. These CLU-style iterators areco-routines in the sense that they keep track of their own programcounter and control can be transferred into and out of the iteratorbody.
An iterator is declared as follows:
iteratorIter<T>(_in-params_)yields(_yield-params_)_specification_{_body_}
whereT
is a list of type parameters (as usual, if there are no typeparameters, “<T>
” is omitted). This declaration gives rise to areference type with the same name,Iter<T>
. In the signature,in-parameters and yield-parameters are the iterator’s analog of amethod’s in-parameters and out-parameters. The difference is that theout-parameters of a method are returned to a caller just once, whereasthe yield-parameters of an iterator are returned each time the iteratorbody performs ayield
. The body consists of statements, like in amethod body, but with the availability also ofyield
statements.
From the perspective of an iterator client, theiterator
declarationcan be understood as generating a classIter<T>
with variousmembers, a simplified version of which is described next.
TheIter<T>
class contains an anonymous constructor whose parametersare the iterator’s in-parameters:
predicateValid()constructor(_in-params_)modifiesthisensuresValid()
An iterator is created usingnew
and this anonymous constructor.For example, an iterator willing to return ten consecutive integersfromstart
can be declared as follows:
iteratorGen(start:int)yields(x:int)yieldensures|xs|<=10&&x==start+|xs|-1{vari:=0;whilei<10invariant|xs|==i{x:=start+i;yield;i:=i+1;}}
An instance of this iterator is created using
iter:=newGen(30);
It is used like this:
methodMain(){vari:=newGen(30);whiletrueinvarianti.Valid()&&fresh(i._new)decreases10-|i.xs|{varm:=i.MoveNext();if(!m){break;}printi.x;}}
The predicateValid()
says when the iterator is in a state where onecan attempt to compute more elements. It is a postcondition of theconstructor and occurs in the specification of theMoveNext
member:
methodMoveNext()returns(more:bool)requiresValid()modifiesthisensuresmore==>Valid()
Note that the iterator remains valid as long asMoveNext
returnstrue
. OnceMoveNext
returnsfalse
, theMoveNext
method can nolonger be called. Note, the client is under no obligation to keepcallingMoveNext
until it returnsfalse
, and the body of theiterator is allowed to keep returning elements forever.
The in-parameters of the iterator are stored in immutable fields ofthe iterator class. To illustrate in terms of the example above, theiterator classGen
contains the following field:
conststart:int
The yield-parameters also result in members of the iterator class:
varx:int
These fields are set by theMoveNext
method. IfMoveNext
returnstrue
, the latest yield values are available in these fields and theclient can read them from there.
To aid in writing specifications, the iterator class also containsghost members that keep the history of values returned byMoveNext
. The names of these ghost fields follow the names of theyield-parameters with an “s
” appended to the name (to suggestplural). Name checking rules make sure these names do not give riseto ambiguities. The iterator class forGen
above thus contains:
ghostvarxs:seq<int>
These history fields are changed automatically byMoveNext
, but arenot assignable by user code.
Finally, the iterator class contains some special fields for use inspecifications. In particular, the iterator specification isrecorded in the following immutable fields:
ghostvar_reads:set<object>ghostvar_modifies:set<object>ghostvar_decreases0:T0ghostvar_decreases1:T1// ...
where there is a_decreases(
i):T(
i)
field for eachcomponent of the iterator’sdecreases
clause.6In addition, there is a field:
ghostvar_new:set<object>;
to which any objects allocated on behalf of the iterator body areadded. The iterator body is allowed to remove elements from the_new
set, but cannot by assignment to_new
add any elements.
Note, in the precondition of the iterator, which is to hold uponconstruction of the iterator, the in-parameters are indeedin-parameters, not fields ofthis
.
reads
clauses on iterators have a different meaning than they do on functions and methods.Iterators may read any memory they like, but because arbitrary code may be executedwhenever theyyield
control, they need to declare what memory locations must not be modifiedby other code in order to maintain correctness.The contents of an iterator’sreads
clauses become part of thereads
clauseof the implicitly createdValid()
predicate.This means if client code modifies any of this state,it will not be able to establish the precondition for the iterator’sMoveNext()
method,and hence the iterator body will never resume if this state is modified.
It is regrettably tricky to use iterators. The language reallyought to have aforeach
statement to make this easier.Here is an example showing a definition and use of an iterator.
iteratorIter<T(0)>(s:set<T>)yields(x:T)yieldensuresxins&&x!inxs[..|xs|-1]ensuress==setz|zinxs{varr:=s;while(r!={})invariantr!!setz|zinxsinvariants==r+setz|zinxs{vary:|yinr;asserty!inxs;r,x:=r-{y},y;asserty!inxs;yield;asserty==xs[|xs|-1];// a lemma to help prove loop invariant}}methodUseIterToCopy<T(0)>(s:set<T>)returns(t:set<T>)ensuress==t{t:={};varm:=newIter(s);while(true)invariantm.Valid()&&fresh(m._new)invariantt==setz|zinm.xsdecreasess-t{varmore:=m.MoveNext();if(!more){break;}t:=t+{m.x};}}
The design of iterators isunder discussion and may change.
5.12. Arrow types (grammar)
Examples:
(int)->int(bool,int)~>bool()-->object?
Functions are first-class values in Dafny. The types of function valuesare calledarrow types (aka,function types).Arrow types have the form(TT)~>U
whereTT
is a (possibly empty)comma-delimited list of types andU
is a type.TT
is called the function’sdomain type(s) andU
is itsrange type. For example, the type of a function
functionF(x:int,arr:array<bool>):realrequiresx<1000readsarr
is(int,array<bool>)~>real
.
As seen in the example above, the functions that are values of a type(TT)~>U
can have a precondition (as indicated by therequires
clause)and can read values in the heap (as indicated by thereads
clause).As described inSection 5.6.3.3,
- the subset type
(TT)-->U
denotes partial (but heap-independent) functions - and the subset type
(TT)->U
denotes total functions.
A function declared without areads
clause is known by the typechecker to be a partial function. For example, the type of
functionF(x:int,b:bool):realrequiresx<1000
is(int,bool)-->real
.Similarly, a function declared with neither areads
clause nor arequires
clause is known by the type checker to be a total function.For example, the type of
functionF(x:int,b:bool):real
is(int,bool)->real
.In addition to functions declared by name, Dafny also supports anonymousfunctions by means oflambda expressions (seeSection 9.13).
To simplify the appearance of the basic case where a function’sdomain consists of a list of exactly one non-function, non-tuple type, the parentheses aroundthe domain type can be dropped in this case. For example, you maywrite justT->U
for a total arrow type.This innocent simplification requires additional explanation in thecase where that one type is a tuple type, since tuple types are alsowritten with enclosing parentheses.If the function takes a single argument that is a tuple, an additionalset of parentheses is needed. For example, the function
functionG(pair:(int,bool)):real
has type((int,bool))->real
. Note the necessary doubleparentheses. Similarly, a function that takes no arguments isdifferent from one that takes a 0-tuple as an argument. For instance,the functions
functionNoArgs():realfunctionZ(unit:()):real
have types()->real
and(())->real
, respectively.
The function arrows are right associative.For example,A->B->C
meansA->(B->C)
, whereasthe other association requires explicit parentheses:(A->B)->C
.As another example,A->B-->C~>D
meansA->(B-->(C~>D))
.
Note that the receiver parameter of a named function is not part ofthe type. Rather, it is used when looking up the function and canthen be thought of as being captured into the function definition.For example, suppose functionF
above is declared in a classC
andthatc
references an object of typeC
; then, the following is typecorrect:
varf:(int,bool)->real:=c.F;
whereas it would have been incorrect to have written something like:
varf':(C,int,bool)->real:=F;// not correct
The arrow types themselves do not divide a function’s parameters into ghostversus non-ghost. Instead, a function used as a first-class value isconsidered to be ghost if either the function or any of its argumentsis ghost. The following example program illustrates:
functionF(x:int,ghosty:int):int{x}methodExample(){ghostvarf:(int,int)->int;varg:(int,int)->int;varh:(int)->int;varx:int;f:=F;x:=F(20,30);g:=F;// error: tries to assign ghost to non-ghosth:=F;// error: wrong arity (and also tries to assign ghost to non-ghost)}
In addition to its type signature, each function value has three properties,described next.
Every function implicitly takes the heap as an argument. No functionever depends on theentire heap, however. A property of thefunction is its declared upper bound on the set of heap locations itdepends on for a given input. This lets the verifier figure out thatcertain heap modifications have no effect on the value returned by acertain function. For a functionf:T~>U
and a valuet
of typeT
, the dependency set is denotedf.reads(t)
and has typeset<object>
.
The second property of functions stems from the fact that every functionis potentiallypartial. In other words, a property of a function is itsprecondition. For a functionf:T~>U
, the precondition off
for aparameter valuet
of typeT
is denotedf.requires(t)
and has typebool
.
The third property of a function is more obvious—the function’sbody. For a functionf:T~>U
, the value that the function yieldsfor an inputt
of typeT
is denotedf(t)
and has typeU
.
Note thatf.reads
andf.requires
are themselves functions.Without loss of generality, supposef
is defined as:
functionf<T,U>(x:T):UreadsR(x)requiresP(x){body(x)}
whereP
,R
, andbody
are declared as:
predicateP<T>(x:T)functionR<T>(x:T):set<object>functionbody<T,U>(x:T):U
Then,f.reads
is a function of typeT~>set<object?>
whosereads
andrequires
properties are given by the definition:
functionf.reads<T>(x:T):set<object>readsR(x)requiresP(x){R(x)}
f.requires
is a function of typeT~>bool
whosereads
andrequires
properties are given by the definition:
predicatef_requires<T>(x:T)requirestruereadsifP(x)thenR(x)else*{P(x)}
where*
is a notation to indicate that any memory location canbe read, but is not valid Dafny syntax.
In these examples, iff
instead had typeT-->U
orT->U
,then the type off.reads
isT->set<object?>
and the typeoff.requires
isT->bool
.
Dafny also supports anonymous functions by means oflambda expressions. SeeSection 9.13.
5.13. Tuple types
TupleType = "(" [ [ "ghost" ] Type { "," [ "ghost" ] Type } ] ")"
Dafny builds in record types that correspond to tuples and gives thesea convenient special syntax, namely parentheses. For example, for whatmight have been declared as
datatypePair<T,U>=Pair(0:T,1:U)
Dafny provides the type(T,U)
and the constructor(t,u)
, asif the datatype’s name were “” (i.e., an empty string)and its type arguments are given inround parentheses, and as if the constructor name were the empty string.Note thatthe destructor names are0
and1
, which are legal identifier namesfor members. For an example showing the use of a tuple destructor, hereis a property that holds of 2-tuples (that is,pairs):
methodm(){assert(5,true).1==true;}
Dafny declaresn-tuples wheren is 0 or 2 or more. There are no1-tuples, since parentheses around a single type or a single value haveno semantic meaning. The 0-tuple type,()
, is often known as theunit type and its single value, also written()
, is known asunit.
Theghost
modifier can be used to mark tuple components as being used for specification only:
constpair:(int,ghostint):=(1,ghost2)
5.14. Algebraic Datatypes (grammar)
Dafny offers two kinds of algebraic datatypes, those definedinductively (withdatatype
) and those defined coinductively (withcodatatype
).The salient property ofevery datatype is that each value of the type uniquely identifies oneof the datatype’s constructors and each constructor is injective inits parameters.
5.14.1. Inductive datatypes
The values of inductive datatypes can be seen as finite trees wherethe leaves are values of basic types, numeric types, reference types,coinductive datatypes, or arrow types. Indeed, values ofinductive datatypes can be compared using Dafny’s well-founded<
ordering.
An inductive datatype is declared as follows:
datatypeD<T>=_Ctors_
whereCtors is a nonempty|
-separated list of(datatype) constructors for the datatype. Each constructor has theform:
C(_params_)
whereparams is a comma-delimited list of types, optionallypreceded by a name for the parameter and a colon, and optionallypreceded by the keywordghost
. If a constructor has no parameters,the parentheses after the constructor name may be omitted. If noconstructor takes a parameter, the type is usually called anenumeration; for example:
datatypeFriends=Agnes|Agatha|Jermaine|Jack
For every constructorC
, Dafny defines adiscriminatorC?
, whichis a member that returnstrue
if and only if the datatype value hasbeen constructed usingC
. For every named parameterp
of aconstructorC
, Dafny defines adestructorp
, which is a memberthat returns thep
parameter from theC
call used to construct thedatatype value; its use requires thatC?
holds. For example, forthe standardList
type
datatypeList<T>=Nil|Cons(head:T,tail:List<T>)
the following holds:
methodm(){assertCons(5,Nil).Cons?&&Cons(5,Nil).head==5;}
Note that the expression
Cons(5,Nil).tail.head
is notwell-formed by itself, sinceCons(5,Nil).tail
does not necessarily satisfyCons?
.
A constructor can have the same name asthe enclosing datatype; this is especially useful forsingle-constructor datatypes, which are often calledrecord types. For example, a record type for black-and-white pixelsmight be represented as follows:
datatypePixel=Pixel(x:int,y:int,on:bool)
To call a constructor, it is usually necessary only to mention thename of the constructor, but if this is ambiguous, it is alwayspossible to qualify the name of constructor by the name of thedatatype. For example,Cons(5,Nil)
above can be written
List.Cons(5,List.Nil)
As an alternative to calling a datatype constructor explicitly, adatatype value can be constructed as a change in one parameter from agiven datatype value using thedatatype update expression. For anyd
whose type is a datatype that includes a constructorC
that hasa parameter (destructor) namedf
of typeT
, and any expressiont
of typeT
,
d.(f:=t)
constructs a value liked
but whosef
parameter ist
. Theoperation requires thatd
satisfiesC?
. For example, thefollowing equality holds:
methodm(){assertCons(4,Nil).(tail:=Cons(3,Nil))==Cons(4,Cons(3,Nil));}
The datatype update expression also accepts multiple fieldnames, provided these are distinct. For example, a node of someinductive datatype for trees may be updated as follows:
node.(left:=L,right:=R)
The operator<
is defined for two operands of the same datataype.It meansis properly contained in. For example, in the code
datatypeX=T(t:X)|I(i:int)methodcomp(){varx:=T(I(0));vary:=I(0);varz:=I(1);assertx.t<x;asserty<x;assert!(x<x);assertz<x;// FAILS}
x
is a datatype value that holds aT
variant, which holds aI
variant, which holds an integer0
.The valuex.t
is a portion of the datatype structure denoted byx
, sox.t<x
is true.Datatype values are immutable mathematical values, so the value ofy
is identical to the value ofx.t
, soy<x
is true also, even thoughy
is constructed from the ground up, rather than asa portion ofx
. However,z
is different than eithery
orx.t
and consequentlyz<x
is not provable.Furthermore,<
does not include==
, sox<x
is false.
Note that only<
is defined; not<=
or>
or>=
.
Also,<
is underspecified. With the above code, one can prove neitherz<x
nor!(z<x)
and neitherz<y
nor!(z<y)
. In each pair, though, one or the other is true, so(z<x)||!(z<x)
is provable.
5.14.2. Coinductive datatypes
Whereas Dafny insists that there is a way to construct every inductivedatatype value from the ground up, Dafny also supportscoinductive datatypes, whose constructors are evaluated lazily, andhence the language allows infinite structures.A coinductive datatype is declaredusing the keywordcodatatype
; other than that, it is declared andused like an inductive datatype.
For example,
codatatypeIList<T>=Nil|Cons(head:T,tail:IList<T>)codatatypeStream<T>=More(head:T,tail:Stream<T>)codatatypeTree<T>=Node(left:Tree<T>,value:T,right:Tree<T>)
declare possibly infinite lists (that is, lists that can be eitherfinite or infinite), infinite streams (that is, lists that are alwaysinfinite), and infinite binary trees (that is, trees where everybranch goes on forever), respectively.
The paperCo-induction Simply, by Leino andMoskal[@LEINO:Dafny:Coinduction], explains Dafny’s implementation andverification of coinductive types. We capture the key features from thatpaper in the following section but the reader is referred to that paper for morecomplete details and to supply bibliographic references that areomitted here.
5.14.3. Coinduction
Mathematical induction is a cornerstone of programming and programverification. It arises in data definitions (e.g., some algebraic datastructures can be described using induction), it underlies programsemantics (e.g., it explains how to reason about finite iteration andrecursion), and it is used in proofs (e.g., supporting lemmas aboutdata structures use inductive proofs). Whereas induction deals withfinite things (data, behavior, etc.), its dual, coinduction, deals withpossibly infinite things. Coinduction, too, is important in programmingand program verification: it arises in data definitions (e.g., lazydata structures), semantics (e.g., concurrency), and proofs (e.g.,showing refinement in a coinductive big-step semantics). It is thusdesirable to have good support for both induction and coinduction in asystem for constructing and reasoning about programs.
Co-datatypes and co-recursive functions make it possible to use lazilyevaluated data structures (like in Haskell or Agda).Greatest predicates,defined by greatest fix-points, let programs state properties of suchdata structures (as can also be done in, for example, Coq). For thepurpose of writing coinductive proofs in the language, we introducegreatest and least lemmas. A greatest lemma invokes the coinduction hypothesismuch like an inductive proof invokes the induction hypothesis. Underneaththe hood, our coinductive proofs are actually approached via induction:greatest and least lemmas provide a syntactic veneer around this approach.
The following example gives a taste of how the coinductive features inDafny come together to give straightforward definitions of infinitematters.
// infinite streamscodatatypeIStream<T>=ICons(head:T,tail:IStream<T>)// pointwise product of streamsfunctionMult(a:IStream<int>,b:IStream<int>):IStream<int>{ICons(a.head*b.head,Mult(a.tail,b.tail))}// lexicographic order on streamsgreatestpredicateBelow(a:IStream<int>,b:IStream<int>){a.head<=b.head&&((a.head==b.head)==>Below(a.tail,b.tail))}// a stream is Below its SquaregreatestlemmaTheorem_BelowSquare(a:IStream<int>)ensuresBelow(a,Mult(a,a)){asserta.head<=Mult(a,a).head;ifa.head==Mult(a,a).head{Theorem_BelowSquare(a.tail);}}// an incorrect property and a bogus proof attemptgreatestlemmaNotATheorem_SquareBelow(a:IStream<int>)ensuresBelow(Mult(a,a),a)// ERROR{NotATheorem_SquareBelow(a);}
The example defines a typeIStream
of infinite streams, with constructorICons
anddestructorshead
andtail
. FunctionMult
performs pointwisemultiplication on infinite streams of integers, defined using aco-recursive call (which is evaluated lazily). Greatest predicateBelow
isdefined as a greatest fix-point, which intuitively means that theco-predicate will take on the value true if the recursion goes on foreverwithout determining a different value. The greatest lemma states the theoremBelow(a,Mult(a,a))
. Its body gives the proof, where the recursiveinvocation of the co-lemma corresponds to an invocation of thecoinduction hypothesis.
The proof of the theorem stated by the first co-lemma lendsitself to the following intuitive reading: To prove thata
is belowMult(a,a)
, check that their heads are ordered and, if the heads areequal, also prove that the tails are ordered. The second co-lemma statesa property that does not always hold; the verifier is not fooled by thebogus proof attempt and instead reports the property as unproved.
We argue that these definitions in Dafny are simple enough to level theplaying field between induction (which is familiar) and coinduction(which, despite being the dual of induction, is often perceived as eerilymysterious). Moreover, the automation provided by our SMT-based verifierreduces the tedium in writing coinductive proofs. For example, itverifiesTheorem_BelowSquare
from the program text given above—noadditional lemmas or tactics are needed. In fact, as a consequence of theautomatic-induction heuristic in Dafny, the verifier willautomatically verifyTheorem_BelowSquare
even given an empty body.
Just like there are restrictions on when aninductive hypothesis can beinvoked, there are restrictions on how acoinductive hypothesis can beused. These are, of course, taken into consideration by Dafny’s verifier.For example, as illustrated by the second greatest lemma above, invoking thecoinductive hypothesis in an attempt to obtain the entire proof goal isfutile. (We explain how this works inthe section about greatest lemmas) Our initial experiencewith coinduction in Dafny shows it to provide an intuitive, low-overheaduser experience that compares favorably to even the best of today’sinteractive proof assistants for coinduction. In addition, thecoinductive features and verification support in Dafny have otherpotential benefits. The features are a stepping stone for verifyingfunctional lazy programs with Dafny. Coinductive features have alsoshown to be useful in defining language semantics, as needed to verifythe correctness of a compiler, so this opens the possibility thatsuch verifications can benefit from SMT automation.
5.14.3.1. Well-Founded Function/Method Definitions
The Dafny programming language supports functions and methods. Afunctionin Dafny is a mathematical function (i.e., it is well-defined,deterministic, and pure), whereas amethod is a body of statements thatcan mutate the state of the program. A function is defined by its givenbody, which is an expression. To ensure that function definitionsare mathematically consistent, Dafny insists that recursive calls be well-founded,enforced as follows: Dafny computes the call graph of functions. The strongly connectedcomponents within it areclusters of mutually recursive definitions; the clusters are arranged ina DAG. This stratifies the functions so that a call from one cluster in the DAG to alower cluster is allowed arbitrarily. For an intra-cluster call, Dafny prescribes a proofobligation that is taken through the program verifier’s reasoning engine. Semantically,each function activation is labeled by arank—a lexicographic tuple determinedby evaluating the function’sdecreases
clause upon invocation of the function. Theproof obligation for an intra-cluster call is thus that the rank of the callee is strictly less(in a language-defined well-founded relation) than the rank of the caller. Becausethese well-founded checks correspond to proving termination of executable code, wewill often refer to them as “termination checks”. The same process applies to methods.
Lemmas in Dafny are commonly introduced by declaring a method, statingthe property of the lemma in thepostcondition (keywordensures
) ofthe method, perhaps restricting the domain of the lemma by also giving aprecondition (keywordrequires
), and using the lemma by invokingthe method. Lemmas are stated, used, and proved as methods, butsince they have no use at run time, such lemma methods are typicallydeclared asghost, meaning that they are not compiled into code. Thekeywordlemma
introduces such a method. Control flow statementscorrespond to proof techniques—case splits are introduced with ifstatements, recursion and loops are used for induction, and method callsfor structuring the proof. Additionally, the statement:
forallx|P(x){Lemma(x);}
is used to invokeLemma(x)
on allx
for whichP(x)
holds. IfLemma(x)
ensuresQ(x)
, then the forall statement establishes
forallx::P(x)==>Q(x).
5.14.3.2. Defining Coinductive Datatypes
Each value of an inductive datatype is finite, in the sense that it canbe constructed by a finite number of calls to datatype constructors. Incontrast, values of a coinductive datatype, or co-datatype for short,can be infinite. For example, a co-datatype can be used to representinfinite trees.
Syntactically, the declaration of a co-datatype in Dafny looks like thatof a datatype, giving prominence to the constructors (following Coq). Thefollowing example defines a co-datatype Stream of possiblyinfinite lists.
codatatypeStream<T>=SNil|SCons(head:T,tail:Stream)functionUp(n:int):Stream<int>{SCons(n,Up(n+1))}functionFivesUp(n:int):Stream<int>decreases4-(n-1)%5{if(n%5==0)thenSCons(n,FivesUp(n+1))elseFivesUp(n+1)}
Stream
is a coinductive datatype whose values are possibly infinitelists. FunctionUp
returns a stream consisting of all integers upwardsofn
andFivesUp
returns a stream consisting of all multiples of 5upwards ofn
. The self-call inUp
and the first self-call inFivesUp
sit in productive positions and are therefore classified as co-recursivecalls, exempt from termination checks. The second self-call inFivesUp
isnot in a productive position and is therefore subject to terminationchecking; in particular, each recursive call must decrease the rankdefined by thedecreases
clause.
Analogous to the common finite list datatype,Stream
declares twoconstructors,SNil
andSCons
. Values can be destructed using matchexpressions and statements. In addition, like for inductive datatypes,each constructorC
automatically gives rise to a discriminatorC?
andeach parameter of a constructor can be named in order to introduce acorresponding destructor. For example, ifxs
is the streamSCons(x,ys)
, thenxs.SCons?
andxs.head==x
hold. In contrastto datatype declarations, there is no grounding check forco-datatypes—since a codatatype admits infinite values, the type isnevertheless inhabited.
5.14.3.3. Creating Values of Co-datatypes
To define values of co-datatypes, one could imagine a “co-function”language feature: the body of a “co-function” could include possiblynever-ending self-calls that are interpreted by a greatest fix-pointsemantics (akin to aCoFixpoint in Coq). Dafny uses a different design:it offers only functions (not “co-functions”), but it classifies eachintra-cluster call as eitherrecursive orco-recursive. Recursive callsare subject to termination checks. Co-recursive calls may benever-ending, which is what is needed to define infinite values of aco-datatype. For example, functionUp(n)
in the preceding example is defined as thestream of numbers fromn
upward: it returns a stream that starts withn
and continues as the co-recursive callUp(n+1)
.
To ensure that co-recursive calls give rise to mathematically consistent definitions,they must occur only in productive positions. This says that it must be possible to determineeach successive piece of a co-datatype value after a finite amount of work. Thiscondition is satisfied if every co-recursive call is syntactically guarded by a constructorof a co-datatype, which is the criterion Dafny uses to classify intra-cluster calls as beingeither co-recursive or recursive. Calls that are classified as co-recursive are exempt fromtermination checks.
A consequence of the productivity checks and termination checks is that, even in theabsence of talking about least or greatest fix-points of self-calling functions, all functionsin Dafny are deterministic. Since there cannot be multiple fix-points,the language allows one function to be involved in both recursive and co-recursive calls,as we illustrate by the functionFivesUp
.
5.14.3.4. Co-Equality
Equality between two values of a co-datatype is a built-in co-predicate.It has the usual equality syntaxs==t
, and the corresponding prefixequality is writtens==#[k]t
. And similarly fors!=t
ands!=#[k]t
.
5.14.3.5. Greatest predicates
Determining properties of co-datatype values may require an infinitenumber of observations. To that end, Dafny providesgreatest predicateswhich are function declarations that use thegreatestpredicate
keyword phrase.Self-calls to a greatest predicate need not terminate. Instead, the valuedefined is the greatest fix-point of the given recurrence equations.Continuing the preceding example, the following code defines agreatest predicate that holds for exactly those streams whose payload consistssolely of positive integers. The greatest predicate definition implicitly alsogives rise to a corresponding prefix predicate,Pos#
. The syntax forcalling a prefix predicate sets apart the argument that specifies theprefix length, as shown in the last line; for this figure, we took theliberty of making up a coordinating syntax for the signature of theautomatically generated prefix predicate (which is not part ofDafny syntax).
greatestpredicatePos[nat](s:Stream<int>){matchscaseSNil=>truecaseSCons(x,rest)=>x>0&&Pos(rest)}
The following code is automatically generated by the Dafny compiler:
predicatePos#[_k:nat](s:Stream<int>)decreases_k{if_k==0thentrueelsematchscaseSNil=>truecaseSCons(x,rest)=>x>0&&Pos#[_k-1](rest)}
Some restrictions apply. To guarantee that the greatest fix-point alwaysexists, the (implicit functor defining the) greatest predicate must bemonotonic. This is enforced by a syntactic restriction on the form of thebody of greatest predicates: after conversion to negation normal form (i.e.,pushing negations down to the atoms), intra-cluster calls ofgreatest predicates must appear only inpositive positions—that is, they mustappear as atoms and must not be negated. Additionally, to guaranteesoundness later on, we require that they appear incontinouspositions—that is, in negation normal form, when they appear underexistential quantification, the quantification needs to be limited to afinite range7. Since the evaluation of a greatest predicate might notterminate, greatest predicates are always ghost. There is also a restriction onthe call graph that a cluster containing a greatest predicate must contain onlygreatest predicates, no other kinds of functions.
extreme predicates and lemmas, one in which_k
has typenat
and one in which it has typeORDINAL
(the default). The continuous restriction applies only when_k
isnat
. Also, higher-order function support in Dafny is rather modest and typical reasoning patterns do not involve them, so this restriction is not as limiting as it would have been in, e.g., Coq.
Agreatest predicate declaration ofP
defines not just a greatest predicate, butalso a correspondingprefix predicateP#
. A prefix predicate is afinite unrolling of a co-predicate. The prefix predicate is constructedfrom the co-predicate by
adding a parameter
_k
of typenat
to denote the prefix length,adding the clause
decreases_k;
to the prefix predicate (thegreatest predicate itself is not allowed to have a decreases clause),replacing in the body of the greatest predicate every intra-clustercall
Q(args)
to a greatest predicate by a callQ#[_k-1](args)
to the corresponding prefix predicate, and thenprepending the body with
if_k==0thentrueelse
.
For example, for greatest predicatePos
, the definition of the prefixpredicatePos#
is as suggested above. Syntactically, the prefix-lengthargument passed to a prefix predicate to indicate how many times tounroll the definition is written in square brackets, as inPos#[k](s)
.In the Dafny grammar this is called aHashCall
. The definition ofPos#
is available only at clusters strictly higher than that ofPos
;that is,Pos
andPos#
must not be in the same cluster. In otherwords, the definition ofPos
cannot depend onPos#
.
5.14.3.6. Coinductive Proofs
From what we have said so far, a program can make use of properties ofco-datatypes. For example, a method that declaresPos(s)
as aprecondition can rely on the streams
containing only positive integers.In this section, we consider how such properties are established in thefirst place.
5.14.3.6.1. Properties of Prefix Predicates
Among other possible strategies for establishing coinductive propertieswe take the time-honored approach of reducing coinduction toinduction. More precisely, Dafny passes to the SMT solver anassumptionD(P)
for every greatest predicateP
, where:
D(P)=forallx•P(x)<==>forallk•P#[k](x)
In other words, a greatest predicate is true iff its corresponding prefixpredicate is true for all finite unrollings.
In Sec. 4 of the paper [Co-induction Simply] a soundness theorem of suchassumptions is given, provided the greatest predicates meet the continousrestrictions. An example proof ofPos(Up(n))
for everyn>0
isshown here:
lemmaUpPosLemma(n:int)requiresn>0ensuresPos(Up(n)){forallk|0<=k{UpPosLemmaK(k,n);}}lemmaUpPosLemmaK(k:nat,n:int)requiresn>0ensuresPos#[k](Up(n))decreasesk{ifk!=0{// this establishes Pos#[k-1](Up(n).tail)UpPosLemmaK(k-1,n+1);}}
The lemmaUpPosLemma
provesPos(Up(n))
for everyn>0
. We firstshowPos#[k](Up(n))
, forn>0
and an arbitraryk
, and then usethe forall statement to showforallk•Pos#[k](Up(n))
. Finally, the axiomD(Pos)
is used (automatically) to establish the greatest predicate.
5.14.3.6.2. Greatest lemmas
As we just showed, with help of theD
axiom we can now prove agreatest predicate by inductively proving that the corresponding prefixpredicate holds for all prefix lengthsk
. In this section, we introducegreatest lemma declarations, which bring about two benefits. The first benefitis that greatest lemmas are syntactic sugar and reduce the tedium of having towrite explicit quantifications overk
. The second benefit is that, insimple cases, the bodies of greatest lemmas can be understood as coinductiveproofs directly. As an example consider the following greatest lemma.
greatestlemmaUpPosLemma(n:int)requiresn>0ensuresPos(Up(n)){UpPosLemma(n+1);}
This greatest lemma can be understood as follows:UpPosLemma
invokes itselfco-recursively to obtain the proof forPos(Up(n).tail)
(sinceUp(n).tail
equalsUp(n+1)
). The proof glue needed to then concludePos(Up(n))
isprovided automatically, thanks to the power of the SMT-based verifier.
5.14.3.6.3. Prefix Lemmas
To understand why the aboveUpPosLemma
greatest lemma code is a sound proof,let us now describe the details of the desugaring of greatest lemmas. Inanalogy to how agreatest predicate declaration defines both a greatest predicate anda prefix predicate, agreatest lemma declaration defines both a greatest lemma andprefix lemma. In the call graph, the cluster containing a greatest lemma mustcontain only greatest lemmas and prefix lemmas, no other methods or function.By decree, a greatest lemma and its corresponding prefix lemma are alwaysplaced in the same cluster. Both greatest lemmas and prefix lemmas are alwaysghost code.
The prefix lemma is constructed from the greatest lemma by
adding a parameter
_k
of typenat
to denote the prefix length,replacing in the greatest lemma’s postcondition the positive continuousoccurrences of greatest predicates by corresponding prefix predicates,passing in
_k
as the prefix-length argument,prepending
_k
to the (typically implicit)decreases clause of the greatest lemma,replacing in the body of the greatest lemma every intra-cluster call
M(args)
to a greatest lemma by a callM#[_k-1](args)
to thecorresponding prefix lemma, and thenmaking the body’s execution conditional on
_k!=0
.
Note that this rewriting removes all co-recursive calls of greatest lemmas,replacing them with recursive calls to prefix lemmas. These recursivecalls are, as usual, checked to be terminating. We allow the pre-declaredidentifier_k
to appear in the original body of thegreatest lemma.8
We can now think of the body of the greatest lemma as being replaced by aforall call, for everyk , to the prefix lemma. By construction,this new body will establish the greatest lemma’s declared postcondition (onaccount of theD
axiom, and remembering that only the positivecontinuous occurrences of greatest predicates in the greatest lemma’s postconditionare rewritten), so there is no reason for the program verifier to checkit.
The actual desugaring of our greatest lemmaUpPosLemma
is in fact theprevious code for theUpPosLemma
lemma except thatUpPosLemmaK
isnamedUpPosLemma#
and modulo a minor syntactic difference in how thek
argument is passed.
In the recursive call of the prefix lemma, there is a proof obligationthat the prefixlength argument_k-1
is a natural number.Conveniently, this follows from the fact that the body has been wrappedin anif_k!=0
statement. This also means that the postcondition musthold trivially when_k==0
, or else a postcondition violation will bereported. This is an appropriate design for our desugaring, becausegreatest lemmas are expected to be used to establish greatest predicates, whosecorresponding prefix predicates hold trivially when_k=0
. (To proveother predicates, use an ordinary lemma, not a greatest lemma.)
It is interesting to compare the intuitive understanding of thecoinductive proof in using a greatest lemma with the inductive proof in usinga lemma. Whereas the inductive proof is performing proofs for deeperand deeper equalities, the greatest lemma can be understood as producing theinfinite proof on demand.
5.14.3.7. Abstemious and voracious functions
Some functions on codatatypes areabstemious, meaning that they do notneed to unfold a datatype instance very far (perhaps just one destructor call) to prove a relevant property. Knowing this is the case can aid the proofs ofproperties about the function. The attribute{:abstemious}
can be applied toa function definition to indicate this.
TODO: Say more about the effect of this attribute and when it should be applied(and likely, correct the paragraph above).
6. Member declarations
Members are the various kinds of methods, the various kinds of functions, mutable fields,and constant fields. These are usually associated with classes, but they also may bedeclared (with limitations) in traits, newtypes and datatypes (but not in subset types or type synonyms).
6.1. Field Declarations (grammar)
Examples:
classC{varc:int// no initializationghostvar123:bv10// name may be a sequence of digitsvard:nat,e:real// type is required}
A field declaration is not permitted in a value type nor as a member of a module(despite there being an implicit unnamed class).
The field name is either anidentifier (that is not allowed to start with a leading underscore) orsome digits. Digits are used if you want to number your fields, e.g. “0”,“1”, etc. The digits do not denote numbers but sequences of digits,so 0, 00, 0_0 are all different.
A field x of some type T is declared as:
varx:T
A field declaration declares one or more fields of the enclosing class.Each field is a named part of the state of an object of that class. Afield declaration is similar to but distinct from a variable declarationstatement. Unlike for local variables and bound variables, the type isrequired and will not be inferred.
Unlike method and function declarations, a field declarationis not permitted as a member of a module, even though there is an implicit class.Fields can be declared in either an explicitclass or a trait. A class that inherits from multiple traits willhave all the fields declared in any of its parent traits.
Fields that are declared asghost
can only be used in specifications,not in code that will be compiled into executable code.
Fields may not be declared static.
6.2. Constant Field Declarations (grammar)
Examples:
constc:intghostconstd:=5classA{conste:boolstaticconstf:int}
Aconst
declaration declares a name bound to a value,which value is fixed after initialization.
The declaration must either have a type or an initializing expression (or both).If the type is omitted, it is inferred from the initializing expression.
- A const declaration may include the
ghost
,static
, andopaque
modifiers, but noothers. - A const declaration may appear within a module or within any declarationthat may contain members (class, trait, datatype, newtype).
- If it is in a module, it is implicitly
static
, and may not also be declaredstatic
. - If the declaration has an initializing expression that is a ghostexpression, then the ghost-ness of the declaration is inferred; the
ghost
modifier may be omitted. - If the declaration includes the
opaque
modifier, then uses of the declaredvariable know its name and type but not its value. The value can be made known forreasoning purposes by using thereveal statement. - The initialization expression may refer to other constant fields that are in scope and declared eitherbefore or after this declaration, but circular references are not allowed.
6.3. Method Declarations (grammar)
Examples:
methodm(i:int)requiresi>0{}methodp()returns(r:int){r:=0;}methodq()returns(r:int,s:int,t:nat)ensuresr<s<t{r:=0;s:=1;t:=2;}ghostmethodg(){}classA{methodf(){}constructorInit(){}staticmethodg<T>(t:T){}}lemmaL(p:bool)ensuresp||!p{}twostatelemmaTL(p:bool)ensuresp||!p{}leastlemmaLL[nat](p:bool)ensuresp||!p{}greatestlemmaGL(p:bool)ensuresp||!p{}abstractmoduleM{methodm(i:int)}moduleNrefinesM{methodm...{}}
Method declarations include a variety of related types of methods:
- method
- constructor
- lemma
- twostate lemma
- least lemma
- greatest lemma
A method signature specifies the method generic parameters,input parameters and return parameters.The formal parameters are not allowed to haveghost
specifiedifghost
was already specified for the method.Within the body of a method, formal (input) parameters are immutable, that is, they may not be assigned to, though their array elements or fields may beassigned, if otherwise permitted.The out-parameters are mutable and must be assigned in the body of the method.
Anellipsis
is used when a method or function is being redeclaredin a module that refines another module. (cf.Section 10)In that case the signature iscopied from the module that is being refined. This works becauseDafny does not support method or function overloading, so thename of the class method uniquely identifies it without thesignature.
SeeSection 7.2 for a description of the method specification.
Here is an example of a method declaration.
method{:att1}{:att2}M<T1,T2>(a:A,b:B,c:C)returns(x:X,y:Y,z:Z)requiresPremodifiesFrameensuresPostdecreasesRank{Body}
where:att1
and:att2
are attributes of the method,T1
andT2
are type parameters of the method (if generic),a,b,c
are the method’s in-parameters,x,y,z
are themethod’s out-parameters,Pre
is a boolean expression denoting themethod’s precondition,Frame
denotes a set of objects whose fields maybe updated by the method,Post
is a boolean expression denoting themethod’s postcondition,Rank
is the method’s variant function, andBody
is a list of statements that implements the method.Frame
can be a listof expressions, each of which is a set of objects or a single object, thelatter standing for the singleton set consisting of that one object. Themethod’s frame is the union of these sets, plus the set of objectsallocated by the method body. For example, ifc
andd
are parametersof a class typeC
, then
modifies{c,d}modifies{c}+{d}modifiesc,{d}modifiesc,d
all mean the same thing.
If the method is anextreme lemma ( aleast
orgreatest
lemma), then the method signature may also state the type of thek parameter as eithernat
orORDINAL
.These are describedinSection 12.5.3 and subsequent sections.
6.3.1. Ordinary methods
A method can be declared as ghost by preceding the declaration with thekeywordghost
and as static by preceding the declaration with the keywordstatic
.The default is non-static (i.e., instance) for methods declared in a type and non-ghost.An instance method has an implicit receiver parameter,this
.A static method M in a class C can be invoked byC.M(…)
.
An ordinary method is declared with themethod
keyword;the section about constructors explains methods that instead use theconstructor
keyword;the section about lemmas discusses methods that aredeclared with thelemma
keyword. Methods declared with theleastlemma
orgreatestlemma
keyword phrasesare discussed later in the context of extremepredicates (seethe section about greatest lemmas).
A method without a body isabstract. A method is allowed to beabstract under the following circumstances:
- It contains an
{:axiom}
attribute - It contains an
{:extern}
attribute (in this case, to be runnable, the method must have a body in non-Dafny compiled code in the target language.) - It is a declaration in an abstract module.Note that when there is no body, Dafny assumes that theensuresclauses are true without proof.
6.3.2. Constructors
To write structured object-oriented programs, one often relies onobjects being constructed only in certain ways. For this purpose, Dafnyprovidesconstructor (method)s.A constructor is declared with the keywordconstructor
instead ofmethod
; constructors are permitted only in classes.A constructor is allowed to be declared asghost
, in which case itcan only be used in ghost contexts.
A constructor can only be called at the time an object is allocated (seeobject-creation examples below). Moreover, when a class contains aconstructor, every call tonew
for a class must be accompaniedby a call to one of its constructors. A class maydeclare no constructors or one or more constructors.
In general, a constructor is responsible for initializating the instance fields of its class. However, any field that is given aninitializer in its declaration may not be reassigned in the bodyof the constructor.
6.3.2.1. Classes with no explicit constructors
For a class that declares no constructors, an instance of the class iscreated with
c:=newC;
This allocates an object and initializes its fields to values of theirrespective types (and initializes eachconst
field with a RHS to its specifiedvalue). The RHS of aconst
field may depend on otherconst
orvar
fields,but circular dependencies are not allowed.
This simple form ofnew
is allowed only if the class declares no constructors,which is not possible to determine in every scope.It is easy to determine whether or not a class declares any constructors if theclass is declared in the same module that performs thenew
. If the class isdeclared in a different module and that module exports a constructor, then it isalso clear that the class has a constructor (and thus this simple form ofnew
cannot be used). (Note that an export set thatreveals
a classC
also exportsthe anonymous constructor ofC
, if any.)But if the module that declaresC
does not export any constructorsforC
, then callers outside the module do not know whether or notC
has aconstructor. Therefore, this simple form ofnew
is allowed only for classes thatare declared in the same module as the use ofnew
.
The simplenewC
is allowed in ghost contexts. Also, unlike the forms ofnew
that call a constructor or initialization method, it can be used in a simultaneousassignment; for example
c,d,e:=newC,newC,15;
is legal.
As a shorthand for writing
c:=newC;c.Init(args);
whereInit
is an initialization method (see the top ofthe section about class types),one can write
c:=newC.Init(args);
but it is more typical in such a case to declare a constructor for the class.
(The syntactic support for initialization methods is provided for historicalreasons. It may be deprecated in some future version of Dafny. In most cases,a constructor is to be preferred.)
6.3.2.2. Classes with one or more constructors
Like other class members, constructors have names. And like other members,their names must be distinct, even if their signatures are different.Being able to name constructors promotes names likeInitFromList
orInitFromSet
(or justFromList
andFromSet
).Unlike other members, one constructor is allowed to beanonymous;in other words, ananonymous constructor is a constructor whose name isessentially the empty string. For example:
classItem{constructorI(xy:int)// ...constructor(x:int,y:int)// ...}
The named constructor is invoked as
i:=newItem.I(42);
The anonymous constructor is invoked as
m:=newItem(45,29);
dropping the “.
”.
6.3.2.3. Two-phase constructors
The body of a constructor contains two sections,an initialization phase and a post-initialization phase, separated by anew;
statement.If there is nonew;
statement, the entire body is the initialization phase.The initialization phase is intended to initialize field variablesthat were not given values in their declaration; it may not reassignto fields that do have initializers in their declarations.In this phase, uses of the object referencethis
are restricted;a program may usethis
- as the receiver on the LHS,
- as the entire RHS of an assignment to a field of
this
, - and as a member of a set on the RHS that is being assigned to a field of
this
.
Aconst
field with a RHS is not allowed to be assigned anywhere else.Aconst
field without a RHS may be assigned only in constructors, and more preciselyonly in the initialization phase of constructors. During this phase, aconst
fieldmay be assigned more than once; whatever value theconst
field has at the end of theinitialization phase is the value it will have forever thereafter.
For a constructor declared asghost
, the initialization phase is allowed to assignboth ghost and non-ghost fields. For such an object, values of non-ghost fields atthe end of the initialization phase are in effect no longer changeable.
There are no restrictions on expressions or statements in the post-initialization phase.
6.3.3. Lemmas
Sometimes there are steps of logic required to prove a program correct,but they are too complex for Dafny to discover and use on its own. Whenthis happens, we can often give Dafny assistance by providing a lemma.This is done by declaring a method with thelemma
keyword.Lemmas are implicitly ghost methods and theghost
keyword cannotbe applied to them.
Syntactically, lemmas can be placed where ghost methods can be placed, but they serve a significantly different function. First of all, a lemma is forbidden to havemodifies
clause: it may not change anything about even the ghost state; ghost methodsmay havemodifies
clauses and may change ghost (but not non-ghost) state. Furthermore, a lemma is not allowed to allocate any new objects.And a lemma may be used in the program text in places where ghost methods may not,such as within expressions (cf.Section 21.1).
Lemmas may, but typically do not, have out-parameters.
In summary, a lemma states a logical fact, summarizing an inference that the verifiercannot do on its own. Explicitly “calling” a lemma in the program text tells the verifierto use that fact at that location with the actual arguments substituted for the formal parameters. The lemma is proved separately for all cases of its formal parametersthat satisfy the preconditions of the lemma.
For an example, see theFibProperty
lemma inSection 12.5.2.
Seethe Dafny Lemmas tutorialfor more examples and hints for using lemmas.
6.3.4. Two-state lemmas and functions
The heap is an implicit parameter to every function, though a function is only allowedto read those parts of the mutable heap that it admits to in itsreads
clause.Sometimes, it is useful for a function to take two heap parameters, for example, sothe function can return the difference between the value of a field in the two heaps.Such atwo-state function is declared bytwostatefunction
(ortwostatepredicate
,which is the same as atwostatefunction
that returns abool
). A two-state functionis always ghost. It is appropriate to think of these two implicit heap parameters asrepresenting a “current” heap and an “old” heap.
For example, the predicate
classCell{vardata:intconstructor(i:int){data:=i;}}twostatepredicateIncreasing(c:Cell)readsc{old(c.data)<=c.data}
returnstrue
if the value ofc.data
has not been reduced from the old state to thecurrent. Dereferences in the current heap are written as usual (e.g.,c.data
) andmust, as usual, be accounted for in the function’sreads
clause. Dereferences in theold heap are enclosed byold
(e.g.,old(c.data)
), just like when one dereferencesa method’s initial heap. The function is allowed to read anything in the old heap;thereads
clause only declares dependencies on locations in the current heap.Consequently, the frame axiom for a two-state function is sensitive to any changein the old-heap parameter; in other words, the frame axiom says nothing about twoinvocations of the two-state function with different old-heap parameters.
At a call site, the two-state function’s current-heap parameter is always passed inas the caller’s current heap. The two-state function’s old-heap parameter is bydefault passed in as the caller’s old heap (that is, the initial heap if the calleris a method and the old heap if the caller is a two-state function). While there isnever a choice in which heap gets passed as the current heap, the caller can useany preceding heap as the argument to the two-state function’s old-heap parameter.This is done by labeling a state in the caller and passing in the label, just likethis is done with the built-inold
function.
For example, the following assertions all hold:
methodCaller(c:Cell)modifiesc{c.data:=c.data+10;labelL:assertIncreasing(c);c.data:=c.data-2;assertIncreasing(c);assert!Increasing@L(c);}
The first call toIncreasing
usesCaller
’s initial state as the old-heap parameter,and so does the second call. The third call instead uses as the old-heap parameterthe heap at labelL
, which is why the third call returnsfalse
.As shown in the example, an explicitly given old-heap parameter is given afteran@
-sign (which follows the name of the function and any explicitly given typeparameters) and before the open parenthesis (after which the ordinary parameters aregiven).
A two-state function is allowed to be called only from a two-state context, whichmeans a method, a two-state lemma (see below), or another two-state function.Just like a label used with anold
expression, any label used in a call to atwo-state function must denote a program point thatdominates the call. This meansthat any control leading to the call must necessarily have passed through the labeledprogram point.
Any parameter (including the receiver parameter, if any) passed to a two-state functionmust have been allocated already in the old state. For example, the second call toDiff
in methodM
is illegal, sinced
was not allocated on entry toM
:
twostatefunctionDiff(c:Cell,d:Cell):intreadsd{d.data-old(c.data)}methodM(c:Cell){vard:=newCell(10);labelL:ghostvarx:=Diff@L(c,d);ghostvary:=Diff(c,d);// error: d is not allocated in old state}
A two-state function may declare that it only assumes a parameter to be allocatedin the current heap. This is done by preceding the parameter with thenew
modifier,as illustrated in the following example, where the first call toDiffAgain
is legal:
twostatefunctionDiffAgain(c:Cell,newd:Cell):intreadsd{d.data-old(c.data)}methodP(c:Cell){vard:=newCell(10);ghostvarx:=DiffAgain(c,d);ghostvary:=DiffAgain(d,c);// error: d is not allocated in old state}
Atwo-state lemma works in an analogous way. It is a lemma with both a current-heapparameter and an old-heap parameter, it can useold
expressions in itsspecification (including in the precondition) and body, its parameters mayuse thenew
modifier, and the old-heap parameter is by default passed in asthe caller’s old heap, which can be changed by using an@
-parameter.
Here is an example of something useful that can be done with a two-state lemma:
functionSeqSum(s:seq<Cell>):intreadss{ifs==[]then0elses[0].data+SeqSum(s[1..])}twostatelemmaIncSumDiff(s:seq<Cell>)requiresforallc::cins==>Increasing(c)ensuresold(SeqSum(s))<=SeqSum(s){ifs==[]{}else{calc{old(SeqSum(s));==// def. SeqSumold(s[0].data+SeqSum(s[1..]));==// distribute oldold(s[0].data)+old(SeqSum(s[1..]));<={assertIncreasing(s[0]);}s[0].data+old(SeqSum(s[1..]));<={IncSumDiff(s[1..]);}s[0].data+SeqSum(s[1..]);==// def. SeqSumSeqSum(s);}}}
A two-state function can be used as a first-class function value, where the receiver(if any), type parameters (if any), and old-heap parameter are determined at thetime the first-class value is mentioned. While the receiver and type parameters canbe explicitly instantiated in such a use (for example,p.F<int>
for a two-stateinstance functionF
that takes one type parameter), there is currently no syntacticsupport for giving the old-heap parameter explicitly. A caller can workaround this restriction by using (fancy-word alert!) eta-expansion, meaningwrapping a lambda expression around the call, as inx=>p.F<int>@L(x)
.The following example illustrates using such an eta-expansion:
classP{twostatefunctionF<X>(x:X):X}methodEtaExample(p:P)returns(ghostf:int->int){labelL:f:=x=>p.F<int>@L(x);}
6.4. Function Declarations (grammar)
6.4.1. Functions
Examples:
functionf(i:int):real{iasreal}functiong():(int,int){(2,3)}functionh(i:int,k:int):intrequiresi>=0{ifi==0then0else1}
Functions may be declared as ghost. If so, all the formal parameters andreturn values are ghost; if it is not a ghost function, then individual parameters may be declared ghost as desired.
SeeSection 7.3 for a description of the function specification.A Dafny function is a pure mathematical function. It is allowed toread memory that was specified in itsreads
expression but is notallowed to have any side effects.
Here is an example function declaration:
function{:att1}{:att2}F<T1,T2>(a:A,b:B,c:C):TrequiresPrereadsFrameensuresPostdecreasesRank{Body}
where:att1
and:att2
are attributes of the function, if any,T1
andT2
are type parameters of the function (if generic),a,b,c
arethe function’s parameters,T
is the type of the function’s result,Pre
is a boolean expression denoting the function’s precondition,Frame
denotes a set of objects whose fields the function body maydepend on,Post
is a boolean expression denoting the function’spostcondition,Rank
is the function’s variant function, andBody
isan expression that defines the function’s return value. The preconditionallows a function to be partial, that is, the precondition says when thefunction is defined (and Dafny will verify that every use of the functionmeets the precondition).
The postcondition is usually not needed, sincethe body of the function gives the full definition. However, thepostcondition can be a convenient place to declare properties of thefunction that may require an inductive proof to establish, such as whenthe function is recursive. For example:
functionFactorial(n:int):intrequires0<=nensures1<=Factorial(n){ifn==0then1elseFactorial(n-1)*n}
says that the result of Factorial is always positive, which Dafnyverifies inductively from the function body.
Within a postcondition, the result of the function is designated bya call of the function, such asFactorial(n)
in the example above.Alternatively, a name for the function result can be given in the signature,as in the following rewrite of the example above.
functionFactorial(n:int):(f:int)requires0<=nensures1<=f{ifn==0then1elseFactorial(n-1)*n}
Pre v4.0, a function isghost
by default, and cannot be called from non-ghostcode. To make it non-ghost, replace the keywordfunction
with the twokeywords “functionmethod
”. From v4.0 on, a function is non-ghost bydefault. To make it ghost, replace the keywordfunction
with the two keywords “ghostfunction
”.(See the–function-syntax option for a description of the migration path for this change in behavior.}
Like methods, functions can be eitherinstance (which they are by default when declared within a type) orstatic (when the function declaration contains the keywordstatic
or is declared in a module).An instance function, but not a static function, has an implicit receiver parameter,this
.
A static functionF
in a classC
can be invokedbyC.F(…)
. This provides a convenient way to declare a number of helperfunctions in a separate class.
As for methods, a...
is used when declaringa function in a module refinement (cf.Section 10). For example, if moduleM0
declaresfunctionF
, a moduleM1
can be declared to refineM0
andM1
can then refineF
. The refinement function,M1.F
can havea...
which means to copy the signature fromM0.F
. A refinement function can furnish a body for a function(ifM0.F
does not provide one). It can also addensures
clauses.
If a function definition does not have a body, the program that contains it may still be verified.The function itself has nothing to verify.However, any calls of a body-less function are treated as unverified assumptions by the caller,asserting the preconditions and assuming the postconditions.Because body-less functions are unverified assumptions, Dafny will not compile them and will complain if called bydafnytranslate
,dafnybuild
or evendafnyrun
6.4.2. Predicates
A function that returns abool
result is called apredicate. As analternative syntax, a predicate can be declared by replacing thefunction
keyword with thepredicate
keyword and possibly omitting a declaration of thereturn type (if it is not named).
6.4.3. Function-by-method
A function with abymethod
clause declares afunction-by-method.A function-by-method gives a way to implement a(deterministic, side-effect free) function by a method (whose body may benondeterministic and may allocate objects that it modifies). This canbe useful if the best implementation uses nondeterminism (for example,because it uses:|
in a nondeterministic way) in a way that does notaffect the result, or if the implementation temporarily makes use of somemutable data structures, or if the implementation is done with a loop.For example, here is the standard definition of the Fibonacci functionbut with an efficient implementation that uses a loop:
functionFib(n:nat):nat{ifn<2thennelseFib(n-2)+Fib(n-1)}bymethod{varx,y:=0,1;fori:=0toninvariantx==Fib(i)&&y==Fib(i+1){x,y:=y,x+y;}returnx;}
Thebymethod
clause is allowed only for non-ghostfunction
orpredicate
declarations (withouttwostate
,least
, andgreatest
, butpossibly withstatic
); it inherits the in-parameters, attributes, andrequires
anddecreases
clauses of the function. The method also gets one out-parameter, correspondingto the function’s result value (and the name of it, if present). Finally,the method gets an emptymodifies
clause and a postconditionensuresr==F(args)
, wherer
is the name of the out-parameter andF(args)
is the function with its arguments. In other words, the methodbody must compute and return exactly what the function says, and mustdo so without modifying any previously existing heap state.
The function body of a function-by-method is allowed to be ghost, but themethod body must be compilable. In non-ghost contexts, the compiler turns acall of the function-by-method into a call that leads to the method body.
Note, the method body of a function-by-method may containprint
statements.This means that the run-time evaluation of an expression may have print effects.If--track-print-effects
is enabled, this use of print in a function contextwill be disallowed.
6.4.4. Function Hiding
A function is said to berevealed at a location if thebody of the function is visible for verification at that point, otherwise it is consideredhidden.
Functions are revealed by default, but can be hidden using thehide
statement, which takes either a specific function or a wildcard, to hide all functions. Hiding a function can speed up verification of a proof if the body of that function is not needed for the proof. See thehide statement for more information.
Although mostly made obsolete by the hide statement, a function can also be hidden using theopaque
keyword, or using the optiondefault-function-opacity
. Here are the rules regarding those:
Inside the module where the function is declared:
- If
--default-function-opacity
is set totransparent
(default), then:- if there is no
opaque
modifier, the function is transparent. - if there is an
opaque
modifier, then the function is opaque. If the function is mentioned in areveal
statement, then its body is available starting at thatreveal
statement.
- if there is no
- If
--default-function-opacity
is set toopaque
, then:- if there is no
{:transparent}
attribute, the function is opaque. If the function is mentioned in areveal
statement, then the body of the function is available starting at thatreveal
statement. - if there is a
{:transparent}
attribute, then the function is transparent.
- if there is no
- If
--default-function-opacity
is set toautoRevealDependencies
, then:- if there is no
{:transparent}
attribute, the function is opaque. However, the body of the function is available inside any callable that depends on this function via an implicitly insertedreveal
statement, unless the callable has the{autoRevealDependenciesk}
attribute for some natural numberk
which is too low. - if there is a
{:transparent}
attribute, then the function is transparent.
- if there is no
Outside the module where the function is declared, the function is visible only if it was listed in the export set by which the contents of its module was imported. In that case, if the function was exported withreveals
, the rules are the same within the importing module as when the function is used inside its declaring module. If the function is exported only withprovides
it is always hidden and is not permitted to be used in a reveal statement.
More information about the Boogie implementation of opaquenes ishere.
6.4.5. Extreme (Least or Greatest) Predicates and Lemmas
SeeSection 12.5.3 for descriptionsof extreme predicates and lemmas.
6.4.6.older
parameters in predicates
A parameter of any predicate (more precisely, of anyboolean-returning, non-extreme function) can be marked asolder
. This specifies that the truth of the predicate implies thatthe allocatedness of the parameter follows from the allocatedness ofthe non-older
parameters.
To understand what this means and why this attribute is useful,consider the following example, which specifies reachability betweennodes in a directed graph. ANode
is declared to have any number ofchildren:
classNode{varchildren:seq<Node>}
There are several ways one could specify reachability betweennodes. One way (which is used inTest/dafny1/SchorrWaite.dfy
in theDafny test suite) is to define a typePath
, representing lists ofNode
s, and to define a predicate that checks if a given list ofNode
s is indeed a path between two given nodes:
datatypePath=Empty|Extend(Path,Node)predicateReachableVia(source:Node,p:Path,sink:Node,S:set<Node>)readsSdecreasesp{matchpcaseEmpty=>source==sinkcaseExtend(prefix,n)=>ninS&&sinkinn.children&&ReachableVia(source,prefix,n,S)}
In a nutshell, the definition ofReachableVia
says
- An empty path lets
source
reachsink
just whensource
andsink
are the same node. - A path
Extend(prefix,n)
letssource
reachsink
just whenthe pathprefix
letssource
reachn
andsink
is one ofthe children nodes ofn
.
To be admissible by Dafny, the recursive predicate must be shown toterminate. Termination is assured by the specificationdecreasesp
,since every such datatype value has a finite structure and everyrecursive call passes in a path that is structurally included in theprevious. PredicateReachableVia
must also declare (an upper boundon) which heap objects it depends on. For this purpose, thepredicate takes an additional parameterS
, which is used to limitthe set of intermediate nodes in the path. More precisely, predicateReachableVia(source,p,sink,S)
returnstrue
if and only ifp
is a list of nodes inS
andsource
can reachsink
viap
.
Using predicateReachableVia
, we can now define reachability inS
:
predicateReachable(source:Node,sink:Node,S:set<Node>)readsS{existsp::ReachableVia(source,p,sink,S)}
This looks like a good definition of reachability, but Dafny won’tadmit it. The reason is twofold:
Quantifiers and comprehensions are allowed to range only overallocated state. Ater all, Dafny is a type-safe language where everyobject reference isvalid (that is, a pointer to allocated storageof the right type)—it should not be possible, not even through abound variable in a quantifier or comprehension, for a program toobtain an object reference that isn’t valid.
This property is ensured by disallowingopen-ended quantifiers.More precisely, the object references that a quantifier may rangeover must be shown to be confined to object references that wereallocated before some of the non-
older
parameters passed to thepredicate. Quantifiers that are not open-ended are calledclose-ended. Note that close-ended refers only to the objectreferences that the quantification or comprehension ranges over—itdoes not say anything about values of other types, like integers.
Often, it is easy to show that a quantifier is close-ended. In fact,if the type of a bound variable does not contain any objectreferences, then the quantifier is trivially close-ended. For example,
forallx:int::x<=Square(x)
is trivially close-ended.
Another innocent-looking quantifier occurs in the following example:
predicateIsCommutative<X>(r:(X,X)->bool){forallx,y::r(x,y)==r(y,x)// error: open-ended quantifier}
Since nothing is known about typeX
, this quantifier might beopen-ended. For example, ifX
were passed in as a class type, thenthe quantifier would be open-ended. One way to fix this predicate isto restrict it to non-heap based types, which is indicated with the(!new)
type characteristic (seeSection 5.3.1.4):
ghostpredicateIsCommutative<X(!new)>(r:(X,X)->bool)// X is restricted to non-heap types{forallx,y::r(x,y)==r(y,x)// allowed}
Another way to makeIsCommutative
close-ended is to constrain the valuesof the bound variablesx
andy
. This can be done by adding a parameterto the predicate and limiting the quantified values to ones in the given set:
predicateIsCommutativeInS<X>(r:(X,X)->bool,S:set<X>){forallx,y::xinS&&yinS==>r(x,y)==r(y,x)// close-ended}
Through a simple syntactic analysis, Dafny detects the antecedentsxinS
andyinS
, and sinceS
is a parameter and thus can only bepassed in as something that the caller has already allocated, thequantifier inIsCommutativeInS
is determined to be close-ended.
Note, thexinS
trick does not work for the motivating example,Reachable
. If you try to write
predicateReachable(source:Node,sink:Node,S:set<Node>)readsS{existsp::pinS&&ReachableVia(source,p,sink,S)// type error: p}
you will get a type error, becausepinS
does not make sense ifp
has typePath
. We need some other way to justify that thequantification inReachable
is close-ended.
Dafny offers a way to extend thexinS
trick to more situations.This is where theolder
modifier comes in. Before we applyolder
in theReachable
example, let’s first look at whatolder
does in aless cluttered example.
Suppose we rewriteIsCommutativeInS
using a programmer-defined predicateIn
:
predicateIn<X>(x:X,S:set<X>){xinS}predicateIsCommutativeInS<X>(r:(X,X)->bool,S:set<X>){forallx,y::In(x,S)&&In(y,S)==>r(x,y)==r(y,x)// error: open-ended?}
The simple syntactic analysis that looks forxinS
finds nothinghere, because thein
operator is relegated to the body of predicateIn
. To inform the analysis thatIn
is a predicate that, in effect,is likein
, you can mark parameterx
witholder
:
predicateIn<X>(olderx:X,S:set<X>){xinS}
This causes the simple syntactic analysis to accept the quantifier inIsCommutativeInS
. Addingolder
also imposes a semantic check onthe body of predicateIn
, enforced by the verifier. The semanticcheck is that all the object references in the valuex
are older (orequally old as) the object references that are part of the otherparameters,in the event that the predicate returns true. That is,older
is designed to help the caller only if the predicate returnstrue
, and the semantic check amounts to nothing if the predicatereturnsfalse
.
Finally, let’s get back to the motivating example. To allow the quantifierinReachable
, mark parameterp
ofReachableVia
witholder
:
classNode{varchildren:seq<Node>}datatypePath=Empty|Extend(Path,Node)ghostpredicateReachable(source:Node,sink:Node,S:set<Node>)readsS{existsp::ReachableVia(source,p,sink,S)// allowed because of 'older p' on ReachableVia}ghostpredicateReachableVia(source:Node,olderp:Path,sink:Node,S:set<Node>)readsSdecreasesp{matchpcaseEmpty=>source==sinkcaseExtend(prefix,n)=>ninS&&sinkinn.children&&ReachableVia(source,prefix,n,S)}
This example is more involved than the simplerIn
exampleabove. Because of theolder
modifier on the parameter, the quantifier inReachable
is allowed. For intuition, you can think of the effect ofolderp
as adding an antecedentpin{source}+{sink}+S
(but, as we have seen, this is not type correct). The semantic checkimposed on the body ofReachableVia
makes sure that, if thepredicate returnstrue
, then every object reference inp
is as oldas some object reference in another parameter to the predicate.
6.5. Nameonly Formal Parameters and Default-Value Expressions
A formal parameter of a method, constructor in a class, iterator,function, or datatype constructor can be declared with an expressiondenoting adefault value. This makes the parameteroptional,as opposed torequired.
For example,
functionf(x:int,y:int:=10):int
may be called as either
consti:=f(1,2)constj:=f(1)
wheref(1)
is equivalent tof(1,10)
in this case.
The above function may also be called as
vark:=f(y:=10,x:=2);
using names; actual arguments with names may be given in any order,though they must be after actual arguments without names.
Formal parameters may also be declarednameonly
, in which case a call sitemust always explicitly name the formal when providing its actual argument.
For example, a functionff
declared as
functionff(x:int,nameonlyy:int):int
must be called either by listing the value for x and then y with a name, as inff(0,y:=4)
or by giving both actuals by name (in any order). Anameonly
formal may also have a default value and thus be optional.
Any formals after anameonly
formal must either benameonly
themselves or have default values.
The formals of datatype constructors are not required to have names.A nameless formal may not have a default value, nor may it follow a formalthat has a default value.
The default-value expression for a parameter is allowed to mention theother parameters, includingthis
(for instance methods and instancefunctions), but not the implicit_k
parameter in least and greatestpredicates and lemmas. The default value of a parameter may mentionboth preceding and subsequent parameters, but there may not be anydependent cycle between the parameters and their default-valueexpressions.
Thewell-formedness of default-value expressions is checked independentof the precondition of the enclosing declaration. For a function, theparameter default-value expressions may only read what the function’sreads
clause allows. For a datatype constructor, parameter default-valueexpressions may not read anything. A default-value expression may not beinvolved in any recursive or mutually recursive calls with the enclosingdeclaration.
7. Specifications
Specifications describe logical properties of Dafny methods, functions,lambdas, iterators and loops. They specify preconditions, postconditions,invariants, what memory locations may be read or modified, andtermination information by means ofspecification clauses.For each kind of specification, zero or more specificationclauses (of the type accepted for that type of specification)may be given, in any order.
We document specifications at these levels:
- At the lowest level are the various kinds of specification clauses,e.g., a
RequiresClause
. - Next are the specifications for entities that need them,e.g., a
MethodSpec
, which typically consist of a sequence ofspecification clauses. - At the top level are the entity declarations that includethe specifications, e.g.,
MethodDecl
.
This section documents the first two of these in a bottom-up manner.We first document the clauses and then the specificationsthat use them.
Specification clauses typically appear in a sequence. They all begin with a keyword and do not end with semicolons.
7.1. Specification Clauses
Within expressions in specification clauses, you can usespecification expressions along with anyother expressions you need.
7.1.1. Requires Clause (grammar)
Examples:
methodm(i:int)requirestruerequiresi>0requiresL:0<i<10
Therequires
clauses specify preconditions for methods,functions, lambda expressions and iterators. Dafny checksthat the preconditions are met at all call sites. Thecallee may then assume the preconditions hold on entry.
If norequires
clause is specified, then a default implicitclauserequirestrue
is used.
If more than onerequires
clause is given, then theprecondition is the conjunction of all of the expressionsfrom all of therequires
clauses, with a collected listof all the given Attributes. The order of conjunctions(and hence the order ofrequires
clauses with respect to each other)can be important: earlier conjuncts can set conditions thatestablish that later conjuncts are well-defined.
The attributes recognized for requires clauses are discussed inSection 11.4.
A requires clause can havecustom error and success messages.
7.1.2. Ensures Clause (grammar)
Examples:
method{:axiom}m(i:int)returns(r:int)ensuresr>0
Anensures
clause specifies the post condition for amethod, function or iterator.
If noensures
clause is specified, then a default implicitclauseensurestrue
is used.
If more than oneensures
clause is given, then thepostcondition is the conjunction of all of the expressionsfrom all of theensures
clauses, with acollected list of all the given Attributes.The order of conjunctions(and hence the order ofensures
clauses with respect to each other)can be important: earlier conjuncts can set conditions thatestablish that later conjuncts are well-defined.
The attributes recognized for ensures clauses are discussed inSection 11.4.
An ensures clause can havecustom error and success messages.
7.1.3. Decreases Clause (grammar)
Examples:
methodm(i:int,j:int)returns(r:int)decreasesi,jmethodn(i:int)returns(r:int)decreases*
Decreases clauses are used to prove termination in thepresence of recursion. If more than onedecreases
clause is givenit is as if a singledecreases
clause had been given with thecollected list of arguments and a collected list of Attributes. That is,
decreasesA,BdecreasesC,D
is equivalent to
decreasesA,B,C,D
Note that changing the order of multipledecreases
clauses will changethe order of the expressions within the equivalent singledecreases
clause, and will therefore have different semantics.
Loops and compiled methods (but not functions and not ghost methods,including lemmas) can be specified to be possibly non-terminating.This is done by declaring the method or loop withdecreases*
, whichcauses the proof of termination to be skipped. If a*
is presentin adecreases
clause, no other expressions are allowed in thedecreases
clause. A method that contains a possibly non-terminatingloop or a call to a possibly non-terminating method must itself bedeclared as possibly non-terminating.
Termination metrics in Dafny, which are declared bydecreases
clauses,are lexicographic tuples of expressions. At each recursive (or mutuallyrecursive) call to a function or method, Dafny checks that the effectivedecreases
clause of the callee is strictly smaller than the effectivedecreases
clause of the caller.
What does “strictly smaller” mean? Dafny provides a built-in well-founded order for every type and, in some cases, between types. For example, the Booleanfalse
is strictly smaller thantrue
, the integer78
is strictly smaller than102
, the set{2,5}
is strictly smaller than (because it is a proper subset of) the set{2,3,5}
, and fors
of typeseq<Color>
whereColor
is some inductive datatype, the colors[0]
is strictly less thans
(provideds
is nonempty).
What does “effective decreases clause” mean? Dafny always appends a“top” element to the lexicographic tuple given by the user. This topelement cannot be syntactically denoted in a Dafny program and it neveroccurs as a run-time value either. Rather, it is a fictitious value,which here we will denote $\top$, such that each value that can ever occurin a Dafny program is strictly less than $\top$. Dafny sometimes alsoprepends expressions to the lexicographic tuple given by the user. Theeffective decreases clause is any such prefix, followed by theuser-provided decreases clause, followed by $\top$. We said “user-provideddecreases clause”, but if the user completely omits adecreases
clause,then Dafny will usually make a guess at one, in which case the effectivedecreases clause is any prefix followed by the guess followed by $\top$.
Here is a simple but interesting example: the Fibonacci function.
functionFib(n:nat):nat{ifn<2thennelseFib(n-2)+Fib(n-1)}
In this example, Dafny supplies adecreasesn
clause.
Let’s take a look at the kind of example where a mysterious-lookingdecreases clause like “Rank, 0” is useful.
Consider two mutually recursive methods,A
andB
:
methodA(x:nat){B(x);}methodB(x:nat){ifx!=0{A(x-1);}}
To prove termination ofA
andB
, Dafny needs to have effectivedecreases clauses for A and B such that:
the measure for the callee
B(x)
is strictly smaller than the measurefor the callerA(x)
, andthe measure for the callee
A(x-1)
is strictly smaller than the measurefor the callerB(x)
.
Satisfying the second of these conditions is easy, but what about thefirst? Note, for example, that declaring bothA
andB
with “decreases x”does not work, because that won’t prove a strict decrease for the callfromA(x)
toB(x)
.
Here’s one possibility:
methodA(x:nat)decreasesx,1{B(x);}methodB(x:nat)decreasesx,0{ifx!=0{A(x-1);}}
For the call fromA(x)
toB(x)
, the lexicographic tuple"x, 0"
isstrictly smaller than"x, 1"
, and for the call fromB(x)
toA(x-1)
, thelexicographic tuple"x-1, 1"
is strictly smaller than"x, 0"
.
Two things to note: First, the choice of “0” and “1” as the second components of these lexicographic tuples is rather arbitrary. It could just as well have been “false” and “true”, respectively, or the sets{2,5}
and{2,3,5}
. Second, the keyworddecreases
often gives rise to an intuitive English reading of the declaration. For example, you might say that the recursive calls in the definition of the familiar Fibonacci functionFib(n)
“decreases n”. But when the lexicographic tuple contains constants, the English reading of the declaration becomes mysterious and may give rise to questions like “how can you decrease the constant 0?”. The keyword is just that—a keyword. It says “here comes a list of expressions that make up the lexicographic tuple we want to use for the termination measure”. What is important is that one effective decreases clause is compared against another one, and it certainly makes sense to compare something to a constant (and to compare one constant to another).
We can simplify things a little bit by remembering that Dafny appends $\top$ to the user-supplied decreases clause. For the A-and-B example, this lets us drop the constant from thedecreases
clause of A:
methodA(x:nat)decreasesx{B(x);}methodB(x:nat)decreasesx,0{ifx!=0{A(x-1);}}
The effective decreases clause ofA
is $(x, \top)$ and the effectivedecreases clause ofB
is $(x, 0, \top)$. These tuples still satisfy the twoconditions $(x, 0, \top) < (x, \top)$ and $(x-1, \top) < (x, 0, \top)$. Andas before, the constant “0” is arbitrary; anything less than $\top$ (whichis any Dafny expression) would work.
Let’s take a look at one more example that better illustrates the utilityof $\top$. Consider again two mutually recursive methods, call themOuter
andInner
, representing the recursive counterparts of what iterativelymight be two nested loops:
methodOuter(x:nat){// set y to an arbitrary non-negative integervary:|0<=y;Inner(x,y);}methodInner(x:nat,y:nat){ify!=0{Inner(x,y-1);}elseifx!=0{Outer(x-1);}}
The body ofOuter
uses an assign-such-that statement to represent somecomputation that takes place beforeInner
is called. It sets “y” to somearbitrary non-negative value. In a more concrete example,Inner
would dosome work for each “y” and then continue asOuter
on the next smaller“x”.
Using adecreases
clause $(x, y)$ forInner
seems natural, but ifwe don’t have any bound on the size of the $y$ computed byOuter
,there is no expression we can write in thedecreases
clause ofOuter
that is sure to lead to a strictly smaller value for $y$ whenInner
is called. $\top$ to the rescue. If we arrange for the effectivedecreases clause ofOuter
to be $(x, \top)$ and the effective decreasesclause forInner
to be $(x, y, \top)$, then we can show the strictdecreases as required. Since $\top$ is implicitly appended, the twodecreases clauses declared in the program text can be:
methodOuter(x:nat)decreasesx{// set y to an arbitrary non-negative integervary:|0<=y;Inner(x,y);}methodInner(x:nat,y:nat)decreasesx,y{ify!=0{Inner(x,y-1);}elseifx!=0{Outer(x-1);}}
Moreover, remember that if a function or method has no user-declareddecreases
clause, Dafny will make a guess. The guess is (usually)the list of arguments of the function/method, in the order given. This isexactly the decreases clauses needed here. Thus, Dafny successfullyverifies the program without any explicitdecreases
clauses:
methodOuter(x:nat){vary:|0<=y;Inner(x,y);}methodInner(x:nat,y:nat){ify!=0{Inner(x,y-1);}elseifx!=0{Outer(x-1);}}
The ingredients are simple, but the end result may seem like magic. For many users, however, there may be no magic at all – the end result may be so natural that the user never even has to be bothered to think about that there was a need to prove termination in the first place.
Dafny also prepends two expressions to the user-specified (or guessed) tuple of expressionsin the decreases clause. The first expression is the ordering ofthe module containing the decreases clause in the dependence-ordering of modules. That is, a module that neither imports or defines (as submodules) any other modules has the lowest value in the order and every other module has a value that is higher thanthat of any module it defines or imports. As a module cannot call a method in amodule that it does not depend on, this is an effective first component to theoverall decreases tuple.
The second prepended expression represents the positionof the method in the call graph within a module. Dafny analyzes the call-graph of the module, grouping all methods into mutually-recursive groups.Any method that calls nothing else is at the lowest level (say level 0).Absent recursion, every method has a level value strictly greater than any method it calls.Methods that are mutually recursive are at the same level and they are abovethe level of anything else they call. With this level value prepended to the decreases clause, the decreases tuple automatically decreases on anycalls in a non-recursive context.
Though Dafny fixes a well-founded order that it uses when checkingtermination, Dafny does not normally surface this ordering directly inexpressions. However, it is possible to write such ordering constraintsusingdecreasesto
expressions.
7.1.4. Framing (grammar)
Examples:
*oo`a`a{o,p,q}{}
Frame expressions are used to denote the set of memory locationsthat a Dafny program element may read or write. They are used inreads
andmodifies
clauses.A frame expression is a set expression. The form{}
is the empty set.The type of the frame expression isset<object>
.
Note that framing only applies to the heap, or memory accessed throughreferences. Local variables are not stored on the heap, so they cannot bementioned (well, they are not in scope in the declaration) in frameannotations. Note also that types like sets, sequences, and multisets arevalue types, and are treated like integers or local variables. Arrays andobjects are reference types, and they are stored on the heap (though asalways there is a subtle distinction between the reference itself and thevalue it points to.)
TheFrameField
construct is used to specify a field of aclass object. The identifier following the back-quote is thename of the field being referenced.If theFrameField
is preceded by an expression the expressionmust be a reference to an object having that field.If theFrameField
is not preceded by an expression thenthe frame expression is referring to that field of the currentobject (this
). This form is only used within a method of a class or trait.
AFrameField
can be useful in the following case:When a method modifies only one field, rather than writing
classA{vari:intvarx0:intvarx1:intvarx2:intvarx3:intvarx4:intmethodM()modifiesthisensuresunchanged(`x0)&&unchanged(`x1)&&unchanged(`x2)&&unchanged(`x3)&&unchanged(`x4){i:=i+1;}}
one can write the more concise:
classA{vari:intvarx0:intvarx1:intvarx2:intvarx3:intvarx4:intmethodM()modifies`i{i:=i+1;}}
There’s (unfortunately) no form of it for arrayelements – but to account for unchanged elements, you can always writeforalli|0<=i<|a|::unchanged(a[i])
.
AFrameField
is not taken into consideration forlambda expressions.
7.1.5. Reads Clause (grammar)
Examples:
consto:objectconsto,oo:objectfunctionf()reads*functiong()readso,oofunctionh()reads{o}methodf()reads*methodg()readso,oomethodh()reads{o}
Functions are not allowed to have side effects; they may also be restricted inwhat they can read. Thereading frame of a function (or predicate) consists of allthe heap memory locations that the function is allowed to read. The reason wemight limit what a function can read is so that when we write to memory,we can be sure that functions that did not read that part of memory havethe same value they did before. For example, we might have two arrays,one of which we know is sorted. If we did not put a reads annotation onthe sorted predicate, then when we modify the unsorted array, we cannotdetermine whether the other array stopped being sorted. While we might beable to give invariants to preserve it in this case, it gets even morecomplex when manipulating data structures. In this case, framing isessential to making the verification process feasible.
By default, methods are not required to list the memory location they read.However, there are use cases for restricting what methods can read as well.In particular, if you want to verify that imperative code is safe to execute concurrently when compiled,you can specify that a method does not read or write any shared state,and therefore cannot encounter race conditions or runtime crashes related tounsafe communication between concurrent executions.Seethe{:concurrent}
attribute for more details.
It is not just the body of a function or method that is subject toreads
checks, but also its precondition and thereads
clause itself.
Areads
clause can list a wildcard*
, which allows the enclosingfunction or method to read anything. This is the implicit default for methods with noreads
clauses,allowing methods to read whatever they like.The default for functions, however, is to not allow reading any memory.Allowing functions to read arbitrary memory is more problematic:in many cases, and in particular in all caseswhere the function is defined recursively, this makes it next toimpossible to make any use of the function. Nevertheless, as anexperimental feature, the language allows it (and it is sound).If areads
clause uses*
, then thereads
clause is not allowed tomention anything else (since anything else would be irrelevant, anyhow).
Areads
clause specifies the set of memory locations that a function,lambda, or method may read. The readable memory locations are all the fieldsof all of the references given in the set specified in the frame expressionand the single fields given inFrameField
elements of the frame expression.For example, in
classC{varx:intvary:intpredicatef(c:C)readsthis,c`x{this.x==c.x}}
thereads
clause allows readingthis.x
,this,y
, andc.x
(which may be the samememory location asthis.x
).}
If more than onereads
clause is givenin a specification the effective read set is the union of the setsspecified. If there are noreads
clauses the effective read set isempty. If*
is given in areads
clause it means any memory may beread.
If areads
clause refers to a sequence or multiset, that collection(call itc
) is converted to a set by adding an implicit setcomprehension of the formseto:object|oinc
before computing theunion of object sets from otherreads
clauses.
An expression in areads
clause is also allowed to be a function call whose value is a collection of references. Such an expression is converted to a set by taking theunion of the function’s image over all inputs. For example, ifF
isa function fromint
toset<object>
, thenreadsF
has the meaning
setx:int,o:object|oinF(x)::o
For each function valuef
, Dafny defines the functionf.reads
,which takes the same arguments asf
and returns that set of objectsthatf
reads (according to its reads clause) with those arguments.f.reads
has typeT~>set<object>
, whereT
is the input type(s) off
.
This is particularly useful when wanting to specify the reads set ofanother function. For example, functionSum
adds up the values off(i)
wherei
ranges fromlo
tohi
:
functionSum(f:int~>real,lo:int,hi:int):realrequireslo<=hirequiresforalli::f.requires(i)readsf.readsdecreaseshi-lo{iflo==hithen0.0elsef(lo)+Sum(f,lo+1,hi)}
Itsreads
specification says thatSum(f,lo,hi)
may read anythingthatf
may read on any input. (The specificationreadsf.reads
gives an overapproximation of whatSum
will actuallyread. More precise would be to specify thatSum
reads only whatf
reads on the values fromlo
tohi
, but the larger set denoted byreadsf.reads
is easier to write down and is often good enough.)
Without suchreads
function, one could also write the more preciseand more verbose:
functionSum(f:int~>real,lo:int,hi:int):realrequireslo<=hirequiresforalli::lo<=i<hi==>f.requires(i)readsseti,o|lo<=i<hi&&oinf.reads(i)::odecreaseshi-lo{iflo==hithen0.0elsef(lo)+Sum(f,lo+1,hi)}
Note, onlyreads
clauses, notmodifies
clauses, are allowed toinclude functions as just described.
Iterator specifications also allowreads
clauses,with the same syntax and interpretation of arguments as above,but the meaning is quite different!SeeSection 5.11 for more details.
7.1.6. Modifies Clause (grammar)
Examples:
classA{varf:int}consto:object?constp:A?methodM()modifies{o,p}methodN()modifies{}methodQ()modifieso,p`f
By default, methods are allowed to readwhatever memory they like, but they are required to list which parts ofmemory they modify, with amodifies
annotation. These are almost identicalto theirreads
cousins, except they say what can be changed, rather thanwhat the definition depends on. In combination with reads,modification restrictions allow Dafny to prove properties of code thatwould otherwise be very difficult or impossible. Reads and modifies areone of the tools that allow Dafny to work on one method at a time,because they restrict what would otherwise be arbitrary modifications ofmemory to something that Dafny can reason about.
Just as for areads
clause, the memory locations allowed to be modifiedin a method are all the fields of any object reference in the frame expressionset and any specific field denoted by aFrameField
in themodifies
clause.For example, in
classC{varnext:C?varvalue:intmethodM()modifiesnext{...}}
methodM
is permitted to modifythis.next.next
andthis.next.value
but notthis.next
. To be allowed to modifythis.next
, the modifies clausemust includethis
, or some expression that evaluates tothis
, orthis`next
.
If an object is newly allocated within the body of a methodor within the scope of amodifies
statement or a loop’smodifies
clause, then the fields of that object may always be modified.
Amodifies
clause specifies the set of memory locations that amethod, iterator or loop body may modify. If more than onemodifies
clause is given in a specification, the effective modifies set is theunion of the sets specified. If nomodifies
clause is given theeffective modifies set is empty. There is no wildcard (*
) allowed ina modifies clause. A loop can also have amodifies
clause. If none is given, the loop may modify anythingthe enclosing context is allowed to modify.
Note thatmodifies here is used in the sense ofwrites. That is, a fieldthat may not be modified may not be written to, even with the same value italready has or even if the value is restored later. The terminology andsemantics varies among specification languages. Some define frame conditionsin this sense (a) ofwrites and others in the sense (b) that allows writinga field with the same value or changing the value so long as the originalvalue is restored by the end of the scope. For example, JML definesassignable
andmodifies
as synonyms in the sense (a), though KeYinterprets JML’sassigns/modifies
in sense (b).ACSL and ACSL++ use theassigns
keyword, but withmodify (b) semantics.Ada/SPARK’s dataflow contracts encodewrite (a) semantics.
7.1.7. Invariant Clause (grammar)
Examples:
methodm(){vari:=10;while0<iinvariant0<=i<10}
Aninvariant
clause is used to specify an invariantfor a loop. If more than oneinvariant
clause is given fora loop, the effective invariant is the conjunction ofthe conditions specified, in the order given in the source text.
The invariant must hold on entry to the loop. And assuming itis valid on entry to a particular iteration of the loop, Dafny must be able to prove that it thenholds at the end of that iteration of the loop.
An invariant can havecustom error and success messages.
7.2. Method Specification (grammar)
Examples:
classC{varnext:C?varvalue:intmethodM(i:int)returns(r:int)requiresi>=0modifiesnextdecreasesiensuresr>=0{...}}
A method specification consists of zero or morereads
,modifies
,requires
,ensures
ordecreases
clauses, in any order.A method does not needreads
clauses in most cases,because methods are allowed to read any memory by default,butreads
clauses are supported for use cases such as verifying safe concurrent execution.Seethe{:concurrent}
attribute for more details.
7.3. Function Specification (grammar)
Examples:
classC{varnext:C?varvalue:intfunctionM(i:int):(r:int)requiresi>=0readsthisdecreasesiensuresr>=0{0}}
A function specification is zero or morereads
,requires
,ensures
ordecreases
clauses, in any order. A functionspecification does not havemodifies
clauses because functions are notallowed to modify any memory.
7.4. Lambda Specification (grammar)
A lambda specification provides a specification for a lambda function expression;it consists of zero or morereads
orrequires
clauses.Anyrequires
clauses may not have labels or attributes.Lambda specifications do not haveensures
clauses because the bodyis never opaque.Lambda specifications do not havedecreases
clauses because lambda expressions do not have names and thus cannot be recursive. Alambda specification does not havemodifies
clauses because lambdasare not allowed to modify any memory.
7.5. Iterator Specification (grammar)
An iterator specification may containsreads
,modifies
,decreases
,requires
,yieldrequires,
ensuresand
yield ensures` clauses.
An iterator specification applies both to the iterator’s constructormethod and to itsMoveNext
method.
- The
reads
andmodifies
clauses apply to both of them (butreads
clauses have adifferent meaning on iterators than on functions or methods). - The
requires
andensures
clauses apply to the constructor. - The
yieldrequires
andyieldensures
clauses apply to theMoveNext
method.
Examples of iterators, including iterator specifications, are given inSection 5.11. Briefly
- a requires clause gives a precondition for creating an iterator
- an ensures clause gives a postcondition when the iterator exits (after all iterations are complete)
- a decreases clause is used to show that the iterator will eventually terminate
- a yield requires clause is a precondition for calling
MoveNext
- a yield ensures clause is a postcondition for calling
MoveNext
- a reads clause gives a set of memory locations that will be unchanged after a
yield
statement - a modifies clause gives a set of memory locations the iterator may write to
7.6. Loop Specification (grammar)
A loop specification provides the information Dafny needs toprove properties of a loop. It containsinvariant
,decreases
, andmodifies
clauses.
Theinvariant
clauseis effectively a precondition and it along with thenegation of the loop test condition provides the postcondition.Thedecreases
clause is used to prove termination.
7.7. Auto-generated boilerplate specifications
AutoContracts is an experimental feature that inserts much of the dynamic-frames boilerplateinto a class. The user simply
- marks the class with
{:autocontracts}
and - declares a function (or predicate) called Valid().
AutoContracts then
- Declares, unless there already exist members with these names:
ghostvarRepr:set(object)predicateValid()
- For function/predicate
Valid()
, insertsreadsthis,ReprensuresValid()==>thisinRepr
- Into body of
Valid()
, inserts (at the beginning of the body)thisinRepr&&null!inRepr
and also inserts, for every array-valued field
A
declared in the class:(A!=null==>AinRepr)&&
and for every field
F
of a class typeT
whereT
has a field calledRepr
, also inserts(F!=null==>FinRepr&&F.ReprSUBSETRepr&&this!inRepr&&F.Valid())
except, if
A
orF
is declared with{:autocontractsfalse}
, then the implication will notbe added. - For every constructor, inserts
ensuresValid()&&fresh(Repr)
- At the end of the body of the constructor, adds
Repr:={this};if(A!=null){Repr:=Repr+{A};}if(F!=null){Repr:=Repr+{F}+F.Repr;}
In all the following cases, nomodifies
clause orreads
clause is added if the userhas given one.
- For every non-static non-ghost method that is not a “simple query method”,inserts
requiresValid()modifiesReprensuresValid()&&fresh(Repr-old(Repr))
- At the end of the body of the method, inserts
if(A!=null&&!(AinRepr)){Repr:=Repr+{A};}if(F!=null&&!(FinRepr&&F.ReprSUBSETRepr)){Repr:=Repr+{F}+F.Repr;}
- For every non-static non-twostate method that is either ghost or is a “simple query method”,add:
requiresValid()
- For every non-static twostate method, inserts
requiresold(Valid())
- For every non-“Valid” non-static function, inserts
requiresValid()readsRepr
7.8. Well-formedness of specifications
Dafny ensures that therequires
clausesandensures
clauses, which are expressions,arewell-formed independent of the bodythey belong to.Examples of conditions this rules out are null pointer dereferencing,out-of-bounds array access, and division by zero.Hence, when declaring the following method:
methodTest(a:array<int>)returns(j:int)requiresa.Length>=1ensuresa.Length%2==0==>j>=10/a.Length{j:=20;vardivisor:=a.Length;ifdivisor%2==0{j:=j/divisor;}}
Dafny will split the verification in twoassertion batchesthat will roughly look like the following lemmas:
lemmaTest_WellFormed(a:array?<int>){assumea!=null;// From the definition of aasserta!=null;// for the `requires a.Length >= 1`assumea.Length>=1;// After well-formedness, we assume the requiresasserta!=null;// Again for the `a.Length % 2`ifa.Length%2==0{asserta!=null;// Again for the final `a.Length`asserta.Length!=0;// Because of the 10 / a.Length}}methodTest_Correctness(a:array?<int>){// Here we assume the well-formedness of the conditionassumea!=null;// for the `requires a.Length >= 1`assumea!=null;// Again for the `a.Length % 2`ifa.Length%2==0{assumea!=null;// Again for the final `a.Length`assumea.Length!=0;// Because of the 10 / a.Length}// Now the body is translatedvarj:=20;asserta!=null;// For `var divisor := a.Length;`vardivisor:=a.Length;if*{assumedivisor%2==0;assertdivisor!=0;j:=j/divisor;}assumedivisor%2==0==>divisor!=0;asserta.Length%2==0==>j>=10/a.Length;}
For this reason the IDE typically reports at least twoassertion batcheswhen hovering a method.
8. Statements (grammar)
Many of Dafny’s statements are similar to those in traditionalprogramming languages, but a number of them are significantly different.Dafny’s various kinds of statements are described in subsequent sections.
Statements have zero or more labels and end with either a semicolon (;
) or a closing curly brace (‘}’).
8.1. Labeled Statement (grammar)
Examples:
classA{varf:int}methodm(a:A){labelx:whiletrue{if(*){breakx;}}a.f:=0;labely:a.f:=1;assertold@y(a.f)==1;}
A labeled statement is just
- the keyword
label
- followed by an identifier, which is the label,
- followed by a colon
- and a statement.
The label may bereferenced in abreak
orcontinue
statement within the labeled statement(seeSection 8.14). That is, the break or continue thatmentions the label must beenclosed in the labeled statement.
The label may also be used in anold
expression (Section 9.22). In this case, the labelmust have been encountered during the control flow en route to theold
expression. We say in this case that the (program point of the) labeldominatesthe (program point of the) use of the label.Similarly, labels are used to indicate previous states in calls oftwo-state predicates,fresh expressions,unchanged expressions, andallocated expressions.
A statement can be given several labels. It makes no difference which of theselabels is used to reference the statement—they are synonyms of each other.The labels must be distinct from each other, and are not allowed to be thesame as any previous enclosing ordominating label.
8.2. Block Statement (grammar)
Examples:
{print0;varx:=0;}
A block statement is a sequence of zero or more statements enclosed by curly braces.Local variables declared in the block end their scope at the end of the block.
8.3. Return Statement (grammar)
Examples:
methodm(i:int)returns(r:int){returni+1;}methodn(i:int)returns(r:int,q:int){returni+1,i+2;}methodp()returns(i:int){i:=1;return;}methodq(){return;}
A return statement can only be used in a method. It is usedto terminate the execution of the method.
To return a value from a method, the value is assigned to oneof the named out-parameters sometime before a return statement.In fact, the out-parameters act very much like local variables,and can be assigned to more than once. Return statements areused when one wants to return before reaching the end of thebody block of the method.
Return statements can be just thereturn
keyword (where the current valuesof the out-parameters are used), or they can take a list of expressions toreturn. If a list is given, the number of expressions given must be the sameas the number of named out-parameters. These expressions areevaluated, then they are assigned to the out-parameters, and then themethod terminates.
8.4. Yield Statement (grammar)
A yield statement may only be used in an iterator.Seeiterator types for more detailsabout iterators.
The body of an iterator is aco-routine. It is usedto yield control to its caller, signaling that a newset of values for the iterator’s yield (out-)parameters (if any)are available. Values are assigned to the yield parametersat or before a yield statement.In fact, the yield parameters act very much like local variables,and can be assigned to more than once. Yield statements areused when one wants to return new yield parameter valuesto the caller. Yield statements can be just theyield
keyword (where the current values of the yield parametersare used), or they can take a list of expressions to yield.If a list is given, the number of expressions given must be thesame as the number of named iterator out-parameters.These expressions are then evaluated, then they areassigned to the yield parameters, and then the iteratoryields.
8.5. Update and Call Statements (grammar)
Examples:
classC{varf:int}classD{vari:intconstructor(i:int){this.i:=i;}}methodq(i:int,j:int){}methodr()returns(s:int,t:int){return2,3;}methodm(){varss:int,tt:int,c:C?,a:array<int>,d:D?;q(0,1);ss,c.f:=r();c:=newC;d:=newD(2);a:=newint[10];ss,tt:=212,33;ss:|ss>7;ss:=*;}
This statement corresponds to familiar assignment or method call statements,with variations. If more than oneleft-hand side is used, these must denote different l-values, unless thecorresponding right-hand sides also denote the same value.
The update statement serves several logical purposes.
8.5.1. Method call with no out-parameters
1) Examples of method calls take this form
m();m(1,2,3){:attr};e.f().g.m(45);
As there are no left-hand-side locations to receive values, this form is allowed only for methods that have no out-parameters.
8.5.2. Method call with out-parameters
This form uses:=
to denote the assignment of the out-parameters of the method to the corresponding number of LHS values.
a,b.e().f:=m(){:attr};
In this case, the right-hand-side must be a method call and the number ofleft-hand sides must match the number of out-parameters of themethod that is called.Note that the result of a method call is not allowed to be used as an argument ofanother method call, as if it were an expression.
8.5.3. Parallel assignment
A parallel-assignment has one-or-more right-hand-side expressions,which may be function calls but may not be method calls.
x,y:=y,x;
The above example swaps the values ofx
andy
. If more than oneleft-hand side is used, these must denote different l-values, unless thecorresponding right-hand sides also denote the same value. There mustbe an equal number of left-hand sides and right-hand sides.The most common case has only one RHS and one LHS.
8.5.4. Havoc assignment
The form with a right-hand-side that is*
is ahavoc assignment.It assigns an arbitrary but type-correct value to the corresponding left-hand-side.It can be mixed with other assignments of computed values.
a:=*;a,b,c:=4,*,5;
8.5.5. Such-that assignment
This form has one or more left-hand-sides, a:|
symbol and then a boolean expression on the right.The effect is to assign values to the left-hand-sides that satisfy the RHS condition.
x,y:|0<x+y<10;
This is read as assign values tox
andy
such that0<x+y<10
is true.The given boolean expression need not constrain the LHS values uniquely:the choice of satisfying values is non-deterministic. This can be used to make a choice as in thefollowing example where we choose an element in a set.
methodSum(X:set<int>)returns(s:int){s:=0;varY:=X;whileY!={}decreasesY{vary:int;y:|yinY;s,Y:=s+y,Y-{y};}}
Dafny will report an error if it cannot prove that valuesexist that satisfy the condition.
In this variation, with anassume
keyword
y:|assumeyinY;
Dafny assumes without proof that an appropriate value exists.
Note that the syntax
Lhs ":"
is interpreted as a label in which the user forgot thelabel
keyword.
8.5.6. Method call with aby
proof
The purpose of this form of a method call is to seperate the called method’sprecondition and its proof from the rest of the correctness proof of thecalling method.
opaquepredicateP(){true}lemmaProveP()ensuresP(){revealP();}methodM(i:int)returns(r:int)requiresP()ensuresr==i{r:=i;}methodC(){varv:=M(1/3)by{// We prove 3 != 0 outside of the by proofProveP();// Prove precondtion}assertv==0;// Use postconditionassertP();// Fails}
By placing the call to lemmaProveP
inside of the by block, we can not useP
after the method call. The well-formedness checks of the arguments to themethod call are not subject to the separation.
8.6. Update with Failure Statement (:-
) (grammar)
See the subsections below for examples.
A:-
9 statement is an alternate form of the:=
statement that allows for abrupt return if a failure is detected.This is a language feature somewhat analogous to exceptions in other languages.
An update-with-failure statement usesfailure-compatible types.A failure-compatible type is a type that has the following (non-static) members (each with no in-parameters and one out-parameter):
- a non-ghost function
IsFailure()
that returns abool
- an optional non-ghost function
PropagateFailure()
that returns a value assignable to the first out-parameter of the caller - an optional function
Extract()
(PropagateFailure and Extract were permitted to be methods (but deprecated) prior to Dafny 4. They will be required to be functions in Dafny 4.)
A failure-compatible type with anExtract
member is calledvalue-carrying.
To use this form of update,
- if the RHS of the update-with-failure statement is a method call, the first out-parameter of the callee must be failure-compatible
- if instead, the RHS of the update-with-failure statement is one or more expressions, the first of these expressions must be a value with a failure-compatible type
- the caller must have a first out-parameter whose type matches the output of
PropagateFailure
applied to the first output of the callee, unless anexpect
,assume
, orassert
keyword is used after:-
(cf.Section 8.6.7). - if the failure-compatible type of the RHS does not have an
Extract
member,then the LHS of the:-
statement has one less expression than the RHS(or than the number of out-parameters from the method call), the value of the first out-parameter or expression being dropped(see the discussion and examples inSection 8.6.2) - if the failure-compatible type of the RHS does have an
Extract
member,then the LHS of the:-
statement has the same number of expressions as the RHS(or as the number of out-parameters from the method call)and the type of the first LHS expression must be assignable from the return type of theExtract
member - the
IsFailure
andPropagateFailure
methods may not be ghost - the LHS expression assigned the output of the
Extract
member is ghost precisely ifExtract
is ghost
The following subsections show various uses and alternatives.
8.6.1. Failure compatible types
A simple failure-compatible type is the following:
datatypeStatus=|Success|Failure(error:string){predicateIsFailure(){this.Failure?}functionPropagateFailure():StatusrequiresIsFailure(){Failure(this.error)}}
A commonly used alternative that carries some value information is something like this generic type:
datatypeOutcome<T>=|Success(value:T)|Failure(error:string){predicateIsFailure(){this.Failure?}functionPropagateFailure<U>():Outcome<U>requiresIsFailure(){Failure(this.error)// this is Outcome<U>.Failure(...)}functionExtract():Trequires!IsFailure(){this.value}}
8.6.2. Simple status return with no other outputs
The simplest use of this failure-return style of programming is to have a method call that just returns a non-value-carryingStatus
value:
methodCallee(i:int)returns(r:Status){ifi<0{returnFailure("negative");}returnSuccess;}methodCaller(i:int)returns(rr:Status){:-Callee(i);...}
Note that there is no LHS to the:-
statement.IfCallee
returnsFailure
, then the caller immediately returns,not executing any statements following the call ofCallee
.The value returned byCaller
(the value ofrr
in the code above) is the result ofPropagateFailure
applied to the value returned byCallee
, which is often just the same value.IfCallee
does not returnFailure
(that is, returns a value for whichIsFailure()
isfalse
)then that return value is forgotten and execution proceeds normally with the statements following the call ofCallee
in the body ofCaller
.
The desugaring of the:-Callee(i);
statement is
vartmp;tmp:=Callee(i);iftmp.IsFailure(){rr:=tmp.PropagateFailure();return;}
In this and subsequent examples of desugaring, thetmp
variable is a new, unique variable, unused elsewhere in the calling member.
8.6.3. Status return with additional outputs
The example in the previous subsection affects the program only through side effects or the status return itself.It may well be convenient to have additional out-parameters, as is allowed for:=
updates;these out-parameters behave just as for:=
.Here is an example:
methodCallee(i:int)returns(r:Status,v:int,w:int){ifi<0{returnFailure("negative"),0,0;}returnSuccess,i+i,i*i;}methodCaller(i:int)returns(rr:Status,k:int){varj:int;j,k:-Callee(i);k:=k+k;...}
HereCallee
has two outputs in addition to theStatus
output.The LHS of the:-
statement accordingly has two l-values to receive those outputs.The recipients of those outputs may be any sort of l-values;here they are a local variable and an out-parameter of the caller.Those outputs are assigned in the:-
call regardless of theStatus
value:
- If
Callee
returns a failure value as its first output, then the other outputs are assigned, thecaller’s first out-parameter (hererr
) is assigned the value ofPropagateFailure
, and the caller returns. - If
Callee
returns a non-failure value as its first output, then the other outputs are assigned and thecaller continues execution as normal.
The desugaring of thej,k:-Callee(i);
statement is
vartmp;tmp,j,k:=Callee(i);iftmp.IsFailure(){rr:=tmp.PropagateFailure();return;}
8.6.4. Failure-returns with additional data
The failure-compatible return value can carry additional data as shown in theOutcome<T>
example above.In this case there is a (first) LHS l-value to receive this additional data. The type of that first LHSvalue is one that is assignable from the result of theExtract
function, not the actual first out-parameter.
methodCallee(i:int)returns(r:Outcome<nat>,v:int){ifi<0{returnFailure("negative"),i+i;}returnSuccess(i),i+i;}methodCaller(i:int)returns(rr:Outcome<int>,k:int){varj:int;j,k:-Callee(i);k:=k+k;...}
SupposeCaller
is called with an argument of10
.ThenCallee
is called with argument10
and returnsr
andv
ofOutcome<nat>.Success(10)
and20
.Herer.IsFailure()
isfalse
, so control proceeds normally.Thej
is assigned the result ofr.Extract()
, which will be10
,andk
is assigned20
.Control flow proceeds to the next line, wherek
now gets the value40
.
Suppose instead thatCaller
is called with an argument of-1
.ThenCallee
is called with the value-1
and returnsr
andv
with valuesOutcome<nat>.Failure("negative")
and-2
.k
is assigned the value ofv
(-2).Butr.IsFailure()
istrue
, so control proceeds directly to return fromCaller
.The first out-parameter ofCaller
(rr
) gets the value ofr.PropagateFailure()
,which isOutcome<int>.Failure("negative")
;k
already has the value-2
.The rest of the body ofCaller
is skipped.In this example, the first out-parameter ofCaller
has a failure-compatible typeso the exceptional return will propagate up the call stack.It will keep propagating up the call stackas long as there are callers with this first special output typeand calls that use:-
and the return value keeps havingIsFailure()
true.
The desugaring of thej,k:-Callee(i);
statement in this example is
vartmp;tmp,k:=Callee(i);iftmp.IsFailure(){rr:=tmp.PropagateFailure();return;}j:=tmp.Extract();
8.6.5. RHS with expression list
Instead of a failure-returning method call on the RHS of the statement,the RHS can instead be a list of expressions.As for a:=
statement, in this form, the expressions on the left and right sides of:-
must correspond,just omitting a LHS l-value for the first RHS expression if its type is not value-carrying.The semantics is very similar to that in the previous subsection.
- The first RHS expression must have a failure-compatible type.
- All the assignments of RHS expressions to LHS values except for the first RHS value are made.
- If the first RHS value (say
r
) respondstrue
tor.IsFailure()
,thenr.PropagateFailure()
is assigned to the first out-parameter of thecallerand the execution of the caller’s body is ended. - If the first RHS value (say
r
) respondsfalse
tor.IsFailure()
, then- if the type of
r
is value-carrying, thenr.Extract()
is assigned to the first LHS value of the:-
statement;ifr
is not value-carrying, then the corresponding LHS l-value is omitted - execution of the caller’s body continues with the statement following the
:-
statement.
- if the type of
A RHS with a method call cannot be mixed with a RHS containing multiple expressions.
For example, the desugaring of
methodm(r:Status)returns(rr:Status){vark;k:-r,7;...}
is
vark;vartmp;tmp,k:=r,7;iftmp.IsFailure(){rr:=tmp.PropagateFailure();return;}
8.6.6. Failure with initialized declaration.
The:-
syntax can also be used in initialization, as in
vars,t:-M();
This is equivalent to
vars,t;s,t:-M();
with the semantics as described above.
8.6.7. Keyword alternative
In any of the above described uses of:-
, the:-
token may be followed immediately by the keywordexpect
,assert
orassume
.
assert
means that the RHS evaluation is expected to be successful, but thatthe verifier should prove that this is so; that is, the verifier should proveassert!r.IsFailure()
(wherer
is the status return from the callee)(cf.Section 8.17)assume
means that the RHS evaluation should be assumed to be successful,as if the statementassume!r.IsFailure()
followed the evaluation of the RHS(cf.Section 8.18)expect
means that the RHS evaluation should be assumed to be successful(like usingassume
above), but that the compiler should include arun-time check for success. This is equivalent to includingexpect!r.IsFailure()
after the RHS evaluation; that is, if the statusreturn is a failure, the program halts.(cf.Section 8.19)
In each of these cases, there is no abrupt return from the caller. Thusthere is no evaluation ofPropagateFailure
. Consequently the firstout-parameter of the caller need not match the return type ofPropagateFailure
; indeed, the failure-compatible type returned by thecallee need not have aPropagateFailure
member.
The equivalent desugaring replaces
iftmp.IsFailure(){rr:=tmp.PropagateFailure();return;}
with
expect!tmp.IsFailure(),tmp;
or
assert!tmp.IsFailure();
or
assume!tmp.IsFailure();
There is a grammatical nuance that the user should be aware of.The keywordsassert
,assume
, andexpect
can start an expression.For example,assertP;E
can be an expression. However, ine:-assertP;E;
theassert
is parsed as the keyword associated with:-
. To have theassert
considered part of the expression use parentheses:e:-(assertP;E);
.
8.6.8. Key points
There are several points to note.
- The first out-parameter of the callee is special.It has a special type and that type indicates that the value is inspected to see if an abrupt returnfrom the caller is warranted.This type is often a datatype, as shown in the examples above, but it may be any type with the appropriate members.
- The restriction on the type of caller’s first out-parameter isjust that it must be possible (perhaps through generic instantiation and type inference, as in these examples) for
PropagateFailure
applied to the failure-compatible output from the callee to produce a value of the caller’s first out-parameter type.If the caller’s first out-parameter type is failure-compatible (which it need not be),then failures can be propagated up the call chain.If the keyword form (e.g.assume
) of the statement is used, then noPropagateFailure
memberis needed, because no failure can occur, and there is no restriction on the caller’s first out-parameter. - In the statement
j,k:-Callee(i);
,when the callee’s return value has anExtract
member,the type ofj
is not the type of the first out-parameter ofCallee
.Rather it is a type assignable from the output type ofExtract
applied to the first out-value ofCallee
. - A method like
Callee
with a special first out-parameter type can still be used in the normal way:r,k:=Callee(i)
.Nowr
gets the first output value fromCallee
, of typeStatus
orOutcome<nat>
in the examples above.No special semantics or exceptional control paths apply.Subsequent code can do its own testing of the value ofr
and whatever other computations or control flow are desired. - The caller and callee can have any (positive) number of output arguments,as long as the callee’s first out-parameter has a failure-compatible typeand the caller’s first out-parameter type matches
PropagateFailure
. - If there is more than one LHS, the LHSs must denote different l-values, unless the RHS is a list of expressions and the corresponding RHS values are equal.
- The LHS l-values are evaluated before the RHS method call,in case the method call has side-effects or return values that modify the l-values prior to assignments being made.
It is important to note the connection between the failure-compatible types used in the caller and callee,if they both use them.They do not have to be the same type, but they must be closely related,as it must be possible for the callee’sPropagateFailure
to return a value of the caller’s failure-compatible type.In practice this means that one such failure-compatible type should be used for an entire program.If a Dafny program uses a library shared by multiple programs, the library should supply such a type and it should be used by all the client programs (and, effectively, all Dafny libraries).It is also the case that it is inconvenient to mix types such asOutcome
andStatus
above within the same program.If there is a mix of failure-compatible types, then the program will need to use:=
statements and code forexplicit handling of failure values.
8.6.9. Failure returns and exceptions
The:-
mechanism is like the exceptions used in other programming languages, with some similarities and differences.
- There is essentially just one kind of ‘exception’ in Dafny,the variations of the failure-compatible data type.
- Exceptions are passed up the call stack whether or not intervening methods are aware of the possibility of an exception,that is, whether or not the intervening methods have declared that they throw exceptions.Not so in Dafny: a failure is passed up the call stack only if each caller has a failure-compatible first out-parameter, is itself called in a
:-
statement, and returns a value that responds true toIsFailure()
. - All methods that contain failure-return callees must explicitly handle those failuresusing either
:-
statements or using:=
statements with a LHS to receive the failure value.
8.7. Variable Declaration Statement (grammar)
Examples:
methodm(){varx,y:int;// x's type is inferred, not necessarily 'int'varb:bool,k:int;x:=1;// settles x's type}
A variable declaration statement is used to declare one or more local variables ina method or function. The type of each local variable must be givenunless its type can be inferred, either from a given initial value, orfrom other uses of the variable. If initial values are given, the numberof values must match the number of variables declared.
The scope of the declared variable extends to the end of the block in which it isdeclared. However, be aware that if a simple variable declaration is followedby an expression (rather than a subsequent statement) then thevar
begins aLet Expression and the scope of the introduced variables isonly to the end of the expression. In this case, though, thevar
is in an expressioncontext, not a statement context.
Note that the type of each variable must be given individually. The following code
varx,y:int;varx,y:=5,6;varx,y:-m();varx,y:|0<x+y<10;var(x,y):=makePair();varCons(x,y)=ConsMaker();
does not declare bothx
andy
to be of typeint
. Rather it will give anerror explaining that the type ofx
is underspecified if it cannot beinferred from uses of x.
The variables can be initialized with syntax similar to update statements (cf.Section 8.5).
If the RHS is a call, then any variable receiving the value of aformal ghost out-parameter will automatically be declared as ghost, evenif theghost
keyword is not part of the variable declaration statement.
The left-hand side can also contain a tuple of patterns that will bematched against the right-hand-side. For example:
functionreturnsTuple():(int,int){(5,10)}functionusesTuple():int{var(x,y):=returnsTuple();x+y}
The initialization with failure operator:-
returns from the enclosing method if the initializer evaluates to a failure value of a failure-compatible type (seeSection 8.6).
8.8. Guards (grammar)
Examples (inif
statements):
methodm(i:int){if(*){printi;}ifi>0{printi;}}
Guards are used inif
andwhile
statements as boolean expressions. Guardstake two forms.
The first and most common form is just a boolean expression.
The second form is either*
or(*)
. These have the same meaning. Anunspecified boolean value is returned. The value returnedmay be different each time it is executed.
8.9. Binding Guards (grammar)
Examples (inif
statements):
methodm(i:int){ghostvark:int;ifi,j:|0<i+j<10{k:=0;}else{k:=1;}}
Anif
statement can also take abinding guard.Such a guard checks if there exist values for the given variables that satisfy the given expression.If so, it binds some satisfying values to the variables and proceedsinto the “then” branch; otherwise it proceeds with the “else” branch,where the bound variables are not in scope.
In other words, the statement
ifx:|P{S}else{T}
has the same meaning as
ifexistsx::P{varx:|P;S}else{T}
The identifiers bound by the binding guard are ghost variablesand cannot be assigned to non-ghost variables. They are onlyused in specification contexts.
Here is another example:
predicateP(n:int){n%2==0}methodM1()returns(ghosty:int)requiresexistsx::P(x)ensuresP(y){ifx:int:|P(x){y:=x;}}
8.10. If Statement (grammar)
Examples:
methodm(i:int){varx:int;ifi>0{x:=i;}else{x:=-i;}if*{x:=i;}else{x:=-i;}ifi:nat,j:nat:|i+j<10{asserti<10;}ifi==0{x:=0;}elseifi>0{x:=1;}else{x:=-1;}ifcasei==0=>x:=0;casei>0=>x:=1;casei<0=>x:=-1;}
The simplest form of anif
statement uses a guard that is a booleanexpression. For example,
ifx<0{x:=-x;}
Unlikematch
statements,if
statements do not have to be exhaustive:omitting theelse
block is the same as including an emptyelse
block. To ensure that anif
statement is exhaustive, use theif-case
statement documented below.
If the guard is an asterisk then a non-deterministic choice is made:
if*{print"True";}else{print"False";}
The then alternative of the if-statement must be a block statement;the else alternative may be either a block statement or another if statement.The condition of the if statement need not (but may) be enclosed in parentheses.
An if statement with a binding guard is non-deterministic;it will not be compiled if--enforce-determinism
is enabled(even if it can be proved that there is a unique value).An if statement with*
for a guard is non-deterministic and ghost.
Theif-case
statement using theAlternativeBlock
form is similar to theif...fi
construct used in the book “A Discipline of Programming” byEdsger W. Dijkstra. It is used for a multi-branchif
.
For example:
methodm(x:int,y:int)returns(max:int){if{casex<=y=>max:=y;casey<=x=>max:=x;}}
In this form, the expressions following thecase
keyword are calledguards. The statement is evaluated by evaluating the guards in anundetermined order until one is found that istrue
and the statementsto the right of=>
for that guard are executed. The statement requiresat least one of the guards to evaluate totrue
(that is,if-case
statements must be exhaustive: the guards must cover all cases).
In the if-with-cases, a sequence of statements may follow the=>
; itmay but need not be a block statement (a brace-enclosed sequence of statements).
The form that used...
(a refinement feature) as the guard is deprecated.
8.11. Match Statement (grammar)
Examples:
matchlist{caseNil=>{}caseCons(head,tail)=>printhead;}matchxcase1=>printx;case2=>vary:=x*x;printy;case_=>print"Other";// Any statement after is captured in this case.
Thematch
statement is used to do case analysis on a value of an expression.The expression may be a value of a basic type (e.g.int
), a newtype, oran inductive or coinductive datatype (which includes the built-in tuple types). The expression after thematch
keyword is called theselector. The selector is evaluated and then matched againsteach clause in order until a matching clause is found.
The process of matching the selector expression against the case patterns isthe same as for match expressions and is described inSection 9.31.2.
The selector need not be enclosed in parentheses; the sequence of cases may but need not be enclosed in braces.The cases need not be disjoint.The cases must be exhaustive, but you can use a wild variable (_
) or a simple identifier to indicate “match anything”.Please refer to thesection about case patterns to learn more about shadowing, constants, etc.
The code below shows an example of a match statement.
datatypeTree=Empty|Node(left:Tree,data:int,right:Tree)// Return the sum of the data in a tree.methodSum(x:Tree)returns(r:int){matchx{caseEmpty=>r:=0;caseNode(t1,d,t2)=>varv1:=Sum(t1);varv2:=Sum(t2);r:=v1+d+v2;}}
Note that theSum
method is recursive yet has nodecreases
annotation.In this case it is not needed because Dafny is able to deduce thatt1
andt2
aresmaller (structurally) thanx
. IfTree
had beencoinductive this would not have been possible sincex
might have beeninfinite.
8.12. While Statement (grammar)
Examples:
methodm(){vari:=10;while0<iinvariant0<=i<=10decreasesi{i:=i-1;}while*{}i:=*;whiledecreasesifi<0then-ielsei{casei<0=>i:=i+1;casei>0=>i:=i-1;}}
Loops
- may be a conventional loop with a condition and a block statement for a body
- need not have parentheses around the condition
- may have a
*
for the condition (the loop is then non-deterministic) - binding guards are not allowed
- may have a case-based structure
- may have no body — a bodyless loop is not compilable, but can be reaosnaed about
Importantly, loops needloop specifications in order for Dafny to prove thatthey obey expected behavior. In some cases Dafny can infer the loop specifications by analyzing the code,so the loop specifications need not always be explicit.These specifications are described inSection 7.6 andSection 8.15.
The general loop statement in Dafny is the familiarwhile
statement.It has two general forms.
The first form is similar to a while loop in a C-like language. Forexample:
methodm(){vari:=0;whilei<5{i:=i+1;}}
In this form, the condition following thewhile
is one of these:
- A boolean expression. If true it means execute one moreiteration of the loop. If false then terminate the loop.
- An asterisk (
*
), meaning non-deterministically yield eithertrue
orfalse
as the value of the condition
Thebody of the loop is usually a block statement, but it can alsobe missing altogether.A loop with a missing body may still pass verification, but any attemptto compile the containing program will result in an error message.When verifying a loop with a missing body, the verifier will skip attemptsto prove loop invariants and decreases assertions that would normally beasserted at the end of the loop body.There is more discussion about bodyless loops inSection 8.15.4.
The second form uses a case-based block. It is similar to thedo...od
construct used in the book “A Discipline of Programming” byEdsger W. Dijkstra. For example:
methodm(n:int){varr:=n;whiledecreasesif0<=rthenrelse-r{caser<0=>r:=r+1;case0<r=>r:=r-1;}}
For this form, the guards are evaluated in some undetermined orderuntil one is found that is true, in which case the corresponding statementsare executed and the while statement is repeated.If none of the guards evaluates to true, then theloop execution is terminated.
The form that used...
(a refinement feature) as the guard is deprecated.
8.13. For Loops (grammar)
Examples:
methodm()decreases*{fori:=0to10{}for_:=0to10{}fori:=0to*invarianti>=0decreases*{}fori:int:=10downto0{}fori:int:=10downto0}
Thefor
statement provides a convenient way to write some common loops.
The statement introduces a local variable with optional type, which is calledtheloop index. The loop index is in scope in the specification and the body,but not after thefor
loop. Assignments to the loop index are not allowed.The type of the loop index can typically be inferred; if so, it need not be givenexplicitly. If the identifier is not used, it can be written as_
, as illustratedin this repeat-20-times loop:
for_:=0to20{Body}
There are four basic variations of thefor
loop:
fori:T:=lotohiLoopSpec{Body}fori:T:=hidowntoloLoopSpec{Body}fori:T:=loto*LoopSpec{Body}fori:T:=hidownto*LoopSpec{Body}
Semantically, they are defined as the following respectivewhile
loops:
{var_lo,_hi:=lo,hi;assert_lo<=_hi&&forall_i:int::_lo<=_i<=_hi==>_iisT;vari:=_lo;whilei!=_hiinvariant_lo<=i<=_hiLoopSpecdecreases_hi-i{Bodyi:=i+1;}}{var_lo,_hi:=lo,hi;assert_lo<=_hi&&forall_i:int::_lo<=_i<=_hi==>_iisT;vari:=_hi;whilei!=loinvariant_lo<=i<=_hiLoopSpecdecreasesi-_lo{i:=i-1;Body}}{var_lo:=lo;assertforall_i:int::_lo<=_i==>_iisT;vari:=_lo;whiletrueinvariant_lo<=iLoopSpec{Bodyi:=i+1;}}{var_hi:=hi;assertforall_i:int::_i<=_hi==>_iisT;vari:=_hi;whiletrueinvarianti<=_hiLoopSpec{i:=i-1;Body}}
The expressionslo
andhi
are evaluated just once, before the loopiterations start.
Also, in all variations the values ofi
in the body are the valuesfromlo
to,but not including,hi
. This makes it convenient towrite common loops, including these:
fori:=0toa.Length{Process(a[i]);}fori:=a.Lengthdownto0{Process(a[i]);}
Nevertheless,hi
must be a legal value for the type of the index variable,since that is how the index variable is used in the invariant.
If the end-expression is not*
, then no explicitdecreases
isallowed, since such a loop is already known to terminate.If the end-expression is*
, then the absence of an explicitdecreases
clause makes it default todecreases*
. So, if the end-expression is*
and noexplicitdecreases
clause is given, the loop is allowed only in methodsthat are declared withdecreases*
.
The directionsto
ordownto
are contextual keywords. That is, these twowords are part of the syntax of thefor
loop, but they are not reservedkeywords elsewhere.
Just like for while loops, the body of a for-loop may be omitted duringverification. This suppresses attempts to check assertions (like invariants)that would occur at the end of the loop. Eventually, however a body mustbe provided; the compiler will not compile a method containing a body-lessfor-loop. There is more discussion about bodyless loops inSection 8.15.4.
8.14. Break and Continue Statements (grammar)
Examples:
classA{varf:int}methodm(a:A){labelx:whiletrue{if(*){break;}}labely:{varz:=1;if*{breaky;}z:=2;}}
Break and continue statements provide a means to transfer controlin a way different than the usual nested control structures.There are two forms of each of these statements: with and without a label.
If a label is used, the break or continue statement must be enclosed in a statementwith that label. The enclosing statement is called thetarget of the breakor continue.
Abreak
statement transfers control to the point immediatelyfollowing the target statement. For example, such a break statement can beused to exit a sequence of statements in a block statement beforereaching the end of the block.
For example,
labelL:{varn:=ReadNext();ifn<0{breakL;}DoSomething(n);}
is equivalent to
{varn:=ReadNext();if0<=n{DoSomething(n);}}
If no label is specified and the statement listsn
occurrences ofbreak
, then the statement must be enclosed inat leastn
levels of loop statements. Control continues after exitingn
enclosing loops. For example,
methodm(){fori:=0to10{forj:=0to10{labelX:{fork:=0to10{ifj+k==15{breakbreak;}}}}// control continues here after the "break break", exiting two loops}}
Note that a non-labeledbreak
pays attention only to loops, not to labeledstatements. For example, the labeled blockX
in the previous exampledoes not play a role in determining the target statement of thebreakbreak;
.
For acontinue
statement, the target statement must be a loop statement.The continue statement transfers control to the point immediatelybefore the closing curly-brace of the loop body.
For example,
methodm(){fori:=0to100{ifi==17{continue;}DoSomething(i);}}methodDoSomething(i:int){}
is equivalent to
methodm(){fori:=0to100{ifi!=17{DoSomething(i);}}}methodDoSomething(i:int){}
The same effect can also be obtained by wrapping the loop body in a labeledblock statement and then usingbreak
with a label, but that usually makesfor a more cluttered program:
methodm(){fori:=0to100{labelLoopBody:{ifi==17{breakLoopBody;}DoSomething(i);}}}methodDoSomething(i:int){}
Stated differently,continue
has the effect of ending the current loop iteration,after which control continues with any remaining iterations. This is most naturalforfor
loops. For awhile
loop, be careful to make progress toward terminationbefore acontinue
statement. For example, the following program snippet showsan easy mistake to make (the verifier will complain that the loop may not terminate):
methodm(){vari:=0;whilei<100{ifi==17{continue;// error: this would cause an infinite loop}DoSomething(i);i:=i+1;}}methodDoSomething(i:int){}
Thecontinue
statement can give a label, provided the label is a label of a loop.For example,
methodm(){labelOuter:fori:=0to100{forj:=0to100{ifi+j==19{continueOuter;}WorkIt(i,j);}PostProcess(i);// the "continue Outer" statement above transfers control to here}}methodWorkIt(i:int,j:int){}methodPostProcess(i:int){}
If a non-labeled continue statement listsn
occurrences ofbreak
before thecontinue
keyword, then the statement must be enclosed in at leastn+1
levelsof loop statements. The effect is tobreak
out of then
most closely enclosingloops and thencontinue
the iterations of the next loop. That is,n
occurrencesofbreak
followed by one morebreak;
will break out ofn
levels of loopsand then do abreak
, whereasn
occurrences ofbreak
followed bycontinue;
will break out ofn
levels of loops and then do acontinue
.
For example, theWorkIt
example above can equivalently be written without labelsas
methodm(){fori:=0to100{forj:=0to100{ifi+j==19{breakcontinue;}WorkIt(i,j);}PostProcess(i);// the "break continue" statement above transfers control to here}}methodWorkIt(i:int,j:int){}methodPostProcess(i:int){}
Note that a loop invariant is checked on entry to a loop and at the closing curly-braceof the loop body. It is not checked at break statements. For continue statements, the loop invariant is checked as usual at the closing curly-bracethat the continue statement jumps to.This checking ensures that the loop invariant holds at the very top ofevery iteration. Commonly, the only exit out of a loop happens when the loop guard evaluatestofalse
. Since no state is changed between the top of an iteration (where the loopinvariant is known to hold) and the evaluation of the loop guard, one can also rely onthe loop invariant to hold immediately following the loop. But the loop invariant maynot hold immediately following a loop if a loop iteration changes the program state andthen exits the loop with a break statement.
For example, the following program verifies:
methodm(){vari:=0;whilei<10invariant0<=i<=10{ifP(i){i:=i+200;break;}i:=i+1;}asserti==10||200<=i<210;}predicateP(i:int)
To explain the example, the loop invariant0<=i<=10
is known to hold at the very topof each iteration,that is, just before the loop guardi<10
is evaluated. If the loop guard evaluatestofalse
, then the negated guard condition (10<=i
) and the invariant hold, soi==10
will hold immediately after the loop. If the loop guard evaluates totrue
(that is,i<10
holds), then the loop body is entered. If the testP(i)
then evaluatestotrue
, the loop adds200
toi
and breaks out of the loop, so on such apath,200<=i<210
is known to hold immediately after the loop. This is summarizedin the assert statement in the example.So, remember, a loop invariant holds at the very top of every iteration, not necessarilyimmediately after the loop.
8.15. Loop Specifications
For some simple loops, such as those mentioned previously, Dafny can figureout what the loop is doing without more help. However, in general the usermust provide more information in order to help Dafny prove the effect ofthe loop. This information is provided by aloop specification. Aloop specification provides information about invariants, termination, andwhat the loop modifies.For additional tutorial information see [@KoenigLeino:MOD2011] or theonline Dafny tutorial.
8.15.1. Loop invariants
Loops present a problem for specification-based reasoning. There is no way toknow in advance how many times the code will go around the loop anda tool cannot reason about every one of a possibly unbounded sequence of unrollings.In order to consider all paths through a program, specification-basedprogram verification tools require loop invariants, which are another kind ofannotation.
A loop invariant is an expression that holds just prior to the loop test,that is, upon entering a loop andafter every execution of the loop body. It captures something that isinvariant, i.e. does not change, about every step of the loop. Now,obviously we are going to want to change variables, etc. each time aroundthe loop, or we wouldn’t need the loop. Like pre- and postconditions, aninvariant is a property that is preserved for each execution of the loop,expressed using the same boolean expressions we have seen. For example,
vari:=0;whilei<ninvariant0<=i{i:=i+1;}
When you specify an invariant, Dafny proves two things: the invariantholds upon entering the loop, and it is preserved by the loop. Bypreserved, we mean that assuming that the invariant holds at thebeginning of the loop (just prior to the loop test), we must show that executing the loop body oncemakes the invariant hold again. Dafny can only know upon analyzing theloop body what the invariants say, in addition to the loop guard (theloop condition). Just as Dafny will not discover properties of a methodon its own, it will not know that any but the most basic properties of a loopare preserved unless it is told via an invariant.
8.15.2. Loop termination
Dafny proves that code terminates, i.e. does not loop forever, by usingdecreases
annotations. For many things, Dafny is able to guess the rightannotations, but sometimes it needs to be made explicit.There are two places Dafny proves termination: loops and recursion.Both of these situations require either an explicit annotation or acorrect guess by Dafny.
Adecreases
annotation, as its name suggests, gives Dafny an expressionthat decreases with every loop iteration or recursive call. There are twoconditions that Dafny needs to verify when using adecreases
expression:
- that the expression actually gets smaller, and
- that it is bounded.
That is, the expression must strictly decrease in a well-founded ordering(cf.Section 12.7).
Many times, an integral value (natural or plain integer) is the quantitythat decreases, but other values can be used as well. In the case ofintegers, the bound is assumed to be zero.For each loop iteration thedecreases
expression at the end of the loopbody must be strictly smaller than its value at the beginning of the loopbody (after the loop test). For integers, the well-founded relation betweenx
andX
isx<X&&0<=X
.Thus if thedecreases
value (X
) is negative at theloop test, it must exit the loop, since there is no permitted value forx
to have at the end of the loop body.
For example, the following isa proper use ofdecreases
on a loop:
methodm(n:nat){vari:=n;while0<iinvariant0<=idecreasesi{i:=i-1;}}
Here Dafny has all the ingredients it needs to prove termination. Thevariablei
becomes smaller each loop iteration, and is bounded below byzero. Wheni
becomes 0, the lower bound of the well-founded order, controlflow exits the loop.
This is fine, except the loop is backwards compared to most loops, whichtend to count up instead of down. In this case, what decreases is not thecounter itself, but rather the distance between the counter and the upperbound. A simple trick for dealing with this situation is given below:
methodm(m:nat,n:int)requiresm<=n{vari:=m;whilei<ninvariant0<=i<=ndecreasesn-i{i:=i+1;}}
This is actually Dafny’s guess for this situation, as it seesi<n
andassumes thatn-i
is the quantity that decreases. The upper bound of theloop invariant implies that0<=n–i
, and gives Dafny a lower bound onthe quantity. This also works when the boundn
is not constant, such asin the binary search algorithm, where two quantities approach each other,and neither is fixed.
If thedecreases
clause of a loop specifies*
, then notermination check will be performed. Use of this feature is sound only withrespect to partial correctness.
8.15.3. Loop framing
The specification of a loop also includesframing, which says what theloop modifies. The loop frame includes both local variables and locationsin the heap.
For local variables, the Dafny verifier performs a syntacticscan of the loop body to find every local variable or out-parameter that occurs as a left-handside of an assignment. These variables are calledsyntactic assignment targets of the loop, orsyntactic loop targets for short.Any local variable or out-parameter that is not a syntactic assignment target is known by theverifier to remain unchanged by the loop.
The heap may or may not be a syntactic loop target. It is when the loop bodysyntactically contains a statement that can modify a heap location. Thisincludes calls to compiled methods, even if such a method has an emptymodifies
clause, since a compiled method is always allowed to allocatenew objects and change their values in the heap.
If the heap is not a syntactic loop target, then the verifier knows the heapremains unchanged by the loop. If the heapis a syntactic loop target,then the loop’s effectivemodifies
clause determines what is allowed to bemodified by iterations of the loop body.
A loop can usemodifies
clauses to declare the effectivemodifies
clauseof the loop. If a loop does not explicitly declare anymodifies
clause, thenthe effectivemodifies
clause of the loop is the effectivemodifies
clauseof the most tightly enclosing loop or, if there is no enclosing loop, themodifies
clause of the enclosing method.
In most cases, there is no need to give an explicitmodifies
clause for aloop. The one case where it is sometimes needed is if a loop modifies lessthan is allowed by the enclosing method. Here are two simple methods thatillustrate this case:
classCell{vardata:int}methodM0(c:Cell,d:Cell)requiresc!=dmodifiesc,densuresc.data==d.data==100{c.data,d.data:=100,0;vari:=0;whilei<100invariantd.data==i// Needs "invariant c.data == 100" or "modifies d" to verify{d.data:=d.data+1;i:=i+1;}}methodM1(c:Cell)modifiescensuresc.data==100{c.data:=100;vari:=0;whilei<100// Needs "invariant c.data == 100" or "modifies {}" to verify{vartmp:=newCell;tmp.data:=i;i:=i+1;}}
InM0
, the effectivemodifies
clause of the loop ismodifiesc,d
. Therefore,the method’s postconditionc.data==100
is not provable. To remedy the situation,the loop needs to be declared either withinvariantc.data==100
or withmodifiesd
.
Similarly, the effectivemodifies
clause of the loop inM1
ismodifiesc
. Therefore,the method’s postconditionc.data==100
is not provable. To remedy the situation,the loop needs to be declared either withinvariantc.data==100
or withmodifies{}
.
When a loop has an explicitmodifies
clause, there is, at the top ofevery iteration, a proof obligation that
- the expressions given in the
modifies
clause arewell-formed, and - everything indicated in the loop
modifies
clause is allowed to be modified by the(effectivemodifies
clause of the) enclosing loop or method.
8.15.4. Body-less methods, functions, loops, and aggregate statements
Methods (including lemmas), functions, loops, andforall
statements are ordinarilydeclared with a body, that is, a curly-braces pair that contains (for methods, loops, andforall
)a list of zero-or-more statements or (for a function) an expression. In each case, Dafny syntacticallyallows these constructs to be given without a body (no braces at all). This is to allow programmers totemporarily postpone the development of the implementation of the method, function, loop, oraggregate statement.
If a method has no body, there is no difference for callers of the method. Callers still reasonabout the call in terms of the method’s specification. But without a body, the verifier hasno method implementation to check against the specification, so the verifier is silently happy.The compiler, on the other hand, will complain if it encounters a body-less method, because thecompiler is supposed to generate code for the method, but it isn’t clever enough to do that byitself without a given method body. If the method implementation is provided by code writtenoutside of Dafny, the method can be marked with an{:extern}
annotation, in which case thecompiler will no longer complain about the absence of a method body; the verifier will not object either, even though there is now no proof that the Dafny specifications are satisfiedby the external implementation.
A lemma is a special kind of (ghost) method. Callers are therefore unaffected by the absence of a body,and the verifier is silently happy with not having a proof to check against the lemma specification.Despite a lemma being ghost, it is still the compiler that checks for, and complains about,body-less lemmas. A body-less lemma is an unproven lemma, which is often known as anaxiom.If you intend to use a lemma as an axiom, omit its body and add the attribute{:axiom}
, whichcauses the compiler to suppress its complaint about the lack of a body.
Similarly, calls to a body-less function use only the specification of the function. Theverifier is silently happy, but the compiler complains (whether or not the function is ghost).As for methods and lemmas, the{:extern}
and{:axiom}
attributes can be used to suppress thecompiler’s complaint.
By supplying a body for a method or function, the verifier will in effect show the feasibility ofthe specification of the method or function. By supplying an{:extern}
or{:axiom}
attribute,you are taking that responsibility into your own hands. Common mistakes include forgetting toprovide an appropriatemodifies
orreads
clause in the specification, or forgetting thatthe results of functions in Dafny (unlike in most other languages) must be deterministic.
Just like methods and functions have two sides, callers and implementations, loops also havetwo sides. One side (analogous to callers) is the context that uses the loop. That context treatsthe loop in the same way, using its specifications, regardless of whether or not the loop has a body. The other sideis the loop body, that is, the implementation of each loop iteration. The verifier checksthat the loop body maintains the loop invariant and that the iterations will eventually terminate,but if there is no loop body, the verifier is silently happy. This allows you to temporarilypostpone the authoring of the loop body until after you’ve made sure that the loop specificationis what you need in the context of the loop.
There is one thing that works differently for body-less loops than for loops with bodies.It is the computation of syntactic loop targets, which become part of the loop frame(seeSection 8.15.3). For a body-less loop, the local variablescomputed as part of the loop frame are the mutable variables that occur free in theloop specification. The heap is considered a part of the loop frame if it is usedfor mutable fields in the loop specification or if the loop has an explicitmodifies
clause.The IDE will display the computed loop frame in hover text.
For example, consider
classCell{vardata:intconstK:int}methodBodylessLoop(n:nat,c:Cell)requiresc.K==8modifiesc{c.data:=5;vara,b:=n,n;fori:=0toninvariantc.K<10invarianta<=ninvariantc.data<10asserta==n;assertb==n;assertc.data==5;}
The loop specification mentions local variablea
, and thusa
is considered part ofthe loop frame. Since what the loop invariant says abouta
is not strong enough toprove the assertiona==n
that follows the loop, the verifier complains about thatassertion.
Local variableb
is not mentioned in the loop specification, and thusb
is notincluded in the loop frame. Since in-parametern
is immutable, it is not includedin the loop frame, either, despite being mentioned in the loop specification. Forthese reasons, the assertionb==n
is provable after the loop.
Because the loop specification mentions the mutable fielddata
, the heap becomespart of the loop frame. Since the loop invariant is not strong enough to prove theassertionc.data==5
that follows the loop, the verifier complains about thatassertion. On the other hand, hadc.data<10
not been mentioned in the loopspecification, the assertion would be verified, since fieldK
is then the onlyfield mentioned in the loop specification andK
is immutable.
Finally, the aggregate statement (forall
) can also be given without a body. Sucha statement claims that the givenensures
clause holds true for all values ofthe bound variables that satisfy the given range constraint. If the statement hasno body, the program is in effect omitting the proof, much like a body-less lemmais omitting the proof of the claim made by the lemma specification. As with theother body-less constructs above, the verifier is silently happy with a body-lessforall
statement, but the compiler will complain.
8.16. Print Statement (grammar)
Examples:
print0,x,list,array;
Theprint
statement is used to print the values of a comma-separatedlist of expressions to the console (standard-out). The generated code usestarget-language-specific idioms to perform this printing.The expressions may of course include strings that are usedfor captions. There is no implicit new line added, so to add a newline you must include"\n"
as part of one of the expressions.Dafny automatically creates implementations of methods that convert values to stringsfor all Dafny data types. For example,
datatypeTree=Empty|Node(left:Tree,data:int,right:Tree)methodMain(){varx:Tree:=Node(Node(Empty,1,Empty),2,Empty);print"x=",x,"\n";}
produces this output:
x=Tree.Node(Tree.Node(Tree.Empty, 1, Tree.Empty), 2, Tree.Empty)
Note that Dafny does not have method overriding and there is no mechanism tooverride the built-in value->string conversion. Nor is there a way toexplicitly invoke this conversion.One can always write an explicit function to convert a data value to a stringand then call it explicitly in aprint
statement or elsewhere.
By default, Dafny does not keep track of print effects, but this can be changedusing the--track-print-effects
command line flag.print
statements are allowedonly in non-ghost contexts and not in expressions, with one exception.The exception is that a function-by-method may containprint
statements,whose effect may be observed as part of the run-time evaluation of such functions(unless--track-print-effects
is enabled).
The verifier checks that each expression is well-defined, but otherwise ignores theprint
statement.
Note:print
writes to standard output. To improve compatibility withnative code and external libraries, the process of encoding Dafny strings passedtoprint
into standard-output byte strings is left to the runtime of thelanguage that the Dafny code is compiled to (some language runtimes use UTF-8 inall cases; others obey the current locale or console encoding).
In most cases, the standard-output encoding can be set before running thecompiled program using language-specific flags or environment variables(e.g.-Dfile.encoding=
for Java). This is in fact howdafnyrun
operates:it uses language-specific flags and variables to enforce UTF-8 output regardlessof the target language (but note that the C++ and Go backends currently havelimited support for UTF-16 surrogates).
8.17. Assert statement (grammar)
Examples:
asserti>0;assertIsPositive:i>0;asserti>0by{...}
Assert
statements are used to express logical propositions that areexpected to be true. Dafny will attempt to prove that the assertionis true and give an error if the assertion cannot be proven.Once the assertion is proved,its truth may aid in proving subsequent deductions.Thus if Dafny is having a difficult time verifying a method,the user may help by inserting assertions that Dafny can prove,and whose truth may aid in the larger verification effort,much as lemmas might be used in mathematical proofs.
Assert
statements are ignored by the compiler.
In theby
form of theassert
statement, there is an additional block of statements that provide the Dafny verifier with additional proof steps.Those statements are often a sequence oflemmas,calc
statements,reveal
statements or otherassert
statements,combined with ghost control flow, ghost variable declarations and ghost update statements of variables declared in theby
block.The intent is that those statements be evaluated in support of proving theassert
statement.For that purpose, they could be simply inserted before theassert
statement.But by using theby
block, the statements in the block are discarded after the assertion is proved.As a result, the statements in the block do not clutter or confuse the solver in performing subsequentproofs of assertions later in the program. Furthermore, by isolating the statements in theby
block,their purpose – to assist in proving the given assertion – is manifest in the structure of the code.
Examples of this form of assert are given in the section of thereveal
statement and inDifferent Styles of Proof
An assert statement may have a label, whose use is explained inSection 8.20.1.
The attributes recognized for assert statements are discussed inSection 11.4.
Using...
as the argument of the statement is deprecated.
An assert statement can havecustom error and success messages.
8.18. Assume Statement (grammar)
Examples:
assumei>0;assume{:axiom}i>0==>-i<0;
Theassume
statement lets the user specify a logical propositionthat Dafny may assume to be true without proof. If in fact theproposition is not true this may lead to invalid conclusions.
Anassume
statement would ordinarily be used as part of a largerverification effort where verification of some other part ofthe program required the proposition. By using theassume
statementthe other verification can proceed. Then when that is completed theuser would come back and replace theassume
withassert
.
To help the user not forget about that last step, a warning is emitted for any assume statement.Adding the{:axiom}
attribute to the assume will suppress the warning,indicating the user takes responsibility for being absolutely sure that the proposition is indeed true.
Using...
as the argument of the statement is deprecated.
8.19. Expect Statement (grammar)
Examples:
expecti>0;expecti>0,"i is positive";
Theexpect
statement states a boolean expression that is(a) assumed to be true by the verifierand (b) checked to be trueat run-time. That is, the compiler inserts into the run-time executable acheck that the given expression is true; if the expression is false, thenthe execution of the program halts immediately. If a second argument isgiven, it may be a value of any type.That value is converted to a string (just like theprint
statement)and the string is includedin the message emitted by the programwhen it halts; otherwise a default message is emitted.
Because the expect expression and optional second argument are compiled, they cannot be ghost expressions.
Theexpect
statement behaves likeassume
for the verifier, but also inserts a run-time check that theassumption is indeed correct (for the test cases used at run-time).
Here are a few use-cases for theexpect
statement.
A) To check the specifications of external methods.
Consider an external methodRandom
that takes anat
as inputand returns anat
value that is less than the input.Such a method could be specified as
method{:extern}Random(n:nat)returns(r:nat)ensuresr<n
But because there is no body forRandom
(only the external non-dafny implementation),it cannot be verified thatRandom
actually satisfies this specification.
To mitigate this situation somewhat, we can define a wrapper function,Random'
,that callsRandom
but in which we can put some run-time checks:
method{:extern}Random(n:nat)returns(r:nat)methodRandom'(n:nat)returns(r:nat)ensuresr<n{r:=Random(n);expectr<n;}
Here we can verify thatRandom'
satisfies its own specification,relying on the unverified specification ofRandom
.But we are also checking at run-time that any input-output pairs forRandom
encountered during executiondo satisfy the specification,as they are checked by theexpect
statement.
Note, in this example, two problems still remain.One problem is that the out-parameter of the externRandom
has typenat
,but there is no check that the value returned really is non-negative.It would be better to declare the out-parameter ofRandom
to beint
andto include0<=r
in the condition checked by theexpect
statement inRandom'
.The other problem is thatRandom
surely will needn
to be strictly positive.This can be fixed by addingrequiresn!=0
toRandom'
andRandom
.
B) Run-time testing
Verification and run-time testing are complementaryand both have their role in assuring that software does what is intended.Dafny can produce executablesand these can be instrumented with unit tests.Annotating a method with the{:test}
attributeindicates to the compilerthat it should produce target codethat is correspondingly annotated to mark the methodas a unit test (e.g., an XUnit test) in the target language.Alternatively, thedafnytest
command will produce a main methodthat invokes all methods with the{:test}
attribute, and hence does notdepend on any testing framework in the target language.Within such methods one might useexpect
statements (as well asprint
statements)to insert checks that the target program is behaving as expected.
C) Debugging
While developing a new program, one work style uses proof attempts and runtime tests in combination.If an assert statement does not prove, one might run the program with a corresponding expect statementto see if there are some conditions when the assert is not actually true. So one might havepaired assert/expect statements:
assert_P_;expect_P_;
Once the program is debugged, both statements can be removed.Note that it is important that theassert
come before theexpect
, becauseby the verifier, theexpect
is interpreted as anassume
, which would automatically makea subsequentassert
succeed.
D) Compiler tests
The same approach might be taken to assure that compiled code is behaving at run-time consistently with the statically verified code,one can again use paired assert/expect statements with the same expression:
assert_P_;expect_P_;
The verifier will check thatP is always true at the given point in a program(at theassert
statement).
At run-time, the compiler will insert checks that the same predicate,in theexpect
statement, is true.Any difference identifies a compiler bug.Again theexpect
must be after theassert
:if theexpect
is first,then the verifier will interpret theexpect
like anassume
,in which case theassert
will be proved triviallyand potential unsoundness will be hidden.
Using...
as the argument of the statement is deprecated.
8.20. Reveal Statement (grammar)
Examples:
revealf(),L;
Thereveal
statement makes available to the solver information that is otherwise not visible, as described in the following subsections.
8.20.1. Revealing assertions
If an assert statement has an expression label, then a proof of that assertion is attempted, but the assertion itselfis not used subsequently. For example, consider
methodm(i:int){assertx:i==0;// Failsasserti==0;// Fails also because the label 'x:' hides the first assertion}
The first assertion fails. Without the labelx:
, the second would succeed because after a failing assertion, the assertion is assumed in the context of the rest of the program. But with the label, the first assertion is hidden fromthe rest of the program. That assertion can berevealed by adding areveal
statement:
methodm(i:int){assertx:i==0;// Failsrevealx;asserti==0;// Now succeeds}
or
methodm(i:int){assertx:i==0;// Failsasserti==0by{revealx;}// Now succeeds}
At the point of thereveal
statement, the labeled assertion is made visible and can be used in proving the second assertion.In this example there is no point to labeling an assertion and then immediately revealing it. More useful are the cases wherethe reveal is in an assert-by block or much later in the method body.
8.20.2. Revealing preconditions
In the same way as assertions, preconditions can be labeled.Within the body of a method, a precondition is an assumption; if the precondition is labeled then that assumption is not visible in the body of the method.Areveal
statement naming the label of the precondition then makes the assumption visible.
Here is a toy example:
methodm(x:int,y:int)returns(z:int)requiresL:0<yensuresz==x+yensuresx<z{z:=x+y;}
The above method will not verify. In particular, the second postcondition cannot be proved.However, if we add arevealL;
statement in the body of the method, then the precondition is visible and both postconditions can be proved.
One could also use this style:
methodm(x:int,y:int)returns(z:int)requiresL:0<yensuresz==x+yensuresx<z{z:=x+y;assertx<zby{revealL;}}
The reason to possibly hide a precondition is the same as the reason to hide assertions: sometimes less information is better for the solver as it helps the solver focus attention on relevant information.
Section 7 ofhttp://leino.science/papers/krml276.html provides an extended illustration of this technique to make all the dependencies of anassert
explicit.
8.20.3. Hiding and revealing function bodies
By default, function bodies are revealed and available for constructing proofs of assertions that use those functions.However, if a function body is not necessary for a proof, the runtime of the proof can be improved by hiding that body.To do this, use the hide statement. Here’s an example:
// We are using the options --isolate-assertions and --type-system-refreshmethodOuter(x:int)requiresComplicatedBody(x){hideComplicatedBody;// This hides the body of ComplicatedBody for the remainder of the method.// The body of ComplicatedBody is not needed to prove the requires of Innervary:=Inner(x);// We reveal ComplicatedBody inside the following expression, to prove that we are not dividing by zerovarz:=(revealComplicatedBody;10/x);}methodInner(x:int)returns(r:int)requiresComplicatedBody(x)predicateComplicatedBody(x:int){x!=0&&true// pretend true is complicated}
Here is a larger example that shows the rules for hide and reveal statements when used on functions:
// We are using the options --isolate-assertions and --type-system-refreshpredicateP(){true}predicateQ(x:bool)requiresxmethodFoo(){varq1:=Q(hideP;P());// error, precondition not satisfiedvarq2:=Q(hideP;revealP;P());// no errorhide*;varq3:=Q(P());// error, precondition not satisfiedvarq4:=Q(revealP;P());// no errorif(*){revealP;assertP();}else{assertP();// error}revealP;if(*){assertP();}else{hide*;assertP();// error}hide*;if(*){revealP;}else{revealP;}assertP();// error, since the previous two reveal statements are out of scope}
8.20.4. Revealing constants
Aconst
declaration can beopaque
. If so the value of the constant is not known in reasoning about its uses, just its type and thefact that the value does not change. The constant’s identifier can be listed in a reveal statement. In that case, like other revealed items,the value of the constant will be known to the reasoning engine until the end of the block containing the reveal statement.
A label or locally declared name in a method body will shadow an opaque constant with the same name outside the method body,making it unable to be revealed without using a qualified name.
8.21. Forall Statement (grammar)
Examples:
foralli|0<=i<a.Length{a[i]:=0;}foralli|0<=i<100{P(i);// P a lemma}foralli|0<=i<100ensuresi<1000{}
Theforall
statement executes the bodysimultaneously for all quantified values in the specified quantifier domain.You can find more details aboutquantifier domains here.
There are several variant uses of theforall
statement and there are a number of restrictions.Aforall
statement can be classified as one of the following:
- Assign - the
forall
statement is used for simultaneous assignment.The target must be an array element or an object field. - Call - The body consists of a single call to a ghost method without side effects
- Proof - The
forall
hasensure
expressions which are effectivelyquantified or proved by the body (if present).
Anassignforall
statement performs simultaneous assignment.The left-hand sides must denote different l-values, unless thecorresponding right-hand sides also coincide.
The following is an excerpt of an example given by Leino inDeveloping Verified Programs with Dafny.When the buffer holding the queue needs to be resized,theforall
statement is used to simultaneously copy the old contentsinto the new buffer.
classSimpleQueue<Data(0)>{ghostvarContents:seq<Data>vara:array<Data>// Buffer holding contents of queue.varm:int// Index head of queue.varn:int// Index just past end of queuemethodEnqueue(d:Data)requiresa.Length>0requires0<=m<=n<=a.Lengthmodifiesthis,this.aensuresContents==old(Contents)+[d]{ifn==a.Length{varb:=a;ifm==0{b:=newData[2*a.Length];}foralli|0<=i<n-m{b[i]:=a[m+i];}a,m,n:=b,0,n-m;}a[n],n,Contents:=d,n+1,Contents+[d];}}
Here is an example of acallforall
statement and thecallee. This is contained in theCloudMake-ConsistentBuilds.dfy
test in the Dafny repository.
methodm(){forallcmd',deps',e'|Hash(Loc(cmd',deps',e'))==Hash(Loc(cmd,deps,e)){HashProperty(cmd',deps',e',cmd,deps,e);}}lemmaHashProperty(cmd:Expression,deps:Expression,ext:string,cmd':Expression,deps':Expression,ext':string)requiresHash(Loc(cmd,deps,ext))==Hash(Loc(cmd',deps',ext'))ensurescmd==cmd'&&deps==deps'&&ext==ext'
The following example of aproofforall
statement comes from the same file:
forallp|pinDomSt(stCombinedC.st)&&pinDomSt(stExecC.st)ensuresGetSt(p,stCombinedC.st)==GetSt(p,stExecC.st){assertDomSt(stCombinedC.st)<=DomSt(stExecC.st);assertstCombinedC.st==Restrict(DomSt(stCombinedC.st),stExecC.st);}
More generally, the statement
forallx|P(x){Lemma(x);}
is used to invokeLemma(x)
on allx
for whichP(x)
holds. IfLemma(x)
ensuresQ(x)
, then the forall statement establishes
forallx::P(x)==>Q(x).
Theforall
statement is also used extensively in the de-sugared forms ofco-predicates and co-lemmas. Seedatatypes.
8.22. Modify Statement (grammar)
The effect of themodify
statementis to say that some undeterminedmodifications have been made to any or all of the memorylocations specified by the givenframe expressions.In the following example, a value is assigned to fieldx
followed by amodify
statement that may modify any fieldin the object. After that we can no longer prove that the fieldx
still has the value we assigned to it. The now unknown valuesstill are values of their type (e.g. of the subset type or newtype).
classMyClass{varx:intmethodN()modifiesthis{x:=18;modifythis;assertx==18;// error: cannot conclude this here}}
Using...
as the argument of the statement is deprecated.
The form of themodify
statement which includes a blockstatement is also deprecated.
Thehavoc assignment also sets a variable or fieldto some arbitrary (but type-consistent) value. The difference is thatthe havoc assignment acts on one LHS variable or memory location;the modify statement acts on all the fields of an object.
8.23. Calc Statement (grammar)
See also:Verified Calculations.
Thecalc
statement supportscalculational proofs using a languagefeature calledprogram-oriented calculations (poC). This feature wasintroduced and explained in the [Verified Calculations] paper by Leinoand Polikarpova[@LEINO:Dafny:Calc]. Please see that paper for a morecomplete explanation of thecalc
statement. We here mention only thehighlights.
Calculational proofs are proofs by stepwise formula manipulationas is taught in elementary algebra. The typical example is to provean equality by starting with a left-hand-side and through a series oftransformations morph it into the desired right-hand-side.
Non-syntactic rules further restrict hints to only ghost and side-effectfree statements, as well as imposing a constraint that onlychain-compatible operators can be used together in a calculation. Thenotion of chain-compatibility is quite intuitive for the operatorssupported by poC; for example, it is clear that “<” and “>” cannot be used withinthe same calculation, as there would be no relation to conclude betweenthe first and the last line. See the [paper][Verified Calculations] fora more formal treatment of chain-compatibility.
Note that we allow a single occurrence of the intransitive operator “!=” toappear in a chain of equalities (that is, “!=” is chain-compatible withequality but not with any other operator, including itself). Calculationswith fewer than two lines are allowed, but have no effect. If a stepoperator is omitted, it defaults to the calculation-wide operator,defined after thecalc
keyword. If that operator is omitted, it defaultsto equality.
Here is an example usingcalc
statements to prove an elementaryalgebraic identity. As it turns out, Dafny is able to prove this withoutthecalc
statements, but the example illustrates the syntax.
lemmadocalc(x:int,y:int)ensures(x+y)*(x+y)==x*x+2*x*y+y*y{calc{(x+y)*(x+y);==// distributive law: (a + b) * c == a * c + b * cx*(x+y)+y*(x+y);==// distributive law: a * (b + c) == a * b + a * cx*x+x*y+y*x+y*y;==calc{y*x;==x*y;}x*x+x*y+x*y+y*y;==calc{x*y+x*y;==// a = 1 * a1*x*y+1*x*y;==// Distributive law(1+1)*x*y;==2*x*y;}x*x+2*x*y+y*y;}}
Here we started with(x+y)*(x+y)
as the left-hand-sideexpressions and gradually transformed it using distributive,commutative and other laws into the desired right-hand-side.
The justification for the steps are given as comments or asnestedcalc
statements that prove equality of some sub-partsof the expression.
The==
operators show the relation betweenthe previous expression and the next. Because of the transitivity ofequality we can then conclude that the original left-hand-side isequal to the final expression.
We can avoid having to supply the relational operator betweenevery pair of expressions by giving a default operator betweenthecalc
keyword and the opening brace as shown in this abbreviatedversion of the above calc statement:
lemmadocalc(x:int,y:int)ensures(x+y)*(x+y)==x*x+2*x*y+y*y{calc=={(x+y)*(x+y);x*(x+y)+y*(x+y);x*x+x*y+y*x+y*y;x*x+x*y+x*y+y*y;x*x+2*x*y+y*y;}}
And since equality is the default operator, we could have omittedit after thecalc
keyword.The purpose of the block statements or thecalc
statements betweenthe expressions is to provide hints to aid Dafny in proving thatstep. As shown in the example, comments can also be used to aidthe human reader in cases where Dafny can prove the step automatically.
8.24. Opaque Block (grammar)
As a Dafny sequence of statements grows in length, it can become harder to verify later statements in the block. With each statement, new information can become available, and with each modification of the heap, it becomes more expensive to access information from an older heap version. To reduce the verification complexity of long lists of statements, Dafny users can extract part of this block into a separate method or lemma. However, doing so introduces some boilerplate, which is where opaque blocks come in. They achieve a similar effect on verification performance as extracting code, but without the boilerplate.
An opaque block is similar to a block statement: it contains a sequence of zero or more statements, enclosed by curly braces. However, an opaque block is preceded by the keyword ‘opaque’, and may define ensures and modifies clauses before the curly braces. Anything that happens inside the block is invisible to the statements that come after it, unless it is specified by the ensures clause. Here is an example:
methodOpaqueBlockUser()returns(x:int)ensuresx>4{x:=1;vary:=1;opaqueensuresx>3{x:=x+y;x:=x+2;}assertx==4;// errorx:=x+1;}
By default, the modifies clause of an opaque block is the same as that of the enclosing context. Opaque blocks may be nested.
9. Expressions
Dafny expressions come in three flavors.
- The bulk of expressions have no side-effects and can be used withinmethods, functions, and specifications, and in either compiled or ghost code.
- Some expressions, calledright-hand-side expressions,do have side-effects and may only be used in specific syntactic locations,such as the right-hand-side of update (assignment) statements; object allocation and method calls are two typical examples ofright-hand-side expressions. Note that method calls are syntacticallyindistinguishable from function calls; both are Expressions (PrimaryExpressionswith anArgumentList suffix). However, method calls are semantically permittedonly in right-hand-side expression locations.
- Some expressions are allowed only in specifications and other ghost code,as listedhere.
The grammar of Dafny expressions follows a hierarchy thatreflects the precedence of Dafny operators. The followingtable shows the Dafny operators and their precedencein order of increasing binding power.
operator | precedence | description |
---|---|---|
; | 0 | That isLemmaCall; Expression |
<==> | 1 | equivalence (if and only if) |
==> | 2 | implication (implies) |
<== | 2 | reverse implication (follows from) |
&& ,& | 3 | conjunction (and) |
|| ,| | 3 | disjunction (or) |
== | 4 | equality |
==#[k] | 4 | prefix equality (coinductive) |
!= | 4 | disequality |
!=#[k] | 4 | prefix disequality (coinductive) |
< | 4 | less than |
<= | 4 | at most |
>= | 4 | at least |
> | 4 | greater than |
in | 4 | collection membership |
!in | 4 | collection non-membership |
!! | 4 | disjointness |
<< | 5 | left-shift |
>> | 5 | right-shift |
+ | 6 | addition (plus) |
- | 6 | subtraction (minus) |
* | 7 | multiplication (times) |
/ | 7 | division (divided by) |
% | 7 | modulus (mod) |
| | 8 | bit-wise or |
& | 8 | bit-wise and |
^ | 8 | bit-wise exclusive-or (not equal) |
as operation | 9 | type conversion |
is operation | 9 | type test |
- | 10 | arithmetic negation (unary minus) |
! | 10 | logical negation, bit-wise complement |
Primary Expressions | 11 |
9.1. Lemma-call expressions (grammar)
Examples:
vara:=L(a,b);a*b
This expression has the formS;E
.The type of the expression is the type ofE
.S
must be a lemma call (though the grammar appears more lenient).The lemma introduces a fact necessary to establish properties ofE
.
Sometimes an expression will fail unless some relevant fact is known.In the following example theF_Fails
function fails to verifybecause theFact(n)
divisor may be zero. But precedingthe expression by a lemma that ensures that the denominatoris not zero allows functionF_Succeeds
to succeed.
functionFact(n:nat):nat{ifn==0then1elsen*Fact(n-1)}lemmaL(n:nat)ensures1<=Fact(n){}functionF_Fails(n:nat):int{50/Fact(n)// error: possible division by zero}functionF_Succeeds(n:nat):int{L(n);// note, this is a lemma call in an expression50/Fact(n)}
One restriction is that a lemma call in this form is permitted only in situations in which the expression itself is not terminated by a semicolon.
A second restriction is thatE
is not always permitted to contain lambda expressions, such as in the expressions that are the body of a lambda expression itself, function, method and iterator specifications,and if and while statements with guarded alternatives.
A third restriction is thatE
is not always permitted to contain a bit-wise or (|
) operator, because it would be ambiguous with the vertical bar used in comprehension expressions.
Note that the effect of the lemma call only extends to the succeeding expressionE
(which may be another;
expression).
9.2. Equivalence Expressions (grammar)
Examples:
AA<==>BA<==>C==>D<==>B
An Equivalence Expression that contains one or more<==>
s isa boolean expression and all the operandsmust also be boolean expressions. In that case each<==>
operator tests for logical equality which is the same asordinary equality (but with a different precedence).
SeeSection 5.2.1.1 for an explanation of the<==>
operator as compared with the==
operator.
The<==>
operator is commutative and associative:A<==>B<==>C
and(A<==>B)<==>C
andA<==>(B<==>C)
andC<==>B<==>A
are all equivalent and are all true iff an even number of operands are false.
9.3. Implies or Explies Expressions (grammar)
Examples:
A==>BA==>B==>C==>DB<==A
SeeSection 5.2.1.3 for an explanationof the==>
and<==
operators.
9.4. Logical Expressions (grammar)
Examples:
A&&BA||B&&A&&B&&C
Note that the Dafny grammar allows a conjunction or disjunction to beprefixed with&&
or||
respectively. This form simply allows aparallel structure to be written:
methodm(x:object?,y:object?,z:object?){varb:bool:=&&x!=null&&y!=null&&z!=null;}
This is purely a syntactic convenience allowing easy edits such as reorderinglines or commenting out lines without having to check that the infixoperators are always where they should be.
Note also that&&
and||
cannot be mixed without using parentheses:A&&B||C
is not permitted. Write(A&&B)||C
orA&&(B||C)
instead.
SeeSection 5.2.1.2 for an explanationof the&&
and||
operators.
9.5. Relational Expressions (grammar)
Examples:
x==yx!=yx<yx>=yxinyx!inyx!!yx==#[k]y
The relation expressions compare two or more terms.As explained inthe section about basic types,==
,!=
,<
,>
,<=
, and>=
arechaining.
Thein
and!in
operators apply to collection types as explained inSection 5.5 and represent membership or non-membershiprespectively.
The!!
represents disjointness for sets and multisets as explained inSection 5.5.1 andSection 5.5.2.
x==#[k]y
is the prefix equality operator that comparescoinductive values for equality to a nesting level of k, asexplained inthe section about co-equality.
9.6. Bit Shifts (grammar)
Examples:
k<<5j>>i
These operators are the left and right shift operators for bit-vector values.They take a bit-vector value and anint
, shifting the bits by the givenamount; the result has the same bit-vector type as the LHS.For the expression to be well-defined, the RHS value must be in the range 0 to the number ofbits in the bit-vector type, inclusive.
The operations are left-associative:a<<i>>j
is(a<<i)>>j
.
9.7. Terms (grammar)
Examples:
x+y-z
Terms
combineFactors
by adding or subtracting.Addition has these meanings for different types:
- arithmetic addition for numeric types (Section 5.2.2])
- union for sets and multisets (Section 5.5.1 andSection 5.5.2)
- concatenation for sequences (Section 5.5.3)
- map merging for maps (Section 5.5.4)
Subtraction is
- arithmetic subtraction for numeric types
- set or multiset subtraction for sets and multisets
- domain subtraction for maps.
All addition operations are associative. Arithmetic addition and union are commutative. Subtraction is neither; it groups to the left as expected:x-y-z
is(x-y)-z
.
9.8. Factors (grammar)
Examples:
x*yx/yx%y
AFactor
combines expressions using multiplication,division, or modulus. For numeric types these are explained inSection 5.2.2.As explained there,/
and%
onint
values representEuclideaninteger division and modulus and not the typical C-like programminglanguage operations.
Only*
has a non-numeric application. It represents set or multisetintersection as explained inSection 5.5.1 andSection 5.5.2.
*
is commutative and associative;/
and%
are neither but do group to the left.
9.9. Bit-vector Operations (grammar)
Examples:
x|yx&yx^y
These operations take two bit-vector values of the same type, returninga value of the same type. The operations perform bit-wiseor (|
),and (&
), andexclusive-or (^
). To perform bit-wise equality, use^
and!
(unary complement) together. (==
is boolean equality of the whole bit-vector.)
These operations are associative and commutative but do not associate with each other.Use parentheses:a&b|c
is illegal; use(a&b)|c
ora&(b|c)
instead.
Bit-vector operations are not allowed in some contexts.The|
symbol is used both for bit-wise or and as the delimiter in acardinality expression: an ambiguity arises ifthe expression E in|E|
contains a|
. This situation is easilyremedied: just enclose E in parentheses, as in|(E)|
.The only type-correct way this can happen is if the expression isa comprehension, as in|setx:int::x|0x101|
.
9.10. As (Conversion) and Is (type test) Expressions (grammar)
Examples:
easMyClassiasbv8eisMyClass
Theas
expression converts the given LHS to the type stated on the RHS,with the result being of the given type. The following combinationsof conversions are permitted:
- Any type to itself
- Any int or real based numeric type or bit-vector type to another int or real based numeric type or bit-vector type
- Any base type to a subset or newtype with that base
- Any subset or newtype to its base type or a subset or newtype of the same base
- Any type to a subset or newtype that has the type as its base
- Any trait to a class or trait that extends (perhaps recursively) that trait
- Any class or trait to a trait extended by that class or trait
Some of the conversions above are already implicitly allowed, without theas
operation, such as from a subset type to its base. In any case, itmust be able to be proved that the value of the given expression is alegal value of the given type. For example,5asMyType
is permitted (by the verifier) only if5
is a legitimate value ofMyType
(which must be a numeric type).
Theas
operation is like a grammatical suffix or postfix operation.However, note that the unary operations bind more tightly than doesas
.That is-5asnat
is(-5)asnat
(which fails), whereasa*basnat
isa*(basnat)
. On the other hand,-a[4]
is-(a[4])
.
Theis
expression is grammatically similar to theas
expression, with thesame binding power. Theis
expression is a type test thatreturns abool
value indicating whether the LHS expression is a legalvalue of the RHS type. The expression can be used to checkwhether a trait value is of a particular class type. That is, the expressionin effect checks the allocated type of a trait.
The RHS type of anis
expression can always be a supertype of the type of the LHSexpression, in which case the result is trivally true. Other than that, the RHS must be based on a reference type and theLHS expression must be assignable to the RHS type. Furthermore, in order to becompilable, the RHS type must not be a subset type other than a non-null referencetype, and the type parameters of the RHS must be uniquely determined from thetype parameters of the LHS type. The last restriction is designed to make itpossible to perform type tests without inspecting type parameters at run time.For example, consider the following types:
traitA{}traitB<X>{}classC<Y>extendsB<Y>{}classD<Y(==)>extendsB<set<Y>>{}classEextendsB<int>{}classF<Z>extendsA{}
A LHS expression of typeB<set<int>>
can be used in a type test where the RHS isB<set<int>>
,C<set<int>>
, orD<int>
, and a LHS expression of typeB<int>
can be used in a type test where the RHS isB<int>
,C<int>
, orE
. Thoseare always allowed in compiled (and ghost) contexts.For an expressiona
of typeA
, the expressionaisF<int>
is a ghost expression;it can be used in ghost contexts, but not in compiled contexts.
For an expressione
and typet
,eist
is the condition determining whethereast
is well-defined (but, as noted above, is not always a legal expression).
The repertoire of types allowed inis
tests may be expanded in the future.
9.11. Unary Expressions (grammar)
Examples:
-x--x!x
A unary expression applies
- logical complement (
!
–Section 5.2.1), - bit-wise complement (
!
–Section 5.2.3), - numeric negation (
-
–Section 5.2.2), or - bit-vector negation (
-
–Section 5.2.3)
to its operand.
9.12. Primary Expressions (grammar)
Examples:
true34M(i,j)b.c.d[1,2,3]{2,3,4}map[1=>2,3=>4](i:int,j:int)=>i+jifbthen4else5
After descending through all the binary and unary operators we arrive atthe primary expressions, which are explained in subsequent sections. A number of these can be followed by 0 or more suffixesto select a component of the value.
9.13. Lambda expressions (grammar)
Examples:
x=>-x_=>true(x,y)=>x*y(x:int,b:bool)=>ifbthenxelse-xxrequiresx>0=>x-1
SeeSection 7.4 for a description of specifications for lambda expressions.
In addition to named functions, Dafny supports expressions that definefunctions. These are calledlambda (expression)s (some languagesknow them asanonymous functions). A lambda expression has theform:
(_params_)_specification_=>_body_
whereparams is a comma-delimited list of parameterdeclarations, each of which has the formx
orx:T
. The typeT
of a parameter can be omitted when it can be inferred. If theidentifierx
is not needed, it can be replaced by_
. Ifparams consists of a single parameterx
(or_
) without anexplicit type, then the parentheses can be dropped; for example, thefunction that returns the successor of a given integer can be writtenas the following lambda expression:
x=>x+1
Thespecification is a list of clausesrequiresE
orreadsW
, whereE
is a boolean expression andW
is a frameexpression.
body is an expression that defines the function’s returnvalue. The body must bewell-formed for all possible values of theparameters that satisfy the precondition (just like the bodies ofnamed functions and methods). In some cases, this means it isnecessary to write explicitrequires
andreads
clauses. Forexample, the lambda expression
xrequiresx!=0=>100/x
would not bewell-formed if therequires
clause were omitted,because of the possibility of division-by-zero.
In settings where functions cannot be partial and there are norestrictions on reading the heap, theeta expansion of a functionF:T->U
(that is, the wrapping ofF
inside a lambda expressionin such a way that the lambda expression is equivalent toF
) wouldbe writtenx=>F(x)
. In Dafny, eta expansion must also account forthe precondition and reads set of the function, so the eta expansionofF
looks like:
xrequiresF.requires(x)readsF.reads(x)=>F(x)
9.14. Left-Hand-Side Expressions (grammar)
Examples:
xa[k]LibraryModule.F().xold(o.f).x
A left-hand-side expression is only used on the left handside of anUpdate statementor anUpdate with Failure Statement.
An LHS can be
- a simple identifier:
k
- an expression with a dot suffix:
this.x
,f(k).y
- an expression with an array selection:
a[k]
,f(a8)[6]
9.15. Right-Hand-Side Expressions (grammar)
Examples:
newint[6]newMyClassnewMyClass(x,y,z)x+y+z*
A Right-Hand-Side expression is an expression-like construct that may have side-effects. Consequently such expressions can only be used within certain statementswithin methods, and not as general expressions or within functions or specifications.
An RHS is either an array allocation, an object allocation,a havoc right-hand-side, a method call, or a simple expression, optionally followedby one or more attributes.
Right-hand-side expressions (that are not just regular expressions) appear in the following constructs:
- return statements,
- yield statements,
- update statements,
- update-with-failure statements, or
- variable declaration statements.
These are the only contexts in which arrays or objects may beallocated or in which havoc may be stipulated.
9.16. Array Allocation (grammar)
Examples:
newint[5,6]newint[5][2,3,5,7,11]newint[][2,3,5,7,11]newint[5](i=>i*i)newint[2,3]((i,j)=>i*j)
This right-hand-side expression allocates a new single or multi-dimensional array (cf.Section 5.10).The initialization portion is optional. One form is anexplicit list of values, in which case the dimension is optional:
vara:=newint[5];varb:=newint[5][2,3,5,7,11];varc:=newint[][2,3,5,7,11];vard:=newint[3][4,5,6,7];// error
The comprehension form requires a dimension and uses a function oftypenat->T
whereT
is the array element type:
vara:=newint[5](i=>i*i);
To allocate a multi-dimensional array, simply give the sizes ofeach dimension. For example,
varm:=newreal[640,480];
allocates a 640-by-480 two-dimensional array ofreal
s. The initializationportion cannot give a display of elements like in the one-dimensionalcase, but it can use an initialization function. A function used to initializea n-dimensional array requires a function from nnat
s to aT
, whereT
is the element type of the array. Here is an example:
vardiag:=newint[30,30]((i,j)=>ifi==jthen1else0);
Array allocation is permitted in ghost contexts. If any expressionused to specify a dimension or initialization value is ghost, then thenew
allocation can only be used in ghost contexts. Because theelements of an array are non-ghost, an array allocated in a ghostcontext in effect cannot be changed after initialization.
9.17. Object Allocation (grammar)
Examples:
newMyClassnewMyClass.InitnewMyClass.Init(1,2,3)
This right-hand-side expression allocates a new object of a class type as explainedin sectionClass Types.
9.18. Havoc Right-Hand-Side (grammar)
Examples:
*
A havoc right-hand-side is just a*
character.It produces an arbitrary value of its associatedtype. The “assign-such-that”operator (:|
) can be used to obtain a more constrained arbitrary value. SeeSection 8.5.
9.19. Constant Or Atomic Expressions (grammar)
Examples:
thisnull55.5true'a'"dafny"(e)|s|old(x)allocated(x)unchanged(x)fresh(e)assigned(x)
These expressions are never l-values. They include
- literal expressions
- parenthesized expressions
this
expressions- fresh expressions
- allocated expressions
- unchanged expressions
- old expressions
- cardinality expressions
- assigned expressions
9.20. Literal Expressions (grammar}
Examples:
55.5true'a'"dafny"
A literal expression is a null object reference or a boolean,integer, real, character or string literal.
9.21.this
Expression (grammar)
Examples:
this
Thethis
token denotes the current object in the context of a constructor, instance method, or instance function.
9.22. Old and Old@ Expressions (grammar)
Examples:
old(c)old@L(c)
Anold expression is used in postconditions or in the body of a methodor in the body or specification of any two-state function or two-state lemma;anold expression with a label is used only in the body of a method at a pointwhere the label dominates its use in the expression.
old(e)
evaluatesthe argument using the value of the heap on entry to the method;old@ident(e)
evaluates the argument using the value of the heap at thegiven statement label.
Note thatold andold@ only affect heap dereferences,likeo.f
anda[i]
.In particular, neither form has any effect on the value returned for localvariables or out-parameters (as they are not on the heap).10If the value of an entire expression at aparticular point in the method body is needed later on in the method body,the clearest means is to declare a ghost variable, initializing it to theexpression in question.If the argument ofold
is a local variable or out-parameter. Dafny issues a warning.
The argument of anold
expression may not contain nestedold
,fresh
,orunchanged
expressions,nortwo-state functions ortwo-state lemmas.
Here are some explanatory examples. Allassert
statements verify to be true.
classA{varvalue:intmethodm(i:int)requiresi==6requiresvalue==42modifiesthis{varj:int:=17;value:=43;labelL:j:=18;value:=44;labelM:assertold(i)==6;// i is local, but can't be changed anywayassertold(j)==18;// j is local and not affected by oldassertold@L(j)==18;// j is local and not affected by oldassertold(value)==42;assertold@L(value)==43;assertold@M(value)==44&&this.value==44;// value is this.value; 'this' is the same// same reference in current and pre state but the// values stored in the heap as its fields are different;// '.value' evaluates to 42 in the pre-state, 43 at L,// and 44 in the current state}}
classA{varvalue:intconstructor()ensuresvalue==10{value:=10;}}classB{vara:Aconstructor(){a:=newA();}methodm()requiresa.value==11modifiesthis,this.a{labelL:a.value:=12;labelM:a:=newA();// Line XlabelN:a.value:=20;labelP:assertold(a.value)==11;assertold(a).value==12;// this.a is from pre-state,// but .value in current stateassertold@L(a.value)==11;assertold@L(a).value==12;// same as aboveassertold@M(a.value)==12;// .value in M state is 12assertold@M(a).value==12;assertold@N(a.value)==10;// this.a in N is the heap// reference at Line Xassertold@N(a).value==20;// .value in current state is 20assertold@P(a.value)==20;assertold@P(a).value==20;}}
classA{varvalue:intconstructor()ensuresvalue==10{value:=10;}}classB{vara:Aconstructor(){a:=newA();}methodm()requiresa.value==11modifiesthis,this.a{labelL:a.value:=12;labelM:a:=newA();// Line XlabelN:a.value:=20;labelP:assertold(a.value)==11;assertold(a).value==12;// this.a is from pre-state,// but .value in current stateassertold@L(a.value)==11;assertold@L(a).value==12;// same as aboveassertold@M(a.value)==12;// .value in M state is 12assertold@M(a).value==12;assertold@N(a.value)==10;// this.a in N is the heap// reference at Line Xassertold@N(a).value==20;// .value in current state is 20assertold@P(a.value)==20;assertold@P(a).value==20;}}
The next example demonstrates the interaction betweenold
and array elements.
classA{varz1:array<nat>varz2:array<nat>methodmm()requiresz1.Length>10&&z1[0]==7requiresz2.Length>10&&z2[0]==17modifiesz2{vara:array<nat>:=z1;asserta[0]==7;a:=z2;asserta[0]==17;assertold(a[0])==17;// a is local with value z2z2[0]:=27;assertold(a[0])==17;// a is local, with current value of// z2; in pre-state z2[0] == 17assertold(a)[0]==27;// a is local, so old(a) has no effect}}
9.23. Fresh Expressions (grammar)
Examples:
fresh(e)fresh@L(e)
fresh(e)
returns a boolean value that is true ifthe objects denoted by expressione
were allfreshly allocated since the time of entry to the enclosing method,or sincelabelL:
in the variantfresh@L(e)
.The argument is an object or set of objects.For example, consider this valid program:
classC{constructor(){}}methodf(c1:C)returns(r:C)ensuresfresh(r){assert!fresh(c1);varc2:=newC();labelAfterC2:varc3:=newC();assertfresh(c2)&&fresh(c3);assertfresh({c2,c3});assert!fresh@AfterC2(c2)&&fresh@AfterC2(c3);r:=c2;}
TheL
in the variantfresh@L(e)
must denote alabel that, in theenclosing method’s control flow,dominates the expression. In thiscase,fresh@L(e)
returnstrue
if the objects denoted bye
were allfreshly allocated since control flow reached labelL
.
The argument offresh
must be either anobject
referenceor a set or sequence of object references.In this case,fresh(e)
(respectivelyfresh@L(e)
with a label)is a synonym ofold(!allocated(e))
(respectivelyold@L(!allocated(e))
)
9.24. Allocated Expressions (grammar)
Examples:
allocated(c)allocated({c1,c2})
For any expressione
, the expressionallocated(e)
evaluates totrue
in a state if the value ofe
is available in that state, meaning thatit could in principle have been the value of a variable in that state.
For example, consider this valid program:
classC{constructor(){}}datatypeD=Nil|Cons(C,D)methodf(){vard1,d2:=Nil,Nil;varc1:=newC();labelL1:varc2:=newC();labelL2:assertold(allocated(d1)&&allocated(d2));d1:=Cons(c1,Nil);assertold(!allocated(d1)&&allocated(d2));d2:=Cons(c2,Nil);assertold(!allocated(d1)&&!allocated(d2));assertallocated(d1)&&allocated(d2);assertold@L1(allocated(d1)&&!allocated(d2));assertold@L2(allocated(d1)&&allocated(d2));d1:=Nil;assertold(allocated(d1)&&!allocated(d2));}
This can be useful when, for example,allocated(e)
is evaluated in anold
state. Like in the example, whered1
is a local variable holding a datatype valueCons(c1,Nil)
wherec1
is an object that was allocated in the enclosingmethod, thenold(allocated(d))
isfalse
.
If the expressione
is of a reference type, then!old(allocated(e))
is the same asfresh(e)
.
9.25. Unchanged Expressions (grammar)
Examples:
unchanged(c)unchanged([c1,c2])unchanged@L(c)
Theunchanged
expression returnstrue
if and only if every referencedenoted by its arguments has the same value for all its fields in theold and current state. For example, ifc
is an object with twofields,x
andy
, thenunchanged(c)
is equivalent to
c.x==old(c.x)&&c.y==old(c.y)
Each argument tounchanged
can be a reference, a set of references, ora sequence of references, each optionally followed by a back-tick and field name. This form with a frame field expresses that just the fieldf
,not necessarily all fields, has the same value in the old and currentstate.If there is such a frame field, all the references must have the same type,which must have a field of that name.
The optional@
-label says to use the state at that label as the old-state instead of usingtheold
state (the pre-state of the method). That is, using the examplec
from above, the expressionunchanged@Lbl(c)
is equivalent to
c.x==old@Lbl(c.x)&&c.y==old@Lbl(c.y)
Each reference denoted by the arguments ofunchanged
must be non-null andmust be allocated in the old-state of the expression.
9.26. Cardinality Expressions (grammar)
Examples:
|s||s[1..i]|
For a finite-collection expressionc
,|c|
is the cardinality ofc
. For afinite set or sequence, the cardinality is the number of elements. Fora multiset, the cardinality is the sum of the multiplicities of theelements. For a finite map, the cardinality is the cardinality of thedomain of the map. Cardinality is not defined for infinite sets or infinite maps.For more information, seeSection 5.5.
9.27. Parenthesized Expressions (grammar)
A parenthesized expression is a list of zero or more expressionsenclosed in parentheses.
If there is exactly one expression enclosed then the value is justthe value of that expression.
If there are zero or more than one, the result is atuple
value.SeeSection 5.13.
9.28. Sequence Display Expression (grammar)
Examples:
[1,2,3][1][]seq(k,n=>n+1)
A sequence display expression provides a way to constructa sequence with given values. For example
[1,2,3]
is a sequence with three elements in it.
seq(k,n=>n+1)
is a sequence of k elements whose values are obtained by evaluating thesecond argument (a function, in this case a lambda expression) on the indices 0 up to k.
Seethis section for more information onsequences.
9.29. Set Display Expression (grammar)
Examples:
{}{1,2,3}iset{1,2,3,4}multiset{1,2,2,3,3,3}multiset(s)
A set display expression provides a way of constructing a set with givenelements. If the keywordiset
is present, then a potentially infiniteset (with the finite set of given elements) is constructed.
For example
{1,2,3}
is a set with three elements in it.SeeSection 5.5.1 for more information onsets.
A multiset display expression provides a way of constructinga multiset with given elements and multiplicities. For example
multiset{1,1,2,3}
is a multiset with three elements in it. The number 1 has a multiplicity of 2,and the numbers 2 and 3 each have a multiplicity of 1.
A multiset cast expression converts a set or a sequenceinto a multiset as shown here:
vars:set<int>:={1,2,3};varms:multiset<int>:=multiset(s);ms:=ms+multiset{1};varsq:seq<int>:=[1,1,2,3];varms2:multiset<int>:=multiset(sq);assertms==ms2;
Note thatmultiset{1,1}
is a multiset holding the value1
with multiplicity 2,but inmultiset({1,1})
the multiplicity is 1, because the expression{1,1}
is the set{1}
,which is then converted to a multiset.
SeeSection 5.5.2 for more information on multisets.
9.30. Map Display Expression (grammar)
Examples:
map[]map[1:="a",2:="b"]imap[1:="a",2:="b"]
A map display expression builds a finite or potentially infinitemap from explicit mappings. For example:
constm:=map[1:="a",2:="b"]ghostconstim:=imap[1:="a",2:="b"]
SeeSection 5.5.4 for more details on maps and imaps.
9.31. Endless Expression (grammar)
Endless expression gets it name from the fact that all its alternateproductions have no terminating symbol to end them, but rather theyall end with an arbitrary expression at the end. The variousendless expression alternatives are described in the following subsections.
9.31.1. If Expression (grammar)
Examples:
ifcthene1elsee2ifx:int:|P(x)thenxelse0
Anif expression is a conditional (ternary) expression. It first evaluatesthe condition expression that follows theif
. If the condition evaluates totrue
thenthe expression following thethen
is evaluated and its value is theresult of the expression. If the condition evaluates tofalse
then theexpression following theelse
is evaluated and that value is the resultof the expression. It is important that only the selected expressionis evaluated as the following example shows.
vark:=10/x;// error, may divide by 0.varm:=ifx!=0then10/xelse1;// ok, guarded
Theif
expression also permits a binding form.In this case the condition of theif
is an existential asking“does there exist a value satisfying the given predicate?”.If not, the else branch is evaluated. But if so, then an(arbitrary) value that does satisfy the given predicate isbound to the given variable and that variable is in scope in the then-branch of the expression.
For example, in the code
predicateP(x:int){x==5||x==-5}methodmain(){assertP(5);vary:=ifx:int:|P(x)thenxelse0;asserty==5||y==-5;}
x
is given some value that satisfiesP(x)
, namely either5
or-5
.That value ofx
is the value of the expression in thethen
branch above; if there is no value satisfyingP(x)
,then0
is returned. Note that ifx
is declared to be anat
in this example, then onlythe value5
would be permissible.
This binding form of theif
expression acts in the same way as the binding form of theif
statement.
In the example given, the binder forx
has no constraining range, so the expression isghost
;if a range is given, such asvary:=ifx:int:|0<=x<10&&P(x)thenxelse0;
,then theif
andy
are no longer ghost, andy
could be used, for example, in aprint
statement.
9.31.2. Case and Extended Patterns (grammar)
Patterns are used for (possibly nested)pattern matching on inductive, coinductive or base type values.They are used inmatch statements,match expressions,let expressions,andvariable declarations.The match expressions and statements allow literals,symbolic constants, and disjunctive (“or”) patterns.
When matching an inductive or coinductive value ina match statement or expression, the patternmust correspond to one of the following:
- (0) a case disjunction (“or-pattern”)
- (1) bound variable (a simple identifier),
- (2) a constructor of the type of the value,
- (3) a literal of the correct type, or
- (4) a symbolic constant.
If the extended pattern is
- a sequence of
|
-separated sub-patterns, then the pattern matches valuesmatched by any of the sub-patterns. - a parentheses-enclosed possibly-empty list of patterns,then the pattern matches a tuple.
- an identifier followedby a parentheses-enclosed possibly-empty list of patterns, then the patternmatches a constructor.
- a literal, then the pattern matches exactly that literal.
- a simple identifier, then the pattern matches
- a parameter-less constructor if there is one defined with the correct type and the given name, else
- the value of a symbolic constant, if a name lookup finds a declaration fora constant with the given name (if the name is declared but with a non-matching type, a type resolution error will occur),
- otherwise, the identifier is a new bound variable
Disjunctive patterns may not bind variables, and may not be nested inside otherpatterns.
Any patterns inside the parentheses of a constructor (or tuple) pattern are thenmatched against the arguments that were given to theconstructor when the value was constructed.The number of patterns must match the numberof parameters to the constructor (or the arity of thetuple).
When matching a value of base type, the pattern shouldeither be a literal expression of the same type as the value,or a single identifier matching all values of this type.
Patterns may be nested. The bound variableidentifiers contained in all the patterns must be distinct.They are bound to the corresponding values in the value beingmatched. (Thus, for example, one cannot repeat a bound variable toattempt to match a constructor that has two identical arguments.)
9.31.3. Match Expression (grammar)
Amatch expression is used to conditionally evaluate and select anexpression depending on the value of an algebraic type, i.e. an inductivetype, a coinductive type, or a base type.
All of the variables in the patterns must be distinct.If types for the identifiers are not given then types are inferredfrom the types of the constructor’s parameters. If types aregiven then they must agree with the types of thecorresponding parameters.
The expression following thematch
keyword is called theselector. A match expression is evaluated by first evaluating the selector.The patterns of each match alternative are then compared, in order, with the resulting value until a matching pattern is found, as described inthesection on case bindings.If the constructor hadparameters, then the actual values used to construct the selectorvalue are bound to the identifiers in the identifier list.The expression to the right of the=>
in the matched alternative is thenevaluated in the environment enriched by this binding. The resultof that evaluation is the result of the match expression.
Note that the braces enclosing the sequence of match alternatives may be omitted.Those braces are required if lemma or lambda expressions are used in thebody of any match alternative; they may also be needed for disambiguation ifthere are nested match expressions.
9.31.4. Quantifier Expression (grammar)
Examples:
forallx:int::x>0forallx:nat|x<10::x*x<100existsx:int::x*x==25
Aquantifier expression is a boolean expression that specifies that agiven expression (the one following the::
) is true for all (forforall) or some (forexists) combination of values of thequantified variables, namely those in the given quantifier domain.SeeSection 2.7.4 for more details on quantifier domains.
Here are some examples:
assertforallx:nat|x<=5::x*x<=25;(foralln::2<=n==>(existsd::n<d<2*n))assertforallx:nat|0<=x<|s|,y<-s[x]::y<x;
The quantifier identifiers arebound within the scope of theexpressions in the quantifier expression.
If types are not given for the quantified identifiers, then Dafnyattempts to infer their types from the context of the expressions.It this is not possible, the program is in error.
9.31.5. Set Comprehension Expressions (grammar)
Examples:
constc1:=setx:nat|x<100constc2:=setx:nat|x<100::x*xconstc3:=setx:nat,y:nat|x<y<100::x*yghostconstc4:=isetx:nat|x>100ghostconstc5:iset<int>:=isetsconstc6:=setx<-c3::x+1
A set comprehension expression is an expression that yields a set(possibly infinite only ifiset
is used) thatsatisfies specified conditions. There are two basic forms.
If there is only one quantified variable, the optional"::"Expression
need not be supplied, in which case it is as if it had been suppliedand the expression consists solely of the quantified variable.That is,
setx:T|P(x)
is equivalent to
setx:T|P(x)::x
For the full form
varS:=setx1:T1<-C1|P1(x1),x2:T2<-C2|P2(x1,x2),...::Q(x1,x2,...)
the elements ofS
will be all values resulting from evaluation ofQ(x1,x2,...)
for all combinations of quantified variablesx1,x2,...
(from their respectiveC1,C2,...
domains) such that all predicatesP1(x1),P2(x1,x2),...
hold.
For example,
varS:=setx:nat,y:nat|x<y<3::(x,y)
yieldsS=={(0,1),(0,2),(1,2)}
The types on the quantified variables are optional and if not given Dafnywill attempt to infer them from the contexts in which they are used in thevarious expressions. The<-C
domain expressions are also optional and default toisetx:T
(i.e. all values of the variable’s type), as are the|P
expressions whichdefault totrue
. See alsoSection 2.7.4 for more details on quantifier domains.
If a finite set was specified (“set” keyword used), Dafny must be able to prove that theresult is finite otherwise the set comprehension expression will not beaccepted.
Set comprehensions involving reference types such as
seto:object
are allowed in ghost expressions within methods, but not in ghost functions11.In particular, in ghost contexts, thecheck that the result is finite should allow any set comprehensionwhere the bound variable is of a reference type. In non-ghost contexts,it is not allowed, because–even though the resulting set would befinite–it is not pleasant or practical to compute at run time.
The universe in which set comprehensions are evaluated is the set of allallocated objects, of the appropriate type and satisfying the given predicate.For example, given
classI{vari:int}methodtest(){ghostvarm:=setx:I::0<=x.i<=10;}
the setm
contains only those instances ofI
that have been allocatedat the point in program execution thattest
is evaluated. This could beno instances, one per value ofx.i
in the stated range, multiple instancesofI
for each value ofx.i
, or any other combination.
9.31.6. Statements in an Expression (grammar)
Examples:
assertx!=0;10/xassertx!=0;asserty>0;y/xassumex!=0;10/xexpectx!=0;10/xrevealM.f;M.f(x)calc{x*0;==0;}x/1;
AStmtInExpr
is a kind of statement that is allowed toprecede an expression in order to ensure that the expressioncan be evaluated without error. For example:
assumex!=0;10/x
Assert
,assume
,expect
,reveal
andcalc
statements can be used in this way.
9.31.7. Let and Let or Fail Expression (grammar)
Examples:
varx:=f(y);x*xvarx:-f(y);x*xvarx:|P(x);x*xvar(x,y):=T();x+y// T returns a tuplevarR(x,y):=T();x+y// T returns a datatype value R
Alet
expression allows binding of intermediate values to identifiersfor use in an expression. The start of thelet
expression issignaled by thevar
keyword. They look much like a local variabledeclaration except the scope of the variable only extends to theenclosed expression.
For example:
varsum:=x+y;sum*sum
In the simple case, the pattern is just an identifier with optionaltype (which if missing is inferred from the rhs).
The more complex case allows destructuring of constructor expressions.For example:
datatypeStuff=SCons(x:int,y:int)|OtherfunctionGhostF(z:Stuff):intrequiresz.SCons?{varSCons(u,v):=z;varsum:=u+v;sum*sum}
The Let expression has a failure variantthat simply uses:-
instead of:=
. This Let-or-Fail expression also permits propagatingfailure results. However, in statements (Section 8.6), failure results inimmediate return from the method; expressions do not have side effects or immediate returnmechanisms. Rather, if the expression to the right of:-
results in a failure valueV
,the overall expression returnsV.PropagateFailure()
; if there is no failure, the expression following the semicolon is returned. Note that these two possible return values must have the same type (or be implicitly convertible to the same type). Typically that means thattmp.PropagateFailure()
is a failure value andE
is a value-carrying success value, both of the same failure-compatible type, as described inSection 8.6.
The expression:-V;E
is desugared into theexpression
vartmp:=V;iftmp.IsFailure()thentmp.PropagateFailure()elseE
The expressionvarv:-V;E
is desugared into theexpression
vartmp:=V;iftmp.IsFailure()thentmp.PropagateFailure()elsevarv:=tmp.Extract();E
If the RHS is a list of expressions then the desugaring is similar.varv,v1:-V,V1;E
becomes
vartmp:=V;iftmp.IsFailure()thentmp.PropagateFailure()elsevarv,v1:=tmp.Extract(),V1;E
So, if tmp is a failure value, then a corresponding failure value is propagated along; otherwise, the expressionis evaluated as normal.
9.31.8. Map Comprehension Expression (grammar)
Examples:
mapx:int|0<=x<=10::x*x;mapx:int|0<=x<=10::-x:=x*x;imapx:int|10<x::x*x;
Amap comprehension expression defines a finite or infinite map valueby defining a domain and for each value in the domain,giving the mapped value using the expression following the “::”.SeeSection 2.7.4 for more details on quantifier domains.
For example:
functionsquare(x:int):int{x*x}methodtest(){varm:=mapx:int|0<=x<=10::x*x;ghostvarim:=imapx:int::x*x;ghostvarim2:=imapx:int::square(x);}
Dafny finite maps must be finite, so the domain must be constrained to be finite.But imaps may be infinite as the examples show. The last example showscreation of an infinite map that gives the same results as a function.
If the expression includes the:=
token, that token separatesdomain values from range values. For example, in the following code
methodtest(){varm:=mapx:int|1<=x<=10::2*x:=3*x;}
m
maps2
to3
,4
to6
, and so on.
9.32. Name Segment (grammar)
Examples:
II<int,C>I#[k]I#<int>[k]
Aname segment names a Dafny entity by giving its declaredname optionally followed by information tomake the name more complete. For the simple case, it isjust an identifier. Note that a name segment may be followedbysuffixes, including the common ‘.’ and further name segments.
If the identifier is for a generic entity, it is followed byaGenericInstantiation
which provides actual types forthe type parameters.
To reference a prefix predicate (seeSection 5.14.3.5) orprefix lemma (seeSection 5.14.3.6.3), the identifiermust be the name of the greatest predicate or greatest lemma and it must befollowed by ahash call.
9.33. Hash call (grammar)
Ahash call is used to call the prefix for a greatest predicate or greatest lemma.In the non-generic case, just insert"#[k]"
before the call argumentlist where k is the number of recursion levels.
In the case where thegreatestlemma
is generic, the generic typeargument is given before. Here is an example:
codatatypeStream<T>=Nil|Cons(head:int,stuff:T,tail:Stream<T>)functionappend(M:Stream,N:Stream):Stream{matchMcaseNil=>NcaseCons(t,s,M')=>Cons(t,s,append(M',N))}functionzeros<T>(s:T):Stream<T>{Cons(0,s,zeros(s))}functionones<T>(s:T):Stream<T>{Cons(1,s,ones(s))}greatestpredicateatmost(a:Stream,b:Stream){matchacaseNil=>truecaseCons(h,s,t)=>b.Cons?&&h<=b.head&&atmost(t,b.tail)}greatestlemma{:inductionfalse}Theorem0<T>(s:T)ensuresatmost(zeros(s),ones(s)){// the following shows two equivalent ways to state the// coinductive hypothesisif(*){Theorem0#<T>[_k-1](s);}else{Theorem0(s);}}
where theHashCall
is"Theorem0#<T>[_k-1](s);"
.SeeSection 5.14.3.5 andSection 5.14.3.6.3.
9.34. Suffix (grammar)
Asuffix describes ways of deriving a new value fromthe entity to which the suffix is appended. The several kindsof suffixes are described below.
9.34.1. Augmented Dot Suffix (grammar)
Examples: (expression with suffix)
a.b(a).b<int>a.b#[k]a.b#<int>[k]
An augmented dot suffix consists of a simpledot suffix optionallyfollowed by either
- a
GenericInstantiation
(for the case where the itemselected by theDotSuffix
is generic), or - a
HashCall
for the case where we want to call a prefix predicateor prefix lemma. The result is the result of calling the prefix predicateor prefix lemma.
9.34.2. Datatype Update Suffix (grammar)
Examples: (expression with suffix)
a.(f:=e1,g:=e2)a.(0:=e1)(e).(f:=e1,g:=e2)
Adatatype update suffix is used to produce a new datatype valuethat is the same as an old datatype value except that thevalue corresponding to a given destructor has the specified value.In amember binding update, the given identifier (or digit sequence) is thename of a destructor (i.e. the formal parameter name) for one of theconstructors of the datatype. The expression to the right of the:=
is the new value for that formal.
All of the destructors in a datatype update suffix must befor the same constructor, and if they do not cover all of thedestructors for that constructor then the datatype value beingupdated must have a value derived from that same constructor.
Here is an example:
moduleNewSyntax{datatypeMyDataType=MyConstructor(myint:int,mybool:bool)|MyOtherConstructor(otherbool:bool)|MyNumericConstructor(42:int)methodtest(datum:MyDataType,x:int)returns(abc:MyDataType,def:MyDataType,ghi:MyDataType,jkl:MyDataType)requiresdatum.MyConstructor?ensuresabc==datum.(myint:=x+2)ensuresdef==datum.(otherbool:=!datum.mybool)// errorensuresghi==datum.(myint:=2).(mybool:=false)// Resolution error: no non_destructor in MyDataType//ensures jkl == datum.(non_destructor := 5) // errorensuresjkl==datum.(42:=7){abc:=MyConstructor(x+2,datum.mybool);abc:=datum.(myint:=x+2);def:=MyOtherConstructor(!datum.mybool);ghi:=MyConstructor(2,false);jkl:=datum.(42:=7);// errorassertabc.(myint:=abc.myint-2)==datum.(myint:=x);}}
9.34.3. Subsequence Suffix (grammar)
Examples: (with leading expression)
a[lo..hi](e)[lo..]e[..hi]e[..]
A subsequence suffix applied to a sequence produces a new sequence whoseelements are taken from a contiguous part of the original sequence. Forexample, expressions[lo..hi]
for sequences
, and integer-basednumeric boundslo
andhi
satisfying0<=lo<=hi<=|s|
. Seethe section about other sequence expressions for details.
A subsequence suffix applied to an array produces asequence consisting of the values of the designated elements. A concise way of converting a whole array to a sequence is to writea[..]
.
9.34.4. Subsequence Slices Suffix (grammar)
Examples: (with leading expression)
a[0:2:3]a[e1:e2:e3]a[0:2:]
Applying asubsequence slices suffix to a sequence produces asequence of subsequences of the original sequence.Seethe section about other sequence expressions for details.
9.34.5. Sequence Update Suffix (grammar)
Examples:
s[1:=2,3:=4]
For a sequences
and expressionsi
andv
, the expressions[i:=v]
is the same as the sequences
except that atindexi
it has valuev
.
If the type ofs
isseq<T>
, thenv
must have typeT
.The indexi
can have any integer- or bit-vector-based type(this is one situation in which Dafny implements implicitconversion, as if anasint
were appended to the index expression).The expressions[i:=v]
has the same type ass
.
9.34.6. Selection Suffix (grammar)
Examples:
a[9]a[i.j.k]
If a selection suffix has only one expression in it, it is azero-based index that may be used to select a single element of asequence or from a single-dimensional array.
If a selection suffix has more than one expression in it, thenit is a list of indices to index into a multi-dimensional array.The rank of the array must be the same as the number of indices.
If the selection suffix is used with an array or a sequence,then each index expression can have any integer- or bit-vector-basedtype(this is one situation in which Dafny implements implicitconversion, as if anasint
were appended to the index expression).
9.34.7. Argument List Suffix (grammar)
Examples:
()(a)(a,b)
An argument list suffix is a parenthesized list of expressions thatare the arguments to pass to a method or function that is beingcalled. Applying such a suffix causes the method or functionto be called and the result is the result of the call.
Note that method calls may only appear inright-hand-sidelocations, whereas function calls may appear in expressions and specifications;this distinction can be made only during name and type resolution, not by theparser.
9.35. Expression Lists (grammar)
Examples:
// empty listaa,b
An expression list is a comma-separated sequence of expressions, used, for example,as actual araguments in a method or function call or in parallel assignment.
9.36. Parameter Bindings (grammar)
Examples:
aa,ba,optimize:=b
Method calls, object-allocation calls (new
), function calls, anddatatype constructors can be called with both positional argumentsand named arguments.
Formal parameters have three ways to indicate how they are to be passed in:
- nameonly: the only way to give a specific argument value is to name the parameter
- positional only: these are nameless parameters (which are allowed only for datatype constructor parameters)
- either positional or by name: this is the most common parameter
A parameter is either required or optional:
- required: a caller has to supply an argument
- optional: the parameter has a default value that is used if a caller omits passing a specific argument
The syntax for giving a positional-only (i.e., nameless) parameter does not allow a default-value expression, so a positional-only parameter is always required.
At a call site, positional arguments are not allowed to follow named arguments. Therefore, ifx
is a nameonly parameter, then there is no way to supply the parameters afterx
by position. Thus, any parameter that followsx
must either be passed by name or have a default value. That is, if a later (in the formal parameter declaration) parameter does not have a default value, it is effectively nameonly.
Positional arguments must be given before any named arguments.Positional arguments are passed to the formals in the correspondingposition. Named arguments are passed to the formal of the givenname. Named arguments can be given out of order from how the correspondingformal parameters are declared. A formal declared with the modifiernameonly
is not allowed to be passed positionally.The list of bindings for a call mustprovide exactly one value for every required parameter and at most onevalue for each optional parameter, and must never namenon-existent formals. Any optional parameter that is not given a valuetakes on the default value declared in the callee for that optional parameter.
9.37. Assigned Expressions
Examples:
assigned(x)
For any variable, constant, out-parameter, or object fieldx
,the expressionassigned(x)
evaluates totrue
in a stateifx
is definitely assigned in that state.
SeeSection 12.6 for more details on definite assignment.
9.38. Termination Ordering Expressions
When proving that a loop or recursive callable terminates, Dafnyautomatically generates a proof obligation that the sequence ofexpressions listed in adecreases
clause gets smaller (in thelexicographic termination ordering) with eachiteration or recursive call. Normally, this proof obligation is purelyinternal. However, it can be written as a Dafny expression using thedecreasesto
operator.
The Boolean expression(a,...,bdecreasestoa',...,b')
encodesthis ordering. (The parentheses can be omitted if there is exactly 1 left-hand sideand exactly 1 right-hand side.) For example, the following assertions are valid:
methodM(x:int,y:int){assert1decreasesto0;assert(true,falsedecreasestofalse,true);assert(x,ydecreasestox-1,y);}
Conversely, the following assertion is invalid:
methodM(x:int,y:int){assertxdecreasestox+1;}
Thedecreasesto
operator is strict, that is, it means “strictly greater than”.Thenonincreasesto
operator is the non-strict (“greater than or equal”) version of it.
9.39. Compile-Time Constants
In certain situations in Dafny it is helpful to know what the value of aconstant is during program analysis, before verification or execution takesplace. For example, a compiler can choose an optimized representation of anewtype
that is a subset ofint
if it knows the range of possible valuesof the subset type: if the range is within 0 to less than 256, then anunsigned 8-bit representation can be used.
To continue this example, suppose a new type is defined as
constMAX:=47newtypemytype=x|0<=x<MAX*4
In this case, we would prefer that Dafny recognize thatMAX*4
isknown to be constant with a value of188
. The kinds of expressionsfor which such an optimization is possible are calledcompile-time constants. Note that the representation ofmytype
makesno difference semantically, but can affect how compiled code is represented at run time.In addition, though, using a symbolic constant (which maywell be used elsewhere as well) improves the self-documentation of the code.
In Dafny, the following expressions are compile-time constants12, recursively(that is, the arguments of any operation must themselves be compile-time constants):
- int, bit-vector, real, boolean, char and string literals
- int operations:
+-*/%
and unary-
and comparisons<<=>>===!=
- real operations:
+-*
and unary-
and comparisons<<=>>===!=
- bool operations:
&&||==><==<==>==!=
and unary!
- bit-vector operations:
+-*/%<<>>&|^
and unary!-
and comparisons<<=>>===!=
- char operations:
<<=>>===!=
- string operations: length:
|...|
, concatenation:+
, comparisons<<===!=
, indexing[]
- conversions between:
int
real
char
bit-vector - newtype operations: newtype arguments, but not newtype results
- symbolic values that are declared
const
and have an explicit initialization value that is a compile-time constant - conditional (if-then-else) expressions
- parenthesized expressions
9.40. List of specification expressions
The following is a list of expressions that can only appear in specification contexts or in ghost blocks.
- Fresh expressions
- Allocated expressions
- Unchanged expressions
- Old expressions
- Assigned expressions
- Assert and calc expressions
- Hash Calls
- Termination ordering expression
10. Refinement
Refinement is the process of replacing something somewhat abstract with something somewhat more concrete.For example, in one module one might declare a type name, with no definition,such astypeT
, and then in a refining module, provide a definition.One could prove general properties about the contents of an (abstract) module,and use that abstract module, and then later provide a more concrete implementation without having to redo all of the proofs.
Dafny supportsmodule refinement, where one module is created from another,and in that process the new module may be made more concrete than the previous.More precisely, refinement takes the following form in Dafny. One moduledeclares some program entities. A second modulerefines the first bydeclaring how to augment or replace (some of) those program entities.The first module is called therefinement parent; the second is therefining module; the result of combining the two (the original declarationsand the augmentation directives) is theassembled module orrefinement result.
Syntactically, the refinement parent is a normal module declaration.The refining module declares which module is its refinement parent with therefines
clause:
moduleP{// refinement parent}moduleMrefinesP{// refining module}
The refinement result is created as follows.
0) The refinement result is a module within the same enclosing module as therefining module, has the same name, and in fact replaces the refining module in their shared scope.
1) All the declarations (including import and export declarations) of the parent are copied into the refinement result.These declarations arenot re-resolved. That is, the assignment ofdeclarations and types to syntactic names is not changed. The refinementresult may exist in a different enclosing module and with a different set ofimports than the refinement parent, so that if names were reresolved, theresult might be different (and possibly not semantically valid).This is why Dafny does not re-resolve the names in their new context.
2) All the declarations of the refining module that have different namesthan the declarations in the refinement parent are also copied into therefinement result.However, because the refining module is just a set of augmentationdirectives and may refer to names copied from the refinement parent,resolution of names and types of the declarations copied in this step isperformed in the context of the full refinement result.
3) Where declarations in the parent and refinement module have the same name,the second refines the first and the combination, a refined declaration, isthe result placed in the refinement result module, to the exclusion of thedeclarations with the same name from the parent and refinement modules.
The way the refinement result declarations are assembled depends on the kind of declaration;the rules are described in subsections below.
So that it is clear that refinement is taking place, refining declarationshave some syntactic indicator that they are refining some parent declaration.Typically this is the presence of a...
token.
10.1. Export set declarations
A refining export set declaration begins withthe syntax
"export" Ident ellipsis
but otherwise contains the sameprovides
,reveals
andextends
sections,with the ellipsis indicating that it is a refining declaration.
The result declaration has the same name as the two input declarations and the unions of names from each of theprovides
,reveals
, andextends
sections, respectively.
An unnamed export set declaration from the parent is copied into the resultmodule with the name of the parent module. The result module has a defaultexport set according to the general rules for export sets, after all ofthe result module’s export set declarations have been assembled.
10.2. Import declarations
Aliasing import declarations are not refined. The result module contains the unionof the import declarations from the two input modules.There must be no names in common among them.
Abstract import declarations (declared with:
instead of=
,Section 4.6) are refined. The refinement parent contains theabstract import and the refining module contains a regular aliasingimport for the same name. Dafny checks that the refining importadheres tothe abstract import.
10.3. Sub-module declarations
With respect to refinement, a nested module behaves just like a top-level module. It may be declared abstract and it may be declared torefine
some refinement parent. If the nested module is not refining anything and not being refined, then it is copied into the refinement result like any other declaration.
Here is some example code:
abstractmoduleP{moduleA{consti:=5}abstractmoduleB{typeT}}moduleXrefinesP{moduleB'refinesP.B{typeT=int}moduleC{constk:=6}}moduleM{importXmethodm(){varz:X.B'.T:=X.A.i+X.C.k;}}
The refinement result ofP
andX
contains nested modulesA
,B'
, andC
. It is this refinement result that is imported intoM
.Hence the namesX.B'.T
,X.A.i
andX.C.k
are all valid.
10.4. Const declarations
Const declarations can be refined as in the following example.
moduleA{constToDefine:intconstToDefineWithoutType:intconstToGhost:int:=1}moduleBrefinesA{constToDefine:int:=2constToDefineWithoutType...:=3ghostconstToGhost:intconstNewConst:int}
Formally, a childconst
declaration may refine aconst
declarationfrom a parent module if
- the parent has no initialization,
- the child has the same type as the parent, and
- one or both of the following holds:
- the child has an initializing expression
- the child is declared
ghost
and the parent is notghost
.
A refining module can also introduce newconst
declarations that donot exist in the refinement parent.
10.5. Method declarations
Method declarations can be refined as in the following example.
abstractmoduleA{methodToImplement(x:int)returns(r:int)ensuresr>xmethodToStrengthen(x:int)returns(r:int)methodToDeterminize(x:int)returns(r:int)ensuresr>=x{vary:|y>=x;returny;}}moduleBrefinesA{methodToImplement(x:int)returns(r:int){returnx+2;}methodToStrengthen...ensuresr==x*2{returnx*2;}methodToDeterminize(x:int)returns(r:int){returnx;}}
Formally, a childmethod
definition may refine a parentmethod
declaration or definition by performing one or more of the followingoperations:
- provide a body missing in the parent (as in
ToImplement
), - strengthen the postcondition of the parent method by adding one or more
ensures
clauses (as inToStrengthen
), - provide a more deterministic version of a non-deterministic parentbody (as in
ToDeterminize
), or
The type signature of a child method must be the same as that of theparent method it refines. This can be ensured by providing an explicittype signature equivalent to that of the parent (with renaming ofparameters allowed) or by using an ellipsis (...
) to indicate copyingof the parent type signature. The body of a child method must satisfyany ensures clauses from its parent in addition to any it adds.
A refined method is allowed only if it does not invalidate any parentlemmas that mention it.
A refining module can also introduce newmethod
declarations ordefinitions that do not exist in the refinement parent.
10.6. Lemma declarations
As lemmas are (ghost) methods, the description of method refinement fromthe previous section also applies to lemma refinement.
A valid refinement is one that does not invalidate any proofs. A lemmafrom a refinement parent must still be valid for the refinement resultof any method or lemma it mentions.
10.7. Function and predicate declarations
Function (and equivalently predicate) declarations can be refined as inthe following example.
abstractmoduleA{functionF(x:int):(r:int)ensuresr>xfunctionG(x:int):(r:int)ensuresr>x{x+1}}moduleBrefinesA{functionF...{x+1}functionG...ensuresr==x+1}
Formally, a childfunction
(orpredicate
) definition can refine aparentfunction
(orpredicate
) declaration or definition to
- provide a body missing in the parent,
- strengthen the postcondition of the parent function by adding one or more
ensures
clauses.
The relation between the type signature of the parent and child functionis the same as for methods and lemmas, as described in the previous section.
A refining module can also introduce newfunction
declarations ordefinitions that do not exist in the refinement parent.
10.8. Class, trait and iterator declarations
Class, trait, and iterator declarations are refined as follows:
- If a class (or trait or iterator, respectively)
C
in a refining parent contains amember that is not matched by a same-named member in the classC
in the refining module, or vice-versa, then that class is copied as is to the refinement result. - When there are members with the same name in the class in the refinement parent and in the refining module, then the combination occurs according to the rules for that category of member.
Here is an example code snippet:
abstractmoduleP{classC{functionF():intensuresF()>0}}moduleXrefinesP{classC...{functionF...ensuresF()>0{1}}}
10.9. Type declarations
Types can be refined in two ways:
- Turning an abstract type into a concrete type;
- Adding members to a datatype or a newtype.
For example, consider the following abstract module:
abstractmoduleParent{typeTtypeB=booltypeS=s:string||s|>0witness"!"newtypePos=n:nat|n>0witness1datatypeBool=True|False}
In this module, typeT
is opaque and hence can be refined with any type,including class types. TypesB
,S
,Pos
, andBool
are concrete andcannot be refined further, except (forPos
andBool
) by giving themadditional members or attributes (or refining their existing members, if any).Hence, the following are valid refinements:
moduleChildWithTraitrefinesParent{traitT{}}moduleChildWithClassrefinesParent{classT{}}moduleChildWithSynonymTyperefinesParent{typeT=bool}moduleChildWithSubsetTyperefinesParent{typeT=s:seq<int>|s!=[]witness[0]}moduleChildWithDataTyperefinesParent{datatypeT=True|False}abstractmoduleChildWithExtraMembersrefinesParent{newtypePos...{methodPrint(){printthis;}}datatypeBool...{functionAsDafnyBool():bool{this.True?}}}
(The last example is markedabstract
because it leavesT
opaque.)
Note that datatype constructors, codatatype destructors, and newtype definitionscannot be refined: it is not possible to add or removedatatype
constructors,nor to change destructors of acodatatype
, nor to change the basetype, constraint, or witness of anewtype
.
When a type takes arguments, its refinement must use the same type argumentswith the same type constraints and the same variance.
When a type has type constraints, these type constraints must be preserved byrefinement. This means that a type declarationtypeT(!new)
cannot be refinedby aclassT
, for example. Similarly, atypeT(00)
cannot be refined by asubset type with awitness*
clause.
The refinement of an abstract type with body-less members can include both a definitionfor the type along with a body for the member, as in this example:
abstractmoduleP{typeT3{functionToString():string}}moduleXrefinesP{newtypeT3=i|0<=i<10{functionToString...{""}}}
Note that type refinements are not required to include the...
indicator that they are refining a parent type.
10.10. Statements
The refinement syntax (...
) in statements is deprecated.
11. Attributes
Dafny allows many of its entities to be annotated withAttributes.Attributes are declared between{:
and}
like this:
{:attributeName"argument","second"+"argument",57}
(White-space may follow but not precede the:
in{:
.)
In general an attribute may have any name the user chooses. It may befollowed by a comma-separated list of expressions. These expressions willbe resolved and type-checked in the context where the attribute appears.
Any Dafny entity may have a list of attributes.Dafny does not check that the attributes listed for an entityare appropriate for it (which means that misspellings maygo silently unnoticed).
The grammar shows where the attribute annotations may appear:
Attribute = "{:" AttributeName [ Expressions ] "}"
Dafny has special processing for some attributes13. Of those,some apply only to the entity bearing the attribute, while others (inheritedattributes) apply to the entity and its descendants (such as nested modules,types, or declarations). The attribute declaration closest to the entityoverrides those further away.
For attributes with a single boolean expression argument, the attributewith no argument is interpreted as if it were true.
11.1. Attributes on top-level declarations
11.1.1.{:autocontracts}
Dynamic frames [@Kassios:FM2006;@SmansEtAl:VeriCool;@SmansEtAl:ImplicitDynamicFrames;@LEINO:Dafny:DynamicFrames]are frame expressions that can vary dynamically duringprogram execution. AutoContracts is an experimental feature that willfill much of the dynamic-frames boilerplate into a class.
From the user’s perspective, what needs to be done is simply:
- mark the class with
{:autocontracts}
- declare a function (or predicate) called
Valid()
AutoContracts will then:
- Declare:
ghostvarRepr:set<object>
- For function/predicate
Valid()
, insert:readsthis,Repr
- Into body of
Valid()
, insert (at the beginning of the body):thisinRepr&&null!inRepr
- and also insert, for every array-valued field
A
declared in the class:&&(A!=null==>AinRepr)
- and for every field
F
of a class typeT
whereT
has a field calledRepr
, also insert:(F!=null==>FinRepr&&F.Repr<=Repr&&this!inF.Repr)
Except, if A or F is declared with
{:autocontractsfalse}
, then the implication will notbe added. - For every constructor, add:
modifiesthisensuresValid()&&fresh(Repr-{this})
- At the end of the body of the constructor, add:
Repr:={this};if(A!=null){Repr:=Repr+{A};}if(F!=null){Repr:=Repr+{F}+F.Repr;}
- For every method, add:
requiresValid()modifiesReprensuresValid()&&fresh(Repr-old(Repr))
- At the end of the body of the method, add:
if(A!=null){Repr:=Repr+{A};}if(F!=null){Repr:=Repr+{F}+F.Repr;}
11.1.2.{:nativeType}
The{:nativeType}
attribute is only recognized by anewtype
declarationwhere the base type is an integral type or a real type. For example:
newtype{:nativeType"byte"}ubyte=x:int|0<=x<256newtype{:nativeType"byte"}bad_ubyte=x:int|0<=x<257// Fails
It can take one of the following forms:
{:nativeType}
- With no parameters it has no effect and the declarationwill have its default behavior, which is to choose a native type that can hold anyvalue satisfying the constraints, if possible, and otherwise to use BigInteger.{:nativeTypetrue}
- Also gives default behavior,but gives an error if the base type is not integral.{:nativeTypefalse}
- Inhibits using a native type. BigInteger is used.{:nativeType"typename"}
- This form has an native integraltype name as a string literal. Acceptable values are:"byte"
8 bits, unsigned"sbyte"
8 bits, signed"ushort"
16 bits, unsigned"short"
16 bits, signed"uint"
32 bits, unsigned"int"
32 bits, signed"number"
53 bits, signed"ulong"
64 bits, unsigned"long"
64 bits, signed
If the target compilerdoes not support a named native type X, then an error is generated. Also, if, afterscrutinizing the constraint predicate, the compiler cannot confirmthat the type’s values will fit in X, an error is generated.The names given above do not have to match the names in the target compilation language,just the characteristics of that type.
11.1.3.{:ignore}
(deprecated)
Ignore the declaration (after checking for duplicate names).
11.1.4.{:extern}
{:extern}
is a target-language dependent modifier used
- to alter the
CompileName
of entities such as modules, classes, methods, etc., - to alter the
ReferenceName
of the entities, - to decide how to define external abstract types,
- to decide whether to emit target code or not, and
- to decide whether a declaration is allowed not to have a body.
TheCompileName
is the name for the entity when translating to one of the target languages.TheReferenceName
is the name used to refer to the entity in the target language.A common use case of{:extern}
is to avoid name clashes with existing library functions.
{:extern}
takes 0, 1, or 2 (possibly empty) string arguments:
{:extern}
: Dafny will use the Dafny-determined name as theCompileName
and not affect theReferenceName
{:externs1}
: Dafny will uses1
as theCompileName
, and replaces the last portion of theReferenceName
bys1
. When used on an abstract type, s1 is used as a hint as to how to declare that type when compiling.{:externs1,s2}
Dafny will uses2
as theCompileName
. Dafny will use a combination ofs1
ands2
such as for examples1.s2
as theReferenceName
It may also be the case that one of the arguments is simply ignored.
Dafny does not perform sanity checks on the arguments—it is the user’s responsibility not to generate malformed target code.
For more detail on the use of{:extern}
, see the correspondingsection in the user’s guide.
11.1.5.{:disableNonlinearArithmetic}
This attribute only applies to module declarations. It overrides the global option--disable-nonlinear-arithmetic
for that specific module. The attribute can be given true or false to disable or enable nonlinear arithmetic. When no value is given, the default value is true.
11.2. Attributes on functions and methods
11.2.1.{:abstemious}
The{:abstemious}
attribute is appropriate for functions on codatatypes.If appropriate to a function, the attribute can aid in proofs that the function isproductive.Seethe section on abstemious functions for more description.
11.2.2.{:autoReq}
For a function declaration, if this attribute is set true at the nearestlevel, then itsrequires
clause is strengthened sufficiently so thatit may call the functions that it calls.
For following example
functionf(x:int):boolrequiresx>3{x>7}// Should succeed thanks to auto_reqsfunction{:autoReq}g(y:int,b:bool):bool{ifbthenf(y+2)elsef(2*y)}
the{:autoReq}
attribute causes Dafny todeduce arequires
clause for g as if it had beendeclared
functionf(x:int):boolrequiresx>3{x>7}functiong(y:int,b:bool):boolrequiresifbtheny+2>3else2*y>3{ifbthenf(y+2)elsef(2*y)}
11.2.3.{:autoRevealDependenciesk}
When setting--default-function-opacity
toautoRevealDependencies
, the{:autoRevealDependenciesk}
attribute can be set on methods and functions to make sure that only function dependencies of depthk
in the call-graph or less are revealed automatically. As special cases, one can also use{:autoRevealDependenciesfalse}
(or{:autoRevealDependencies0}
) to make sure that no dependencies are revealed, and{:autoRevealDependenciestrue}
to make sure that all dependencies are revealed automatically.
For example, when the following code is run with--default-function-opacity
set toautoRevealDependencies
, the functionp()
should verify andq()
should not.
functiont1():bool{true}functiont2():bool{t1()}function{:autoRevealDependencies1}p():(r:bool)ensuresr{t1()}function{:autoRevealDependencies1}q():(r:bool)ensuresr{t2()}
11.2.4.{:axiom}
The{:axiom}
attribute may be placed on a function or method.It means that the post-condition may be assumed to be truewithout proof. In that case also the body of the function ormethod may be omitted.
The{:axiom}
attribute only prevents Dafny from verifying that the body matches the post-condition.Dafny still verifies thewell-formedness of pre-conditions, of post-conditions, and of the body if provided.To prevent Dafny from running all these checks, one would use{:verifyfalse}
, which is not recommended.
The compiler will still emit code for an{:axiom}
, if it is afunction
, amethod
or afunctionbymethod
with a body.
11.2.5.{:compile}
The{:compile}
attribute takes a boolean argument. It may be applied toany top-level declaration. If that argument is false, then that declarationwill not be compiled at all.The difference with{:extern}
is that{:extern}
will still emit declaration code if necessary,whereas{:compilefalse}
will just ignore the declaration for compilation purposes.
11.2.6.{:concurrent}
The{:concurrent}
attribute indicates that the compiled code for a function or methodmay be executed concurrently.While Dafny is a sequential language and does not support any native concepts for spawningor controlling concurrent execution,it does support restricting the specification of declarations such that it is safe to execute them concurrentlyusing integration with the target language environment.
Currently, the only way to satisfy this requirement is to ensure that the specificationof the function or method includes the equivalent ofreads{}
andmodifies{}
.This ensures that the code does not read or write any shared mutable state,although it is free to read and write newly allocated objects.
11.2.7.{:extern<name>}
See{:extern<name>}
.
11.2.8.{:fuelX}
The fuel attribute is used to specify how much “fuel” a function should have,i.e., how many times the verifier is permitted to unfold its definition. The{:fuel}
annotation can be added to the function itself, in whichcase it will apply to all uses of that function, or it can be overriddenwithin the scope of a module, function, method, iterator, calc, forall,while, assert, or assume. The general format is:
{:fuelfunctionName,lowFuel,highFuel}
When applied as an annotation to the function itself, omitfunctionName. If highFuel is omitted, it defaults to lowFuel + 1.
The default fuel setting for recursive functions is 1,2. Setting thefuel higher, say, to 3,4, will give more unfoldings, which may makesome proofs go through with less programmer assistance (e.g., withfewer assert statements), but it may also increase verification time,so use it with care. Setting the fuel to 0,0 is similar to making thedefinition opaque, except when used with all literal arguments.
11.2.9.{:id<string>}
Assign a custom unique ID to a function or a method to be used for verificationresult caching.
11.2.10.{:induction}
The{:induction}
attribute controls the application ofproof by induction to two contexts. Given a list ofvariables on which induction might be applied, the{:induction}
attribute selects a sub-list of thosevariables (in the same order) to which to apply induction.
Dafny issue34proposes to remove the restriction that the sub-listbe in the same order, and would apply induction in theorder given in the{:induction}
attribute.
The two contexts are:
- A method, in which case the bound variables are all thein-parameters of the method.
- Aquantifier expression, in which case the bound variablesare the bound variables of the quantifier expression.
The form of the{:induction}
attribute is one of the following:
{:induction}
or{:inductiontrue}
– apply induction to all bound variables{:inductionfalse}
– suppress induction, that is, don’t apply it to any bound variable{:inductionL}
whereL
is a sublist of the bound variables– apply induction to the specified bound variables{:inductionX}
whereX
is anything else – raise an error.
Here is an example of using it on a quantifier expression:
datatypeUnary=Zero|Succ(Unary)functionUnaryToNat(n:Unary):nat{matchncaseZero=>0caseSucc(p)=>1+UnaryToNat(p)}functionNatToUnary(n:nat):Unary{ifn==0thenZeroelseSucc(NatToUnary(n-1))}lemmaCorrespondence()ensuresforalln:nat{:inductionn}::UnaryToNat(NatToUnary(n))==n{}
11.2.11.{:inductionTrigger}
Dafny automatically generates triggers for quantified induction hypotheses. The default selection can be overridden using the{:inductionTrigger}
attribute, which works like the usual{:trigger}
attribute.
11.2.12.{:only}
method{:only}X(){}
orfunction{:only}X(){}
temporarily disables the verification of all other non-{:only}
members, e.g. other functions and methods, in the same file, even if they containassertions with{:only}
.
method{:only}TestVerified(){asserttrue;// Uncheckedassert{:only}trueby{// Checkedasserttrue;// Checked}asserttrue;// Unchecked}methodTestUnverified(){asserttrue;// Uncheckedassert{:only}trueby{// Unchecked because of {:only} Test()asserttrue;// Unchecked}asserttrue;// Unchecked}
{:only}
can help focusing on a particular member, for example a lemma or a function, as it simply disables the verification of all other lemmas, methods and functions in the same file. It’s equivalent to adding{:verifyfalse}
to all other declarations simulatenously on the same file. Since it’s meant to be a temporary construct, it always emits a warning.
More information about the Boogie implementation of{:opaque}
ishere.
11.2.13.{:print}
This attribute declares that a method may have print effects,that is, it may useprint
statements and may call other methodsthat have print effects. The attribute can be applied to compiledmethods, constructors, and iterators, and it gives an error ifapplied to functions or ghost methods. An overriding method isallowed to use a{:print}
attribute only if the overridden methoddoes.Print effects are enforced only with--track-print-effects
.
11.2.14.{:priority}
{:priorityN}
assigns a positive priority ‘N’ to a method or function to control the orderin which methods or functions are verified (default: N = 1).
11.2.15.{:resource_limit}
and{:rlimit}
{:resource_limitN}
limits the verifier resource usage to verify the method or function toN
.
This is the per-method equivalent of the command-line flag/rlimit:N
or--resource-limitN
.If using{:isolate_assertions}
as well, the limit will be set for each assertion.
The attribute{:rlimitN}
is also available, and limits the verifier resource usage to verify the method or function toN*1000
. This version is deprecated, however.
To give orders of magnitude about resource usage, here is a list of examples indicating how many resources are used to verify each method:
- 8K resource usage
methodf(){asserttrue;}
- 10K resource usage using assertions that do not add assumptions:
methodf(a:bool,b:bool){asserta:(a==>b)<==>(!b==>!a);assertb:(a==>b)<==>(!b==>!a);assertc:(a==>b)<==>(!b==>!a);assertd:(a==>b)<==>(!b==>!a);}
- 40K total resource usage using
{:isolate_assertions}
method{:isolate_assertions}f(a:bool,b:bool){asserta:(a==>b)<==>(!b==>!a);assertb:(a==>b)<==>(!b==>!a);assertc:(a==>b)<==>(!b==>!a);assertd:(a==>b)<==>(!b==>!a);}
- 37K total resource usage and thus fails with
outofresource
.method{:rlimit30}f(a:int,b:int,c:int){assert((1+a*a)*c)/(1+a*a)==c;}
Note that, the default solver Z3 tends to overshoot by7K
to8K
, so if you put{:rlimit20}
in the last example, the total resource usage would be27K
.
11.2.16.{:selective_checking}
Turn all assertions into assumptions except for the ones reachable from after theassertions marked with the attribute{:start_checking_here}
.Thus,assume{:start_checking_here}something;
becomes an inverseofassumefalse;
: the first one disables all verification beforeit, and the second one disables all verification after.
11.2.17.{:tailrecursion}
This attribute is used on method or function declarations. It has a boolean argument.
If specified with afalse
value, it means the user specificallyrequested no tail recursion, so none is done.
If specified with atrue
value, or if no argument is specified,then tail recursive optimization will be attempted subject tothe following conditions:
- It is an error if the method is a ghost method and tailrecursion was explicitly requested.
- Only direct recursion is supported, not mutually recursive methods.
- If
{:tailrecursiontrue}
was specified but the code does not allow it,an error message is given.
If you have a stack overflow, it might be that you havea function on which automatic attempts of tail recursionfailed, but for which efficient iteration can be implemented by hand. To do this,use afunction by method anddefine the loop in the method yourself,proving that it implements the function.
Using a function by method to implement recursion canbe tricky. It usually helps to look at the result of the functionon two to three iterations, without simplification,and see what should be the first computation. For example,consider the following tail-recursion implementation:
datatypeResult<V,E>=Success(value:V)|Failure(error:E)functionf(x:int):Result<int,string>// {:tailrecursion true} Not possible herefunctionMakeTailRec(obj:seq<int>):Result<seq<int>,string>{if|obj|==0thenSuccess([])elsevartail:=MakeTailRec(obj[1..]);varr:=f(obj[0]);ifr.Failure?thenFailure(r.error)elseiftail.Failure?thentailelseSuccess([r.value]+tail.value)}bymethod{vari:nat:=|obj|;vartail:=Success([]);// Base casewhilei!=0decreasesiinvarianttail==MakeTailRec(obj[i..]){i:=i-1;varr:=f(obj[i]);ifr.Failure?{tail:=Failure(r.error);}elseiftail.Success?{tail:=Success([r.value]+tail.value);}else{}}returntail;}
The rule of thumb to unroll a recursive call into a sequential oneis to look at how the result would be computed if the operations were notsimplified. For example, unrolling the function on[1,2,3]
yields the resultSuccess([f(1).value]+([f(2).value]+([f(3).value]+[])))
.If you had to compute this expression manually, you’d start with([f(3).value]+[])
, then add[f(2).value]
to the left, then[f(1).value]
.This is why the method loop iterates with the objectsfrom the end, and why the intermediate invariants areall about provingtail==MakeTailRec(obj[i..])
, whichmakes verification succeed easily because we replicateexactly the behavior ofMakeTailRec
.If we were not interested in the first error but the last one,a possible optimization would be, on the first error, to finishiterate with a ghost loop that is not executed.
Note that the function definition can be changed by computingthe tail closer to where it’s used or switching the order of computingr
andtail
, but thebymethod
body can stay the same.
11.2.18.{:test}
This attribute indicates the target function or method is meantto be executed at runtime in order to test that the program is working as intended.
There are two different ways to dynamically test functionality in a test:
- A test can optionally return a single value to indicate success or failure.If it does, this must be afailure-compatible typejust as theupdate-with-failure statement requires. That is,the returned type must define a
IsFailure()
function method. IfIsFailure()
evaluates totrue
on the return value, the test will be marked a failure, and thisreturn value used as the failure message. - Code in the control flow of the test can use
expect
statementsto dynamically test if a boolean expression is true, and cause the test to haltif not (but not the overall testing process). The optional second argument to a failedexpect
statement will be used as the test failure message.
Note that theexpect
keyword can also be used to form “assign or halt” statementssuch asvarx:-expectCalculateX();
, which is a convenient way to invoke a methodthat may produce a failure within a test without having to return a value from the test.
There are also two different approaches to executing all tests in a program:
- By default, the compiler will mark each compiled method as necessary so thata designated target language testing framework will discover and run it.This is currently only implemented for C#, using the xUnit
[Fact]
annotation. - If
dafnytest
is used, Dafny will instead produce a main methodthat invokes each test and prints the results.This runner is currently very basic, but avoids introducing any additional targetlanguage dependencies in the compiled code.
A method marked{:test}
may not have any input arguments. If there is anoutput value that does not have a failure-compatible type, that value is ignored. A method that does have input arguments can be wrapped in a testharness that supplies input arguments but has no inputs of its own and thatchecks any output values, perhaps withexpect
statements. The test harnessis then the method marked with{:test}
.
11.2.19.{:timeLimitN}
Set the time limit for verifying a given function or method.
11.2.20.{:timeLimitMultiplierX}
This attribute may be placed on a method or function declarationand has an integer argument. If{:timeLimitMultiplierX}
wasspecified a{:timeLimitY}
attribute is passed on to BoogiewhereY
isX
times either the default verification time limitfor a function or method, or times the value specified by theBoogie-timeLimit
command-line option.
11.2.21.{:transparent}
By default, the body of a function is transparent to its users. This can be overridden using the--default-function-opacity
command line flag. If default function opacity is set toopaque
orautoRevealDependencies
, then this attribute can be used on functions to make them always non-opaque.
11.2.22.{:verifyfalse}
Skip verification of a function or a method altogether,not even trying to verify thewell-formedness of postconditions and preconditions.We discourage using this attribute and prefer{:axiom}
,which performs these minimal checks while not checking that the body satisfies the postconditions.
If you simply want to temporarily disable all verification except on a single function or method, use the{:only}
attribute on that function or method.
11.2.23.{:vcs_max_costN}
Per-method version of the command-line option/vcsMaxCost
.
Theassertion batch of a methodwill not be split unless the cost of anassertion batch exceeds thisnumber, defaults to 2000.0. Inkeep-going mode, only applies to the first round.If{:isolate_assertions}
is set, then this parameter is useless.
11.2.24.{:vcs_max_keep_going_splitsN}
Per-method version of the command-line option/vcsMaxKeepGoingSplits
.If set to more than 1, activates thekeep going mode where, after the first round of splitting,assertion batches that timed out are split into Nassertion batches and retrieduntil we succeed proving them, or there is only onesingle assertion that it timeouts (in whichcase an error is reported for that assertion).Defaults to 1.If{:isolate_assertions}
is set, then this parameter is useless.
11.2.25.{:vcs_max_splitsN}
Per-method version of the command-line option/vcsMaxSplits
.Maximal number ofassertion batches generated for this method.Inkeep-going mode, only applies to the first round.Defaults to 1.If{:isolate_assertions}
is set, then this parameter is useless.
11.2.26.{:isolate_assertions}
Per-method version of the command-line option/vcsSplitOnEveryAssert
In the first and only verification round, this option will split the originalassertion batchinto one assertion batch per assertion.This is mostly helpful for debugging which assertion is taking the most time to prove, e.g. to profile them.
11.2.27.{:synthesize}
The{:synthesize}
attribute must be used on methods that have no body andreturn one or more fresh objects. During compilation, the postconditions associated with such amethod are translated to a series of API calls to the target languages’smocking framework. The object returned, therefore, behaves exactly as thepostconditions specify. If there is a possibility that this behavior violatesthe specifications on the object’s instance methods or hardcodes the values ofits fields, the compiler will throw an error but the compilation will gothrough. Currently, this compilation pass is only supported in C# and requiresadding the latest version of the Moq library to the .csproj file beforegenerating the binary.
Not all Dafny postconditions can be successfully compiled - below is thegrammar for postconditions that are supported (S
is the start symbol,EXPR
stands for an arbitrary Dafny expression, andID
stands forvariable/method/type identifiers):
S = FORALL | EQUALS | S && SEQUALS = ID.ID (ARGLIST) == EXPR // stubs a function call | ID.ID == EXPR // stubs field access | EQUALS && EQUALSFORALL = forall BOUNDVARS :: EXPR ==> EQUALSARGLIST = ID // this can be one of the bound variables | EXPR // this expr may not reference any of the bound variables | ARGLIST, ARGLISTBOUNDVARS = ID : ID | BOUNDVARS, BOUNDVARS
11.2.28.{:optionsOPT0,OPT1,...}
This attribute applies only to modules. It configures Dafny as ifOPT0
,OPT1
, … had been passed on the command line. Outside of the module,options revert to their previous values.
Only a small subset of Dafny’s command line options is supported. Use the/attrHelp
flag to see which ones.
11.3. Attributes on reads and modifies clauses
11.3.1.{:assume_concurrent}
This attribute is used to allow non-emptyreads
ormodifies
clauses on methodswith the{:concurrent}
attribute, which would otherwise reject them.
In some cases it is possible to know that Dafny code that reads or writes shared mutable stateis in fact safe to use in a concurrent setting, especially when that state is exclusively ghost.Since the semantics of{:concurrent}
aren’t directly expressible in Dafny syntax,it isn’t possible to express this assumption with anassume{:axiom}...
statement.
See also the{:concurrent}
attribute.
11.4. Attributes on assertions, preconditions and postconditions
11.4.1.{:only}
assert{:only}X;
temporarily transforms all other non-{:only}
assertions in the surrounding declaration into assumptions.
methodTest(){asserttrue;// Uncheckedassert{:only}trueby{// Checkedasserttrue;// Checked}asserttrue;// Uncheckedassert{:only"after"}true;// Checkedasserttrue;// Checkedassert{:only"before"}true;// Checkedasserttrue;// Unchecked}
{:only}
can help focusing on a particular proof or a particular branch, as it transforms not only other explicit assertions, but also other implicit assertions, and call requirements, into assumptions.Since it’s meant to be a temporary construct, it always emits a warning.It also has two variantsassert{:only"before"}
andassert{:only"after"}
.Here is precisely how Dafny determines what to verify or not.Each{:only}
annotation defines a “verification interval” which is visual:
assert{:only}X[by{...}|;]
sets a verification interval that starts at the keywordassert
and ends either at the end of the proof}
or the semicolon;
, depending on which variant ofassert
is being used.assert{:only}...
inside another verification interval removes that verification interval and sets a new one.assert{:only"before"}...
inside another verification interval finishes that verification interval earlier at the end of this assertion. Outside a verification interval, it sets a verification interval from the beginning of the declaration to the end of this assertion, but only if there were no other verification intervals before.assert{:only"after"}...
inside another verification interval moves the start of that verification interval to the start of this new assert. Outside a verification interval, it sets a verification interval from the beginning of thisassert
to the end of the declaration.
The start of an asserted expression is used to determines if it’s inside a verification interval or not.For example, inassertB==>(assert{:only"after"}true;C)
,C
is actually the start of the asserted expression, so it is verified because it’s afterassert{:only"after"}true
.
As soon as a declaration contains oneassert{:only}
, none of the postconditions are verified; you’d need to make them explicit with assertions if you wanted to verify them at the same time.
You can also isolate the verification of a single member usinga similar{:only}
attribute.
11.4.2.{:focus}
assert{:focus}X;
splits verification into twoassertion batches.The first batch considers all assertions that are not on the block containing theassert{:focus}X;
The second batch considers all assertions that are on the block containing theassert{:focus}X;
and those that willalways follow afterwards.Hence, it might also occasionally double-report errors.If you truly want a split on the batches, prefer{:split_here}
.
Here are two examples illustrating how{:focus}
works, where--
in the comments stands forAssumption
:
methoddoFocus1(x:bool)returns(y:int){y:=1;// Batch 1 Batch 2asserty==1;// Assertion --ifx{iffalse{asserty>=0;// -- Assertionassert{:focus}y<=2;// -- Assertiony:=2;asserty==2;// -- Assertion}}else{asserty==1;// Assertion --}asserty==1;// Assertion Assertionif!x{asserty>=1;// Assertion Assertion}else{asserty<=1;// Assertion Assertion}}
And another one where the focused block is guarded with awhile
, resulting in remaining assertions not being part of the first assertion batch:
methoddoFocus2(x:bool)returns(y:int){y:=1;// Batch 1 Batch 2asserty==1;// Assertion --ifx{whilefalse{asserty>=0;// -- Assertionassert{:focus}y<=2;// -- Assertiony:=2;asserty==2;// -- Assertion}}else{asserty==1;// Assertion --}asserty==1;// Assertion --if!x{asserty>=1;// Assertion --}else{asserty<=1;// Assertion --}}
11.4.3.{:split_here}
assert{:split_here}X;
splits verification into twoassertion batches.It verifies the code leading to this point (excluded) in a first assertion batch,and the code leading from this point (included) to the next{:split_here}
or until the end in a second assertion batch.It might help with timeouts.
Here is one example, where--
in the comments stands forAssumption
:
methoddoSplitHere(x:bool)returns(y:int){y:=1;// Batch 1 Batch 2 Batch 3asserty>=0;// Assertion -- --ifx{asserty<=1;// Assertion -- --assert{:split_here}true;// -- Assertion --asserty<=2;// -- Assertion --assert{:split_here}true;// -- -- Assertionifx{asserty==1;// -- -- Assertion}else{asserty>=1;// -- -- Assertion}}else{asserty<=3;// Assertion -- --}asserty>=-1;// Assertion -- --}
11.4.4.{:subsumptionn}
Overrides the/subsumption
command-line setting for this assertion.{:subsumption0}
checks an assertion but does not assume it after proving it.You can achieve the same effect usinglabelled assertions.
11.4.5.{:error"errorMessage","successMessage"}
Provides a custom error message in case the assertion fails.As a hint, messages indicating what the user needs to do to fix the error are usually better than messages that indicate the error only.For example:
methodProcess(instances:int,price:int)requires{:error"There should be an even number of instances","The number of instances is always even"}instances%2==0requires{:error"Could not prove that the price is positive","The price is always positive"}price>=0{}methodTest(){if*{Process(1,0);// Error: There should be an even number of instances}if*{Process(2,-1);// Error: Could not prove that the price is positive}if*{Process(2,5);// Success: The number of instances is always even// Success: The price is always positive}}
The success message is optional but is recommended if errorMessage is set.
11.4.6.{:contradiction}
Silences warnings about this assertion being involved in a proof using contradictory assumptions when--warn-contradictory-assumptions
is enabled. This allows clear identification of intentional proofs by contradiction.
11.5. Attributes on variable declarations
11.5.1.{:assumption}
This attribute can only be placed on a local ghost boolvariable of a method. Its declaration cannot have a rhs, but it isallowed to participate as the lhs of exactly one assignment of theform:b:=b&&expr;
. Such a variable declaration translates in theBoogie output to a declaration followed by anassumeb
command.See [@LeinoWuestholz2015], Section 3, for example uses of the{:assumption}
attribute in Boogie.
11.6. Attributes on quantifier expressions (forall, exists)
11.6.1.{:heapQuantifier}
This attribute has been removed.
11.6.2.{:induction}
See{:induction}
for functions and methods.
11.6.3.{:trigger}
Trigger attributes are used on quantifiers and comprehensions.
The verifier instantiates the body of a quantified expression only when it can find an expression that matches the provided trigger.
Here is an example:
predicateP(i:int)predicateQ(i:int)lemma{:axiom}PHoldEvenly()ensuresforalli{:triggerQ(i)}::P(i)==>P(i+2)&&Q(i)lemmaPHoldsForTwo()ensuresforalli::P(i)==>P(i+4){forallj:intensuresP(j)==>P(j+4){ifP(j){assertP(j);// Trivial assertionPHoldEvenly();// Invoking the lemma assumes `forall i :: P(i) ==> P(i + 4)`,// but it's not instantiated yet// The verifier sees `Q(j)`, so it instantiates// `forall i :: P(i) ==> P(i + 4)` with `j`// and we get the axiom `P(j) ==> P(j + 2) && Q(j)`assertQ(j);// hence it can prove `Q(j)`assertP(j+2);// and it can prove `P(j + 2)`assertP(j+4);// But it cannot prove this// because it did not instantiate `forall i :: P(i) ==> P(i + 4)` with `j+2`}}}
Here are ways one can proveassertP(j+4);
:
- Add
assertQ(j+2);
just beforeassertP(j+4);
, so that the verifier sees the trigger. - Change the trigger
{:triggerQ(i)}
to{:triggerP(i)}
(replace the trigger) - Change the trigger
{:triggerQ(i)}
to{:triggerQ(i)}{:triggerP(i)}
(add a trigger) - Remove
{:triggerQ(i)}
so that it will automatically determine all possible triggers thanks to the option/autoTriggers:1
which is the default.
11.7. Deprecated attributes
These attributes have been deprecated or removed. They are no longer useful (or perhaps never were) or were experimental.They will likely be removed entirely sometime soon after the release of Dafny 4.
Removed:
- :heapQuantifier
- :dllimport
- :handle
Deprecated:
- :opaque : This attribute has been promoted to a first-class modifier for functions. Find more informationhere.
11.8. Other undocumented verification attributes
A scan of Dafny’s sources shows it checks for thefollowing attributes.
{:$}
{:$renamed$}
{:InlineAssume}
{:PossiblyUnreachable}
{:__dominator_enabled}
{:__enabled}
{:a##post##}
{:absdomain}
{:ah}
{:assumption}
{:assumption_variable_initialization}
{:atomic}
{:aux}
{:both}
{:bvbuiltin}
{:candidate}
{:captureState}
{:checksum}
{:constructor}
{:datatype}
{:do_not_predicate}
{:entrypoint}
{:existential}
{:exitAssert}
{:expand}
{:extern}
{:focus}
{:hidden}
{:ignore}
{:inline}
{:left}
{:linear}
{:linear_in}
{:linear_out}
{:msg}
{:name}
{:originated_from_invariant}
{:partition}
{:positive}
{:post}
{:pre}
{:precondition_previous_snapshot}
{:qid}
{:right}
{:selective_checking}
{:si_fcall}
{:si_unique_call}
{:sourcefile}
{:sourceline}
{:split_here}
{:stage_active}
{:stage_complete}
{:staged_houdini_tag}
{:start_checking_here}
{:subsumption}
{:template}
{:terminates}
{:upper}
{:verified_under}
{:weight}
{:yields}
11.9. New attribute syntax
There is a new syntax for typed prefix attributes that is being added:@Attribute(...)
.For now, the new syntax works only as top-level declarations. When all previous attributes will be migrated, this section will be rewritten. For example, you can write
@IsolateAssertionsmethodTest(){}
instead of
method{:isolate_assertions}Test(){}
Dafny rewrites@
-attributes to old-style equivalent attributes. The definition of these attributes is similar to the following:
datatypeAttribute=|AutoContracts|AutoRequires|AutoRevealDependenciesAll|AutoRevealDependencies|Axiom|Compile(bool)|Concurrent|DisableNonlinearArithmetic|Fuel(low:int,high:int:=low+1,functionName:string:="")|IsolateAssertions|NativeUInt8|NativeInt8...|NativeUInt128|NativeInt128|NativeInt|NativeNone|NativeIntOrReal|Options(string)|Print|Priority(weight:int)|ResourceLimit(value:string)|Synthesize|TimeLimit(amount:int)|TimeLimitMultiplier(multiplier:int)|TailRecursion|Test|TestEntry|TestInline(amount:int)|Transparent|VcsMaxCost|VcsMaxKeepGoingSplits|VcsMaxSplits|Verify(verify:bool)|VerifyOnly
@-attributes have the same checks as regular resolved datatype values
- The attribute should exist
- Arguments should be compatible with the parameters, like for a datatype constructor call
However, @-attributes have more checks:
- The attribute should be applied to a place where it can be used by Dafny
- Arguments should be literals
12. Advanced Topics
12.1. Type Parameter Completion
Generic types, likeA<T,U>
, consist of atype constructor, hereA
, and type parameters, hereT
andU
.Type constructors are not first-class entities in Dafny, they are always used syntactically to constructtype names; to do so, they must have the requisite number of type parameters, which must be either concrete types, type parameters, or a generic type instance.
However, those type parameters do not always have to be explicit; Dafny can often infer what they ought to be.For example, here is a fully parameterized function signature:
typeList<T>functionElements<T>(list:List<T>):set<T>
However, Dafny also accepts
typeList<T>functionElements(list:List):set
In the latter case, Dafny knows that the already defined typesset
andList
each take one type parameterso it fills in<T>
(using some unique type parameter name) and then determines that the function itself needsa type parameter<T>
as well.
Dafny also accepts
typeList<T>functionElements<T>(list:List):set
In this case, the function already has a type parameter list.List
andset
are each known to need type parameters,so Dafny takes the firstn
parameters from the function signature and applies them toList
andset
, wheren
(here1
) is thenumber needed by those type constructors.
It never hurts to simply write in all the type parameters, but that can reduce readability.Omitting them in cases where Dafny can intuit them makes a more compact definition.
This process is described in more detail with more examples in this paper:http://leino.science/papers/krml270.html.
12.2. Type Inference
Signatures of methods, functions, fields (exceptconst
fields with aRHS), and datatype constructors have to declare the types of theirparameters. In other places, types can be omitted, in which caseDafny attempts to infer them. Type inference is “best effort” and mayfail. If it fails to infer a type, the remedy is simply for theprogram to give the type explicitly.
Despite being just “best effort”, the types of most local variables,bound variables, and the type parameters of calls are usually inferredwithout the need for a program to give the types explicitly. Here aresome notes about type inference:
- With some exceptions, type inference is performed across a wholemethod body. In some cases, the information needed to infer a localvariable’s type may be found after the variable has been declaredand used. For example, the nonsensical program
```dafnymethodM(n:nat)returns(y:int){vara,b;fori:=0ton{ifi%2==0{a:=a+b;}}y:=a;}```
usesa
andb
after their declarations. Still, their types are inferred to beint
, because of the presence of the assignmenty:=a;
.
A more useful example is this:
```dafnyclassCell{vardata:int}methodLastFive(a:array<int>)returns(r:int){varu:=null;fori:=0toa.Length{ifa[i]==5{u:=newCell;u.data:=i;}}r:=ifu==nullthena.Lengthelseu.data;}```
Here, using only the assignmentu:=null;
to infer the type ofu
would not be helpful. But Dafny looks past the initial assignment and infers the type ofu
to beCell?
.
- The primary example where type inference does not inspect the entirecontext before giving up on inference is when there is a memberlookup. For example,
```dafnydatatypeList<T>=Nil|Cons(T,List<T>)methodTutone(){assertforallpair::pair.0==867&&pair.1==5309==>pair==(867,5309);// error: members .0 and .1 not foundassertforallpair:(int,int)::pair.0==867&&pair.1==5309==>pair==(867,5309);}```
In the first quantifier, type inference fails to infer the type ofpair
before it tries to look up the members.0
and.1
, which results in a “type of the receiver not fully determined” error. The remedy is to provide the type ofpair
explicitly, as is done in the second quantifier.
(In the future, Dafny may do more type inference before giving up on the member lookup.)
- If type parameters cannot be inferred, then they can be givenexplicitly in angle brackets. For example, in
```dafnydatatypeOption<T>=None|Some(T)methodM(){vara:Option<int>:=None;varb:=None;// error: type is underspecifiedvarc:=Option<int>.None;vard:=None;d:=Some(400);}```
the type ofb
cannot be inferred, because it is underspecified. However, the types ofc
andd
are inferred to beOption<int>
.
Here is another example:
```dafnyfunctionEmptySet<T>():set<T>{{}}methodM(){vara:=EmptySet();// error: type is underspecifiedvarb:=EmptySet();b:=b+{2,3,5};varc:=EmptySet<int>();}```
The type instantiation in the initial assignment toa
cannot be inferred, because it is underspecified. However, the type instantiation in the initial assignment tob
is inferred to beint
, and the types ofb
andc
are inferred to beset<int>
.
- Even the element type of
new
is optional, if it can be inferred. For example, in
```dafnymethodNewArrays(){vara:=newint[3];varb:array<int>:=new[3];varc:=new[3];c[0]:=200;vard:=new[3][200,800,77];vare:=new[][200,800,77];varf:=new[3](_=>990);}```
the omitted types of local variables are all inferred asarray<int>
and the omitted element type of eachnew
is inferred to beint
.
In the absence of any other information, integer-looking literals(like
5
and7
) are inferred to have typeint
(and not, say,bv128
orORDINAL
).Many of the types inferred can be inspected in the IDE.
12.3. Ghost Inference
After14type inference, Dafny revisits the programand makes a final decision about which statements are to be compiled,and which statements are ghost.The ghost statements form what is called theghost context of expressions.
These statements are determined to be ghost:
assert
,assume
,reveal
, andcalc
statements.- The body of the
by
of anassert
statement. - Calls to ghost methods, includinglemmas.
if
,match
, andwhile
statements with condition expressions or alternatives containing ghost expressions. Their bodies are also ghost.for
loops whose start expression contains ghost expressions.- Variable declarations if they are explicitly ghost or if their respective right-hand side is a ghost expression.
- Assignments or update statement if all updated variables are ghost.
forall
statements, unless there is exactly one assignment to a non-ghost array in its body.
These statements always non-ghost:
The following expressions are ghost, which is used in some of the tests above:
- Allspecification expressions
- All calls to functions and predicates marked as
ghost
- All variables,constants andfields declared using the
ghost
keyword
Note that inferring ghostness can uncover other errors, such as updating non-ghost variables in ghost contexts.For example, iff
is a ghost function, in the presence of the following code:
varx:=1;if(f(x)){x:=2;}
Dafny will infer that the entireif
is ghost because the condition uses a ghost function,and will then raise the error that it’s not possible to update the non-ghost variablex
in a ghost context.
12.4. Well-founded Functions and Extreme Predicates
Recursive functions are a core part of computer science and mathematics.Roughly speaking, when the definition of such a function spells out aterminating computation from given arguments, we may refer toit as awell-founded function. For example, the common factorial andFibonacci functions are well-founded functions.
There are also other ways to define functions. An important caseregards the definition of a boolean function as an extreme solution(that is, a least or greatest solution) to some equation. Forcomputer scientists with interests in logic or programming languages,theseextreme predicates are important because they describe thejudgments that can be justified by a given set of inference rules(see, e.g., [@CamilleriMelham:InductiveRelations;@Winskel:FormalSemantics; @LeroyGrall:CoinductiveBigStep; @Pierce:SoftwareFoundations; @NipkowKlein:ConcreteSemantics]).
To benefit from machine-assisted reasoning, it is necessary not justto understand extreme predicates but also to have techniques forproving theorems about them. A foundation for this reasoning wasdeveloped by Paulin-Mohring [@PaulinMohring:InductiveCoq] and is thebasis of the constructive logic supported by Coq [@Coq:book] as wellas other proof assistants [@BoveDybjerNorell:BriefAgda;@SwamyEtAl:Fstar2011]. Essentially, the idea is to represent theknowledge that an extreme predicate holds by the proof term by whichthis knowledge was derived. For a predicate defined as the leastsolution, such proof terms are values of an inductive datatype (thatis, finite proof trees), and for the greatest solution, a coinductivedatatype (that is, possibly infinite proof trees). This means thatone can use induction and coinduction when reasoning about these prooftrees. These extreme predicates are known as,respectively,least predicates andgreatest predicates.Support for extreme predicates is alsoavailable in the proof assistants Isabelle [@Paulson:CADE1994] and HOL[@Harrison:InductiveDefs].
Dafny supports both well-founded functions and extreme predicates.This section describes the difference in generalterms, and then describes novel syntactic support in Dafny fordefining and proving lemmas with extreme predicates. Although Dafny’sverifier has at its core a first-order SMT solver, Dafny’s logicalencoding makes it possible to reason about fixpoints in an automatedway.
The encoding for greatest predicates in Dafny was described previously[@LeinoMoskal:Coinduction] and is here described inthe section about datatypes.
12.4.1. Function Definitions
To define a function $f \colon X \to Y$ in terms of itself, one canwrite a general equation like
$$f = \mathcal{F}(f)$$
where $\mathcal{F}$ is a non-recursive function of type$(X \to Y) \to X \to Y$.Because it takes a function as an argument,$\mathcal{F}$is referred to as afunctor (orfunctional, but not to beconfused by the category-theory notion of a functor).Throughout, assume that$\mathcal{F}(f)$by itself is well defined,for example that it does not divide by zero. Also assume that$f$occursonly in fully applied calls in$\mathcal{F}(f)$; eta expansion can be applied toensure this. If$f$is aboolean
function, that is, if$Y$isthe type of booleans, then $f$ is calledapredicate.
For example, the common Fibonacci function over thenatural numbers can be defined by the equation
$$\mathit{fib} = \lambda n \bullet\: \mathbf{if}\:n < 2 \:\mathbf{then}\: n \:\mathbf{else}\: \mathit{fib}(n-2) + \mathit{fib}(n-1)$$
With the understanding that the argument $n$ is universallyquantified, we can write this equation equivalently as
$$\mathit{fib}(n) = \mathbf{if}\:n < 2\:\mathbf{then}\:n\:\mathbf{else}\:\mathit{fib}(n-2)%2B\mathit{fib}(n-1)$$
The fact that the function being defined occurs on both sides of the equationcauses concern that we might not be defining the function properly, leading to alogical inconsistency. In general, therecould be many solutions to an equation likethe general equation or there could be none.Let’s consider two ways to make sure we’re defining the function uniquely.
12.4.1.1. Well-founded Functions
A standard way to ensure thatthe general equation has a unique solution in $f$ isto make sure the recursion is well-founded, which roughly means that therecursion terminates. This is done by introducing any well-foundedrelation $\ll$ on the domain of $f$ and making sure that the argument to each recursivecall goes down in this ordering. More precisely, if we formulatethe general equation as
$$f(x) = \mathcal{F}{'}(f)$$
then we want to check $E \ll x$ for each call $f(E)$ in $f(x) = \mathcal{F}’(f)$.When a functiondefinition satisfies thisdecrement condition, then the function is said to bewell-founded.
For example, to check the decrement condition for $\mathit{fib}$inthe fib equation, we can pick $\ll$to be the arithmetic less-than relation on natural numbers and check thefollowing, for any $n$:
$$ 2 \leq n \;\Longrightarrow\; n-2 \ll n \;\wedge\; n-1 \ll n $$
Note that we are entitled to use the antecedent$2 \leq n$ because that is thecondition under which the else branch inthe fib equation is evaluated.
A well-founded function is often thought of as “terminating” in the sensethat the recursivedepth in evaluating $f$on any given argument is finite. That is, there are no infinite descending chainsof recursive calls. However, the evaluation of $f$ on a given argumentmay fail to terminate, because itswidth may be infinite. For example, let $P$be some predicate defined on the ordinals and let $\mathit{P}_\downarrow$ be a predicate on theordinals defined by the following equation:
$\mathit{P}_\downarrow = P(o) \;\wedge\; \forall p \bullet\; p \ll o \;\Longrightarrow\; \mathit{P}_\downarrow(p)$
With $\ll$ as the usual ordering on ordinals, this equation satisfies the decrementcondition, but evaluating $\mathit{P}_\downarrow(\omega)$ would require evaluating$\mathit{P}_\downarrow(n)$ for every natural number $n$. However, what we are concernedabout here is to avoid mathematical inconsistencies, and that isindeed a consequence of the decrement condition.
12.4.1.2. Example with Well-founded Functions
So that we can later see how inductive proofs are done in Dafny, let’s prove thatfor any $n$, $\mathit{fib}(n)$ is even iff $n$ is a multiple of $3$.We split our task intotwo cases. If $n < 2$, then the property follows directly from the definitionof $\mathit{fib}$. Otherwise, note that exactly one of the three numbers $n-2$, $n-1$, and $n$is a multiple of 3. If $n$ is the multiple of 3, then by invoking theinduction hypothesis on $n-2$and $n-1$, we obtain that $\mathit{fib}(n-2) + \mathit{fib}(n-1)$ is the sum of two odd numbers,which is even. If $n-2$ or $n-1$ is a multiple of 3, then by invoking the inductionhypothesis on $n-2$ and $n-1$, we obtain that $\mathit{fib}(n-2) + \mathit{fib}(n-1)$ is the sum of aneven number and an odd number, which is odd. In this proof, we invoked the inductionhypothesis on $n-2$ and on $n-1$. This is allowed, because both are smaller than$n$, and hence the invocations go down in the well-founded ordering on natural numbers.
12.4.1.3. Extreme Solutions
We don’t need to exclude the possibility ofthe general equation having multiplesolutions—instead, we can just be clear about which one of them we want.Let’s explore this, after a smidgen of lattice theory.
For any complete lattice $(Y,\leq)$ and any set $X$, we can bypointwise extension definea complete lattice $(X \to Y, \dot{\Rightarrow})$, where for any $f,g \colon X \to Y$,
$$f \dot{\Rightarrow} g \;\;\equiv\;\; \forall x \bullet\; f(x) \leq g(x)$$
In particular, if $Y$ is the set of booleans ordered by implication (false
$\leq$true
),then the set of predicates over any domain $X$ forms a complete lattice.Tarski’s Theorem [@Tarski:theorem] tells us that any monotonic function over acomplete lattice has a least and a greatest fixpoint. In particular, this means that$\mathcal{F}$ has a least fixpoint and a greatest fixpoint, provided $\mathcal{F}$ is monotonic.
Speaking about theset of solutions in $f$ tothe general equation is the same as speakingabout theset of fixpoints of functor $\mathcal{F}$. In particular, the least and greatestsolutions tothe general equation are the same as the least and greatest fixpoints of $\mathcal{F}$.In casual speak, it happens that we say “fixpoint ofthe general equation”, or moregrotesquely, “fixpoint of $f$” when we really mean “fixpoint of $\mathcal{F}$”.
To conclude our little excursion into lattice theory, we have that, under theproviso of $\mathcal{F}$ being monotonic, the set of solutions in $f$ tothe general equation is nonempty,and among these solutions, there is in the $\dot{\Rightarrow}$ ordering a least solution (that is,a function that returnsfalse
more often than any other) and a greatest solution (thatis, a function that returnstrue
more often than any other).
When discussing extreme solutions, let’s now restrict our attention to boolean functions(that is, with $Y$ being the type of booleans). Functor $\mathcal{F}$ is monotonicif the calls to $f$ in $\mathcal{F}’(f)$ are inpositive positions (that is, under an even numberof negations). Indeed, from now on, we will restrict our attention to such monotonicfunctors $\mathcal{F}$.
Here is a running example. Consider the following equation,where $x$ ranges over the integers:
$$g(x) = (x = 0 \:\vee\: g(x-2))$$
This equation has four solutions in $g$. With $w$ ranging over the integers, they are:
$$ \begin{array}{r@{}l} g(x) \;\;\equiv\;\;{}& x \in \{w \;|\; 0 \leq w \;\wedge\; w\textrm{ even}\} \\ g(x) \;\;\equiv\;\;{}& x \in \{w \;|\; w\textrm{ even}\} \\ g(x) \;\;\equiv\;\;{}& x \in \{w \;|\; (0 \leq w \;\wedge\; w\textrm{ even}) \:\vee\: w\textrm{ odd}\} \\ g(x) \;\;\equiv\;\;{}& x \in \{w \;|\; \mathit{true}\} \end{array}$$
The first of these is the least solution and the last is the greatest solution.
In the literature, the definition of an extreme predicate is often given as a set ofinference rules. To designate the least solution, a single line separating theantecedent (on top) from conclusion (on bottom) is used:
$$\dfrac{}{g(0)} \qquad\qquad \dfrac{g(x-2)}{g(x)}$$
Through repeated applications of such rules, one can show that the predicate holds fora particular value. For example, thederivation, orproof tree,to the left inthe proof tree figure shows that $g(6)$ holds.(In this simple example, the derivation is a rather degenerate proof “tree”.)The use of these inference rules gives rise to a least solution, because proof trees areaccepted only if they arefinite.
When inference rules are to designate the greatest solution, a thickline is used:
$$\genfrac{}{}{1.2pt}0{}{g(0)} \qquad\qquad \genfrac{}{}{1.2pt}0{g(x-2)}{g(x)}$$
In this case, proof trees are allowed to be infinite.For example, the left-hand example below shows a finite proof tree that usesthe inductive rules to establish $g(6)$.On the right is a partial depiction of an infinite proof tree that usesthe coinductive rules to establish $g(1)$.
$$\dfrac{ \dfrac{ \dfrac{ \dfrac{}{g(0)} }{g(2)} }{g(4)} }{g(6)}\qquad\qquad\genfrac{}{}{1.2pt}0{ \genfrac{}{}{1.2pt}0{ \genfrac{}{}{1.2pt}0{ \genfrac{}{}{1.2pt}0{ {} {\vdots } }{g(-5)} }{g(-3)} }{g(-1)} }{g(1)}$$
Note that derivations may not be unique. For example, in the case of the greatestsolution for $g$, there are two proof trees that establish $g(0)$: one is the finiteproof tree that uses the left-hand rule ofthese coinductive rules once, the other is the infiniteproof tree that keeps on using the right-hand rule ofthese coinductive rules.
12.4.2. Working with Extreme Predicates
In general, one cannot evaluate whether or not an extreme predicate holds for someinput, because doing so may take an infinite number of steps. For example, followingthe recursive calls in the definitionthe EvenNat equation to try to evaluate $g(7)$ would neverterminate. However, there are useful ways to establish that an extreme predicate holdsand there are ways to make use of one once it has been established.
For any $\mathcal{F}$ as inthe general equation, define two infinite series of well-foundedfunctions, ${ {}^{\flat}\kern-1mm f}_k$ and ${ {}^{\sharp}\kern-1mm f}_k$where $k$ ranges over the natural numbers:
$$ { {}^{\flat}\kern-1mm f}_k(x) = \left\{ \begin{array}{ll} \mathit{false} & \textrm{if } k = 0 \\ \mathcal{F}({ {}^{\flat}\kern-1mm f}_{k-1})(x) & \textrm{if } k > 0 \end{array} \right\} $$
$$ { {}^{\sharp}\kern-1mm f}_k(x) = \left\{ \begin{array}{ll} \mathit{true} & \textrm{if } k = 0 \\ \mathcal{F}({ {}^{\sharp}\kern-1mm f}_{k-1})(x) & \textrm{if } k > 0 \end{array} \right\} $$
These functions are called theiterates of $f$, and we will also refer to themas theprefix predicates of $f$ (or theprefix predicate of $f$, if we thinkof $k$ as being a parameter).Alternatively, we can define ${ {}^{\flat}\kern-1mm f}_k$ and ${ {}^{\sharp}\kern-1mm f}_k$ without mentioning $x$:let $\bot$ denote the function that always returnsfalse
, let $\top$denote the function that always returnstrue
, and let a superscript on $\mathcal{F}$ denoteexponentiation (for example, $\mathcal{F}^0(f) = f$ and $\mathcal{F}^2(f) = \mathcal{F}(\mathcal{F}(f))$).Then,the least approx definition andthe greatest approx definition can be stated equivalently as${ {}^{\flat}\kern-1mm f}_k = \mathcal{F}^k(\bot)$ and ${ {}^{\sharp}\kern-1mm f}_k = \mathcal{F}^k(\top)$.
For any solution $f$ tothe general equation, we have, for any $k$ and $\ell$such that $k \leq \ell$:
$$ {\;{}^{\flat}\kern-1mm f}_k \quad\;\dot{\Rightarrow}\;\quad {\;{}^{\flat}\kern-1mm f}_\ell \quad\;\dot{\Rightarrow}\;\quad f \quad\;\dot{\Rightarrow}\;\quad {\;{}^{\sharp}\kern-1mm f}_\ell \quad\;\dot{\Rightarrow}\;\quad { {}^{\sharp}\kern-1mm f}_k $$
In other words, every ${\;{}^{\flat}\kern-1mm f}_{k}$ is apre-fixpoint of $f$ and every ${\;{}^{\sharp}\kern-1mm f}_{k}$ is apost-fixpointof $f$. Next, define two functions, $f^{\downarrow}$ and $f^{\uparrow}$, interms of the prefix predicates:
$$ f^{\downarrow}(x) \;=\; \exists k \bullet\; { {}^{\flat}\kern-1mm f}_k(x) $$
$$ f^{\uparrow}(x) \;=\; \forall k \bullet\; { {}^{\sharp}\kern-1mm f}_k(x) $$
Bythe prefix postfix result, we also have that $f^{\downarrow}$ is a pre-fixpoint of $\mathcal{F}$ and $f^{\uparrow}$is a post-fixpoint of $\mathcal{F}$. The marvelous thing is that, if $\mathcal{F}$ iscontinuous, then$f^{\downarrow}$ and $f^{\uparrow}$ are the least and greatest fixpoints of $\mathcal{F}$.These equations let us do proofs by induction when dealing with extreme predicates.The extreme predicate section explains how to check for continuity.
Let’s consider two examples, both involving function $g$ inthe EvenNat equation. As it turns out, $g$’s defining functor is continuous,and therefore I will write $g^{\downarrow}$ and $g^{\uparrow}$ to denote theleast and greatest solutions for $g$ inthe EvenNat equation.
12.4.2.1. Example with Least Solution
The main technique for establishing that $g^{\downarrow}(x)$ holds for some$x$, that is, proving something of the form $Q \Longrightarrow g^{\downarrow}(x)$, is toconstruct a proof tree like the one for $g(6)$ inthe proof tree figure.For a proof in this direction, since we’re justapplying the defining equation, the fact thatwe’re using a least solution for $g$ never plays a role (as long as welimit ourselves to finite derivations).
The technique for going in the other direction, proving somethingfrom an established$g^{\downarrow}$ property, that is, showing something of the form $g^{\downarrow}(x) \Longrightarrow R$, typicallyuses induction on the structure of the proof tree. When the antecedent of our proofobligation includes a predicate term $g^{\downarrow}(x)$, it is sound toimagine that we have been given a proof tree for $g^{\downarrow}(x)$. Such a proof treewould be a data structure—to be more precise, a term in aninductive datatype.Least solutions like $g^{\downarrow}$ have been given thenameleast predicate.
Let’s prove $g^{\downarrow}(x) \Longrightarrow 0 \leq x \wedge x \text{ even}$.We split our task into two cases, corresponding to which of the twoproof rules inthe inductive rules was thelast one applied to establish $g^{\downarrow}(x)$. If it was the left-hand rule, then $x=0$,which makes it easy to establish the conclusion of our proof goal. If it was theright-hand rule, then we unfold the proof tree one level and obtain $g^{\downarrow}(x-2)$.Since the proof tree for $g^{\downarrow}(x-2)$ is smaller than where we started, we invoketheinduction hypothesis and obtain $0 \leq (x-2) \wedge (x-2) \textrm{ even}$, from whichit is easy to establish the conclusion of our proof goal.
Here’s how we do the proof formally usingthe least exists definition. We massage thegeneral form of our proof goal:
$$\begin{array}{lll} & f^{\uparrow}(x) \;\Longrightarrow\; R & \\ = & & \textrm{ (the least exists definition) } \\ & (\exists k \bullet\; { {}^{\flat}\kern-1mm f}_k(x)) \;\Longrightarrow\; R & \\ = & & \text{distribute} \;\Longrightarrow\; \text{over} \;\exists\; \text{to the left} \\ & \forall k \bullet\; ({ {}^{\flat}\kern-1mm f}_k(x) \;\Longrightarrow\; R) & \end{array}$$
The last line can be proved by induction over $k$. So, in our case, we prove${ {}^{\flat}\kern-1mm g}_k(x) \Longrightarrow 0 \leq x \wedge x \textrm{ even}$ for every $k$.If $k = 0$, then ${ {}^{\flat}\kern-1mm g}_k(x)$ isfalse
, so our goal holds trivially.If $k > 0$, then ${ {}^{\flat}\kern-1mm g}_k(x) = (x = 0 :\vee: { {}^{\flat}\kern-1mm g}_{k-1}(x-2))$. Our goal holds easilyfor the first disjunct ($x=0$). For the other disjunct,we apply the induction hypothesis (on the smaller $k-1$ and with $x-2$) andobtain $0 \leq (x-2)\;\wedge\; (x-2) \textrm{ even}$, from which our proof goalfollows.
12.4.2.2. Example with Greatest Solution
We can think of a predicate $g^{\uparrow}(x)$ as being representedby a proof tree—in this case a term in acoinductive datatype,since the proof may be infinite.Greatest solutions like $g^{\uparrow}$ havebeen given the namegreatest predicate.The main technique for proving something from a given proof tree, thatis, to prove something of the form $g^{\uparrow}(x) \;\Longrightarrow\; R$, is todestruct the proof. Since this is just unfolding the definingequation, the fact that we’re using a greatest solution for $g$ neverplays a role (as long as we limit ourselves to a finite number ofunfoldings).
To go in the other direction, to establish a predicate defined as a greatest solution,like $Q \Longrightarrow g^{\uparrow}(x)$, we may need an infinite number of steps. For this purpose,we can use induction’s dual,coinduction. Were it not for one little detail, coinductionis as simple as continuations in programming: the next part of the proof obligationis delegated to thecoinduction hypothesis. The little detail is making sure thatit is the “next” part we’re passing on for the continuation, not the same part. Thisdetail is calledproductivity and corresponds to the requirement ininduction of making sure we’re going down a well-founded relation whenapplying the induction hypothesis. There aremany sources with more information, see for example the classic account byJacobs and Rutten [@JacobsRutten:IntroductionCoalgebra]or a new attempt by Kozen and Silvathat aims to emphasize the simplicity, not the mystery, ofcoinduction [@KozenSilva:Coinduction].
Let’s prove $\mathit{true} \Longrightarrow g^{\uparrow}(x)$. The intuitive coinductive proof goes like this:According to the right-hand rule ofthese coinductive rules, $g^{\uparrow}(x)$ follows if weestablish $g^{\uparrow}(x-2)$, and that’s easy to do by invoking the coinduction hypothesis.The “little detail”, productivity, is satisfied in this proof because we applieda rule inthese coinductive rules before invoking the coinduction hypothesis.
For anyone who may have felt that the intuitive proof felt too easy, here is a formalproof usingthe greatest forall definition, which relies only on induction. We massage thegeneral form of our proof goal:
$$\begin{array}{lll} & Q \;\Longrightarrow\; f^{\uparrow}(x) & \\ = & & \textrm{ (the greatest forall definition) } \\ & Q \;\Longrightarrow\; \forall k \bullet\; { {}^{\sharp}\kern-1mm f}_k(x) & \\ = & & \text{distribute} \;\Longrightarrow\; \text{over} \;\forall\; \text{to the right } \\ & \forall k \bullet\; Q \;\Longrightarrow\; { {}^{\sharp}\kern-1mm f}_k(x) &\end{array}$$
The last line can be proved by induction over $k$. So, in our case, we prove$\mathit{true} \;\Longrightarrow\; { {}^{\sharp}\kern-1mm g}_k(x)$ for every $k$.If $k=0$, then ${ {}^{\sharp}\kern-1mm g}_k(x)$ is $\mathit{true}$, so our goal holds trivially.If $k > 0$, then ${ {}^{\sharp}\kern-1mm g}_k(x) = (x = 0 :\vee: { {}^{\sharp}\kern-1mm g}_{k-1}(x-2))$. We establish the seconddisjunct by applying the induction hypothesis (on the smaller $k-1$ and with $x-2$).
12.4.3. Other Techniques
Although this section has considered only well-founded functions and extremepredicates, it is worth mentioning that there are additional ways of making sure thatthe set of solutions tothe general equation is nonempty. For example, if all calls to $f$ in$\mathcal{F}’(f)$ aretail-recursive calls, then (under the assumption that $Y$ is nonempty) the set ofsolutions is nonempty. To see this, consider an attempted evaluation of $f(x)$ that failsto determine a definite result value because of an infinite chain of calls that applies $f$to each value of some subset $X’$ of $X$. Then, apparently, the value of $f$ for any oneof the values in $X’$ is not determined by the equation, but picking any particular resultvalue for these makes for a consistent definition.This was pointed out by Manolios and Moore [@ManoliosMoore:PartialFunctions].Functions can be underspecified in this way in the proof assistants ACL2 [@ACL2:book]and HOL [@Krauss:PhD].
12.5. Functions in Dafny
This section explains with examples the support inDafny for well-founded functions, extreme predicates,and proofs regarding these, building on the concepts explained in the previous section.
12.5.1. Well-founded Functions in Dafny
Declarations of well-founded functions are unsurprising. For example, the Fibonaccifunction is declared as follows:
functionfib(n:nat):nat{ifn<2thennelsefib(n-2)+fib(n-1)}
Dafny verifies that the body (given as an expression in curly braces) is well defined.This includes decrement checks for recursive (and mutually recursive) calls. Dafnypredefines a well-founded relation on each type and extends it to lexicographic tuplesof any (fixed) length. For example, the well-founded relation $x \ll y$ for integersis $x < y \;\wedge\; 0 \leq y$, the one for reals is $x \leq y - 1.0 \;\wedge\; 0.0 \leq y$(this is the same ordering as for integers, if you read the integerrelation as $x \leq y - 1 \;\wedge\; 0 \leq y$), the one for inductivedatatypes is structural inclusion,and the one for coinductive datatypes isfalse
.
Using adecreases
clause, the programmer can specify the term in this predefinedorder. When a function definition omits adecreases
clause, Dafny makes a simpleguess. This guess (which can be inspected by hovering over the function name in theDafny IDE) is very often correct, so users are rarely bothered to provide explicitdecreases
clauses.
If a function returnsbool
, one can drop the result type:bool
and change thekeywordfunction
topredicate
.
12.5.2. Proofs in Dafny
Dafny haslemma
declarations, as described inSection 6.3.3:lemmas can have pre- and postcondition specifications and their body is a code block.Here is the lemma we stated and proved inthe fib example in the previous section:
lemmaFibProperty(n:nat)ensuresfib(n)%2==0<==>n%3==0{ifn<2{}else{FibProperty(n-2);FibProperty(n-1);}}functionfib(n:nat):nat{ifn<2thennelsefib(n-2)+fib(n-1)}
The postcondition of this lemma (keywordensures
) gives the proofgoal. As in any program-correctness logic (e.g.,[@Hoare:AxiomaticBasis]), the postcondition mustbe established on every control path through the lemma’s body. ForFibProperty
, I give the proof byanif
statement, hence introducing a case split. The then branch is empty, becauseDafny can prove the postcondition automatically in this case. The else branchperforms two recursive calls to the lemma. These are the invocations of the inductionhypothesis and they follow the usual program-correctness rules,namely: the precondition must hold at the call site, the call must terminate, and thenthe caller gets to assume the postcondition upon return. The “proof glue” neededto complete the proof is done automatically by Dafny.
Dafny features an aggregate statement using which it is possible to make (possiblyinfinitely) many calls at once. For example, the induction hypothesis can be calledat once on all valuesn'
smaller thann
:
foralln'|0<=n'<n{FibProperty(n');}
For our purposes, this corresponds tostrong induction. Moregenerally, theforall
statement has the form
forallk|P(k)ensuresQ(k){Statements;}
Logically, this statement corresponds touniversal introduction: the body proves thatQ(k)
holds for an arbitraryk
such thatP(k)
, and the conclusion of theforall
statementis then $\forall k \bullet\; P(k) \;\Longrightarrow\; Q(k)$. When the body of theforall
statement isa single call (orcalc
statement), theensures
clause is inferred and can be omitted,like in ourFibProperty
example.
LemmaFibProperty
is simple enough that its whole body can be replaced by the oneforall
statement above. In fact, Dafny goes one step further: it automaticallyinserts such aforall
statement at the beginning of every lemma [@Leino:induction].Thus,FibProperty
can be declared and proved simply by:
lemmaFibProperty(n:nat)ensuresfib(n)%2==0<==>n%3==0{}functionfib(n:nat):nat{ifn<2thennelsefib(n-2)+fib(n-1)}
Going in the other direction from universal introduction is existential elimination,also known as Skolemization. Dafny has a statement for this, too:for any variablex
and boolean expressionQ
, theassign such that statementx:|Q;
says to assign tox
a value such thatQ
will hold. A proof obligation when using this statement is to show that thereexists anx
such thatQ
holds. For example, if the fact$\exists k \bullet\; 100 \leq \mathit{fib}(k) < 200$ is known, then the statementk:|100<=fib(k)<200;
will assign tok
some value (chosen arbitrarily)for whichfib(k)
falls in the given range.
12.5.3. Extreme Predicates in Dafny
The previous subsection explained that apredicate
declaration introduces awell-founded predicate. The declarations for introducing extreme predicates areleastpredicate
andgreatestpredicate
. Here is the definition of the least andgreatest solutions of $g$ from above; let’s call themg
andG
:
leastpredicateg[nat](x:int){x==0||g(x-2)}greatestpredicateG[nat](x:int){x==0||G(x-2)}
When Dafny receives either of these definitions, it automatically declares the correspondingprefix predicates. Instead of the names ${ {}^{\flat}\kern-1mm g}_k$ and ${ {}^{\sharp}\kern-1mm g}_k$ that I used above, Dafnynames the prefix predicatesg#[k]
andG#[k]
, respectively, that is, the name ofthe extreme predicate appended with#
, and the subscript is given as an argument insquare brackets. The definition of the prefix predicate derives from the body ofthe extreme predicate and follows the form inthe least approx definition andthe greatest approx definition.Using a faux-syntax for illustrative purposes, here are the prefixpredicates that Dafny defines automatically from the extremepredicatesg
andG
:
predicateg#[_k:nat](x:int){_k!=0&&(x==0||g#[_k-1](x-2))}predicateG#[_k:nat](x:int){_k!=0==>(x==0||G#[_k-1](x-2))}
The Dafny verifier is aware of the connection between extreme predicates and theirprefix predicates,the least exists definition andthe greatest forall definition.
Remember that to be well defined, the defining functor of an extreme predicatemust be monotonic, and forthe least exists definition andthe greatest forall definition to hold,the functor must be continuous. Dafny enforces the former of these by checking thatrecursive calls of extreme predicates are in positive positions. The continuityrequirement comes down to checking that they are also incontinuous positions:that recursive calls to least predicates arenot inside unbounded universal quantifiers and that recursive calls to greatest predicatesare not inside unbounded existential quantifiers [@Milner:CCS; @LeinoMoskal:Coinduction].
12.5.4. Proofs about Extreme Predicates
From what has been presented so far, we can do the formal proofs forthe example about the least solution andthe example about the greatest solution. Here is theformer:
leastpredicateg[nat](x:int){x==0||g(x-2)}greatestpredicateG[nat](x:int){x==0||G(x-2)}lemmaEvenNat(x:int)requiresg(x)ensures0<=x&&x%2==0{vark:nat:|g#[k](x);EvenNatAux(k,x);}lemmaEvenNatAux(k:nat,x:int)requiresg#[k](x)ensures0<=x&&x%2==0{ifx==0{}else{EvenNatAux(k-1,x-2);}}
LemmaEvenNat
states the property we wish to prove. From itsprecondition (keywordrequires
) andthe least exists definition, we know there is somek
that will make the condition in theassign-such-that statement true. Such a value is then assigned tok
and passed tothe auxiliary lemma, which promises to establish the proof goal. Given the conditiong#[k](x)
, the definition ofg#
lets us concludek!=0
as well as the disjunctionx==0||g#[k-1](x-2)
. The then branch considers the case of the first disjunct,from which the proof goal follows automatically. The else branch can then assumeg#[k-1](x-2)
and calls the induction hypothesis with those parameters. The proofglue that shows the proof goal forx
to follow from the proof goal withx-2
isdone automatically.
Because Dafny automatically inserts the statement
forallk',x'|0<=k'<k&&g#[k'](x'){EvenNatAux(k',x');}
at the beginning of the body ofEvenNatAux
, the body can be left empty and Dafnycompletes the proof automatically.
Here is the Dafny program that gives the proof fromthe example of the greatest solution:
leastpredicateg[nat](x:int){x==0||g(x-2)}greatestpredicateG[nat](x:int){x==0||G(x-2)}lemmaAlways(x:int)ensuresG(x){forallk:nat{AlwaysAux(k,x);}}lemmaAlwaysAux(k:nat,x:int)ensuresG#[k](x){}
While each of these proofs involves only basic proof rules, the setup feels a bit clumsy,even with the empty body of the auxiliary lemmas. Moreover,the proofs do not reflect the intuitive proofs described inthe example of the least solution andthe example of the greatest solution.These shortcomings are addressed in the next subsection.
12.5.5. Nicer Proofs of Extreme Predicates
The proofs we just saw follow standard forms:use Skolemization to convert the least predicate into a prefix predicate for somek
and then do the proof inductively overk
; respectively,by induction overk
, prove the prefix predicate for everyk
, then useuniversal introduction to convert to the greatest predicate.With the declarationsleastlemma
andgreatestlemma
, Dafny offers toset up the proofsin these standard forms. What is gained is not just fewer characters in the programtext, but also a possible intuitive reading of the proofs. (Okay, to be fair, thereading is intuitive for simpler proofs; complicated proofs may or may not be intuitive.)
Somewhat analogous to the creation of prefix predicates from extreme predicates, Dafnyautomatically creates aprefix lemmaL#
from each “extreme lemma”L
. The pre-and postconditions of a prefix lemma are copied from those of the extreme lemma,except for the following replacements:
- for a least lemma, Dafny looks in the precondition to find calls (in positive, continuouspositions) to least predicates
P(x)
and replaces these withP#[_k](x)
; - for a greatest lemma,Dafny looks in the postcondition to find calls (in positive, continuous positions)to greatest predicates
P
(including equality among coinductive datatypes, which is a built-ingreatest predicate) and replaces these withP#[_k](x)
.In each case, these predicatesP
are the lemma’sfocal predicates.
The body of the extreme lemma is moved to the prefix lemma, but withreplacing each recursivecallL(x)
withL#[_k-1](x)
and replacing each occurrence of a callto a focal predicateP(x)
withP#[_k-1](x)
. The bodies of the extreme lemmas are then replaced as shownin the previous subsection. By construction, this new body correctly leads to theextreme lemma’s postcondition.
Let us see what effect these rewrites have on how one can write proofs. Here are the proofsof our running example:
leastpredicateg(x:int){x==0||g(x-2)}greatestpredicateG(x:int){x==0||G(x-2)}leastlemmaEvenNat(x:int)requiresg(x)ensures0<=x&&x%2==0{ifx==0{}else{EvenNat(x-2);}}greatestlemmaAlways(x:int)ensuresG(x){Always(x-2);}
Both of these proofs follow the intuitive proofs given inthe example of the least solution andthe example of the greatest solution. Note that in thesesimple examples, the user is never bothered with either prefix predicates norprefix lemmas—the proofs just look like “what you’d expect”.
Since Dafny automatically inserts calls to the induction hypothesis at the beginning ofeach lemma, the bodies of the given extreme lemmasEvenNat
andAlways
can be empty and Dafny still completes the proofs.Folks, it doesn’t get any simpler than that!
12.6. Variable Initialization and Definite Assignment
The Dafny language semantics ensures that any use (read) of a variable (or constant,parameter, object field, or array element) gives a value of the variable’s type.It is easy to see that this property holds for any variable that is declared withan initializing assignment. However, for many useful programs, it would be too strictto require an initializing assignment at the time a variable is declared.Instead, Dafny ensures the property throughauto-initialization and rules fordefinite assignment.
As explained insection 5.3.1, each type in Dafny is one of the following:
- auto-init type: the type is nonempty and the compiler has some way to emit code that constructs a value
- nonempty type: the type is nonempty, but the compiler does not know how perform automatic initialization
- possibly empty type: the type is not known for sure to have a value
For a variable of an auto-init type, the compiler can initialize the variable automatically.This means that the variable can be used immediately after declaration, even if the program does notexplicitly provide an initializing assignment.
In a ghost context, one can an imagine a “ghost” that initializes variables. Unlike the compiler, sucha “ghost” does not need to emit code that constructs an initializing value; it suffices for the ghost toknow that a value exists. Therefore, in a ghost context, a variable of a nonempty type can be used immediatelyafter declaration.
Before a variable of a possibly empty type can be used, the program must initialize it.The variable need not be given a value when it is declared,but it must have a value by the time it is first used. Dafny uses the precision of the verifier toreason about the control flow between assignments and uses of variables, and it reports an errorif it cannot assure itself that the variable has been given a value.
The elements of an array must be assured to have values already in the statement that allocates the array.This is achieved in any of the following four ways:
- If the array is allocated to be empty (that is, one of its dimensions is requested to be 0), thenthe array allocation trivially satisfies the requirement.
- If the element type of the array is an auto-init type, then nothing further is required by the program.
- If the array allocation occurs in a ghost context and the element type is a nonempty type, then nothingfurther is required by the program.
- Otherwise, the array allocation must provide an initialization display or an initialization function.Seesection 5.10 for information about array initialization.
The fields of a class must have values by the end of the first phase of each constructor (that is, atthe explicit or implicitnew;
statement in the constructor). If a class has a compiled field that isnot of an auto-init type, or if it has a ghost field of a possibly empty type, then the class is requiredto declare a(t least one) constructor.
The yield-parameters of aniterator
turn into fields of the corresponding iterator class, but thereis no syntactic place to give these initial values. Therefore, every compiled yield-parameter must be ofauto-init types and every ghost yield-parameter must be of an auto-init or nonempty type.
For local variables and out-parameters, Dafny supports two definite-assignment modes:
- A strict mode (the default, which is
--relax-definite-assignment=false
; or/definiteAssignment:4
in the legacy CLI), in which local variables and out-parameters are always subjectto definite-assignment rules, even for auto-initializable types. - A relaxed mode (enabled by the option
--relax-definite-assignment
; or/definiteAssignment:1
in the legacy CLI), in which the auto-initialization (or, for ghost variables and parametes, nonemptiness)is sufficient to satisfy the definite assignment rules.
A program using the strict mode can still indicate that it is okay with an arbitrary value of a variablex
by using an assignment statementx:=*;
, provided the type ofx
is an auto-init type (or, ifx
isghost, a nonempty type). (Ifx
is of a possibly nonempty type, thenx:=*;
is still allowed, but itsetsx
to a value of its type only if the type actually contains a value. Therefore, whenx
is ofa possibly empty type,x:=*;
does not count as a definite assignment tox
.)
Note that auto-initialization is nondeterministic. Dafny only guarantees that each value it assigns toa variable of an auto-init type issome value of the type. Indeed, a variable may be auto-initializedto different values in different runs of the program or even at different times during the same run ofthe program. In other words, Dafny does not guarantee the “zero-equivalent value” initialization thatsome languages do. Along these lines, also note that thewitness
value provided in some subset-typedeclarations is not necessarily the value chosen by auto-initialization, though it does esstablish thatthe type is an auto-init type.
In some programs (for example, in some test programs), it is desirable to avoid nondeterminism.For that purpose, Dafny provides an--enforce-determinism
option. It forbids use of any programstatement that may have nondeterministic behavior and it disables auto-initialization.This mode enforces definite assignments everywhere, going beyond what the strict mode does by enforcingdefinite assignment also for fields and array elements. It also forbids the use ofiterator
declarationsandconstructor
-lessclass
declarations. It is up to a user’s build process to ensure that--enforce-determinism
is used consistently throughout the program. (In the legacy CLI, thismode is enabled by/definiteAssignment:3
.)
This document, which is intended for developers of theDafny tool itself, has more detail on auto-initialization and how it is implemented.
Finally, note that--relax-definite-assignment=false
is the default in the command-based CLI,but, for backwards compatibility, the relaxed rules (`/definiteAssignment:1) are still the defaultin the legacy CLI.
12.7. Well-founded Orders
The well-founded order relations for a variety of built-in types in Dafnyare given in the following table:
type ofX andx | x strictly belowX |
---|---|
bool | X&&!x |
int | x<X&&0<=X |
real | x<=X-1.0&&0.0<=X |
set<T> | x is a proper subset ofX |
multiset<T> | x is a proper multiset-subset ofX |
seq<T> | x is a consecutive proper sub-sequence ofX |
map<K,V> | x.Keys is a proper subset ofX.Keys |
inductive datatypes | x is structurally included inX |
reference types | x==null&&X!=null |
coinductive datatypes | false |
type parameter | false |
arrow types | false |
Also, there are a few relations between the rows in the table above. For example, a datatype valuex
sitting inside a set that sits inside another datatype valueX
is considered to be strictly belowX
. Here’s an illustration of that order, in a program that verifies:
datatypeD=D(s:set<D>)methodTestD(dd:D){vard:=dd;whiled!=D({})decreasesd{varx:|xind.s;d:=x;}}
12.8. Quantifier instantiation rules
During verification, when Dafny knows that a universal quantifier is true, such as when verifying the body of a function that has the requires clauseforallx::f(x)==1
, it may instantiate the quantifier. Instantiation means Dafny will pick a value for all the variables of the quantifier, leading to a new expression, which it hopes to use to prove an assertion. In the above example, instantiating using3
forx
will lead to the expressionf(3)==1
.
For each universal quantifier, Dafny generates rules to determine which instantiations are worthwhile doing. We call these rules triggers, a term that originates from SMT solvers. If Dafny can not generate triggers for a specific quantifier, it falls back to a set of generic rules. However, this is likely to be problematic, since the generic rules can cause many useless instantiations, leading to verification timing out or failing to proof a valid assertion. When the generic rules are used, Dafny emits a warning telling the user no triggers were found for the quantifier, indicating the Dafny program should be changed so Dafny can find triggers for this quantifier.
Here follows the approach Dafny uses to generate triggers based on a quantifier. Dafny finds terms in the quantifier body where a quantified variable is used in an operation, such as in a function applicationP(x)
, array accessa[x]
, member accessesx.someField
, or set membership testsxinS
. To find a trigger, Dafny must find a set of such terms so that each quantified variable is used. You can investigate which triggers Dafny finds by hovering over quantifiers in the IDE and looking for ‘Selected triggers’, or by using the options--show-tooltips
when using the LCI.
There are particular expressions which, for technical reasons, Dafny can not use as part of a trigger. Among others, these expression include:match,let,arithmetic operations and logical connectives. For example, in the quantifierforallx::xinS⇐⇒f(x)>f(x+1)
, Dafny will usexinS
andf(x)
as trigger terms, but will not usex+1
or any terms that contain it. You can investigate which triggers Dafny can not use by hovering over quantifiers in the IDE and looking for ‘Rejected triggers’, or by using the options--show-tooltips
when using the LCI.
Besides not finding triggers, another problematic situation is when Dafny was able to generate triggers, but believes the triggers it found may still cause useless instantiations because they create matching loops. Dafny emits a warning when this happens, indicating the Dafny program should be changed so Dafny can find triggers for this quantifier that do not cause matching loops.
To understand matching loops, one needs to understand how triggers are used. During a single verification run, such as verifying a method or function, Dafny maintains a set of expressions which it believes to be true, which we call the ground terms. For example, in the body of a method, Dafny knows the requires clauses of that method hold, so the expressions in those will be ground terms. When Dafny steps through the statements of the body, the set of ground terms grows. For example, when an assignmentvarx:=3
is evaluated, a ground termx==3
will be added. Given a universal quantifier that’s a ground term, Dafny will try to pattern match its triggers on sub-expressions of other ground terms. If the pattern matches, that sub-expression is used to instantiate the quantifier.
Dafny makes sure not to perform the exact same instantiation twice. However, if an instantiation leads to a new term that also matches the trigger, but is different from the term used for the instantiation, the quantifier may be instantiated too often, an event we call a matching loop. For example, given the ground termsf(3)
andforallx{f(x)}::f(x)+f(f(x))
, where{f(x)}
indicates the trigger for the quantifier, Dafny may instantiate the quantifier using3
forx
. This creates a new ground termf(3)+f(f(3))
, of which the right hand side again matches the trigger, allowing Dafny to instantiate the quantifier again usingf(3)
forx
, and again and again, leading to an unbounded amount of instantiations.
Even existential quantifiers need triggers. This is because when Dafny determines an existential quantifier is false, for example in the body of a method that hasrequires!existsx::f(x)==2
, Dafny will use a logical rewrite rule to change this existential into a universal quantifier, so it becomesrequiresforallx::f(x)!=2
. Before verification, Dafny can not determine whether quantifiers will be determined to be true or false, so it must assume any quantifier may turn into a universal quantifier, and thus they all need triggers. Besides quantifiers, comprehensions such as set and map comprehensions also need triggers, since these are modeled using universal quantifiers.
Dafny may report ‘Quantifier was split into X parts’. This occurs when Dafny determines it can only generate good triggers for a quantifier by splitting it into multiple smaller quantifiers, whose aggregation is logically equivalent to the original one. To maintain logical equivalence, Dafny may have to generate more triggers than if the split had been done manually in the Dafny source file. An example is the expressionforallx::P(x)&&(Q(x)=⇒P(x+1))
, which Dafny will split into
forallx{P(x)}{Q(x)}::P(x)&&forallx{(Q(x)}::Q(x)=⇒P(x+1)
Note the trigger{Q(x)}
in the first quantifier, which was added to maintain equivalence with the original quantifier. If the quantifier had been split in source, only the trigger{P(x)}
would have been added forforallx::P(x)
.
13. Dafny User’s Guide
Most of this document describes the Dafny programming language.This section describes thedafny
tool, a combined verifier and compilerthat implements the Dafny language.
The development of the Dafny language and tool is a GitHub project athttps://github.com/dafny-lang/dafny.The project is open source, with collaborators from various organizations; additional contributors are welcome.The software itself is licensed under theMIT license.
13.1. Introduction
Thedafny
tool implements the following primary capabilities, implemented as variouscommands within thedafny
tool:
- checking that the input files represent a valid Dafny program (i.e., syntax, grammar and name and type resolution);
- verifying that the program meets its specifications, by translating the program to verification conditionsand checking those with Boogie and an SMT solver, typically Z3;
- compiling the program to a target language, such as C#, Java, Javascript, Go (and others in development);
- running the executable produced by the compiler.
In addition there are a variety of other capabilities, such as formatting files, also implemented as commands;more such commands are expected in the future.
13.2. Installing Dafny
13.2.1. Command-line tools
The instructions for installingdafny
and the required dependencies and environmentare described on the Dafny wiki:https://github.com/dafny-lang/dafny/wiki/INSTALL.They are not repeated here to avoid replicating information thateasily becomes inconsistent and out of date.The dafny tool can also be installed usingdotnettoolinstall--globaldafny
(presuming thatdotnet
is already installed on your system).
Most users will find it most convenient to install the pre-built Dafny binaries available on the project release site or using thedotnet
CLI.As is typical for Open Source projects, dafny can also be built directly from the source files maintained in the github project.
Current and past Dafny binary releases can be found athttps://github.com/dafny-lang/dafny/releases for each supported platform.Each release is a .zip file with a name combining the release name and theplatform. Current platforms are Windows 11, Ubuntu 20 and later, and MacOS 10.14 and later.
The dafny tool is distributed as a standalone executable. A compatible version of the required Z3 solver is included in the release.There are additional dependencies that are needed to compile dafny to particular target languages,as described in the release instructions.A development environment tobuild dafny from source requires additional dependencies, describedhere.
13.2.2. IDEs for Dafny
Dafny source files are text files and can of course be edited with anytext editor. However, some tools provide syntax-aware features:
- VSCode, a cross-platform editor for many programming languages has an extension for Dafny. VSCode is availablehere and the Dafny extension can be installed from within VSCode.Theextension provides syntax highlighting, in-line parser,type and verification errors, code navigation, counter-example display and gutter highlights.
- There is aDafny mode for Emacs.
- An old Visual Studio plugin is no longer supported
Information about installing IDE extensions for Dafny is foundon theDafny INSTALL page in the wiki.
More information about using VSCode IDE ishere.
13.3. Dafny Programs and Files
A Dafny program is a set of modules.Modules can refer to other modules, such as throughimport
declarationsorrefines
clauses.A Dafny program consists of all the modules needed so that all modulereferences are resolved.Dafny programs are contained in files that have a.dfy
suffix.Such files each holdsome number of top-level declarations. Thus a full program may bedistributed among multiple files.To apply thedafny
tool to a Dafny program, thedafny
tool must begiven all the files making up a complete program (or, possibly, more thanone program at a time). This can be effected either by listing all of the filesby name on the command-line or by usinginclude
directives within a fileto stipulate what other files contain modules that the given files need.Thus the complete set of modules are all the modules in all the files listedon the command-line or referenced, recursively, byinclude
directiveswithin those files. It does not matter if files are repeated either asincludes or on the command-line.15
All files recursively included are always parsed and type-checked.However, which files are verified, built, run, or processed by otherdafny commands depends on the individual command. These commands are described inSection 13.6.1.
For the purpose of detecting duplicates, file names are considered equal if they have the same absolute path, compared as case-sensitive strings (regardless of whether the underlying file-system is case sensitive). Using symbolic links may make the same file have a different absolute path; this will generally cause duplicate declaration errors.
13.3.1. Dafny Verification Artifacts: the Library Backend and .doo Files
As of Dafny 4.1,dafny
now supports outputting a single file containinga fully-verified program along with metadata about how it was verified.Such files use the extension.doo
, for Dafny Output Object,and can be used as input anywhere a.dfy
file can be.
.doo
files are produced by an additional backend called the “Dafny Library” backend,identified with the namelib
on the command line. For example, to build multipleDafny files into a single build artifact for shared reuse, the command would look something like:
dafny build-t:lib A.dfy B.dfy C.dfy--output:MyLib.doo
The Dafny code contained in a.doo
file is not re-verified when passed back to thedafny
tool,just as included files and those passed with the--library
option are not.Using.doo
files provides a guarantee that the Dafny code was in fact verified,however, and therefore offers protection against build system mistakes..doo
files are therefore ideal for sharing Dafny libraries between projects.
.doo
files also contain metadata about the version of Dafny used to verify themand the values of relevant options that affect the sound separate verification andcompilation of Dafny code, such as--unicode-char
.This means attempting to use a library that was built with optionsthat are not compatible with the currently executing command optionswill lead to errors.This also includes attempting to use a.doo
file built with a different version of Dafny,although this restriction may be lifted in the future.
A.doo
file is a compressed archive of multiple files, similar to the.jar
file format for Java packages.The exact file format is internal and may evolve over time to support additional features.
Note that the library backend only supports thenewer command-style CLI interface.
13.3.2. Dafny Translation Artifacts: .dtr Files
Some options, such as--outer-module
or--optimize-erasable-datatype-wrapper
,affect what target language code the same Dafny code is translated to.In order to translate Dafny libraries separately from their consuming codebases,the translation process for consuming code needs to be awareof what options were used when translating the library.
For example, if a library defines aFoo()
function in anA
module,but--outer-moduleorg.coolstuff.foolibrary.dafnyinternal
is specified when translating the library to Java,then a reference toA.Foo()
in a consuming Dafny projectneeds to be translated toorg.coolstuff.foolibrary.dafnyinternal.A.Foo()
,independently of what value of--outer-module
is used for the consuming project.
To meet this need,dafnytranslate
also outputs a<program-name>-<targetid>.dtr
Dafny Translation Record file.Like.doo
files,.dtr
files record all the relevant options that were used,in this case relevant to translation rather than verification.These files can be provided to future calls todafnytranslate
using the--translation-record
option,in order to provide the details of how various libraries provided with the--library
flag were translated.
Currently--outer-module
is the only option recorded in.dtr
files,but more relevant options will be added in the future.A later version of Dafny will also require.dtr
files that cover all modulesthat are defined in--library
options,to support checking that all relevant options are compatible.
13.4. Dafny Standard Libraries
As of Dafny 4.4, thedafny
tool includes standard libraries that any Dafny code base can depend on.For now they are only available when the--standard-libraries
option is provided,but they will likely be available by default in the next major version of Dafny.
See https://github.com/dafny-lang/dafny/blob/master/Source/DafnyStandardLibraries/README.md for details.
13.5. Dafny Code Style
There are coding style conventions for Dafny code, recordedhere.Most significantly, code is written without tabs and with a 2 space indentation. Following code style conventions improves readability but does not alter program semantics.
13.6. Using Dafny From the Command Line
dafny
is a conventional command-line tool, operating just like othercommand-line tools in Windows and Unix-like systems.In general, the format of a command-line is determined by the shell program that is executing the command-line (.e.g., bash, the windows shell, COMMAND, etc.), but is expected to be a series of space-separated “words”, each representing a command, option, option argument, file, or folder.
13.6.1. dafny commands
As of v3.9.0,dafny
uses a command-style command-line (likegit
for example); prior to v3.9.0, the command line consisted only of options and files.It is expected that additional commands will be added in the future.Each command may have its own subcommands and its own options, in addition to generally applicable options. Thus the format of the command-line isa command name, followed by options and files:dafny<command><options><files>
;the command-name must be the first command-line argument.
The command-linedafny--help
ordafny-h
lists all the available commands.
The command-linedafny<command>--help
(or-h
or-?
) gives help information for that particular <command>, including the list of options.Some options for a particular command are intended only for internal tool development; those are shown using the--help-internal
option instead of--help
.
Also, the command-style command-line has modernized the syntax of options; they are now POSIX-compliant.Like many other tools, options now typically begin with a double hyphen, with some options having a single-hyphen short form, such as--help
and-h
.
If no <command> is given, then the command-line is presumed to use old-style syntax, so any previously written command-line will still be valid.
dafny
recognizes the commands described in the following subsections. The most commonly usedaredafnyverify
,dafnybuild
, anddafnyrun
.
The command-line also expects the following:
- Files are designated by absolute paths or paths relative to the currentworking directory. A command-line argument not matching a known option is considered a filepath, and likely onewith an unsupported suffix, provoking an error message.
- Files containing dafny code must have a
.dfy
suffix. - There must be at least one
.dfy
file (except when using--stdin
or in the case ofdafnyformat
, see theDafny format section) - The command-line may contain other kinds of files appropriate tothe language that the Dafny files are being compiled to. The kind of file is determined by its suffix.
- Escape characters are determined by the shell executing the command-line.
- Per POSIX convention, the option
--
means that all subsequent command-line arguments are not options to the dafny tool; they are either files or arguments to thedafnyrun
command. - If an option is repeated (e.g., with a different argument), then the later instance on the command-line supersedes the earlier instance, with just a few options accumulating arguments.
- If an option takes an argument, the option name is followed by a
:
or=
or whitespace and then by the argument value; if the argument itself contains white space, the argument must be enclosed in quotes. It is recommended to use the:
or=
style to avoid misinterpretation or separation of a value from its option. - Boolean options can take the values
true
andfalse
(or any case-insensitive version of those words). For example, the value of--no-verify
is by defaultfalse
(that is, do verification). It can be explicitly set to true (no verification) using--no-verify
,--no-verify:true
,--no-verify=true
,--noverifytrue
; it can be explicitly set false (do verification) using--no-verify:false
or--no-verify=false
or--no-verifyfalse
. - There is a potential ambiguity when the form
--optionvalue
is used if the value is optional (such as for boolean values). In such a case an argument after an option (that does not have an argument given with:
or=
) is interpreted as the value if it is indeed a valid value for that option. However, better style advises always using a ‘:’ or ‘=’ to set option values.No valid option values in dafny look like filenames or begin with--
.
13.6.1.1. Options that are not associated with a command
A few options are not part of a command. In these cases any single-hyphen spelling also permits a spelling beginning with ‘/’.
dafny--help
ordafny-h
lists all the available commandsdafny-?
ordafny-help
list all legacy optionsdafny--version
(or-version
) prints out the number of the version this build of dafny implements
13.6.1.2.dafnyresolve
Thedafnyresolve
command checks the command-line and then parses and typechecks the given files and any included files.
The set of files considered bydafny
are those listed on the command-line,including those named in a--library
option, and all files that arenamed, recursively, ininclude
directives in files in the set being considered by the tool.
The set of files presented to an invocation of thedafny
tool must contain all the declarations needed to resolve all names and types, else name or type resolution errors will be emitted.
dafny
can parse and verify sets of files that do not form a complete program because they are missing the implementations of some constructs such as functions, lemmas, and loop bodies.16However,dafny
will need all implementations in order to compile a working executable.
declaration and implementation of methods, functions and types in separate files, nor, for that matter,separation of specification and declaration. Implementations can be omitted simply by leaving them out of the declaration (or a lemma, for example).However, a combination oftraits
andclasses
can achieve a separation of interfaceand specification fromimplementation.
The options relevant to this command are
- those relevant to the command-line itself
--allow-warnings
— return a successexit code, even when there are warnings
- those that affect dafny` as a whole, such as
--cores
— set the number of cores dafny should use--show-snippets
— emit a line or so of source code along with an error message--library
— include this file in the program, but do not verify or compile it (multiple such library files can be listed using multiple instances of the--library
option)--stdin
– read from standard input
- those that affect the syntax of Dafny, such as
--prelude
--unicode-char
--function-syntax
--quantifier-syntax
--track-print-effects
--warn-shadowing
--warn-missing-constructor-parentheses
13.6.1.3.dafnyverify
Thedafnyverify
command performs thedafnyresolve
checks and then attempts to verify each declaration in the program.
A guide to controlling and aiding the verification process is given ina later section.
To be consideredverified all the methods in all the files in a program must be verified, with consistent sets of options,and with no unproven assumptions (seedafnyaudit
for a tool to help identify such assumptions).
Dafny worksmodularly, meaning that each method is considered by itself, using only the specifications of other methods.So, when using the dafny tool, you can verify the program all at once or one file at a time or groups of files at a time.On a large program, verifying all files at once can take quite a while, with little feedback as to progress, though it doessave a small amount of work by parsing all files just once. But, one way or another, to have a complete verification, all implementations of all methods and functions must eventually be verified.
- By default, only those files listed on the command-line are verified in a given invocation of the
dafny
tool. - The option
--verify-included-files
(-verifyAllModules
in legacy mode) forces the contents of all non-library files to be verified, whether they are listed on the command-line or recursively included by files on the command-line. - The
--library
option marks files that are excluded from--verify-included-files
. Such a file may also, but need not, be the target of aninclude
directive in some file of the program; in any case, such files are included in the program but not in the set of files verified (or compiled). The intent of this option is to mark files thatshould be considered as libraries that are independently verified prior to being released for shared use. - Verifying files individually is equivalent to verifying them in groups, presuming no other changes.It is also permitted to verify completely disjoint files orprograms together in a single run of
dafny
.
Various options control the verification process, in addition to all those described fordafnyresolve
.
- What is verified
--verify-included-files
(when enabled, all included files are verified, except library files, otherwise just those files on the command-line)--relax-definite-assignment
--track-print-effects
--disable-nonlinear-arithmetic
--filter-symbol
- Control of the proof engine
--manual-lemma-induction
--verification-time-limit
--boogie
--solver-path
13.6.1.4.dafnytranslate<language>
Thedafnytranslate
command translates Dafny source code to source code for another target programming language.The command always performs the actions ofdafnyresolve
and, unless the--no-verify
option is specified, does the actions ofdafnyverify
.The language is designated by a subcommand argument, rather than an option, and is required.The current set of supported target languages is
- cs (C#)
- java (Java)
- js (JavaScript)
- py (Python)
- go (Go)
- cpp (C++ – but only limited support)
In addition to generating the target source code,dafny
may generate build artifacts to assist in compiling the generated code.The specific files generated depend on the target programming language.More detail is given inthe section on compilation.
Thedafny
tool intends that the compiled program in the target language be a semantically faithful rendering of the (verified) Dafny program. However, resource and language limitations make this not always possible. For example, though Dafny can express and reason about arrays of unbounded size, not all target programming languages can represent arrays larger than the maximum signed 32-bit integer.
Various options control the translation process, in addition to all those described fordafnyresolve
anddafnyverify
.
- General options:
--no-verify
— turns off all attempts to verify the program--verbose
— print information about generated files
- The translation results
--output
(or-o
) — location of the generated file(s) (this specifies a file path and name; a folder location for artifacts is derived from this name)--include-runtime
— include the Dafny runtime for the target language in the generated artifacts--optimize-erasable-datatype-wrapper
--enforce-determinism
--test-assumptions
— (experimental) inserts runtime checks for unverified assumptions when they are compilable
13.6.1.5.dafnybuild
Thedafnybuild
command runsdafnytranslate
and then compiles the result into an executable artifact for the target platform,such as a.exe
or.dll
or executable.jar
, or just the source code for an interpreted language.If the Dafny program does not have a Main entry point, then the build command creates a library, such as a.dll
or.jar
.As withdafnytranslate
, all the previous phases are also executed, including verification (unless--no-verify
is a command-line option).By default, the generated file is in the same directory and has the same name with a different extension as the first.dfy file on the command line. This location and name can be set by the--output
option.
The location of theMain
entry point is described [here](#sec-user-guide-main}.
There are no additional options fordafnybuild
beyond those fordafnytranslate
and the previous compiler phases.
Note thatdafnybuild
may do optimizations thatdafnyrun
does not.
Details for specific target platforms are describedin Section 25.7.
13.6.1.6.dafnyrun
Thedafnyrun
command compiles the Dafny program and then runs the resulting executable.Note thatdafnyrun
is engineered to quickly compile and launch the program;dafnybuild
may take more time to do optimizations of the build artifacts.
The form of thedafnyrun
command-line is slightly different than for other commands.
- It permits just one
.dfy
file, which must be the file containing theMain
entry point;the location of theMain
entry point is described [here](#sec-user-guide-main}. - Other files are included in the program either by
include
directives within that one file or by the--input
option on the command-line. - Anything that is not an option and is not that one dfy fileis an argument to the program being run (and not to dafny itself).
- If the
--
option is used, then anything after that option is a command-line argument to the program being run.
During development, users must usedafnyrun--allow-warnings
if they want to run their Dafny code when it contains warnings.
Here are some examples:
dafnyrunA.dfy
– builds and runs the Main program inA.dfy
with no command-line argumentsdafnyrunA.dfy--no-verify
– builds the Main program inA.dfy
using the--no-verify
option, and then runs the program with no command-line argumentsdafnyrunA.dfy----no-verify
– builds the Main program inA.dfy
(not using the--no-verify
option), and then runs the program with one command-line argument, namely--no-verify
dafnyrunA.dfy123B.dfy
– builds the Main program inA.dfy
andthen runs it with the four command-line arguments123B.dfy
dafnyrunA.dfy123--inputB.dfy
– builds the Main program inA.dfy
andB.dfy
, andthen runs it with the three command-line arguments123
dafnyrunA.dfy12--3-quiet
– builds the Main program inA.dfy
and then runs it with the four command-line arguments123-quiet
Each timedafnyrun
is invoked, the input Dafny program is compiled before it is executed.If a Dafny program should be run more than once, it can be faster to usedafnybuild
,which enables compiling a Dafny program once and then running it multiple times.
Note:dafnyrun
will typically produce the same results as the executables produced bydafnybuild
. The only expected differences are these:
- performance —
dafnyrun
may not optimize as much asdafnybuild
- target-language-specific configuration issues — e.g. encoding issues:
dafnyrun
sets language-specific flags to request UTF-8 output for theprint
statement in all languages, whereasdafnybuild
leaves language-specific runtime configuration to the user.
13.6.1.7.dafnyserver
Thedafnyserver
command starts the Dafny Language Server, which is anLSP-compliant implementation of Dafny.TheDafny VSCode extension uses this LSP implementation, which in turn uses the same core Dafny implementation as the command-line tool.
The Dafny Language Server is described in more detailhere.
13.6.1.8.dafnyaudit
Thedafnyaudit
command reports issues in the Dafny code that might limit the soundness claims of verification.
This command is under development.
The command executes thedafnyresolve
phase (accepting its options) and has the following additional options:
--report-file:<report-file>
— specifies the path where the audit report file will be stored. Without this option, the report will be issued as standard warnings, written to standard-out.--report-format:<format>
— specifies the file format to use for the audit report. Supported options include:- ‘txt, ‘text’: plain text in the format of warnings
- ‘html’: standalone HTML (‘html’)
- ‘md’, ‘markdown’, ‘md-table’, ‘markdown-table’: a Markdown table
- ‘md-ietf’, ‘markdown-ietf’: an IETF-language document in Markdown format
- The default is to infer the format from the filename extension
--compare-report
— compare the report that would have been generated with the existing file given by –report-file, and fail if they differ.
The command emits exit codes of
- 1 for command-line errors
- 2 for parsing, type-checking or serious errors in running the auditor (e.g. failure to write a report or when report comparison fails)
- 0 for normal operation, including operation that identifies audit findings
It also takes the--verbose
option, which then gives information about the files being formatted.
Thedafnyaudit
command currently reports the following:
Any declaration marked with the
{:axiom}
attribute.This is typically used to mark that a lemma with no body (and is therefore assumed to always be true) is intended as an axiom.The key purpose of theaudit
command is to ensure that all assumptions are intentional and acknowledged.To improve assurance, however, try to provide a proof.Any declaration marked with the
{:verifyfalse}
attribute, which tells the verifier to skip verifying this declaration.Removing the attribute and providing a proof will improve assurance.Any declaration marked with the
{:extern}
attribute that has at least onerequires
orensures
clause.If code implemented externally, and called from Dafny, has anensures
clause, Dafny assumes that it satisfies that clause.Since Dafny cannot prove properties about code written in other languages,adding tests to provide evidence that anyensures
clauses do hold can improve assurance.The same considerations apply torequires
clauses on Dafny code intended to be called from external code.Any definition with an
assume
statement in its body.To improve assurance, attempt to convert it to anassert
statement and prove that it holds.Such a definition will not be compilable unless the statement is also marked with{:axiom}
.Alternatively, converting it to anexpect
statement will cause it to be checked at runtime.Any method marked with
decreases*
.Such a method may not terminate.Although this cannot cause an unsound proof, in the logic of Dafny,it’s generally important that any non-termination be intentional.Any
forall
statement without a body.This is equivalent to an assumption of its conclusion.To improve assurance, provide a body that proves the conclusion.Any loop without a body.This is equivalent to an assumption of any loop invariants in the code after the loop.To improve assurance, provide a body that establishes any stated invariants.
Any declaration with no body and at least one
ensures
clause.Any code that calls this declaration will assume that allensures
clauses are true after it returns.To improve assurance, provide a body that proves that anyensures
clauses hold.
13.6.1.9.dafnyformat
Dafny supports a formatter, which for now only changes the indentation of lines in a Dafny file, so that it conformsto the idiomatic Dafny code formatting style.For the formatter to work, the file should be parsed correctly by Dafny.
There are four ways to use the formatter:
dafnyformat<oneormore.dfyfilesorfolders>
formats the given Dafny files and the Dafny files in the folders, recursively, altering the files in place. For example,dafnyformat.
formats all the Dafny files recursively in the current folder.dafnyformat--print<filesand/orfolders>
formats each file but instead of altering the files, output the formatted content to stdoutdafnyformat--check<filesand/orfolders>
does not alter files. It will print a message concerning which files need formatting and return a non-zero exit code if any files would be changed by formatting.
You can also use--stdin
instead of providing a file, to format a full Dafny file from the standard input.Input files can be named along with--stdin
, in which case both the files and the content of the stdin are formatted.
Each version ofdafnyformat
returns a non-zero return code if there are any command-line or parsingerrors or if –check is stipulated and at least one file is not the same as its formatted version.dafnyformat
does not necessarily report name or type resolution errors and does not attempt verification.
13.6.1.10.dafnytest
This command (verifies and compiles the program and) runs every method in the program that is annotated with the{:test}
attribute.Verification can be disabled using the--no-verify
option.dafnytest
also accepts all other options of thedafnybuild
command. In particular, it accepts the--target
option that specifies the programming language used in the build and execution phases.
dafnytest
also accepts these options:
-spill-translation
- (default disabled) when enabled the compilation artifacts are retained--output
- gives the folder and filename root for compilation artifacts--methods-to-test
- the value is a (.NET) regular expression that is matched against the fullyqualified name of the method; only those methods that match are tested--coverage-report
- the value is a directory in which Dafny will save an html coverage report highlighting parts ofthe program that execution of the tests covered.
The order in which the tests are run is not specified.
For example, this code (as the filet.dfy
)
method{:test}m(){mm();print"Hi!\n";}methodmm(){print"mm\n";}moduleM{method{:test}q(){print42,"\n";}}classA{staticmethod{:test}t(){print"T\n";}}
and this command-line
dafnytest--no-verify t.dfy
produce this output text:
M.q: 42PASSEDA.t: TPASSEDm: mmHi!PASSED
and this command-line
dafnytest--no-verify--methods-to-test='m' t.dfy
produces this output text:
m: mmHi!PASSED
13.6.1.11.dafnydoc
[Experimental]
Thedafnydoc
command generates HTML documentation pages describing the contents of eachmodule in a set of files, using the documentation comments in the source files.This command is experimental; user feedback and contributor PRs on the layout of information and the navigation are welcome.
- The format of the documentation comments is describedhere.
- The
dafnydoc
command accepts either files or folders as command-line arguments. A folderrepresents all the.dfy
files contained recursively in that folder. A file that is a.toml
project file represents all the files and options listed in the project file. - The command first parses and resolves the given files; it only proceeds to produce documentationif type resolution is successful (on all files). All the command-line options relevant to
dafnyresolve
are available fordafnydoc
. - The value of the
--output
option is a folder in which all the generated files will be placed.The default location is./docs
. The folder is created if it does not already exist.Any existing content of the folder is overwritten. - If
--verbose
is enabled, a list of the generated files is emitted to stdout. - The output files contain information stating the source .dfy file in which the module isdeclared. The
--file-name
option controls the form of the filename in that information:- –file-name:none – no source file information is emitted
- –file-name:name – (default) just the file name is emitted (e.g.,
Test.dfy
) - –file-name:absolute – an absolute full path is emitted
- –file-name:relative=
-- a file name relative to the given prefix is emitted
- If
--modify-time
is enabled, then the generated files contain information stating thelast modified time of the source of the module being documented. - The
--program-name
option states text that will be included in the heading of the TOC and index pages
The output files are HTML files, all contained in the given folder, one per module plus anindex.html
file giving an overall table of contents and anameindex.html
file containingan alphabetical by name list of all the declarations in all the modules.The documentation for the root module is in_.html
.
13.6.1.12.dafnygenerate-tests
Thisexperimental command allows generating tests from Dafny programs.The tests provide complete coverage of the implementation and one can execute them using thedafnytest
command.Dafny can target different notions of coverage while generating tests, with basic-block coverage being the recommended setting.Basic blocks are extracted from the Boogie representation of the Dafny program, with one basic block correspondingto a statement or a non-short-circuiting subexpression in the Dafny code. The underlying implementation uses the verifier to reason about the reachability of different basic blocks in the program and infers necessary test inputs from counterexamples.
For example, this code (as the fileprogram.dfy
)
moduleM{function{:testEntry}Min(a:int,b:int):int{ifa<bthenaelseb}}
and this command-line
dafny generate-tests Block program.dfy
produce two tests:
include"program.dfy"moduleUnitTests{importMmethod{:test}Test0(){varr0:=M.Min(0,0);}method{:test}Test1(){varr0:=M.Min(0,1);}}
The two tests together cover every basic block within theMin
function in the input program. Note that theMin
function is annotated with the{:testEntry}
attribute. This attribute marksMin
as the entry point for all generated tests, and there must always be at least one method or function so annotated.Another requirement is that any top-level declaration that is not itself a module (such as class, method, function, etc.) must be a member of an explicitly named module, which is calledM
in the example above.
This command is under development and not yet fully functional.
13.6.1.13.Inlining
By default, when asked to generate tests, Dafny will produceunit tests, which guarantee coverage of basic blockswithin the method they call but not within any of its callees. By contrast, system-level tests canguarantee coverage of a large part of the program while at the same time using a single method as an entry point. In order to prompt Dafny to generate system-level tests, one must use the{:testInline}
attribute.
For example, this code (as the fileprogram.dfy
)
moduleM{function{:testInline}Min(a:int,b:int):int{ifa<bthenaelseb}method{:testEntry}Max(a:int,b:int)returns(c:int)// the tests convert the postcondition below into runtime check:ensuresc==ifa>bthenaelseb{return-Min(-a,-b);}}
and this command-line
dafny generate-tests Block program.dfy
produce two tests:
include"program.dfy"moduleUnitTests{importMmethod{:test}Test0(){varr0:=M.Max(7719,7720);expectr0==if7719>7720then7719else7720;}method{:test}Test1(){varr0:=M.Max(1,0);expectr0==if1>0then1else0;}}
Without the use of the{:testInline}
attribute in the example above, Dafny will only generate a single test because there is only one basic-block within theMax
method itself – all the branching occurs within theMin
function.Note also that Dafny automatically converts all non-ghost postconditions on the method under tests intoexpect
statements,which the compiler translates to runtime checks in the target language of choice.
When the inlined method or function is recursive, it might be necessary to unroll the recursion several times to get adequate code coverage. The depth of recursion unrolling should be provided as an integer argument to the{:testInline}
attribute. For example, in the program below, the functionMod3
is annotated with{:testInline2}
and will, therefore, be unrolled twice during test generation. The function naively implements division by repeatedly andrecursively subtracting3
from its argument, and it returns the remainder of the division, which is one of the three base cases. Because theTestEntry
method callsMod3
with an argument that is guaranteed to be at least3
,the base case will never occur on first iteration, and the function must be unrolled at least twice for Dafny to generatetests covering any of the base cases:
moduleM{function{:testInline2}Mod3(n:nat):natdecreasesn{ifn==0then0elseifn==1then1elseifn==2then2elseMod3(n-3)}method{:testEntry}TestEntry(n:nat)returns(r:nat)requiresn>=3{r:=Mod3(n);}}
13.6.1.14.CommandLineOptions
Test generation supports a number of command-line options that control its behavior.
The first argument to appear after thegenerate-test
command specifies the coverage criteria Dafny will attempt to satisfy. Of these, we recommend basic-block coverage (specified with keywordBlock
), which is also the coverage criteria usedthroughout the relevant parts of this reference manual. The alternatives are path coverage (Path
) and block coverage after inlining (InlinedBlock
). Path coverage provides the most diverse set of tests but it is also the most expensive in terms of time it takes to produce these tests. Block coverage after inlining is a call-graph sensitive version of block coverage - it takes into account every block in a given method for every path through the call-graph to that method.
The following is a list of command-line-options supported by Dafny during test generation:
--verification-time-limit
- the value is an integer that sets a timeout for generating a single test. The default is 20 seconds.--length-limit
- the value is an integer that is used to limit the lengths or all sequences and sizes of all maps and sets that test generation will consider as valid test inputs. This can sometimes be necessary to prevent test generation from creating unwieldy tests with excessively long strings or large maps. This option isdisabled by default--coverage-report
- the value is a directory in which Dafny will save an html coverage report highlighting parts ofthe program that the generated tests are expected to cover.--print-bpl
- the value is the name of the file to which Dafny will save the Boogie code used for generating tests.This options is mostly useful for debugging test generation functionality itself.--force-prune
- this flag enables axiom pruning, a feature which might significantly speed up test generation but can also reduce coverage or cause Dafny to produce tests that do not satisfy the preconditions.
Dafny will also automatically enforce the following options during test generation:--enforce-determinism
,/typeEncoding:p
(an option passed on to Boogie).
13.6.1.15.dafnyfind-dead-code
Thisexperimental command finds dead code in a program, that is, basic-blocks within a method that are not reachable by any inputs that satisfy the method’s preconditions. The underlying implementation is identical to that ofdafnygenerate-tests
command and can be controlled by the same command line options and method attributes.
For example, this code (as the fileprogram.dfy
)
moduleM{function{:testEntry}DescribeProduct(a:int):string{ifa*a<0then"Product is negative"else"Product is nonnegative"}}
and this command-line
dafny find-dead-code program.dfy
produce this output:
program.dfy(5,9) is reachable.program.dfy(3,4):initialstate is reachable.program.dfy.dfy(5,9)#elseBranch is reachable.program.dfy.dfy(4,9)#thenBranch is potentially unreachable.Out of 4 basic blocks, 3 are reachable.
Dafny reports that the then branch of the condition is potentially unreachable because the verifier proves that noinput can reach it. In this case, this is to be expected, since the product of two numbers can never be negative. Inpractice,find-dead-code
command can produce both false positives (if the reachability query times out) and falsenegatives (if the verifier cannot prove true unreachability), so the results of such a report should always bereviewed.
This command is under development and not yet fully functional.
13.6.1.16.dafnymeasure-complexity
Thisexperimental command reports complexity metrics of a program.
This command is under development and not yet functional.
13.6.1.17. Plugins
This execution mode is not a command, per se, but rather a command-line option that enables executing plugins to the dafny tool.Plugins may be either standalone tools or be additions to existing commands.
The form of the command-line isdafny--plugin:<path-to-one-assembly[,argument]*>
ordafny<command>--plugin:<path-to-one-assembly[,argument]*>
where the argument to--plugin
gives the path to the compiled assembly of the plugin and the arguments to be provided to the plugin.
More on writing and building plugins can be foundin this section.
13.6.1.18. Legacy operation
Prior to implementing the command-based CLI, thedafny
command-line simply took files and options and the arguments to options.That legacy mode of operation is still supported, though discouraged. The commanddafny-?
produces the list of legacy options.In particular, the common commands likedafnyverify
anddafnybuild
are accomplished with combinations of options like-compile
,-compileTarget
and-spillTargetCode
.
Users are encouraged to migrate to the command-based style of command-lines and the double-hyphen options.
13.6.2. In-tool help
As is typical for command-line tools,dafny
provides in-tool help through the-h
and--help
options:
dafny-h
,dafny--help
list the commands available in thedafny
tooldafny-?
lists all the (legacy) options implemented indafny
dafny<command>-h
,dafny<command>--help
,dafny<command>-?
list the options available for that command
13.6.3. dafny exit codes
The basic resolve, verify, translate, build, run and commands of dafny terminate with these exit codes.
- 0 – success
- 1 – invalid command-line arguments
- 2 – syntax, parse, or name or type resolution errors
- 3 – compilation errors
- 4 – verification errors
Errors in earlier phases of processing typically hide later errors.For example, if a program has parsing errors, verification or compilation willnot be attempted.
Other dafny commands may have their own conventions for exit codes. However in all cases, an exit code of 0 indicates successful completion of the command’stask and small positive integer values indicate errors of some sort.
13.6.4. dafny output
Most output fromdafny
is directed to the standard output of the shell invoking the tool, though some goes to standard error.
- Command-line errors: these are produced by the dotnet CommandLineOptions package are directed tostandard-error
- Other errors: parsing, typechecking, verification and compilation errors are directed tostandard-out
- Non-error progress information also is output tostandard-out
- Dafny
print
statements, when executed, send output tostandard-out - Dafny
expect
statements (when they fail) send a message tostandard-out. - Dafny I/O libraries send output explicitly to eitherstandard-out or standard-error
13.6.5. Project files
Commands on the Dafny CLI that can be passed a Dafny file can also be passed a Dafny project file. Such a project file may define which Dafny files the project contains and which Dafny options it uses. The project file must be aTOML file nameddfyconfig.toml
for it to work on both the CLI and in the Dafny IDE, although the CLI will accept any.toml
file. Here’s an example of a Dafny project file:
includes=["src/**/*.dfy"]excludes=["**/ignore.dfy"]base=["../commonOptions.dfyconfig.toml"][options]enforce-determinism=truewarn-shadowing=true
- At most one
.toml
file may be named on the command-line; when using the command-line no.toml
file is used by default. - In the
includes
andexcludes
lists, the file paths may have wildcards, including**
to mean any number of directory levels; filepaths are relative to the location of the.toml
file in which they are named. - Dafny will process the union of (a) the files on the command-line and (b) the files designated in the
.toml
file, which are those specified by theincludes
, omitting those specified by theexcludes
.Theexcludes
does not remove any files that are listed explicitly on the command-line. - Under the section
[options]
, any options from the Dafny CLI can be specified using the option’s name without the--
prefix. - When executing a
dafny
command using a project file, any options specified in the file that can be applied to the command, will be. Options that can’t be applied are ignored; options that are invalid for any dafny command trigger warnings. - Options specified on the command-line take precedence over any specified in the project file, no matter the order of items on the command-line.
When using a Dafny IDE based on the
dafnyserver
command, the IDE will search for project files by traversing up the file tree looking for the closestdfyconfig.toml
file to the dfy being parsed that it can find. Options from the project file will override options passed todafnyserver
.- The field ‘base’ can be used to let one project file inherit options from another. If an option is specified in both, then the value specified in the inheriting project is used. Includes from the inheritor override excludes from the base.
It’s not possible to use Dafny project files in combination with the legacy CLI UI.
13.7. Verification
In this section, we suggest a methodology to figure outwhy a single assertion might not hold, we propose techniques to deal withassertions that slow a proof down, we explain how toverify assertions in parallel or in a focused way, and we also give some more examples ofuseful options and attributes to control verification.
13.7.1. Verification debugging when verification fails
Let’s assume one assertion is failing (“assertion might not hold” or “postcondition might not hold”). What should you do next?
The following section is textual description of the animation below, which illustrates the principle of debugging an assertion by computing the weakest precondition:
13.7.1.1. Failing postconditions
Let’s look at an example of a failing postcondition.
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;ifb{returnj;}//^^^^^^^ a postcondition might not hold on this return path.i:=2;}
One first thing you can do is replace the statementreturnj;
by two statementsi:=j;return;
to better understand what is wrong:
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;ifb{i:=j;return;}//^^^^^^^ a postcondition might not hold on this return path.i:=2;}
Now, you can assert the postcondition just before the return:
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;ifb{i:=j;assert2<=i;// This assertion might not holdreturn;}i:=2;}
That’s it! Now the postcondition is not failing anymore, but theassert
contains the error!you can now move to the next section to find out how to debug thisassert
.
13.7.1.2. Failing asserts
In theprevious section, we arrived at the point where we have a failing assertion:
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;ifb{i:=j;assert2<=i;// This assertion might not holdreturn;}i:=2;}
To debug why this assert might not hold, we need tomove this assert up, which is similar tocomputing the weakest precondition.For example, if we havex:=Y;assertF;
and theassertF;
might not hold, the weakest precondition for it to hold beforex:=Y;
can be written as the assertionassertF[x:=Y];
, where we replace every occurrence ofx
inF
intoY
.Let’s do it in our example:
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;ifb{assert2<=j;// This assertion might not holdi:=j;assert2<=i;return;}i:=2;}
Yay! The assertionassert2<=i;
is not proven wrong, which means that if we manage to proveassert2<=j;
, it will work.Now, this assert should hold only if we are in this branch, so tomove the assert up, we need to guard it.Just before theif
, we can add the weakest preconditionassertb==>(2<=j)
:
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{varj:=if!bthen3else1;assertb==>2<=j;// This assertion might not holdifb{assert2<=j;i:=j;assert2<=i;return;}i:=2;}
Again, now the error is only on the topmost assert, which means that we are making progress.Now, either the error is obvious, or we can one more time replacej
by its value and create the assertassertb==>((if!bthen3else1)>=2);
methodFailingPostcondition(b:bool)returns(i:int)ensures2<=i{assertb==>2<=(if!bthen3else1);// This assertion might not holdvarj:=if!bthen3else1;assertb==>2<=j;ifb{assert2<=j;i:=j;assert2<=i;return;}i:=2;}
At this point, this is pure logic. We can simplify the assumption:
b==>2<=(if!bthen3else1)!b||(if!bthen2<=3else2<=1)!b||(if!bthentrueelsefalse)!b||!b;!b;
Now we can understand what went wrong: When b is true, all of these formulas above are false, this is why thedafny
verifier was not able to prove them.In the next section, we will explain how to “move asserts up” in certain useful patterns.
13.7.1.3. Failing asserts cases
This list is not exhaustive but can definitely be useful to provide the next step to figure out why Dafny could not prove an assertion.
Failing assert | Suggested rewriting |
---|---|
x:=Y; assertP; | assertP[x:=Y]; x:=Y; assertP; |
ifB{ assertP; ... } | assertB==>P; ifB{ assertP; ... } |
ifB{ ... }else{ assertP; ... } | assert!B==>P; ifB{ ... }else{ assertP; ... } |
ifX{ ... }else{ ... } assertA; | ifX{ ... assertA; }else{ ... assertA; } assertA; |
assertforallx::Q(x); | forallx ensuresQ(x) { assertQ(x); }; assertforallx::Q(x); |
assertforallx::P(x)==>Q(x); | forallx|P(x) ensuresQ(x) { assertQ(x); }; assertforallx::P(x)==>Q(x); |
assertexistsx|P(x)::Q(x); assertexistsx|P(x)::Q'(x); | ifx:|P(x){ assertQ(x); assertQ'(x); }else{ assertfalse; } |
assertexistsx::P(x); | assertP(x0); assertexistsx::P(x); for a given expression x0 . |
ensuresexistsi::P(i); | returns(j:int) ensuresP(j)ensuresexistsi::P(i) in a lemma, so that the j can be computed explicitly. |
assertA==B; callLemma(x); assertB==C; | calc=={ A; B; {callLemma(x);} C; }; assertA==B; where the calc statement can be used to make intermediate computation steps explicit. Works with< ,> ,<= ,>= ,==> ,<== and<==> for example. |
assertA==>B; | ifA{ assertB; }; assertA==>B; |
assertA&&B; | assertA; assertB; assertA&&B; |
ensuresP==>Q on a lemma | requiresPensuresQ to avoid accidentally calling the lemma on inputs that do not satisfyP |
seq(size,i=>P) | seq(size,irequires0<=i<size=>P); |
assertforallx::G(i)==>R(i); | assertG(i0); assertR(i0); assertforalli::G(i)==>R(i); with a guess of thei0 that makes the second assert to fail. |
assertforalli|0<i<=m::P(i); | assertforalli|0<i<m::P(i); assertforalli|i==m::P(i); assertforalli|0<i<=m::P(i); |
assertforalli|i==m::P(m); | assertP(m); assertforalli|i==m::P(i); |
methodm(i)returns(j:T) requiresA(i) ensuresB(i,j) { ... } methodn(){ ... varx:=m(a); assertP(x); | methodm(i)returns(j:T) requiresA(i) ensuresB(i,j) { ... } methodn(){ ... assertA(k); assertforallx::B(k,x)==>P(x); varx:=m(k); assertP(x); |
methodm_mod(i)returns(j:T) requiresA(i) modifiesthis,i ensuresB(i,j) { ... } methodn_mod(){ ... varx:=m_mod(a); assertP(x); | methodm_mod(i)returns(j:T) requiresA(i) modifiesthis,i ensuresB(i,j) { ... } methodn_mod(){ ... assertA(k); modifythis,i;// Temporarily varx:T;// Temporarily assumeB(k,x); // var x := m_mod(k); assertP(x); |
modifyx,y; assertP(x,y,z); | assertx!=z&&y!=z; modifyx,y; assertP(x,y,z); |
13.7.1.4. Counterexamples
When verification fails, we can rerun Dafny with--extract-counterexample
flag to get a counterexample that can potentially explain the proof failure.Note that Danfy cannot guarantee that the counterexample it reports provably violates the assertion it was generated for (see17)The counterexample takes the form of assumptions that can be inserted into the code to describe the potential conditions under which the given assertion is violated. This output should be inspected manually and treated as a hint.
13.7.2. Verification debugging when verification is slow
In this section, we describe techniques to apply in the case when verification is slower than expected, does not terminate, or times out.
Additional detail is available in theverification optimization guide.
13.7.2.1.assumefalse;
Assumingfalse
is an empirical way to short-circuit the verifier and usually stop verification at a given point,18 and since the final compilation steps do not accept this command, it is safe to use it during development.Another similar command,assertfalse;
, would also short-circuit the verifier, but it would still make the verifier try to provefalse
, which can also lead to timeouts.
Thus, let us say a program of this shape takes forever to verify.
methodNotTerminating(b:bool){assertX;ifb{assertY;}else{assertZ;assertP;}}
What we can first do is add anassumefalse
at the beginning of the method:
methodNotTerminating(){assumefalse;// Will never compile, but everything verifies instantlyassertX;ifb{assertY;}else{assertZ;assertP;}assertW;}
This verifies instantly. This gives us a strategy to bisect, or do binary search to find the assertion that slows everything down.Now, we move theassumefalse;
below the next assertion:
methodNotTerminating(){assertX;assumefalse;ifb{assertY;}else{assertZ;assertP;}assertW;}
If verification is slow again, we can usetechniques seen before to decompose the assertion and find which component is slow to prove.
If verification is fast, that’s the sign thatX
is probably not the problem,. We now move theassumefalse;
after the if/then block:
methodNotTerminating(){assertX;ifb{assertY;}else{assertZ;assertP;}assumefalse;assertW;}
Now, if verification is fast, we know thatassertW;
is the problem. If it is slow, we know that one of the two branches of theif
is the problem.The next step is to put anassumefalse;
at the end of thethen
branch, and anassumefalse
at the beginning of the else branch:
methodNotTerminating(){assertX;ifb{assertY;assumefalse;}else{assumefalse;assertZ;assertP;}assertW;}
Now, if verification is slow, it means thatassertY;
is the problem.If verification is fast, it means that the problem lies in theelse
branch.One trick to ensure we measure the verification time of theelse
branch and not the then branch is to move the firstassumefalse;
to the top of the then branch, along with a comment indicating that we are short-circuiting it for now.Then, we can move the secondassumefalse;
down and identify which of the two assertions makes the verifier slow.
methodNotTerminating(){assertX;ifb{assumefalse;// Short-circuit because this branch is verified anywayassertY;}else{assertZ;assumefalse;assertP;}assertW;}
If verification is fast, which of the two assertionsassertZ;
orassertP;
causes the slowdown?19
We now hope you know enough ofassumefalse;
to locate assertions that make verification slow.Next, we will describe some other strategies at the assertion level to figure out what happens and perhaps fix it.
13.7.2.2.assert...by{}
If an assertionassertX;
is slow, it is possible that calling a lemma or invoking other assertions can help to prove it: The postcondition of this lemma, or the added assertions, could help thedafny
verifier figure out faster how to prove the result.
assertSOMETHING_HELPING_TO_PROVE_LEMMA_PRECONDITION;LEMMA();assertX;...lemma()requiresLEMMA_PRECONDITIONensuresX{...}
However, this approach has the problem that it exposes the asserted expressions and lemma postconditions not only for the assertion we want to prove faster,but also for every assertion that appears afterwards. This can result in slowdowns20.A good practice consists of wrapping the intermediate verification steps in anassert...by{}
, like this:
assertXby{assertSOMETHING_HELPING_TO_PROVE_LEMMA_PRECONDITION;LEMMA();}
Now, onlyX
is available for thedafny
verifier to prove the rest of the method.
13.7.2.3. Labeling and revealing assertions
Another way to prevent assertions or preconditions from cluttering the verifier20 is to label and reveal them.Labeling an assertion has the effect of “hiding” its result, until there is a “reveal” calling that label.
The example of theprevious section could be written like this.
assertp:SOMETHING_HELPING_TO_PROVE_LEMMA_PRECONDITION;// p is not available here.assertXby{revealp;LEMMA();}
Similarly, if a precondition is only needed to prove a specific result in a method, one can label and reveal the precondition, like this:
methodSlow(i:int,j:int)requiresgreater:i>j{asserti>=jby{revealgreater;}}
Labelled assert statements are available both in expressions and statements.Assertion labels are not accessible outside of the block which the assert statement is in.If you need to access an assertion label outside of the enclosing expression or statement,you need to lift the labelled statement at the right place manually, e.g. rewrite
ghostpredicateP(i:int)methodTestMethod(x:bool)requiresr:x<==>P(1){ifx{asserta:P(1)by{revealr;}}assertx==>P(1)by{reveala;}// Error, a is not accessible}
to
ghostpredicateP(i:int)methodTestMethod(x:bool)requiresr:x<==>P(1){asserta:x==>P(1)by{ifx{assertP(1)by{revealr;}// Proved without revealing the precondition}}assertx==>P(1)by{reveala;}// Now a is accessible}
To lift assertions, please refer to the techniques described inVerification Debugging.
13.7.2.4. Non-opaquefunctionmethod
Functions are normally used for specifications, but their functional syntax is sometimes also desirable to write application code.However, doing so naively results in the body of afunctionmethodFun()
be available for every caller, which can cause the verifier to time out or get extremely slow20.A solution for that is to add the attribute{:opaque}
right betweenfunctionmethod
andFun()
, and userevealFun();
in the calling functions or methods when needed.
13.7.2.5. Conversion to and from bitvectors
Bitvectors and natural integers are very similar, but they are not treated the same by thedafny
verifier. As such, conversion frombv8
to anint
and vice-versa is not straightforward, and can result in slowdowns.
There are two solutions to this for now. First, one can define asubset type instead of using the built-in typebv8
:
typebyte=x|0<=x<256
One of the problems of this approach is that additions, subtractions and multiplications do not enforce the result to be in the same bounds, so it would have to be checked, and possibly truncated with modulos. For example:
typebyte=x|0<=x<256methodm(){vara:byte:=250;varb:byte:=200;varc:=b-a;// inferred to be an 'int', its value will be 50.vard:=a+b;// inferred to be an 'int', its value will be 450.vare:=(a+b)%256;// still inferred to be an 'int'...varf:byte:=(a+b)%256;// OK}
A better solution consists of creating anewtype that will have the ability to check bounds of arithmetic expressions, and can actually be compiled to bitvectors as well.
newtype{:nativeType"short"}byte=x|0<=x<256methodm(){vara:byte:=250;varb:byte:=200;varc:=b-a;// OK, inferred to be a bytevard:=a+b;// Error: cannot prove that the result of a + b is of type `byte`.varf:=((aasint+basint)%256)asbyte;// OK}
One might consider refactoring this code into separate functions if used over and over.
13.7.2.6. Nested loops
In the case of nested loops, the verifier might timeout sometimes because of inadequate or too much available information20.One way to mitigate this problem, when it happens, is to isolate the inner loop by refactoring it into a separate method, with suitable pre and postconditions that will usually assume and prove the invariant again.For example,
whileXinvariantY{whileX'invariantY'{}}
could be refactored as this:
`whileXinvariantY{innerLoop();}...methodinnerLoop()requireY'ensuresY'
In the next section, when everything can be proven in a timely manner, we explain another strategy to decrease proof time by parallelizing it if needed, and making the verifier focus on certain parts.
13.7.3. Assertion batches, well-formedness, correctness
To understand how to control verification,it is first useful to understand howdafny
verifies functions and methods.
For every method (or function, constructor, etc.),dafny
extractsassertions.Assertions can roughly be sorted into two kinds: Well-formedness and correctness.
Well-formedness assertions: All the implicit requirementsof native operation calls (such as indexing and asserting that divisiors are nonzero),
requires
clauses of function calls, explicitassertion expressions anddecreases
clauses at function call sitesgenerate well-formedness assertions.
An expression is said to bewell-formed in a context ifall well-formedness assertions can be proven in that context.Correctness assertions: All remaining assertions and clauses
For example, given the following statements:
ifb{asserta*a!=0;}c:=(assertb==>a!=0;ifbthen3/aelsef(a));assertc!=5/a;
Dafny performs the following checks:
varc:int;ifb{asserta*a!=0;// Correctness}assertb==>a!=0;// Well-formednessifb{asserta!=0;// Well-formedness}else{assertf.requires(a);// Well-formedness}c:=ifbthen3/aelsef(a);asserta!=0;// Well-formednessassertc!=5/a;// Correctness
Well-formedness is proved at the same time as correctness, except forwell-formedness of requires and ensures clauseswhich is proved separately from the well-formedness and correctness of the rest of the method/function.For the rest of this section, we don’t differentiate between well-formedness assertions and correctness assertions.
We can also classify the assertions extracted by Dafny in a few categories:
Integer assertions:
- Everydivision yields anassertion that the divisor is never zero.
- Everybounded number operation yields anassertion that the result will be within the same bounds (no overflow, no underflows).
- Everyconversion yields anassertion that conversion is compatible.
- Everybitvector shift yields anassertion that the shift amount is never negative, and that the shift amount is within the width of the value.
Object assertions:
- Everyobject property access yields anassertion that the object is not null.
- Every assignment
o.f:=E;
yields anassertion thato
is among the set of objects of themodifies
clause of the enclosingloop ormethod. - Every read
o.f
yields anassertion thato
is among the set of objects of thereads
clause of the enclosing function or predicate. - Everyarray access
a[x]
yields the assertion that0<=x<a.Length
. - Everysequence access
a[x]
yields anassertion, that0<=x<|a|
, because sequences are never null. - Everydatatype update expression anddatatype destruction yields anassertion that the object has the given property.
- Every method overriding a
trait
yields anassertion that any postcondition it provides implies the postcondition of its parent trait, and anassertion that any precondition it provides is implied by the precondition of its parent trait.
Other assertions:
- Every value whose type is assigned to asubset type yields anassertion that it satisfies the subset type constraint.
- Every non-emptysubset type yields anassertion that its witness satisfies the constraint.
- EveryAssign-such-that operator
x:|P(x)
yields anassertion thatexistsx::P(x)
. In casex:|P(x);Body(x)
appears in an expression andx
is non-ghost, it also yieldsforallx,y|P(x)&&P(y)::Body(x)==Body(y)
. - Every recursive function yields anassertion thatit terminates.
- Everymatch expression oralternative if statement yields anassertion that all cases are covered.
- Every call to a function or method with a
requires
clause yieldsone assertion per requires clause21(special cases such as sequence indexing come with a specialrequires
clause that the index is within bounds).
Specification assertions:
- Any explicit
assert
statement isan assertion21. - A consecutive pair of lines in a
calc
statement formsan assertion that the expressions are related according to the common operator. - Every
ensures
clause yields anassertion at the end of the method and on every return, and onforall
statements. - Every
invariant
clause yields anassertion that it holds before the loop and anassertion that it holds at the end of the loop. - Every
decreases
clause yields anassertion at either a call site or at the end of a while loop. - Every
yieldensures
clause on aniterator yieldsassertions that the clause holds at every yielding point. - Every
yieldrequires
clause on aniterator yieldsassertions that the clause holds at every point when the iterator is called.
It is useful to mentally visualize all these assertions as a list that roughly follows the order in the code,except forensures
ordecreases
that generate assertions that seem earlier in the code but, for verification purposes, would be placed later.In this list, each assertion depends on other assertions, statements and expressions that appear earlier in the control flow22.
The fundamental unit of verification indafny
is anassertion batch, which consists of one or more assertions from this “list”, along with all the remaining assertions turned into assumptions. To reduce overhead, by defaultdafny
collects all the assertions in the body of a given method into a single assertion batch that it sends to the verifier, which tries to prove it correct.
- If the verifier says it is correct,17 it means that all the assertions hold.
- If the verifier returns a counterexample, this counterexample is used to determine both the failing assertion and the failing path.In order to retrieve additional failing assertions,
dafny
will again query the verifier after turning previously failed assertions into assumptions.2324 - If the verifier returns
unknown
or times out, or even preemptively for difficult assertions or to reduce the chance that the verifier will ‘be confused’ by the many assertions in a large batch,dafny
may partition the assertions into smaller batches25. An extreme case is the use of the/vcsSplitOnEveryAssert
command-line option or the{:isolate_assertions}
attribute, which causesdafny
to make one batch for each assertion.
13.7.3.1. Controlling assertion batches
When Dafny verifies a symbol, such as a method, a function or a constant with a subset type, that verification may contain multiple assertions. A symbol is generally verified the fastest when all assertions it in are verified together, in what we call a single ‘assertion batch’. However, is it possible to split verification of a symbol into multiple batches, and doing so makes the individual batches simpler, which can lead to less brittle verification behavior. Dafny contains several attributes that allow you to customize how verification is split into batches.
Firstly, you can instruct Dafny to verify individual assertions in separate batches. You can place the{:isolate}
attribute on a single assertion to place it in a separate batch, or you can place{:isolate_assertions}
on a symbol, such as a function or method declaration, to place all assertions in it into separate batches. The CLI option--isolate-assertions
will place all assertions into separate batches for all symbols.{:isolate}
can be used onassert
,return
andcontinue
statements. When placed on areturn
statement, it will verify the postconditions for all paths leading to thatreturn
in a separate batch. When placed on acontinue
, it will verify the loop invariants for all paths leading to thatcontinue
in a separate batch.
Given an assertion that is placed into a separate batch, you can then further simplify the verification of this assertion by placing each control flow path that leads to this assertion into a separate batch. You can do this using the attribute{:isolate"paths"}
.
13.7.4. Command-line options and other attributes to control verification
There are many great options that control various aspects of verifying dafny programs. Here we mention only a few:
- Control of output:
/dprint
,/rprint
,/stats
,/compileVerbose
- Whether to print warnings:
/proverWarnings
- Control of time:
/timeLimit
- Control of resources:
/rLimit
and{:rlimit}
- Control of the prover used:
/prover
- Control of how many times tounroll functions:
{:fuel}
You can search for them inthis file as some of them are still documented in raw text format.
13.7.5. Analyzing proof dependencies
When Dafny successfully verifies a particular definition, it can ask thesolver for information about what parts of the program were actuallyused in completing the proof. The program components that canpotentially form part of a proof include:
assert
statements (and the implicit assumption that they hold in subsequent code),- implicit assertions (such as array or sequence bounds checks),
assume
statements,ensures
clauses,requires
clauses,- function definitions,
- method calls, and
- assignment statements.
Understanding what portions of the program the proof depended on canhelp identify mistakes, and to better understand the structure of yourproof (which can help when optimizing it, among other things). Inparticular, there are two key dependency structures that tend toindicate mistakes, both focused on what parts of the program werenotincluded in the proof.
Redundant assumptions. In some cases, a proof can be completed withoutthe need of certain
assume
statements orrequires
clauses. Thissituation might represent a mistake, and when the mistake is correctedthose program elements may become required. However, they may alsosimply be redundant, and the program will become simpler if they’reremoved. Dafny will report assumptions of this form when verifyingwith the flag--warn-redundant-assumptions
. Note thatassert
statements may be warned about, as well, indicating that the factproved by the assertion wasn’t needed to prove anything else in theprogram.Contradictory assumptions. If the combination of all assumptions inscope at a particular program point is contradictory, anything can beproved at that point. This indicates the serious situation that,unless done on purpose in a proof by contradiction, your proof may beentirely vacuous. It therefore may not say what you intended, givingyou a false sense of confidence. The
--warn-contradictory-assumptions
flag instructs Dafny to warn aboutany assertion that was proved through the use of contradictionsbetween assumptions. If a particularassert
statement is part of anintentional proof by contradiction, annotating it with the{:contradiction}
attribute will silence this warning.
These options can be specified indfyconfig.toml
, and this is typically the most convenient way to use them with the IDE.
More detailed information is available using either the--log-formattext
or--verification-coverage-report
option todafnyverify
. The former willinclude a list of proof dependencies (including source location anddescription) alongside every assertion batch in the generated logwhenever one of the two warning options above is also included. Thelatter will produce a highlighted HTML version of your source code, inthe same format used bydafnytest--coverage-report
anddafnygenerate-tests--verification-coverage-report
,indicating which parts of the program were used, not used, or partlyused in the verification of the entire program.
13.7.6. Debugging brittle verification
When evolving a Dafny codebase, it can sometimes occur that a proofobligation succeeds at first only for the prover to time out or report apotential error after minor, valid changes. We refer to such a proofobligation asbrittle. This is ultimately due to decidabilitylimitations in the form of automated reasoning that Dafny uses. The Z3SMT solver that Dafny depends on attempts to efficiently search forproofs, but does so using both incomplete heuristics and a degree ofrandomness, with the result that it can sometimes fail to find a proofeven when one exists (or continue searching forever).
Dafny provides some features to mitigate this issue, primarily focusedon early detection. The philosophy is that, if Dafny programmers arealerted to proofs that show early signs of brittleness, before they areobviously so, they can refactor the proofs to make them less brittlebefore further development becomes difficult.
The mechanism for early detection focuses on measuring the resourcesused to discharge a proof obligation (either using duration or a moredeterministic “resource count” metric available from Z3). Dafny canre-run a given proof attempt multiple times after automatically makingminor changes to the structure of the input or to the random choicesmade by the solver. If the resources used during these attempts (or theability to find a proof at all) vary widely, we use this as a proxymetric indicating that the proof may be brittle.
13.7.6.1. Measuring proof brittleness
To measure the brittleness of your proofs, start by using thedafnymeasure-complexity
command with the--iterationsN
flag to instructDafny to attempt each proof goalN
times, using a different randomseed each time. The random seed used for each attempt is derived fromthe global random seedS
specified with-randomSeed:S
, whichdefaults to0
. The random seed affects the structure of the SMTqueries sent to the solver, changing the ordering of SMT commands, thevariable names used, and the random seed the solver itself uses whenmaking decisions that can be arbitrary.
For most use cases, it also makes sense to specify the--log-formatcsv
flag, to log verification cost statistics to aCSV file. By default, the resulting CSV files will be created in theTestResults
folder of the current directory.
Once Dafny has completed, thedafny-reportgenerator
tool is a convenient way to process the output. It allows you to specifyseveral limits on statistics computed from the elapsed time or solverresource use of each proof goal, returning an error code when it detectsviolations of these limits. You can find documentation on the full setof options fordafny-reportgenerator
in itsREADME.md
file.
In general, we recommend something like the following:
dafny-reportgenerator--max-resource-cv-pct 10 TestResults/*.csv
This bounds thecoefficient ofvariation ofthe solver resource count at 10% (0.10). We recommend a limit of lessthan 20%, perhaps even as low as 5%. However, when beginning to analyzea new project, it may be necessary to set limits as high as a fewhundred percent and incrementally ratchet down the limit over time.
When first analyzing proof brittleness, you may also find that certain proofgoals succeed on some iterations and fail on others. If your aim isfirst to ensure that brittleness doesn’t worsen and then to startreducing it, integratingdafny-reportgenerator
into CI and using the--allow-different-outcomes
flag may be appropriate. Then, once you’veimproved brittleness sufficiently, you can likely remove that flag (andlikely have significantly lower limits on other metrics).
13.7.6.2. Improving proof brittleness
Reducing proof brittleness is typically closely related to improvingperformance overall. As such,techniques for debugging slowverification are typically useful fordebugging brittle proofs, as well. See also theverification optimizationguide.
13.8. Compilation
Thedafny
tool can compile a Dafny program to one of several target languages. Details and idiosyncrasies of eachof these are described in the following subsections. In general note the following:
- The compiled code originating from
dafny
can be combined with other source and binary code, but only thedafny
-originated code is verified. - Output file or folder names can be set using
--output
. - Code generated by
dafny
requires a Dafny-specific runtime library. By default the runtime is included in the generated code. However fordafnytranslate
it is notincluded by default and must be explicitly requested using--include-runtime
. All runtime libraries are part of the Binary (./DafnyRuntime.*
) and Source (./Source/DafnyRuntime/DafnyRuntime.*
) releases. - Names in Dafny are written out as names in the target language. In some cases this can result in naming conflicts. Thus if a Dafny program is intended to be compiled to a target language X, you should avoid using Dafny identifiers that are not legal identifiers in X or that conflict with reserved words in X.
To be compilable to an executable program, a Dafny program must contain aMain
entry point, as describedhere.
13.8.1.1 Built-in declarations
Dafny includes several built-in types such as tuples, arrays, arrows (functions), and thenat
subset type.The supporting target language code for these declarations could be emitted on-demand,but these could then become multiple definitions of the same symbols when compiling multiple components separately. Instead, all such built-ins up to a pre-configured maximum size are included in most of the runtime libraries.This means that when compiling to certain target languages, the use of such built-ins above these maximum sizes,such as tuples with more than 20 elements, is not supported.See theSupported features by target language tablefor the details on these limits.
13.8.2.extern
declarations
A Dafny declaration can be marked with the{:extern}
attribute toindicate that it refers to an external definition that is alreadypresent in the language that the Dafny code will be compiled to (or willbe present by the time the final target-language program is compiled orrun).
Because the{:extern}
attribute controls interaction with code writtenin one of many languages, it has some language-specific behavior,documented in the following sections. However, some aspects aretarget-language independent and documented here.
The attribute can also take several forms, each defining a differentrelationship between a Dafny name and a target language name. In theform{:extern}
, the name of the external definition isassumed to be the name of the Dafny declaration after sometarget-specific name mangling. However, because naming conventions (andthe set of allowed identifiers) vary between languages, Dafny allowsadditional forms for the{:extern}
attribute.
The form{:extern<s1>}
instructsdafny
to compile references to mostdeclarations using the names1
instead of the Dafny name. Forabstracttypes, however,s1
is sometimes used as a hint asto how to declare that type when compiling. This hint is interpreteddifferently by each compiler.
Finally, the form{:extern<s1>,<s2>}
instructsdafny
to uses2
asthe direct name of the declaration.dafny
will typically use acombination ofs1
ands2
, such ass1.s2
, to reference thedeclaration. It may also be the case that one of the arguments is simplyignored, depending on the target language.
The recommended style is to prefer{:extern}
when possible, and usesimilar names across languages. This is usually feasible becauseexisting external code is expected to have the same interface as thecode thatdafny
would generate for a declaration of that form. Becausemany Dafny types compile down to custom types defined in the Dafnyruntime library, it’s typically necessary to write wrappers by hand thatencapsulate existing external code using a compatible interface, andthose wrappers can have names chosen for compatibility. For example,retrieving the list of command line arguments when compiling to C#requires a wrapper such as the following:
usingicharseq=Dafny.ISequence<char>;usingcharseq=Dafny.Sequence<char>;namespaceExterns_Compile{publicpartialclass__default{publicstaticDafny.ISequence<icharseq>GetCommandLineArgs(){vardafnyArgs=Environment.GetCommandLineArgs().Select(charseq.FromString);returnDafny.Sequence<icharseq>.FromArray(dafnyArgs.ToArray());}}
This serves as an example of implementing an extern,but was only necessary to retrieve command line arguments historically,asdafny
now supports capturing these arguments via a main methodthat accepts aseq<string>
(see the section on theMain method).
Note thatdafny
does not check the arguments to{:extern}
, so it isthe user’s responsibility to ensure that the provided names result incode that is well-formed in the target language.
Also note that the interface the external code needs to implementmay be affected by compilation flags. In this case, if--unicode-char:true
is provided,dafny
will compile itschar
type to theDafny.Rune
C# type instead, so the references to the C# typechar
abovewould need to be changed accordingly. The reference tocharseq.FromString
would in turn need to be changed tocharseq.UnicodeFromString
toreturn the correct type.
Most declarations, including those for modules, classes, traits, membervariables, constructors, methods, function methods, and abstract types,can be marked with{:extern}
.
Marking a module with{:extern}
indicates that the declarationscontained within can be found within the given module, namespace, package, orsimilar construct within the target language. Some members of the Dafnymodule may contain definitions, in which case code for those definitionswill be generated. Whether this results in valid target code may dependon some target language support for something resembling “partial”modules, where different subsets of the contents are defined indifferent places.
The story for classes is similar. Code for a class will be generatedif any of its members are not{:extern}
. Depending on the targetlanguage, making either all or none of the members{:extern}
may bethe only options that result in valid target code. Traits with{:extern}
can refer to existing traits or interfaces in the targetlanguage, or can refer to the interfaces of existing classes.
Member variables marked with{:extern}
refer to fields or propertiesin existing target-language code. Constructors, methods, and functionsrefer to the equivalent concepts in the target language. Theycan have contracts, which are then assumed to hold for the existingtarget-language code. They can also have bodies, but the bodies will notbe compiled in the presence of the{:extern}
attribute. Bodies canstill be used for reasoning, however, so may be valuable in some cases,especially for function methods.
Types marked with{:extern}
must be opaque. The name argument, if any,usually refers to the type name in the target language, but somecompilers treat it differently.
Detailed description of thedafnybuild
anddafnyrun
commands and the--input
option (needed whendafnyrun
has more than one input file)is containedin the section on command-line structure.
13.8.3. Replaceable modules
To enable easily customising runtime behavior across an entire Dafny program, Dafny has placeholder modules. Here follows an example:
replaceablemoduleFoo{methodBar()returns(i:int)ensuresi>=2}methodMain(){varx:=Foo.Bar();printx;}// At this point, the program can be verified but not run.moduleConcreteFooreplacesFoo{methodBar()returns(i:int){return3;// Main will print 3.}}// ConcreteFoo can be swapped out for different replacements of Foo, to customize runtime behavior.
When replacing a replaceable module, the same rules apply as when refining an abstract module. However, unlike an abstract module, a placeholder module can be used as if it is a concrete module. When executing code, using for exampledafnyrun
ordafnytranslate
, any program that contains a placeholder module must also contain a replacement of this placeholder. When usingdafnyverify
, placeholder modules do not have to be replaced.
Replaceable modules are particularly useful for defining behavior that depends on which target language Dafny is translated to.
13.8.4. C#
For a simple Dafny-only program, the translation step converts aA.dfy
file intoA.cs
;the build step then produces aA.dll
, which can be used as a library or as an executable (run usingdotnetA.dll
).
It is also possible to run the dafny files as part of acsproj
project, with these steps:
- create a dotnet project file with the command
dotnetnewconsole
- delete the
Program.cs
file - build the dafny program:
dafnybuildA.dfy
- run the built program
dotnetA.dll
The last two steps can be combined:dafnyrunA.dfy
Note that all input.dfy
files and any needed runtime library code are combined into a single.cs
file, which is then compiled bydotnet
to a.dll
.
Examples of how to integrate C# libraries and source code with Dafny source codeare contained inthis separate document.
13.8.5. Java
The Dafny-to-Java compiler translation phase writes out the translated files of a fileA.dfy
to a directoryA-java
. The build phase writes out a library or executable jar file.The--output
option (-out
in the legacy CLI) can be used to choose adifferent jar file path and name and correspondingly different directory for .java and .class files.
The compiler produces a single wrapper method that then calls classes in relevant other.java
files. Because Java files must be named consistentwith the class they contain, but Dafny files do not, there may be no relationbetween the Java file names and the Dafny file names.However, the wrapper class that contains the Javamain
method is named forthe first.dfy
file on the command-line.
The step of compiling Java files (usingjavac
) requires the Dafny runtime library. That library is automatically included if dafny is doing the compilation,but not if dafny is only doing translation.
Examples of how to integrate Java source code and libraries with Dafny sourceare contained inthis separate document.
13.8.6. Javascript
The Dafny-to-Javascript compiler translates all the given.dfy
files into a single.js
file, which can then be run usingnode
. (Javascript has no compilation step). The build and run steps are simply
dafnybuild--target:jsA.dfy
nodeA.js
Or, in one step,
dafnyrunA.dfy
Examples of how to integrate Javascript libraries and source code with Dafny sourceare contained inthis separate document.
13.8.7. Go
The Dafny-to-Go compiler translates all the given.dfy
files into a single.go
file inA-go/src/A.go
; the output folder can be specified with the-out
option. For an input fileA.dfy
the default output folder isA-go
. Then, Dafny compiles this program and creates anA.exe
executable in the same folder asA.dfy
.Some system runtime code is also placed inA-go/src
.The build and run steps are
dafnybuild--target:goA.dfy
./A
The uncompiled code can be compiled and run bygo
itself using
(cdA-go;GO111MODULE=autoGOPATH=`pwd`gorunsrc/A.go)
The one-step process is
dafnyrun--target:goA.dfy
TheGO111MODULE
variable is used because Dafny translates to pre-module Go code.When the implementation changes to current Go, the above command-line willchange, though the./A
alternative will still be supported.
Examples of how to integrate Go source code and libraries with Dafny sourceare contained inthis separate document.
13.8.8. Python
The Dafny-to-Python compiler is still under development. However, simpleDafny programs can be built and run as follows. The Dafny-to-Pythoncompiler translates the.dfy
files into a single.py
file along with supporting runtime library code, all placed in the output location (A-py
for an input file A.dfy, by default).
The build and run steps are
dafnybuild--target:pyA.dfy
pythonA-py/A.py
In one step:
dafnyrun--target:pyA.dfy
Examples of how to integrate Python libraries and source code with Dafny sourceare contained inthis separate document.
13.8.9. C++
The C++ backend was written assuming that it would primarily support writingC/C++ style code in Dafny, which leads to some limitations in the currentimplementation.
- The C++ compiler does not support BigIntegers, so do not use
int
, or raw instances ofarr.Length
, or sequence length, etc. in executable code. You can however,usearr.Lengthasuint64
if you can prove your array is an appropriatesize. The compiler will report inappropriate integer use. - The C++ compiler does not support advanced Dafny features like traits or coinductivetypes.
- There is very limited support for higher order functions even for array initialization. Useextern definitions like newArrayFill (seeextern.dfy) orsimilar. See also the example in [
functions.dfy
](https://github.com/dafny-lang/dafny/blob/master/Test/c++/functions.dfy). - The current backend also assumes the use of C++17 in order to cleanly andperformantly implement datatypes.
13.8.10. Supported features by target language
Some Dafny features are not supported by every target language.The table below shows which features are supported by each backend.An empty cell indicates that a feature is not supported,while an X indicates that it is.
13.9. Dafny Command Line Options
There are many command-line options to thedafny
tool.The most current documentation of the options is within the tool itself,using the-?
or--help
or-h
options.
Remember that options are typically stated with either a leading--
.
Legacy options begin with either ‘-‘ or ‘/’; however they are beingmigrated to the POSIX-compliant--
form as needed.
13.9.1. Help and version information
These options emit general information about commands, options and attributes.When present, the dafny program will terminates after emitting the requested informationbut without processing any files.
--help
,-h
- shows the various commands (which have help information under them asdafny<command>-h
--version
- show the version of the build
Legacy options:
-?
- print out the legacy list of command-line optionsand terminate. All of these options are also described in this andthe following sections.-attrHelp
- print out the current list of supported attributedeclarations and terminate.-env:<n>
- print the command-line arguments supplied to the program.The value of<n>
can be one of the following.0
- never print command-line arguments.1
(default) - print them to Boogie (.bpl
) files and prover logs.2
- operate like with option1
but also print to standard output.
-wait
- wait for the user to pressEnter
before terminating after a successful execution.
13.9.2. Controlling input
These options control how Dafny processes its input.
-stdin
- read standard input and treat it as Dafny source code,instead of reading from a file.--library:<files>
- treat the given files aslibrary code, namely, skipthese files (and any files recursively included) during verification;the value may be a comma-separated-list of files or folders; folders are expanded intoa list of all .dfy files contained, recursively, in those folders--prelude:<file>
(was-dprelude
) - select an alternative Dafny prelude file. Thisfile contains Boogie definitions (including many axioms) required bythe translator from Dafny to Boogie. Using an alternative prelude isprimarily useful if you’re extending the Dafny language or changinghow Dafny constructs are modeled. The default prelude ishere.
13.9.3. Controlling plugins
Dafny has a plugin capability. A plugin has access to an AST of the dafny input filesafter all parsing and resolution are performed (but not verification)and also to the command-line options.
This facility is stillexperimental and very much in flux, particularly the form of the AST. The best guides to writing a new plugin are(a) the documentation inthe section of this manual on plugins and (b) example plugins in thesrc/Tools
folder of thedafny-lang/compiler-bootstrap
repo.
The value of the option--plugin
is a path to a dotnet dll that containsthe compiled plugin.
13.9.4. Controlling output
These options instruct Dafny to print various information about yourprogram during processing, including variations of the original sourcecode (which can be helpful for debugging).
--use-basename-for-filename
- when enabled, just the filename without the directory path is used in error messages; this make error message shorter and not tied to the local environment (which is a help in testing)--output
,-o
- location of output files [translate, build]--show-snippets
- include with an error message some of the source code textin the neighborhood of the error; the error location (file, line, column) is always given--solver-log<file>
- [verification only] the file in which to place the SMT text sent to the solver--log-format<configuration>
- [verification only] (was-verificationLogger:<configurationstring>
)log verificationresults to the given test result logger. The currently supportedloggers aretrx
,csv
, andtext
. These are the XML-based formatscommonly used for test results for .NET languages, a custom CSVschema, and a textual format meant for human consumption,respectively. You can provide configuration using the same stringformat as when using the--logger
option for dotnet test, such as:-verificationLogger:trx;LogFileName=<...>
The exact mapping of verification concepts to these formats isexperimental and subject to change!
The
trx
andcsv
loggers automatically choose an output file nameby default, and print the name of this file to the console. Thetext
logger prints its output to the console by default, but can sendoutput to a file given theLogFileName
option.The
text
logger also includes a more detailed breakdown of whatassertions appear in each assertion batch. When combined with the-vcsSplitOnEveryAssert
option, it will provide approximate time andresource use costs for each assertion, allowing identification ofespecially expensive assertions.
Legacy options:
-stats
- print various statistics about the Dafny files supplied onthe command line. The statistics include the number of totalfunctions, recursive functions, total methods, ghost methods, classes,and modules. They also include the maximum call graph width and themaximum module height.-dprint:<file>
- print the Dafny program after parsing (use-
for<file>
to print to the console).-rprint:<file>
- print the Dafny program after type resolution (use-
for<file>
to print to the console).-printMode:<Everything|DllEmbed|NoIncludes|NoGhost>
- select what toinclude in the output requested by-dprint
or-rprint
. Theargument can be one of the following.Everything
(default) - include everything.DllEmbed
- print the source that will be included in a compiled DLL.NoIncludes
- disable printing of methods incorporated via theinclude mechanism that have the{:verifyfalse}
attribute, as wellas datatypes and fields included from other files.NoGhost
- disables printing of functions, ghost methods, and proofstatements in implementation methods. Also disable anythingNoIncludes
disables.
-printIncludes:<None|Immediate|Transitive>
- select what informationfrom included files to incorporate into the output selected by-dprint
or-rprint
. The argument can be one of the following.None
(default) - don’t print anything from included files.Immediate
- print files directly included by files specified onthe command line. Exit after printing.Transitive
- print files transitively included by files specifiedon the command line. Exit after printing.
-view:<view1,view2>
- this option limits what is printed by /rprintfor a module to the names that are part of the given export set;the option argument is a comma-separated list of fully-qualified exportset names.-funcCallGraph
- print out the function call graph. Each line hasthe formatfunc,mod=callee*
, wherefunc
is the name of a function,mod
is the name of its containing module, andcallee*
is aspace-separated list of the functions thatfunc
calls.--show-snippets
(was-showSnippets:<n>
) - show a source code snippet for each Dafnymessage. The legacy option was-showSnippets
with values 0 and 1 for false and true.-printTooltips
- dump additional positional information (displayedas mouse-over tooltips by LSP clients) to standard output asInfo
messages.-pmtrace
- print debugging information from the pattern-matchcompiler.-titrace
- print debugging information during the type inferenceprocess.-diagnosticsFormat:<text|json>
- control how to report errors, warnings, and infomessages.<fmt>
may be one of the following:text
(default): Report diagnostics in human-readable format.json
: Report diagnostics in JSON format, one object per diagnostic, onediagnostic per line. Info-level messages are only included with-printTooltips
. End positions are only included with-showSnippets:1
.Diagnostics are the following format (but without newlines):{"location":{"filename":"xyz.dfy","range":{//Startand(optional)endofdiagnostic"start":{"pos":83,//0-basedcharacteroffsetininput"line":6,//1-basedlinenumber"character":0//0-basedcolumnnumber},"end":{"pos":86,"line":6,"character":3}}},"severity":2,//1:error;2:warning;4:info"message":"module-level const declarations are always non-instance ...","source":"Parser","relatedInformation":[//Additionalmessages,ifany{"location":{...},//Likeabove"message":"...",}]}
13.9.5. Controlling language features
These options allow some Dafny language features to be enabled ordisabled. Some of these options exist for backward compatibility witholder versions of Dafny.
--default-function-opacity:<transparent|autoRevealDependencies|opaque>
- Change the default opacity of functions.transparent
(default) means functions are transparent, can be manually made opaque and then revealed.autoRevealDependencies
makes all functions not explicitly labelled as opaque to be opaque but reveals them automatically in scopes which do not have{:autoRevealDependenciesfalse}
.opaque
means functions are always opaque so the opaque keyword is not needed, and functions must be revealed everywhere needed for a proof.
--function-syntax
(value ‘3’ or ‘4’) - permits a choice of using the Dafny 3 syntax (function
andfunctionmethod
)or the Dafny 4 syntax (ghostfunction
andfunction
)--quantifier-syntax
(value ‘3’ or ‘4’) - permits a choice between the Dafny 3 and Dafny 4 syntax for quantifiers--unicode-char
- if false, thechar
type represents any UTF-16 code unit,that is, any 16-bit value, including surrogate code points andallows\uXXXX
escapes in string and character literals.If true,char
represents any Unicode scalar value,that is, any Unicode code point excluding surrogates andallows\U{X..X}
escapes in string and character literals. The default is false for Dafny version 3 and true for version 4.The legacy option was-unicodeChar:<n>
with values 0 and 1 forfalse and true above.
Legacy options:
-noIncludes
- ignoreinclude
directives in the program.-noExterns
- ignoreextern
attributes in the program.
--function-syntax:<version>
(was-functionSyntax:<version>
) - select what function syntax torecognize. The syntax for functions is changing from Dafny version 3to version 4. This switch gives early access to the new syntax, andalso provides a mode to help with migration. The valid argumentsinclude the following.3
(default) - compiled functions are writtenfunctionmethod
andpredicatemethod
. Ghost functions are writtenfunction
andpredicate
.4
- compiled functions are writtenfunction
andpredicate
.Ghost functions are writtenghostfunction
andghostpredicate
.migration3to4
- compiled functions are writtenfunctionmethod
andpredicatemethod
. Ghost functions are writtenghostfunction
andghostpredicate
. To migrate from version 3 to version 4, usethis flag on your version 3 program to flag all occurrences offunction
andpredicate
as parsing errors. These are ghostfunctions, so change those into the new syntaxghostfunction
andghostpredicate
. Then, start using-functionSyntax:4
. This willflag all occurrences offunctionmethod
andpredicatemethod
asparsing errors. So, change those to justfunction
andpredicate
.As a result, your program will use version 4 syntax and have thesame meaning as your previous version 3 program.experimentalDefaultGhost
- likemigration3to4
, but allowfunction
andpredicate
as alternatives to declaring ghostfunctions and predicates, respectivelyexperimentalDefaultCompiled
- likemigration3to4
, but allowfunction
andpredicate
as alternatives to declaring compiledfunctions and predicates, respectivelyexperimentalPredicateAlwaysGhost
- compiled functions are writtenfunction
. Ghost functions are writtenghostfunction
. Predicatesare always ghost and are writtenpredicate
.
This option can also be set locally (at the module level) using the
:options
attribute:
module{:options"--function-syntax:4"}M{predicateCompiledPredicate(){true}}
--quantifier-syntax:<version>
(was-quantifierSyntax:<version>
) - select what quantifier syntax to recognize. The syntax for quantification domains is changing from Dafny version 3 to version 4, more specifically where quantifier ranges (|<Range>
) are allowed. This switch gives early access to the new syntax.3
(default) - Ranges are only allowed after all quantified variables are declared. (e.g.setx,y|0<=x<|s|&&yins[x]&&0<=y::y
)4
- Ranges are allowed after each quantified variable declaration. (e.g.setx|0<=x<|s|,y<-s[x]|0<=y::y
)
Note that quantifier variable domains (
<-<Domain>
) are available in both syntax versions.-disableScopes
- treat all export sets asexportreveal*
to never hide function bodies or type definitions during translation.-allowsGlobals
- allow the implicit class_default
to containfields, instance functions, and instance methods. These class membersare declared at the module scope, outside of explicit classes. Thiscommand-line option is provided to simplify a transition from thebehavior in the language prior to version 1.9.3, from which pointonward all functions and methods declared at the module scope areimplicitly static and field declarations are not allowed at themodule scope.
13.9.6. Controlling warnings
These options control what warnings Dafny produces, and whether to treatwarnings as errors.
--warn-as-errors
(was-warningsAsErrors
) - treat warnings as errors.--warn-shadowing
(was-warnShadowing
) - emit a warning if the name of a declared variable caused another variable to be shadowed.--warn-missing-constructor-parentheses
- warn if a constructor name in a pattern might be misinterpreted
Legacy options
-deprecation:<n>
- control warnings about deprecated features. Thevalue of<n>
can be any of the following.0
- don’t issue any warnings.1
(default) - issue warnings.2
- issue warnings and advise about alternate syntax.
13.9.7. Controlling verification
These options control how Dafny verifies the input program, includinghow much it verifies, what techniques it uses to perform verification,and what information it produces about the verification process.
--no-verify
- turns off verification (for translate, build, run commands)--verify-included-files
(was-verifyAllModules
) - verify modules that come from include directives.By default, Dafny only verifies files explicitly listed on the commandline: if
a.dfy
includesb.dfy
, a call toDafnya.dfy
will detectand report verification errors froma.dfy
but not fromb.dfy
.With this option, Dafny will instead verify everything: all inputmodules and all their transitive dependencies. This way
Dafnya.dfy
will verifya.dfy
and all files that it includes (hereb.dfy
), aswell all files that these files include, etc.Running Dafny with this option on the file containing yourmain result is a good way to ensure that all its dependencies verify.
--track-print-effects
- If true, a compiled method, constructor, or iterator is allowed to have print effects only if it is marked with . (default false) The legacy option was-trackPrintEffects:<n>
) with values 0 or 1 for false and true.--relax-definite-assignment
- control the rules governing definiteassignment, the property that every variable is eventually assigned avalue before it is used.- if false (default), enforce definite-assignment for all non-yield-parametervariables and fields, regardless of their types
- if false and
--enforce-determinism
is true, then also performs checks in the compiler that no nondeterministic statements are used - if true, enforce definite-assignment rules for compiledvariables and fields whose types do not support auto-initializationand for ghost variables and fields whose type is possibly empty.
--disable-nonlinear-arithmetic
(was-noNLarith
) - reduce Z3’s knowledge of non-linear arithmetic (theoperators*
,/
, and%
). Enabling this option will typicallyrequire more manual work to complete proofs (by explicitly applyinglemmas about non-linear operators), but will also result in morepredictable behavior, since Z3 can sometimes get stuck down anunproductive path while attempting to prove things about thoseoperators. (This option will perhaps be replaced by-arith
in thefuture. For now, it takes precedence over-arith
.)The behavior of
disable-nonlinear-arithmetic
can be turned on and off on a per-module basis by placing the attribute{:disable-nonlinear-arithmetic}
after the module keyword.The attribute optionally takes the valuefalse
to enable nonlinear arithmetic.--manual-lemma-induction
- disables automatic inducntion for lemmas--isolate-assertions
- verify assertions individually--extract-counterexample
- if verification fails, report a potentialcounterexample as a set of assumptions that can be inserted into the code.Note that Danfy cannot guarantee that the counterexampleit reports provably violates the assertion or that the assumptions are notmutually inconsistent (see17), so this output should be inspected manually and treated as a hint.
Controlling the proof engine:
--cores:<n>
- sets the number or percent of the available cores to be used for verification--verification-time-limit<seconds>
- imposes a time limit on each verification attempt--verification-error-limit<number>
- limits the number of verification errors reported (0 is no limit)--resource-limit
- states a resource limit (to be used by the backend solver)
Legacy options:
-dafnyVerify:<n>
[discouraged] - turn verification of the program on or off. Thevalue of<n>
can be any of the following.0
- stop after type checking.1
- continue on to verification and compilation.
-separateModuleOutput
- output verification results for each moduleseparately, rather than aggregating them after they are all finished.-mimicVerificationOf:<dafnyversion>
- letdafny
attempt to mimicthe verification behavior of a previous version ofdafny
. This can beuseful during migration to a newer version ofdafny
when a Dafnyprogram has proofs, such as methods or lemmas, that are highly variable inthe sense that their verification may become slower or fail altogetherafter logically irrelevant changes are made in the verification input.Accepted versions are:
3.3
. Note that falling back on the behaviorof version 3.3 turns off features that prevent certain classes ofverification variability.-noCheating:<n>
- control whether certain assumptions are allowed.The value of<n>
can be one of the following.0
(default) - allowassume
statements and free invariants.1
- treat all assumptions asassert
statements, and drop freeinvariants.
-induction:<n>
- control the behavior of induction. The value of<n>
can be one of the following.0
- never do induction, not even when attributes request it.1
- apply induction only when attributes request it.2
- apply induction as requested (by attributes) and also forheuristically chosen quantifiers.3
- apply induction as requested, and for heuristically chosenquantifiers and lemmas.4
(default) - apply induction as requested, and for all lemmas.
-inductionHeuristic:<n>
- control the heuristics used for induction.The value of<n>
can be one of the following.0
- use the least discriminating induction heuristic (that is,lean toward applying induction more often).1
,2
,3
,4
,5
- use an intermediate heuristic, ordered asfollows as far as how discriminating they are: 0 < 1 < 2 < (3,4) < 5< 6.6
(default) - use the most discriminating induction heuristic.
-allocated:<n>
- specify defaults for where Dafny should assert andassumeallocated(x)
for various parametersx
, local variablesx
,bound variablesx
, etc. Lower<n>
may require more manualallocated(x)
annotations and thus may be more difficult to use. Thevalue of<n>
can be one of the following.0
- never assume or assertallocated(x)
by default.1
- assumeallocated(x)
only for non-ghost variables and fields.(These assumptions are free, since non-ghost variables alwayscontain allocated values at run-time.) This option may speed upverification relative to-allocated:2
.2
- assert/assumeallocated(x)
on all variables, even boundvariables in quantifiers. This option is the easiest to use for codethat uses the heap heavily.3
- (default) make frugal use of heap parameters.4
- like3
but addallocated
antecedents when ranges don’t implyallocatedness.
Warning: this option should be chosen consistently across an entireproject; it would be unsound to use different defaults for differentfiles or modules within a project. Furthermore, modes
-allocated:0
and-allocated:1
let functions depend on the allocation state, which isnot sound in general.-noAutoReq
- ignoreautoReq
attributes, and therefore do notautomatically generaterequires
clauses.-autoReqPrint:<file>
- print the requires clauses that wereautomatically generated byautoReq
to the given<file>
.-arith:<n>
- control how arithmetic is modeled during verification.This is an experimental switch, and its options may change. The valueof<n>
can be one of the following.0
- use Boogie/Z3 built-ins for all arithmetic operations.1
(default) - like0
, but introduce symbolic synonyms for*
,/
, and%
, and allow these operators to be used in triggers.2
- like1
, but also introduce symbolic synonyms for+
and-
.3
- turn off non-linear arithmetic in the SMT solver. Still useBoogie/Z3 built-in symbols for all arithmetic operations.4
- like3
, but introduce symbolic synonyms for*
,/
, and%
,and allow these operators to be used in triggers.5
- like4
, but also introduce symbolic synonyms for+
and-
.6
- like5
, and introduce axioms that distribute+
over*
.7
- like6
, and introduce facts about the associativity ofliteral arguments over*
.8
- like7
, and introduce axioms for the connection between*
,/
, and%
.9
- like8
, and introduce axioms for sign of multiplication.10
- like9
, and introduce axioms for commutativity andassociativity of*
.
-autoTriggers:<n>
- control automatic generation of{:trigger}
annotations. Seetriggers. The value of<n>
can be oneof the following.0
- do not generate{:trigger}
annotations for user-levelquantifiers.1
(default) - add a{:trigger}
annotation to each user-levelquantifier. Existing annotations are preserved.
-rewriteFocalPredicates:<n>
- control rewriting of predicates in thebody of prefix lemmas. Seethe section about nicer extreme proofs.The value of<n>
can be one of the following.0
- don’t rewrite predicates in the body of prefix lemmas.1
(default) - in the body of prefix lemmas, rewrite any use of afocal predicateP
toP#[_k-1]
.
13.9.8. Controlling compilation
These options control what code gets compiled, what target language isused, how compilation proceeds, and whether the compiled program isimmediately executed.
--target:<s>
or-t:<s>
(was-compileTarget:<s>
) - set the target programming language for thecompiler. The value of<s>
can be one of the following.cs
- C# . Produces a .dll file that can be run usingdotnet
. For example,dafnyHello.dfy
will produceHello.dll
andHello.runtimeconfig.json
. The dll can be run usingdotnetHello.dll
.go
- Go. The default output ofdafnyHello.dfy-compileTarget:go
is in theHello-go
folder. It is run usingGOPATH=`pwd`/Hello-go/GO111MODULE=autogorunHello-go/src/Hello.go
js
- Javascript. The default output ofdafnyHello.dfy-compileTarget:js
is the fileHello.js
, which can be run usingnodeHello.js
. (You must havebignumber.js
installed.)java
- Java. The default output ofdafnyHello.dfy-compileTarget:java
is in theHello-java
folder. The compiled program can be run usingjava-cpHello-java:Hello-java/DafnyRuntime.jarHello
.py
- Python. The default output ofdafnyHello.dfy-compileTarget:py
is in theHello-py
folder. The compiled program can be run usingpythonHello-py/Hello.py
, wherepython
is Python version 3.cpp
- C++. The default output ofdafnyHello.dfy-compileTarget:cpp
isHello.exe
and other files written to the current folder. The compiled program can be run using./Hello.exe
.
--input<file>
- designates files to be include in the compilation in addition to the main file indafnyrun
; these may be non-.dfy files; this option may be specified more than once--output:<file>
or-o:<file>
(was-out:<file>
) - set the name to use for compiled code files.
By default,dafny
reuses the name of the Dafny file being compiled.Compilers that generate a single file use the file name as-is (e.g. theC# backend will generate<file>.dll
and optionally<file>.cs
with-spillTargetCode
). Compilers that generate multiple files use the filename as a directory name (e.g. the Java backend will generate files indirectory<file>-java/
). Any file extension is ignored, so-out:<file>
is the same as-out:<file>.<ext>
if<file>
contains noperiods.
--include-runtime
- include the runtime library for the target language inthe generated artifacts. This is true by default for build and run, but false by default for translate. The legacy option-useRuntimeLib
had the opposite effect: when enabled, the compiled assembly referred tothe pre-builtDafnyRuntime.dll
in thecompiled assembly rather than includingDafnyRuntime.cs
in the buildprocess.
Legacy options:
-compile:<n>
- [obsolete - usedafnybuild
ordafnyrun
] control whether compilation happens. The value of<n>
can be one of the following. Note that if the program is compiled, it will be compiled to the target language determined by the-compileTarget
option, which is C# by default.0
- do not compile the program1
(default) - upon successful verification, compile the programto the target language.2
- always compile, regardless of verification success.3
- if verification is successful, compile the program (likeoption1
), and then if there is aMain
method, attempt to run theprogram.4
- always compile (like option2
), and then if there is aMain
method, attempt to run the program.
-spillTargetCode:<n>
- [obsolete - usedafnytranslate
) control whether to write out compiled code inthe target language (instead of just holding it in internal temporarymemory). The value of<n>
can be one of the following.0
(default) - don’t make any extra effort to write the textualtarget program (but still compile it, if-compile
indicates to doso).1
- write it out to the target language, if it is being compiled.2
- write the compiled program if it passes verification,regardless of the-compile
setting.3
- write the compiled program regardless of verification successand the-compile
setting.
Note that some compiler targets may (always or in some situations) writeout the textual target program as part of compilation, in which case-spillTargetCode:0
behaves the same way as-spillTargetCode:1
.
-Main:<name>
- specify the (fully-qualified) name of the method touse as the executable entry point. The default is the method with the{:main}
attribute, or else the method namedMain
.-compileVerbose:<n>
- control whether to write out compilationprogress information. The value of<n>
can be one of the following.0
- do not print any information (silent mode)1
(default) - print information such as the files being created bythe compiler
-coverage:<file>
- emit branch-coverage calls and outputs into<file>
, including a legend that gives a description of eachsource-location identifier used in the branch-coverage calls. (Use-
as<file>
to print to the console.)-optimize
- produce optimized C# code by passing the/optimize
flag to thecsc
executable.-optimizeResolution:<n>
- control optimization of method targetresolution. The value of<n>
can be one of the following.0
- resolve and translate all methods.1
- translate methods only in the call graph of the currentverification target.2
(default) - as in1
, but resolve only methods that are definedin the current verification target file, not in included files.
-testContracts:<mode>
- test certain function and method contractsat runtime. This works by generating a wrapper for each function ormethod to be tested that includes a sequence ofexpect
statementsfor each requires clause, a call to the original, and sequence ofexpect
statements for eachensures
clause. This is particularlyuseful for code marked with the{:extern}
attribute and implementedin the target language instead of Dafny. Having runtime checks of thecontracts on such code makes it possible to gather evidence that thetarget-language code satisfies the assumptions made of it during Dafnyverification through mechanisms ranging from manual tests throughfuzzing to full verification. For the latter two use cases, havingchecks forrequires
clauses can be helpful, even if the Dafnycalling code will never violate them.The
<mode>
parameter can currently be one of the following.Externs
- insert dynamic checks when calling any function ormethod marked with the{:extern}
attribute, wherever the calloccurs.TestedExterns
- insert dynamic checks when calling any function ormethod marked with the{:extern}
attribute directly from afunction or method marked with the{:test}
attribute.
13.9.9. Controlling Boogie
Dafny builds on top of Boogie, a general-purpose intermediate languagefor verification. Options supported by Boogie on its own are alsosupported by Dafny. Some of the Boogie options most relevant to Dafnyusers include the following. We use the term “procedure” below to referto a Dafny function, lemma, method, or predicate, following Boogieterminology.
--solver-path
- specifies a custom SMT solver to use--solver-plugin
- specifies a plugin to use as the SMT solver, instead of an external pdafny translaterocess--boogie
- arguments to send to boogie
Legacy options:
-proc:<name>
- verify only the procedure named<name>
. The namecan include*
to indicate arbitrary sequences of characters.-trace
- print extra information during verification, includingtiming, resource use, and outcome for each procedure incrementally, asverification finishes.-randomSeed:<n>
- turn on randomization of the input that Boogiepasses to the SMT solver and turn on randomization in the SMT solveritself.Certain Boogie inputs cause proof variability in the sense that changes to theinput that preserve its meaning may cause the output to change. The
-randomSeed
option simulates meaning-preserving changes to theinput without requiring the user to actually make those changes.The
-randomSeed
option is implemented by renaming variables andreordering declarations in the input, and by settingsolver options that have similar effects.-randomSeedIterations:<n>
- attempt to prove each VC<n>
timeswith<n>
random seeds. If-randomSeed
has been provided, eachproof attempt will use a new random seed derived from this originalseed. If not, it will implicitly use-randomSeed:0
to ensure adifference between iterations. This option can be very useful foridentifying input programs for which verification is highly variable. If theverification times or solver resource counts associated with eachproof attempt vary widely for a given procedure, small changes to thatprocedure might be more likely to cause proofs to fail in the future.-vcsSplitOnEveryAssert
- prove each (explicit or implicit) assertionin each procedure separately. See also the attribute{:isolate_assertions}
forrestricting this option on specific procedures. By default, Boogieattempts to prove that every assertion in a given procedure holds allat once, in a single query to an SMT solver. This usually performswell, but sometimes causes the solver to take longer. If a proof thatyou believe should succeed is timing out, using this option cansometimes help.-timeLimit:<n>
- spend at most<n>
seconds attempting to prove anysingle SMT query. This setting can also be set per method using theattribute{:timeLimitn}
.-rlimit:<n>
- set the maximum solver resource count to use whileproving a single SMT query. This can be a more deterministic approachthan setting a time limit. To choose an appropriate value, pleaserefer to the documentation of the attribute{:rlimit}
that can be applied per procedure.-print:<file>
- print the translation of the Dafny file to a Boogie file.
If you have Boogie installed locally, you can run the printed Boogie file with the following script:
DOTNET=$(which dotnet)BOOGIE_ROOT="path/to/boogie/Source"BOOGIE="$BOOGIE_ROOT/BoogieDriver/bin/Debug/net6.0/BoogieDriver.dll"if[[!-x"$DOTNET"]];thenecho"Error: Dafny requires .NET Core to run on non-Windows systems."exit1fi#Uncomment if you prefer to use the executable instead of the DLL#BOOGIE=$(which boogie)BOOGIE_OPTIONS="/infer:j"PROVER_OPTIONS="\ /proverOpt:O:auto_config=false\ /proverOpt:O:type_check=true\ /proverOpt:O:smt.case_split=3\ /proverOpt:O:smt.qi.eager_threshold=100\ /proverOpt:O:smt.delay_units=true\ /proverOpt:O:smt.arith.solver=2\ ""$DOTNET""$BOOGIE"$BOOGIE_OPTIONS$PROVER_OPTIONS"$@"#Uncomment if you want to use the executable instead of the DLL#"$BOOGIE" $BOOGIE_OPTIONS $PROVER_OPTIONS "$@"
13.9.10. Controlling the prover
Much of controlling the prover is accomplished by controlling verification condition generation (25.9.7) or Boogie (Section 13.9.9). The following options are also commonly used:
--verification-error-limit:<n>
- limits the number of verification errors reported per procedure.Default is 5; 0 means as many as possible; a small positive number runs fasterbut a large positive number reports more errors per run--verification-time-limit:<n>
(was-timeLimit:<n>
) - limits the number of seconds spent trying to verify each procedure.
13.9.11. Controlling test generation
Dafny is capable of generating unit (runtime) tests. It does so by asking the prover to solvefor values of inputs to a method that cause the program to execute specific blocks or paths.A detailed description of how to do this is given ina separate document.
14. Dafny VSCode extension and the Dafny Language Server
14.1. Dafny functionality within VSCode
There is a language server for Dafny, whichimplements theLanguage Server Protocol.This server is used by the Dafny VSCode Extension; it currently offers the following features:
- Quick syntax highlighting
- As-you-type parsing, resolution and verification diagnostics
- Support forDafny plugins
- Expanded explanations (in addition to the error message) for selected errors (and more being added), shown by hovering
- Quick fixes for selected errors (and more being added)
- Limited support for symbol completion
- Limited support for code navigation
- Counter-example display
- Highlighting of ghost statements
- Gutter highlights
- A variety of Preference settings
Most of the Dafny functionality is simply there when editing a .dfy file with VSCode that has the Dafny extension installed.Some actions are available through added menu items.The Dafny functionality within VSCode can be found in these locations:
- The preferences are under the menu Code->Preferences->Settings->Dafny extension configuration. There are two sections of settings.
- A hover over an error location will bring up a hover popup, which will show expanded error information and any quick fix options that are available.
- Within a .dfy editor, a right-click brings up a context menu, which has a menu item ‘Dafny’. Under it are actions to Build or Run a program,to turn on or off counterexample display, find definitions, and the like.
14.2. Gutter highlights
Feedback on a program is show visually as underlining with squiggles within the text and as various markings in various colors in thegutter down the left side of an editor window.
The first time a file is loaded, the gutter will highlight in a transparent squiggly green line all the methods that need to be verified, like this:
When the file is saved (in verification on save), or whenever the Dafny verifier is ready (in verification on change), it will start to verify methods.That line will turn into a thin green rectangle on methods that have been verified, and display an animated less transparent green squiggly line on methods that are being actively verified:
When the verification finishes, if a method, a function, a constant with default initialization or a subset type with a witness has some verification errors in it,the editor will display two yellow vertical rails indicating an error context.
Inside this context, if there is a failing assertion on a line,it will fill the gap between the vertical yellow bars with a red rectangle, even if there might be other assertions that are verified on the line.If there is no error on a line, but there is at least one assertion that verified, it will display a green disk with a white checkmark on it,which can be used to check progress in a proof search.
As soon as a line is changed, the gutter icons turn transparent and squiggly, to indicate their obsolescence.
The red error rectangles occupy only half the horizontal space, to visualise their possible obsolescence.
When the file is saved (in verification on save), or as soon as possible otherwise,these squiggly icons will be animated while the Dafny verifier inspect the area.
If the method was verifying before a change, instead of two yellow vertical bars with a red squiggly line,the gutter icons display an animated squiggly but more firm green line, thereby indicating that the method used to verify,but Dafny is still re-verifying it.
If there is a parse or resolution error, the previous gutter icons turn gray and a red triangle indicates the position of the parse or resolution error.
14.3. The Dafny Server
Before Dafnyimplemented the officialLanguage Server Protocol, it implemented its own protocol forEmacs, which resulted in a project calledDafnyServer. While the latest Dafny releases still contain a working DafnyServer binary, this component has been feature frozen since 2022, and it may not support features that were added to Dafny after that time. We do not recommend using it.
The Dafny Server hasintegration tests that serve as the basis of the documentation.
The server is essentially a REPL, which produces output in the same format as the Dafny CLI; clients thusdo not need to understand the internals of Dafny’s caching. A typical editing session proceeds as follows:
- When a new Dafny file is opened, the editor starts a new instance of theDafny server. The cache is blank at that point.
- The editor sends a copy of the buffer for initial verification. This takessome time, after which the server returns a list of errors.
- The user makes modifications; the editor periodically sends a new copy ofthe buffer’s contents to the Dafny server, which quickly returns an updatedlist of errors.
The client-server protocol is sequential, uses JSON, and works over ASCIIpipes by base64-encoding utf-8 queries. It defines one type of query, and twotypes of responses:
Queries are of the following form:
verify<base64encodedJSONpayload>[[DAFNY-CLIENT:EOM]]
Responses are of the following form:
<listoferrorsandusualoutput,asproducedbytheDafnyCLI>[SUCCESS][[DAFNY-SERVER:EOM]]
or
<errormessage>[FAILURE][[DAFNY-SERVER:EOM]]
The JSON payload is an utf-8 encoded string resulting of the serialization ofa dictionary with 4 fields:
- args: An array of Dafny arguments, as passed to the Dafny CLI
- source: A Dafny program, or the path to a Dafny source file.
- sourceIsFile: A boolean indicating whether the ‘source’ argument is a Dafny program or the path to one.
- filename: The name of the original source file, to be used in error messages
For small files, embedding the Dafny source directly into a message isconvenient; for larger files, however, it is generally better for performanceto write the source snapshot to a separate file, and to pass that to Dafnyby setting the ‘sourceIsFile’ flag to true.
For example, if you compile and runDafnyServer.exe
, you could paste the following command:
verifyeyJhcmdzIjpbIi9jb21waWxlOjAiLCIvcHJpbnRUb29sdGlwcyIsIi90aW1lTGltaXQ6MjAiXSwiZmlsZW5hbWUiOiJ0cmFuc2NyaXB0Iiwic291cmNlIjoibWV0aG9kIEEoYTppbnQpIHJldHVybnMgKGI6IGludCkge1xuICBiIDo9IGE7XG4gIGFzc2VydCBmYWxzZTtcbn1cbiIsInNvdXJjZUlzRmlsZSI6ZmFsc2V9[[DAFNY-CLIENT:EOM]]
The interpreter sees the commandverify
, and then starts reading every line until it sees[[DAFNY-CLIENT:EOM]]
The payload is a base64 encoded string that you could encode or decode using JavaScript’satob
andbtoa
function.For example, the payload above was generated using the following code:
btoa(JSON.stringify({"args":["/compile:0","/printTooltips","/timeLimit:20"],"filename":"transcript","source":`method A(a:int) returns (b: int) { b := a; assert false;}`,"sourceIsFile":false}))==="eyJhcmdzIjpbIi9jb21waWxlOjAiLCIvcHJpbnRUb29sdGlwcyIsIi90aW1lTGltaXQ6MjAiXSwiZmlsZW5hbWUiOiJ0cmFuc2NyaXB0Iiwic291cmNlIjoibWV0aG9kIEEoYTppbnQpIHJldHVybnMgKGI6IGludCkge1xuICBiIDo9IGE7XG4gIGFzc2VydCBmYWxzZTtcbn1cbiIsInNvdXJjZUlzRmlsZSI6ZmFsc2V9"
Thus to decode such output, you’d manually useJSON.parse(atob(payload))
.
15. Plugins to Dafny
Dafny has a plugin architecture that permits users to build tools for the Dafny language without having to replicate parsing and name/type resolution of Dafny programs. Such a tool might just do some analysis on the Dafny program,without concern for verifying or compiling the program. Or it might modify the program (actually, modify the program’s AST) and then continue on with verification and compilation with the core Dafny tool. A user plugin might also be usedin the Language Server and thereby be available in the VSCode (or other) IDE.
This is an experimental aspect of Dafny.The plugin API directly exposes the Dafny AST, which is constantly evolving.Hence, always recompile your plugin against the binary of Dafny that will be importing your plugin.
Plugins are libraries linked to aDafny.dll
of the same version as the Language Server.A plugin typically defines:
- Zero or one class extending
Microsoft.Dafny.Plugins.PluginConfiguration
, which receives plugins arguments in its methodParseArguments
, and- Can return a list of
Microsoft.Dafny.Plugins.Rewriter
s when its methodGetRewriters()
is called by Dafny, - Can return a list of
Microsoft.Dafny.Plugins.Compiler
s when its methodGetCompilers()
is called by Dafny, - If the configuration extends the subclass
Microsoft.Dafny.LanguageServer.Plugins.PluginConfiguration
:- Can return a list of
Microsoft.Dafny.LanguageServer.Plugins.DafnyCodeActionProvider
s when its methodGetDafnyCodeActionProviders()
is called by the Dafny Language Server. - Can return a modified version of
OmniSharp.Extensions.LanguageServer.Server.LanguageServerOptions
when its methodWithPluginHandlers()
is called by the Dafny Language Server.
- Can return a list of
- Can return a list of
- Zero or more classes extending
Microsoft.Dafny.Plugins.Rewriter
.If a configuration class is provided, it is responsible for instantiating them and returning them inGetRewriters()
.If no configuration class is provided, an automatic configuration will load every definedRewriter
automatically. - Zero or more classes extending
Microsoft.Dafny.Plugins.Compiler
.If a configuration class is provided, it is responsible for instantiating them and returning them inGetCompilers()
.If no configuration class is provided, an automatic configuration will load every definedCompiler
automatically. - Zero or more classes extending
Microsoft.Dafny.LanguageServer.Plugins.DafnyCodeActionProvider
.Only a configuration class of typeMicrosoft.Dafny.LanguageServer.Plugins.PluginConfiguration
can be responsible for instantiating them and returning them inGetDafnyCodeActionProviders()
.
The most important methods of the classRewriter
that plugins override are
- (experimental)
PreResolve(ModuleDefinition)
: Here you can optionally modify the AST before it is resolved. PostResolve(ModuleDefinition)
: This method is repeatedly called with every resolved and type-checked module, before verification.Plugins override this method typically to report additional diagnostics.PostResolve(Program)
: This method is called once after allPostResolve(ModuleDefinition)
have been called.
Plugins are typically used to report additional diagnostics such as unsupported constructs for specific compilers (through the methodsÈrror(...)
andWarning(...)
of the fieldReporter
of the classRewriter
)
Note that all plugin errors should use the original program’s expressions’ token and NOTToken.NoToken
, else no error will be displayed in the IDE.
15.1. Language Server plugin tutorial
In this section, we will create a plugin that enhances the functionality of the Language Server.We will start by showing the steps needed to create a plugin, followed by an example implementation that demonstrates how to provide more code actions and add custom request handlers.
15.1.1. Create plugin project
Assuming the Dafny source code is installed in the folderdafny/
start by creating an empty folder next to it, e.g.PluginTutorial/
mkdirPluginTutorialcdPluginTutorial
Then, create a dotnet class project
dotnet new classlib
It will create a fileClass1.cs
that you can rename
mvClass1.cs MyPlugin.cs
Open the newly created filePluginTutorial.csproj
, and add the following after</PropertyGroup>
:
<ItemGroup><ProjectReferenceInclude="../dafny/source/DafnyLanguageServer/DafnyLanguageServer.csproj"/></ItemGroup>
15.1.2. Implement plugin
15.1.2.1. Code actions plugin
This code action plugin will add a code action that allows you to place a dummy comment in front of the first method name, only if the selection is on the line of the method.
Open the fileMyPlugin.cs
, remove everything, and write the imports and a namespace:
usingMicrosoft.Dafny;usingMicrosoft.Dafny.LanguageServer.Plugins;usingMicrosoft.Boogie;usingMicrosoft.Dafny.LanguageServer.Language;usingSystem.Linq;usingRange=OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;namespaceMyPlugin;
After that, add aPluginConfiguration
that will expose all the quickfixers of your plugin.This class will be discovered and instantiated automatically by Dafny.
publicclassTestConfiguration:PluginConfiguration{publicoverrideDafnyCodeActionProvider[]GetDafnyCodeActionProviders(){returnnewDafnyCodeActionProvider[]{newAddCommentDafnyCodeActionProvider()};}}
Note that you could also override the methodsGetRewriters()
andGetCompilers()
for other purposes, but this is out of scope for this tutorial.
Then, we need to create the quickFixerAddCommentDafnyCodeActionProvider
itself:
publicclassAddCommentDafnyCodeActionProvider:DafnyCodeActionProvider{publicoverrideIEnumerable<DafnyCodeAction>GetDafnyCodeActions(IDafnyCodeActionInputinput,Rangeselection){returnnewDafnyCodeAction[]{};}}
For now, this quick fixer returns nothing.input
is the program state, andselection
is where the caret is.We replace the return statement with a conditional that tests whether the selection is on the first line:
varfirstTokenRange=input.Program?.GetFirstTopLevelToken()?.GetLspRange();if(firstTokenRange!=null&&firstTokenRange.Start.Line==selection.Start.Line){returnnewDafnyCodeAction[]{// TODO};}else{returnnewDafnyCodeAction[]{};}
Every quick fix consists of a title (provided immediately), and zero or moreDafnyCodeActionEdit
(computed lazily).ADafnyCodeActionEdit
has aRange
to remove and somestring
to insert instead. AllDafnyCodeActionEdit
sof the sameDafnyCodeAction
are applied at the same time if selected.
To create aDafnyCodeAction
, we can either use the easy-to-useInstantDafnyCodeAction
, which accepts a title and an array of edits:
returnnewDafnyCodeAction[]{newInstantDafnyCodeAction("Insert comment",newDafnyCodeActionEdit[]{newDafnyCodeActionEdit(firstTokenRange.GetStartRange(),"/*First comment*/")})};
or we can implement our custom inherited class ofDafnyCodeAction
:
publicclassCustomDafnyCodeAction:DafnyCodeAction{publicRangewhereToInsert;publicCustomDafnyCodeAction(RangewhereToInsert):base("Insert comment"){this.whereToInsert=whereToInsert;}publicoverrideDafnyCodeActionEdit[]GetEdits(){returnnewDafnyCodeActionEdit[]{newDafnyCodeActionEdit(whereToInsert.GetStartRange(),"/*A comment*/")};}}
In that case, we could return:
returnnewDafnyCodeAction[]{newCustomDafnyCodeAction(firstTokenRange)};
15.1.2.2. Request handler plugin
This request handler plugin enhances the Language Server to support a request with aTextDocumentIdentifier
as parameter, which will return abool
value denoting whether the providedDocumentUri
has anyLoopStmt
’s in it.
Open the fileMyPlugin.cs
, remove everything, and write the imports and a namespace:
usingOmniSharp.Extensions.JsonRpc;usingOmniSharp.Extensions.LanguageServer.Server;usingOmniSharp.Extensions.LanguageServer.Protocol.Models;usingMicrosoft.Dafny.LanguageServer.Plugins;usingMicrosoft.Dafny.LanguageServer.Workspace;usingMediatR;usingMicrosoft.Dafny;namespaceMyPlugin;
After that, add aPluginConfiguration
that will add all the request handlers of your plugin.This class will be discovered and instantiated automatically by Dafny.
publicclassTestConfiguration:PluginConfiguration{publicoverrideLanguageServerOptionsWithPluginHandlers(LanguageServerOptionsoptions){returnoptions.WithHandler<DummyHandler>();}}
Then, we need to create the request handlerDummyHandler
itself:
[Parallel][Method("dafny/request/dummy",Direction.ClientToServer)]publicrecordDummyParams:TextDocumentIdentifier,IRequest<bool>;publicclassDummyHandler:IJsonRpcRequestHandler<DummyParams,bool>{privatereadonlyIProjectDatabaseprojects;publicDummyHandler(IProjectDatabaseprojects){this.projects=projects;}publicasyncTask<bool>Handle(DummyParamsrequest,CancellationTokencancellationToken){varstate=awaitprojects.GetParsedDocumentNormalizeUri(request);if(state==null){returnfalse;}returnstate.Program.Descendants().OfType<LoopStmt>().Any();}}
For more advanced example implementations of request handlers, look atdafny/Source/DafnyLanguageServer/Handlers/*
.
15.1.3. Building plugin
That’s it! Now, build your library while inside your folder:
> dotnet build
This will create the filePluginTutorial/bin/Debug/net6.0/PluginTutorial.dll
.Now, open VSCode, open Dafny settings, and enter the absolute path to this DLL in the plugins section.Restart VSCode, and it should work!
16. Full list of legacy command-line options {#sec-full-command-line-options}
For the on-line version only, the output ofdafny-?
follows. Note that with the advent ofdafny commands, many options are only applicable to some (if any) commands, some are renamed, and some are obsolete and will eventually be removed.
Use'dafny --help'toseehelpforthenewDafnyCLIformat.Usage:dafny[option...][filename...]----Generaloptions-------------------------------------------------------/versionprintthedafnyversionnumber/helpprintthismessage/attrHelpprintamessageaboutsupporteddeclarationattributes/env:<n>printcommandlinearguments0-never,1(default)-duringBPLprintandproverlog,2-like1andalsotostandardoutput/printVerifiedProceduresCount:<n>0-no1(default)-yes/waitawaitEnterfromkeyboardbeforeterminatingprogram/xml:<file>alsoproduceoutputinXMLformatto<file>Allthe.dfyfilessuppliedonthecommandlinealongwithfilesrecursivelyincludedby'include'directivesareconsideredasingleDafnyprogram;howeveronlythosefileslistedonthecommandlineareverified.Exitcode:0--success;1--invalidcommand-line;2--parseortypeerrors;3--compilationerrors;4--verificationerrors----Inputconfiguration---------------------------------------------------/dprelude:<file>ChoosetheDafnypreludefile./stdinReadstandardinputandtreatitasaninput.dfyfile.----Plugins-------------------------------------------------------------------Overallreportingandprinting----------------------------------------/showSnippets:<value>0(default)-Don'tshowsourcecodesnippetsforDafnymessages.1-ShowasourcecodesnippetforeachDafnymessage./statsPrintinterestingstatisticsabouttheDafnyfilessupplied./printIncludes:<None|Immediate|Transitive>None(default)-Printnothing.Immediate-Printfilesincludedbyfileslistedonthecommandline.Transitive-RecursesonthefilesprintedbyImmediate.ImmediateandTransitivewillexitafterprinting./view:<view1,view2>Printthefilteredviewsofamoduleafteritisresolved(/rprint).Ifprintbeforethemoduleisresolved(/dprint),theneverythinginthemoduleisprinted.Ifnoviewisspecified,theneverythinginthemoduleisprinted./funcCallGraphPrintoutthefunctioncallgraph.Formatis:func,mod=callee*/pmtracePrintpattern-matchcompilerdebuginfo./printTooltipsDumpadditionalpositionalinformation(displayedasmouse-overtooltipsbytheVSCodeplugin)tostdoutas'Info'messages./diagnosticsFormat:<text|json>Choosehowtoreporterrors,warnings,andinfomessages.text(default)-Usehumanreadableoutputjson-PrinteachmessageasaJSONobject,oneperline.----Languagefeatureselection--------------------------------------------/defaultFunctionOpacity:<value>Changethedefaultopacityoffunctions.`transparent`(default)meansfunctionsaretransparent,canbemanuallymadeopaqueandthenrevealed.`autoRevealDependencies`makesallfunctionsnotexplicitlylabelledasopaquetobeopaquebutrevealsthemautomaticallyinscopeswhichdonothave`{:autoRevealDependenciesfalse}`.`opaque`meansfunctionsarealwaysopaquesotheopaquekeywordisnotneeded,andfunctionsmustberevealedeverywhereneededforaproof./readsClausesOnMethods:<value>0(default)-Readsclausesonmethodsareforbidden.1-Readsclausesonmethodsarepermitted(withadefaultof'reads *')./standardLibraries:<value>0(default)-DonotallowDafnycodetodependonthestandardlibrariesincludedwiththeDafnydistribution.1-AllowDafnycodetodependonthestandardlibrariesincludedwiththeDafnydistribution.Seehttps://github.com/dafny-lang/dafny/blob/master/Source/DafnyStandardLibraries/README.md for more information.Notcompatiblewiththe/unicodeChar:0option./noIncludesIgnoreincludedirectives./noExternsIgnoreexternattributes./functionSyntax:<version>ThesyntaxforfunctionsischangingfromDafnyversion3toversion4.Thisswitchgivesearlyaccesstothenewsyntax,andalsoprovidesamodetohelpwithmigration.3-Compiledfunctionsarewritten`functionmethod`and`predicatemethod`.Ghostfunctionsarewritten`function`and`predicate`.4(default)-Compiledfunctionsarewritten`function`and`predicate`.Ghostfunctionsarewritten`ghostfunction`and`ghostpredicate`.migration3to4-Compiledfunctionsarewritten`functionmethod`and`predicatemethod`.Ghostfunctionsarewritten`ghostfunction`and`ghostpredicate`.Tomigratefromversion3toversion4,usethisflagonyourversion3program.Thiswillgiveflagalloccurrencesof`function`and`predicate`asparsingerrors.Theseareghostfunctions,sochangethoseintothenewsyntax`ghostfunction`and`ghostpredicate`.Then,startusing/functionSyntax:4.Thiswillflagalloccurrencesof`functionmethod`and`predicatemethod`asparsingerrors.So,changethosetojust`function`and`predicate`.Now,yourprogramusesversion4syntaxandhastheexactsamemeaningasyourpreviousversion3program.experimentalDefaultGhost-Likemigration3to4,butallow`function`and`predicate`asalternativestodeclaringghostfunctionsandpredicates,respectively.experimentalDefaultCompiled-Likemigration3to4,butallow`function`and`predicate`asalternativestodeclaringcompiledfunctionsandpredicates,respectively.experimentalPredicateAlwaysGhost-Compiledfunctionsarewritten`function`.Ghostfunctionsarewritten`ghostfunction`.Predicatesarealwaysghostandarewritten`predicate`./quantifierSyntax:<version>ThesyntaxforquantificationdomainsischangingfromDafnyversion3toversion4,morespecificallywherequantifierranges(|<Range>)areallowed.Thisswitchgivesearlyaccesstothenewsyntax.3-Rangesareonlyallowedafterallquantifiedvariablesaredeclared.(e.g.setx,y|0<=x<|s|&&yins[x]&&0<=y::y)4(default)-Rangesareallowedaftereachquantifiedvariabledeclaration.(e.g.setx|0<=x<|s|,y<-s[x]|0<=y::y)Notethatquantifiervariabledomains(<-<Domain>)areavailableinbothsyntaxversions./disableScopesTreatallexportsetsas'export reveal *'.i.e.don'thidefunctionbodiesortypedefinitionsduringtranslation.----Warningselection-----------------------------------------------------/warnShadowingEmitsawarningifthenameofadeclaredvariablecausedanothervariabletobeshadowed./warnMissingConstructorParenthesisEmitsawarningwhenaconstructornameinacasepatternisnotfollowedbyparentheses./deprecation:<n>0-Don'tgiveanywarningsaboutdeprecatedfeatures.1(default)-Showwarningsaboutdeprecatedfeatures./warningsAsErrorsTreatwarningsaserrors.----Verificationoptions-------------------------------------------------/allowAxioms:<value>Preventsawarningfrombeinggeneratedforaxioms,suchasassumestatementsandfunctionsormethodswithoutabody,thatdon'thavean{:axiom}attribute./verificationLogger:<configuration>Logsverificationresultsusingthegiventestresultformat.Thecurrentlysupportedformatsare`trx`,`csv`,and`text`.Theseare:theXML-basedformatcommonlyusedfortestresultsfor.NETlanguages,acustomCSVschema,andatextualformatmeantforhumanconsumption.Youcanprovideconfigurationusingthesamestringformataswhenusingthe--loggeroptionfordotnettest,suchas:--format"trx;LogFileName=<...>");The`trx`and`csv`formatsautomaticallychooseanoutputfilenamebydefault,andprintthenameofthisfiletotheconsole.The`text`formatprintsitsoutputtotheconsolebydefault,butcansendoutputtoafilegiventhe`LogFileName`option.The`text`formatalsoincludesamoredetailedbreakdownofwhatassertionsappearineachassertionbatch.Whencombinedwiththeisolate-assertionsoption,itwillprovideapproximatetimeandresourceusecostsforeachassertion,allowingidentificationofespeciallyexpensiveassertions./dafnyVerify:<n>0-Stopafterresolutionandtypechecking.1-Continueontoverificationandcompilation./verifyAllModulesVerifymodulesthatcomefromanincludedirective./emitUncompilableCodeAllowcompilerstoemituncompilablecodethatusuallycontainusefulinformationaboutwhatfeatureismissing,ratherthanstoppingonthefirstproblem/separateModuleOutputOutputverificationresultsforeachmoduleseparately,ratherthanaggregatingthemaftertheyareallfinished./noCheating:<n>0(default)-Allowassumestatementsandfreeinvariants.1-Treatallassumptionsasasserts,anddropfree./induction:<n>0-Neverdoinduction,notevenwhenattributesrequestit.1-Onlyapplyinductionwhenattributesrequestit.2-Applyinductionasrequested(byattributes)andalsoforheuristicallychosenquantifiers.3-Applyinductionasrequested,andforheuristicallychosenquantifiersandlemmas.4(default)-Applyinductionasrequested,andforlemmas./inductionHeuristic:<n>0-Leastdiscriminatinginductionheuristic(thatis,leantowardapplyinginductionmoreoften).1,2,3,4,5-Levelsinbetween,orderedasfollowsasfarashowdiscriminatingtheyare:0<1<2<(3,4)<5<6.6(default)-Mostdiscriminating./trackPrintEffects:<n>0(default)-Everycompiledmethod,constructor,anditerator,whetherornotitbearsa{:print}attribute,mayhaveprinteffects.1-Acompiledmethod,constructor,oriteratorisallowedtohaveprinteffectsonlyifitismarkedwith{:print}./definiteAssignment:<n>0-Ignoresdefinite-assignmentrules.Thismodeisfortestingonly--itisnotsound.1(default)-Enforcesdefinite-assignmentrulesforcompiledvariablesandfieldswhosetypesdonotsupportauto-initialization,andforghostvariablesandfieldswhosetypeispossiblyempty.2-Enforcesdefinite-assignmentforallnon-yield-parametervariablesandfields,regardlessoftheirtypes.3-Like2,butalsoperformschecksinthecompilerthatnonondeterministicstatementsareused;thus,aprogramthatpassesatthislevel3isonethatthelanguageguaranteesthatvaluesseenduringexecutionwillbethesameineveryrunoftheprogram.4-Like1,butenforcesdefiniteassignmentforalllocalvariablesandout-parameters,regardlessoftheirtypes.(Whetherornotfieldsandnewarraysaresubjecttodefiniteassignmentsdependsontheirtypes.)/noAutoReqIgnoreautoReqattributes./autoReqPrint:<file>PrintoutrequirementsthatwereautomaticallygeneratedbyautoReq./noNLarithReduceZ3'sknowledgeofnon-lineararithmetic(*,/,%).Resultsinmoremanualwork,butalsoproducesmorepredictablebehavior.(Thisswitchwillperhapsbereplacedby/arithinthefuture.Fornow,ittakesprecedenceof/arith.)/arith:<n>(experimental)AdjusthowDafnyinterpretsarithmeticoperations.0-UseBoogie/Z3built-insforallarithmeticoperations.1(default)-Like0,butintroducesymbolicsynonymsfor*,/,%,andallowtheseoperatorstobeusedintriggers.2-Like1,butintroducesymbolicsynonymsalsofor+,-.3-Turnoffnon-lineararithmeticintheSMTsolver.Still,useBoogie/Z3built-insymbolsforallarithmeticoperations.4-Like3,butintroducesymbolicsynonymsfor*,/,%,andallowtheseoperatorstobeusedintriggers.5-Like4,butintroducesymbolicsynonymsalsofor+,-.6-Like5,andintroduceaxiomsthatdistribute+over*.7-like6,andintroducefactsthatassociateliteralsargumentsof*.8-Like7,andintroduceaxiomfortheconnectionbetween*,/,%.9-Like8,andintroduceaxiomsforsignofmultiplication.10-Like9,andintroduceaxiomsforcommutativityandassociativityof*./autoTriggers:<n>0-Donotgenerate{:trigger}annotationsforuser-levelquantifiers.1(default)-Adda{:trigger}toeachuser-levelquantifier.Existingannotationsarepreserved./rewriteFocalPredicates:<n>0-Don'trewritepredicatesinthebodyofprefixlemmas.1(default)-Inthebodyofprefixlemmas,rewriteanyuseofafocalpredicatePtoP#[_k-1]./extractCounterexampleIfverificationfails,reportadetailedcounterexampleforthefirstfailingassertion(experimental).----Compilationoptions---------------------------------------------------/compileTarget:<language>cs(default)-Compileto.NETviaC#.go-CompiletoGo.js-CompiletoJavaScript.java-CompiletoJava.py-CompiletoPython.cpp-CompiletoC++.dfy-CompiletoDafny.NotethattheC++backendhasvariouslimitations(seeDocs/Compilation/Cpp.md).ThisincludeslackofsupportforBigIntegers(akaint),mosthigherorderfunctions,andadvancedfeaturesliketraitsorco-inductivetypes./library:<value>Thecontentsofthisfileandanyfilesitincludescanbereferencedfromotherfilesasiftheywereincluded.However,thesecontentsareskippedduringcodegenerationandverification.Thisoptionisusefulinadiamonddependencysituation,topreventcodefromthebottomdependencyfrombeinggeneratedmorethanonce.Thevaluemaybeacomma-separatedlistoffilesandfolders./optimizeErasableDatatypeWrapper:<value>0-Includeallnon-ghostdatatypeconstructorsinthecompiledcode1(default)-Inthecompiledtargetcode,transformanynon-externdatatypewithasinglenon-ghostconstructorthathasasinglenon-ghostparameterintojustthatparameter.Forexample,thetypedatatypeRecord=Record(x:int)istransformedintojust'int'inthetargetcode./out:<file>Specifythefilenameandlocationforthegeneratedtargetlanguagefiles./runAllTests:<n>0(default)-Annotatescompiledmethodswiththe{:test}attributesuchthattheycanbetestedusingatestingframeworkinthetargetlanguage(e.g.xUnitforC#).1-Emitsamainmethodinthetargetlanguagethatwillexecuteeverymethodintheprogramwiththe{:test}attribute.Cannotbeusediftheprogramalreadycontainsamainmethod.Notethat/compile:3or4mustbeprovidedaswelltoactuallyexecutethismainmethod!/compile:<n>0-DonotcompileDafnyprogram.1(default)-UponsuccessfulverificationoftheDafnyprogram,compileittothedesignatedtargetlanguage.(/noVerifyautomaticallycountsasafailedverification.)2-AlwaysattempttocompileDafnyprogramtothetargetlanguage,regardlessofverificationoutcome.3-IfthereisaMainmethodandtherearenoverificationerrorsand/noVerifyisnotused,compilesprograminmemory(i.e.,doesnotwriteanoutputfile)andrunsit.4-Like(3),butattemptstocompileandrunregardlessofverificationoutcome./Main:<name>Specifythe(fully-qualified)nameofthemethodtouseastheexecutableentrypoint.Defaultisthemethodwiththe{:main}attribute,orelsethemethodnamed'Main'.AMainmethodcanhaveatmostone(non-ghost)argumentoftype`seq<string>`--args<arg1><arg2>...WhenrunningaDafnyfilethrough/compile:3or/compile:4,'--args'providesallargumentsafterittotheMainfunction,atindexstartingat1.Index0isusedtostoretheexecutable'snameifitexists./compileVerbose:<n>0-Don'tprintstatusofcompilationtotheconsole.1(default)-Printinformationsuchasfilesbeingwrittenbythecompilertotheconsole./spillTargetCode:<n>Explicitlywritesthecodeinthetargetlanguagetooneormorefiles.ThisisnotnecessarytorunaDafnyprogram,butmaybeofinterestwhenbuildingmulti-languageprogramsorfordebugging.0(default)-Don'tmakeanyextraefforttowritethetextualtargetprogram(butstillcompileit,if/compileindicatestodoso).1-Writethetextualtargetprogram,ifitisbeingcompiled.2-Writethetextualtargetprogram,provideditpassestheverifier(and/noVerifyisNOTused),regardlessof/compilesetting.3-Writethetextualtargetprogram,regardlessofverificationoutcomeand/compilesetting.Note,somecompilertargetsmay(alwaysorinsomesituations)writeoutthetextualtargetprogramaspartofcompilation,inwhichcase/spillTargetCode:0behavesthesamewayas/spillTargetCode:1./coverage:<file>Thecompileremitsbranch-coveragecallsandoutputsinto<file>alegendthatgivesadescriptionofeachsource-locationidentifierusedinthebranch-coveragecalls.(Use-as<file>toprinttotheconsole.)/optimizeProduceoptimizedC#codebypassingthe/optimizeflagtocsc.exe./optimizeResolution:<n>0-Resolveandtranslateallmethods.1-Translatemethodsonlyinthecallgraphofcurrentverificationtarget.2(default)-Asin1,butonlyresolvemethodbodiesinnon-includedDafnysources./useRuntimeLibRefertoapre-builtDafnyRuntime.dllinthecompiledassemblyratherthanincludingDafnyRuntime.csverbatim./testContracts:<Externs|TestedExterns>Enablerun-timetestingofthecompilableportionsofcertainfunctionormethodcontracts,attheircallsites.Thecurrentimplementationfocuseson{:extern}codebutmaysupportothercodeinthefuture.Externs-Checkcontractsoneverycalltoafunctionormethodmarkedwiththe{:extern}attribute,regardlessofwhereitoccurs.TestedExterns-Checkcontractsoneverycalltoafunctionormethodmarkedwiththe{:extern}attributewhenitoccursinamethodwiththe{:test}attribute,andwarnifnocorrespondingtestexistsforagivenexternaldeclaration.----------------------------------------------------------------------------DafnygenerallyacceptsBoogieoptionsandpassestheseontoBoogie.However,someBoogieoptions,like/loopUnroll,maynotbesoundforDafnyormaynothavethesamemeaningforaDafnyprogramasitwouldforasimilarBoogieprogram.----Boogieoptions--------------------------------------------------------Multiple.bplfilessuppliedonthecommandlineareconcatenatedintooneBoogieprogram./lib:<name>:Includedefinitionsinlibrary<name>.Thefile<name>.bplmustbeanincludedresourceinCore.dll.Currently,thefollowinglibrariesaresupported---base,node./proc:<p>:Onlycheckproceduresmatchedbypattern<p>.Thisoptionmaybespecifiedmultipletimestomatchmultiplepatterns.Thepattern<p>matchesthewholeprocedurenameandmaycontain*wildcardswhichmatchanycharacterzeroormoretimes./noProc:<p>:Donotcheckproceduresmatchedbypattern<p>.Exclusionswith/noProcareappliedafterinclusionswith/proc./noResolve:parseonly/noTypecheck:parseandresolveonly/print:<file>:printBoogieprogramafterparsingit(use-as<file>toprinttoconsole)/pretty:<n>0-printeachBoogiestatementononeline(faster).1(default)-pretty-printwithsomelinebreaks./printWithUniqueIds:printaugmentedinformationthatuniquelyidentifiesvariables/printUnstructured:with/printoption,desugarsallstructuredstatements/printPassive:with/printoption,printspassiveversionofprogram/printDesugared:with/printoption,desugarscalls/printLambdaLifting:with/printoption,desugarslambdalifting/freeVarLambdaLifting:Boogie'slambdaliftingtransformsthebodiesoflambdaexpressionsintotemplateswithholes.Bydefault,holesaremaximallylargesubexpressionsthatdonotcontainboundvariables.Thisoptionperformsaformoflambdaliftinginwhichholesarethelambda'sfreevariables./overlookTypeErrors:skipanyimplementationwithresolutionortypecheckingerrors/loopUnroll:<n>unrollloops,followinguptonbackedges(andthensome)defaultis-1,whichmeansloopsarenotunrolled/extractLoopsextractreducibleloopsintorecursiveproceduresandinlineirreducibleloopsusingtheboundsuppliedby/loopUnroll:<n>/soundLoopUnrollingsoundloopunrolling/doModSetAnalysisautomaticallyinfermodifiesclauses/printModel:<n>0(default)-donotprintZ3'serrormodel1-printZ3'serrormodel/printModelToFile:<file>printmodelto<file>insteadofconsole/mv:<file>Specifyfiletosavethemodelwithcapturedstates(seedocumentationfor:captureStateattribute)/enhancedErrorMessages:<n>0(default)-noenhancederrormessages1-Z3errormodelenhancederrormessages/printCFG:<prefix>:printcontrolflowgraphofeachimplementationinGraphvizformattofilesnamed:<prefix>.<procedurename>.dot/useBaseNameForFileName:Whenparsingusebasenameoffilefortokensinsteadofthepathsuppliedonthecommandline/emitDebugInformation:<n>0-donotemitdebuginformation1(default)-emitthedebuginformation:qid,:skolemidandset-info:boogie-vc-id/normalizeNames:<n>0(default)-KeepBoogieprogramnameswhengeneratingSMTcommands1-NormalizeBoogieprogramnameswhengeneratingSMTcommands.ThiskeepsSMTsolverinput,andthusoutput,constantwhenrenamingdeclarationsintheinputprogram./normalizeDeclarationOrder:<n>0-Keeporderoftop-leveldeclarationswhengeneratingSMTcommands.1(default)-Normalizeorderoftop-leveldeclarationswhengeneratingSMTcommands.ThiskeepsSMTsolverinput,andthusoutput,constantwhenreorderingdeclarationsintheinputprogram.----Inferenceoptions-----------------------------------------------------/infer:<flags>useabstractinterpretationtoinferinvariants<flags>mustspecifyexactlyoneofthefollowingdomains:t=trivialbottom/toplatticej=strongerintervalstogetherwithanyofthefollowingoptions:s=debugstatistics0..9=numberofiterationsbeforeapplyingawiden(default=0)/checkInferinstrumentinferredinvariantsasassertstobecheckedbytheoremprover/contractInferperformprocedurecontractinference/instrumentInferh-instrumentinferredinvariantsonlyatbeginningofloopheaders(default)e-instrumentinferredinvariantsatbeginningandendofeveryblock(thismodeisintendedforuseindebuggingofabstractdomains)/printInstrumentedprintBoogieprogramafterithasbeeninstrumentedwithinvariants----Debuggingandgeneraltracingoptions---------------------------------/silentprintnothingatall/quietprintnothingbutwarningsanderrors/traceblurtoutvariousdebugtraceinformation/traceTimesoutputtiminginformationatcertainpointsinthepipeline/tracePOsoutputinformationaboutthenumberofproofobligations(alsoincludedinthe/traceoutput)/breaklaunchandbreakintodebugger----Civloptions----------------------------------------------------------/trustMoverTypesdonotverifymovertypeannotationsonatomicactiondeclarations/trustNoninterferencedonotperformnoninterferencechecks/trustRefinementdonotperformrefinementchecks/trustLayersUpto:<n>donotverifylayers<n>andbelow/trustLayersDownto:<n>donotverifylayers<n>andabove/trustSequentializationdonotperformsequentializationchecks/civlDesugaredFile:<file>printplainBoogieprogramto<file>----Verification-conditiongenerationoptions-----------------------------/liveVariableAnalysis:<c>0=donotperformlivevariableanalysis1=performlivevariableanalysis(default)2=performinterprocedurallivevariableanalysis/noVerifyskipVCgenerationandinvocationofthetheoremprover/verifySnapshots:<n>verifyseveralprogramsnapshots(named<filename>.v0.bplto<filename>.vN.bpl)usingverificationresultcaching:0-donotuseanyverificationresultcaching(default)1-usethebasicverificationresultcaching2-usethemoreadvancedverificationresultcaching3-usethemoreadvancedcachingandreporterrorsaccordingtothenewsourcelocationsforerrorsandtheirrelatedlocations(butnot/errorTraceandCaptureStatelocations)/traceCaching:<n>0(default)-none1-fortesting2-forbenchmarking3-fortesting,benchmarking,anddebugging/verifySeparatelyverifyeachinputprogramseparately/removeEmptyBlocks:<c>0-donotremoveemptyblocksduringVCgeneration1-removeemptyblocks(default)/coalesceBlocks:<c>0=donotcoalesceblocks1=coalesceblocks(default)/traceverifyprintdebugoutputduringverificationconditiongeneration/subsumption:<c>applysubsumptiontoassertedconditions:0-never,1-notforquantifiers,2(default)-always/alwaysAssumeFreeLoopInvariantsusually,afreeloopinvariant(orassumestatementinthatposition)isignoredincheckingcontexts(likeotherfreethings);thisoptionincludesthesefreeloopinvariantsasassumesinbothcontexts/inline:<i>useinliningstrategy<i>forprocedureswiththe:inlineattribute,see/attrHelpfordetails:noneassume(default)assertspec/printInlinedprinttheimplementationafterinliningcallstoprocedureswiththe:inlineattribute(workswith/inline)/recursionBound:<n>Settherecursionboundforstratifiedinliningtoben(default500)/smokeSoundnessSmokeTest:trytostickassertfalse;insomeplacesintheBPLandseeifwecanstillproveit/smokeTimeout:<n>Timeout,inseconds,forasingletheoremproverinvocationduringsmoketest,defaultsto10./typeEncoding:<t>EncodingoftypeswhengeneratingVCofapolymorphicprogram:m=monomorphic(default)p=predicatesa=argumentsBoogieautomaticallydetectsmonomorphicprogramsandenablesmonomorphicVCgeneration,therebyoverridingtheaboveoption.Ifthelattertwooptionsareused,thenarraysarehandledviaaxioms./useArrayAxiomsIfmonomorphictypeencodingisused,arraysarehandledbydefaultwiththeSMTtheoryofarrays.Thisoptionallowstheuseofaxiomsinstead./reflectAddIntheVC,generateanauxiliarysymbol,elsewheredefinedtobe+,insteadof+./prune:<n>0-Turnoffpruning.1-Turnonpruning(default).Pruningwillremoveanytop-levelBoogiedeclarationsthatarenotaccessiblebytheimplementationthatisabouttobeverified.Withoutpruning,duetotheunstablenatureofSMTsolvers,achangetoanypartofaBoogieprogramhasthepotentialtoaffecttheverificationofanyotherpartoftheprogram.Onlyusethisifyourprogramcontainsusesclauseswhererequired,otherwisepruningwillbreakyourprogram.Moreinformationcanbefoundhere:https://github.com/boogie-org/boogie/blob/afe8eb0ffbb48d593de1ae3bf89712246444daa8/Source/ExecutionEngine/CommandLineOptions.cs#L160/printPruned:<file>Afterpruning,printtheBoogieprogramtothespecifiedfile./relaxFocusProcessfociinabottom-upfashion.Thiswayonlygeneratesalinearnumberofsplits.Thedefaultway(top-down)ismoreaggressiveanditmaycreateanexponentialnumberofsplits./randomSeed:<s>Supplytherandomseedfor/randomizeVcIterationsoption./randomizeVcIterations:<n>TurnonrandomizationoftheinputthatBoogiepassestotheSMTsolverandturnonrandomizationintheSMTsolveritself.AttempttorandomizeandproveeachVCntimesusingtherandomseedsprovidedbytheoption/randomSeed:<s>.If/randomSeedoptionisnotprovided,sischosentobezero.CertainBoogieinputsareunstableinthesensethatchangestotheinputthatpreserveitsmeaningmaycausetheoutputtochange.Thisoptionsimulatesmeaning-preservingchangestotheinputwithoutrequiringtheusertoactuallymakethosechanges.Thisoptionisimplementedbyrenamingvariablesandreorderingdeclarationsintheinput,andbysettingsolveroptionsthathavesimilareffects./trackVerificationCoverageTrackandreportwhichprogramelementslabeledwithan`{:id...}`attributewerenecessarytocompleteverification.Assumptions,assertions,requiresclauses,ensuresclauses,assignments,andcallscanbelabeledforinclusioninthisreport.Thisgeneralizesandreplacestheprevious(undocumented)`/printNecessaryAssertions`option./keepQuantifierIfpool-basedquantifierinstantiationcreatesinstancesofaquantifierthenkeepthequantifieralongwiththeinstances.Bydefault,thequantifierisdroppedifanyinstancesarecreated.----Verification-conditionsplitting--------------------------------------/vcsMaxCost:<f>VCwillnotbesplitunlessthecostofaVCexceedsthisnumber,defaultsto2000.0.ThisdoesNOTapplyinthekeep-goingmodeafterfirstroundofsplitting./vcsMaxSplits:<n>MaximalnumberofVCgeneratedpermethod.Inkeepgoingmodeonlyappliestothefirstround.Defaultsto1./vcsMaxKeepGoingSplits:<n>Ifsettomorethan1,activatesthekeepgoingmode,whereafterthefirstroundofsplitting,VCsthattimedoutaresplitinto<n>piecesandretrieduntilwesucceedprovingthem,orthereisonlyoneassertiononasinglepathandittimeouts(inwhichcaseerrorisreportedforthatassertion).Defaultsto1./vcsKeepGoingTimeout:<n>Timeoutinsecondsforasingletheoremproverinvocationinkeepgoingmode,exceptforthefinalsingle-assertioncase.Defaultsto1s./vcsFinalAssertTimeout:<n>Timeoutinsecondsforthesinglelastassertioninthekeepgoingmode.Defaultsto30s./vcsPathJoinMult:<f>Ifmorethanonepathjoinatablock,byhowmuchmultiplythenumberofpathsinthatblock,toaccomodateforthefactthattheproverwilllearnsomethingononepaths,beforeproceedingtoanother.Defaultsto0.8./vcsPathCostMult:<f1>/vcsAssumeMult:<f2>Thecostofablockis(<assert-cost>+<f2>*<assume-cost>)*(1.0+<f1>*<entering-paths>)<f1>defaultsto1.0,<f2>defaultsto0.01.Thecostofasingleassertionorassumptioniscurrentlyalways1.0./vcsPathSplitMult:<f>IfthebestpathsplitofaVCofcostAisintoVCsofcostBandC,thenthesplitisappliedifA>=<f>*(B+C),otherwiseassertionsplittingwillbeapplied.Defaultsto0.5(alwaysdopathsplittingifpossible),settomoretodolesspathsplittingandmoreassertionsplitting./vcsSplitOnEveryAssertSplitseveryVCsothateachassertionisisolatedintoitsownVC.MayresultinVCswithoutanyassertions./vcsDumpSplitsForsplit#ndumpsplit.n.dotandsplit.n.bpl.Warning:Affectserrorreporting./vcsCores:<n>Trytoverify<n>VCsatonce.Defaultsto1./vcsLoad:<f>SetsvcsCorestothemachine'sProcessorCount*f,roundedtothenearestinteger(where0.0<=f<=3.0),butnevertolessthan1.----Proveroptions--------------------------------------------------------/errorLimit:<num>Limitthenumberoferrorsproducedforeachprocedure(defaultis5,someproversmaysupportonly1).Setnumto0tofindasmanyassertionfailuresaspossible./timeLimit:<num>Limitthenumberofsecondsspenttryingtoverifyeachprocedure/rlimit:<num>LimittheZ3resourcespenttryingtoverifyeachprocedure./errorTrace:<n>0-noTracelabelsintheerroroutput,1(default)-includeusefulTracelabelsinerroroutput,2-includeallTracelabelsintheerroroutput/vcBrackets:<b>bracketodd-characteredidentifiernameswith|'s. <b> is:0-no(default),1-yes/proverDll:<tp>usetheoremprover<tp>,where<tp>iseitherthenameofaDLLcontainingtheproverinterfacelocatedintheBoogiedirectory,orafullpathtoaDLLcontainingsuchaninterface.Thedefaultinterfaceshippedis:SMTLib(usestheSMTLib2formatandcallsanSMTsolver)/proverOpt:KEY[=VALUE]Provideaprover-specificoption(shortform/p)./proverHelpPrintprover-specificoptionssupportedby/proverOpt./proverLog:<file>Loginputforthetheoremprover.Likefilenamessuppliedasargumentstootheroptions,<file>canusethefollowingmacros:@TIME@expandstothecurrenttime@PREFIX@expandstotheconcatenationofstringsgivenby/logPrefixoptions@FILE@expandstothelastfilenamespecifiedonthecommandlineInaddition,/proverLogcanalsousethemacro'@PROC@',whichcausestheretobeoneproverlogfileperverificationcondition,andthemacrothenexpandstothenameoftheprocedurethattheverificationconditionisfor./logPrefix:<str>Definestheexpansionofthemacro'@PREFIX@',whichcanbeusedinvariousfilenamesspecifiedbyotheroptions./proverLogAppendAppend(notoverwrite)thespecifiedproverlogfile/proverWarnings0(default)-don'tprint,1-printtostdout,2-printtostderr/restartProverRestarttheproveraftereachquery
17. Dafny Grammar
The Dafny grammar has a traditional structure: a scanner tokenizes the textual input into a sequence of tokens; the parser consumes the tokensto produce an AST. The AST is then passed on for name and type resolution and further processing.
Dafny uses the Coco/R lexer and parser generator for its lexer and parser(http://www.ssw.uni-linz.ac.at/Research/Projects/Coco)[@Linz:Coco].See theCoco/R Referencemanualfor details.The Dafny input file to Coco/R is theDafny.atg
file in the source tree.
The grammar is anattributed extended BNF grammar.Theattributed adjective indicates that the BNF productions areparameterized by boolean parameters that control variations of the production rules, such as whether a particular alternative is permitted ornot. Using such attributes allows combining non-terminals with quitesimilar production rules, making a simpler, more compact and morereadable grammer.
The grammar rules presented here replicate those in the sourcecode, but omit semantic actions, error recovery markers, andconflict resolution syntax. Some uses of the attributeparameters are described informally.
The names of character sets and tokens start with a lower caseletter; the names of grammar non-terminals start withan upper-case letter.
17.1. Dafny Syntax
This section gives the definitions of Dafny tokens.
17.1.1. Classes of characters
These definitions define some names as representing subsets of the set of characters. Here,
- double quotes enclose the set of characters constituting the class,
- single quotes enclose a single character (perhaps an escaped representation using
\
), - the binary
+
indicates set union, - binary
-
indicates set difference, and ANY
indicates the set of all (unicode) characters.
letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"digit = "0123456789"posDigit = "123456789"posDigitFrom2 = "23456789"hexdigit = "0123456789ABCDEFabcdef"special = "'_?"cr = '\r'lf = '\n'tab = '\t'space = ' 'nondigitIdChar = letter + specialidchar = nondigitIdChar + digitnonidchar = ANY - idcharcharChar = ANY - '\'' - '\\' - cr - lfstringChar = ANY - '"' - '\\' - cr - lfverbatimStringChar = ANY - '"'
Anonidchar
is any character except those that can be used in an identifier.Here the scanner generator will interpretANY
as any unicode character.However,nonidchar
is used only to mark the end of the!in
token;in this context any character other thanwhitespace or printable ASCIIwill trigger a subsequent scanning or parsing error.
17.1.2. Definitions of tokens
These definitions use
- double-quotes to indicate a verbatim string (with no escaping of characters)
'"'
to indicate a literal double-quote character- vertical bar to indicate alternatives
- square brackets to indicate an optional part
- curly braces to indicate 0-or-more repetitions
- parentheses to indicate grouping
- a
-
sign to indicate set difference: any character sequence matched by the left operand except character sequences matched by the right operand - a sequence of any of the above to indicate concatenation without whitespace
reservedword = "abstract" | "allocated" | "as" | "assert" | "assume" | "bool" | "break" | "by" | "calc" | "case" | "char" | "class" | "codatatype" | "const" | "constructor" | "continue" | "datatype" | "decreases" | "else" | "ensures" | "exists" | "expect" | "export" | "extends" | "false" | "for" | "forall" | "fresh" | "function" | "ghost" | "if" | "imap" | "import" | "in" | "include" | "int" | "invariant" | "is" | "iset" | "iterator" | "label" | "lemma" | "map" | "match" | "method" | "modifies" | "modify" | "module" | "multiset" | "nameonly" | "nat" | "new" | "newtype" | "null" | "object" | "object?" | "old" | "opaque" | "opened" | "ORDINAL" "predicate" | "print" | "provides" | "reads" | "real" | "refines" | "requires" | "return" | "returns" | "reveal" | "reveals" | "seq" | "set" | "static" | "string" | "then" | "this" | "trait" | "true" | "twostate" | "type" | "unchanged" | "var" | "while" | "witness" | "yield" | "yields" | arrayToken | bvTokenarrayToken = "array" [ posDigitFrom2 | posDigit digit { digit }]["?"]bvToken = "bv" ( 0 | posDigit { digit } )ident = nondigitIdChar { idchar } - charToken - reservedworddigits = digit {["_"] digit}hexdigits = "0x" hexdigit {["_"] hexdigit}decimaldigits = digit {["_"] digit} '.' digit {["_"] digit}escapedChar = ( "\'" | "\"" | "\\" | "\0" | "\n" | "\r" | "\t" | "\u" hexdigit hexdigit hexdigit hexdigit | "\U{" hexdigit { hexdigit } "}" )charToken = "'" ( charChar | escapedChar ) "'"stringToken = '"' { stringChar | escapedChar } '"' | "@" '"' { verbatimStringChar | '"' '"' } '"'ellipsis = "..."
There are a few words that have a special meaning in certain contexts, but are not reserved words and can be used as identifiers outside of those contexts:
least
andgreatest
are recognized as adjectives to the keywordpredicate
(cf.Section 12.4).older
is a modifier for parameters of non-extreme predicates (cf.Section 6.4.6).
The\uXXXX
form of anescapedChar
is only used when the option--unicode-char=false
is set (which is the default for Dafny 3.x);the\U{XXXXXX}
form of anescapedChar
is only used when the option--unicode-char=true
is set (which is the default for Dafny 4.x).
17.2. Dafny Grammar productions
The grammar productions are presented in the following Extended BNF syntax:
- identifiers starting with a lower case letter denoteterminal symbols (tokens) as defined in theprevious subsection
- identifiers starting with an upper case letter denote nonterminalsymbols
- strings (a sequence of characters enclosed by double quote characters)denote the sequence of enclosed characters
=
separates the sides of a production, e.g.A=abc
|
separates alternatives, e.g.ab|c|de
meansab
orc
orde
(
)
groups alternatives, e.g.(a|b)c
meansac
orbc
[]
option, e.g.[a]b
meansab
orb
{}
iteration (0 or more times), e.g.{a}b
meansb
orab
oraab
or …- We allow
|
inside[]
and{}
. So[a|b]
is short for[(a|b)]
and{a|b}
is short for{(a|b)}
. //
in a line introduces a comment that extends to the end-of-the line, but does not terminate the production- The first production defines the name of the grammar, in this case
Dafny
.
In addition to the Coco rules, for the sake of readability we have adoptedthese additional conventions.
- We allow
-
to be used.a-b
means it matches if it matchesa
but notb
. - We omit the
.
that marks the end of a CoCo/R production. - we omit deprecated features.
To aid in explaining the grammar we have added some additional productionsthat are not present in the original grammar. We name these with a trailingunderscore. Inlining these where they are referenced will reconstruct the original grammar.
17.2.1. Programs
Dafny = { IncludeDirective_ } { TopDecl(isTopLevel:true, isAbstract: false) } EOF
17.2.1.1. Include directives
IncludeDirective_ = "include" stringToken
17.2.1.2. Top-level declarations
TopDecl(isTopLevel, isAbstract) = { DeclModifier } ( SubModuleDecl(isTopLevel) | ClassDecl | DatatypeDecl | NewtypeDecl | SynonymTypeDecl // includes abstract types | IteratorDecl | TraitDecl | ClassMemberDecl(allowConstructors: false, isValueType: true, moduleLevelDecl: true) )
17.2.1.3. Declaration modifiers
DeclModifier = ( "abstract" | "ghost" | "static" | "opaque" )
17.2.2. Modules
SubModuleDecl(isTopLevel) = ( ModuleDefinition | ModuleImport | ModuleExport )
Module export declarations are not permitted ifisTopLevel
is true.
17.2.2.1. Module Definitions
ModuleDefinition(isTopLevel) = "module" { Attribute } ModuleQualifiedName [ "refines" ModuleQualifiedName ] "{" { TopDecl(isTopLevel:false, isAbstract) } "}"
TheisAbstract
argument is true if the precedingDeclModifiers
include “abstract”.
17.2.2.2. Module Imports
ModuleImport = "import" [ "opened" ] ( QualifiedModuleExport | ModuleName "=" QualifiedModuleExport | ModuleName ":" QualifiedModuleExport )QualifiedModuleExport = ModuleQualifiedName [ "`" ModuleExportSuffix ]ModuleExportSuffix = ( ExportId | "{" ExportId { "," ExportId } "}" )
17.2.2.3. Module Export Definitions
ModuleExport = "export" [ ExportId ] [ "..." ] { "extends" ExportId { "," ExportId } | "provides" ( ExportSignature { "," ExportSignature } | "*" ) | "reveals" ( ExportSignature { "," ExportSignature } | "*" ) }ExportSignature = TypeNameOrCtorSuffix [ "." TypeNameOrCtorSuffix ]
17.2.3. Types
Type = DomainType_ | ArrowType_DomainType_ = ( BoolType_ | CharType_ | IntType_ | RealType_ | OrdinalType_ | BitVectorType_ | ObjectType_ | FiniteSetType_ | InfiniteSetType_ | MultisetType_ | FiniteMapType_ | InfiniteMapType_ | SequenceType_ | NatType_ | StringType_ | ArrayType_ | TupleType | NamedType )NamedType = NameSegmentForTypeName { "." NameSegmentForTypeName }NameSegmentForTypeName = Ident [ GenericInstantiation ]
17.2.3.1. Basic types
BoolType_ = "bool"IntType_ = "int"RealType_ = "real"BitVectorType_ = bvTokenOrdinalType_ = "ORDINAL"CharType_ = "char"
17.2.3.2. Generic instantiation
GenericInstantiation = "<" Type { "," Type } ">"
17.2.3.3. Type parameter
GenericParameters(allowVariance) = "<" [ Variance ] TypeVariableName { TypeParameterCharacteristics } { "," [ Variance ] TypeVariableName { TypeParameterCharacteristics } } ">"// The optional Variance indicator is permitted only if allowVariance is trueVariance = ( "*" | "+" | "!" | "-" )TypeParameterCharacteristics = "(" TPCharOption { "," TPCharOption } ")"TPCharOption = ( "==" | "0" | "00" | "!" "new" )
17.2.3.4. Collection types
FiniteSetType_ = "set" [ GenericInstantiation ]InfiniteSetType_ = "iset" [ GenericInstantiation ]MultisetType_ = "multiset" [ GenericInstantiation ]SequenceType_ = "seq" [ GenericInstantiation ]StringType_ = "string"FiniteMapType_ = "map" [ GenericInstantiation ]InfiniteMapType_ = "imap" [ GenericInstantiation ]
17.2.3.5. Type definitions
SynonymTypeDecl = SynonymTypeDecl_ | OpaqueTypeDecl_ | SubsetTypeDecl_SynonymTypeName = NoUSIdentSynonymTypeDecl_ = "type" { Attribute } SynonymTypeName { TypeParameterCharacteristics } [ GenericParameters ] "=" TypeOpaqueTypeDecl_ = "type" { Attribute } SynonymTypeName { TypeParameterCharacteristics } [ GenericParameters ] [ TypeMembers ]TypeMembers = "{" { { DeclModifier } ClassMemberDecl(allowConstructors: false, isValueType: true, moduleLevelDecl: false, isWithinAbstractModule: module.IsAbstract) } "}"SubsetTypeDecl_ = "type" { Attribute } SynonymTypeName [ GenericParameters ] "=" LocalIdentTypeOptional "|" Expression(allowLemma: false, allowLambda: true) [ "ghost" "witness" Expression(allowLemma: false, allowLambda: true) | "witness" Expression((allowLemma: false, allowLambda: true) | "witness" "*" ]NatType_ = "nat"NewtypeDecl = "newtype" { Attribute } NewtypeName "=" [ ellipsis ] ( LocalIdentTypeOptional "|" Expression(allowLemma: false, allowLambda: true) [ "ghost" "witness" Expression(allowLemma: false, allowLambda: true) | "witness" Expression((allowLemma: false, allowLambda: true) | "witness" "*" ] | Type ) [ TypeMembers ]
17.2.3.6. Class type
ClassDecl = "class" { Attribute } ClassName [ GenericParameters ] ["extends" Type {"," Type} | ellipsis ] "{" { { DeclModifier } ClassMemberDecl(modifiers, allowConstructors: true, isValueType: false, moduleLevelDecl: false) } "}"ClassMemberDecl(modifiers, allowConstructors, isValueType, moduleLevelDecl) = ( FieldDecl(isValueType) // allowed iff moduleLevelDecl is false | ConstantFieldDecl(moduleLevelDecl) | FunctionDecl(isWithinAbstractModule) | MethodDecl(modifiers, allowConstructors) )
17.2.3.7. Trait types
TraitDecl = "trait" { Attribute } ClassName [ GenericParameters ] [ "extends" Type { "," Type } | ellipsis ] "{" { { DeclModifier } ClassMemberDecl(allowConstructors: true, isValueType: false, moduleLevelDecl: false, isWithinAbstractModule: false) } "}"
17.2.3.8. Object type
ObjectType_ = "object" | "object?"
17.2.3.9. Array types
ArrayType_ = arrayToken [ GenericInstantiation ]
17.2.3.10. Iterator types
IteratorDecl = "iterator" { Attribute } IteratorName ( [ GenericParameters ] Formals(allowGhostKeyword: true, allowNewKeyword: false, allowOlderKeyword: false) [ "yields" Formals(allowGhostKeyword: true, allowNewKeyword: false, allowOlderKeyword: false) ] | ellipsis ) IteratorSpec [ BlockStmt ]
17.2.3.11. Arrow types
ArrowType_ = ( DomainType_ "~>" Type | DomainType_ "-->" Type | DomainType_ "->" Type )
17.2.3.12. Algebraic datatypes
DatatypeDecl = ( "datatype" | "codatatype" ) { Attribute } DatatypeName [ GenericParameters ] "=" [ ellipsis ] [ "|" ] DatatypeMemberDecl { "|" DatatypeMemberDecl } [ TypeMembers ]DatatypeMemberDecl = { Attribute } DatatypeMemberName [ FormalsOptionalIds ]
17.2.4. Type member declarations
17.2.4.1. Fields
FieldDecl(isValueType) = "var" { Attribute } FIdentType { "," FIdentType }
AFieldDecl
is not permitted ifisValueType
is true.
17.2.4.2. Constant fields
ConstantFieldDecl(moduleLevelDecl) = "const" { Attribute } CIdentType [ ellipsis ] [ ":=" Expression(allowLemma: false, allowLambda:true) ]
IfmoduleLevelDecl
is true, then thestatic
modifier is not permitted(the constant field is static implicitly).
17.2.4.3. Method declarations
MethodDecl(isGhost, allowConstructors, isWithinAbstractModule) = MethodKeyword_ { Attribute } [ MethodFunctionName ] ( MethodSignature_(isGhost, isExtreme: true iff this is a least or greatest lemma declaration) | ellipsis ) MethodSpec(isConstructor: true iff this is a constructor declaration) [ BlockStmt ]MethodKeyword_ = ( "method" | "constructor" | "lemma" | "twostate" "lemma" | "least" "lemma" | "greatest" "lemma" )MethodSignature_(isGhost, isExtreme) = [ GenericParameters ] [ KType ] // permitted only if isExtreme == true Formals(allowGhostKeyword: !isGhost, allowNewKeyword: isTwostateLemma, allowOlderKeyword: false, allowDefault: true) [ "returns" Formals(allowGhostKeyword: !isGhost, allowNewKeyword: false, allowOlderKeyword: false, allowDefault: false) ]KType = "[" ( "nat" | "ORDINAL" ) "]"Formals(allowGhostKeyword, allowNewKeyword, allowOlderKeyword, allowDefault) = "(" [ { Attribute } GIdentType(allowGhostKeyword, allowNewKeyword, allowOlderKeyword, allowNameOnlyKeyword: true, allowDefault) { "," { Attribute } GIdentType(allowGhostKeyword, allowNewKeyword, allowOlderKeyword, allowNameOnlyKeyword: true, allowDefault) } ] ")"
IfisWithinAbstractModule
is false, then the method must havea body for the program that contains the declaration to be compiled.
TheKType
may be specified only for least and greatest lemmas.
17.2.4.4. Function declarations
FunctionDecl(isWithinAbstractModule) = ( [ "twostate" ] "function" [ "method" ] { Attribute } MethodFunctionName FunctionSignatureOrEllipsis_(allowGhostKeyword: ("method" present), allowNewKeyword: "twostate" present) | "predicate" [ "method" ] { Attribute } MethodFunctionName PredicateSignatureOrEllipsis_(allowGhostKeyword: ("method" present), allowNewKeyword: "twostate" present, allowOlderKeyword: true) | ( "least" | "greatest" ) "predicate" { Attribute } MethodFunctionName PredicateSignatureOrEllipsis_(allowGhostKeyword: false, allowNewKeyword: "twostate" present, allowOlderKeyword: false)) ) FunctionSpec [ FunctionBody ]FunctionSignatureOrEllipsis_(allowGhostKeyword) = FunctionSignature_(allowGhostKeyword) | ellipsisFunctionSignature_(allowGhostKeyword, allowNewKeyword) = [ GenericParameters ] Formals(allowGhostKeyword, allowNewKeyword, allowOlderKeyword: true, allowDefault: true) ":" ( Type | "(" GIdentType(allowGhostKeyword: false, allowNewKeyword: false, allowOlderKeyword: false, allowNameOnlyKeyword: false, allowDefault: false) ")" )PredicateSignatureOrEllipsis_(allowGhostKeyword, allowNewKeyword, allowOlderKeyword) = PredicateSignature_(allowGhostKeyword, allowNewKeyword, allowOlderKeyword) | ellipsisPredicateSignature_(allowGhostKeyword, allowNewKeyword, allowOlderKeyword) = [ GenericParameters ] [ KType ] Formals(allowGhostKeyword, allowNewKeyword, allowOlderKeyword, allowDefault: true) [ ":" ( Type | "(" Ident ":" "bool" ")" ) ]FunctionBody = "{" Expression(allowLemma: true, allowLambda: true) "}" [ "by" "method" BlockStmt ]
17.2.5. Specifications
17.2.5.1. Method specifications
MethodSpec = { ModifiesClause(allowLambda: false) | RequiresClause(allowLabel: true) | EnsuresClause(allowLambda: false) | DecreasesClause(allowWildcard: true, allowLambda: false) }
17.2.5.2. Function specifications
FunctionSpec = { RequiresClause(allowLabel: true) | ReadsClause(allowLemma: false, allowLambda: false, allowWild: true) | EnsuresClause(allowLambda: false) | DecreasesClause(allowWildcard: false, allowLambda: false) }
17.2.5.3. Lambda function specifications
LambdaSpec = { ReadsClause(allowLemma: true, allowLambda: false, allowWild: true) | "requires" Expression(allowLemma: false, allowLambda: false) }
17.2.5.4. Iterator specifications
IteratorSpec = { ReadsClause(allowLemma: false, allowLambda: false, allowWild: false) | ModifiesClause(allowLambda: false) | [ "yield" ] RequiresClause(allowLabel: !isYield) | [ "yield" ] EnsuresClause(allowLambda: false) | DecreasesClause(allowWildcard: false, allowLambda: false) }
17.2.5.5. Loop specifications
LoopSpec = { InvariantClause_ | DecreasesClause(allowWildcard: true, allowLambda: true) | ModifiesClause(allowLambda: true) }
17.2.5.6. Requires clauses
RequiresClause(allowLabel) = "requires" { Attribute } [ LabelName ":" ] // Label allowed only if allowLabel is true Expression(allowLemma: false, allowLambda: false)
17.2.5.7. Ensures clauses
EnsuresClause(allowLambda) = "ensures" { Attribute } Expression(allowLemma: false, allowLambda)
17.2.5.8. Decreases clauses
DecreasesClause(allowWildcard, allowLambda) = "decreases" { Attribute } DecreasesList(allowWildcard, allowLambda)DecreasesList(allowWildcard, allowLambda) = PossiblyWildExpression(allowLambda, allowWildcard) { "," PossiblyWildExpression(allowLambda, allowWildcard) }PossiblyWildExpression(allowLambda, allowWild) = ( "*" // if allowWild is false, using '*' provokes an error | Expression(allowLemma: false, allowLambda) )
17.2.5.9. Modifies clauses
ModifiesClause(allowLambda) = "modifies" { Attribute } FrameExpression(allowLemma: false, allowLambda) { "," FrameExpression(allowLemma: false, allowLambda) }
17.2.5.10. Invariant clauses
InvariantClause_ = "invariant" { Attribute } Expression(allowLemma: false, allowLambda: true)
17.2.5.11. Reads clauses
ReadsClause(allowLemma, allowLambda, allowWild) = "reads" { Attribute } PossiblyWildFrameExpression(allowLemma, allowLambda, allowWild) { "," PossiblyWildFrameExpression(allowLemma, allowLambda, allowWild) }
17.2.5.12. Frame expressions
FrameExpression(allowLemma, allowLambda) = ( Expression(allowLemma, allowLambda) [ FrameField ] | FrameField )FrameField = "`" IdentOrDigitsPossiblyWildFrameExpression(allowLemma, allowLambda, allowWild) = ( "*" // error if !allowWild and '*' | FrameExpression(allowLemma, allowLambda) )
17.2.6. Statements
17.2.6.1. Labeled statement
Stmt = { "label" LabelName ":" } NonLabeledStmt
17.2.6.2. Non-Labeled statement
NonLabeledStmt = ( AssertStmt | AssumeStmt | BlockStmt | BreakStmt | CalcStmt | ExpectStmt | ForallStmt | IfStmt | MatchStmt | ModifyStmt | PrintStmt | ReturnStmt | RevealStmt | UpdateStmt | UpdateFailureStmt | VarDeclStatement | WhileStmt | ForLoopStmt | YieldStmt )
17.2.6.3. Break and continue statements
BreakStmt = ( "break" LabelName ";" | "continue" LabelName ";" | { "break" } "break" ";" | { "break" } "continue" ";" )
17.2.6.4. Block statement
BlockStmt = "{" { Stmt } "}"
17.2.6.5. Return statement
ReturnStmt = "return" [ Rhs { "," Rhs } ] ";"
17.2.6.6. Yield statement
YieldStmt = "yield" [ Rhs { "," Rhs } ] ";"
17.2.6.7. Update and call statement
UpdateStmt = Lhs ( {Attribute} ";" | { "," Lhs } ( ":=" Rhs { "," Rhs } | ":|" [ "assume" ] Expression(allowLemma: false, allowLambda: true) ) ";" )
17.2.6.8. Update with failure statement
UpdateFailureStmt = [ Lhs { "," Lhs } ] ":-" [ "expect" | "assert" | "assume" ] Expression(allowLemma: false, allowLambda: false) { "," Rhs } ";"
17.2.6.9. Variable declaration statement
VarDeclStatement = [ "ghost" ] "var" { Attribute } ( LocalIdentTypeOptional { "," { Attribute } LocalIdentTypeOptional } [ ":=" Rhs { "," Rhs } | ":-" [ "expect" | "assert" | "assume" ] Expression(allowLemma: false, allowLambda: false) { "," Rhs } | { Attribute } ":|" [ "assume" ] Expression(allowLemma: false, allowLambda: true) ] | CasePatternLocal ( ":=" | { Attribute } ":|" ) Expression(allowLemma: false, allowLambda: true) ) ";"CasePatternLocal = ( [ Ident ] "(" CasePatternLocal { "," CasePatternLocal } ")" | LocalIdentTypeOptional )
17.2.6.10. Guards
Guard = ( "*" | "(" "*" ")" | Expression(allowLemma: true, allowLambda: true) )
17.2.6.11. Binding guards
BindingGuard(allowLambda) = IdentTypeOptional { "," IdentTypeOptional } { Attribute } ":|" Expression(allowLemma: true, allowLambda)
17.2.6.12. If statement
IfStmt = "if" ( AlternativeBlock(allowBindingGuards: true) | ( BindingGuard(allowLambda: true) | Guard ) BlockStmt [ "else" ( IfStmt | BlockStmt ) ] )AlternativeBlock(allowBindingGuards) = ( { AlternativeBlockCase(allowBindingGuards) } | "{" { AlternativeBlockCase(allowBindingGuards) } "}" )AlternativeBlockCase(allowBindingGuards) = { "case" ( BindingGuard(allowLambda: false) //permitted iff allowBindingGuards == true | Expression(allowLemma: true, allowLambda: false) ) "=>" { Stmt } }
17.2.6.13. While Statement
WhileStmt = "while" ( LoopSpec AlternativeBlock(allowBindingGuards: false) | Guard LoopSpec ( BlockStmt | // no body ) )
17.2.6.14. For statement
ForLoopStmt = "for" IdentTypeOptional ":=" Expression(allowLemma: false, allowLambda: false) ( "to" | "downto" ) ( "*" | Expression(allowLemma: false, allowLambda: false) ) LoopSpec ( BlockStmt | // no body )
17.2.6.15. Match statement
MatchStmt = "match" Expression(allowLemma: true, allowLambda: true) ( "{" { CaseStmt } "}" | { CaseStmt } )CaseStmt = "case" ExtendedPattern "=>" { Stmt }
17.2.6.16. Assert statement
AssertStmt = "assert" { Attribute } [ LabelName ":" ] Expression(allowLemma: false, allowLambda: true) ( ";" | "by" BlockStmt )
17.2.6.17. Assume statement
AssumeStmt = "assume" { Attribute } Expression(allowLemma: false, allowLambda: true) ";"
17.2.6.18. Expect statement
ExpectStmt = "expect" { Attribute } Expression(allowLemma: false, allowLambda: true) [ "," Expression(allowLemma: false, allowLambda: true) ] ";"
17.2.6.19. Print statement
PrintStmt = "print" Expression(allowLemma: false, allowLambda: true) { "," Expression(allowLemma: false, allowLambda: true) } ";"
17.2.6.20. Reveal statement
RevealStmt = "reveal" Expression(allowLemma: false, allowLambda: true) { "," Expression(allowLemma: false, allowLambda: true) } ";"
17.2.6.21. Forall statement
ForallStmt = "forall" ( "(" [ QuantifierDomain ] ")" | [ QuantifierDomain ] ) { EnsuresClause(allowLambda: true) } [ BlockStmt ]
17.2.6.22. Modify statement
ModifyStmt = "modify" { Attribute } FrameExpression(allowLemma: false, allowLambda: true) { "," FrameExpression(allowLemma: false, allowLambda: true) } ";"
17.2.6.23. Calc statement
CalcStmt = "calc" { Attribute } [ CalcOp ] "{" CalcBody_ "}"CalcBody_ = { CalcLine_ [ CalcOp ] Hints_ }CalcLine_ = Expression(allowLemma: false, allowLambda: true) ";"Hints_ = { ( BlockStmt | CalcStmt ) }CalcOp = ( "==" [ "#" "[" Expression(allowLemma: true, allowLambda: true) "]" ] | "<" | ">" | "!=" | "<=" | ">=" | "<==>" | "==>" | "<==" )
17.2.6.24. Opaque block
OpaqueBlock = "opaque" OpaqueSpec BlockStmt OpaqueSpec = { | ModifiesClause(allowLambda: false) | EnsuresClause(allowLambda: false)}
17.2.7. Expressions
17.2.7.1. Top-level expression
Expression(allowLemma, allowLambda, allowBitwiseOps = true) = EquivExpression(allowLemma, allowLambda, allowBitwiseOps) [ ";" Expression(allowLemma, allowLambda, allowBitwiseOps) ]
The “allowLemma” argument says whether or not the expressionto be parsed is allowed to have the formS;E
whereS
is a call to a lemma.“allowLemma” should be passed in as “false” whenever the expression tobe parsed sits in a context that itself is terminated by a semi-colon.
The “allowLambda” says whether or not the expression to be parsed isallowed to be a lambda expression. More precisely, an identifier orparenthesized, comma-delimited list of identifiers is allowed tocontinue as a lambda expression (that is, continue with areads
,requires
,or=>
) only if “allowLambda” is true. This affects function/method/iteratorspecifications, if/while statements with guarded alternatives, and expressionsin the specification of a lambda expression itself.
17.2.7.2. Equivalence expression
EquivExpression(allowLemma, allowLambda, allowBitwiseOps) = ImpliesExpliesExpression(allowLemma, allowLambda, allowBitwiseOps) { "<==>" ImpliesExpliesExpression(allowLemma, allowLambda, allowBitwiseOps) }
17.2.7.3. Implies expression
ImpliesExpliesExpression(allowLemma, allowLambda, allowBitwiseOps) = LogicalExpression(allowLemma, allowLambda) [ ( "==>" ImpliesExpression(allowLemma, allowLambda, allowBitwiseOps) | "<==" LogicalExpression(allowLemma, allowLambda, allowBitwiseOps) { "<==" LogicalExpression(allowLemma, allowLambda, allowBitwiseOps) } ) ]ImpliesExpression(allowLemma, allowLambda, allowBitwiseOps) = LogicalExpression(allowLemma, allowLambda, allowBitwiseOps) [ "==>" ImpliesExpression(allowLemma, allowLambda, allowBitwiseOps) ]
17.2.7.4. Logical expression
LogicalExpression(allowLemma, allowLambda, allowBitwiseOps) = [ "&&" | "||" ] RelationalExpression(allowLemma, allowLambda, allowBitwiseOps) { ( "&&" | "||" ) RelationalExpression(allowLemma, allowLambda, allowBitwiseOps) }
17.2.7.5. Relational expression
RelationalExpression(allowLemma, allowLambda, allowBitwiseOps) = ShiftTerm(allowLemma, allowLambda, allowBitwiseOps) { RelOp ShiftTerm(allowLemma, allowLambda, allowBitwiseOps) }RelOp = ( "==" [ "#" "[" Expression(allowLemma: true, allowLambda: true) "]" ] | "!=" [ "#" "[" Expression(allowLemma: true, allowLambda: true) "]" ] | "<" | ">" | "<=" | ">=" | "in" | "!in" | "!!" )
17.2.7.6. Bit-shift expression
ShiftTerm(allowLemma, allowLambda, allowBitwiseOps) = Term(allowLemma, allowLambda, allowBitwiseOps) { ShiftOp Term(allowLemma, allowLambda, allowBitwiseOps) }ShiftOp = ( "<<" | ">>" )
17.2.7.7. Term (addition operations)
Term(allowLemma, allowLambda, allowBitwiseOps) = Factor(allowLemma, allowLambda, allowBitwiseOps) { AddOp Factor(allowLemma, allowLambda, allowBitwiseOps) }AddOp = ( "+" | "-" )
17.2.7.8. Factor (multiplication operations)
Factor(allowLemma, allowLambda, allowBitwiseOps) = BitvectorFactor(allowLemma, allowLambda, allowBitwiseOps) { MulOp BitvectorFactor(allowLemma, allowLambda, allowBitwiseOps) }MulOp = ( "*" | "/" | "%" )
17.2.7.9. Bit-vector expression
BitvectorFactor(allowLemma, allowLambda, allowBitwiseOps) = AsExpression(allowLemma, allowLambda, allowBitwiseOps) { BVOp AsExpression(allowLemma, allowLambda, allowBitwiseOps) }BVOp = ( "|" | "&" | "^" )
IfallowBitwiseOps
is false, it is an error to have a bitvector operation.
17.2.7.10. As/Is expression
AsExpression(allowLemma, allowLambda, allowBitwiseOps) = UnaryExpression(allowLemma, allowLambda, allowBitwiseOps) { ( "as" | "is" ) Type }
17.2.7.11. Unary expression
UnaryExpression(allowLemma, allowLambda, allowBitwiseOps) = ( "-" UnaryExpression(allowLemma, allowLambda, allowBitwiseOps) | "!" UnaryExpression(allowLemma, allowLambda, allowBitwiseOps) | PrimaryExpression(allowLemma, allowLambda, allowBitwiseOps) )
17.2.7.12. Primary expression
PrimaryExpression(allowLemma, allowLambda, allowBitwiseOps) = ( NameSegment { Suffix } | LambdaExpression(allowLemma, allowBitwiseOps) | MapDisplayExpr { Suffix } | SeqDisplayExpr { Suffix } | SetDisplayExpr { Suffix } | EndlessExpression(allowLemma, allowLambda, allowBitwiseOps) | ConstAtomExpression { Suffix } )
17.2.7.13. Lambda expression
LambdaExpression(allowLemma, allowBitwiseOps) = ( WildIdent | "(" [ IdentTypeOptional { "," IdentTypeOptional } ] ")" ) LambdaSpec "=>" Expression(allowLemma, allowLambda: true, allowBitwiseOps)
17.2.7.14. Left-hand-side expression
(discussion) {
Lhs = ( NameSegment { Suffix } | ConstAtomExpression Suffix { Suffix } )
17.2.7.15. Right-hand-side expression
Rhs = ArrayAllocation | ObjectAllocation_ | Expression(allowLemma: false, allowLambda: true, allowBitwiseOps: true) | HavocRhs_ ) { Attribute }
17.2.7.16. Array allocation right-hand-side expression
ArrayAllocation_ = "new" [ Type ] "[" [ Expressions ] "]" [ "(" Expression(allowLemma: true, allowLambda: true) ")" | "[" [ Expressions ] "]" ]
17.2.7.17. Object allocation right-hand-side expression
ObjectAllocation_ = "new" Type [ "." TypeNameOrCtorSuffix ] [ "(" [ Bindings ] ")" ]
17.2.7.18. Havoc right-hand-side expression
HavocRhs_ = "*"
17.2.7.19. Atomic expressions
ConstAtomExpression = ( LiteralExpression | ThisExpression_ | FreshExpression_ | AllocatedExpression_ | UnchangedExpression_ | OldExpression_ | CardinalityExpression_ | ParensExpression )
17.2.7.20. Literal expressions
LiteralExpression = ( "false" | "true" | "null" | Nat | Dec | charToken | stringToken )Nat = ( digits | hexdigits )Dec = decimaldigits
17.2.7.21. This expression
ThisExpression_ = "this"
17.2.7.22. Old and Old@ Expressions
OldExpression_ = "old" [ "@" LabelName ] "(" Expression(allowLemma: true, allowLambda: true) ")"
17.2.7.23. Fresh Expressions
FreshExpression_ = "fresh" [ "@" LabelName ] "(" Expression(allowLemma: true, allowLambda: true) ")"
17.2.7.24. Allocated Expressions
AllocatedExpression_ = "allocated" "(" Expression(allowLemma: true, allowLambda: true) ")"
17.2.7.25. Unchanged Expressions
UnchangedExpression_ = "unchanged" [ "@" LabelName ] "(" FrameExpression(allowLemma: true, allowLambda: true) { "," FrameExpression(allowLemma: true, allowLambda: true) } ")"
17.2.7.26. Cardinality Expressions
CardinalityExpression_ = "|" Expression(allowLemma: true, allowLambda: true) "|"
17.2.7.27. Parenthesized Expression
ParensExpression = "(" [ TupleArgs ] ")"TupleArgs = [ "ghost" ] ActualBinding(isGhost) // argument is true iff the ghost modifier is present { "," [ "ghost" ] ActualBinding(isGhost) // argument is true iff the ghost modifier is present }
17.2.7.28. Sequence Display Expression
SeqDisplayExpr = ( "[" [ Expressions ] "]" | "seq" [ GenericInstantiation ] "(" Expression(allowLemma: true, allowLambda: true) "," Expression(allowLemma: true, allowLambda: true) ")" )
17.2.7.29. Set Display Expression
SetDisplayExpr = ( [ "iset" | "multiset" ] "{" [ Expressions ] "}" | "multiset" "(" Expression(allowLemma: true, allowLambda: true) ")" )
17.2.7.30. Map Display Expression
MapDisplayExpr = ("map" | "imap" ) "[" [ MapLiteralExpressions ] "]"MapLiteralExpressions = Expression(allowLemma: true, allowLambda: true) ":=" Expression(allowLemma: true, allowLambda: true) { "," Expression(allowLemma: true, allowLambda: true) ":=" Expression(allowLemma: true, allowLambda: true) }
17.2.7.31. Endless Expression
EndlessExpression(allowLemma, allowLambda, allowBitwiseOps) = ( IfExpression(allowLemma, allowLambda, allowBitwiseOps) | MatchExpression(allowLemma, allowLambda, allowBitwiseOps) | QuantifierExpression(allowLemma, allowLambda) | SetComprehensionExpr(allowLemma, allowLambda, allowBitwiseOps) | StmtInExpr Expression(allowLemma, allowLambda, allowBitwiseOps) | LetExpression(allowLemma, allowLambda, allowBitwiseOps) | MapComprehensionExpr(allowLemma, allowLambda, allowBitwiseOps) )
17.2.7.32. If expression
IfExpression(allowLemma, allowLambda, allowBitwiseOps) = "if" ( BindingGuard(allowLambda: true) | Expression(allowLemma: true, allowLambda: true, allowBitwiseOps: true) ) "then" Expression(allowLemma: true, allowLambda: true, allowBitwiseOps: true) "else" Expression(allowLemma, allowLambda, allowBitwiseOps)
17.2.7.33. Match Expression
MatchExpression(allowLemma, allowLambda, allowBitwiseOps) = "match" Expression(allowLemma, allowLambda, allowBitwiseOps) ( "{" { CaseExpression(allowLemma: true, allowLambda, allowBitwiseOps: true) } "}" | { CaseExpression(allowLemma, allowLambda, allowBitwiseOps) } )CaseExpression(allowLemma, allowLambda, allowBitwiseOps) = "case" { Attribute } ExtendedPattern "=>" Expression(allowLemma, allowLambda, allowBitwiseOps)
17.2.7.34. Case and Extended Patterns
CasePattern = ( IdentTypeOptional | [Ident] "(" [ CasePattern { "," CasePattern } ] ")" )SingleExtendedPattern = ( PossiblyNegatedLiteralExpression | IdentTypeOptional | [ Ident ] "(" [ SingleExtendedPattern { "," SingleExtendedPattern } ] ")" )ExtendedPattern = ( [ "|" ] SingleExtendedPattern { "|" SingleExtendedPattern } )PossiblyNegatedLiteralExpression = ( "-" ( Nat | Dec ) | LiteralExpression )
17.2.7.35. Quantifier expression
QuantifierExpression(allowLemma, allowLambda) = ( "forall" | "exists" ) QuantifierDomain "::" Expression(allowLemma, allowLambda)
17.2.7.36. Set Comprehension Expressions
SetComprehensionExpr(allowLemma, allowLambda) = [ "set" | "iset" ] QuantifierDomain(allowLemma, allowLambda) [ "::" Expression(allowLemma, allowLambda) ]
17.2.7.37. Map Comprehension Expression
MapComprehensionExpr(allowLemma, allowLambda) = ( "map" | "imap" ) QuantifierDomain(allowLemma, allowLambda) "::" Expression(allowLemma, allowLambda) [ ":=" Expression(allowLemma, allowLambda) ]
17.2.7.38. Statements in an Expression
StmtInExpr = ( AssertStmt | AssumeStmt | ExpectStmt | RevealStmt | CalcStmt | ForallStmt )
17.2.7.39. Let and Let or Fail Expression
LetExpression(allowLemma, allowLambda) = ( [ "ghost" ] "var" CasePattern { "," CasePattern } ( ":=" | ":-" | { Attribute } ":|" ) Expression(allowLemma: false, allowLambda: true) { "," Expression(allowLemma: false, allowLambda: true) } | ":-" Expression(allowLemma: false, allowLambda: true) ) ";" Expression(allowLemma, allowLambda)
17.2.7.40. Name Segment
NameSegment = Ident [ GenericInstantiation | HashCall ]
17.2.7.41. Hash Call
HashCall = "#" [ GenericInstantiation ] "[" Expression(allowLemma: true, allowLambda: true) "]" "(" [ Bindings ] ")"
17.2.7.42. Suffix
Suffix = ( AugmentedDotSuffix_ | DatatypeUpdateSuffix_ | SubsequenceSuffix_ | SlicesByLengthSuffix_ | SequenceUpdateSuffix_ | SelectionSuffix_ | ArgumentListSuffix_ )
17.2.7.43. Augmented Dot Suffix
AugmentedDotSuffix_ = "." DotSuffix [ GenericInstantiation | HashCall ]
17.2.7.44. Datatype Update Suffix
DatatypeUpdateSuffix_ = "." "(" MemberBindingUpdate { "," MemberBindingUpdate } ")"MemberBindingUpdate = ( ident | digits ) ":=" Expression(allowLemma: true, allowLambda: true)
17.2.7.45. Subsequence Suffix
SubsequenceSuffix_ = "[" [ Expression(allowLemma: true, allowLambda: true) ] ".." [ Expression(allowLemma: true, allowLambda: true) ] "]"
17.2.7.46. Subsequence Slices Suffix
SlicesByLengthSuffix_ = "[" Expression(allowLemma: true, allowLambda: true) ":" [ Expression(allowLemma: true, allowLambda: true) { ":" Expression(allowLemma: true, allowLambda: true) } [ ":" ] ] "]"
17.2.7.47. Sequence Update Suffix
SequenceUpdateSuffix_ = "[" Expression(allowLemma: true, allowLambda: true) ":=" Expression(allowLemma: true, allowLambda: true) "]"
17.2.7.48. Selection Suffix
SelectionSuffix_ = "[" Expression(allowLemma: true, allowLambda: true) { "," Expression(allowLemma: true, allowLambda: true) } "]"
17.2.7.49. Argument List Suffix
ArgumentListSuffix_ = "(" [ Expressions ] ")"
17.2.7.50. Expression Lists
Expressions = Expression(allowLemma: true, allowLambda: true) { "," Expression(allowLemma: true, allowLambda: true) }
17.2.7.51. Parameter Bindings
ActualBindings = ActualBinding { "," ActualBinding }ActualBinding(isGhost = false) = [ NoUSIdentOrDigits ":=" ] Expression(allowLemma: true, allowLambda: true)
17.2.7.52. Quantifier domains
QuantifierDomain(allowLemma, allowLambda) = QuantifierVarDecl(allowLemma, allowLambda) { "," QuantifierVarDecl(allowLemma, allowLambda) }QuantifierVarDecl(allowLemma, allowLambda) = IdentTypeOptional [ "<-" Expression(allowLemma, allowLambda) ] { Attribute } [ | Expression(allowLemma, allowLambda) ]
17.2.7.53. Basic name and type combinations
Ident = identDotSuffix = ( ident | digits | "requires" | "reads" )NoUSIdent = ident - "_" { idchar }WildIdent = NoUSIdent | "_"IdentOrDigits = Ident | digitsNoUSIdentOrDigits = NoUSIdent | digitsModuleName = NoUSIdentClassName = NoUSIdent // also traitsDatatypeName = NoUSIdentDatatypeMemberName = NoUSIdentOrDigitsNewtypeName = NoUSIdentSynonymTypeName = NoUSIdentIteratorName = NoUSIdentTypeVariableName = NoUSIdentMethodFunctionName = NoUSIdentOrDigitsLabelName = NoUSIdentOrDigitsAttributeName = NoUSIdentExportId = NoUSIdentOrDigitsTypeNameOrCtorSuffix = NoUSIdentOrDigitsModuleQualifiedName = ModuleName { "." ModuleName }IdentType = WildIdent ":" TypeFIdentType = NoUSIdentOrDigits ":" TypeCIdentType = NoUSIdentOrDigits [ ":" Type ]GIdentType(allowGhostKeyword, allowNewKeyword, allowOlderKeyword, allowNameOnlyKeyword, allowDefault) = { "ghost" | "new" | "nameonly" | "older" } IdentType [ ":=" Expression(allowLemma: true, allowLambda: true) ]LocalIdentTypeOptional = WildIdent [ ":" Type ]IdentTypeOptional = WildIdent [ ":" Type ]TypeIdentOptional = { Attribute } { "ghost" | "nameonly" } [ NoUSIdentOrDigits ":" ] Type [ ":=" Expression(allowLemma: true, allowLambda: true) ]FormalsOptionalIds = "(" [ TypeIdentOptional { "," TypeIdentOptional } ] ")"
18. Testing syntax rendering
Sample math B: $a \to b$ or
$$ a \to \pi $$
or ( a \top ) or [ a \to \pi ]
Colors
integerliteral:10hexliteral:0xDEADrealliteral:1.1booleanliteral:truefalsecharliteral:'c'stringliteral:"abc"verbatimstring:@"abc"ident:ijktype:intgenerictype:map<int,T>operator:<=punctuation:{}keyword:whilespec:requirescomment:// commentattribute{:name}error:$
Syntax color tests:
integer:00020010_1float:.01.01.0_1.1_0bad:0_hex:0x10_abcdefABCDEFstring:"string \n \t \r \0""a\"b""'""\'"""string:"!@#$%^&*()_-+={}[]|:;\\<>,.?/~`"string:"\u1234 "string:" ":"\0\n\r\t\'\"\\"notstring:"abcdenotstring:"\u123 " : "x\Zz" : "x\ux"vstring:@""@"a"@""""@"'\"@"\u"vstring:@"xx""y y""zz "vstring:@" "@" "vstring:@"xx"bad:@!char:'a''\n''\'''"''\"'' ''\\'char:'\0''\r''\t''\u1234'badchar:$`ids:'\u123''\Z''\u''\u2222Z'ids:'\u123ZZZ''\u2222Z'ids:'a : a':'ab':'a'b':'a''b'ids:a_b_abab?_0id-label:a@labelliteral:truefalsenullop:-!~x-!~xop:a+b-c*d/e%fa+b-c*d/e%fop:<=>=<>==!=b&&c||==><==><==op:!=#!!in!inop:!in∆!inénotop:!inxpunc:.,::|:|:=()[]{}types:intrealstringcharboolnatORDINALtypes:objectobject?types:bv1bv10bv0types:arrayarray2array20array10types:array?array2?array20?array10?ids:array1array0array02bv02bv_1ids:intxnatxint0int_int?bv1_bv1xarray2xtypes:seq<int>set<bool>types:map<bool,bool>imap<bool,bool>types:seq<Node>seq<Node>types:seq<set<real>>types:map<set<int>,seq<bool>>types:G<A,int>G<G<A>,G<bool>>types:seqmapimapsetisetmultisetids:seqxmapxnoarg:seq<>seq<,>seq<bool,,bool>seq<bool,>keywords:ifwhileassertassumespec:requiresreadsmodifiesattribute:{:MyAttribute"asd",34}attribute:{:MyAttribute}comment:// commentcomment:/* comment */aftercomment:// comment /* asd */ dfgcomment:/* comment /* embedded */ tail */aftercomment:/* comment // embedded */aftercomment:/* comment /* inner comment */ outer comment */aftermoreafter
19. References
The binding power of shift and bit-wise operations is different than in C-like languages. ↩
This is likely to change in the future to disallowmultiple occurrences of the same key. ↩
This is likely to change in the future asfollows: The
in
and!in
operations will no longer besupported on maps, withxinm
replaced byxinm.Keys
,and similarly for!in
. ↩Traits are new to Dafny and are likely to evolve for a while. ↩
It is possible to conceive of a mechanism for disambiguating conflicting names, but this would add complexity to the language that does not appear to be needed, at least as yet. ↩
It would make sense to rename the specialfields
_reads
and_modifies
to have the same names as thecorresponding keywords,reads
andmodifies
, as is done forfunction values. Also, the various_decreases\(_i_\)
fields can becombined into one field nameddecreases
whose type is an-tuple. These changes may be incorporated into a future versionof Dafny. ↩To be specific, Dafny has two forms of ↩
Note, two places where co-predicatesand co-lemmas are not analogous are (a) co-predicates must not makerecursive calls to their prefix predicates and (b) co-predicates cannotmention
_k
. ↩The
:-
token is called the elephant symbol or operator. ↩The semantics of
old
in Dafny differs from similar constructs in other specification languages like ACSL or JML. ↩In order to be deterministic, the result of a function should only depend on the arguments and of the objects itreads, and Dafny does not provide a way to explicitly pass the entire heap as the argument to a function. Seethis post for more insights. ↩
This set of operations that are constant-folded may be enlarged in future versions of
dafny
. ↩All entities that Dafny translates to Boogie have their attributes passed on to Boogie except for the
{:axiom}
attribute (which conflicts with Boogie usage) and the{:trigger}
attribute which is instead converted into a Boogie quantifiertrigger. See Section 11 of [@Leino:Boogie2-RefMan]. ↩Ghost inference has to be performed after type inference, at least because it is not possible to determine if a member access
a.b
refers to a ghost variable until the type ofa
is determined. ↩Files may be included more than once or both included and listed on the command line. Duplicate inclusions are detected and each file processed only once. ↩
Unlike some languages, Dafny does not allow separation of ↩
The formula sent to the underlying SMT solver is the negation of the formula that the verifier wants to prove - also called a VC or verification condition. Hence, if the SMT solver returns “unsat”, it means that the SMT formula is always false, meaning the verifier’s formula is always true. On the other side, if the SMT solver returns “sat”, it means that the SMT formula can be made true with a special variable assignment, which means that the verifier’s formula is false under that same variable assignment, meaning it’s a counter-example for the verifier. In practice and because of quantifiers, the SMT solver will usually return “unknown” instead of “sat”, but will still provide a variable assignment that it couldn’t prove that it does not make the formula true.
dafny
reports it as a “counter-example” but it might not be a real counter-example, only provide hints about whatdafny
knows. ↩ ↩2 ↩3assumefalse
tells thedafny
verifier “Assume everything is true from this point of the program”. The reason is that, ‘false’ proves anything. For example,false==>A
is always true because it is equivalent to!false||A
, which reduces totrue||A
, which reduces totrue
. ↩assertP;
. ↩By default, the expression of an assertion or a precondition is added to the knowledge base of the
dafny
verifier for further assertions or postconditions. However, this is not always desirable, because if the verifier has too much knowledge, it might get lost trying to prove something in the wrong direction. ↩ ↩2 ↩3 ↩4dafny
actually breaks things down further. For example, a preconditionrequiresA&&B
or an assert statementassertA&&B;
turns into two assertions, more or less likerequiresArequiresB
andassertA;assertB;
. ↩ ↩2All the complexities of the execution paths (if-then-else, loops, goto, break….) are, down the road and for verification purposes, cleverly encoded with variables recording the paths and guarding assumptions made on each path. In practice, a second clever encoding of variables enables grouping many assertions together, and recovers which assertion is failing based on the value of variables that the SMT solver returns. ↩
Thispost gives an overview of how assertions are turned into assumptions for verification purposes. ↩
Caveat about assertion and assumption: One big difference between an “assertion transformed in an assumption” and the original “assertion” is that the original “assertion” can unroll functions twice, whereas the “assumed assertion” can unroll them only once. Hence,
dafny
can still continue to analyze assertions after a failing assertion without automatically proving “false” (which would make all further assertions vacuous). ↩To create a smaller batch,
dafny
duplicates the assertion batch, and arbitrarily transforms the clones of an assertion into assumptions except in exactly one batch, so that each assertion is verified only in one batch. This results in “easier” formulas for the verifier because it has less to prove, but it takes more overhead because every verification instance have a common set of axioms and there is no knowledge sharing between instances because they run independently. ↩‘Sequentializing’ a
forall
statement refers to compiling it directly to a series of nested loopswith the statement’s body directly inside. The alternative, default compilation strategyis to calculate the quantified variable bindings separately as a collection of tuples,and then execute the statement’s body for each tuple.Not allforall
statements can be sequentialized. ↩This refers to an expression such as
['H','e','l','l','o']
, as opposed to a string literal such as"Hello"
. ↩This refers to assign-such-that statements with multiple variables,and where at least one variable has potentially infinite bounds.For example, the implementation of the statement
varx:nat,y:nat:|0<x&&0<y&&x*x==y*y*y+1;
needs to avoid the naive approach of iterating all possible values ofx
andy
in a nested loop. ↩Sequence construction expressions often use a direct lambda expression, as in
seq(10,x=>x*x)
,but they can also be used with arbitrary function values, as inseq(10,squareFn)
. ↩