Dafny Programming Guide
Dafny Programming Guide
Abstract: This is the Dafny reference manual; it describes the Dafny programming language
and how to use the Dafny verification system. Parts of this manual are more tutorial in nature
in order to help the user understand how to do proofs with Dafny.
Acknowledgements
Rustan Leino is the designer of the Dafny language and the chief implementor of the dafny tools.
Leino began this document, with additions from Richard Ford, David R. Cok, and other members
of the Dafny project. Many others contributed to the implementation and to documenting various
aspects of the language, as described on the project website at https://github.com/dafny-
lang/dafny. David R. Cok is the primary author and editor.
1
Contents
1. Introduction 11
1.1. Dafny 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3. Programs (grammar) 25
3.1. Include Directives (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2. Top Level Declarations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3. Declaration Modifiers (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.4. Executable programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4. Modules (grammar) 29
4.1. Declaring New Modules (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2. Declaring nested modules standalone . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.3. Importing Modules (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.4. Opening Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.5. Export Sets and Access Control (grammar) . . . . . . . . . . . . . . . . . . . . . . 34
4.5.1. Provided and revealed names . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.5.2. Extends list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.6. Module Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.7. Module Ordering and Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.8. Name Resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.8.1. Modules and name spaces . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.8.2. Module Id Context Name Resolution . . . . . . . . . . . . . . . . . . . . . 45
4.8.3. Expression Context Name Resolution . . . . . . . . . . . . . . . . . . . . . 46
2
4.8.4. Type Context Name Resolution . . . . . . . . . . . . . . . . . . . . . . . . 47
5. Types 48
5.1. Kinds of types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.1.1. Value Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.1.2. Reference Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.1.3. Named Types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.2. Basic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.2.1. Booleans (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.2.2. Numeric Types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.2.3. Bit-vector Types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2.4. Ordinal type (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.2.5. Characters (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3. Type parameters (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.3.1. Declaring restrictions on type parameters . . . . . . . . . . . . . . . . . . . 59
5.3.2. Type parameter variance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5.4. Generic Instantiation (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5.5. Collection types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.5.1. Sets (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.5.2. Multisets (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
5.5.3. Sequences (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.5.4. Finite and Infinite Maps (grammar) . . . . . . . . . . . . . . . . . . . . . . 70
5.5.5. Iterating over collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.6. Types that stand for other types (grammar) . . . . . . . . . . . . . . . . . . . . . 74
5.6.1. Type synonyms (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.6.2. Abstract types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.6.3. Subset types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.7. Newtypes (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.7.1. Conversion operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.8. Class types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.9. Trait types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.9.1. Type object (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.9.2. Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.9.3. Example of traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.10. Array types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.10.1. One-dimensional arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.10.2. Converting arrays to sequences . . . . . . . . . . . . . . . . . . . . . . . . 92
5.10.3. Multi-dimensional arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.11. Iterator types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.12. Arrow types (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.13. Tuple types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.14. Algebraic Datatypes (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.14.1. Inductive datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.14.2. Coinductive datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.14.3. Coinduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
3
6. Member declarations 113
6.1. Field Declarations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2. Constant Field Declarations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . 114
6.3. Method Declarations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.3.1. Ordinary methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.3.2. Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.3.3. Lemmas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.3.4. Two-state lemmas and functions . . . . . . . . . . . . . . . . . . . . . . . . 119
6.4. Function Declarations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.4.1. Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.4.2. Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.4.3. Function-by-method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.4.4. Function Hiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.4.5. Extreme (Least or Greatest) Predicates and Lemmas . . . . . . . . . . . . 125
6.4.6. older parameters in predicates . . . . . . . . . . . . . . . . . . . . . . . . 125
6.5. Nameonly Formal Parameters and Default-Value Expressions . . . . . . . . . . . . 129
7. Specifications 131
7.1. Specification Clauses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
7.1.1. Requires Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
7.1.2. Ensures Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.1.3. Decreases Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.1.4. Framing (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.1.5. Reads Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
7.1.6. Modifies Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
7.1.7. Invariant Clause (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 142
7.2. Method Specification (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
7.3. Function Specification (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
7.4. Lambda Specification (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
7.5. Iterator Specification (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
7.6. Loop Specification (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
7.7. Auto-generated boilerplate specifications . . . . . . . . . . . . . . . . . . . . . . . 144
7.8. Well-formedness of specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
4
8.6. Update with Failure Statement (:-) (grammar) . . . . . . . . . . . . . . . . . . . 152
8.6.1. Failure compatible types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
8.6.2. Simple status return with no other outputs . . . . . . . . . . . . . . . . . . 153
8.6.3. Status return with additional outputs . . . . . . . . . . . . . . . . . . . . . 154
8.6.4. Failure-returns with additional data . . . . . . . . . . . . . . . . . . . . . . 155
8.6.5. RHS with expression list . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
8.6.6. Failure with initialized declaration. . . . . . . . . . . . . . . . . . . . . . . 157
8.6.7. Keyword alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
8.6.8. Key points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
8.6.9. Failure returns and exceptions . . . . . . . . . . . . . . . . . . . . . . . . . 159
8.7. Variable Declaration Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . 159
8.8. Guards (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
8.9. Binding Guards (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
8.10. If Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
8.11. Match Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.12. While Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
8.13. For Loops (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.14. Break and Continue Statements (grammar) . . . . . . . . . . . . . . . . . . . . . 168
8.15. Loop Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.15.1. Loop invariants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
8.15.2. Loop termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
8.15.3. Loop framing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
8.15.4. Body-less methods, functions, loops, and aggregate statements . . . . . . 176
8.16. Print Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
8.17. Assert statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
8.18. Assume Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
8.19. Expect Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
8.20. Reveal Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
8.20.1. Revealing assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
8.20.2. Revealing preconditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
8.20.3. Hiding and revealing function bodies . . . . . . . . . . . . . . . . . . . . . 184
8.20.4. Revealing constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
8.21. Forall Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
8.22. Modify Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.23. Calc Statement (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.24. Opaque Block (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
9. Expressions 192
9.1. Lemma-call expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 193
9.2. Equivalence Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 194
9.3. Implies or Explies Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . 195
9.4. Logical Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
9.5. Relational Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
9.6. Bit Shifts (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
9.7. Terms (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
9.8. Factors (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
5
9.9. Bit-vector Operations (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.10. As (Conversion) and Is (type test) Expressions (grammar) . . . . . . . . . . . . 197
9.11. Unary Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
9.12. Primary Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
9.13. Lambda expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
9.14. Left-Hand-Side Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . 200
9.15. Right-Hand-Side Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . 200
9.16. Array Allocation (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
9.17. Object Allocation (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
9.18. Havoc Right-Hand-Side (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 202
9.19. Constant Or Atomic Expressions (grammar) . . . . . . . . . . . . . . . . . . . . 202
9.20. Literal Expressions (grammar} . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
9.21. this Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
9.22. Old and Old@ Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . 203
9.23. Fresh Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
9.24. Allocated Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 208
9.25. Unchanged Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 208
9.26. Cardinality Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . 209
9.27. Parenthesized Expressions (grammar) . . . . . . . . . . . . . . . . . . . . . . . . 209
9.28. Sequence Display Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . 209
9.29. Set Display Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 210
9.30. Map Display Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . 211
9.31. Endless Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
9.31.1. If Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
9.31.2. Case and Extended Patterns (grammar) . . . . . . . . . . . . . . . . . . . 212
9.31.3. Match Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . 213
9.31.4. Quantifier Expression (grammar) . . . . . . . . . . . . . . . . . . . . . . . 213
9.31.5. Set Comprehension Expressions (grammar) . . . . . . . . . . . . . . . . . 214
9.31.6. Statements in an Expression (grammar) . . . . . . . . . . . . . . . . . . . 215
9.31.7. Let and Let or Fail Expression (grammar) . . . . . . . . . . . . . . . . . . 216
9.31.8. Map Comprehension Expression (grammar) . . . . . . . . . . . . . . . . . 217
9.32. Name Segment (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
9.33. Hash call (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
9.34. Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
9.34.1. Augmented Dot Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . 219
9.34.2. Datatype Update Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . 219
9.34.3. Subsequence Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . . . 220
9.34.4. Subsequence Slices Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . 221
9.34.5. Sequence Update Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . 221
9.34.6. Selection Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . 221
9.34.7. Argument List Suffix (grammar) . . . . . . . . . . . . . . . . . . . . . . . 222
9.35. Expression Lists (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
9.36. Parameter Bindings (grammar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
9.37. Assigned Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
9.38. Termination Ordering Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 223
9.39. Compile-Time Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
6
9.40. List of specification expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
7
11.2.25. {:vcs_max_splits N} . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
11.2.26. {:isolate_assertions} . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
11.2.27. {:synthesize} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
11.2.28. {:options OPT0, OPT1, ... } . . . . . . . . . . . . . . . . . . . . . . . 246
11.3. Attributes on reads and modifies clauses . . . . . . . . . . . . . . . . . . . . . . . 247
11.3.1. {:assume_concurrent} . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
11.4. Attributes on assertions, preconditions and postconditions . . . . . . . . . . . . . 247
11.4.1. {:only} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
11.4.2. {:focus} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
11.4.3. {:split_here} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
11.4.4. {:subsumption n} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
11.4.5. {:error "errorMessage", "successMessage"} . . . . . . . . . . . . . . 250
11.4.6. {:contradiction} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
11.5. Attributes on variable declarations . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.5.1. {:assumption} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.6. Attributes on quantifier expressions (forall, exists) . . . . . . . . . . . . . . . . . 251
11.6.1. {:heapQuantifier} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.6.2. {:induction} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.6.3. {:trigger} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
11.7. Deprecated attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
11.8. Other undocumented verification attributes . . . . . . . . . . . . . . . . . . . . . 252
11.9. New attribute syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
8
13.3. Dafny Programs and Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
13.3.1. Dafny Verification Artifacts: the Library Backend and .doo Files . . . . . 277
13.3.2. Dafny Translation Artifacts: .dtr Files . . . . . . . . . . . . . . . . . . . . 278
13.4. Dafny Standard Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
13.5. Dafny Code Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
13.6. Using Dafny From the Command Line . . . . . . . . . . . . . . . . . . . . . . . . 279
13.6.1. dafny commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
13.6.2. In-tool help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
13.6.3. dafny exit codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
13.6.4. dafny output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
13.6.5. Project files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
13.7. Verification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
13.7.1. Verification debugging when verification fails . . . . . . . . . . . . . . . . 294
13.7.2. Verification debugging when verification is slow . . . . . . . . . . . . . . . 299
13.7.3. Assertion batches, well-formedness, correctness . . . . . . . . . . . . . . . 305
13.7.4. Command-line options and other attributes to control verification . . . . 308
13.7.5. Analyzing proof dependencies . . . . . . . . . . . . . . . . . . . . . . . . . 309
13.7.6. Debugging brittle verification . . . . . . . . . . . . . . . . . . . . . . . . . 310
13.8. Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
13.8.1.1 Built-in declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
13.8.2. extern declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
13.8.3. Replaceable modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
13.8.4. C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
13.8.5. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
13.8.6. Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
13.8.7. Go . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
13.8.8. Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
13.8.9. C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
13.8.10. Supported features by target language . . . . . . . . . . . . . . . . . . . 316
13.9. Dafny Command Line Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
13.9.1. Help and version information . . . . . . . . . . . . . . . . . . . . . . . . . 321
13.9.2. Controlling input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
13.9.3. Controlling plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
13.9.4. Controlling output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
13.9.5. Controlling language features . . . . . . . . . . . . . . . . . . . . . . . . . 325
13.9.6. Controlling warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
13.9.7. Controlling verification . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
13.9.8. Controlling compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
13.9.9. Controlling Boogie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
13.9.10. Controlling the prover . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
13.9.11. Controlling test generation . . . . . . . . . . . . . . . . . . . . . . . . . . 336
14. Dafny VSCode extension and the Dafny Language Server 337
14.1. Dafny functionality within VSCode . . . . . . . . . . . . . . . . . . . . . . . . . 337
14.2. Gutter highlights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
14.3. The Dafny Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
9
15. Plugins to Dafny 347
15.1. Language Server plugin tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
15.1.1. Create plugin project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
15.1.2. Implement plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
15.1.3. Building plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
10
Abstract: This is the Dafny reference manual; it describes the Dafny programming language
and how to use the Dafny verification system. Parts of this manual are more tutorial in nature
in order to help the user understand how to do proofs with Dafny.
(Link to current document as html)
1. Introduction
Dafny (Leino 2010) is a programming language with built-in specification constructs, so that
verifying a program’s correctness with respect to those specifications is a natural part of writing
software. The Dafny static program verifier can be used to verify the functional correctness of
programs. This document is a reference manual for the programming language and a user guide
for the dafny tool that performs verification and compilation to an executable form.
The Dafny programming language is designed to support the static verification of programs. It
is imperative, sequential, supports generic classes, inheritance and abstraction, methods and
functions, dynamic allocation, inductive and coinductive datatypes, and specification constructs.
The specifications include pre- and postconditions, frame specifications (read and write sets),
and termination metrics. To further support specifications, the language also offers updatable
ghost variables, recursive functions, and types like sets and sequences. Specifications and ghost
constructs are used only during verification; the compiler omits them from the executable code.
The dafny verifier is run as part of the compiler. As such, a programmer interacts with it in much
the same way as with the static type checker—when the tool produces errors, the programmer
responds by changing the program’s type declarations, specifications, and statements.
(This document typically uses “Dafny” to refer to the programming language and dafny to refer
to the software tool that verifies and compiles programs in the Dafny language.)
The easiest way to try out the Dafny language is to download the supporting tools and documen-
tation and run dafny on your machine as you follow along with the Dafny tutorial. The dafny
tool can be run from the command line (on Linux, MacOS, Windows or other platforms) or from
IDEs such as emacs and VSCode, which can provide syntax highlighting and code manipulation
capabilities.
The verifier is powered by Boogie (Barnett et al. 2006; Leino 2008b; Leino and Rümmer 2010)
and Z3 (Moura and Bjørner 2008).
From verified programs, the dafny compiler can produce code for a number of 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 is based on the following references:
(Leino 2010), (Leino 2008a), (Leino and Polikarpova 2013), (Leino and Moskal 2014a), Co-
induction Simply.
The main part of the reference manual is in top down order except for an initial section that
deals with the lowest level constructs.
11
The details of using (and contributing to) the dafny tool are described in the User Guide (Section
13).
12
ghost var Repr: set<Node>
var head: int
var next: Node? // Node? means a Node value or null
13
var jj,i := ll,0;
while (jj != null && jj.head != 0)
invariant jj != null ==>
jj.Valid() &&
i + |jj.List| == |ll.List| &&
ll.List[i..] == jj.List
invariant jj == null ==> i == |ll.List|
invariant 0 !in ll.List[..i]
decreases |ll.List| - i
{
jj := jj.next;
i := i + 1;
}
r := i;
}
}
method Main()
{
var list: Node? := null;
list := list.Cons(0, list);
list := list.Cons(5, list);
list := list.Cons(0, list);
list := list.Cons(8, list);
var r := Search(list);
print "Search returns ", r, "\n";
assert r == 1;
}
}
14
2. Lexical and Low Level Grammar
As with most languages, Dafny syntax is defined in two levels. First the stream of input charac-
ters is broken up into tokens. Then these tokens are parsed using the Dafny grammar.
The Dafny grammar is designed as an attributed grammar, which is a conventional BNF-style
set of productions, but in which the productions can have arguments. The arguments control
some alternatives within the 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 in Section 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 as grammar.
15
seq<set<int>>, the grammar knows that >> is two individual > tokens terminating the nested
type parameter lists; the right shift operator >> would never be valid here. Similarly, the se-
quence ==> 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.
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)
16
The special characters are the characters in addition to alphanumeric characters that are allowed
to appear in a Dafny identifier. These are
• ' because mathematicians like to put primes on identifiers and some ML programmers 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?.
A nonidchar is any character except those that can be used in an identifier. Here the scanner
generator will interpret ANY as any unicode character. However, nonidchar is used only to mark
the end of the !in token; in this context any character other than whitespace or printable ASCII
will 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-formatting
and provide accurate source locations for error messages.) Thus comments are token separators:
a/*x*/b becomes two tokens a and b.
Comments may be nested, but note that the nesting of multi-line comments is behavior that is
different from most programming languages. In Dafny,
method m() {
/* comment
/* nested comment
*/
rest of outer comment
*/
}
is permitted; this feature is convenient for commenting out blocks of program statements that al-
ready 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
method m() {
/* var i: int;
// */ line comment
var j: int;
*/
}
and
17
method m() {
/* var i: int;
var s: string := "a*/ b";
var j: int;
*/
}
the */ inside the line comment and the string are seen as the end of the outer comment, leaving
trailing text that will provoke parsing errors.
18
/* Just a comment */
function c2(): (r: int) // Docstring about c2
ensures r > 0
{ 8 } // Just a comment
// just a comment
const c5 := 8
export
// This is the docstring of the eponymous export set intended for most clients
provides A, B, C
19
2.6. Tokens (grammar)
The Dafny tokens are defined in this section.
2.6.2. Identifiers
In general, an ident token (an identifier) is a sequence of idchar characters where the first
character is a nondigitIdChar. However tokens that fit this pattern are not identifiers if they
look like a character literal or 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
A digits 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.
A hexdigits token denotes a hexadecimal constant, and is a sequence of hexadecimal digits
(hexdigit) prefaced by 0x and possibly interspersed with underscores for readability (but not
beginning or ending with an underscore). Example: 0xffff_ffff.
A decimaldigits 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.
20
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 Unicode letters are not allowed in Dafny identifiers, Dafny does support
Unicode in its character, string, and verbatim strings constants and in its comments.
2.6.7. Ellipsis
The ellipsisToken is the character sequence ... and is typically used to designate something
missing that will later be inserted through refinement or is already present in a parent declaration.
21
destructor name, a digit sequence is treated as a string, not a number: internal underscores
matter, so 10 is different from 1_0 and from 010.
• m.requires is used to denote the precondition for method m.
• m.reads is used to denote the things that method m may read.
2.7.1.3. Wild identifier A wild identifier is a no-underscore-identifier except that the sin-
gleton _ is allowed. The _ is replaced conceptually by a unique identifier 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
22
2.7.2. Qualified Names
A qualified name starts with the name of a top-level entity and then is followed by zero or more
DotSuffixs 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 be separated by whitespace.
However, Dafny infers types in many circumstances, and in those, the type can be omitted.
The type is required for 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 described above.
23
three optional elements:
1. The optional syntax : T declares the type of the quantified variable. If not provided, it
will be inferred from context.
2. The optional syntax <- C attaches a collection expression C as a quantified variable domain.
Here a collection is any value of a type that supports the in operator, namely sets, multisets,
maps, and sequences. The domain restricts the bindings to the elements of the collection:
x <- C implies x in C. The example above can also be expressed as var c := [0, 1, 2,
3, 4, 5]; forall x <- c :: x * x <= 25.
3. The optional syntax | E attaches a boolean expression E as a quantified variable range,
which restricts the bindings to values that satisfy this expression. In the example above x
<= 5 is the range attached to the x 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 domain i | 0 <= i < |s|, y <-
s[i] | i < y, the expression s[i] is always well-formed because the range attached to i ensures
i is a valid index in the sequence s.
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 (See
Section 13.9.5).
24
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 and coinductive datatypes, newtypes, type synonyms, abstract types, and iter-
ators) 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 a Main() method. If a legal
Main() method is found, the compiler will emit an executable appropriate to the target language;
otherwise it will emit a library or individual files. The conditions for a legal Main() method are
described in the User Guide (Section 3.4). If there is more than one Main(), Dafny will emit an
error message.
An invocation of Dafny may specify a number of source files. Each Dafny file follows the grammar
of the Dafny non-terminal.
A file consists of - a sequence of optional include directives, followed by - top level declarations,
followed by - the end of the file.
Include directives have the form "include" stringToken where the string token is either a
normal string token or a verbatim string token. The stringToken is interpreted as the name
of a file that will be included in the Dafny source. These included files also obey the Dafny
grammar. Dafny parses and processes the transitive 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
listed explicitly on the command line or the --verify-included-files option is specified.
The file name may be a path using the customary /, ., and .. punctuation. The interpretation
of the name (e.g., case-sensitivity) will depend on the underlying operating system. A path not
beginning with / is looked up in the underlying file system relative to the location of the file in
which the include directive is stated. Paths beginning with a device designator (e.g., C:) are only
permitted on Windows systems. Better style advocates using relative paths in include directives
so that groups 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.
25
class C { }
datatype D = A | B
newtype pos = i: int | i >= 0
type T = i: int | 0 <= i < 100
method m() {}
function f(): int
const c: 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 of various kinds of declarations described later. Top-
level declarations are implicitly 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 declaration that is in
the top-level implicit module. These declarations are all implicitly static (and may not be
declared explicitly static).
Top level declarations may be preceded by zero or more declaration modifiers. Not all of these
are allowed in all contexts.
The abstract modifier may only be used for module declarations. An abstract module can leave
some entities underspecified. Abstract modules are not compiled.
The ghost modifier is used to mark entities as being used for specification only, not for compi-
lation to code.
The opaque modifier may be used on const declarations and functions.
The static modifier is used for class members that are associated with the class as a whole
rather than with an instance of the class. This modifier may not be used with declarations that
are implicitly static, as are members of the top-level, unnamed implicit class.
26
The following table shows modifiers that are available for each of the kinds of declaration. In the
table we use already-ghost (already-non-ghost) to denote that the item is not allowed to have
the ghost modifier because it is already implicitly ghost (non-ghost).
27
• 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 the enclosing type is a class,
then that class must not declare any constructor. In this case, the runtime system will
allocate an object of the enclosing class and will invoke the entry-point method on it.
• If the method is an instance (that is, non-static) method and the enclosing type is not
a class, then the enclosing type must, when instantiated with auto-initializing type pa-
rameters, be an auto-initializing type. In this case, the runtime system will invoke 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 a decreases *. (If Main()
has a decreases *, then its execution may go on forever, but in the absence of a decreases
* on Main(), dafny will have verified that the entire execution will eventually terminate.)
If no legal candidate entry point is identified, dafny will still produce executable output files,
but they will need to be linked with some other code in the target language that provides a main
entry point.
If the Main method takes an argument (of type seq<string>), the value of that input argument
is the sequence of 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.
28
4. Modules (grammar)
Examples:
module N { }
import A
export A reveals f
Structuring a program by breaking it into parts is an important part of creating large programs.
In Dafny, this is accomplished via modules. Modules provide a way to group together related
types, classes, methods, functions, and other modules, as well as to control the scope of declara-
tions. Modules may import each other for code reuse, and it is possible to abstract over modules
to separate an implementation from an interface.
Module declarations are of three types: - a module definition - a module import - a module
export definition
Module definitions and imports each declare a submodule of its enclosing module, which may be
the implicit, undeclared, top-level module.
A module 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 top level: classes, datatypes,
types, methods, functions, etc.
module Mod {
class C {
var f: int
method m()
}
datatype Option = A(int) | B(int)
type T
method m()
function f(): int
}
29
class C {
method doIt()
var f: int
}
}
}
Then you can refer to the members of the Helpers module within the Mod module by prefixing
them with “Helpers.”. For example:
module Mod {
module Helpers {
class C {
constructor () { f := 0; }
method doIt()
var f: int
}
}
method m() {
var x := new Helpers.C();
x.doIt();
x.f := 4;
}
}
Methods and functions defined at the module level are available like classes, with just the module
name prefixing them. They are also available in the methods and functions of the classes in the
same module.
module Mod {
module Helpers {
function addOne(n: nat): nat {
n + 1
}
}
method m() {
var x := 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 part of a single implicit unnamed global module.
30
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 module outside of its “containing” module. So instead of
module A {
module B {
}
}
The second module is completely separate; for example, it can be in a different file. This feature
provides flexibility in writing and maintenance; for example, it can reduce the size of module A
by extracting module A.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 module A that module A.B exists; the existence of A.B might cause names
to be resolved differently and the semantics of the program might be (silently) different if A.B
is present or absent.
Sometimes you want to refer to things from an existing module, such as a library. In this case,
you can import one module into another. This is done via the import keyword, which has two
forms with different meanings. The simplest form is the concrete import, which has the form
import A = B. This declaration creates a reference to the module B (which must already exist),
and binds it to the new local name A. This form can also be used to create a reference to a nested
module, as in import A = B.C. The other form, using a :, is described in Section 4.6.
As modules in the same scope must have different names, this ability to bind a module to a new
name allows disambiguating separately developed external modules that have the same name.
Note that the new name is only bound in the scope containing the import declaration; it does
not create a global alias. For example, if Helpers was defined outside of Mod, then we could
import it:
31
module Helpers {
function addOne(n: nat): nat {
n + 1
}
}
module Mod {
import A = Helpers
method m() {
assert A.addOne(5) == 6;
}
}
Note that inside m(), we have to use A instead of Helpers, as we bound it to a different name.
The name Helpers is not available inside m() (or anywhere else inside Mod), as only names that
have been bound inside Mod are available. In order to use the members from another module,
that other module either has to be declared there with module or imported with import. (As
described below, the resolution of the ModuleQualifiedName that follows the = in the import
statement or the refines in a module declaration uses slightly different rules.)
We don’t have to give Helpers a new name, though, if we don’t want to. We can write import
Helpers = Helpers to import the module under its own name; Dafny even provides the short-
hand import Helpers for this behavior. You can’t bind two modules with the same name at the
same time, so sometimes you have to use the = version to ensure the names do not clash. When
importing nested modules, import B.C means import C = 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 constitute the 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, only sibling 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 a
shorthand name, to a module. For example,
module MyModule { } // declare MyModule
import MyModule // error: cannot add a module named MyModule
// because there already is one
import M = MyModule // OK. M and MyModule are equivalent
32
previous example as:
module Helpers {
function addOne(n: nat): nat {
n + 1
}
}
module Mod {
import opened Helpers
method m() {
assert addOne(5) == 6;
}
}
When opening modules, the newly bound members have lower priority than local definitions.
This means if you define a local function called addOne, the function from Helpers will no
longer be available under that name. When modules are opened, the original name binding is
still present however, so you can always use the name that was bound to get to anything that is
hidden.
module Helpers {
function addOne(n: nat): nat {
n + 1
}
}
module Mod {
import opened H = Helpers
function addOne(n: nat): nat {
n - 1
}
method m() {
assert addOne(5) == 6; // this is now false,
// as this is the function just defined
assert H.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 it would be ambiguous which one was meant.
Just opening the two modules is not an error, however, as long as you don’t attempt to use
members with common names. However, if the ambiguous references actually refer to the same
declaration, then they are permitted. The opened keyword may be used with any kind of import
declaration, including the module abstraction form.
An import opened may occur at the top-level as well. For example,
module MyModule { } // declares MyModule
import opened MyModule // does not declare a new module, but does
33
// make all names in MyModule available in
// the current scope, without needing
// qualification
import opened M = 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 obvious and unambiguous meanings and when using qualified
names would be verbose enough to impede understanding.
There is a special case in which the behavior described above is altered. If a module M declares a
type M and M is import opened without renaming inside another module X, then the rules above
would have, within X, M mean the module and M.M mean the type. This is verbose. So in this
somewhat common case, the type M is effectively made a local declaration within X so that it has
precedence over the module name. Now M refers to the type. If one needs to refer to the module,
it will have to be renamed as part of the import opened statement.
This special-case behavior does give rise to a source of ambiguity. Consider the example
module Option {
const a := 1
datatype Option = A|B { static const a := 2 }
}
module X {
import opened Option
method M() { print Option.a; }
}
Option.a now means the a in the datatype instead of the a in the module. To avoid confusion
in such cases, it is an ambiguity error if a name that is declared in both the datatype and the
module is used when there is an import open of the module (without renaming).
In some programming languages, keywords such as public, private, and protected are used
to control access to (that is, visibility of) declared program entities. In Dafny, modules and
34
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 different kinds of access to the module’s declarations. Each export
set designates a list of names, which must be names 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 the module using the import mechanism. An export set enables a module
to disallow the use of some declarations outside the module.
An export set has an optional name used to disambiguate in case of multiple export sets; If
specified, such names are used in import statements to designate which export set of a module
is being imported. If a module M has export sets E1 and E2, we can write import A = M`E1
to create a module alias A that contains only the names in E1. Or we can write import A =
M`{E1,E2} to import the union of names in E1 and E2 as module alias A. As before, import
M`E1 is an abbreviation of import M = M`E1.
If no export set is given in an import statement, the default export set of the module is used.
There are various defaults that apply differently in different cases. The following description is
with respect to an example module M:
M has no export sets declared. Then another module may simply import Z = 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 can write import Z =
M`E or import Z = M`{E,F} to obtain access to the names that are listed in export set E or to
the union of those in export sets E and F, 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 write simply
import Z = M.
M has an unnamed export set, along with other export sets (e.g., named E). The unnamed export
set is the default export set and implicitly has the same name as the module. Because there
is a default export set, another module may write either import Z = M or import Z = M`M to
import the names in that default export set. You can also still use the other export sets with
the explicit designator: import Z = M`E
M declares an export set with the same name as the module. This is equivalent to declaring an
export set without a name. import M and import M`M perform the same function in either case;
the export set with or without the name of the module is the default export set for the module.
Note that names of module aliases (declared by import statements) are just like other names
in a module; they can be included or omitted from export sets. Names brought into a module
by refinement are treated the same as locally declared names and can be listed in export set
declarations. However, names brought into a module by import opened (either into a module
or a refinement parent of a module) may not be further exported. For example,
module A {
const a := 10
35
const z := 10
}
module B {
import opened Z = A // includes a, declares Z
const b := Z.a // OK
}
module C {
import opened B // includes b, Z, but not a
method m() {
//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 known
assert b == Z.a; // OK: module Z is known and includes a
}
}
36
}
provokes a warning telling the user that the least goes with the export.
module B {
import A
method m() {
assert A.a == 10; // a is known, but not its value
// assert A.b == 20; // b is not known through A`A
}
}
Since a is imported into module B through the default export set A`A, it can be referenced in the
assert statement. The constant b is not exported, so it is not available. But the assert about a
is not provable because the value of a is not known in module B.
In contrast, if a is exported as revealed, as shown in the next example, its value is known and
the assertion can be proved.
module A {
export reveals a
const a := 10
const b := 20
}
module B {
import A
method m() {
assert A.a == 10; // a and its value are known
// assert A.b == 20; // b is not known through A`A
}
}
37
The following table shows which parts of a declaration are exported by an export set that
provides or reveals 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... | |
38
} | |
---------------------|---------------------|---------------------
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, twostate function) are handled like function above,
and variations of methods (e.g., lemma and twostate lemma) are treated like method above.
Since the whole signature is exported, a function or method is exported to be of the same kind,
even through provides. For example, an exported twostate lemma is exported as a twostate
lemma (and thus is known by importers to have two implicit heap parameters), and an exported
least predicate P is exported as a least predicate P (and thus importers can use both P
and its prefix predicate P#).
39
If C is a class, trait, or iterator, then provides C exports the non-null reference type C as
an abstract type. This does not reveal that C is a reference type, nor does it export the nullable
type C?.
In most cases, exporting a class, trait, datatype, codatatype, or abstract type does not
automatically export its members. Instead, any member to be exported must be listed explicitly.
For example, consider the type declaration
trait Tr {
function F(x: int): int { 10 }
function G(x: int): int { 12 }
function H(x: int): int { 14 }
}
An export set that contains only reveals Tr has the effect of exporting
trait Tr {
}
and an export set that contains only provides Tr, Tr.F reveals Tr.H has the effect of ex-
porting
type Tr {
function F(x: int): int
function H(x: int): int { 14 }
}
module M {
40
export
provides Lib
provides xyz // error: 'xyz' is not declared locally
const k0 := Lib.xyz
const k1 := xyz
}
module Client {
import opened M
const a0 := M.Lib.xyz
const a1 := Lib.xyz
const a2 := M.xyz // error: M does not have a declaration 'xyz'
const a3 := xyz // error: unresolved identifier 'xyz'
}
As highlighted in this example, module M can use xyz as if it were a local name (see declaration
k1), but the unqualified name xyz is not made available to importers of M (see declarations a2
and a3), nor is it possible for M to export the name xyz.
A few other notes:
• A provides list can mention *, which means that all local names (except export set names)
in the module are exported as provides.
• A reveals list can mention *, which means that all local names (except export set names)
in the module are exported as reveals, if the declaration is allowed to appear in a reveals
clause, or as provides, if the declaration is not allowed to appear in a reveals clause.
• If no export sets are declared, then the implicit export set is export reveals *.
• A refinement module acquires all the export sets from its refinement parent.
• Names acquired by a module from its refinement parent are also subject to export lists.
(These are local names just like those declared directly.)
41
export A reveals a
export B reveals b
export C extends A, B
reveals c
}
We can be more precise if we know that addSome actually adds exactly one. The following
module has this behavior. Further, the postcondition is stronger, so this is actually a refinement
of the Interface module.
module Implementation {
function addSome(n: nat): nat
ensures addSome(n) == n + 1
{
n + 1
}
}
42
abstract module Mod {
import A : Interface
method m() {
assert 6 <= A.addSome(5);
}
}
module Implementation {
function addSome(n: nat): nat
ensures addSome(n) == n + 1
{
n + 1
}
}
module Mod2 refines Mod {
import A = Implementation
...
}
When you refine an abstract import into a concrete one Dafny checks that the concrete module is
a refinement of the abstract one. This means that the methods must have compatible signatures,
all the classes and datatypes with their constructors and fields in the abstract one must be
present in the concrete one, the specifications must be compatible, etc.
A module that includes an abstract import must be declared abstract.
You can have import statements at the toplevel and you can import modules defined at the same
level:
import A = B
method m() {
A.whatever();
}
module B { method whatever() {} }
In this case, everything is well defined because we can put B first, followed by the A import, and
then finally m(). If there is no permitted ordering, then Dafny will give an error, complaining
43
about a cyclic dependency.
Note that when rearranging modules and imports, they have to be kept in the same containing
module, which disallows some pathological module structures. Also, the imports and submodules
are always considered to be before their containing module, even at the toplevel. This means
that the following is not well formed:
method doIt() { }
module M {
method m() {
doIt(); // error: M precedes doIt
}
}
because the module M must come before any other kind of members, such as methods. To define
global functions like this, you can put them in a module (called Globals, say) and open it into
any module that needs its functionality. Finally, if you import via a path, such as import A =
B.C, then this creates a dependency of A on B, and B itself depends on its own nested module
B.C.
44
Within each namespace, the local names are unique. Thus a module may not reuse a name
that a refinement parent has declared (unless it is a refining declaration, which replaces both
declarations, as described in Section 10).
Local names take precedence over imported names. If a name is used more than once among
imported names (coming from different imports), then it is ambiguous to use the name without
qualification.
45
In the example, the A in refines A refers to the global A, not the submodule A.
46
• If E denotes an expression:
4. Let T be the type of E. Look up id in T.
47
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 and coinductive datatypes,
* function (arrow) types, and * types, such as subset types, derived from other types.
48
The following sections describe each of these kinds of types in more detail.
49
5.2. Basic types
Dafny offers these basic types: bool for booleans, char for characters, int and nat for integers,
real for reals, ORDINAL, and bit-vector types.
Negation is unary; the others are binary. The table shows the operators in groups of increasing
binding power, with equality binding stronger than conjunction and disjunction, and weaker
than negation. Within each group, different operators do not associate, so parentheses need to
be used. For example,
A && B || C // error
or
A && (B || C)
5.2.1.1. Equivalence Operator The expressions A <==> B and A == B give the same value,
but note that <==> is associative whereas == is chaining and they have different precedence. So,
A <==> B <==> C
is the same as
50
A <==> (B <==> C)
and
(A <==> B) <==> C
whereas
A == 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 are short circuiting (from left to right), meaning that their second argument is evaluated
only if the evaluation of the first operand does not determine the value of the expression. Logically
speaking, the expression A && B is defined when A is defined and either A evaluates to false
or B is defined. When A && B is defined, its meaning is the same as the ordinary, symmetric
mathematical conjunction &. The same holds for || and |.
5.2.1.3. Implication and Reverse Implication Implication is right associative and is short-
circuiting from left to right. Reverse implication B <== A is exactly the same as A ==> B, but
gives the ability to write the operands in the opposite order. Consequently, reverse implication
is left associative and is short-circuiting from right to left. To illustrate the associativity rules,
each of the following four lines expresses the same property, for any A, B, and C of type bool:
A ==> B ==> C
A ==> (B ==> C) // parentheses redundant, ==> is right associative
C <== B <== A
(C <== B) <== A // parentheses redundant, <== is left associative
To illustrate the short-circuiting rules, note that the expression a.Length is defined for an array
a only if a is not null (see Section 5.1.2), which means the following two expressions are well-
formed:
a != null ==> 0 <= a.Length
0 <= a.Length <== a != null
51
but these expressions might not necessarily be well-formed, since well-formedness requires the
left (and right, respectively) operand, a.Length < 0, to be well-formed in their context.
Implication A ==> B is equivalent to the disjunction !A || B, but is sometimes (especially in
specifications) clearer to read. Since, || is short-circuiting from left to right, note that
a == null || 0 <= a.Length
For integers (in both decimal and hexadecimal form) and reals, any two digits in a literal may be
separated by an underscore in order to improve human readability of the literals. For example:
const c1 := 1_000_000 // easier to read than 1000000
const c2 := 0_12_345_6789 // strange but legal formatting of 123456789
const c3 := 0x8000_0000 // same as 0x80000000 -- hex digits are
// often placed in groups of 4
const c4 := 0.000_000_000_1 // same as 0.0000000001 -- 1 Angstrom
52
In addition to equality and disequality, numeric types support the following relational operations,
which have the same precedence as equality:
operator description
< less than
<= at most
>= at least
> greater than
Like equality and disequality, these operators are chaining, as long as they are chained in the
“same direction”. That is,
A <= B < C == D <= E
whereas
A < B > C
is not allowed.
There are also operators on each numeric type:
The binary operators are left associative, and they associate with each other in the two groups.
The groups are listed in order of increasing binding power, with equality binding less strongly
than any of these operators. There is no implicit conversion between int and real: use as int
or as real conversions to write an explicit conversion (cf. Section 9.10).
Modulus is supported only for integer-based numeric types. Integer division and modulus are
the Euclidean division and modulus. This means that modulus always returns a non-negative
value, regardless of the signs of the two operands. More precisely, for any integer a and non-zero
integer b,
53
a == a / b * b + a % b
0 <= a % b < B
Note in the third line that member access (like .Floor) binds stronger than unary minus. The
fourth line uses the conversion function as real from int to real, as described in Section 9.10.
Bit-vector values can be converted to and from int and other bit-vector types, as long as the
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 operations truncate the high-order bits from
the results; that is, they perform unsigned arithmetic modulo 2^{number of bits}, like 2’s-
complement machine arithmetic.
54
operator precedence description
—————– ————————————
+ 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.1 All 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 an int value, must be non-negative, and no
more than the number of bits in the type. There is no signed right shift as all bit-vector values
correspond to non-negative integers.
Bit-vector negation returns an unsigned value in the correct range for the type. It has the
properties x + (-x) == 0 and (!x) + 1 == -x, for a bitvector value x of at least one bit.
The argument of the RotateLeft and RotateRight operations is a non-negative int
that is no larger than the bit-width of the value being rotated. RotateLeft moves
bits to higher bit positions (e.g., (2 as bv4).RotateLeft(1) == (4 as bv4) and (8 as
bv4).RotateLeft(1) == (1 as bv4)); RotateRight moves bits to lower bit positions, so
b.RotateLeft(n).RotateRight(n) == b.
Here are examples of the various operations (all the assertions are true except where indicated):
const i: bv4 := 9
const j: bv4 := 3
method m() {
assert (i & j) == (1 as bv4);
assert (i | j) == (11 as bv4);
assert (i ^ j) == (10 as bv4);
assert !i == (6 as bv4);
1 The binding power of shift and bit-wise operations is different than in C-like languages.
55
assert -i == (7 as bv4);
assert (i + i) == (2 as bv4);
assert (j - i) == (10 as bv4);
assert (i * j) == (11 as bv4);
assert (i as int) / (j as int) == 3;
assert (j << 1) == (6 as bv4);
assert (i << 1) == (2 as bv4);
assert (i >> 1) == (4 as bv4);
assert i == 9; // auto conversion of literal to bv4
assert i * 4 == j + 8 + 9; // arithmetic is modulo 16
assert i + j >> 1 == (i + j) >> 1; // + - bind tigher than << >>
assert i + j ^ 2 == i + (j^2);
assert i * j & 1 == i * (j&1); // & | ^ bind tighter than + - *
}
method m() {
assert i & 4 | j == 0 ; // parentheses required
}
const k: bv4 := 9
method p() {
assert k as bv5 == 9 as bv6; // error: mismatched types
}
method m() {
assert i as bv3 == 1; // error: i is out of range for bv3
}
const j: bv4 := 9
method n() {
assert j == 25; // error: 25 is out of range for bv4
}
Bit-vector constants (like all constants) can be initialized using expressions, but pay attention
to how type inference applies to such expressions. For example,
56
const a: bv3 := -1
is legal because Dafny interprets -1 as a bv3 expression, because a has type bv3. Consequently
the - is bv3 negation and the 1 is a bv3 literal; the value of the expression -1 is the bv3 value
7, which is then the value of a.
On the other hand,
const b: bv3 := 6 & 11
is illegal because, again, the & is bv3 bit-wise-and and the numbers must be valid bv3 literals.
But 11 is not a valid bv3 literal.
57
Character literals are enclosed in single quotes, as in 'D'. To write a single quote as a character
literal, it is necessary to use an escape sequence. Escape sequences can also be used to write
other characters. The supported escape sequences are the following:
The escape sequence for a double quote is redundant, because '"' and '\"' denote the same
character—both forms are provided in order to support the same escape sequences in string
literals (Section 5.5.3.5).
In the form \uxxxx, which is only allowed if --unicode-char is disabled, the u is always lower
case, but the four hexadecimal digits are case insensitive.
In the form \U{x..x}, which is only allowed if --unicode-char is enabled, the U is always upper
case, but the hexadecimal digits are case insensitive, and there must be 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 standard relational operators:
operator description
< less than
<= at most
>= at least
> greater than
58
5.3. Type parameters (grammar)
Examples:
type G1<T>
type G2<T(0)>
type G3<+T(==),-U>
Many of the types, functions, and methods in Dafny can be parameterized by types. These type
parameters are declared inside angle brackets and can stand for any type.
Dafny has some inference support that makes certain signatures less cluttered (described in
Section 12.2).
will give an error because it is not known whether the type Q is non-empty (00). Instead, one
needs to write
class A<T(00)> {}
type Q(00)
const a: A?<Q> := null
59
For example,
method Compare<T(==)>(a: T, b: T) returns (eq: bool)
{
if a == b { eq := true; } else { eq := false; }
}
is a method whose type parameter is restricted to equality-supporting types when used in a non-
ghost context. Again, note that all types support equality in ghost contexts; the difference is
only for non-ghost (that is, compiled) code. Coinductive datatypes, arrow types, and inductive
datatypes with ghost parameters are examples of types that are not equality supporting.
constructor () {
new; // error: field 'x' has not been given a value`
assert n >= 0; // true, regardless of the value of 'n'
assert i >= 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 initialized in the constructor
because they are auto-initialized to an arbitrary value.
Local variables and out-parameters are however, subject to definite assignment rules. The fol-
lowing example requires --relax-definite-assignment, which is not the default.
method m() {
var n: nat; // Auto-initialized to an arbitrary value of type `nat`
assert n >= 0; // true, regardless of the value of n
var i: int;
assert i >= 0; // possibly false, arbitrary ints may be negative
}
With the default behavior of definite assignment, n and i need to be initialized to an explicit
value of their type or to an arbitrary value using, for example, var n: nat := *;.
For some types (known as auto-init types), the compiler can choose an initial value, but for others
it does not. Variables and fields whose type the compiler does not auto-initialize are subject to
60
definite-assignment rules. These ensure that the program explicitly assigns a value to a variable
before it is used. For more details see Section 12.6 and the --relax-definite-assignment
command-line option. More detail on auto-initializing is in this 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,
method AutoInitExamples<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-parameter x has not been assigned—since
nothing is known about type X, variables of type X are subject to definite-assignment rules. In
contrast, since type parameter A is declared to be restricted to auto-init types, the program does
not need to explicitly assign any value to the out-parameter a.
5.3.1.3. Nonempty types: T(00) Auto-init types are important in compiled contexts. In
ghost contexts, it may still be important to know that a type is nonempty. Dafny supports
a type characteristic for nonempty types, written with the suffix (00). For example, with
--relax-definite-assignment, the following example happens:
method NonemptyExamples<B(00), X>() returns (b: B, ghost g: B, ghost h: 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 of B’s nonempty type characteristic, ghost parameter g does not need to be explicitly
assigned. However, Dafny reports an error for the non-ghost b, since B is not an auto-init type,
and reports an error for h, since the type X 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 see Section 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, like classes and arrays, and those that are strictly value-based,
like basic types and datatypes. The practical implication is that references depend on allocation
state (e.g., are affected by the old operation) whereas non-reference values are not. Thus it
can be relevant to know whether the values of a type parameter are heap-based or not. This is
indicated by the mode suffix (!new).
A type parameter characterized by (!new) is recursively independent of the allocation state. For
example, a datatype is not a reference, but for a parameterized data type such as
61
datatype Result<T> = Failure(error: string) | Success(value: T)
class C {}
method m() {
var x1: Result<int>;
var x2: ResultN<int>;
var x3: Result<C>;
var x4: ResultN<C>; // error
var x5: Result<array<int>>;
var x6: ResultN<array<int>>; // error
}
62
as '-X' or '!X') The type X -> bool has strictly more values than the type X. This affects
certain uses of the type, so Dafny requires the declaration of T to explicitly say so. Marking the
type parameter X with - or ! announces that the cardinality of T<X> may by larger than that
of X. If you use -, you’re also declaring T to be contravariant in its type argument, and if you
use !, you’re declaring that T is non-variant in its type argument.
To fix it, we use the variance !:
This states that T does not preserve the cardinality of X, meaning there could be strictly more
values of type T<E> than values of type E for any E.
A more detailed explanation of these topics is here.
63
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 try to fill
these in (cf. Section 12.2).
64
5.5. Collection types
Dafny offers several built-in collection types.
are three examples of set displays. There is also a set comprehension expression (with a binder,
like in logical quantifications), described in Section 9.31.5.
In addition to equality and disequality, set types support the following relational operations:
operator precedence
< 4
<= 4
>= 4
> 4
The associativity rules of +, -, and * are like those of the arithmetic operators with the same
names. The expression A !! B, whose binding power is the same as equality (but which neither
associates nor chains with equality), says that sets A and B have no elements in common, that
is, it is equivalent to
65
A * B == {}
However, the disjointness operator is chaining though in a slightly different way than other
chaining operators: A !! B !! C !! D means that A, B, C and D are all mutually disjoint, that
is
A * B == {} && (A + B) * C == {} && (A + B + C) * D == {}
In addition, for any set s of type set<T> or iset<T> and any expression e of type T, sets support
the following operations:
operator precedence
< 4
<= 4
>= 4
> 4
66
Like the arithmetic relational operators, these operators are chaining.
Multisets support the following binary operators, listed in order of increasing binding power:
The associativity rules of +, -, and * are like those of the arithmetic operators with the same
names. The + operator adds the multiplicity of corresponding elements, the - operator subtracts
them (but 0 is the minimum multiplicity), and the * has multiplicity that is the minimum of the
multiplicity of the operands. There is no operator for multiset union, which would compute the
maximum of the multiplicities of the operands.
The expression A !! B says that multisets A and B 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 multiset s of type multiset<T>, expression e of type T, and non-negative
integer-based numeric n, multisets support the following operations:
The expression e in s returns true if and only if s[e] != 0. The expression e !in s is a
syntactic shorthand for !(e in s). The expression s[e := n] denotes a multiset like s, but
where the multiplicity of element e is n. Note that the multiset update s[e := 0] results in a
multiset like s but without any occurrences of e (whether or not s has occurrences of e in the
first place). As another example, note that s - multiset{e} is equivalent to:
if e in s then s[e := s[e] - 1] else s
67
5.5.3.1. Sequence Displays A sequence can be formed using a sequence display expression,
which is a possibly empty, ordered list of expressions enclosed in square brackets. To illustrate,
[] [3, 1, 4, 1, 5, 9, 3] [4+2, 1+5, a*b]
operator precedence
< 4
<= 4
Like the arithmetic relational operators, these operators are chaining. Note the absence of > and
>=.
operator precedence
+ 6
Operator + is associative, like the arithmetic operator with the same name.
5.5.3.4. Other Sequence Expressions In addition, for any sequence s of type seq<T>,
expression e of type T, integer-based numeric index i satisfying 0 <= i < |s|, and integer-
based numeric bounds lo and hi satisfying 0 <= lo <= hi <= |s|, noting that bounds can
equal the length of the sequence, sequences support the following operations:
result
expression precedencetype description
e in s 4 bool sequence membership
e !in s 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
68
result
expression precedencetype description
s[lo..] 11 seq<T> drop
s[..hi] 11 seq<T> take
s[slices] 11 slice
seq<seq<T>>
multiset(s) 11 sequence conversion to a multiset<T>
multiset<T>
Expression s[i := e] returns a sequence like s, except that the element at index i is e. The
expression e in s says there exists an index i such that s[i] == e. It is allowed in non-ghost
contexts only if the element type T is equality supporting. The expression e !in s is a syntactic
shorthand for !(e in s).
Expression s[lo..hi] yields a sequence formed by taking the first hi elements and then dropping
the first lo elements. The resulting sequence thus has length hi - lo. Note that s[0..|s|]
equals s. If the upper bound is omitted, it defaults to |s|, so s[lo..] yields the sequence
formed by dropping the first lo elements of s. If the lower bound is omitted, it defaults to 0, so
s[..hi] yields the sequence formed by taking the first hi elements of s.
In the sequence slice operation, slices is a nonempty list of length designators separated and
optionally terminated by a colon, and there is at least one colon. Each length designator is a
non-negative integer-based numeric; the sum of the length designators is no greater than |s|.
If there are k colons, the operation produces k + 1 consecutive subsequences from s, with the
length of each indicated by the corresponding length designator, and returns these as a sequence
of sequences. If slices is terminated by a colon, then the length of the last slice extends until the
end of s, that is, its length is |s| minus the sum of the given length designators. For example,
the following equalities hold, for any sequence s of length at least 10:
method m(s: seq<int>) {
var t := [3.14, 2.7, 1.41, 1985.44, 100.0, 37.2][1:0:3];
assert |t| == 3 && t[0] == [3.14] && t[1] == [];
assert t[2] == [2.7, 1.41, 1985.44];
var u := [true, false, false, true][1:1:];
assert |u| == 3 && u[0][0] && !u[1][0] && u[2] == [false, true];
assume |s| > 10;
assert s[10:][0] == s[..10];
assert s[10:][1] == s[10..];
}
The operation multiset(s) yields the multiset of elements of sequence s. It is allowed in non-
ghost contexts only if the element type T is equality supporting.
5.5.3.5. Strings (grammar) A special case of a sequence type is seq<char>, for which Dafny
provides a synonym: string. Strings are like other sequences, but provide additional syntax for
sequence display expressions, namely string literals. There are two forms of the syntax for string
literals: the standard form and the verbatim form.
69
String literals of the standard form are enclosed in double quotes, as in "Dafny". To include a
double quote in such a string literal, it is necessary to use an escape sequence. Escape sequences
can also be used to include other characters. The supported escape sequences are the same as
those for character literals (Section 5.2.5). For example, the Dafny expression "say \"yes\""
represents the string 'say "yes"'. The escape sequence for a single quote is redundant, because
"\'" and "\'" denote the same string—both forms are provided in order to support the same
escape sequences as do character literals.
String literals of the verbatim form are bracketed by @" and ", as in @"Dafny". To include a
double quote in such a string literal, it is necessary to use the escape sequence "", that is, to
write the character twice. 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 operators still denote proper prefix and prefix, respectively, not some kind of alphabetic
comparison as might be desirable, for example, when sorting strings.
are three examples of map displays. By using the keyword imap instead of map, the map produced
will be of type imap<T,U> instead of map<T,U>. Note that an infinite map (imap) is allowed to
have a finite domain, whereas a finite map (map) is not allowed to have an infinite domain. If the
same key occurs more than once in a map display expression, only the last occurrence appears in
70
the resulting map.2 There is also a map comprehension expression, explained in Section 9.31.8.
For any map fm of type map<T,U>, any map m of type map<T,U> or imap<T,U>, any expression t
of type T, any expression u of type U, and any d in the domain of m (that is, satisfying d in m),
maps support the following operations:
|fm| denotes the number of mappings in fm, that is, the cardinality of the domain of fm. Note
that the cardinality operator is not supported for infinite maps. Expression m[d] returns the U
value that m associates with d. Expression m[t := u] is a map like m, except that the element
at key t is u. The expression t in m says t is in the domain of m and t !in m is a syntactic
shorthand for !(t in m).3
The expressions m.Keys, m.Values, and m.Items return, as sets, the domain, the range, and the
2-tuples holding the key-value associations in the map. Note that m.Values will have a different
cardinality than m.Keys and m.Items if different keys are associated with the same value. If m
is an imap, then these expressions return iset values. If m is a map, m.Values and m.Items
require the type of the range U to support equality.
Here is a small example, where a map cache of type map<int,real> is used to cache computed
values of Joule-Thomson coefficients for some fixed gas at a given temperature:
if K in cache { // check if temperature is in domain of cache
coeff := cache[K]; // read result in cache
} else {
coeff := ComputeJTCoefficient(K); // do expensive computation
cache := 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.
2 Thisis likely to change in the future to disallow multiple occurrences of the same key.
3 Thisis likely to change in the future as follows: The in and !in operations will no longer be supported on
maps, with x in m replaced by x in m.Keys, and similarly for !in.
71
The - operator implements a map difference operator. Here the LHS is a map<K,V> or imap<K,V>
and the RHS is a set<K> (but not an iset); the operation removes from 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 might compli-
cate Dafny’s prover finding proofs, not all properties of maps are known by the prover by default.
For example, the following does not prove:
method mmm<K(==),V(==)>(m: map<K,V>, k: K, v: V) {
var mm := m[k := v];
assert v in mm.Values;
}
Rather, one must provide an intermediate step, which is not entirely obvious:
method mmm<K(==),V(==)>(m: map<K,V>, k: K, v: V) {
var mm := m[k := v];
assert k in mm.Keys;
assert v in mm.Values;
}
5.5.5.1. Sequences and arrays Sequences and arrays are indexable and have a length. So
the idiom to iterate over the contents is well-known. For an array:
method m(a: array<int>) {
var i := 0;
var sum := 0;
while i < a.Length {
sum := sum + a[i];
i := i + 1;
}
}
72
}
}
The forall statement (Section 8.21) can also be used with arrays where parallel assignment is
needed:
method m(s: array<int>) {
var rev := new int[s.Length];
forall i | 0 <= i < s.Length {
rev[i] := s[s.Length-i-1];
}
}
5.5.5.2. Sets There is no intrinsic order to the elements of a set. Nevertheless, we can extract
an arbitrary element of a nonempty set, performing an iteration as follows:
method m(s: set<int>) {
var ss := s;
while ss != {}
decreases |ss|
{
var i: int :| i in ss;
ss := ss - {i};
print i, "\n";
}
}
Because isets may be infinite, Dafny does not permit iteration over an iset.
5.5.5.3. Maps Iterating over the contents of a map uses the component sets: Keys, Values,
and Items. The iteration loop follows the same patterns as for sets:
method m<T(==),U(==)> (m: map<T,U>) {
var items := m.Items;
while items != {}
decreases |items|
{
var item :| item in items;
items := items - { item };
print item.0, " ", item.1, "\n";
}
}
There are no mechanisms currently defined in Dafny for iterating over imaps.
73
5.6. Types that stand for other types (grammar)
It is sometimes useful to know a type by several names or to treat a type abstractly. There are
several mechanisms in Dafny to do this:
• (Section 5.6.1) A typical synonym type, in which a type name is a synonym for another
type
• (Section 5.6.2) An abstract type, in which a new type name is declared as an uninterpreted
type
• (Section 5.6.3) A subset type, in which a new type name is given to a subset of the values
of a given type
• (Section 5.7) A newtype, in which a subset type is declared, but with restrictions on con-
verting to and from its base type
type T = int
type SS<T> = set<set<T>>
declares Y<T> to be a synonym for the type G. If the = G is omitted then the declaration just
declares a name as an uninterpreted abstract type, as described in Section 5.6.2. Such types may
be given a definition elsewhere in the Dafny program.
Here, T is a nonempty list of type parameters (each of which optionally has a type characteristics
suffix), which can be used as free type variables in G. 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 messages produced, between Y<T> and G.
For example, the names of the following type synonyms may improve the readability of a program:
type Replacements<T> = map<T,T>
type Vertex = int
The new type name itself may have type 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:
type Z(==)<T(0)>
As already described in Section 5.5.3.5, string is a built-in type synonym for seq<char>, as if
it would have been declared as follows:
type string_(==,0,!new) = seq<char>
If the implicit declaration did not include the type characteristics, they would be inferred in any
case.
74
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
datatype Pair<T> = Pair(first: T, second: T)
type IntPair = Pair<int>
In the declaration of q, IntPair is the name of a type, not the name of a function or datatype
constructor.
An abstract type is a special case of a type synonym that is underspecified. Such a type is
declared simply by:
type Y<T>
Its definition can be stated in a refining module. The name Y can be immediately followed
by a type characteristics suffix (Section 5.3.1). Because there is no defining RHS, the type
characteristics cannot be inferred and so must be stated. If, in some refining module, a definition
of the type is given, the type characteristics must match those of the new definition.
For example, the declarations
type T
function F(t: T): T
can be used to model an uninterpreted function F on some arbitrary type T. As another example,
type Monad<T>
module X refines P {
newtype T = i | 0 <= i < 10 {
75
function ToString(): string { "" }
}
}
The abstract type P.T has a declared member ToString, which can be called wherever P.T may
be used. In the refining module X, T is declared to be a newtype, in which ToString now has a
body.
It would be an error to refine P.T as a simple type synonym or subset type in X, say type T =
int, because type synonyms may not have members.
A subset type is a restricted use of an existing type, called the base type of the subset type. A
subset type is like a combined use of the base type and a predicate on the base type.
An assignment from a subset type to its base type is always allowed. An assignment in the other
direction, from the base type to a subset type, is allowed provided the value assigned does indeed
satisfy the predicate of the subset type. This condition is checked by the verifier, not by the type
checker. Similarly, assignments from one subset type to another (both with the same base type)
are also permitted, as long as it can be established that the value being assigned satisfies the
predicate defining the receiving subset type. (Note, in contrast, assignments between a newtype
and its base type are never allowed, even if the value assigned is a value of the target type. For
such assignments, an explicit conversion must be used, see Section 9.10.)
The declaration of a subset type permits an optional witness clause, to declare that there is
a value that satisfies the subset type’s predicate; that is, the witness clause establishes that
the defined type is not empty. The compiler may, but is not obligated to, use this value when
auto-initializing a newly declared variable of the subset type.
Dafny builds in three families of subset types, as described next.
5.6.3.1. Type nat The built-in type nat, which represents the non-negative integers (that is,
the natural numbers), is a subset type:
type nat = n: int | 0 <= n
A simple example that puts subset type nat to good use is the standard Fibonacci function:
function Fib(n: nat): nat
{
76
if n < 2 then n else Fib(n-2) + Fib(n-1)
}
An equivalent, but clumsy, formulation of this function (modulo the wording of any error mes-
sages produced at call sites) would be to use type int and to write the restricting predicate in
pre- and postconditions:
function Fib(n: int): int
requires 0 <= n // the function argument must be non-negative
ensures 0 <= Fib(n) // the function result is non-negative
{
if n < 2 then n else Fib(n - 2) + Fib(n - 1)
}
5.6.3.2. Non-null types Every class, trait, and iterator declaration C gives rise to two types.
One type has the name C? (that is, the name of the class, trait, or iterator declaration with a ?
character appended to the end). The values of C? are the references to C objects, and also the
value null. In other words, C? is the type of possibly null references (aka, nullable references)
to C objects.
The other type has the name C (that is, the same name as the class, trait, or iterator declaration).
Its values are the references to C objects, and does not contain the value null. In other words,
C is the type of non-null references to C objects.
The type C is a subset type of C?:
type C = c: C? | c != null
(It may be natural to think of the type C? as the union of type C and the value null, but,
technically, Dafny defines C as a subset type with base type C?.)
From being a subset type, we get that C is a subtype of C?. Moreover, if a class or trait C extends
a trait B, then type C is a subtype of B and type C? is a subtype of B?.
Every possibly-null reference type is a subtype of the built-in possibly-null trait type object?,
and every non-null reference type is a subtype of the built-in non-null trait type object. (And,
from the fact that object is a subset type of object?, we also have that object is a subtype of
object?.)
Arrays are references and array types also come in these two flavors. For example, array? and
array2? are possibly-null (1- and 2-dimensional) array types, and array and array2 are their
respective non-null types.
Note that ? is not an operator. Instead, it is simply the last character of the name of these
various possibly-null types.
5.6.3.3. Arrow types: ->, -->, and ~> For more information about arrow types (function
types), see Section 5.12. This section is a preview to point out the subset-type relationships
among the kinds of function types.
77
The built-in type
• -> stands for total functions,
• --> stands for partial functions (that is, functions with possible requires clauses), and
• ~> stands for all functions.
More precisely, type constructors exist for any arity (() -> X, A -> X, (A, B) -> X, (A, B,
C) -> X, etc.).
For a list of types TT and a type U, the values of the arrow type (TT) ~> U are functions from
TT to U. This includes functions that may read the heap and functions that are not defined on
all inputs. It is not common to need this generality (and working with such general functions is
difficult). 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 functions do not read the
(mutable parts of the) heap. Values of type (TT) --> U are called partial functions, and the
subset type (TT) --> U is called the partial arrow type. (As a mnemonic to help you remember
that this is the partial arrow, you may think of the little gap between the two hyphens in -->
as showing a broken arrow.)
Intuitively, the built-in partial arrow type is defined as follows (here shown for arrows with arity
1):
type A --> B = f: A ~> B | forall a :: f.reads(a) == {}
(except that what is shown here left of the = is not legal Dafny syntax and that the restriction
could not be verified as is). That is, the partial arrow type is defined as those functions f whose
reads frame is empty for all inputs. More precisely, taking variance into account, the partial
arrow type is defined as
type -A --> +B = f: A ~> B | forall a :: f.reads(a) == {}
The type (TT) -> U is, in turn, a subset type of (TT) --> U, adding the restriction that the func-
tions must not impose any precondition. That is, values of type (TT) -> U are total functions,
and the subset type (TT) -> U is called the total arrow type.
The built-in total arrow type is defined as follows (here shown for arrows with arity 1):
type -A -> +B = f: A --> B | forall a :: f.requires(a)
That is, the total arrow type is defined as those partial functions f whose precondition evaluates
to true 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, they have 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 to remember that --> and -> are subset types is in some
error messages. For example, if you try to assign a partial function to a variable whose type is
78
a total arrow type and the verifier is not able to prove that the partial function really is total,
then you’ll get an error saying that the subset-type constraint may not be satisfied.
For more information about arrow types, see Section 5.12.
5.6.3.4. Witness clauses The declaration of a subset type permits an optional witness
clause. Types in Dafny are generally expected to be non-empty, in part because variables 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 Dafny to be non-empty. However, Dafny cannot
always make this determination. If it cannot, a witness clause is required. The value given
in the witness clause must be a valid value for the type and assures Dafny that the type is
non-empty. (The variation witness * is described below.)
For example,
type OddInt = x: int | x % 2 == 1
If the witness is only available in ghost code, you can declare the witness as a ghost witness.
In this case, the Dafny verifier knows that the type is non-empty, but it will not be able to
auto-initialize a variable of that type in compiled code.
There is even room to do the following:
type BaseType
predicate RHS(x: BaseType)
type MySubset = x: BaseType | RHS(x) ghost witness MySubsetWitness()
Here the type is given a ghost witness: the result of the expression MySubsetWitness(), 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 is satisfied regarding the
non-emptiness of the type. However, the function has 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 viable candidate 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
clause witness *, which tells the verifier not to check for a satisfying witness. A declaration
like this produces an empty type:
79
type ReallyEmpty = x: int | false witness *
which does verify. But the method can never be called because there is no value that can be
supplied as the argument. Even this code
method P() returns (seven: int)
ensures seven == 7
{
var x: ReallyEmpty;
seven := 10;
}
does not complain about x unless x is actually used, in which case it must have a value. The
postcondition in P does not verify, but not because of the empty type.
80
5.7. Newtypes (grammar)
Examples:
newtype I = int
newtype D = i: int | 0 <= i < 10
newtype uint8 = i | 0 <= i < 256
A newtype is like a type synonym or subset type except that it declares a wholly new type name
that is distinct from its base type. It also accepts an optional witness clause.
A new type can be declared with the newtype declaration, for example:
newtype N = x: M | Q
where M is a type and Q is a boolean expression that can use x as a free variable. If M is an
integer-based numeric type, then so is N; if M is real-based, then so is N. If the type M can be
inferred from Q, the “: M” can be omitted. If Q is just true, then the declaration can be given
simply as:
newtype N = M
Type M is known as the base type of N. At present, Dafny only supports int and real as base
types of newtypes.
A newtype is a type that supports the same operations as its base type. The newtype is distinct
from and incompatible with other types; in particular, it is not assignable to its base type without
an explicit conversion. An important difference between the operations on a newtype and the
operations on its base type is that the newtype operations are defined only if the result satisfies
the predicate Q, and likewise for the literals of the newtype.
For example, suppose lo and hi are integer-based numeric bounds that satisfy 0 <= lo <= hi
and consider the following code fragment:
var mid := (lo + hi) / 2;
If lo and hi have type int, then the code fragment is legal; in particular, it never overflows, since
int has no upper bound. In contrast, if lo and hi are variables of a newtype int32 declared as
follows:
newtype int32 = x | -0x8000_0000 <= x < 0x8000_0000
then the code fragment is erroneous, since the result of the addition may fail to satisfy the
predicate in the definition of int32. The code fragment can be rewritten as
var mid := lo + (hi - lo) / 2;
81
var mid := lo + (hi/2 - lo/2);
Since a newtype is incompatible with its base type and since all results of the newtype’s op-
erations are members of the newtype, a compiler for Dafny is free to specialize the run-time
representation of the newtype. For example, by scrutinizing the definition of int32 above, a
compiler may decide to store int32 values using signed 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 in Section 5.6.3 may be more appropriate.
Note that the bound variable x in Q has type M, not N. Consequently, it may not be possible
to state Q about the N value. For example, consider the following type of 8-bit 2’s complement
integers:
newtype int8 = x: int | -128 <= x < 128
is not well-defined, because the comparisons require each operand to have type int8, which
means the literal 128 is checked to be of type int8, which it is not. A proper way to write this
expression is to use a conversion operation, described in Section 5.7.1, on c to convert it to the
base type:
-128 <= c as int < 128
If possible, Dafny compilers will represent values of the newtype using a native type for the sake
of efficiency. This action can be inhibited or a specific native data type selected by using the
{:nativeType} attribute, as explained in Section 11.1.2.
Furthermore, for the compiler to be able to make an appropriate choice of representation, the
constants in the defining expression as shown above must be known constants at compile-time.
They need not be numeric literals; combinations of basic operations and symbolic constants are
also allowed as described in Section 9.39.
82
where the type of mid is inferred to be int. Since the result value of the division is a member
of type int32, one can introduce yet another conversion operation to make the type of mid be
int32:
var mid := ((lo as int + hi as int) / 2) as int32;
If the compiler does specialize the run-time representation for int32, then these statements come
at the expense of two, respectively three, run-time conversions.
The as N conversion operation is grammatically a suffix operation like .field and array indexing,
but binds less tightly than unary operations: - x as int is (- x) as int; a + b as int is a
+ (b as int).
The as N conversion can also be used with reference types. For example, if C is a class, c is
an expression of type C, and o is an expression of type object, then c as object and c as
object? are upcasts and o is C is a downcast. A downcast requires the LHS expression to
have the RHS type, as is enforced by the verifier.
For some types (in particular, reference types), there is also a corresponding is operation (Section
9.10) that tests whether a value is valid for a given type.
83
5.8. Class types (grammar)
Examples:
trait T {}
class A {}
class B extends T {
const b: B?
var v: int
constructor (vv: int) { v := vv; b := null; }
function toString(): string { "a B" }
method m(i: int) { var x := new B(0); }
static method q() {}
}
Declarations within a class all begin with keywords and do not end with semicolons.
A class C is a reference type declared as follows:
class C<T> extends J1, ..., Jn
{
_members_
}
where the <>-enclosed list of one-or-more type parameters T is optional. The text “extends
J1, ..., Jn” is also optional and says that the class extends traits J1 … Jn. The members
of a class are fields, constant fields, functions, and methods. These are accessed or invoked by
dereferencing a reference to a C instance.
A function or method is invoked on an instance of C, unless the function or method is declared
static. A function or method that is not static is called an instance function or method.
An instance function or method takes an implicit receiver parameter, namely, the instance used
to access the member. In the specification and body of an instance function or method, the
receiver parameter can be referred to explicitly by the keyword this. However, in such places,
members of this can also be mentioned without any qualification. To illustrate, the qualified
this.f and the unqualified f refer to the same field of the same object in the following example:
class C {
var f: int
var x: int
method Example() returns (b: bool)
{
var x: int;
b := f == this.f;
}
}
so the method body always assigns true to the out-parameter b. However, in this example, x
and this.x are different because the field x is shadowed by the declaration of the local variable x.
84
There is no semantic difference between qualified and unqualified accesses to the same receiver
and member.
A C instance is created using new. There are three forms of new, depending on whether or not
the class declares any constructors (see Section 6.3.2):
c := new C;
c := new C.Init(args);
c := new C(args);
For a class with no constructors, the first two forms can be used. The first form simply allocates a
new instance of a C object, initializing its fields to values of their respective types (and initializing
each const field with a RHS to its specified value). The second form additionally invokes an
initialization method (here, named Init) on the newly allocated object and the given arguments.
It is therefore a shorthand for
c := new C;
c.Init(args);
An initialization method is an ordinary method that has no out-parameters and that modifies
no more than this.
For a class that declares one or more constructors, the second and third forms of new can be
used. For such a class, the second form invokes the indicated constructor (here, named Init),
which allocates and initializes the object. The third form is the same as the second, but invokes
the anonymous constructor of the class (that is, a constructor declared with the empty-string
name).
The details of constructors and other class members are described in Section 6.3.2.
85
5.9. Trait types (grammar)
A trait is an abstract superclass, similar to an “interface” or “mixin”. A trait can be extended
only by another trait or by a class (and in the latter case we say that the class implements the
trait). More specifically, algebraic datatypes cannot extend traits.4
The declaration of a trait is much like that of a class:
trait J
{
_members_
}
where members can include fields, constant fields, functions, methods and declarations of nested
traits, but no constructor methods. The functions and methods are allowed to be declared
static.
A reference type C that extends a trait J is assignable to a variable of type J; a value of type J
is assignable to a variable of a reference type C that extends J only if the verifier can prove that
the reference does indeed refer to an object of allocated type C. The members of J are available
as members of C. A member in J is not allowed to be redeclared in C, except if the member is a
non-static function or method without a body in J. By doing so, type C can supply a stronger
specification and a body for the member. There is further discussion on this point in Section
5.9.2.
new is not allowed to be used with traits. Therefore, there is no object whose allocated type is
a trait. But there can of course be objects of a class C that implement a trait J, and a reference
to such a C object can be used as a value of type J.
86
5.9.2. Inheritance
The purpose of traits is to be able to express abstraction: a trait encapsulates a set of behaviors;
classes and traits that extend it inherit those behaviors, perhaps specializing them.
A trait or class may extend multiple other traits. The traits syntactically listed in a trait or
class’s extends clause are called its direct parents; the transitive parents of a trait or class are its
direct parents, the transitive parents of its direct parents, and the object trait (if it is not itself
object). These are sets of traits, in that it does not matter if there are repetitions of a given
trait in a class or trait’s direct or transitive 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
directed extends relationship may not have any cycles.
A class or trait inherits (as if they are copied) all the instance members of its transitive parents.
However, since names may not be overloaded in Dafny, 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.[^overload] This restriction does mean that traits from different sources
that coincidentally use the same name for different purposes cannot be combined by being part
of the set of transitive parents for some new trait or class.
A declaration of member C.M in a class or trait overrides any other declarations of the same
name (and signature) in a transitive parent. C.M is then called an override; a declaration that
does not override anything is called an original 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.
[//]: # Caution - a newline (not a blank line) ends a footnote [^overload]: 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.
Where traits within an extension hierarchy do declare instance members with the same name (and
thus the same signature), some rules apply. Recall that, for methods, every declaration includes
a specification; if no specification is given explicitly, 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 body and no
transitive parent that declares M with a body must itself redeclare M with 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 and Q.M, then either P.M must override Q.M or Q.M must override P.M.
87
The last restriction above is the current implementation. It effectively limits inheritance of a
method M to a single “chain” of declarations and does not permit mixins.
Each of any method declarations explicitly or implicitly includes a specification. In simple cases,
those syntactically separate specifications will be copies of each other (up to renaming to take
account of differing formal parameter names). However they need not be. The rule is that the
specifications of M in a given class or trait must be as strong as M’s specifications in a transitive
parent. Here as strong as means that it must 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 of P.M,
• C.M’s requires clause must be implied by P.M’s requires clause
• C.M’s ensures clause must imply P.M’s ensures clause
• C.M’s reads set must be a subset of P.M’s reads set
• C.M’s modifies set must be a subset of P.M’s modifies set
• C.M’s decreases expression must be smaller than or equal to P.M’s decreases 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 an initializer
can initialize the field in a constructor. If the declaring trait does give an initial value in the
declaration, the extending class or trait may not either redeclare 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.
Members Width and Move are abstract (that is, body-less) and can be implemented differently
by different classes that extend the trait. The implementation of method MoveH is given in the
88
trait and thus is used by all classes that extend Shape. Here are two classes that each extend
Shape:
class UnitSquare extends Shape
{
var x: real, y: real
function Width(): real
decreases 0
{ // note the empty reads clause
1.0
}
method Move(dx: real, dy: real)
modifies this
{
x, y := x + dx, y + dy;
}
}
Note that the classes can declare additional members, that they supply implementations for
the abstract members of the trait, that they repeat the member signatures, and that they are
responsible for providing their own member specifications that both strengthen the corresponding
specification in the trait and are satisfied by the provided body. Finally, here is some code that
creates two class instances and uses them together as shapes:
method m() {
var myShapes: seq<Shape>;
var A := new UnitSquare;
myShapes := [A];
var tri := new LowerRightTriangle;
// myShapes contains two Shape values, of different classes
myShapes := myShapes + [tri];
89
// move shape 1 to the right by the width of shape 0
myShapes[1].MoveH(myShapes[0].Width());
}
90
5.10. Array types (grammar)
Dafny supports mutable fixed-length array types of any positive dimension. Array types are
(heap-based) reference types.
arrayToken is a kind of reserved token, such as array, array?, array2, array2?, array3, and
so on (but not array1). 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 the arrayToken itself is
also inferrable and can be omitted.
The initial values of the array elements are arbitrary values of type T. A one-dimensional array
value can also be assigned using an ordered list of expressions enclosed in square brackets, as
follows:
a := new T[] [t1, t2, t3, t4];
The initialization can also use an expression that returns a function of type nat -> T:
a := new int[5](i => i*i);
In fact, the initializer can simply be a function name for the right type of function:
a := new int[5](Square);
The length of an array is retrieved using the immutable Length member. For example, the array
allocated with a := new T[n]; satisfies:
a.Length == n
Caveat: The type of the array created by new T[n] is array<T>. A mistake that is simple
to make and that can lead to befuddlement is to write array<T> instead of T after new. For
91
example, consider the following:
type T(0)
method m(n: nat) {
var a := new array<T>;
var b := new array<T>[n];
var c := new array<T>(n); // resolution error
var d := new array(n); // resolution error
}
The first statement allocates an array of type array<T>, but of unknown length. The second
allocates an array of type array<array<T>> of length n, that is, an array that holds n values
of type array<T>. The third statement allocates an array of type array<T> and then attempts
to invoke an anonymous constructor on this array, passing argument n. Since array has no
constructors, let alone an anonymous constructor, this statement gives rise to an error. If the
type-parameter list is omitted for a type that expects type parameters, Dafny will attempt to fill
these in, so as long as the array type parameter can be inferred, it is okay to leave off the “<T>”
in the fourth statement above. However, as with the third statement, array has no anonymous
constructor, so an error message is generated.
expression description
a[lo..hi] subarray conversion to sequence
a[lo..] drop
a[..hi] take
a[..] array conversion to sequence
The expression a[lo..hi] takes the first hi elements of the array, then drops the first lo elements
thereof and returns what remains as a sequence, with length hi - lo. The other operations are
special instances of the first. If lo is omitted, it defaults to 0 and if hi is omitted, it defaults
to a.Length. In the last operation, both lo and hi have been omitted, thus a[..] returns the
sequence consisting of all the array elements of a.
The subarray operations are especially useful in specifications. For example, the loop invariant
of a binary search algorithm that uses variables lo and hi to delimit the subarray where the
search key may still be found can be expressed as follows:
key !in a[..lo] && key !in a[hi..]
Another use is to say that a certain range of array elements have not been changed since the
beginning of a method:
92
a[lo..hi] == old(a[lo..hi])
Note that the type of prevElements in this example is seq<T>, if a has type array<T>.
A final example of the subarray operation lies in expressing that an array’s elements are a
permutation of the array’s elements at the beginning of a method, as would be done in most
sorting algorithms. Here, the subarray operation is combined with the sequence-to-multiset
conversion:
multiset(a[..]) == multiset(old(a[..]))
create a 2-dimensional array whose dimensions have lengths m and n, respectively, and then
swaps the elements at i,j and x,y. The type of matrix is array2<T>, and similarly for
higher-dimensional arrays (array3<T>, array4<T>, etc.). Note, however, that there is no type
array0<T>, and what could have been array1<T> is actually named just array<T>. (Accordingly,
array0 and array1 are just normal identifiers, not type names.)
The new operation above requires m and n to be non-negative integer-based numerics. These
lengths can be retrieved using the immutable fields Length0 and Length1. For example, the
following holds 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 update statement require that the indices are in bounds. For example, the
swap statement above is well-formed only if:
0 <= i < matrix.Length0 && 0 <= j < matrix.Length1 &&
0 <= x < matrix.Length0 && 0 <= y < matrix.Length1
93
There is however syntax to create a multi-dimensional array value using a function: see Section
9.16.
94
5.11. Iterator types (grammar)
See Section 7.5 for a description of iterator specifications.
An iterator provides a programming abstraction for writing code that iteratively returns elements.
These CLU-style iterators are co-routines in the sense that they keep track of their own program
counter and control can be transferred into and out of the iterator body.
An iterator is declared as follows:
iterator Iter<T>(_in-params_) yields (_yield-params_)
_specification_
{
_body_
}
where T is a list of type parameters (as usual, if there are no type parameters, “<T>” is omitted).
This declaration gives rise to a reference type with the same name, Iter<T>. In the signa-
ture, in-parameters and yield-parameters are the iterator’s analog of a method’s in-parameters
and out-parameters. The difference is that the out-parameters of a method are returned to a
caller just once, whereas the yield-parameters of an iterator are returned each time the iterator
body performs a yield. The body consists of statements, like in a method body, but with the
availability also of yield statements.
From the perspective of an iterator client, the iterator declaration can be understood as gen-
erating a class Iter<T> with various members, a simplified version of which is described next.
The Iter<T> class contains an anonymous constructor whose parameters are the iterator’s in-
parameters:
predicate Valid()
constructor (_in-params_)
modifies this
ensures Valid()
An iterator is created using new and this anonymous constructor. For example, an iterator
willing to return ten consecutive integers from start can be declared as follows:
iterator Gen(start: int) yields (x: int)
yield ensures |xs| <= 10 && x == start + |xs| - 1
{
var i := 0;
while i < 10 invariant |xs| == i {
x := start + i;
yield;
i := i + 1;
}
}
95
iter := new Gen(30);
The predicate Valid() says when the iterator is in a state where one can attempt to compute
more elements. It is a postcondition of the constructor and occurs in the specification of the
MoveNext member:
method MoveNext() returns (more: bool)
requires Valid()
modifies this
ensures more ==> Valid()
Note that the iterator remains valid as long as MoveNext returns true. Once MoveNext returns
false, the MoveNext method can no longer be called. Note, the client is under no obligation
to keep calling MoveNext until it returns false, and the body of the iterator is allowed to keep
returning elements forever.
The in-parameters of the iterator are stored in immutable fields of the iterator class. To illustrate
in terms of the example above, the iterator class Gen contains the following field:
const start: int
These fields are set by the MoveNext method. If MoveNext returns true, the latest yield values
are available in these fields and the client can read them from there.
To aid in writing specifications, the iterator class also contains ghost members that keep the
history of values returned by MoveNext. The names of these ghost fields follow the names of the
yield-parameters with an “s” appended to the name (to suggest plural). Name checking rules
make sure these names do not give rise to ambiguities. The iterator class for Gen above thus
contains:
ghost var xs: seq<int>
These history fields are changed automatically by MoveNext, but are not assignable by user code.
96
Finally, the iterator class contains some special fields for use in specifications. In particular, the
iterator specification is recorded in the following immutable fields:
ghost var _reads: set<object>
ghost var _modifies: set<object>
ghost var _decreases0: T0
ghost var _decreases1: T1
// ...
where there is a _decreases(i): T(i) field for each component of the iterator’s decreases
clause.5 In addition, there is a field:
ghost var _new: set<object>;
to which any objects allocated on behalf of the iterator body are added. 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 upon construction of the iterator, the
in-parameters are indeed in-parameters, not fields of this.
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 executed whenever
they yield control, they need to declare what memory locations must not be modified by other
code in order to maintain correctness. The contents of an iterator’s reads clauses become part of
the reads clause of the implicitly created Valid() predicate. This means if client code modifies
any of this state, it will not be able to establish the precondition for the iterator’s MoveNext()
method, and hence the iterator body will never resume if this state is modified.
It is regrettably tricky to use iterators. The language really ought to have a foreach statement
to make this easier. Here is an example showing a definition and use of an iterator.
iterator Iter<T(0)>(s: set<T>) yields (x: T)
yield ensures x in s && x !in xs[..|xs|-1]
ensures s == set z | z in xs
{
var r := s;
while (r != {})
invariant r !! set z | z in xs
invariant s == r + set z | z in xs
{
var y :| y in r;
assert y !in xs;
r, x := r - {y}, y;
assert y !in xs;
5 It would make sense to rename the special fields _reads and _modifies to have the same names as the
corresponding keywords, reads and modifies, as is done for function values. Also, the various _decreases\(_i_\)
fields can be combined into one field named decreases whose type is a n-tuple. These changes may be incorporated
into a future version of Dafny.
97
yield;
assert y == xs[|xs|-1]; // a lemma to help prove loop invariant
}
}
98
5.12. Arrow types (grammar)
Examples:
(int) -> int
(bool,int) ~> bool
() --> object?
Functions are first-class values in Dafny. The types of function values are called arrow types
(aka, function types). Arrow types have the form (TT) ~> U where TT is a (possibly empty)
comma-delimited list of types and U is a type. TT is called the function’s domain type(s) and U
is its range type. For example, the type of a function
function F(x: int, arr: array<bool>): real
requires x < 1000
reads arr
is (int, bool) --> real. Similarly, a function declared with neither a reads clause nor a
requires clause is known by the type checker to be a total function. For example, the type of
function F(x: int, b: bool): real
is (int, bool) -> real. In addition to functions declared by name, Dafny also supports anony-
mous functions by means of lambda expressions (see Section 9.13).
To simplify the appearance of the basic case where a function’s domain consists of a list of exactly
one non-function, non-tuple type, the parentheses around the domain type can be dropped in this
case. For example, you may write just T -> U for a total arrow type. This innocent simplification
requires additional explanation in the case where that one type is a tuple type, since tuple types
are also written with enclosing parentheses. If the function takes a single argument that is a
tuple, an additional set of parentheses is needed. For example, the function
function G(pair: (int, bool)): real
has type ((int, bool)) -> real. Note the necessary double parentheses. Similarly, a function
that takes no arguments is different from one that takes a 0-tuple as an argument. For instance,
the functions
99
function NoArgs(): real
function Z(unit: ()): real
The arrow types themselves do not divide a function’s parameters into ghost versus non-ghost.
Instead, a function used as a first-class value is considered to be ghost if either the function or
any of its arguments is ghost. The following example program illustrates:
function F(x: int, ghost y: int): int
{
x
}
method Example() {
ghost var f: (int, int) -> int;
var g: (int, int) -> int;
var h: (int) -> int;
var x: int;
f := F;
x := F(20, 30);
g := F; // error: tries to assign ghost to non-ghost
h := 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 function ever depends on the
entire heap, however. A property of the function is its declared upper bound on the set of heap
locations it depends on for a given input. This lets the verifier figure out that certain heap
modifications have no effect on the value returned by a certain function. For a function f: T ~>
U and a value t of type T, the dependency set is denoted f.reads(t) and has type set<object>.
The second property of functions stems from the fact that every function is potentially partial.
In other words, a property of a function is its precondition. For a function f: T ~> U, the
100
precondition of f for a parameter value t of type T is denoted f.requires(t) and has type
bool.
The third property of a function is more obvious—the function’s body. For a function f: T ~>
U, the value that the function yields for an input t of type T is denoted f(t) and has type U.
Note that f.reads and f.requires are themselves functions. Without loss of generality, suppose
f is defined as:
function f<T,U>(x: T): U
reads R(x)
requires P(x)
{
body(x)
}
Then, f.reads is a function of type T ~> set<object?> whose reads and requires properties
are given by the definition:
function f.reads<T>(x: T): set<object>
reads R(x)
requires P(x)
{
R(x)
}
f.requires is a function of type T ~> bool whose reads and requires properties are given by
the definition:
predicate f_requires<T>(x: T)
requires true
reads if P(x) then R(x) else *
{
P(x)
}
where * is a notation to indicate that any memory location can be read, but is not valid Dafny
syntax.
In these examples, if f instead had type T --> U or T -> U, then the type of f.reads is T ->
set<object?> and the type of f.requires is T -> bool.
Dafny also supports anonymous functions by means of lambda expressions. See Section 9.13.
101
5.13. Tuple types
TupleType = "(" [ [ "ghost" ] Type { "," [ "ghost" ] Type } ] ")"
Dafny builds in record types that correspond to tuples and gives these a convenient special
syntax, namely parentheses. For example, for what might have been declared as
datatype Pair<T,U> = Pair(0: T, 1: U)
Dafny provides the type (T, U) and the constructor (t, u), as if the datatype’s name were
“” (i.e., an empty string) and its type arguments are given in round parentheses, and as if the
constructor name were the empty string. Note that the destructor names are 0 and 1, which are
legal identifier names for members. For an example showing the use of a tuple destructor, here
is a property that holds of 2-tuples (that is, pairs):
method m(){
assert (5, true).1 == true;
}
Dafny declares n-tuples where n is 0 or 2 or more. There are no 1-tuples, since parentheses
around a single type or a single value have no semantic meaning. The 0-tuple type, (), is often
known as the unit type and its single value, also written (), is known as unit.
The ghost modifier can be used to mark tuple components as being used for specification only:
const pair: (int, ghost int) := (1, ghost 2)
102
5.14. Algebraic Datatypes (grammar)
Dafny offers two kinds of algebraic datatypes, those defined inductively (with datatype) and
those defined coinductively (with codatatype). The salient property of every datatype is that
each value of the type uniquely identifies one of the datatype’s constructors and each constructor
is injective in its parameters.
where Ctors is a nonempty |-separated list of (datatype) constructors for the datatype. Each
constructor has the form:
C(_params_)
where params is a comma-delimited list of types, optionally preceded by a name for the parameter
and a colon, and optionally preceded by the keyword ghost. If a constructor has no parameters,
the parentheses after the constructor name may be omitted. If no constructor takes a parameter,
the type is usually called an enumeration; for example:
datatype Friends = Agnes | Agatha | Jermaine | Jack
For every constructor C, Dafny defines a discriminator C?, which is a member that returns true
if and only if the datatype value has been constructed using C. For every named parameter p of
a constructor C, Dafny defines a destructor p, which is a member that returns the p parameter
from the C call used to construct the datatype value; its use requires that C? holds. For example,
for the standard List type
datatype List<T> = Nil | Cons(head: T, tail: List<T>)
is not well-formed by itself, since Cons(5, Nil).tail does not necessarily satisfy Cons?.
A constructor can have the same name as the enclosing datatype; this is especially useful for
single-constructor datatypes, which are often called record types. For example, a record type for
black-and-white pixels might be represented as follows:
103
datatype Pixel = Pixel(x: int, y: int, on: bool)
To call a constructor, it is usually necessary only to mention the name of the constructor, but
if this is ambiguous, it is always possible to qualify the name of constructor by the name of the
datatype. For example, Cons(5, Nil) above can be written
List.Cons(5, List.Nil)
constructs a value like d but whose f parameter is t. The operation requires that d satisfies C?.
For example, the following equality holds:
method m(){
assert Cons(4, Nil).(tail := Cons(3, Nil)) == Cons(4, Cons(3, Nil));
}
The datatype update expression also accepts multiple field names, provided these are distinct.
For example, a node of some inductive 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 means is properly contained
in. For example, in the code
datatype X = T(t: X) | I(i: int)
method comp() {
var x := T(I(0));
var y := I(0);
var z := I(1);
assert x.t < x;
assert y < x;
assert !(x < x);
assert z < x; // FAILS
}
x is a datatype value that holds a T variant, which holds a I variant, which holds an integer 0.
The value x.t is a portion of the datatype structure denoted by x, so x.t < x is true. Datatype
values are immutable mathematical values, so the value of y is identical to the value of x.t, so y
< x is true also, even though y is constructed from the ground up, rather than as a portion of x.
However, z is different than either y or x.t and consequently z < x is not provable. Furthermore,
< does not include ==, so x < x is false.
Note that only < is defined; not <= or > or >=.
104
Also, < is underspecified. With the above code, one can prove neither z < x nor !(z < x) and
neither z < y nor !(z < y). In each pair, though, one or the other is true, so (z < x) || !(z
< x) is provable.
declare possibly infinite lists (that is, lists that can be either finite or infinite), infinite streams
(that is, lists that are always infinite), and infinite binary trees (that is, trees where every branch
goes on forever), respectively.
The paper Co-induction Simply, by Leino and Moskal(Leino and Moskal 2014a), explains Dafny’s
implementation and verification of coinductive types. We capture the key features from that
paper in the following section but the reader is referred to that paper for more complete details
and to supply bibliographic references that are omitted here.
5.14.3. Coinduction
Mathematical induction is a cornerstone of programming and program verification. It arises
in data definitions (e.g., some algebraic data structures can be described using induction), it
underlies program semantics (e.g., it explains how to reason about finite iteration and recursion),
and it is used in proofs (e.g., supporting lemmas about data structures use inductive proofs).
Whereas induction deals with finite things (data, behavior, etc.), its dual, coinduction, deals with
possibly infinite things. Coinduction, too, is important in programming and program verification:
it arises in data definitions (e.g., lazy data structures), semantics (e.g., concurrency), and proofs
(e.g., showing refinement in a coinductive big-step semantics). It is thus desirable to have good
support for both induction and coinduction in a system for constructing and reasoning about
programs.
Co-datatypes and co-recursive functions make it possible to use lazily evaluated data structures
(like in Haskell or Agda). Greatest predicates, defined by greatest fix-points, let programs state
properties of such data structures (as can also be done in, for example, Coq). For the purpose of
writing coinductive proofs in the language, we introduce greatest and least lemmas. A greatest
lemma invokes the coinduction hypothesis much like an inductive proof invokes the induction
hypothesis. Underneath the 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 in Dafny come together to
give straightforward definitions of infinite matters.
105
// infinite streams
codatatype IStream<T> = ICons(head: T, tail: IStream<T>)
The example defines a type IStream of infinite streams, with constructor ICons and destructors
head and tail. Function Mult performs pointwise multiplication on infinite streams of integers,
defined using a co-recursive call (which is evaluated lazily). Greatest predicate Below is defined
as a greatest fix-point, which intuitively means that the co-predicate will take on the value true
if the recursion goes on forever without determining a different value. The greatest lemma states
the theorem Below(a, Mult(a, a)). Its body gives the proof, where the recursive invocation
of the co-lemma corresponds to an invocation of the coinduction hypothesis.
The proof of the theorem stated by the first co-lemma lends itself to the following intuitive
reading: To prove that a is below Mult(a, a), check that their heads are ordered and, if the
heads are equal, also prove that the tails are ordered. The second co-lemma states a property
that does not always hold; the verifier is not fooled by the bogus proof attempt and instead
reports the property as unproved.
We argue that these definitions in Dafny are simple enough to level the playing field between
induction (which is familiar) and coinduction (which, despite being the dual of induction, is often
perceived as eerily mysterious). Moreover, the automation provided by our SMT-based verifier
106
reduces the tedium in writing coinductive proofs. For example, it verifies Theorem_BelowSquare
from the program text given above—no additional lemmas or tactics are needed. In fact, as a
consequence of the automatic-induction heuristic in Dafny, the verifier will automatically verify
Theorem_BelowSquare even given an empty body.
Just like there are restrictions on when an inductive hypothesis can be invoked, there are restric-
tions on how a coinductive hypothesis can be used. These are, of course, taken into consideration
by Dafny’s verifier. For example, as illustrated by the second greatest lemma above, invoking
the coinductive hypothesis in an attempt to obtain the entire proof goal is futile. (We explain
how this works in the section about greatest lemmas) Our initial experience with coinduction
in Dafny shows it to provide an intuitive, low-overhead user experience that compares favorably
to even the best of today’s interactive proof assistants for coinduction. In addition, the coinduc-
tive features and verification support in Dafny have other potential benefits. The features are
a stepping stone for verifying functional lazy programs with Dafny. Coinductive features have
also shown to be useful in defining language semantics, as needed to verify the correctness of a
compiler, so this opens the possibility that such verifications can benefit from SMT automation.
is used to invoke Lemma(x) on all x for which P(x) holds. If Lemma(x) ensures Q(x), then the
forall statement establishes
107
forall x :: P(x) ==> Q(x).
Stream is a coinductive datatype whose values are possibly infinite lists. Function Up returns
a stream consisting of all integers upwards of n and FivesUp returns a stream consisting of all
multiples of 5 upwards of n . The self-call in Up and the first self-call in FivesUp sit in productive
positions and are therefore classified as co-recursive calls, exempt from termination checks. The
second self-call in FivesUp is not in a productive position and is therefore subject to termination
checking; in particular, each recursive call must decrease the rank defined by the decreases
clause.
Analogous to the common finite list datatype, Stream declares two constructors, SNil and SCons.
Values can be destructed using match expressions and statements. In addition, like for inductive
datatypes, each constructor C automatically gives rise to a discriminator C? and each parameter
of a constructor can be named in order to introduce a corresponding destructor. For example,
if xs is the stream SCons(x, ys), then xs.SCons? and xs.head == x hold. In contrast to
datatype declarations, there is no grounding check for co-datatypes—since a codatatype admits
infinite values, the type is nevertheless inhabited.
108
as the co-recursive call Up(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 determine each successive
piece of a co-datatype value after a finite amount of work. This condition is satisfied if every
co-recursive call is syntactically guarded by a constructor of a co-datatype, which is the criterion
Dafny uses to classify intra-cluster calls as being either co-recursive or recursive. Calls that are
classified as co-recursive are exempt from termination checks.
A consequence of the productivity checks and termination checks is that, even in the absence
of talking about least or greatest fix-points of self-calling functions, all functions in 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 function FivesUp.
Some restrictions apply. To guarantee that the greatest fix-point always exists, the (implicit
109
functor defining the) greatest predicate must be monotonic. This is enforced by a syntactic
restriction on the form of the body of greatest predicates: after conversion to negation nor-
mal form (i.e., pushing negations down to the atoms), intra-cluster calls of greatest predicates
must appear only in positive positions—that is, they must appear as atoms and must not be
negated. Additionally, to guarantee soundness later on, we require that they appear in continous
positions—that is, in negation normal form, when they appear under existential quantification,
the quantification needs to be limited to a finite range6 . Since the evaluation of a greatest predi-
cate might not terminate, greatest predicates are always ghost. There is also a restriction on the
call graph that a cluster containing a greatest predicate must contain only greatest predicates,
no other kinds of functions.
A greatest predicate declaration of P defines not just a greatest predicate, but also a corre-
sponding prefix predicate P#. A prefix predicate is a finite unrolling of a co-predicate. The prefix
predicate is constructed from the co-predicate by
• adding a parameter _k of type nat to denote the prefix length,
• adding the clause decreases _k; to the prefix predicate (the greatest predicate itself is
not allowed to have a decreases clause),
• replacing in the body of the greatest predicate every intra-cluster call Q(args) to a greatest
predicate by a call Q#[_k - 1](args) to the corresponding prefix predicate, and then
• prepending the body with if _k == 0 then true else.
For example, for greatest predicate Pos, the definition of the prefix predicate Pos# is as suggested
above. Syntactically, the prefix-length argument passed to a prefix predicate to indicate how
many times to unroll the definition is written in square brackets, as in Pos#[k](s). In the Dafny
grammar this is called a HashCall. The definition of Pos# is available only at clusters strictly
higher than that of Pos; that is, Pos and Pos# must not be in the same cluster. In other words,
the definition of Pos cannot depend on Pos#.
5.14.3.6. Coinductive Proofs From what we have said so far, a program can make use of
properties of co-datatypes. For example, a method that declares Pos(s) as a precondition can
rely on the stream s containing only positive integers. In this section, we consider how such
properties are established in the first place.
5.14.3.6.1. Properties of Prefix Predicates Among other possible strategies for estab-
lishing coinductive properties we take the time-honored approach of reducing coinduction to
induction. More precisely, Dafny passes to the SMT solver an assumption D(P) for every great-
est predicate P, where:
D(P) = forall x • P(x) <==> forall k • P#[k](x)
6 To be specific, Dafny has two forms of extreme predicates and lemmas, one in which _k has type nat and
one in which it has type ORDINAL (the default). The continuous restriction applies only when _k is nat. 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.
110
In other words, a greatest predicate is true iff its corresponding prefix predicate is true for all
finite unrollings.
In Sec. 4 of the paper [Co-induction Simply] a soundness theorem of such assumptions is
given, provided the greatest predicates meet the continous restrictions. An example proof of
Pos(Up(n)) for every n > 0 is shown here:
lemma UpPosLemma(n: int)
requires n > 0
ensures Pos(Up(n))
{
forall k | 0 <= k { UpPosLemmaK(k, n); }
}
The lemma UpPosLemma proves Pos(Up(n)) for every n > 0. We first show Pos#[k](Up(n
)), for n > 0 and an arbitrary k, and then use the forall statement to show forall k •
Pos#[k](Up(n)). Finally, the axiom D(Pos) is used (automatically) to establish the greatest
predicate.
5.14.3.6.2. Greatest lemmas As we just showed, with help of the D axiom we can now prove
a greatest predicate by inductively proving that the corresponding prefix predicate holds for all
prefix lengths k. In this section, we introduce greatest lemma declarations, which bring about
two benefits. The first benefit is that greatest lemmas are syntactic sugar and reduce the tedium
of having to write explicit quantifications over k. The second benefit is that, in simple cases,
the bodies of greatest lemmas can be understood as coinductive proofs directly. As an example
consider the following greatest lemma.
greatest lemma UpPosLemma(n: int)
requires n > 0
ensures Pos(Up(n))
{
UpPosLemma(n+1);
}
This greatest lemma can be understood as follows: UpPosLemma invokes itself co-recursively to
obtain the proof for Pos(Up(n).tail) (since Up(n).tail equals Up(n+1)). The proof glue
needed to then conclude Pos(Up(n)) is provided automatically, thanks to the power of the
111
SMT-based verifier.
5.14.3.6.3. Prefix Lemmas To understand why the above UpPosLemma greatest lemma code
is a sound proof, let us now describe the details of the desugaring of greatest lemmas. In
analogy to how a greatest predicate declaration defines both a greatest predicate and a prefix
predicate, a greatest lemma declaration defines both a greatest lemma and prefix lemma. In
the call graph, the cluster containing a greatest lemma must contain only greatest lemmas and
prefix lemmas, no other methods or function. By decree, a greatest lemma and its corresponding
prefix lemma are always placed in the same cluster. Both greatest lemmas and prefix lemmas
are always ghost code.
The prefix lemma is constructed from the greatest lemma by
• adding a parameter _k of type nat to denote the prefix length,
• replacing in the greatest lemma’s postcondition the positive continuous occurrences 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 call M#[_k - 1](args) to the corresponding prefix lemma, and then
• making 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 recursive calls are, as usual, checked to be terminating.
We allow the pre-declared identifier _k to appear in the original body of the greatest lemma.7
We can now think of the body of the greatest lemma as being replaced by a forall call, for every k ,
to the prefix lemma. By construction, this new body will establish the greatest lemma’s declared
postcondition (on account of the D axiom, and remembering that only the positive continuous
occurrences of greatest predicates in the greatest lemma’s postcondition are rewritten), so there
is no reason for the program verifier to check it.
The actual desugaring of our greatest lemma UpPosLemma is in fact the previous code for the
UpPosLemma lemma except that UpPosLemmaK is named UpPosLemma# and modulo a minor syn-
tactic difference in how the k argument is passed.
In the recursive call of the prefix lemma, there is a proof obligation that the prefixlength argument
_k - 1 is a natural number. Conveniently, this follows from the fact that the body has been
wrapped in an if _k != 0 statement. This also means that the postcondition must hold trivially
when _k == 0, or else a postcondition violation will be reported. This is an appropriate design for
our desugaring, because greatest lemmas are expected to be used to establish greatest predicates,
whose corresponding prefix predicates hold trivially when _k = 0. (To prove other predicates,
use an ordinary lemma, not a greatest lemma.)
7 Note, two places where co-predicates and co-lemmas are not analogous are (a) co-predicates must not make
recursive calls to their prefix predicates and (b) co-predicates cannot mention _k.
112
It is interesting to compare the intuitive understanding of the coinductive proof in using a greatest
lemma with the inductive proof in using a lemma. Whereas the inductive proof is performing
proofs for deeper and deeper equalities, the greatest lemma can be understood as producing the
infinite proof on demand.
5.14.3.7. Abstemious and voracious functions Some functions on codatatypes are ab-
stemious, meaning that they do not need 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
of properties about the function. The attribute {:abstemious} can be applied to a 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 be declared (with
limitations) in traits, newtypes and datatypes (but not in subset types or type synonyms).
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 an identifier (that is not allowed to start with a leading underscore) or
some 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:
var x: 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. A field declaration is similar to but distinct from a variable
declaration statement. Unlike for local variables and bound variables, the type is required and
will not be inferred.
Unlike method and function declarations, a field declaration is not permitted as a member of a
module, even though there is an implicit class. Fields can be declared in either an explicit class
113
or a trait. A class that inherits from multiple traits will have all the fields declared in any of its
parent traits.
Fields that are declared as ghost can only be used in specifications, not in code that will be
compiled into executable code.
Fields may not be declared static.
A const 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, and opaque modifiers, but no others.
• A const declaration may appear within a module or within any declaration that may contain
members (class, trait, datatype, newtype).
• If it is in a module, it is implicitly static, and may not also be declared static.
• If the declaration has an initializing expression that is a ghost expression, 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 declared variable know its
name and type but not its value. The value can be made known for reasoning purposes by
using the reveal statement.
• The initialization expression may refer to other constant fields that are in scope and de-
clared either before or after this declaration, but circular references are not allowed.
114
lemma L(p: bool) ensures p || !p {}
twostate lemma TL(p: bool) ensures p || !p {}
least lemma LL[nat](p: bool) ensures p || !p {}
greatest lemma GL(p: bool) ensures p || !p {}
abstract module M { method m(i: int) }
module N refines M { method m ... {} }
where :att1 and :att2 are attributes of the method, T1 and T2 are type parameters of the
method (if generic), a, b, c are the method’s in-parameters, x, y, z are the method’s out-
parameters, Pre is a boolean expression denoting the method’s precondition, Frame denotes
a set of objects whose fields may be updated by the method, Post is a boolean expression
denoting the method’s postcondition, Rank is the method’s variant function, and Body is a list
of statements that implements the method. Frame can be a list of expressions, each of which is
a set of objects or a single object, the latter standing for the singleton set consisting of that one
object. The method’s frame is the union of these sets, plus the set of objects allocated by the
method body. For example, if c and d are parameters of a class type C, then
modifies {c, d}
modifies {c} + {d}
115
modifies c, {d}
modifies c, d
6.3.2. Constructors
To write structured object-oriented programs, one often relies on objects being constructed only
in certain ways. For this purpose, Dafny provides constructor (method)s. A constructor is
declared with the keyword constructor instead of method; constructors are permitted only in
classes. A constructor is allowed to be declared as ghost, in which case it can only be used in
ghost contexts.
A constructor can only be called at the time an object is allocated (see object-creation examples
below). Moreover, when a class contains a constructor, every call to new for a class must be
accompanied by a call to one of its constructors. A class may declare 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 an initializer in its declaration may not be reassigned in the body of the
constructor.
116
6.3.2.1. Classes with no explicit constructors For a class that declares no constructors,
an instance of the class is created with
c := new C;
This allocates an object and initializes its fields to values of their respective types (and initializes
each const field with a RHS to its specified value). The RHS of a const field may depend on
other const or var fields, but circular dependencies are not allowed.
This simple form of new is allowed only if the class declares no constructors, which is not pos-
sible to determine in every scope. It is easy to determine whether or not a class declares any
constructors if the class is declared in the same module that performs the new. If the class is
declared in a different module and that module exports a constructor, then it is also clear that
the class has a constructor (and thus this simple form of new cannot be used). (Note that an
export set that reveals a class C also exports the anonymous constructor of C, if any.) But
if the module that declares C does not export any constructors for C, then callers outside the
module do not know whether or not C has a constructor. Therefore, this simple form of new is
allowed only for classes that are declared in the same module as the use of new.
The simple new C is allowed in ghost contexts. Also, unlike the forms of new that call a con-
structor or initialization method, it can be used in a simultaneous assignment; for example
c, d, e := new C, new C, 15;
is legal.
As a shorthand for writing
c := new C;
c.Init(args);
where Init is an initialization method (see the top of the section about class types), one can
write
c := new C.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 historical reasons. 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 like InitFromList or InitFromSet
(or just FromList and FromSet). Unlike other members, one constructor is allowed to be anony-
mous; in other words, an anonymous constructor is a constructor whose name is essentially the
empty string. For example:
class Item {
constructor I(xy: int) // ...
constructor (x: int, y: int)
117
// ...
}
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. When this happens, we can often give Dafny assistance
by providing a lemma. This is done by declaring a method with the lemma keyword. Lemmas
are implicitly ghost methods and the ghost keyword cannot be 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 have modifies clause: it
may not change anything about even the ghost state; ghost methods may have modifies 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).
118
Lemmas may, but typically do not, have out-parameters.
In summary, a lemma states a logical fact, summarizing an inference that the verifier cannot do
on its own. Explicitly “calling” a lemma in the program text tells the verifier to 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 parameters that satisfy the preconditions of the
lemma.
For an example, see the FibProperty lemma in Section 12.5.2.
See the Dafny Lemmas tutorial for more examples and hints for using lemmas.
returns true if the value of c.data has not been reduced from the old state to the current.
Dereferences in the current heap are written as usual (e.g., c.data) and must, as usual, be
accounted for in the function’s reads clause. Dereferences in the old heap are enclosed by old
(e.g., old(c.data)), just like when one dereferences a method’s initial heap. The function is
allowed to read anything in the old heap; the reads clause only declares dependencies on locations
in the current heap. Consequently, the frame axiom for a two-state function is sensitive to any
change in the old-heap parameter; in other words, the frame axiom says nothing about two
invocations 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 in as the caller’s
current heap. The two-state function’s old-heap parameter is by default passed in as the caller’s
old heap (that is, the initial heap if the caller is a method and the old heap if the caller is
a two-state function). While there is never a choice in which heap gets passed as the current
heap, the caller can use any 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 like this
is done with the built-in old function.
For example, the following assertions all hold:
119
method Caller(c: Cell)
modifies c
{
c.data := c.data + 10;
label L:
assert Increasing(c);
c.data := c.data - 2;
assert Increasing(c);
assert !Increasing@L(c);
}
The first call to Increasing uses Caller’s initial state as the old-heap parameter, and so does
the second call. The third call instead uses as the old-heap parameter the heap at label L, which
is why the third call returns false. As shown in the example, an explicitly given old-heap
parameter is given after an @-sign (which follows the name of the function and any explicitly
given type parameters) and before the open parenthesis (after which the ordinary parameters
are given).
A two-state function is allowed to be called only from a two-state context, which means a method,
a two-state lemma (see below), or another two-state function. Just like a label used with an old
expression, any label used in a call to a two-state function must denote a program point that
dominates the call. This means that any control leading to the call must necessarily have passed
through the labeled program point.
Any parameter (including the receiver parameter, if any) passed to a two-state function must
have been allocated already in the old state. For example, the second call to Diff in method M
is illegal, since d was not allocated on entry to M:
twostate function Diff(c: Cell, d: Cell): int
reads d
{
d.data - old(c.data)
}
A two-state function may declare that it only assumes a parameter to be allocated in the current
heap. This is done by preceding the parameter with the new modifier, as illustrated in the
following example, where the first call to DiffAgain is legal:
twostate function DiffAgain(c: Cell, new d: Cell): int
reads d
{
120
d.data - old(c.data)
}
A two-state lemma works in an analogous way. It is a lemma with both a current-heap parameter
and an old-heap parameter, it can use old expressions in its specification (including in the
precondition) and body, its parameters may use the new modifier, and the old-heap parameter
is by default passed in as the 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:
function SeqSum(s: seq<Cell>): int
reads s
{
if s == [] then 0 else s[0].data + SeqSum(s[1..])
}
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 the time the first-class value is
mentioned. While the receiver and type parameters can be explicitly instantiated in such a use
121
(for example, p.F<int> for a two-state instance function F that takes one type parameter), there
is currently no syntactic support for giving the old-heap parameter explicitly. A caller can work
around this restriction by using (fancy-word alert!) eta-expansion, meaning wrapping a lambda
expression around the call, as in x => p.F<int>@L(x). The following example illustrates using
such an eta-expansion:
class P {
twostate function F<X>(x: X): X
}
Functions may be declared as ghost. If so, all the formal parameters and return values are ghost;
if it is not a ghost function, then individual parameters may be declared ghost as desired.
See Section 7.3 for a description of the function specification. A Dafny function is a pure
mathematical function. It is allowed to read memory that was specified in its reads expression
but is not allowed to have any side effects.
Here is an example function declaration:
function {:att1}{:att2} F<T1, T2>(a: A, b: B, c: C): T
requires Pre
reads Frame
ensures Post
decreases Rank
{
Body
}
where :att1 and :att2 are attributes of the function, if any, T1 and T2 are type parameters of
the function (if generic), a, b, c are the 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 may depend on, Post is a boolean expression denoting
the function’s postcondition, Rank is the function’s variant function, and Body is an expression
that defines the function’s return value. The precondition allows a function to be partial, that
122
is, the precondition says when the function is defined (and Dafny will verify that every use of
the function meets the precondition).
The postcondition is usually not needed, since the body of the function gives the full definition.
However, the postcondition can be a convenient place to declare properties of the function that
may require an inductive proof to establish, such as when the function is recursive. For example:
function Factorial(n: int): int
requires 0 <= n
ensures 1 <= Factorial(n)
{
if n == 0 then 1 else Factorial(n-1) * n
}
says that the result of Factorial is always positive, which Dafny verifies inductively from the
function body.
Within a postcondition, the result of the function is designated by a call of the function, such as
Factorial(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.
function Factorial(n: int): (f: int)
requires 0 <= n
ensures 1 <= f
{
if n == 0 then 1 else Factorial(n-1) * n
}
Pre v4.0, a function is ghost by default, and cannot be called from non-ghost code. To make
it non-ghost, replace the keyword function with the two keywords “function method”. From
v4.0 on, a function is non-ghost by default. To make it ghost, replace the keyword function
with the two keywords “ghost function”. (See the –function-syntax option for a description of
the migration path for this change in behavior.}
Like methods, functions can be either instance (which they are by default when declared within
a type) or static (when the function declaration contains the keyword static or is declared in
a module). An instance function, but not a static function, has an implicit receiver parameter,
this.
A static function F in a class C can be invoked by C.F(…). This provides a convenient way to
declare a number of helper functions in a separate class.
As for methods, a ... is used when declaring a function in a module refinement (cf. Section 10).
For example, if module M0 declares function F, a module M1 can be declared to refine M0 and
M1 can then refine F. The refinement function, M1.F can have a ... which means to copy the
signature from M0.F. A refinement function can furnish a body for a function (if M0.F does not
provide one). It can also add ensures 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.
123
Because body-less functions are unverified assumptions, Dafny will not compile them and will
complain if called by dafny translate, dafny build or even dafny run
6.4.2. Predicates
A function that returns a bool result is called a predicate. As an alternative syntax, a predicate
can be declared by replacing the function keyword with the predicate keyword and possibly
omitting a declaration of the return type (if it is not named).
6.4.3. Function-by-method
A function with a by method clause declares a function-by-method. A function-by-method gives
a way to implement a (deterministic, side-effect free) function by a method (whose body may
be nondeterministic and may allocate objects that it modifies). This can be useful if the best
implementation uses nondeterminism (for example, because it uses :| in a nondeterministic way)
in a way that does not affect the result, or if the implementation temporarily makes use of some
mutable data structures, or if the implementation is done with a loop. For example, here is the
standard definition of the Fibonacci function but with an efficient implementation that uses a
loop:
function Fib(n: nat): nat {
if n < 2 then n else Fib(n - 2) + Fib(n - 1)
} by method {
var x, y := 0, 1;
for i := 0 to n
invariant x == Fib(i) && y == Fib(i + 1)
{
x, y := y, x + y;
}
return x;
}
The by method clause is allowed only for non-ghost function or predicate declarations (with-
out twostate, least, and greatest, but possibly with static); it inherits the in-parameters,
attributes, and requires and decreases clauses of the function. The method also gets one out-
parameter, corresponding to the function’s result value (and the name of it, if present). Finally,
the method gets an empty modifies clause and a postcondition ensures r == F(args), where
r is the name of the out-parameter and F(args) is the function with its arguments. In other
words, the method body must compute and return exactly what the function says, and must do
so without modifying any previously existing heap state.
The function body of a function-by-method is allowed to be ghost, but the method body must
be compilable. In non-ghost contexts, the compiler turns a call of the function-by-method into
a call that leads to the method body.
Note, the method body of a function-by-method may contain print 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 context will be disallowed.
124
6.4.4. Function Hiding
A function is said to be revealed at a location if the body of the function is visible for verification
at that point, otherwise it is considered hidden.
Functions are revealed by default, but can be hidden using the hide 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 the hide
statement for more information.
Although mostly made obsolete by the hide statement, a function can also be hidden using the
opaque keyword, or using the option default-function-opacity. Here are the rules regarding
those:
Inside the module where the function is declared: - If --default-function-opacity is set to
transparent (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 a
reveal statement, then its body is available starting at that reveal statement.
• If --default-function-opacity is set to opaque, then:
– if there is no {:transparent} attribute, the function is opaque. If the function is
mentioned in a reveal statement, then the body of the function is available starting
at that reveal statement.
– if there is a {:transparent} attribute, then the function is transparent.
• If --default-function-opacity is set to autoRevealDependencies, 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 implic-
itly inserted reveal statement, unless the callable has the {autoRevealDependencies
k} attribute for some natural number k which is too low.
– if there is a {:transparent} attribute, then the function is transparent.
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 with reveals, 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 with provides it is
always hidden and is not permitted to be used in a reveal statement.
More information about the Boogie implementation of opaquenes is here.
125
To understand what this means and why this attribute is useful, consider the following example,
which specifies reachability between nodes in a directed graph. A Node is declared to have any
number of children:
class Node {
var children: seq<Node>
}
There are several ways one could specify reachability between nodes. One way (which is used in
Test/dafny1/SchorrWaite.dfy in the Dafny test suite) is to define a type Path, representing
lists of Nodes, and to define a predicate that checks if a given list of Nodes is indeed a path
between two given nodes:
datatype Path = Empty | Extend(Path, Node)
This looks like a good definition of reachability, but Dafny won’t admit it. The reason is twofold:
126
• Quantifiers and comprehensions are allowed to range only over allocated state. Ater all,
Dafny is a type-safe language where every object reference is valid (that is, a pointer to
allocated storage of the right type)—it should not be possible, not even through a bound
variable in a quantifier or comprehension, for a program to obtain an object reference that
isn’t valid.
• This property is ensured by disallowing open-ended quantifiers. More precisely, the object
references that a quantifier may range over must be shown to be confined to object refer-
ences that were allocated before some of the non-older parameters passed to the predicate.
Quantifiers that are not open-ended are called close-ended. Note that close-ended refers
only to the object references that the quantification or comprehension ranges over—it does
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 object references, then the quantifier is trivially close-ended. For example,
forall x: int :: x <= Square(x)
is trivially close-ended.
Another innocent-looking quantifier occurs in the following example:
predicate IsCommutative<X>(r: (X, X) -> bool)
{
forall x, y :: r(x, y) == r(y, x) // error: open-ended quantifier
}
Since nothing is known about type X, this quantifier might be open-ended. For example, if X were
passed in as a class type, then the quantifier would be open-ended. One way to fix this predicate
is to restrict it to non-heap based types, which is indicated with the (!new) type characteristic
(see Section 5.3.1.4):
ghost predicate IsCommutative<X(!new)>(r: (X, X) -> bool) // X is restricted to non-heap types
{
forall x, y :: r(x, y) == r(y, x) // allowed
}
Another way to make IsCommutative close-ended is to constrain the values of the bound variables
x and y. This can be done by adding a parameter to the predicate and limiting the quantified
values to ones in the given set:
predicate IsCommutativeInS<X>(r: (X, X) -> bool, S: set<X>)
{
forall x, y :: x in S && y in S ==> r(x, y) == r(y, x) // close-ended
}
Through a simple syntactic analysis, Dafny detects the antecedents x in S and y in S, and
since S is a parameter and thus can only be passed in as something that the caller has already
allocated, the quantifier in IsCommutativeInS is determined to be close-ended.
Note, the x in S trick does not work for the motivating example, Reachable. If you try to write
127
predicate Reachable(source: Node, sink: Node, S: set<Node>)
reads S
{
exists p :: p in S && ReachableVia(source, p, sink, S) // type error: p
}
you will get a type error, because p in S does not make sense if p has type Path. We need some
other way to justify that the quantification in Reachable is close-ended.
Dafny offers a way to extend the x in S trick to more situations. This is where the older
modifier comes in. Before we apply older in the Reachable example, let’s first look at what
older does in a less cluttered example.
Suppose we rewrite IsCommutativeInS using a programmer-defined predicate In:
predicate In<X>(x: X, S: set<X>) {
x in S
}
The simple syntactic analysis that looks for x in S finds nothing here, because the in operator
is relegated to the body of predicate In. To inform the analysis that In is a predicate that, in
effect, is like in, you can mark parameter x with older:
predicate In<X>(older x: X, S: set<X>) {
x in S
}
This causes the simple syntactic analysis to accept the quantifier in IsCommutativeInS. Adding
older also imposes a semantic check on the body of predicate In, enforced by the verifier. The
semantic check is that all the object references in the value x are older (or equally old as) the
object references that are part of the other parameters, in the event that the predicate returns
true. That is, older is designed to help the caller only if the predicate returns true, and the
semantic check amounts to nothing if the predicate returns false.
Finally, let’s get back to the motivating example. To allow the quantifier in Reachable, mark
parameter p of ReachableVia with older:
class Node {
var children: seq<Node>
}
128
reads S
{
exists p :: ReachableVia(source, p, sink, S) // allowed because of 'older p' on ReachableVia
}
This example is more involved than the simpler In example above. Because of the older modifier
on the parameter, the quantifier in Reachable is allowed. For intuition, you can think of the
effect of older p as adding an antecedent p in {source} + {sink} + S (but, as we have seen,
this is not type correct). The semantic check imposed on the body of ReachableVia makes sure
that, if the predicate returns true, then every object reference in p is as old as some object
reference in another parameter to the predicate.
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 declared nameonly, in which case a call site must always explicitly
name the formal when providing its actual argument.
129
For example, a function ff declared as
function ff(x: int, nameonly y: int): int
must be called either by listing the value for x and then y with a name, as in ff(0, y := 4)
or by giving both actuals by name (in any order). A nameonly formal may also have a default
value and thus be optional.
Any formals after a nameonly formal must either be nameonly 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 formal that has a default value.
The default-value expression for a parameter is allowed to mention the other parameters, includ-
ing this (for instance methods and instance functions), but not the implicit _k parameter in
least and greatest predicates and lemmas. The default value of a parameter may mention both
preceding and subsequent parameters, but there may not be any dependent cycle between the
parameters and their default-value expressions.
The well-formedness of default-value expressions is checked independent of the precondition of
the enclosing declaration. For a function, the parameter default-value expressions may only read
what the function’s reads clause allows. For a datatype constructor, parameter default-value
expressions may not read anything. A default-value expression may not be involved in any
recursive or mutually recursive calls with the enclosing declaration.
130
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, and termination information by means of specification clauses. For each kind of
specification, zero or more specification clauses (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 of specification clauses.
• At the top level are the entity declarations that include the 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 specifications that use them.
Specification clauses typically appear in a sequence. They all begin with a keyword and do not
end with semicolons.
The requires clauses specify preconditions for methods, functions, lambda expressions and
iterators. Dafny checks that the preconditions are met at all call sites. The callee may then
assume the preconditions hold on entry.
If no requires clause is specified, then a default implicit clause requires true is used.
If more than one requires clause is given, then the precondition is the conjunction of all of
the expressions from all of the requires clauses, with a collected list of all the given Attributes.
The order of conjunctions (and hence the order of requires clauses with respect to each other)
can be important: earlier conjuncts can set conditions that establish that later conjuncts are
well-defined.
The attributes recognized for requires clauses are discussed in Section 11.4.
A requires clause can have custom error and success messages.
131
7.1.2. Ensures Clause (grammar)
Examples:
method {:axiom} m(i: int) returns (r: int)
ensures r > 0
An ensures clause specifies the post condition for a method, function or iterator.
If no ensures clause is specified, then a default implicit clause ensures true is used.
If more than one ensures clause is given, then the postcondition is the conjunction of all of
the expressions from all of the ensures clauses, with a collected list of all the given Attributes.
The order of conjunctions (and hence the order of ensures clauses with respect to each other)
can be important: earlier conjuncts can set conditions that establish that later conjuncts are
well-defined.
The attributes recognized for ensures clauses are discussed in Section 11.4.
An ensures clause can have custom error and success messages.
Decreases clauses are used to prove termination in the presence of recursion. If more than one
decreases clause is given it is as if a single decreases clause had been given with the collected
list of arguments and a collected list of Attributes. That is,
decreases A, B
decreases C, D
is equivalent to
decreases A, B, C, D
Note that changing the order of multiple decreases clauses will change the order of the expres-
sions within the equivalent single decreases 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
with decreases *, which causes the proof of termination to be skipped. If a * is present in a
decreases clause, no other expressions are allowed in the decreases clause. A method that
contains a possibly non-terminating loop or a call to a possibly non-terminating method must
itself be declared as possibly non-terminating.
Termination metrics in Dafny, which are declared by decreases clauses, are lexicographic tuples
of expressions. At each recursive (or mutually recursive) call to a function or method, Dafny
132
checks that the effective decreases clause of the callee is strictly smaller than the effective
decreases 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 Boolean false is strictly smaller than true,
the integer 78 is strictly smaller than 102, the set {2,5} is strictly smaller than (because it is a
proper subset of) the set {2,3,5}, and for s of type seq<Color> where Color is some inductive
datatype, the color s[0] is strictly less than s (provided s is nonempty).
What does “effective decreases clause” mean? Dafny always appends a “top” element to the lex-
icographic tuple given by the user. This top element cannot be syntactically denoted in a Dafny
program and it never occurs as a run-time value either. Rather, it is a fictitious value, which
here we will denote ⊤, such that each value that can ever occur in a Dafny program is strictly
less than ⊤. Dafny sometimes also prepends expressions to the lexicographic tuple given by the
user. The effective decreases clause is any such prefix, followed by the user-provided decreases
clause, followed by ⊤. We said “user-provided decreases clause”, but if the user completely omits
a decreases clause, then Dafny will usually make a guess at one, in which case the effective
decreases clause is any prefix followed by the guess followed by ⊤.
Here is a simple but interesting example: the Fibonacci function.
function Fib(n: nat) : nat
{
if n < 2 then n else Fib(n-2) + Fib(n-1)
}
To prove termination of A and B, Dafny needs to have effective decreases clauses for A and B
such that:
• the measure for the callee B(x) is strictly smaller than the measure for the caller A(x), and
• the measure for the callee A(x-1) is strictly smaller than the measure for the caller B(x).
Satisfying the second of these conditions is easy, but what about the first? Note, for example,
133
that declaring both A and B with “decreases x” does not work, because that won’t prove a strict
decrease for the call from A(x) to B(x).
Here’s one possibility:
method A(x: nat)
decreases x, 1
{
B(x);
}
For the call from A(x) to B(x), the lexicographic tuple "x, 0" is strictly smaller than "x, 1",
and for the call from B(x) to A(x-1), the lexicographic 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 lexico-
graphic 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 keyword decreases 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 function Fib(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 ⊤ to the user-supplied
decreases clause. For the A-and-B example, this lets us drop the constant from the decreases
clause of A:
method A(x: nat)
decreases x
{
B(x);
}
134
The effective decreases clause of A is (𝑥, ⊤) and the effective decreases clause of B is (𝑥, 0, ⊤).
These tuples still satisfy the two conditions (𝑥, 0, ⊤) < (𝑥, ⊤) and (𝑥 − 1, ⊤) < (𝑥, 0, ⊤). And
as before, the constant “0” is arbitrary; anything less than ⊤ (which is any Dafny expression)
would work.
Let’s take a look at one more example that better illustrates the utility of ⊤. Consider again two
mutually recursive methods, call them Outer and Inner, representing the recursive counterparts
of what iteratively might be two nested loops:
method Outer(x: nat)
{
// set y to an arbitrary non-negative integer
var y :| 0 <= y;
Inner(x, y);
}
The body of Outer uses an assign-such-that statement to represent some computation that takes
place before Inner is called. It sets “y” to some arbitrary non-negative value. In a more concrete
example, Inner would do some work for each “y” and then continue as Outer on the next smaller
“x”.
Using a decreases clause (𝑥, 𝑦) for Inner seems natural, but if we don’t have any bound on the
size of the 𝑦 computed by Outer, there is no expression we can write in the decreases clause of
Outer that is sure to lead to a strictly smaller value for 𝑦 when Inner is called. ⊤ to the rescue.
If we arrange for the effective decreases clause of Outer to be (𝑥, ⊤) and the effective decreases
clause for Inner to be (𝑥, 𝑦, ⊤), then we can show the strict decreases as required. Since ⊤ is
implicitly appended, the two decreases clauses declared in the program text can be:
method Outer(x: nat)
decreases x
{
// set y to an arbitrary non-negative integer
var y :| 0 <= y;
Inner(x, y);
}
135
if y != 0 {
Inner(x, y-1);
} else if x != 0 {
Outer(x-1);
}
}
Moreover, remember that if a function or method has no user-declared decreases clause, Dafny
will make a guess. The guess is (usually) the list of arguments of the function/method, in the
order given. This is exactly the decreases clauses needed here. Thus, Dafny successfully verifies
the program without any explicit decreases clauses:
method Outer(x: nat)
{
var y :| 0 <= y;
Inner(x, y);
}
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 expressions in
the decreases clause. The first expression is the ordering of the 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 than that of any module it defines or imports. As a module cannot
call a method in a module that it does not depend on, this is an effective first component to the
overall decreases tuple.
The second prepended expression represents the position of 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 above the level of anything else they
call. With this level value prepended to the decreases clause, the decreases tuple automatically
decreases on any calls in a non-recursive context.
Though Dafny fixes a well-founded order that it uses when checking termination, Dafny does
136
not normally surface this ordering directly in expressions. However, it is possible to write such
ordering constraints using decreases to expressions.
Frame expressions are used to denote the set of memory locations that a Dafny program element
may read or write. They are used in reads and modifies clauses. A frame expression is a set
expression. The form {} is the empty set. The type of the frame expression is set<object>.
Note that framing only applies to the heap, or memory accessed through references. Local
variables are not stored on the heap, so they cannot be mentioned (well, they are not in scope
in the declaration) in frame annotations. Note also that types like sets, sequences, and multisets
are value types, and are treated like integers or local variables. Arrays and objects are reference
types, and they are stored on the heap (though as always there is a subtle distinction between
the reference itself and the value it points to.)
The FrameField construct is used to specify a field of a class object. The identifier following
the back-quote is the name of the field being referenced. If the FrameField is preceded by an
expression the expression must be a reference to an object having that field. If the FrameField
is not preceded by an expression then the frame expression is referring to that field of the current
object (this). This form is only used within a method of a class or trait.
A FrameField can be useful in the following case: When a method modifies only one field, rather
than writing
class A {
var i: int
var x0: int
var x1: int
var x2: int
var x3: int
var x4: int
method M()
modifies this
ensures unchanged(`x0) && unchanged(`x1) && unchanged(`x2) && unchanged(`x3) && unchanged(`x4)
{ i := i + 1; }
}
137
class A {
var i: int
var x0: int
var x1: int
var x2: int
var x3: int
var x4: int
method M()
modifies `i
{ i := i + 1; }
}
There’s (unfortunately) no form of it for array elements – but to account for unchanged elements,
you can always write forall i | 0 <= i < |a| :: unchanged(a[i]).
A FrameField is not taken into consideration for lambda expressions.
Functions are not allowed to have side effects; they may also be restricted in what they can read.
The reading frame of a function (or predicate) consists of all the heap memory locations that
the function is allowed to read. The reason we might 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
have the 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 on the sorted predicate, then when we
modify the unsorted array, we cannot determine whether the other array stopped being sorted.
While we might be able to give invariants to preserve it in this case, it gets even more complex
when manipulating data structures. In this case, framing is essential to making the verification
process feasible.
By default, methods are not required to list the memory location they read. However, there
138
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 to unsafe communication between concurrent executions. See the
{:concurrent} attribute for more details.
It is not just the body of a function or method that is subject to reads checks, but also its
precondition and the reads clause itself.
A reads clause can list a wildcard *, which allows the enclosing function or method to read
anything. This is the implicit default for methods with no reads 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 cases where the function is defined recursively, this makes it next to impossible
to make any use of the function. Nevertheless, as an experimental feature, the language allows
it (and it is sound). If a reads clause uses *, then the reads clause is not allowed to mention
anything else (since anything else would be irrelevant, anyhow).
A reads clause specifies the set of memory locations that a function, lambda, or method may
read. The readable memory locations are all the fields of all of the references given in the set
specified in the frame expression and the single fields given in FrameField elements of the frame
expression. For example, in
class C {
var x: int
var y: int
predicate f(c: C)
reads this, c`x
{
this.x == c.x
}
}
the reads clause allows reading this.x, this,y, and c.x (which may be the same memory
location as this.x). }
If more than one reads clause is given in a specification the effective read set is the union of
the sets specified. If there are no reads clauses the effective read set is empty. If * is given in a
reads clause it means any memory may be read.
If a reads clause refers to a sequence or multiset, that collection (call it c) is converted to a set by
adding an implicit set comprehension of the form set o: object | o in c before computing
the union of object sets from other reads clauses.
An expression in a reads 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 the union of the function’s image
over all inputs. For example, if F is a function from int to set<object>, then reads F has the
meaning
139
set x: int, o: object | o in F(x) :: o
For each function value f, Dafny defines the function f.reads, which takes the same arguments
as f and returns that set of objects that f reads (according to its reads clause) with those
arguments. f.reads has type T ~> set<object>, where T is the input type(s) of f.
This is particularly useful when wanting to specify the reads set of another function. For example,
function Sum adds up the values of f(i) where i ranges from lo to hi:
function Sum(f: int ~> real, lo: int, hi: int): real
requires lo <= hi
requires forall i :: f.requires(i)
reads f.reads
decreases hi - lo
{
if lo == hi then 0.0 else
f(lo) + Sum(f, lo + 1, hi)
}
Its reads specification says that Sum(f, lo, hi) may read anything that f may read on any
input. (The specification reads f.reads gives an overapproximation of what Sum will actually
read. More precise would be to specify that Sum reads only what f reads on the values from lo
to hi, but the larger set denoted by reads f.reads is easier to write down and is often good
enough.)
Without such reads function, one could also write the more precise and more verbose:
function Sum(f: int ~> real, lo: int, hi: int): real
requires lo <= hi
requires forall i :: lo <= i < hi ==> f.requires(i)
reads set i, o | lo <= i < hi && o in f.reads(i) :: o
decreases hi - lo
{
if lo == hi then 0.0 else
f(lo) + Sum(f, lo + 1, hi)
}
Note, only reads clauses, not modifies clauses, are allowed to include functions as just described.
Iterator specifications also allow reads clauses, with the same syntax and interpretation of
arguments as above, but the meaning is quite different! See Section 5.11 for more details.
140
method M()
modifies { o, p }
method N()
modifies { }
method Q()
modifies o, p`f
By default, methods are allowed to read whatever memory they like, but they are required to
list which parts of memory they modify, with a modifies annotation. These are almost identical
to their reads cousins, except they say what can be changed, rather than what the definition
depends on. In combination with reads, modification restrictions allow Dafny to prove properties
of code that would otherwise be very difficult or impossible. Reads and modifies are one of the
tools that allow Dafny to work on one method at a time, because they restrict what would
otherwise be arbitrary modifications of memory to something that Dafny can reason about.
Just as for a reads clause, the memory locations allowed to be modified in a method are all
the fields of any object reference in the frame expression set and any specific field denoted by a
FrameField in the modifies clause. For example, in
class C {
var next: C?
var value: int
method M()
modifies next
{
...
}
}
141
of the scope. For example, JML defines assignable and modifies as synonyms in the sense
(a), though KeY interprets JML’s assigns/modifies in sense (b). ACSL and ACSL++ use
the assigns keyword, but with modify (b) semantics. Ada/SPARK’s dataflow contracts encode
write (a) semantics.
An invariant clause is used to specify an invariant for a loop. If more than one invariant
clause is given for a loop, the effective invariant is the conjunction of the conditions specified, in
the order given in the source text.
The invariant must hold on entry to the loop. And assuming it is valid on entry to a particular
iteration of the loop, Dafny must be able to prove that it then holds at the end of that iteration
of the loop.
An invariant can have custom error and success messages.
142
7.3. Function Specification (grammar)
Examples:
class C {
var next: C?
var value: int
143
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
and also inserts, for every array-valued field A declared in the class:
(A != null ==> A in Repr) &&
and for every field F of a class type T where T has a field called Repr, also inserts
(F != null ==> F in Repr && F.Repr SUBSET Repr && this !in Repr && F.Valid())
except, if A or F is declared with {:autocontracts false}, then the implication will not be
added. - For every constructor, inserts
ensures Valid() && fresh(Repr)
In all the following cases, no modifies clause or reads clause is added if the user has given one.
144
• For every non-static non-ghost method that is not a “simple query method”, inserts
requires Valid()
modifies Repr
ensures Valid() && fresh(Repr - old(Repr))
• For every non-static non-twostate method that is either ghost or is a “simple query method”,
add:
requires Valid()
Dafny will split the verification in two assertion batches that will roughly look like the following
lemmas:
lemma Test_WellFormed(a: array?<int>)
{
assume a != null; // From the definition of a
assert a != null; // for the `requires a.Length >= 1`
145
assume a.Length >= 1; // After well-formedness, we assume the requires
assert a != null; // Again for the `a.Length % 2`
if a.Length % 2 == 0 {
assert a != null; // Again for the final `a.Length`
assert a.Length != 0; // Because of the 10 / a.Length
}
}
For this reason the IDE typically reports at least two assertion batches when hovering a method.
146
8. Statements (grammar)
Many of Dafny’s statements are similar to those in traditional programming 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
(‘}’).
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 be referenced in a break or continue statement within the labeled statement
(see Section 8.14). That is, the break or continue that mentions the label must be enclosed in
the labeled statement.
The label may also be used in an old expression (Section 9.22). In this case, the label must
have been encountered during the control flow en route to the old expression. We say in this
case that the (program point of the) label dominates the (program point of the) use of the
label. Similarly, labels are used to indicate previous states in calls of two-state predicates, fresh
expressions, unchanged expressions, and allocated expressions.
A statement can be given several labels. It makes no difference which of these labels 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 the same as any previous enclosing or dominating label.
147
var x := 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.
A return statement can only be used in a method. It is used to terminate the execution of the
method.
To return a value from a method, the value is assigned to one of the named out-parameters some-
time 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 are used when one wants to return
before reaching the end of the body block of the method.
Return statements can be just the return keyword (where the current values of the out-
parameters are used), or they can take a list of expressions to return. If a list is given, the
number of expressions given must be the same as the number of named out-parameters. These
expressions are evaluated, then they are assigned to the out-parameters, and then the method
terminates.
148
can be just the yield keyword (where the current values of the yield parameters are used), or
they can take a list of expressions to yield. If a list is given, the number of expressions given
must be the same as the number of named iterator out-parameters. These expressions are then
evaluated, then they are assigned to the yield parameters, and then the iterator yields.
This statement corresponds to familiar assignment or method call statements, with variations.
If more than one left-hand side is used, these must denote different l-values, unless the corre-
sponding right-hand sides also denote the same value.
The update statement serves several logical purposes.
As there are no left-hand-side locations to receive values, this form is allowed only for methods
that have no out-parameters.
149
8.5.2. Method call with out-parameters
This form uses := to denote the assignment of the out-parameters of the method to the corre-
sponding 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 of left-hand sides must
match the number of out-parameters of the method that is called. Note that the result of a
method call is not allowed to be used as an argument of another method call, as if it were an
expression.
The above example swaps the values of x and y. If more than one left-hand side is used, these
must denote different l-values, unless the corresponding right-hand sides also denote the same
value. There must be an equal number of left-hand sides and right-hand sides. The most common
case has only one RHS and one LHS.
This is read as assign values to x and y such that 0 < 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 the following example where we choose
an element in a set.
method Sum(X: set<int>) returns (s: int)
{
s := 0; var Y := X;
while Y != {}
decreases Y
150
{
var y: int;
y :| y in Y;
s, Y := s + y, Y - {y};
}
}
Dafny will report an error if it cannot prove that values exist that satisfy the condition.
In this variation, with an assume keyword
y :| assume y in Y;
method C() {
var v := M(1/3) by { // We prove 3 != 0 outside of the by proof
ProveP(); // Prove precondtion
}
assert v == 0; // Use postcondition
assert P(); // Fails
}
By placing the call to lemma ProveP inside of the by block, we can not use P after the method
call. The well-formedness checks of the arguments to the method call are not subject to the
separation.
151
8.6. Update with Failure Statement (:-) (grammar)
See the subsections below for examples.
A :-8 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 uses failure-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 a bool
• 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 meth-
ods (but deprecated) prior to Dafny 4. They will be required to be functions in Dafny 4.)
A failure-compatible type with an Extract member is called value-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 an expect, assume, or
assert 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 in Section 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 the Extract member
• the IsFailure and PropagateFailure methods may not be ghost
• the LHS expression assigned the output of the Extract member is ghost precisely if
Extract is ghost
The following subsections show various uses and alternatives.
152
{
predicate IsFailure() { this.Failure? }
function PropagateFailure(): Status
requires IsFailure()
{
Failure(this.error)
}
}
A commonly used alternative that carries some value information is something like this generic
type:
datatype Outcome<T> =
| Success(value: T)
| Failure(error: string)
{
predicate IsFailure() {
this.Failure?
}
function PropagateFailure<U>(): Outcome<U>
requires IsFailure()
{
Failure(this.error) // this is Outcome<U>.Failure(...)
}
function Extract(): T
requires !IsFailure()
{
this.value
}
}
153
Note that there is no LHS to the :- statement. If Callee returns Failure, then the caller
immediately returns, not executing any statements following the call of Callee. The value
returned by Caller (the value of rr in the code above) is the result of PropagateFailure
applied to the value returned by Callee, which is often just the same value. If Callee does not
return Failure (that is, returns a value for which IsFailure() is false) then that return value
is forgotten and execution proceeds normally with the statements following the call of Callee
in the body of Caller.
The desugaring of the :- Callee(i); statement is
var tmp;
tmp := Callee(i);
if tmp.IsFailure() {
rr := tmp.PropagateFailure();
return;
}
In this and subsequent examples of desugaring, the tmp variable is a new, unique variable, unused
elsewhere in the calling member.
Here Callee has two outputs in addition to the Status 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 the Status value:
• If Callee returns a failure value as its first output, then the other outputs are assigned,
the caller’s first out-parameter (here rr) is assigned the value of PropagateFailure, and
154
the caller returns.
• If Callee returns a non-failure value as its first output, then the other outputs are assigned
and the caller continues execution as normal.
The desugaring of the j, k :- Callee(i); statement is
var tmp;
tmp, j, k := Callee(i);
if tmp.IsFailure() {
rr := tmp.PropagateFailure();
return;
}
Suppose Caller is called with an argument of 10. Then Callee is called with argument 10
and returns r and v of Outcome<nat>.Success(10) and 20. Here r.IsFailure() is false, so
control proceeds normally. The j is assigned the result of r.Extract(), which will be 10, and
k is assigned 20. Control flow proceeds to the next line, where k now gets the value 40.
Suppose instead that Caller is called with an argument of -1. Then Callee is called with the
value -1 and returns r and v with values Outcome<nat>.Failure("negative") and -2. k is
assigned the value of v (-2). But r.IsFailure() is true, so control proceeds directly to return
from Caller. The first out-parameter of Caller (rr) gets the value of r.PropagateFailure(),
which is Outcome<int>.Failure("negative"); k already has the value -2. The rest of the body
of Caller is skipped. In this example, the first out-parameter of Caller has a failure-compatible
type so the exceptional return will propagate up the call stack. It will keep propagating up the
call stack as long as there are callers with this first special output type and calls that use :- and
the return value keeps having IsFailure() true.
155
The desugaring of the j, k :- Callee(i); statement in this example is
var tmp;
tmp, k := Callee(i);
if tmp.IsFailure() {
rr := tmp.PropagateFailure();
return;
}
j := tmp.Extract();
is
var k;
var tmp;
tmp, k := r, 7;
if tmp.IsFailure() {
rr := tmp.PropagateFailure();
return;
}
156
8.6.6. Failure with initialized declaration.
The :- syntax can also be used in initialization, as in
var s, t :- M();
This is equivalent to
var s, t;
s, t :- M();
with
expect !tmp.IsFailure(), tmp;
or
assert !tmp.IsFailure();
or
assume !tmp.IsFailure();
157
There is a grammatical nuance that the user should be aware of. The keywords assert, assume,
and expect can start an expression. For example, assert P; E can be an expression. However,
in e :- assert P; E; the assert is parsed as the keyword associated with :-. To have the
assert considered part of the expression use parentheses: e :- (assert P; E);.
158
8.6.9. Failure returns and exceptions
The :- mechanism is like the exceptions used in other programming languages, with some simi-
larities 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 to IsFailure().
• All methods that contain failure-return callees must explicitly handle those failures using
either :- statements or using := statements with a LHS to receive the failure value.
A variable declaration statement is used to declare one or more local variables in a method or
function. The type of each local variable must be given unless its type can be inferred, either
from a given initial value, or from other uses of the variable. If initial values are given, the
number of values must match the number of variables declared.
The scope of the declared variable extends to the end of the block in which it is declared.
However, be aware that if a simple variable declaration is followed by an expression (rather than
a subsequent statement) then the var begins a Let Expression and the scope of the introduced
variables is only to the end of the expression. In this case, though, the var is in an expression
context, not a statement context.
Note that the type of each variable must be given individually. The following code
var x, y : int;
var x, y := 5, 6;
var x, y :- m();
var x, y :| 0 < x + y < 10;
var (x, y) := makePair();
var Cons(x, y) = ConsMaker();
does not declare both x and y to be of type int. Rather it will give an error explaining that the
type of x is underspecified if it cannot be inferred from uses of x.
The variables can be initialized with syntax similar to update statements (cf. Section 8.5).
159
If the RHS is a call, then any variable receiving the value of a formal ghost out-parameter
will automatically be declared as ghost, even if the ghost keyword is not part of the variable
declaration statement.
The left-hand side can also contain a tuple of patterns that will be matched against the right-
hand-side. For example:
function returnsTuple() : (int, int)
{
(5, 10)
}
The initialization with failure operator :- returns from the enclosing method if the initializer
evaluates to a failure value of a failure-compatible type (see Section 8.6).
Guards are used in if and while statements as boolean expressions. Guards take two forms.
The first and most common form is just a boolean expression.
The second form is either * or (*). These have the same meaning. An unspecified boolean value
is returned. The value returned may be different each time it is executed.
160
An if statement can also take a binding 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 proceeds into the “then” branch; otherwise it proceeds with the “else” branch,
where the bound variables are not in scope.
In other words, the statement
if x :| P { S } else { T }
The identifiers bound by the binding guard are ghost variables and cannot be assigned to non-
ghost variables. They are only used in specification contexts.
Here is another example:
predicate P(n: int)
{
n % 2 == 0
}
161
assert i < 10;
}
if i == 0 {
x := 0;
} else if i > 0 {
x := 1;
} else {
x := -1;
}
if
case i == 0 => x := 0;
case i > 0 => x := 1;
case i < 0 => x := -1;
}
The simplest form of an if statement uses a guard that is a boolean expression. For example,
if x < 0 {
x := -x;
}
Unlike match statements, if statements do not have to be exhaustive: omitting the else block
is the same as including an empty else block. To ensure that an if statement is exhaustive,
use the if-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.
The if-case statement using the AlternativeBlock form is similar to the if ... fi construct
used in the book “A Discipline of Programming” by Edsger W. Dijkstra. It is used for a multi-
branch if.
For example:
method m(x: int, y: int) returns (max: int)
{
if {
162
case x <= y => max := y;
case y <= x => max := x;
}
}
In this form, the expressions following the case keyword are called guards. The statement is
evaluated by evaluating the guards in an undetermined order until one is found that is true
and the statements to the right of => for that guard are executed. The statement requires at
least one of the guards to evaluate to true (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 =>; it may 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.
match list {
case Nil => {}
case Cons(head,tail) => print head;
}
match x
case 1 =>
print x;
case 2 =>
var y := x*x;
print y;
case _ =>
print "Other";
// Any statement after is captured in this case.
The match 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, or an inductive or coinductive datatype (which
includes the built-in tuple types). The expression after the match keyword is called the selector.
The selector is evaluated and then matched against each clause in order until a matching clause
is found.
The process of matching the selector expression against the case patterns is the same as for
match expressions and is described in Section 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 the
section about case patterns to learn more about shadowing, constants, etc.
163
The code below shows an example of a match statement.
datatype Tree = Empty | Node(left: Tree, data: int, right: Tree)
Note that the Sum method is recursive yet has no decreases annotation. In this case it is not
needed because Dafny is able to deduce that t1 and t2 are smaller (structurally) than x. If
Tree had been coinductive this would not have been possible since x might have been infinite.
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 need loop specifications in order for Dafny to prove that they obey expected
164
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 in Section 7.6
and Section 8.15.
The general loop statement in Dafny is the familiar while statement. It has two general forms.
The first form is similar to a while loop in a C-like language. For example:
method m(){
var i := 0;
while i < 5 {
i := i + 1;
}
}
For this form, the guards are evaluated in some undetermined order until one is found that is true,
in which case the corresponding statements are executed and the while statement is repeated. If
none of the guards evaluates to true, then the loop execution is terminated.
The form that used ... (a refinement feature) as the guard is deprecated.
165
8.13. For Loops (grammar)
Examples:
method m() decreases * {
for i := 0 to 10 {}
for _ := 0 to 10 {}
for i := 0 to * invariant i >= 0 decreases * {}
for i: int := 10 downto 0 {}
for i: int := 10 downto 0
}
The for statement provides a convenient way to write some common loops.
The statement introduces a local variable with optional type, which is called the loop index. The
loop index is in scope in the specification and the body, but not after the for 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 given explicitly. If the identifier is not used, it can be written as _, as illustrated in
this repeat-20-times loop:
for _ := 0 to 20 {
Body
}
for i: T := hi downto lo
LoopSpec
{ Body }
for i: T := lo to *
LoopSpec
{ Body }
for i: T := hi downto *
LoopSpec
{ Body }
166
invariant _lo <= i <= _hi
LoopSpec
decreases _hi - i
{
Body
i := i + 1;
}
}
{
var _lo, _hi := lo, hi;
assert _lo <= _hi && forall _i: int :: _lo <= _i <= _hi ==> _i is T;
var i := _hi;
while i != lo
invariant _lo <= i <= _hi
LoopSpec
decreases i - _lo
{
i := i - 1;
Body
}
}
{
var _lo := lo;
assert forall _i: int :: _lo <= _i ==> _i is T;
var i := _lo;
while true
invariant _lo <= i
LoopSpec
{
Body
i := i + 1;
}
}
{
var _hi := hi;
assert forall _i: int :: _i <= _hi ==> _i is T;
var i := _hi;
while true
invariant i <= _hi
LoopSpec
{
i := i - 1;
167
Body
}
}
The expressions lo and hi are evaluated just once, before the loop iterations start.
Also, in all variations the values of i in the body are the values from lo to, but not including,
hi. This makes it convenient to write common loops, including these:
for i := 0 to a.Length {
Process(a[i]);
}
for i := a.Length downto 0 {
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 explicit decreases is allowed, since such a loop is already
known to terminate. If the end-expression is *, then the absence of an explicit decreases clause
makes it default to decreases *. So, if the end-expression is * and no explicit decreases clause
is given, the loop is allowed only in methods that are declared with decreases *.
The directions to or downto are contextual keywords. That is, these two words are part of the
syntax of the for loop, but they are not reserved keywords elsewhere.
Just like for while loops, the body of a for-loop may be omitted during verification. This
suppresses attempts to check assertions (like invariants) that would occur at the end of the loop.
Eventually, however a body must be provided; the compiler will not compile a method containing
a body-less for-loop. There is more discussion about bodyless loops in Section 8.15.4.
168
Break and continue statements provide a means to transfer control in 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 statement with that
label. The enclosing statement is called the target of the break or continue.
A break statement transfers control to the point immediately following the target statement.
For example, such a break statement can be used to exit a sequence of statements in a block
statement before reaching the end of the block.
For example,
label L: {
var n := ReadNext();
if n < 0 {
break L;
}
DoSomething(n);
}
is equivalent to
{
var n := ReadNext();
if 0 <= n {
DoSomething(n);
}
}
If no label is specified and the statement lists n occurrences of break, then the statement must
be enclosed in at least n levels of loop statements. Control continues after exiting n enclosing
loops. For example,
method m() {
for i := 0 to 10 {
for j := 0 to 10 {
label X: {
for k := 0 to 10 {
if j + k == 15 {
break break;
}
}
}
}
// control continues here after the "break break", exiting two loops
}
}
Note that a non-labeled break pays attention only to loops, not to labeled statements. For
169
example, the labeled block X in the previous example does not play a role in determining the
target statement of the break break;.
For a continue statement, the target statement must be a loop statement. The continue state-
ment transfers control to the point immediately before the closing curly-brace of the loop body.
For example,
method m() {
for i := 0 to 100 {
if i == 17 {
continue;
}
DoSomething(i);
}
}
method DoSomething(i:int){}
is equivalent to
method m() {
for i := 0 to 100 {
if i != 17 {
DoSomething(i);
}
}
}
method DoSomething(i:int){}
The same effect can also be obtained by wrapping the loop body in a labeled block statement
and then using break with a label, but that usually makes for a more cluttered program:
method m() {
for i := 0 to 100 {
label LoopBody: {
if i == 17 {
break LoopBody;
}
DoSomething(i);
}
}
}
method DoSomething(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 natural for for loops. For a while
loop, be careful to make progress toward termination before a continue statement. For example,
the following program snippet shows an easy mistake to make (the verifier will complain that
the loop may not terminate):
170
method m() {
var i := 0;
while i < 100 {
if i == 17 {
continue; // error: this would cause an infinite loop
}
DoSomething(i);
i := i + 1;
}
}
method DoSomething(i:int){}
The continue statement can give a label, provided the label is a label of a loop. For example,
method m() {
label Outer:
for i := 0 to 100 {
for j := 0 to 100 {
if i + j == 19 {
continue Outer;
}
WorkIt(i, j);
}
PostProcess(i);
// the "continue Outer" statement above transfers control to here
}
}
method WorkIt(i:int, j:int){}
method PostProcess(i:int){}
If a non-labeled continue statement lists n occurrences of break before the continue keyword,
then the statement must be enclosed in at least n + 1 levels of loop statements. The effect is
to break out of the n most closely enclosing loops and then continue the iterations of the next
loop. That is, n occurrences of break followed by one more break; will break out of n levels of
loops and then do a break, whereas n occurrences of break followed by continue; will break
out of n levels of loops and then do a continue.
For example, the WorkIt example above can equivalently be written without labels as
method m() {
for i := 0 to 100 {
for j := 0 to 100 {
if i + j == 19 {
break continue;
}
WorkIt(i, j);
}
171
PostProcess(i);
// the "break continue" statement above transfers control to here
}
}
method WorkIt(i:int, j:int){}
method PostProcess(i:int){}
Note that a loop invariant is checked on entry to a loop and at the closing curly-brace of the
loop body. It is not checked at break statements. For continue statements, the loop invariant is
checked as usual at the closing curly-brace that the continue statement jumps to. This checking
ensures that the loop invariant holds at the very top of every iteration. Commonly, the only
exit out of a loop happens when the loop guard evaluates to false. Since no state is changed
between the top of an iteration (where the loop invariant is known to hold) and the evaluation
of the loop guard, one can also rely on the loop invariant to hold immediately following the loop.
But the loop invariant may not hold immediately following a loop if a loop iteration changes the
program state and then exits the loop with a break statement.
For example, the following program verifies:
method m() {
var i := 0;
while i < 10
invariant 0 <= i <= 10
{
if P(i) {
i := i + 200;
break;
}
i := i + 1;
}
assert i == 10 || 200 <= i < 210;
}
predicate P(i:int)
To explain the example, the loop invariant 0 <= i <= 10 is known to hold at the very top of
each iteration, that is, just before the loop guard i < 10 is evaluated. If the loop guard evaluates
to false, then the negated guard condition (10 <= i) and the invariant hold, so i == 10 will
hold immediately after the loop. If the loop guard evaluates to true (that is, i < 10 holds),
then the loop body is entered. If the test P(i) then evaluates to true, the loop adds 200 to i
and breaks out of the loop, so on such a path, 200 <= i < 210 is known to hold immediately
after the loop. This is summarized in the assert statement in the example. So, remember, a loop
invariant holds at the very top of every iteration, not necessarily immediately after the loop.
172
to help Dafny prove the effect of the loop. This information is provided by a loop specification. A
loop specification provides information about invariants, termination, and what the loop modifies.
For additional tutorial information see (Koenig and Leino 2012) or the online Dafny tutorial.
When you specify an invariant, Dafny proves two things: the invariant holds upon entering the
loop, and it is preserved by the loop. By preserved, we mean that assuming that the invariant
holds at the beginning of the loop (just prior to the loop test), we must show that executing the
loop body once makes the invariant hold again. Dafny can only know upon analyzing the loop
body what the invariants say, in addition to the loop guard (the loop condition). Just as Dafny
will not discover properties of a method on its own, it will not know that any but the most basic
properties of a loop are preserved unless it is told via an invariant.
173
Many times, an integral value (natural or plain integer) is the quantity that decreases, but other
values can be used as well. In the case of integers, the bound is assumed to be zero. For each loop
iteration the decreases expression at the end of the loop body must be strictly smaller than
its value at the beginning of the loop body (after the loop test). For integers, the well-founded
relation between x and X is x < X && 0 <= X. Thus if the decreases value (X) is negative at
the loop test, it must exit the loop, since there is no permitted value for x to have at the end of
the loop body.
For example, the following is a proper use of decreases on a loop:
method m(n: nat){
var i := n;
while 0 < i
invariant 0 <= i
decreases i
{
i := i - 1;
}
}
Here Dafny has all the ingredients it needs to prove termination. The variable i becomes smaller
each loop iteration, and is bounded below by zero. When i becomes 0, the lower bound of the
well-founded order, control flow exits the loop.
This is fine, except the loop is backwards compared to most loops, which tend to count up instead
of down. In this case, what decreases is not the counter itself, but rather the distance between
the counter and the upper bound. A simple trick for dealing with this situation is given below:
method m(m: nat, n: int)
requires m <= n
{
var i := m;
while i < n
invariant 0 <= i <= n
decreases n - i
{
i := i + 1;
}
}
This is actually Dafny’s guess for this situation, as it sees i < n and assumes that n - i is the
quantity that decreases. The upper bound of the loop invariant implies that 0 <= n – i, and
gives Dafny a lower bound on the quantity. This also works when the bound n is not constant,
such as in the binary search algorithm, where two quantities approach each other, and neither
is fixed.
If the decreases clause of a loop specifies *, then no termination check will be performed. Use
of this feature is sound only with respect to partial correctness.
174
8.15.3. Loop framing
The specification of a loop also includes framing, which says what the loop modifies. The loop
frame includes both local variables and locations in the heap.
For local variables, the Dafny verifier performs a syntactic scan of the loop body to find every
local variable or out-parameter that occurs as a left-hand side of an assignment. These variables
are called syntactic assignment targets of the loop, or syntactic loop targets for short. Any local
variable or out-parameter that is not a syntactic assignment target is known by the verifier to
remain unchanged by the loop.
The heap may or may not be a syntactic loop target. It is when the loop body syntactically
contains a statement that can modify a heap location. This includes calls to compiled methods,
even if such a method has an empty modifies clause, since a compiled method is always allowed
to allocate new objects and change their values in the heap.
If the heap is not a syntactic loop target, then the verifier knows the heap remains unchanged
by the loop. If the heap is a syntactic loop target, then the loop’s effective modifies clause
determines what is allowed to be modified by iterations of the loop body.
A loop can use modifies clauses to declare the effective modifies clause of the loop. If a loop
does not explicitly declare any modifies clause, then the effective modifies clause of the loop is
the effective modifies clause of the most tightly enclosing loop or, if there is no enclosing loop,
the modifies clause of the enclosing method.
In most cases, there is no need to give an explicit modifies clause for a loop. The one case
where it is sometimes needed is if a loop modifies less than is allowed by the enclosing method.
Here are two simple methods that illustrate this case:
class Cell {
var data: int
}
175
method M1(c: Cell)
modifies c
ensures c.data == 100
{
c.data := 100;
var i := 0;
while i < 100
// Needs "invariant c.data == 100" or "modifies {}" to verify
{
var tmp := new Cell;
tmp.data := i;
i := i + 1;
}
}
In M0, the effective modifies clause of the loop is modifies c, d. Therefore, the method’s
postcondition c.data == 100 is not provable. To remedy the situation, the loop needs to be
declared either with invariant c.data == 100 or with modifies d.
Similarly, the effective modifies clause of the loop in M1 is modifies c. Therefore, the method’s
postcondition c.data == 100 is not provable. To remedy the situation, the loop needs to be
declared either with invariant c.data == 100 or with modifies {}.
When a loop has an explicit modifies clause, there is, at the top of every iteration, a proof
obligation that
• the expressions given in the modifies clause are well-formed, and
• everything indicated in the loop modifies clause is allowed to be modified by the (effective
modifies clause of the) enclosing loop or method.
176
even though there is now no proof that the Dafny specifications are satisfied by 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 an
axiom. If you intend to use a lemma as an axiom, omit its body and add the attribute {:axiom},
which causes 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. The verifier
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 the
compiler’s complaint.
By supplying a body for a method or function, the verifier will in effect show the feasibility of
the 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
to provide an appropriate modifies or reads clause in the specification, or forgetting that the
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 have
two sides. One side (analogous to callers) is the context that uses the loop. That context treats
the loop in the same way, using its specifications, regardless of whether or not the loop has
a body. The other side is the loop body, that is, the implementation of each loop iteration.
The verifier checks that 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 temporarily postpone the authoring of the loop body until after you’ve made sure that the
loop specification is 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 (see Section
8.15.3). For a body-less loop, the local variables computed as part of the loop frame are the
mutable variables that occur free in the loop specification. The heap is considered a part of the
loop frame if it is used for mutable fields in the loop specification or if the loop has an explicit
modifies clause. The IDE will display the computed loop frame in hover text.
For example, consider
class Cell {
var data: int
const K: int
}
177
var a, b := n, n;
for i := 0 to n
invariant c.K < 10
invariant a <= n
invariant c.data < 10
assert a == n;
assert b == n;
assert c.data == 5;
}
The loop specification mentions local variable a, and thus a is considered part of the loop frame.
Since what the loop invariant says about a is not strong enough to prove the assertion a == n
that follows the loop, the verifier complains about that assertion.
Local variable b is not mentioned in the loop specification, and thus b is not included in the loop
frame. Since in-parameter n is immutable, it is not included in the loop frame, either, despite
being mentioned in the loop specification. For these reasons, the assertion b == n is provable
after the loop.
Because the loop specification mentions the mutable field data, the heap becomes part of the
loop frame. Since the loop invariant is not strong enough to prove the assertion c.data ==
5 that follows the loop, the verifier complains about that assertion. On the other hand, had
c.data < 10 not been mentioned in the loop specification, the assertion would be verified, since
field K is then the only field mentioned in the loop specification and K is immutable.
Finally, the aggregate statement (forall) can also be given without a body. Such a statement
claims that the given ensures clause holds true for all values of the bound variables that satisfy
the given range constraint. If the statement has no body, the program is in effect omitting
the proof, much like a body-less lemma is omitting the proof of the claim made by the lemma
specification. As with the other body-less constructs above, the verifier is silently happy with a
body-less forall statement, but the compiler will complain.
The print statement is used to print the values of a comma-separated list of expressions to the
console (standard-out). The generated code uses target-language-specific idioms to perform this
printing. The expressions may of course include strings that are used for captions. There is
no implicit new line added, so to add a new line you must include "\n" as part of one of the
expressions. Dafny automatically creates implementations of methods that convert values to
strings for all Dafny data types. For example,
datatype Tree = Empty | Node(left: Tree, data: int, right: Tree)
method Main()
{
178
var x : Tree := Node(Node(Empty, 1, Empty), 2, Empty);
print "x=", x, "\n";
}
Note that Dafny does not have method overriding and there is no mechanism to override the
built-in value->string conversion. Nor is there a way to explicitly invoke this conversion. One
can always write an explicit function to convert a data value to a string and then call it explicitly
in a print statement or elsewhere.
By default, Dafny does not keep track of print effects, but this can be changed using the
--track-print-effects command line flag. print statements are allowed only in non-ghost
contexts and not in expressions, with one exception. The exception is that a function-by-method
may contain print 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 the print state-
ment.
Note: print writes to standard output. To improve compatibility with native code and external
libraries, the process of encoding Dafny strings passed to print into standard-output byte strings
is left to the runtime of the language that the Dafny code is compiled to (some language runtimes
use UTF-8 in all cases; others obey the current locale or console encoding).
In most cases, the standard-output encoding can be set before running the compiled program
using language-specific flags or environment variables (e.g. -Dfile.encoding= for Java). This is
in fact how dafny run operates: it uses language-specific flags and variables to enforce UTF-8
output regardless of the target language (but note that the C++ and Go backends currently
have limited support for UTF-16 surrogates).
Assert statements are used to express logical propositions that are expected to be true. Dafny
will attempt to prove that the assertion is 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.
179
Assert statements are ignored by the compiler.
In the by form of the assert statement, there is an additional block of statements that provide
the Dafny verifier with additional proof steps. Those statements are often a sequence of lemmas,
calc statements, reveal statements or other assert statements, combined with ghost control
flow, ghost variable declarations and ghost update statements of variables declared in the by
block. The intent is that those statements be evaluated in support of proving the assert
statement. For that purpose, they could be simply inserted before the assert statement. But
by using the by 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 subsequent
proofs of assertions later in the program. Furthermore, by isolating the statements in the by
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 the reveal statement and in Different
Styles of Proof
An assert statement may have a label, whose use is explained in Section 8.20.1.
The attributes recognized for assert statements are discussed in Section 11.4.
Using ... as the argument of the statement is deprecated.
An assert statement can have custom error and success messages.
The assume statement lets the user specify a logical proposition that Dafny may assume to be
true without proof. If in fact the proposition is not true this may lead to invalid conclusions.
An assume statement would ordinarily be used as part of a larger verification effort where
verification of some other part of the program required the proposition. By using the assume
statement the other verification can proceed. Then when that is completed the user would come
back and replace the assume with assert.
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.
180
The expect statement states a boolean expression that is (a) assumed to be true by the verifier
and (b) checked to be true at run-time. That is, the compiler inserts into the run-time executable
a check that the given expression is true; if the expression is false, then the execution of the
program halts immediately. If a second argument is given, it may be a value of any type. That
value is converted to a string (just like the print statement) and the string is included in the
message emitted by the program when it halts; otherwise a default message is emitted.
Because the expect expression and optional second argument are compiled, they cannot be ghost
expressions.
The expect statement behaves like assume for the verifier, but also inserts a run-time check that
the assumption is indeed correct (for the test cases used at run-time).
Here are a few use-cases for the expect statement.
A) To check the specifications of external methods.
Consider an external method Random that takes a nat as input and returns a nat value that is
less than the input. Such a method could be specified as
method {:extern} Random(n: nat) returns (r: nat)
ensures r < n
But because there is no body for Random (only the external non-dafny implementation), it cannot
be verified that Random actually satisfies this specification.
To mitigate this situation somewhat, we can define a wrapper function, Random', that calls
Random but in which we can put some run-time checks:
method {:extern} Random(n: nat) returns (r: nat)
Here we can verify that Random' satisfies its own specification, relying on the unverified specifi-
cation of Random. But we are also checking at run-time that any input-output pairs for Random
encountered during execution do satisfy the specification, as they are checked by the expect
statement.
Note, in this example, two problems still remain. One problem is that the out-parameter of the
extern Random has type nat, but there is no check that the value returned really is non-negative.
It would be better to declare the out-parameter of Random to be int and to include 0 <= r in
the condition checked by the expect statement in Random'. The other problem is that Random
surely will need n to be strictly positive. This can be fixed by adding requires n != 0 to
Random' and Random.
B) Run-time testing
181
Verification and run-time testing are complementary and both have their role in assuring that
software does what is intended. Dafny can produce executables and these can be instrumented
with unit tests. Annotating a method with the {:test} attribute indicates to the compiler that
it should produce target code that is correspondingly annotated to mark the method as a unit
test (e.g., an XUnit test) in the target language. Alternatively, the dafny test command will
produce a main method that invokes all methods with the {:test} attribute, and hence does
not depend on any testing framework in the target language. Within such methods one might
use expect statements (as well as print 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 combi-
nation. If an assert statement does not prove, one might run the program with a corresponding
expect statement to see if there are some conditions when the assert is not actually true. So one
might have paired assert/expect statements:
assert _P_;
expect _P_;
Once the program is debugged, both statements can be removed. Note that it is important that
the assert come before the expect, because by the verifier, the expect is interpreted as an
assume, which would automatically make a subsequent assert succeed.
D) Compiler tests
The same approach might be taken to assure that compiled code is behaving at run-time consis-
tently 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 that P is always true at the given point in a program (at the assert
statement).
At run-time, the compiler will insert checks that the same predicate, in the expect statement,
is true. Any difference identifies a compiler bug. Again the expect must be after the assert: if
the expect is first, then the verifier will interpret the expect like an assume, in which case the
assert will be proved trivially and potential unsoundness will be hidden.
Using ... as the argument of the statement is deprecated.
The reveal statement makes available to the solver information that is otherwise not visible, as
described in the following subsections.
182
8.20.1. Revealing assertions
If an assert statement has an expression label, then a proof of that assertion is attempted, but
the assertion itself is not used subsequently. For example, consider
method m(i: int) {
assert x: i == 0; // Fails
assert i == 0; // Fails also because the label 'x:' hides the first assertion
}
The first assertion fails. Without the label x:, 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 from the rest of the program. That assertion can be revealed by
adding a reveal statement:
method m(i: int) {
assert x: i == 0; // Fails
reveal x;
assert i == 0; // Now succeeds
}
or
method m(i: int) {
assert x: i == 0; // Fails
assert i == 0 by { reveal x; } // Now succeeds
}
At the point of the reveal 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 where the reveal is in an assert-by block or
much later in the method body.
183
The above method will not verify. In particular, the second postcondition cannot be proved.
However, if we add a reveal L; statement in the body of the method, then the precondition is
visible and both postconditions can be proved.
One could also use this style:
method m(x: int, y: int) returns (z: int)
requires L: 0 < y
ensures z == x+y
ensures x < z
{
z := x + y;
assert x < z by { reveal L; }
}
The reason to possibly hide a precondition is the same as the reason to hide assertions: some-
times less information is better for the solver as it helps the solver focus attention on relevant
information.
Section 7 of http://leino.science/papers/krml276.html provides an extended illustration of this
technique to make all the dependencies of an assert explicit.
// We reveal ComplicatedBody inside the following expression, to prove that we are not dividing b
var z := (reveal ComplicatedBody; 10 / x);
}
184
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-refresh
predicate P() { true }
predicate Q(x: bool) requires x
method Foo() {
var q1 := Q(hide P; P()); // error, precondition not satisfied
var q2 := Q(hide P; reveal P; P()); // no error
hide *;
if (*) {
reveal P;
assert P();
} else {
assert P(); // error
}
reveal P;
if (*) {
assert P();
} else {
hide *;
assert P(); // error
}
hide *;
if (*) {
reveal P;
} else {
reveal P;
}
assert P(); // error, since the previous two reveal statements are out of scope
}
185
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.
The forall statement executes the body simultaneously for all quantified values in the specified
quantifier domain. You can find more details about quantifier domains here.
There are several variant uses of the forall statement and there are a number of restrictions.
A forall 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 has ensure expressions which are effectively quantified or proved by
the body (if present).
An assign forall statement performs simultaneous assignment. The left-hand sides must denote
different l-values, unless the corresponding right-hand sides also coincide.
The following is an excerpt of an example given by Leino in Developing Verified Programs with
Dafny. When the buffer holding the queue needs to be resized, the forall statement is used to
simultaneously copy the old contents into the new buffer.
class SimpleQueue<Data(0)>
{
ghost var Contents: seq<Data>
var a: array<Data> // Buffer holding contents of queue.
var m: int // Index head of queue.
var n: int // Index just past end of queue
186
{
if n == a.Length {
var b := a;
if m == 0 { b := new Data[2 * a.Length]; }
forall i | 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 a call forall statement and the callee. This is contained in the
CloudMake-ConsistentBuilds.dfy test in the Dafny repository.
method m() {
forall cmd', deps', e' |
Hash(Loc(cmd', deps', e')) == Hash(Loc(cmd, deps, e)) {
HashProperty(cmd', deps', e', cmd, deps, e);
}
}
The following example of a proof forall statement comes from the same file:
forall p | p in DomSt(stCombinedC.st) && p in DomSt(stExecC.st)
ensures GetSt(p, stCombinedC.st) == GetSt(p, stExecC.st)
{
assert DomSt(stCombinedC.st) <= DomSt(stExecC.st);
assert stCombinedC.st == Restrict(DomSt(stCombinedC.st),
stExecC.st);
}
is used to invoke Lemma(x) on all x for which P(x) holds. If Lemma(x) ensures Q(x), then the
forall statement establishes
forall x :: P(x) ==> Q(x).
The forall statement is also used extensively in the de-sugared forms of co-predicates and
co-lemmas. See datatypes.
187
8.22. Modify Statement (grammar)
The effect of the modify statement is to say that some undetermined modifications have been
made to any or all of the memory locations specified by the given frame expressions. In the
following example, a value is assigned to field x followed by a modify statement that may modify
any field in the object. After that we can no longer prove that the field x still has the value we
assigned to it. The now unknown values still are values of their type (e.g. of the subset type or
newtype).
class MyClass {
var x: int
method N()
modifies this
{
x := 18;
modify this;
assert x == 18; // error: cannot conclude this here
}
}
188
including itself). Calculations with fewer than two lines are allowed, but have no effect. If a step
operator is omitted, it defaults to the calculation-wide operator, defined after the calc keyword.
If that operator is omitted, it defaults to equality.
Here is an example using calc statements to prove an elementary algebraic identity. As it turns
out, Dafny is able to prove this without the calc statements, but the example illustrates the
syntax.
lemma docalc(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 * c
x * (x + y) + y * (x + y);
==
// distributive law: a * (b + c) == a * b + a * c
x * 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 * a
1 * x * y + 1 * x * y;
==
// Distributive law
(1 + 1) * x * y;
==
2 * x * y;
}
x * x + 2 * x * y + y * y;
}
}
189
The == operators show the relation between the previous expression and the next. Because of
the transitivity of equality we can then conclude that the original left-hand-side is equal to the
final expression.
We can avoid having to supply the relational operator between every pair of expressions by giving
a default operator between the calc keyword and the opening brace as shown in this abbreviated
version of the above calc statement:
lemma docalc(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 omitted it after the calc keyword. The
purpose of the block statements or the calc statements between the expressions is to provide
hints to aid Dafny in proving that step. As shown in the example, comments can also be used
to aid the human reader in cases where Dafny can prove the step automatically.
190
{
x := x + y;
x := x + 2;
}
assert x == 4; // error
x := 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.
191
9. Expressions
Dafny expressions come in three flavors. - The bulk of expressions have no side-effects and can
be used within methods, functions, and specifications, and in either compiled or ghost code. -
Some expressions, called right-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 of right-hand-side expressions. Note
that method calls are syntactically indistinguishable from function calls; both are Expressions
(PrimaryExpressions with an ArgumentList suffix). However, method calls are semantically
permitted only in right-hand-side expression locations. - Some expressions are allowed only in
specifications and other ghost code, as listed here.
The grammar of Dafny expressions follows a hierarchy that reflects the precedence of Dafny
operators. The following table shows the Dafny operators and their precedence in order of
increasing binding power.
192
operator precedence description
————————– ———————
—————
+ 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
This expression has the form S; E. The type of the expression is the type of E. S must be a
lemma call (though the grammar appears more lenient). The lemma introduces a fact necessary
to establish properties of E.
Sometimes an expression will fail unless some relevant fact is known. In the following example
the F_Fails function fails to verify because the Fact(n) divisor may be zero. But preceding the
expression by a lemma that ensures that the denominator is not zero allows function F_Succeeds
to succeed.
function Fact(n: nat): nat
{
193
if n == 0 then 1 else n * Fact(n-1)
}
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 that E 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 that E 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 expression E (which may
be another ; expression).
An Equivalence Expression that contains one or more <==>s is a boolean expression and all the
operands must also be boolean expressions. In that case each <==> operator tests for logical
equality which is the same as ordinary equality (but with a different precedence).
See Section 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
and A <==> (B <==> C) and C <==> B <==> A are all equivalent and are all true iff an even
number of operands are false.
194
9.3. Implies or Explies Expressions (grammar)
Examples:
A ==> B
A ==> B ==> C ==> D
B <== A
See Section 5.2.1.3 for an explanation of the ==> and <== operators.
Note that the Dafny grammar allows a conjunction or disjunction to be prefixed with && or ||
respectively. This form simply allows a parallel structure to be written:
method m(x: object?, y:object?, z: object?) {
var b: bool :=
&& x != null
&& y != null
&& z != null
;
}
This is purely a syntactic convenience allowing easy edits such as reordering lines or commenting
out lines without having to check that the infix operators 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 or A && (B || C) instead.
See Section 5.2.1.2 for an explanation of the && and || operators.
195
The relation expressions compare two or more terms. As explained in the section about basic
types, ==, !=, <, >, <=, and >= are chaining.
The in and !in operators apply to collection types as explained in Section 5.5 and represent
membership or non-membership respectively.
The !! represents disjointness for sets and multisets as explained in Section 5.5.1 and Section
5.5.2.
x ==#[k] y is the prefix equality operator that compares coinductive values for equality to a
nesting level of k, as explained in the section about co-equality.
These operators are the left and right shift operators for bit-vector values. They take a bit-vector
value and an int, shifting the bits by the given amount; 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 of bits in the bit-vector type, inclusive.
The operations are left-associative: a << i >> j is (a << i) >> j.
Terms combine Factors 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 and Section 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. Sub-
traction is neither; it groups to the left as expected: x - y -z is (x - y) -z.
196
x * y
x / y
x % y
A Factor combines expressions using multiplication, division, or modulus. For numeric types
these are explained in Section 5.2.2. As explained there, / and % on int values represent Eu-
clidean integer division and modulus and not the typical C-like programming language operations.
Only * has a non-numeric application. It represents set or multiset intersection as explained in
Section 5.5.1 and Section 5.5.2.
* is commutative and associative; / and % are neither but do group to the left.
These operations take two bit-vector values of the same type, returning a value of the same
type. The operations perform bit-wise or (|), and (&), and exclusive-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 or a & (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 a cardinality expression: an ambiguity arises if the expression E in |
E | contains a |. This situation is easily remedied: just enclose E in parentheses, as in |(E)|.
The only type-correct way this can happen is if the expression is a comprehension, as in | set
x: int :: x | 0x101 |.
The as expression converts the given LHS to the type stated on the RHS, with the result being
of the given type. The following combinations of 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
197
• 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 the as operation, such as
from a subset type to its base. In any case, it must be able to be proved that the value of the
given expression is a legal value of the given type. For example, 5 as MyType is permitted (by
the verifier) only if 5 is a legitimate value ofMyType (which must be a numeric type).
The as operation is like a grammatical suffix or postfix operation. However, note that the unary
operations bind more tightly than does as. That is - 5 as nat is (- 5) as nat (which fails),
whereas a * b as nat is a * (b as nat). On the other hand, - a[4] is - (a[4]).
The is expression is grammatically similar to the as expression, with the same binding power.
The is expression is a type test that returns a bool value indicating whether the LHS expression
is a legal value of the RHS type. The expression can be used to check whether a trait value is of
a particular class type. That is, the expression in effect checks the allocated type of a trait.
The RHS type of an is expression can always be a supertype of the type of the LHS expression,
in which case the result is trivally true. Other than that, the RHS must be based on a reference
type and the LHS expression must be assignable to the RHS type. Furthermore, in order to be
compilable, the RHS type must not be a subset type other than a non-null reference type, and
the type parameters of the RHS must be uniquely determined from the type parameters of the
LHS type. The last restriction is designed to make it possible to perform type tests without
inspecting type parameters at run time. For example, consider the following types:
trait A { }
trait B<X> { }
class C<Y> extends B<Y> { }
class D<Y(==)> extends B<set<Y>> { }
class E extends B<int> { }
class F<Z> extends A { }
A LHS expression of type B<set<int>> can be used in a type test where the RHS is B<set<int>>,
C<set<int>>, or D<int>, and a LHS expression of type B<int> can be used in a type test where
the RHS is B<int>, C<int>, or E. Those are always allowed in compiled (and ghost) contexts.
For an expression a of type A, the expression a is F<int> is a ghost expression; it can be used
in ghost contexts, but not in compiled contexts.
For an expression e and type t, e is t is the condition determining whether e as t is well-
defined (but, as noted above, is not always a legal expression).
The repertoire of types allowed in is tests may be expanded in the future.
198
-x
- - x
! x
After descending through all the binary and unary operators we arrive at the primary expressions,
which are explained in subsequent sections. A number of these can be followed by 0 or more
suffixes to select a component of the value.
199
where params is a comma-delimited list of parameter declarations, each of which has the form x
or x: T. The type T of a parameter can be omitted when it can be inferred. If the identifier x is
not needed, it can be replaced by _. If params consists of a single parameter x (or _) without an
explicit type, then the parentheses can be dropped; for example, the function that returns the
successor of a given integer can be written as the following lambda expression:
x => x + 1
The specification is a list of clauses requires E or reads W, where E is a boolean expression and
W is a frame expression.
body is an expression that defines the function’s return value. The body must be well-formed for
all possible values of the parameters that satisfy the precondition (just like the bodies of named
functions and methods). In some cases, this means it is necessary to write explicit requires
and reads clauses. For example, the lambda expression
x requires x != 0 => 100 / x
would not be well-formed if the requires clause were omitted, because of the possibility of
division-by-zero.
In settings where functions cannot be partial and there are no restrictions on reading the heap,
the eta expansion of a function F: T -> U (that is, the wrapping of F inside a lambda expression
in such a way that the lambda expression is equivalent to F) would be written x => F(x). In
Dafny, eta expansion must also account for the precondition and reads set of the function, so
the eta expansion of F looks like:
x requires F.requires(x) reads F.reads(x) => F(x)
A left-hand-side expression is only used on the left hand side of an Update statement or an
Update 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]
200
new int[6]
new MyClass
new MyClass(x,y,z)
x+y+z
*
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 an explicit list of values, in which case
the dimension is optional:
var a := new int[5];
var b := new int[5][2,3,5,7,11];
var c := new int[][2,3,5,7,11];
var d := new int[3][4,5,6,7]; // error
The comprehension form requires a dimension and uses a function of type nat -> T where T is
the array element type:
var a := new int[5](i => i*i);
To allocate a multi-dimensional array, simply give the sizes of each dimension. For example,
201
var m := new real[640, 480];
allocates a 640-by-480 two-dimensional array of reals. The initialization portion cannot give a
display of elements like in the one-dimensional case, but it can use an initialization function. A
function used to initialize a n-dimensional array requires a function from n nats to a T, where T
is the element type of the array. Here is an example:
var diag := new int[30, 30]((i, j) => if i == j then 1 else 0);
Array allocation is permitted in ghost contexts. If any expression used to specify a dimension or
initialization value is ghost, then the new allocation can only be used in ghost contexts. Because
the elements of an array are non-ghost, an array allocated in a ghost context in effect cannot be
changed after initialization.
This right-hand-side expression allocates a new object of a class type as explained in section
Class Types.
202
allocated(x)
unchanged(x)
fresh(e)
assigned(x)
A literal expression is a null object reference or a boolean, integer, real, character or string literal.
The this token denotes the current object in the context of a constructor, instance method, or
instance function.
203
old(e) evaluates the 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 the given statement label.
Note that old and old@ only affect heap dereferences, like o.f and a[i]. In particular, neither
form has any effect on the value returned for local variables or out-parameters (as they are not
on the heap).9 If the value of an entire expression at a particular 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 the expression in question. If the argument of old is a local variable or out-parameter.
Dafny issues a warning.
The argument of an old expression may not contain nested old, fresh, or unchanged expressions,
nor two-state functions or two-state lemmas.
Here are some explanatory examples. All assert statements verify to be true.
class A {
9 The semantics of old in Dafny differs from similar constructs in other specification languages like ACSL or
JML.
204
class A {
var value: int
constructor ()
ensures value == 10
{
value := 10;
}
}
class B {
var a: A
constructor () { a := new A(); }
method m()
requires a.value == 11
modifies this, this.a
{
label L:
a.value := 12;
label M:
a := new A(); // Line X
label N:
a.value := 20;
label P:
205
value := 10;
}
}
class B {
var a: A
constructor () { a := new A(); }
method m()
requires a.value == 11
modifies this, this.a
{
label L:
a.value := 12;
label M:
a := new A(); // Line X
label N:
a.value := 20;
label P:
The next example demonstrates the interaction between old and array elements.
class A {
var z1: array<nat>
var z2: array<nat>
method mm()
requires z1.Length > 10 && z1[0] == 7
requires z2.Length > 10 && z2[0] == 17
modifies z2
{
206
var a: array<nat> := z1;
assert a[0] == 7;
a := z2;
assert a[0] == 17;
assert old(a[0]) == 17; // a is local with value z2
z2[0] := 27;
assert old(a[0]) == 17; // a is local, with current value of
// z2; in pre-state z2[0] == 17
assert old(a)[0] == 27; // a is local, so old(a) has no effect
}
}
fresh(e) returns a boolean value that is true if the objects denoted by expression e were all
freshly allocated since the time of entry to the enclosing method, or since label L: in the
variant fresh@L(e). The argument is an object or set of objects. For example, consider this
valid program:
class C { constructor() {} }
method f(c1: C) returns (r: C)
ensures fresh(r)
{
assert !fresh(c1);
var c2 := new C();
label AfterC2:
var c3 := new C();
assert fresh(c2) && fresh(c3);
assert fresh({c2, c3});
assert !fresh@AfterC2(c2) && fresh@AfterC2(c3);
r := c2;
}
The L in the variant fresh@L(e) must denote a label that, in the enclosing method’s control
flow, dominates the expression. In this case, fresh@L(e) returns true if the objects denoted by
e were all freshly allocated since control flow reached label L.
The argument of fresh must be either an object reference or a set or sequence of object
references. In this case, fresh(e) (respectively fresh@L(e) with a label) is a synonym of
old(!allocated(e)) (respectively old@L(!allocated(e)))
207
9.24. Allocated Expressions (grammar)
Examples:
allocated(c)
allocated({c1,c2})
For any expression e, the expression allocated(e) evaluates to true in a state if the value of e
is available in that state, meaning that it could in principle have been the value of a variable in
that state.
For example, consider this valid program:
class C { constructor() {} }
datatype D = Nil | Cons(C, D)
method f() {
var d1, d2 := Nil, Nil;
var c1 := new C();
label L1:
var c2 := new C();
label L2:
assert old(allocated(d1) && allocated(d2));
d1 := Cons(c1, Nil);
assert old(!allocated(d1) && allocated(d2));
d2 := Cons(c2, Nil);
assert old(!allocated(d1) && !allocated(d2));
assert allocated(d1) && allocated(d2);
assert old@L1(allocated(d1) && !allocated(d2));
assert old@L2(allocated(d1) && allocated(d2));
d1 := Nil;
assert old(allocated(d1) && !allocated(d2));
}
This can be useful when, for example, allocated(e) is evaluated in an old state. Like in the
example, where d1 is a local variable holding a datatype value Cons(c1, Nil) where c1 is an
object that was allocated in the enclosing method, then old(allocated(d)) is false.
If the expression e is of a reference type, then !old(allocated(e)) is the same as fresh(e).
The unchanged expression returns true if and only if every reference denoted by its arguments
has the same value for all its fields in the old and current state. For example, if c is an object
with two fields, x and y, then unchanged(c) is equivalent to
208
c.x == old(c.x) && c.y == old(c.y)
Each reference denoted by the arguments of unchanged must be non-null and must be allocated
in the old-state of the expression.
For a finite-collection expression c, |c| is the cardinality of c. For a finite set or sequence,
the cardinality is the number of elements. For a multiset, the cardinality is the sum of the
multiplicities of the elements. For a finite map, the cardinality is the cardinality of the domain
of the map. Cardinality is not defined for infinite sets or infinite maps. For more information,
see Section 5.5.
A sequence display expression provides a way to construct a sequence with given values. For
example
209
[1, 2, 3]
is a sequence of k elements whose values are obtained by evaluating the second argument (a
function, in this case a lambda expression) on the indices 0 up to k.
See this section for more information on sequences.
A set display expression provides a way of constructing a set with given elements. If the keyword
iset is present, then a potentially infinite set (with the finite set of given elements) is constructed.
For example
{1, 2, 3}
is a set with three elements in it. See Section 5.5.1 for more information on sets.
A multiset display expression provides a way of constructing a 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 sequence into a multiset as shown here:
var s : set<int> := {1, 2, 3};
var ms : multiset<int> := multiset(s);
ms := ms + multiset{1};
var sq : seq<int> := [1, 1, 2, 3];
var ms2 : multiset<int> := multiset(sq);
assert ms == ms2;
Note that multiset{1, 1} is a multiset holding the value 1 with multiplicity 2, but in
multiset({1,1}) the multiplicity is 1, because the expression {1,1} is the set {1}, which is
then converted to a multiset.
See Section 5.5.2 for more information on multisets.
210
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 infinite map from explicit mappings. For
example:
const m := map[1 := "a", 2 := "b"]
ghost const im := imap[1 := "a", 2 := "b"]
The if expression also permits a binding form. In this case the condition of the if 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 is
bound to the given variable and that variable is in scope in the then-branch of the expression.
For example, in the code
predicate P(x: int) {
x == 5 || x == -5
}
method main() {
assert P(5);
211
var y := if x: int :| P(x) then x else 0;
assert y == 5 || y == -5;
}
x is given some value that satisfies P(x), namely either 5 or -5. That value of x is the value of
the expression in the then branch above; if there is no value satisfying P(x), then 0 is returned.
Note that if x is declared to be a nat in this example, then only the value 5 would be permissible.
This binding form of the if expression acts in the same way as the binding form of the if
statement.
In the example given, the binder for x has no constraining range, so the expression is ghost;
if a range is given, such as var y := if x: int :| 0 <= x < 10 && P(x) then x else 0;,
then the if and y are no longer ghost, and y could be used, for example, in a print statement.
212
Any patterns inside the parentheses of a constructor (or tuple) pattern are then matched against
the arguments that were given to the constructor when the value was constructed. The number
of patterns must match the number of parameters to the constructor (or the arity of the tuple).
When matching a value of base type, the pattern should either 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 variable identifiers contained in all the patterns must be
distinct. They are bound to the corresponding values in the value being matched. (Thus, for
example, one cannot repeat a bound variable to attempt to match a constructor that has two
identical arguments.)
A quantifier expression is a boolean expression that specifies that a given expression (the one
following the ::) is true for all (for forall) or some (for exists) combination of values of the
quantified variables, namely those in the given quantifier domain. See Section 2.7.4 for more
details on quantifier domains.
Here are some examples:
assert forall x : nat | x <= 5 :: x * x <= 25;
(forall n :: 2 <= n ==> (exists d :: n < d < 2*n))
assert forall x: nat | 0 <= x < |s|, y <- s[x] :: y < x;
213
The quantifier identifiers are bound within the scope of the expressions in the quantifier expres-
sion.
If types are not given for the quantified identifiers, then Dafny attempts to infer their types from
the context of the expressions. It this is not possible, the program is in error.
A set comprehension expression is an expression that yields a set (possibly infinite only if iset
is used) that satisfies 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 supplied and the expression consists solely of the quantified
variable. That is,
set x : T | P(x)
is equivalent to
set x : T | P(x) :: x
the elements of S will be all values resulting from evaluation of Q(x1, x2, ...) for all combi-
nations of quantified variables x1, x2, ... (from their respective C1, C2, ... domains) such
that all predicates P1(x1), P2(x1, x2), ... hold.
For example,
var S := set x:nat, y:nat | x < y < 3 :: (x, y)
214
as are the | P expressions which default to true. See also Section 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 the result is
finite otherwise the set comprehension expression will not be accepted.
Set comprehensions involving reference types such as
set o: object
are allowed in ghost expressions within methods, but not in ghost functions10 . In particular,
in ghost contexts, the check that the result is finite should allow any set comprehension where
the bound variable is of a reference type. In non-ghost contexts, it is not allowed, because–even
though the resulting set would be finite–it is not pleasant or practical to compute at run time.
The universe in which set comprehensions are evaluated is the set of all allocated objects, of the
appropriate type and satisfying the given predicate. For example, given
class I {
var i: int
}
method test() {
ghost var m := set x: I :: 0 <= x.i <= 10;
}
the set m contains only those instances of I that have been allocated at the point in program
execution that test is evaluated. This could be no instances, one per value of x.i in the stated
range, multiple instances of I for each value of x.i, or any other combination.
Assert, assume, expect, reveal and calc statements can be used in this way.
10 In order to be deterministic, the result of a function should only depend on the arguments and of the objects
it reads, and Dafny does not provide a way to explicitly pass the entire heap as the argument to a function. See
this post for more insights.
215
9.31.7. Let and Let or Fail Expression (grammar)
Examples:
var x := f(y); x*x
var x :- f(y); x*x
var x :| P(x); x*x
var (x, y) := T(); x + y // T returns a tuple
var R(x,y) := T(); x + y // T returns a datatype value R
A let expression allows binding of intermediate values to identifiers for use in an expression.
The start of the let expression is signaled by the var keyword. They look much like a local
variable declaration except the scope of the variable only extends to the enclosed expression.
For example:
var sum := x + y; sum * sum
In the simple case, the pattern is just an identifier with optional type (which if missing is inferred
from the rhs).
The more complex case allows destructuring of constructor expressions. For example:
datatype Stuff = SCons(x: int, y: int) | Other
function GhostF(z: Stuff): int
requires z.SCons?
{
var SCons(u, v) := z; var sum := u + v; sum * sum
}
The Let expression has a failure variant that simply uses :- instead of :=. This Let-or-Fail
expression also permits propagating failure results. However, in statements (Section 8.6), failure
results in immediate return from the method; expressions do not have side effects or immediate
return mechanisms. Rather, if the expression to the right of :- results in a failure value V, the
overall expression returns V.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 that tmp.PropagateFailure()
is a failure value and E is a value-carrying success value, both of the same failure-compatible
type, as described in Section 8.6.
The expression :- V; E is desugared into the expression
var tmp := V;
if tmp.IsFailure()
then tmp.PropagateFailure()
else E
216
then tmp.PropagateFailure()
else var v := tmp.Extract(); E
If the RHS is a list of expressions then the desugaring is similar. var v, v1 :- V, V1; E
becomes
var tmp := V;
if tmp.IsFailure()
then tmp.PropagateFailure()
else var v, v1 := tmp.Extract(), V1; E
So, if tmp is a failure value, then a corresponding failure value is propagated along; otherwise,
the expression is evaluated as normal.
A map comprehension expression defines a finite or infinite map value by defining a domain and
for each value in the domain, giving the mapped value using the expression following the “::”.
See Section 2.7.4 for more details on quantifier domains.
For example:
function square(x : int) : int { x * x }
method test()
{
var m := map x : int | 0 <= x <= 10 :: x * x;
ghost var im := imap x : int :: x * x;
ghost var im2 := imap x : 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 shows creation of an infinite map that
gives the same results as a function.
If the expression includes the := token, that token separates domain values from range values.
For example, in the following code
method test()
{
var m := map x : int | 1 <= x <= 10 :: 2*x := 3*x;
}
217
9.32. Name Segment (grammar)
Examples:
I
I<int,C>
I#[k]
I#<int>[k]
A name segment names a Dafny entity by giving its declared name optionally followed by in-
formation to make the name more complete. For the simple case, it is just an identifier. Note
that a name segment may be followed by suffixes, including the common ‘.’ and further name
segments.
If the identifier is for a generic entity, it is followed by a GenericInstantiation which provides
actual types for the type parameters.
To reference a prefix predicate (see Section 5.14.3.5) or prefix lemma (see Section 5.14.3.6.3), the
identifier must be the name of the greatest predicate or greatest lemma and it must be followed
by a hash call.
218
greatest predicate atmost(a: Stream, b: Stream)
{
match a
case Nil => true
case Cons(h,s,t) => b.Cons? && h <= b.head && atmost(t, b.tail)
}
where the HashCall is "Theorem0#<T>[_k-1](s);". See Section 5.14.3.5 and Section 5.14.3.6.3.
An augmented dot suffix consists of a simple dot suffix optionally followed by either
• a GenericInstantiation (for the case where the item selected by the DotSuffix is
generic), or
• a HashCall for the case where we want to call a prefix predicate or prefix lemma. The
result is the result of calling the prefix predicate or prefix lemma.
219
a.(f := e1, g:= e2)
a.(0 := e1)
(e).(f := e1, g:= e2)
A datatype update suffix is used to produce a new datatype value that is the same as an old
datatype value except that the value corresponding to a given destructor has the specified value.
In a member binding update, the given identifier (or digit sequence) is the name of a destructor
(i.e. the formal parameter name) for one of the constructors 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 be for the same constructor, and if they
do not cover all of the destructors for that constructor then the datatype value being updated
must have a value derived from that same constructor.
Here is an example:
module NewSyntax {
datatype MyDataType = MyConstructor(myint:int, mybool:bool)
| MyOtherConstructor(otherbool:bool)
| MyNumericConstructor(42:int)
220
a[lo .. hi ]
(e)[ lo .. ]
e[ .. hi ]
e[ .. ]
A subsequence suffix applied to a sequence produces a new sequence whose elements are taken
from a contiguous part of the original sequence. For example, expression s[lo..hi] for sequence
s, and integer-based numeric bounds lo and hi satisfying 0 <= lo <= hi <= |s|. See the
section about other sequence expressions for details.
A subsequence suffix applied to an array produces a sequence consisting of the values of the
designated elements. A concise way of converting a whole array to a sequence is to write a[..].
For a sequence s and expressions i and v, the expression s[i := v] is the same as the sequence
s except that at index i it has value v.
If the type of s is seq<T>, then v must have type T. The index i can have any integer- or bit-
vector-based type (this is one situation in which Dafny implements implicit conversion, as if an
as int were appended to the index expression). The expression s[i := v] has the same type
as s.
If a selection suffix has only one expression in it, it is a zero-based index that may be used to
select a single element of a sequence or from a single-dimensional array.
If a selection suffix has more than one expression in it, then it 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.
221
If the selection suffix is used with an array or a sequence, then each index expression can have
any integer- or bit-vector-based type (this is one situation in which Dafny implements implicit
conversion, as if an as int were appended to the index expression).
An argument list suffix is a parenthesized list of expressions that are the arguments to pass to
a method or function that is being called. Applying such a suffix causes the method or function
to be called and the result is the result of the call.
Note that method calls may only appear in right-hand-side locations, whereas function calls may
appear in expressions and specifications; this distinction can be made only during name and
type resolution, not by the parser.
Method calls, object-allocation calls (new), function calls, and datatype constructors can be
called with both positional arguments and 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
222
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, if
x is a nameonly parameter, then there is no way to supply the parameters after x by position.
Thus, any parameter that follows x 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 corresponding position. Named arguments are passed to the formal
of the given name. Named arguments can be given out of order from how the corresponding
formal parameters are declared. A formal declared with the modifier nameonly is not allowed to
be passed positionally. The list of bindings for a call must provide exactly one value for every
required parameter and at most one value for each optional parameter, and must never name
non-existent formals. Any optional parameter that is not given a value takes on the default value
declared in the callee for that optional parameter.
For any variable, constant, out-parameter, or object field x, the expression assigned(x) evalu-
ates to true in a state if x is definitely assigned in that state.
See Section 12.6 for more details on definite assignment.
223
method M(x: int, y: int) {
assert x decreases to x + 1;
}
The decreases to operator is strict, that is, it means “strictly greater than”. The nonincreases
to operator is the non-strict (“greater than or equal”) version of it.
In this case, we would prefer that Dafny recognize that MAX*4 is known to be constant with a value
of 188. The kinds of expressions for which such an optimization is possible are called compile-
time constants. Note that the representation of mytype makes no difference semantically, but
can affect how compiled code is represented at run time. In addition, though, using a symbolic
constant (which may well be used elsewhere as well) improves the self-documentation of the
code.
In Dafny, the following expressions are compile-time constants11 , recursively (that is, the argu-
ments 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
11 This set of operations that are constant-folded may be enlarged in future versions of dafny.
224
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
225
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 as type T, 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 supports module 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 module declares some program entities. A second module
refines the first by declaring how to augment or replace (some of) those program entities. The
first module is called the refinement parent; the second is the refining module; the result of
combining the two (the original declarations and the augmentation directives) is the assembled
module or refinement result.
Syntactically, the refinement parent is a normal module declaration. The refining module declares
which module is its refinement parent with the refines clause:
module P { // refinement parent
}
module M refines P { // refining module
}
226
So that it is clear that refinement is taking place, refining declarations have some syntactic
indicator that they are refining some parent declaration. Typically this is the presence of a ...
token.
but otherwise contains the same provides, reveals and extends 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 the provides, reveals, and extends sections, respectively.
An unnamed export set declaration from the parent is copied into the result module with the
name of the parent module. The result module has a default export set according to the general
rules for export sets, after all of the result module’s export set declarations have been assembled.
module X refines P {
module B' refines P.B { type T = int }
module C { const k := 6}
}
module M {
227
import X
method m() {
var z: X.B'.T := X.A.i + X.C.k;
}
}
The refinement result of P and X contains nested modules A, B', and C. It is this refinement result
that is imported into M. Hence the names X.B'.T, X.A.i and X.C.k are all valid.
module B refines A {
const ToDefine: int := 2
const ToDefineWithoutType ... := 3
ghost const ToGhost: int
const NewConst: int
}
Formally, a child const declaration may refine a const declaration from 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 not ghost.
A refining module can also introduce new const declarations that do not exist in the refinement
parent.
228
ensures r >= x
{
var y :| y >= x;
return y;
}
module B refines A {
method ToImplement(x: int) returns (r: int)
{
return x + 2;
}
Formally, a child method definition may refine a parent method declaration or definition by
performing one or more of the following operations:
• 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 in ToStrengthen),
• provide a more deterministic version of a non-deterministic parent body (as in
ToDeterminize), or
The type signature of a child method must be the same as that of the parent method it refines.
This can be ensured by providing an explicit type signature equivalent to that of the parent
(with renaming of parameters allowed) or by using an ellipsis (...) to indicate copying of the
parent type signature. The body of a child method must satisfy any ensures clauses from its
parent in addition to any it adds.
A refined method is allowed only if it does not invalidate any parent lemmas that mention it.
A refining module can also introduce new method declarations or definitions that do not exist in
the refinement parent.
229
10.6. Lemma declarations
As lemmas are (ghost) methods, the description of method refinement from the previous section
also applies to lemma refinement.
A valid refinement is one that does not invalidate any proofs. A lemma from a refinement parent
must still be valid for the refinement result of any method or lemma it mentions.
module B refines A {
function F ...
{ x + 1 }
function G ...
ensures r == x + 1
}
Formally, a child function (or predicate) definition can refine a parent function (or
predicate) 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 function is the same as for
methods and lemmas, as described in the previous section.
A refining module can also introduce new function declarations or definitions that do not exist
in the refinement parent.
230
Here is an example code snippet:
abstract module P {
class C {
function F(): int
ensures F() > 0
}
}
module X refines P {
class C ... {
function F...
ensures F() > 0
{ 1 }
}
}
In this module, type T is opaque and hence can be refined with any type, including class types.
Types B, S, Pos, and Bool are concrete and cannot be refined further, except (for Pos and Bool)
by giving them additional members or attributes (or refining their existing members, if any).
Hence, the following are valid refinements:
module ChildWithTrait refines Parent {
trait T {}
}
231
type T = bool
}
module X refines P {
newtype T3 = i | 0 <= i < 10 {
function ToString... { "" }
}
}
232
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.
233
11. Attributes
Dafny allows many of its entities to be annotated with Attributes. Attributes are declared
between {: and } like this:
{:attributeName "argument", "second" + "argument", 57}
Dafny has special processing for some attributes12 . Of those, some apply only to the entity
bearing the attribute, while others (inherited attributes) apply to the entity and its descendants
(such as nested modules, types, or declarations). The attribute declaration closest to the entity
overrides those further away.
For attributes with a single boolean expression argument, the attribute with no argument is
interpreted as if it were true.
attribute (which conflicts with Boogie usage) and the {:trigger} attribute which is instead converted into a
Boogie quantifier trigger. See Section 11 of (Leino 2008b).
234
reads this, Repr
• and also insert, for every array-valued field A declared in the class:
&& (A != null ==> A in Repr)
• and for every field F of a class type T where T has a field called Repr, also insert:
(F != null ==> F in Repr && F.Repr <= Repr && this !in F.Repr)
Except, if A or F is declared with {:autocontracts false}, then the implication will not be
added.
• For every constructor, add:
modifies this
ensures Valid() && fresh(Repr - {this})
11.1.2. {:nativeType}
The {:nativeType} attribute is only recognized by a newtype declaration where the base type
is an integral type or a real type. For example:
newtype {:nativeType "byte"} ubyte = x : int | 0 <= x < 256
newtype {:nativeType "byte"} bad_ubyte = x : int | 0 <= x < 257 // Fails
235
• {:nativeType true} - Also gives default behavior, but gives an error if the base type is
not integral.
• {:nativeType false} - Inhibits using a native type. BigInteger is used.
• {:nativeType "typename"} - This form has an native integral type 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 compiler does not support a named native type X, then an error is generated.
Also, if, after scrutinizing the constraint predicate, the compiler cannot confirm that 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.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.
The CompileName is the name for the entity when translating to one of the target languages.
The ReferenceName 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 the CompileName and not affect
the ReferenceName
• {:extern s1}: Dafny will use s1 as the CompileName, and replaces the last portion of the
ReferenceName by s1. When used on an abstract type, s1 is used as a hint as to how to
declare that type when compiling.
• {:extern s1, s2} Dafny will use s2 as the CompileName. Dafny will use a combination
of s1 and s2 such as for example s1.s2 as the ReferenceName It may also be the case
that one of the arguments is simply ignored.
236
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 corresponding section 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.2. {:autoReq}
For a function declaration, if this attribute is set true at the nearest level, then its requires
clause is strengthened sufficiently so that it may call the functions that it calls.
For following example
function f(x:int) : bool
requires x > 3
{
x > 7
}
the {:autoReq} attribute causes Dafny to deduce a requires clause for g as if it had been
declared
function f(x:int) : bool
requires x > 3
{
x > 7
}
function g(y:int, b:bool) : bool
requires if b then y + 2 > 3 else 2 * y > 3
237
{
if b then f(y + 2) else f(2*y)
}
11.2.3. {:autoRevealDependencies k}
When setting --default-function-opacity to autoRevealDependencies, the {:autoRevealDependencies
k} attribute can be set on methods and functions to make sure that only function dependencies
of depth k in the call-graph or less are revealed automatically. As special cases, one can also
use {:autoRevealDependencies false} (or {:autoRevealDependencies 0}) to make sure
that no dependencies are revealed, and {:autoRevealDependencies true} to make sure that
all dependencies are revealed automatically.
For example, when the following code is run with --default-function-opacity set to
autoRevealDependencies, the function p() should verify and q() should not.
function t1() : bool { true }
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 true without proof. In that case also the body of the function or method
may be omitted.
The {:axiom} attribute only prevents Dafny from verifying that the body matches the post-
condition. Dafny still verifies the well-formedness of pre-conditions, of post-conditions, and of
the body if provided. To prevent Dafny from running all these checks, one would use {:verify
false}, which is not recommended.
The compiler will still emit code for an {:axiom}, if it is a function, a method or a function
by method with a body.
11.2.5. {:compile}
The {:compile} attribute takes a boolean argument. It may be applied to any top-level dec-
laration. If that argument is false, then that declaration will not be compiled at all. The dif-
ference with {:extern} is that {:extern} will still emit declaration code if necessary, whereas
{:compile false} will just ignore the declaration for compilation purposes.
238
11.2.6. {:concurrent}
The {:concurrent} attribute indicates that the compiled code for a function or method may
be executed concurrently. While Dafny is a sequential language and does not support any
native concepts for spawning or controlling concurrent execution, it does support restricting the
specification of declarations such that it is safe to execute them concurrently using integration
with the target language environment.
Currently, the only way to satisfy this requirement is to ensure that the specification of the
function or method includes the equivalent of reads {} and modifies {}. 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.8. {:fuel X}
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 which case it will apply to all uses of that function, or it can be overridden
within the scope of a module, function, method, iterator, calc, forall, while, assert, or assume.
The general format is:
{:fuel functionName,lowFuel,highFuel}
When applied as an annotation to the function itself, omit functionName. If highFuel is omitted,
it defaults to lowFuel + 1.
The default fuel setting for recursive functions is 1,2. Setting the fuel higher, say, to 3,4, will
give more unfoldings, which may make some proofs go through with less programmer assistance
(e.g., with fewer assert statements), but it may also increase verification time, so use it with
care. Setting the fuel to 0,0 is similar to making the definition opaque, except when used with
all literal arguments.
11.2.10. {:induction}
The {:induction} attribute controls the application of proof by induction to two contexts.
Given a list of variables on which induction might be applied, the {:induction} attribute
selects a sub-list of those variables (in the same order) to which to apply induction.
Dafny issue 34 proposes to remove the restriction that the sub-list be in the same order, and
would apply induction in the order given in the {:induction} attribute.
The two contexts are:
239
• A method, in which case the bound variables are all the in-parameters of the method.
• A quantifier expression, in which case the bound variables are the bound variables of the
quantifier expression.
The form of the {:induction} attribute is one of the following:
• {:induction} or {:induction true} – apply induction to all bound variables
• {:induction false} – suppress induction, that is, don’t apply it to any bound variable
• {:induction L} where L is a sublist of the bound variables – apply induction to the
specified bound variables
• {:induction X} where X is anything else – raise an error.
Here is an example of using it on a quantifier expression:
datatype Unary = Zero | Succ(Unary)
lemma Correspondence()
ensures forall n: nat {:induction n} :: UnaryToNat(NatToUnary(n)) == n
{
}
11.2.11. {:inductionTrigger}
Dafny automatically generates triggers for quantified induction hypotheses. The default selec-
tion can be overridden using the {:inductionTrigger} attribute, which works like the usual
{:trigger} attribute.
11.2.12. {:only}
method {:only} X() {} or function {: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
contain assertions with {:only}.
method {:only} TestVerified() {
assert true; // Unchecked
assert {:only} true by { // Checked
assert true; // Checked
}
240
assert true; // Unchecked
}
method TestUnverified() {
assert true; // Unchecked
assert {:only} true by { // Unchecked because of {:only} Test()
assert true; // Unchecked
}
assert true; // 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 {:verify false} 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} is here.
11.2.13. {:print}
This attribute declares that a method may have print effects, that is, it may use print statements
and may call other methods that have print effects. The attribute can be applied to compiled
methods, constructors, and iterators, and it gives an error if applied to functions or ghost meth-
ods. An overriding method is allowed to use a {:print} attribute only if the overridden method
does. Print effects are enforced only with --track-print-effects.
11.2.14. {:priority}
{:priority N} assigns a positive priority ‘N’ to a method or function to control the order in
which methods or functions are verified (default: N = 1).
241
• 10K resource usage using assertions that do not add assumptions:
method f(a: bool, b: bool) {
assert a: (a ==> b) <==> (!b ==> !a);
assert b: (a ==> b) <==> (!b ==> !a);
assert c: (a ==> b) <==> (!b ==> !a);
assert d: (a ==> b) <==> (!b ==> !a);
}
• 37K total resource usage and thus fails with out of resource.
method {:rlimit 30} 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 by 7K to 8K, so if you put {:rlimit 20} in
the last example, the total resource usage would be 27K.
11.2.16. {:selective_checking}
Turn all assertions into assumptions except for the ones reachable from after the assertions
marked with the attribute {:start_checking_here}. Thus, assume {:start_checking_here}
something; becomes an inverse of assume false;: the first one disables all verification before
it, 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 a false value, it means the user specifically requested no tail recursion, so none
is done.
If specified with a true value, or if no argument is specified, then tail recursive optimization will
be attempted subject to the following conditions:
• It is an error if the method is a ghost method and tail recursion was explicitly requested.
• Only direct recursion is supported, not mutually recursive methods.
• If {:tailrecursion true} 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 have a function on which automatic attempts
of tail recursion failed, but for which efficient iteration can be implemented by hand. To do this,
242
use a function by method and define the loop in the method yourself, proving that it implements
the function.
Using a function by method to implement recursion can be tricky. It usually helps to look at
the result of the function on two to three iterations, without simplification, and see what should
be the first computation. For example, consider the following tail-recursion implementation:
datatype Result<V,E> = Success(value: V) | Failure(error: E)
The rule of thumb to unroll a recursive call into a sequential one is to look at how the result would
be computed if the operations were not simplified. For example, unrolling the function on [1,
243
2, 3] yields the result Success([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 objects from the end, and why the intermediate invariants are all about proving tail ==
MakeTailRec(obj[i..]), which makes verification succeed easily because we replicate exactly
the behavior of MakeTailRec. If we were not interested in the first error but the last one, a
possible optimization would be, on the first error, to finish iterate with a ghost loop that is not
executed.
Note that the function definition can be changed by computing the tail closer to where it’s used
or switching the order of computing r and tail, but the by method body can stay the same.
11.2.18. {:test}
This attribute indicates the target function or method is meant to 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:
1. A test can optionally return a single value to indicate success or failure. If it does, this must
be a failure-compatible type just as the update-with-failure statement requires. That is,
the returned type must define a IsFailure() function method. If IsFailure() evaluates
to true on the return value, the test will be marked a failure, and this return value used
as the failure message.
2. Code in the control flow of the test can use expect statements to dynamically test if a
boolean expression is true, and cause the test to halt if not (but not the overall testing
process). The optional second argument to a failed expect statement will be used as the
test failure message.
Note that the expect keyword can also be used to form “assign or halt” statements such as var
x :- expect CalculateX();, which is a convenient way to invoke a method that 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:
1. By default, the compiler will mark each compiled method as necessary so that a desig-
nated target language testing framework will discover and run it. This is currently only
implemented for C#, using the xUnit [Fact] annotation.
2. If dafny test is used, Dafny will instead produce a main method that invokes each test
and prints the results. This runner is currently very basic, but avoids introducing any
additional target language dependencies in the compiled code.
A method marked {:test} may not have any input arguments. If there is an output 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 test harness that supplies input arguments but has no inputs of
its own and that checks any output values, perhaps with expect statements. The test harness
is then the method marked with {:test}.
244
11.2.19. {:timeLimit N}
Set the time limit for verifying a given function or method.
11.2.20. {:timeLimitMultiplier X}
This attribute may be placed on a method or function declaration and has an integer argument.
If {:timeLimitMultiplier X} was specified a {:timeLimit Y} attribute is passed on to Boogie
where Y is X times either the default verification time limit for a function or method, or times
the value specified by the Boogie -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 to opaque
or autoRevealDependencies, then this attribute can be used on functions to make them always
non-opaque.
11.2.23. {:vcs_max_cost N}
Per-method version of the command-line option /vcsMaxCost.
The assertion batch of a method will not be split unless the cost of an assertion batch ex-
ceeds this number, defaults to 2000.0. In keep-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_splits N}
Per-method version of the command-line option /vcsMaxKeepGoingSplits. If set to more than
1, activates the keep going mode where, after the first round of splitting, assertion batches that
timed out are split into N assertion batches and retried until we succeed proving them, or there
is only one single assertion that it timeouts (in which case 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_splits N}
Per-method version of the command-line option /vcsMaxSplits. Maximal number of assertion
batches generated for this method. In keep-going mode, only applies to the first round. Defaults
to 1. If {:isolate_assertions} is set, then this parameter is useless.
245
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 original assertion batch into
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 and return one or
more fresh objects. During compilation, the postconditions associated with such a method are
translated to a series of API calls to the target languages’s mocking framework. The object
returned, therefore, behaves exactly as the postconditions specify. If there is a possibility that
this behavior violates the specifications on the object’s instance methods or hardcodes the values
of its fields, the compiler will throw an error but the compilation will go through. Currently,
this compilation pass is only supported in C# and requires adding the latest version of the Moq
library to the .csproj file before generating the binary.
Not all Dafny postconditions can be successfully compiled - below is the grammar for postcondi-
tions that are supported (S is the start symbol, EXPR stands for an arbitrary Dafny expression,
and ID stands for variable/method/type identifiers):
S = FORALL
| EQUALS
| S && S
EQUALS = ID.ID (ARGLIST) == EXPR // stubs a function call
| ID.ID == EXPR // stubs field access
| EQUALS && EQUALS
FORALL = forall BOUNDVARS :: EXPR ==> EQUALS
ARGLIST = ID // this can be one of the bound variables
| EXPR // this expr may not reference any of the bound variables
| ARGLIST, ARGLIST
BOUNDVARS = ID : ID
| BOUNDVARS, BOUNDVARS
246
11.3. Attributes on reads and modifies clauses
11.3.1. {:assume_concurrent}
This attribute is used to allow non-empty reads or modifies clauses on methods with 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 state is
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 an assume {:axiom} ... statement.
See also the {:concurrent} attribute.
{: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 variants assert {:only "before"} and assert {: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 keyword
assert and ends either at the end of the proof } or the semicolon ;, depending on which
variant of assert 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 verifica-
tion 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.
247
• 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 this assert 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, in assert B ==> (assert {:only "after"} true; C), C is actually the start of
the asserted expression, so it is verified because it’s after assert {:only "after"} true.
As soon as a declaration contains one assert {: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 using a similar {:only} attribute.
11.4.2. {:focus}
assert {:focus} X; splits verification into two assertion batches. The first batch considers
all assertions that are not on the block containing the assert {:focus} X; The second batch
considers all assertions that are on the block containing the assert {:focus} X; and those that
will always 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 for
Assumption:
method doFocus1(x: bool) returns (y: int) {
y := 1; // Batch 1 Batch 2
assert y == 1; // Assertion --
if x {
if false {
assert y >= 0; // -- Assertion
assert {:focus} y <= 2; // -- Assertion
y := 2;
assert y == 2; // -- Assertion
}
} else {
assert y == 1; // Assertion --
}
assert y == 1; // Assertion Assertion
if !x {
assert y >= 1; // Assertion Assertion
} else {
assert y <= 1; // Assertion Assertion
}
}
And another one where the focused block is guarded with a while, resulting in remaining asser-
tions not being part of the first assertion batch:
248
method doFocus2(x: bool) returns (y: int) {
y := 1; // Batch 1 Batch 2
assert y == 1; // Assertion --
if x {
while false {
assert y >= 0; // -- Assertion
assert {:focus} y <= 2; // -- Assertion
y := 2;
assert y == 2; // -- Assertion
}
} else {
assert y == 1; // Assertion --
}
assert y == 1; // Assertion --
if !x {
assert y >= 1; // Assertion --
} else {
assert y <= 1; // Assertion --
}
}
11.4.3. {:split_here}
assert {:split_here} X; splits verification into two assertion 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 for Assumption:
method doSplitHere(x: bool) returns (y: int) {
y := 1; // Batch 1 Batch 2 Batch 3
assert y >= 0; // Assertion -- --
if x {
assert y <= 1; // Assertion -- --
assert {:split_here} true; // -- Assertion --
assert y <= 2; // -- Assertion --
assert {:split_here} true; // -- -- Assertion
if x {
assert y == 1; // -- -- Assertion
} else {
assert y >= 1; // -- -- Assertion
}
} else {
assert y <= 3; // Assertion -- --
}
249
assert y >= -1; // Assertion -- --
}
11.4.4. {:subsumption n}
Overrides the /subsumption command-line setting for this assertion. {:subsumption 0} checks
an assertion but does not assume it after proving it. You can achieve the same effect using
labelled assertions.
11.4.6. {:contradiction}
Silences warnings about this assertion being involved in a proof using contradictory assump-
tions when --warn-contradictory-assumptions is enabled. This allows clear identification of
intentional proofs by contradiction.
250
11.5. Attributes on variable declarations
11.5.1. {:assumption}
This attribute can only be placed on a local ghost bool variable of a method. Its declaration
cannot have a rhs, but it is allowed to participate as the lhs of exactly one assignment of the form:
b := b && expr;. Such a variable declaration translates in the Boogie output to a declaration
followed by an assume b command. See (Leino and Wüstholz 2015), Section 3, for example uses
of the {:assumption} attribute in Boogie.
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:
predicate P(i: int)
predicate Q(i: int)
lemma PHoldsForTwo()
ensures forall i :: P(i) ==> P(i + 4)
{
forall j: int
ensures P(j) ==> P(j + 4)
{
if P(j) {
assert P(j); // Trivial assertion
PHoldEvenly();
// Invoking the lemma assumes `forall i :: P(i) ==> P(i + 4)`,
// but it's not instantiated yet
251
// `forall i :: P(i) ==> P(i + 4)` with `j`
// and we get the axiom `P(j) ==> P(j + 2) && Q(j)`
assert Q(j); // hence it can prove `Q(j)`
assert P(j + 2); // and it can prove `P(j + 2)`
assert P(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 prove assert P(j + 4);: * Add assert Q(j + 2); just before assert
P(j + 4);, so that the verifier sees the trigger. * Change the trigger {:trigger Q(i)} to
{:trigger P(i)} (replace the trigger) * Change the trigger {:trigger Q(i)} to {:trigger
Q(i)} {:trigger P(i)} (add a trigger) * Remove {:trigger Q(i)} so that it will automati-
cally determine all possible triggers thanks to the option /autoTriggers:1 which is the default.
252
• {: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}
253
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
@IsolateAssertions
method Test() {
}
instead of
method {:isolate_assertions} Test() {
}
Dafny rewrites @-attributes to old-style equivalent attributes. The definition of these attributes
is similar to the following:
datatype Attribute =
| 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 | NativeIn
| 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
254
@-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
255
12. Advanced Topics
12.1. Type Parameter Completion
Generic types, like A<T,U>, consist of a type constructor, here A, and type parameters, here T and
U. Type constructors are not first-class entities in Dafny, they are always used syntactically to
construct type 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:
type List<T>
function Elements<T>(list: List<T>): set<T>
In the latter case, Dafny knows that the already defined types set and List each take one type
parameter so it fills in <T> (using some unique type parameter name) and then determines that
the function itself needs a type parameter <T> as well.
Dafny also accepts
type List<T>
function Elements<T>(list: List): set
In this case, the function already has a type parameter list. List and set are each known to
need type parameters, so Dafny takes the first n parameters from the function signature and
applies them to List and set, where n (here 1) is the number needed by those type constructors.
It never hurts to simply write in all the type parameters, but that can reduce readability. Omit-
ting 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.
256
has been declared and used. For example, the nonsensical program
method M(n: nat) returns (y: int)
{
var a, b;
for i := 0 to n {
if i % 2 == 0 {
a := a + b;
}
}
y := a;
}
uses a and b after their declarations. Still, their types are inferred to be int, because of
the presence of the assignment y := a;.
A more useful example is this:
class Cell {
var data: int
}
Here, using only the assignment u := null; to infer the type of u would not be helpful.
But Dafny looks past the initial assignment and infers the type of u to be Cell?.
• The primary example where type inference does not inspect the entire context before giving
up on inference is when there is a member lookup. For example,
datatype List<T> = Nil | Cons(T, List<T>)
method Tutone() {
assert forall pair :: pair.0 == 867 && pair.1 == 5309 ==> pair == (867, 5309); // error: mem
assert forall pair: (int, int) :: pair.0 == 867 && pair.1 == 5309 ==> pair == (867, 5309);
}
In the first quantifier, type inference fails to infer the type of pair before it tries to look up
the members .0 and .1, which results in a “type of the receiver not fully determined” error.
257
The remedy is to provide the type of pair 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 given explicitly in angle brackets.
For example, in
datatype Option<T> = None | Some(T)
method M() {
var a: Option<int> := None;
var b := None; // error: type is underspecified
var c := Option<int>.None;
var d := None;
d := Some(400);
}
the type of b cannot be inferred, because it is underspecified. However, the types of c and
d are inferred to be Option<int>.
Here is another example:
function EmptySet<T>(): set<T> {
{}
}
method M() {
var a := EmptySet(); // error: type is underspecified
var b := EmptySet();
b := b + {2, 3, 5};
var c := EmptySet<int>();
}
258
the omitted types of local variables are all inferred as array<int> and the omitted element
type of each new is inferred to be int.
• In the absence of any other information, integer-looking literals (like 5 and 7) are inferred
to have type int (and not, say, bv128 or ORDINAL).
• Many of the types inferred can be inspected in the IDE.
Dafny will infer that the entire if 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 variable x in a ghost context.
13 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 of a is determined.
259
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 a terminating computation from given arguments,
we may refer to it as a well-founded function. For example, the common factorial and Fibonacci
functions are well-founded functions.
There are also other ways to define functions. An important case regards the definition of a
boolean function as an extreme solution (that is, a least or greatest solution) to some equation.
For computer scientists with interests in logic or programming languages, these extreme predicates
are important because they describe the judgments that can be justified by a given set of inference
rules (see, e.g., (Camilleri and Melham 1992; Winskel 1993; Leroy and Grall 2009; Pierce et al.
2015; Nipkow and Klein 2014)).
To benefit from machine-assisted reasoning, it is necessary not just to understand extreme pred-
icates but also to have techniques for proving theorems about them. A foundation for this
reasoning was developed by Paulin-Mohring (Paulin-Mohring 1993) and is the basis of the con-
structive logic supported by Coq (Bertot and Castéran 2004) as well as other proof assistants
(Bove, Dybjer, and Norell 2009; Swamy et al. 2011). Essentially, the idea is to represent the
knowledge that an extreme predicate holds by the proof term by which this knowledge was de-
rived. For a predicate defined as the least solution, such proof terms are values of an inductive
datatype (that is, finite proof trees), and for the greatest solution, a coinductive datatype (that
is, possibly infinite proof trees). This means that one can use induction and coinduction when
reasoning about these proof trees. These extreme predicates are known as, respectively, least
predicates and greatest predicates. Support for extreme predicates is also available in the proof
assistants Isabelle (Paulson 1994) and HOL (Harrison 1995).
Dafny supports both well-founded functions and extreme predicates. This section describes the
difference in general terms, and then describes novel syntactic support in Dafny for defining and
proving lemmas with extreme predicates. Although Dafny’s verifier has at its core a first-order
SMT solver, Dafny’s logical encoding makes it possible to reason about fixpoints in an automated
way.
The encoding for greatest predicates in Dafny was described previously (Leino and Moskal 2014b)
and is here described in the section about datatypes.
𝑓 = ℱ(𝑓)
260
For example, the common Fibonacci function over the natural numbers can be defined by the
equation
With the understanding that the argument 𝑛 is universally quantified, we can write this equation
equivalently as
The fact that the function being defined occurs on both sides of the equation causes concern that
we might not be defining the function properly, leading to a logical inconsistency. In general,
there could be many solutions to an equation like the 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 that the general equation
has a unique solution in 𝑓 is to make sure the recursion is well-founded, which roughly means
that the recursion terminates. This is done by introducing any well-founded relation ≪ on the
domain of 𝑓 and making sure that the argument to each recursive call goes down in this ordering.
More precisely, if we formulate the general equation as
𝑓(𝑥) = ℱ′ (𝑓)
then we want to check 𝐸 ≪ 𝑥 for each call 𝑓(𝐸) in 𝑓(𝑥) = ℱ′ (𝑓). When a function definition
satisfies this decrement condition, then the function is said to be well-founded.
For example, to check the decrement condition for fib in the fib equation, we can pick ≪ to be
the arithmetic less-than relation on natural numbers and check the following, for any 𝑛:
Note that we are entitled to use the antecedent 2 ≤ 𝑛 because that is the condition under which
the else branch in the fib equation is evaluated.
A well-founded function is often thought of as “terminating” in the sense that the recursive depth
in evaluating 𝑓 on any given argument is finite. That is, there are no infinite descending chains of
recursive calls. However, the evaluation of 𝑓 on a given argument may fail to terminate, because
its width may be infinite. For example, let 𝑃 be some predicate defined on the ordinals and let
P↓ be a predicate on the ordinals defined by the following equation:
P↓ = 𝑃 (𝑜) ∧ ∀𝑝 • 𝑝 ≪ 𝑜 ⟹ P↓ (𝑝)
With ≪ as the usual ordering on ordinals, this equation satisfies the decrement condition, but
evaluating P_ ↓ (𝜔) would require evaluating P_ ↓ (𝑛) for every natural number 𝑛. However,
what we are concerned about here is to avoid mathematical inconsistencies, and that is indeed
a consequence of the decrement condition.
261
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 that for any 𝑛, fib(𝑛) is even iff 𝑛 is a multiple of 3. We
split our task into two cases. If 𝑛 < 2, then the property follows directly from the definition of
fib. Otherwise, note that exactly one of the three numbers 𝑛 − 2, 𝑛 − 1, and 𝑛 is a multiple of
3. If 𝑛 is the multiple of 3, then by invoking the induction hypothesis on 𝑛 − 2 and 𝑛 − 1, we
obtain that fib(𝑛 − 2) + fib(𝑛 − 1) is the sum of two odd numbers, which is even. If 𝑛 − 2 or 𝑛 − 1
is a multiple of 3, then by invoking the induction hypothesis on 𝑛 − 2 and 𝑛 − 1, we obtain that
fib(𝑛 − 2) + fib(𝑛 − 1) is the sum of an even number and an odd number, which is odd. In this
proof, we invoked the induction hypothesis on 𝑛 − 2 and on 𝑛 − 1. This is allowed, because both
are smaller than 𝑛, 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 of the general equation
having multiple solutions—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 (𝑌 , ≤) and any set 𝑋, we can by pointwise extension define a complete
̇ where for any 𝑓, 𝑔 ∶ 𝑋 → 𝑌 ,
lattice (𝑋 → 𝑌 , ⇒),
̇
𝑓 ⇒𝑔 ≡ ∀𝑥 • 𝑓(𝑥) ≤ 𝑔(𝑥)
In particular, if 𝑌 is the set of booleans ordered by implication (false ≤ true), then the set
of predicates over any domain 𝑋 forms a complete lattice. Tarski’s Theorem (Tarski 1955)
tells us that any monotonic function over a complete lattice has a least and a greatest fixpoint.
In particular, this means that ℱ has a least fixpoint and a greatest fixpoint, provided ℱ is
monotonic.
Speaking about the set of solutions in 𝑓 to the general equation is the same as speaking about
the set of fixpoints of functor ℱ. In particular, the least and greatest solutions to the general
equation are the same as the least and greatest fixpoints of ℱ. In casual speak, it happens that
we say “fixpoint of the general equation”, or more grotesquely, “fixpoint of 𝑓” when we really
mean “fixpoint of ℱ”.
To conclude our little excursion into lattice theory, we have that, under the proviso of ℱ being
monotonic, the set of solutions in 𝑓 to the general equation is nonempty, and among these
solutions, there is in the ⇒̇ ordering a least solution (that is, a function that returns false more
often than any other) and a greatest solution (that is, a function that returns true more often
than any other).
When discussing extreme solutions, let’s now restrict our attention to boolean functions (that
is, with 𝑌 being the type of booleans). Functor ℱ is monotonic if the calls to 𝑓 in ℱ′ (𝑓) are in
positive positions (that is, under an even number of negations). Indeed, from now on, we will
restrict our attention to such monotonic functors ℱ.
Here is a running example. Consider the following equation, where 𝑥 ranges over the integers:
262
This equation has four solutions in 𝑔. With 𝑤 ranging over the integers, they are:
𝑔(𝑥) ≡ 𝑥 ∈ {𝑤 | 0 ≤ 𝑤 ∧ 𝑤 even}
𝑔(𝑥) ≡ 𝑥 ∈ {𝑤 | 𝑤 even}
𝑔(𝑥) ≡ 𝑥 ∈ {𝑤 | (0 ≤ 𝑤 ∧ 𝑤 even) ∨ 𝑤 odd}
𝑔(𝑥) ≡ 𝑥 ∈ {𝑤 | true}
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 of inference rules.
To designate the least solution, a single line separating the antecedent (on top) from conclusion
(on bottom) is used:
𝑔(𝑥 − 2)
𝑔(0) 𝑔(𝑥)
Through repeated applications of such rules, one can show that the predicate holds for a partic-
ular value. For example, the derivation, or proof tree, to the left in the proof tree figure shows
that 𝑔(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 are accepted only if
they are finite.
When inference rules are to designate the greatest solution, a thick line is used:
𝑔(𝑥 − 2)
𝑔(0) 𝑔(𝑥)
In this case, proof trees are allowed to be infinite. For example, the left-hand example below
shows a finite proof tree that uses the inductive rules to establish 𝑔(6). On the right is a partial
depiction of an infinite proof tree that uses the coinductive rules to establish 𝑔(1).
⋮
𝑔(0) 𝑔(−5)
𝑔(2) 𝑔(−3)
𝑔(4) 𝑔(−1)
𝑔(6) 𝑔(1)
Note that derivations may not be unique. For example, in the case of the greatest solution for 𝑔,
there are two proof trees that establish 𝑔(0): one is the finite proof tree that uses the left-hand
rule of these coinductive rules once, the other is the infinite proof tree that keeps on using the
right-hand rule of these coinductive rules.
263
12.4.2. Working with Extreme Predicates
In general, one cannot evaluate whether or not an extreme predicate holds for some input,
because doing so may take an infinite number of steps. For example, following the recursive calls
in the definition the EvenNat equation to try to evaluate 𝑔(7) would never terminate. However,
there are useful ways to establish that an extreme predicate holds and there are ways to make
use of one once it has been established.
For any ℱ as in the general equation, define two infinite series of well-founded functions, ♭𝑓 𝑘 and
♯
𝑓 𝑘 where 𝑘 ranges over the natural numbers:
♭ false if 𝑘 = 0
𝑓 𝑘 (𝑥) = { }
ℱ(♭𝑓 𝑘−1 )(𝑥) if 𝑘 > 0
♯ true if 𝑘 = 0
𝑓 𝑘 (𝑥) = { }
ℱ(♯𝑓 𝑘−1 )(𝑥) if 𝑘 > 0
These functions are called the iterates of 𝑓, and we will also refer to them as the prefix predicates
of 𝑓 (or the prefix predicate of 𝑓, if we think of 𝑘 as being a parameter). Alternatively, we can
define ♭𝑓 𝑘 and ♯𝑓 𝑘 without mentioning 𝑥: let ⊥ denote the function that always returns false, let
⊤ denote the function that always returns true, and let a superscript on ℱ denote exponentiation
(for example, ℱ0 (𝑓) = 𝑓 and ℱ2 (𝑓) = ℱ(ℱ(𝑓))). Then, the least approx definition and the
greatest approx definition can be stated equivalently as ♭𝑓 𝑘 = ℱ𝑘 (⊥) and ♯𝑓 𝑘 = ℱ𝑘 (⊤).
For any solution 𝑓 to the general equation, we have, for any 𝑘 and ℓ such that 𝑘 ≤ ℓ:
♭ ♭ ♯ ♯
𝑓𝑘 ⇒̇ 𝑓ℓ ⇒̇ 𝑓 ⇒̇ 𝑓ℓ ⇒̇ 𝑓𝑘
In other words, every ♭𝑓_𝑘 is a pre-fixpoint of 𝑓 and every ♯𝑓_𝑘 is a post-fixpoint of 𝑓. Next,
define two functions, 𝑓 ↓ and 𝑓 ↑ , in terms of the prefix predicates:
𝑓 ↓ (𝑥) = ∃𝑘 • ♭𝑓 𝑘 (𝑥)
𝑓 ↑ (𝑥) = ∀𝑘 • ♯𝑓 𝑘 (𝑥)
By the prefix postfix result, we also have that 𝑓 ↓ is a pre-fixpoint of ℱ and 𝑓 ↑ is a post-fixpoint
of ℱ. The marvelous thing is that, if ℱ is continuous, then 𝑓 ↓ and 𝑓 ↑ are the least and great-
est fixpoints of ℱ. 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 𝑔 in the EvenNat equation. As it turns
out, 𝑔’s defining functor is continuous, and therefore I will write 𝑔↓ and 𝑔↑ to denote the least
and greatest solutions for 𝑔 in the EvenNat equation.
264
12.4.2.1. Example with Least Solution The main technique for establishing that 𝑔↓ (𝑥)
holds for some 𝑥, that is, proving something of the form 𝑄 ⟹ 𝑔↓ (𝑥), is to construct a proof
tree like the one for 𝑔(6) in the proof tree figure. For a proof in this direction, since we’re just
applying the defining equation, the fact that we’re using a least solution for 𝑔 never plays a role
(as long as we limit ourselves to finite derivations).
The technique for going in the other direction, proving something from an established 𝑔↓ property,
that is, showing something of the form 𝑔↓ (𝑥) ⟹ 𝑅, typically uses induction on the structure of
the proof tree. When the antecedent of our proof obligation includes a predicate term 𝑔↓ (𝑥), it
is sound to imagine that we have been given a proof tree for 𝑔↓ (𝑥). Such a proof tree would be
a data structure—to be more precise, a term in an inductive datatype. Least solutions like 𝑔↓
have been given the name least predicate.
Let’s prove 𝑔↓ (𝑥) ⟹ 0 ≤ 𝑥 ∧ 𝑥 even. We split our task into two cases, corresponding to which
of the two proof rules in the inductive rules was the last one applied to establish 𝑔↓ (𝑥). If it was
the left-hand rule, then 𝑥 = 0, which makes it easy to establish the conclusion of our proof goal.
If it was the right-hand rule, then we unfold the proof tree one level and obtain 𝑔↓ (𝑥 − 2). Since
the proof tree for 𝑔↓ (𝑥 − 2) is smaller than where we started, we invoke the induction hypothesis
and obtain 0 ≤ (𝑥 − 2) ∧ (𝑥 − 2) even, from which it is easy to establish the conclusion of our
proof goal.
Here’s how we do the proof formally using the least exists definition. We massage the general
form of our proof goal:
𝑓 ↑ (𝑥) ⟹ 𝑅
= (the least exists definition)
(∃𝑘 • ♭𝑓 𝑘 (𝑥)) ⟹ 𝑅
= distribute ⟹ over ∃ to the left
∀𝑘 • (♭𝑓 𝑘 (𝑥) ⟹ 𝑅)
The last line can be proved by induction over 𝑘. So, in our case, we prove ♭𝑔_𝑘(𝑥) ⟹ 0 ≤
𝑥 ∧ 𝑥 even for every 𝑘. If 𝑘 = 0, then ♭𝑔_𝑘(𝑥) is false, so our goal holds trivially. If 𝑘 > 0, then
♭
𝑔_𝑘(𝑥) = (𝑥 = 0 ∨ ♭𝑔_𝑘 − 1(𝑥 − 2)). Our goal holds easily for the first disjunct (𝑥 = 0). For
the other disjunct, we apply the induction hypothesis (on the smaller 𝑘 − 1 and with 𝑥 − 2) and
obtain 0 ≤ (𝑥 − 2) ∧ (𝑥 − 2) even, from which our proof goal follows.
12.4.2.2. Example with Greatest Solution We can think of a predicate 𝑔↑ (𝑥) as being
represented by a proof tree—in this case a term in a coinductive datatype, since the proof may
be infinite. Greatest solutions like 𝑔↑ have been given the name greatest predicate. The main
technique for proving something from a given proof tree, that is, to prove something of the form
𝑔↑ (𝑥) ⟹ 𝑅, is to destruct the proof. Since this is just unfolding the defining equation, the fact
that we’re using a greatest solution for 𝑔 never plays a role (as long as we limit ourselves to a
finite number of unfoldings).
To go in the other direction, to establish a predicate defined as a greatest solution, like 𝑄 ⟹
𝑔↑ (𝑥), 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, coinduction is as simple as continuations in
265
programming: the next part of the proof obligation is delegated to the coinduction hypothesis.
The little detail is making sure that it is the “next” part we’re passing on for the continuation,
not the same part. This detail is called productivity and corresponds to the requirement in
induction of making sure we’re going down a well-founded relation when applying the induction
hypothesis. There are many sources with more information, see for example the classic account
by Jacobs and Rutten (Jacobs and Rutten 2011) or a new attempt by Kozen and Silva that aims
to emphasize the simplicity, not the mystery, of coinduction (Kozen and Silva 2012).
Let’s prove true ⟹ 𝑔↑ (𝑥). The intuitive coinductive proof goes like this: According to the
right-hand rule of these coinductive rules, 𝑔↑ (𝑥) follows if we establish 𝑔↑ (𝑥 − 2), and that’s easy
to do by invoking the coinduction hypothesis. The “little detail”, productivity, is satisfied in
this proof because we applied a rule in these coinductive rules before invoking the coinduction
hypothesis.
For anyone who may have felt that the intuitive proof felt too easy, here is a formal proof using
the greatest forall definition, which relies only on induction. We massage the general form of our
proof goal:
𝑄 ⟹ 𝑓 ↑ (𝑥)
= (the greatest forall definition)
𝑄 ⟹ ∀𝑘 • ♯𝑓 𝑘 (𝑥)
= distribute ⟹ over ∀ to the right
∀𝑘 • 𝑄 ⟹ ♯𝑓 𝑘 (𝑥)
The last line can be proved by induction over 𝑘. So, in our case, we prove true ⟹ ♯𝑔𝑘 (𝑥) for
every 𝑘. If 𝑘 = 0, then ♯𝑔_𝑘(𝑥) is true, so our goal holds trivially. If 𝑘 > 0, then ♯𝑔_𝑘(𝑥) = (𝑥 =
0 ∨ ♯𝑔_𝑘 − 1(𝑥 − 2)). We establish the second disjunct by applying the induction hypothesis (on
the smaller 𝑘 − 1 and with 𝑥 − 2).
266
12.5.1. Well-founded Functions in Dafny
Declarations of well-founded functions are unsurprising. For example, the Fibonacci function is
declared as follows:
function fib(n: nat): nat
{
if n < 2 then n else fib(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. Dafny predefines a well-
founded relation on each type and extends it to lexicographic tuples of any (fixed) length. For
example, the well-founded relation 𝑥 ≪ 𝑦 for integers is 𝑥 < 𝑦 ∧ 0 ≤ 𝑦, the one for reals is
𝑥 ≤ 𝑦 − 1.0 ∧ 0.0 ≤ 𝑦 (this is the same ordering as for integers, if you read the integer relation
as 𝑥 ≤ 𝑦 − 1 ∧ 0 ≤ 𝑦), the one for inductive datatypes is structural inclusion, and the one for
coinductive datatypes is false.
Using a decreases clause, the programmer can specify the term in this predefined order. When
a function definition omits a decreases clause, Dafny makes a simple guess. This guess (which
can be inspected by hovering over the function name in the Dafny IDE) is very often correct, so
users are rarely bothered to provide explicit decreases clauses.
If a function returns bool, one can drop the result type : bool and change the keyword function
to predicate.
The postcondition of this lemma (keyword ensures) gives the proof goal. As in any program-
correctness logic (e.g., (Hoare 1969)), the postcondition must be established on every control
path through the lemma’s body. For FibProperty, I give the proof by an if statement, hence
introducing a case split. The then branch is empty, because Dafny can prove the postcondition
267
automatically in this case. The else branch performs two recursive calls to the lemma. These
are the invocations of the induction hypothesis and they follow the usual program-correctness
rules, namely: the precondition must hold at the call site, the call must terminate, and then the
caller gets to assume the postcondition upon return. The “proof glue” needed to complete the
proof is done automatically by Dafny.
Dafny features an aggregate statement using which it is possible to make (possibly infinitely)
many calls at once. For example, the induction hypothesis can be called at once on all values
n' smaller than n:
forall n' | 0 <= n' < n {
FibProperty(n');
}
For our purposes, this corresponds to strong induction. More generally, the forall statement
has the form
forall k | P(k)
ensures Q(k)
{ Statements; }
Logically, this statement corresponds to universal introduction: the body proves that Q(k) holds
for an arbitrary k such that P(k), and the conclusion of the forall statement is then ∀𝑘 •
𝑃 (𝑘) ⟹ 𝑄(𝑘). When the body of the forall statement is a single call (or calc statement),
the ensures clause is inferred and can be omitted, like in our FibProperty example.
Lemma FibProperty is simple enough that its whole body can be replaced by the one forall
statement above. In fact, Dafny goes one step further: it automatically inserts such a forall
statement at the beginning of every lemma (Leino 2012). Thus, FibProperty can be declared
and proved simply by:
lemma FibProperty(n: nat)
ensures fib(n) % 2 == 0 <==> n % 3 == 0
{ }
function fib(n: nat): nat
{
if n < 2 then n else fib(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 variable x and boolean expression
Q, the assign such that statement x :| Q; says to assign to x a value such that Q will hold. A
proof obligation when using this statement is to show that there exists an x such that Q holds.
For example, if the fact
𝑒𝑥𝑖𝑠𝑡𝑠𝑘 • 100 ≤ fib(𝑘) < 200 is known, then the statement k :| 100 <= fib(k) < 200; will
assign to k some value (chosen arbitrarily) for which fib(k) falls in the given range.
268
12.5.3. Extreme Predicates in Dafny
The previous subsection explained that a predicate declaration introduces a well-founded pred-
icate. The declarations for introducing extreme predicates are least predicate and greatest
predicate. Here is the definition of the least and greatest solutions of 𝑔 from above; let’s call
them g and G:
least predicate g[nat](x: int) { x == 0 || g(x-2) }
greatest predicate G[nat](x: int) { x == 0 || G(x-2) }
When Dafny receives either of these definitions, it automatically declares the corresponding prefix
predicates. Instead of the names ♭𝑔𝑘 and ♯𝑔𝑘 that I used above, Dafny names the prefix predicates
g#[k] and G#[k], respectively, that is, the name of the extreme predicate appended with #, and
the subscript is given as an argument in square brackets. The definition of the prefix predicate
derives from the body of the extreme predicate and follows the form in the least approx definition
and the greatest approx definition. Using a faux-syntax for illustrative purposes, here are the
prefix predicates that Dafny defines automatically from the extreme predicates g and G:
predicate g#[_k: nat](x: int) { _k != 0 && (x == 0 || g#[_k-1](x-2)) }
predicate G#[_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 their prefix predi-
cates, the least exists definition and the greatest forall definition.
Remember that to be well defined, the defining functor of an extreme predicate must be mono-
tonic, and for the least exists definition and the greatest forall definition to hold, the functor
must be continuous. Dafny enforces the former of these by checking that recursive calls of ex-
treme predicates are in positive positions. The continuity requirement comes down to checking
that they are also in continuous positions: that recursive calls to least predicates are not inside
unbounded universal quantifiers and that recursive calls to greatest predicates are not inside
unbounded existential quantifiers (Milner 1982; Leino and Moskal 2014b).
269
{
if x == 0 { } else { EvenNatAux(k-1, x-2); }
}
Lemma EvenNat states the property we wish to prove. From its precondition (keyword requires)
and the least exists definition, we know there is some k that will make the condition in the assign-
such-that statement true. Such a value is then assigned to k and passed to the auxiliary lemma,
which promises to establish the proof goal. Given the condition g#[k](x), the definition of g#
lets us conclude k != 0 as well as the disjunction x == 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 assume g#[k-1](x-2) and calls the induction hypothesis with those parameters.
The proof glue that shows the proof goal for x to follow from the proof goal with x-2 is done
automatically.
Because Dafny automatically inserts the statement
forall k', x' | 0 <= k' < k && g#[k'](x') {
EvenNatAux(k', x');
}
at the beginning of the body of EvenNatAux, the body can be left empty and Dafny completes
the proof automatically.
Here is the Dafny program that gives the proof from the example of the greatest solution:
least predicate g[nat](x: int) { x == 0 || g(x-2) }
greatest predicate G[nat](x: int) { x == 0 || G(x-2) }
lemma Always(x: int)
ensures G(x)
{ forall k: nat { AlwaysAux(k, x); } }
lemma AlwaysAux(k: nat, x: int)
ensures G#[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 in the example of the least solution and the example of the greatest solution. These
shortcomings are addressed in the next subsection.
270
Somewhat analogous to the creation of prefix predicates from extreme predicates, Dafny auto-
matically creates a prefix lemma L# 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 replace-
ments: * for a least lemma, Dafny looks in the precondition to find calls (in positive, continuous
positions) to least predicates P(x) and replaces these with P#[_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-in greatest predi-
cate) and replaces these with P#[_k](x). In each case, these predicates P are the lemma’s focal
predicates.
The body of the extreme lemma is moved to the prefix lemma, but with replacing each recursive
call L(x) with L#[_k-1](x) and replacing each occurrence of a call to a focal predicate P(x)
with P#[_k-1](x). The bodies of the extreme lemmas are then replaced as shown in the previous
subsection. By construction, this new body correctly leads to the extreme lemma’s postcondition.
Let us see what effect these rewrites have on how one can write proofs. Here are the proofs of
our running example:
least predicate g(x: int) { x == 0 || g(x-2) }
greatest predicate G(x: int) { x == 0 || G(x-2) }
least lemma EvenNat(x: int)
requires g(x)
ensures 0 <= x && x % 2 == 0
{ if x == 0 { } else { EvenNat(x-2); } }
greatest lemma Always(x: int)
ensures G(x)
{ Always(x-2); }
Both of these proofs follow the intuitive proofs given in the example of the least solution and the
example of the greatest solution. Note that in these simple examples, the user is never bothered
with either prefix predicates nor prefix lemmas—the proofs just look like “what you’d expect”.
Since Dafny automatically inserts calls to the induction hypothesis at the beginning of each
lemma, the bodies of the given extreme lemmas EvenNat and Always can be empty and Dafny
still completes the proofs. Folks, it doesn’t get any simpler than that!
271
• nonempty type: the type is nonempty, but the compiler does not know how perform auto-
matic 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 not
explicitly provide an initializing assignment.
In a ghost context, one can an imagine a “ghost” that initializes variables. Unlike the compiler,
such a “ghost” does not need to emit code that constructs an initializing value; it suffices for the
ghost to know that a value exists. Therefore, in a ghost context, a variable of a nonempty type
can be used immediately after 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 to reason about the control flow between
assignments and uses of variables, and it reports an error if 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), then the 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 nothing further is required by the program. - Otherwise,
the array allocation must provide an initialization display or an initialization function. See
section 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,
at the explicit or implicit new; statement in the constructor). If a class has a compiled field that
is not of an auto-init type, or if it has a ghost field of a possibly empty type, then the class is
required to declare a(t least one) constructor.
The yield-parameters of an iterator turn into fields of the corresponding iterator class, but
there is no syntactic place to give these initial values. Therefore, every compiled yield-parameter
must be of auto-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 subject to 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
variable x by using an assignment statement x := *;, provided the type of x is an auto-init type
(or, if x is ghost, a nonempty type). (If x is of a possibly nonempty type, then x := *; is still
272
allowed, but it sets x to a value of its type only if the type actually contains a value. Therefore,
when x is of a possibly empty type, x := *; does not count as a definite assignment to x.)
Note that auto-initialization is nondeterministic. Dafny only guarantees that each value it assigns
to a variable of an auto-init type is some value of the type. Indeed, a variable may be auto-
initialized to different values in different runs of the program or even at different times during
the same run of the program. In other words, Dafny does not guarantee the “zero-equivalent
value” initialization that some languages do. Along these lines, also note that the witness
value provided in some subset-type declarations is not necessarily the value chosen by auto-
initialization, though it does esstablish that the 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
program statement that may have nondeterministic behavior and it disables auto-initialization.
This mode enforces definite assignments everywhere, going beyond what the strict mode does
by enforcing definite assignment also for fields and array elements. It also forbids the use of
iterator declarations and constructor-less class 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, this mode is enabled by /definiteAssignment:3.)
This document, which is intended for developers of the Dafny 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
default in the legacy CLI.
Also, there are a few relations between the rows in the table above. For example, a datatype
273
value x sitting inside a set that sits inside another datatype value X is considered to be strictly
below X. Here’s an illustration of that order, in a program that verifies:
datatype D = D(s: set<D>)
method TestD(dd: D) {
var d := dd;
while d != D({})
decreases d
{
var x :| x in d.s;
d := x;
}
}
274
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 assignment var x := 3 is evaluated, a ground term x == 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 terms f(3) and forall x {f(x)} :: f(x) + f(f(x)), where
{f(x)} indicates the trigger for the quantifier, Dafny may instantiate the quantifier using 3 for
x. This creates a new ground term f(3) + f(f(3)), of which the right hand side again matches
the trigger, allowing Dafny to instantiate the quantifier again using f(3) for x, 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 has requires !exists x :: f(x)
== 2, Dafny will use a logical rewrite rule to change this existential into a universal quantifier,
so it becomes requires forall x :: 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, compre-
hensions 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 expression forall x :: P(x) && (Q(x) =� P(x+1)), which Dafny will
split into
forall x {P(x)} {Q(x)} :: P(x) &&
forall x {(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 for forall x :: P(x).
275
13. Dafny User’s Guide
Most of this document describes the Dafny programming language. This section describes the
dafny tool, a combined verifier and compiler that implements the Dafny language.
The development of the Dafny language and tool is a GitHub project at https://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 the MIT license.
13.1. Introduction
The dafny tool implements the following primary capabilities, implemented as various commands
within the dafny 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 verifica-
tion conditions and 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.
276
13.2.2. IDEs for Dafny
Dafny source files are text files and can of course be edited with any text editor. However, some
tools provide syntax-aware features:
• VSCode, a cross-platform editor for many programming languages has an extension for
Dafny. VSCode is available here and the Dafny extension can be installed from within
VSCode. The extension provides syntax highlighting, in-line parser, type and verification
errors, code navigation, counter-example display and gutter highlights.
• There is a Dafny mode for Emacs.
• An old Visual Studio plugin is no longer supported
Information about installing IDE extensions for Dafny is found on the Dafny INSTALL page in
the wiki.
More information about using VSCode IDE is here.
13.3.1. Dafny Verification Artifacts: the Library Backend and .doo Files
As of Dafny 4.1, dafny now supports outputting a single file containing a 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 name lib on the command line. For example, to build multiple Dafny files into a single
build artifact for shared reuse, the command would look something like:
14 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. 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.
277
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 the dafny 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 them and the values
of relevant options that affect the sound separate verification and compilation of Dafny code,
such as --unicode-char. This means attempting to use a library that was built with options
that are not compatible with the currently executing command options will 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 the newer command-style CLI interface.
278
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.
279
The command-line may contain other kinds of files appropriate to the 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 the dafny run 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 and false (or any case-insensitive version of those words). For example, the
value of --no-verify is by default false (that is, do verification). It can be explicitly set to true
(no verification) using --no-verify, --no-verify:true, --no-verify=true, --noverify true;
it can be explicitly set false (do verification) using --no-verify:false or --no-verify=false
or --no-verify false. - There is a potential ambiguity when the form --option value is used
if the value is optional (such as for boolean values). In such a case an argument afer 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 or dafny -h lists all the available commands - dafny -? or dafny -help
list all legacy options - dafny --version (or -version) prints out the number of the version
this build of dafny implements
13.6.1.2. dafny resolve The dafny resolve command checks the command-line and then
parses and typechecks the given files and any included files.
The set of files considered by dafny are those listed on the command-line, including those named
in a --library option, and all files that are named, recursively, in include directives in files in
the set being considered by the tool.
The set of files presented to an invocation of the dafny 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.15
However, dafny will need all implementations in order to compile a working executable.
The options relevant to this command are - those relevant to the command-line itself -
--allow-warnings — return a success exit code, even when there are warnings
• those that affect dafny‘ as a whole, such as
15 Unlike some languages, Dafny does not allow separation of declaration and implementation of methods, func-
tions 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
of traits and classes can achieve a separation of interface and specification from implementation.
280
– --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. dafny verify The dafny verify command performs the dafny resolve checks
and then attempts to verify each declaration in the program.
A guide to controlling and aiding the verification process is given in a later section.
To be considered verified all the methods in all the files in a program must be verified, with
consistent sets of options, and with no unproven assumptions (see dafny audit for a tool to
help identify such assumptions).
Dafny works modularly, meaning that each method is considered by itself, using only the specifi-
cations 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 does save 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 an include 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 that should 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 or programs together in a
single run of dafny.
Various options control the verification process, in addition to all those described for dafny
resolve.
281
• What is verified
– --verify-included-files (when enabled, all included files are verified, except li-
brary 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. dafny translate <language> The dafny translate command translates Dafny
source code to source code for another target programming language. The command always
performs the actions of dafny resolve and, unless the --no-verify option is specified, does
the actions of dafny verify. 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 in the section on compilation.
The dafny 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 for dafny
resolve and dafny verify.
• 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 assump-
tions when they are compilable
282
13.6.1.5. dafny build The dafny build command runs dafny translate 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
with dafny translate, 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 the Main entry point is described [here](#sec-user-guide-main}.
There are no additional options for dafny build beyond those for dafny translate and the
previous compiler phases.
Note that dafny build may do optimizations that dafny run does not.
Details for specific target platforms are described in Section 25.7.
13.6.1.6. dafny run The dafny run command compiles the Dafny program and then runs
the resulting executable. Note that dafny run is engineered to quickly compile and launch the
program; dafny build may take more time to do optimizations of the build artifacts.
The form of the dafny run command-line is slightly different than for other commands. - It
permits just one .dfy file, which must be the file containing the Main entry point; the location
of the Main 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 file is 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 use dafny run --allow-warnings if they want to run their
Dafny code when it contains warnings.
Here are some examples: - dafny run A.dfy – builds and runs the Main program in A.dfy
with no command-line arguments - dafny run A.dfy --no-verify – builds the Main program
in A.dfy using the --no-verify option, and then runs the program with no command-line
arguments - dafny run A.dfy -- --no-verify – builds the Main program in A.dfy (not using
the --no-verify option), and then runs the program with one command-line argument, namely
--no-verify - dafny run A.dfy 1 2 3 B.dfy – builds the Main program in A.dfy and then
runs it with the four command-line arguments 1 2 3 B.dfy - dafny run A.dfy 1 2 3 --input
B.dfy – builds the Main program in A.dfy and B.dfy, and then runs it with the three command-
line arguments 1 2 3 - dafny run A.dfy 1 2 -- 3 -quiet – builds the Main program in A.dfy
and then runs it with the four command-line arguments 1 2 3 -quiet
Each time dafny run 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 use dafny build, which
enables compiling a Dafny program once and then running it multiple times.
Note: dafny run will typically produce the same results as the executables produced by dafny
build. The only expected differences are these: - performance — dafny run may not optimize
283
as much as dafny build - target-language-specific configuration issues — e.g. encoding issues:
dafny run sets language-specific flags to request UTF-8 output for the print statement in all
languages, whereas dafny build leaves language-specific runtime configuration to the user.
13.6.1.7. dafny server The dafny server command starts the Dafny Language Server,
which is an LSP-compliant implementation of Dafny. The Dafny 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 detail here.
13.6.1.8. dafny audit The dafny audit command reports issues in the Dafny code that
might limit the soundness claims of verification.
This command is under development.
The command executes the dafny resolve phase (accepting its options) and has the following
additional options:
• --report-file:<report-file> — spcifies 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. Sup-
ported 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.
The dafny audit 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 the audit 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 {:verify false} attribute, which tells the verifier to
skip verifying this declaration. Removing the attribute and providing a proof will improve
assurance.
284
• Any declaration marked with the {:extern} attribute that has at least one requires or
ensures clause. If code implemented externally, and called from Dafny, has an ensures
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 any ensures
clauses do hold can improve assurance. The same considerations apply to requires 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 an assert 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 an expect 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 all ensures clauses are true after it returns. To improve
assurance, provide a body that proves that any ensures clauses hold.
13.6.1.9. dafny format Dafny supports a formatter, which for now only changes the inden-
tation of lines in a Dafny file, so that it conforms to 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:
• dafny format <one or more .dfy files or folders> formats the given Dafny files
and the Dafny files in the folders, recursively, altering the files in place. For example,
dafny format . formats all the Dafny files recursively in the current folder.
• dafny format --print <files and/or folders> formats each file but instead of alter-
ing the files, output the formatted content to stdout
• dafny format --check <files and/or folders> 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 of dafny format returns a non-zero return code if there are any command-line or
parsing errors or if –check is stipulated and at least one file is not the same as its formatted
version.
285
dafny format does not necessarily report name or type resolution errors and does not attempt
verification.
13.6.1.10. dafny test 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. dafny test also accepts all other options of the dafny
build command. In particular, it accepts the --target option that specifies the programming
language used in the build and execution phases.
dafny test also accepts these options:
• -spill-translation - (default disabled) when enabled the compilation artifacts are re-
tained
• --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
fully qualified 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 of the program that execution of the tests covered.
The order in which the tests are run is not specified.
For example, this code (as the file t.dfy)
method {:test} m() {
mm();
print "Hi!\n";
}
method mm() {
print "mm\n";
}
module M {
method {:test} q() {
print 42, "\n";
}
}
class A {
static method {:test} t() { print "T\n"; }
}
286
M.q: 42
PASSED
A.t: T
PASSED
m: mm
Hi!
PASSED
13.6.1.11. dafny doc [Experimental] The dafny doc command generates HTML documen-
tation pages describing the contents of each module 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 described here.
• The dafny doc command accepts either files or folders as command-line arguments. A
folder represents 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 documen-
tation if type resolution is successful (on all files). All the command-line options relevant
to dafny resolve are available for dafny doc.
• 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 is
declared. 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 the last
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 an
index.html file giving an overall table of contents and a nameindex.html file containing an
alphabetical by name list of all the declarations in all the modules. The documentation for the
287
root module is in _.html.
13.6.1.12. dafny generate-tests This experimental command allows generating tests from
Dafny programs. The tests provide complete coverage of the implementation and one can exe-
cute them using the dafny test command. Dafny can target different notions of coverage while
generating tests, with basic-block coverage being the recommended setting. Basic blocks are ex-
tracted from the Boogie representation of the Dafny program, with one basic block corresponding
to a statement or a non-short-circuiting subexpression in the Dafny code. The underlying im-
plementation 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 file program.dfy)
module M {
function {:testEntry} Min(a: int, b: int): int {
if a < b then a else b
}
}
The two tests together cover every basic block within the Min function in the input program.
Note that the Min function is annotated with the {:testEntry} attribute. This attribute marks
Min 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 called M 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 produce unit tests,
which guarantee coverage of basic blocks within the method they call but not within any of its
callees. By contrast, system-level tests can guarantee coverage of a large part of the program
288
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 file program.dfy)
module M {
function {:testInline} Min(a: int, b: int): int {
if a < b then a else b
}
method {:testEntry} Max(a: int, b: int) returns (c: int)
// the tests convert the postcondition below into runtime check:
ensures c == if a > b then a else b
{
return -Min(-a, -b);
}
}
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 the Max method itself – all the branching
occurs withing the Min function. Note also that Dafny automatically converts all non-ghost
postconditions on the method under tests into expect 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 function Mod3 is annotated with {:testInline 2} and will, therefore, be unrolled twice
during test generation. The function naively implements division by repeatedly and recursively
subtracting 3 from its argument, and it returns the remainder of the division, which is one
of the three base cases. Because the TestEntry method calls Mod3 with an argument that is
289
guaranteed to be at least 3, the base case will never occur on first iteration, and the function
must be unrolled at least twice for Dafny to generate tests covering any of the base cases:
module M {
function {:testInline 2} Mod3 (n: nat): nat
decreases n
{
if n == 0 then 0 else
if n == 1 then 1 else
if n == 2 then 2 else
Mod3(n-3)
}
method {:testEntry} TestEntry(n: nat) returns (r: nat)
requires n >= 3
{
r := Mod3(n);
}
}
13.6.1.14. Command Line Options Test generation supports a number of command-line op-
tions that control its behavior.
The first argument to appear after the generate-test command specifies the coverage criteria
Dafny will attempt to satisfy. Of these, we recommend basic-block coverage (specified with
keyword Block), which is also the coverage criteria used throughout 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 lenghts 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 is disabled by default
• --coverage-report - the value is a directory in which Dafny will save an html coverage
report highlighting parts of the 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.
290
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. dafny find-dead-code This experimental command finds dead code in a pro-
gram, that is, basic-blocks within a method that are not reachable by any inputs that sat-
isfy the method’s preconditions. The underlying implementation is identical to that of dafny
generate-tests command and can be controlled by the same command line options and method
attributes.
For example, this code (as the file program.dfy)
module M {
function {:testEntry} DescribeProduct(a: int): string {
if a * a < 0
then "Product is negative"
else "Product is nonnegative"
}
}
Dafny reports that the then branch of the condition is potentially unreachable because the verifier
proves that no input can reach it. In this case, this is to be expected, since the product of two
numbers can never be negative. In practice, find-dead-code command can produce both false
positives (if the reachability query times out) and false negatives (if the verifier cannot prove
true unreachability), so the results of such a report should always be reviewed.
This command is under development and not yet fully 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 is dafny --plugin:<path-to-one-assembly[,argument]*> or
dafny <command> --plugin:<path-to-one-assembly[,argument]*> where the argument to
291
--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 found in this section.
13.6.1.18. Legacy operation Prior to implementing the command-based CLI, the dafny
command-line simply took files and options and the arguments to options. That legacy mode
of operation is still supported, though discouraged. The command dafny -? produces the
list of legacy options. In particular, the common commands like dafny verify and dafny
build 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.
292
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 a TOML file named dfyconfig.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 = true
warn-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 and excludes 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 the includes, omitting those specified by
the excludes. The excludes 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 dafny server command, the IDE will search for
project files by traversing up the file tree looking for the closest dfyconfig.toml file to
the dfy being parsed that it can find. Options from the project file will override options
passed to dafny server.
• 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.
293
13.7. Verification
In this section, we suggest a methodology to figure out why a single assertion might not hold,
we propose techniques to deal with assertions that slow a proof down, we explain how to verify
assertions in parallel or in a focused way, and we also give some more examples of useful options
and attributes to control verification.
294
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
var j := if !b then 3 else 1;
if b {
return j;
}//^^^^^^^ a postcondition might not hold on this return path.
i := 2;
}
One first thing you can do is replace the statement return j; by two statements i := j;
return; to better understand what is wrong:
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
var j := if !b then 3 else 1;
if b {
i := j;
return;
}//^^^^^^^ a postcondition might not hold on this return path.
i := 2;
}
Now, you can assert the postcondition just before the return:
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
var j := if !b then 3 else 1;
if b {
i := j;
assert 2 <= i; // This assertion might not hold
return;
}
i := 2;
}
That’s it! Now the postcondition is not failing anymore, but the assert contains the error! you
can now move to the next section to find out how to debug this assert.
13.7.1.2. Failing asserts In the previous section, we arrived at the point where we have a
failing assertion:
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
295
var j := if !b then 3 else 1;
if b {
i := j;
assert 2 <= i; // This assertion might not hold
return;
}
i := 2;
}
To debug why this assert might not hold, we need to move this assert up, which is similar
to computing the weakest precondition. For example, if we have x := Y; assert F; and the
assert F; might not hold, the weakest precondition for it to hold before x := Y; can be written
as the assertion assert F[x:= Y];, where we replace every occurence of x in F into Y. Let’s do
it in our example:
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
var j := if !b then 3 else 1;
if b {
assert 2 <= j; // This assertion might not hold
i := j;
assert 2 <= i;
return;
}
i := 2;
}
Yay! The assertion assert 2 <= i; is not proven wrong, which means that if we manage to
prove assert 2 <= j;, it will work. Now, this assert should hold only if we are in this branch,
so to move the assert up, we need to guard it. Just before the if, we can add the weakest
precondition assert b ==> (2 <= j):
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
var j := if !b then 3 else 1;
assert b ==> 2 <= j; // This assertion might not hold
if b {
assert 2 <= j;
i := j;
assert 2 <= i;
return;
}
i := 2;
}
296
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 replace j by its value and create the
assert assert b ==> ((if !b then 3 else 1) >= 2);
method FailingPostcondition(b: bool) returns (i: int)
ensures 2 <= i
{
assert b ==> 2 <= (if !b then 3 else 1); // This assertion might not hold
var j := if !b then 3 else 1;
assert b ==> 2 <= j;
if b {
assert 2 <= j;
i := j;
assert 2 <= i;
return;
}
i := 2;
}
Now we can understand what went wrong: When b is true, all of these formulas above are false,
this is why the dafny 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.
297
Failing assert Suggested rewriting
assert exists x | P(x) :: if x :| P(x) { assert Q(x); assert Q'(x);}
Q(x);assert exists x | P(x) :: else { assert false;}
Q'(x);
assert exists x :: P(x); assert P(x0);assert exists x :: P(x);for a given
expression x0.
ensures exists i :: P(i); returns (j: int)ensures P(j) ensures exists i
:: P(i)in a lemma, so that the j can be computed
explicitly.
assert A == calc == { A; B; { callLemma(x); } C;};assert
B;callLemma(x);assert B == C; A == B;where the calc statement can be used to make
intermediate computation steps explicit. Works with <,
>, <=, >=, ==>, <== and <==> for example.
assert A ==> B; if A { assert B;};assert A ==> B;
assert A && B; assert A;assert B;assert A && B;
ensures P ==> Q on a lemma requires P ensures Q to avoid accidentally calling
the lemma on inputs that do not satisfy P
seq(size, i => P) seq(size, i requires 0 <= i < size => P);
assert forall x :: G(i) ==> assert G(i0);assert R(i0);assert forall i ::
R(i); G(i) ==> R(i); with a guess of the i0 that makes the
second assert to fail.
assert forall i | 0 < i <= m assert forall i | 0 < i < m :: P(i);assert
:: P(i); forall i | i == m :: P(i);assert forall i | 0
< i <= m :: P(i);
assert forall i | i == m :: assert P(m);assert forall i | i == m :: P(i);
P(m);
method m(i) returns (j: method m(i) returns (j: T) requires
T) requires A(i) ensures B(i, A(i) ensures B(i, j){ ...}method n()
j){ ...}method n() { ... var x { ... assert A(k); assert forall x :: B(k, x)
:= m(a); assert P(x); ==> P(x); var x := m(k); assert P(x);
method m_mod(i) returns (j: method m_mod(i) returns (j: T) requires
T) requires A(i) modifies A(i) modifies this, i ensures B(i,
this, i ensures B(i, j){ ...}method n_mod() { ... assert
j){ ...}method n_mod() A(k); modify this, i; // Temporarily var x:
{ ... var x := T; // Temporarily assume B(k, x); // var
m_mod(a); assert P(x); x := m_mod(k); assert P(x);
modify x, y;assert P(x, y, z); assert x != z && y != z;modify x, y;assert
P(x, y, z);
298
provably violates the assertion it was generated for (see 16 ) 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.1. assume false; Assuming false is an empirical way to short-circuit the verifier
and usually stop verification at a given point,17 and since the final compilation steps do not
accept this command, it is safe to use it during development. Another similar command, assert
false;, would also short-circuit the verifier, but it would still make the verifier try to prove
false, which can also lead to timeouts.
Thus, let us say a program of this shape takes forever to verify.
method NotTerminating(b: bool) {
assert X;
if b {
assert Y;
} else {
assert Z;
assert P;
}
}
What we can first do is add an assume false at the beginning of the method:
method NotTerminating() {
assume false; // Will never compile, but everything verifies instantly
assert X;
if b {
assert Y;
} else {
assert Z;
16 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 what dafny knows.
17 assume false tells the dafny 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 to true || A, which reduces to true.
299
assert P;
}
assert W;
}
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 the assume false; below the next assertion:
method NotTerminating() {
assert X;
assume false;
if b {
assert Y;
} else {
assert Z;
assert P;
}
assert W;
}
If verification is slow again, we can use techniques seen before to decompose the assertion and
find which component is slow to prove.
If verification is fast, that’s the sign that X is probably not the problem,. We now move the
assume false; after the if/then block:
method NotTerminating() {
assert X;
if b {
assert Y;
} else {
assert Z;
assert P;
}
assume false;
assert W;
}
Now, if verification is fast, we know that assert W; is the problem. If it is slow, we know that
one of the two branches of the if is the problem. The next step is to put an assume false; at
the end of the then branch, and an assume false at the beginning of the else branch:
method NotTerminating() {
assert X;
if b {
assert Y;
assume false;
} else {
300
assume false;
assert Z;
assert P;
}
assert W;
}
Now, if verification is slow, it means that assert Y; is the problem. If verification is fast, it
means that the problem lies in the else branch. One trick to ensure we measure the verification
time of the else branch and not the then branch is to move the first assume false; 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 second assume false; down and identify which of the two assertions
makes the verifier slow.
method NotTerminating() {
assert X;
if b {
assume false; // Short-circuit because this branch is verified anyway
assert Y;
} else {
assert Z;
assume false;
assert P;
}
assert W;
}
If verification is fast, which of the two assertions assert Z; or assert P; causes the slowdown?18
We now hope you know enough of assume false; 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.
However, this approach has the problem that it exposes the asserted expressions and lemma
18 assert P;.
301
postconditions not only for the assertion we want to prove faster, but also for every assertion
that appears afterwards. This can result in slowdowns19 . A good practice consists of wrapping
the intermediate verification steps in an assert ... by {}, like this:
assert X by {
assert SOMETHING_HELPING_TO_PROVE_LEMMA_PRECONDITION;
LEMMA();
}
Now, only X is available for the dafny verifier to prove the rest of the method.
13.7.2.3. Labeling and revealing assertions Another way to prevent assertions or precon-
ditions 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 the previous section could be written like this.
assert p: SOMETHING_HELPING_TO_PROVE_LEMMA_PRECONDITION;
// p is not available here.
assert X by {
reveal p;
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:
method Slow(i: int, j: int)
requires greater: i > j {
assert i >= j by {
reveal greater;
}
}
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
ghost predicate P(i: int)
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.
20 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.
302
requires r: x <==> P(1)
{
if x {
assert a: P(1) by { reveal r; }
}
assert x ==> P(1) by { reveal a; } // Error, a is not accessible
}
to
ghost predicate P(i: int)
13.7.2.4. Non-opaque function method 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 a function method Fun() be available for every caller, which can
cause the verifier to time out or get extremely slow21 . A solution for that is to add the attribute
{:opaque} right between function method and Fun(), and use reveal Fun(); 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 the dafny verifier. As such, conversion from bv8
to an int and vice-versa is not straightforward, and can result in slowdowns.
There are two solutions to this for now. First, one can define a subset type instead of using the
built-in type bv8:
type byte = x | 0 <= x < 256
One of the problems of this approach is that additions, substractions 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:
21 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.
303
type byte = x | 0 <= x < 256
method m() {
var a: byte := 250;
var b: byte := 200;
var c := b - a; // inferred to be an 'int', its value will be 50.
var d := a + b; // inferred to be an 'int', its value will be 450.
var e := (a + b) % 256; // still inferred to be an 'int'...
var f: byte := (a + b) % 256; // OK
}
A better solution consists of creating a newtype 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 < 256
method m() {
var a: byte := 250;
var b: byte := 200;
var c := b - a; // OK, inferred to be a byte
var d := a + b; // Error: cannot prove that the result of a + b is of type `byte`.
var f := ((a as int + b as int) % 256) as byte; // 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 information22 . 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,
while X
invariant Y
{
while X'
invariant Y'
{
}
}
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.
304
{
innerLoop();
}
...
method innerLoop()
require Y'
ensures Y'
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.
305
c := if b then 3/a else f(a);
assert a != 0; // Well-formedness
assert c != 5/a; // Correctness
Well-formedness is proved at the same time as correctness, except for well-formedness of requires
and ensures clauses which is proved separatedly 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:
• Every division yields an assertion that the divisor is never zero.
• Every bounded number operation yields an assertion that the result will be within the
same bounds (no overflow, no underflows).
• Every conversion yields an assertion that conversion is compatible.
• Every bitvector shift yields an assertion that the shift amount is never negative, and that
the shift amount is within the width of the value.
Object assertions:
• Every object property access yields an assertion that the object is not null.
• Every assignment o.f := E; yields an assertion that o is among the set of objects of the
modifies clause of the enclosing loop or method.
• Every read o.f yields an assertion that o is among the set of objects of the reads clause
of the enclosing function or predicate.
• Every array access a[x] yields the assertion that 0 <= x < a.Length.
• Every sequence access a[x] yields an assertion, that 0 <= x < |a|, because sequences are
never null.
• Every datatype update expression and datatype destruction yields an assertion that the
object has the given property.
• Every method overriding a trait yields an assertion that any postcondition it provides
implies the postcondition of its parent trait, and an assertion that any precondition it
provides is implied by the precondition of its parent trait.
Other assertions:
• Every value whose type is assigned to a subset type yields an assertion that it satisfies the
subset type constraint.
• Every non-empty subset type yields an assertion that its witness satisfies the constraint.
• Every Assign-such-that operator x :| P(x) yields an assertion that exists x :: P(x).
In case x :| P(x); Body(x) appears in an expression and x is non-ghost, it also yields
forall x, y | P(x) && P(y) :: Body(x) == Body(y).
• Every recursive function yields an assertion that it terminates.
• Every match expression or alternative if statement yields an assertion that all cases are
covered.
• Every call to a function or method with a requires clause yields one assertion per requires
306
clause23 (special cases such as sequence indexing come with a special requires clause that
the index is within bounds).
Specification assertions:
• Any explicit assert statement is an assertion24 .
• A consecutive pair of lines in a calc statement forms an assertion that the expressions are
related according to the common operator.
• Every ensures clause yields an assertion at the end of the method and on every return,
and on forall statements.
• Every invariant clause yields an assertion that it holds before the loop and an assertion
that it holds at the end of the loop.
• Every decreases clause yields an assertion at either a call site or at the end of a while
loop.
• Every yield ensures clause on an iterator yields assertions that the clause holds at every
yielding point.
• Every yield requires clause on an iterator yields assertions 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 for ensures or decreases 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 flow25 .
The fundamental unit of verification in dafny is an assertion batch, which consists of one or more
assertions from this “list”, along with all the remaining assertions turned into assumptions. To
reduce overhead, by default dafny 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,26 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
23 dafny actually breaks things down further. For example, a precondition requires A && B or an assert state-
ment assert A && B; turns into two assertions, more or less like requires A requires B and assert A; assert
B;.
24 dafny actually breaks things down further. For example, a precondition requires A && B or an assert state-
ment assert A && B; turns into two assertions, more or less like requires A requires B and assert A; assert
B;.
25 All 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.
26 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 what dafny knows.
307
will again query the verifier after turning previously failed assertions into assumptions.27
28
• 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 batches29 . An extreme case is the
use of the /vcsSplitOnEveryAssert command-line option or the {:isolate_assertions}
attribute, which causes dafny 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 on assert, return
and continue statements. When placed on a return statement, it will verify the postconditions
for all paths leading to that return in a separate batch. When placed on a continue, it will
verify the loop invariants for all paths leading to that continue in a separate batch.
Given an assertion that is placed into a separate batch, you can then further simplify the ver-
ification 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"}.
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.
308
• Control of how many times to unroll functions: {:fuel}
You can search for them in this file as some of them are still documented in raw text format.
309
version of your source code, in the same format used by dafny test --coverage-report and
dafny generate-tests --verification-coverage-report, indicating which parts of the
program were used, not used, or partly used in the verification of the entire program.
13.7.6.1. Measuring proof brittleness To measure the brittleness of your proofs, start
by using the dafny measure-complexity command with the --iterations N flag to instruct
Dafny to attempt each proof goal N times, using a different random seed each time. The
random seed used for each attempt is derived from the global random seed S specified with
-randomSeed:S, which defaults to 0. The random seed affects the structure of the SMT queries
sent to the solver, changing the ordering of SMT commands, the variable names used, and the
random seed the solver itself uses when making decisions that can be arbitary.
For most use cases, it also makes sense to specify the --log-format csv flag, to log verification
cost statistics to a CSV file. By default, the resulting CSV files will be created in the TestResults
folder of the current directory.
Once Dafny has completed, the dafny-reportgenerator tool is a convenient way to process the
output. It allows you to specify several limits on statistics computed from the elapsed time or
solver resource use of each proof goal, returning an error code when it detects violations of these
limits. You can find documentation on the full set of options for dafny-reportgenerator in its
README.md file.
In general, we recommend something like the following:
dafny-reportgenerator --max-resource-cv-pct 10 TestResults/*.csv
310
This bounds the coefficient of variation of the solver resource count at 10% (0.10). We recommend
a limit of less than 20%, perhaps even as low as 5%. However, when beginning to analyze a new
project, it may be necessary to set limits as high as a few hundred percent and incrementally
ratchet down the limit over time.
When first analyzing proof brittleness, you may also find that certain proof goals succeed
on some iterations and fail on others. If your aim is first to ensure that brittleness doesn’t
worsen and then to start reducing it, integrating dafny-reportgenerator into CI and using the
--allow-different-outcomes flag may be appropriate. Then, once you’ve improved brittleness
sufficiently, you can likely remove that flag (and likely have significantly lower limits on other
metrics).
13.7.6.2. Improving proof brittleness Reducing proof brittleness is typically closely re-
lated to improving performance overall. As such, techniques for debugging slow verification are
typically useful for debugging brittle proofs, as well. See also the verification optimization guide.
13.8. Compilation
The dafny tool can compile a Dafny program to one of several target languages. Details and
idiosyncrasies of each of 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 the dafny-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 for dafny translate it is
not included 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 a Main entry point,
as described here.
311
elements, is not supported. See the Supported features by target language table for the details
on these limits.
namespace Externs_Compile {
public partial class __default {
public static Dafny.ISequence<icharseq> GetCommandLineArgs() {
var dafnyArgs = Environment
.GetCommandLineArgs()
.Select(charseq.FromString);
return Dafny.Sequence<icharseq>.FromArray(dafnyArgs.ToArray());
312
}
}
This serves as an example of implementing an extern, but was only necessary to retrieve command
line arguments historically, as dafny now supports capturing these arguments via a main method
that accepts a seq<string> (see the section on the Main method).
Note that dafny does not check the arguments to {:extern}, so it is the user’s responsibility to
ensure that the provided names result in code that is well-formed in the target language.
Also note that the interface the external code needs to implement may be affected by compilation
flags. In this case, if --unicode-char:true is provided, dafny will compile its char type to the
Dafny.Rune C# type instead, so the references to the C# type char above would need to be
changed accordingly. The reference to charseq.FromString would in turn need to be changed
to charseq.UnicodeFromString to return the correct type.
Most declarations, including those for modules, classes, traits, member variables, constructors,
methods, function methods, and abstract types, can be marked with {:extern}.
Marking a module with {:extern} indicates that the declarations contained within can be
found within the given module, namespace, package, or similar construct within the target
language. Some members of the Dafny module may contain definitions, in which case code for
those definitions will be generated. Whether this results in valid target code may depend on some
target language support for something resembling “partial” modules, where different subsets of
the contents are defined in different places.
The story for classes is similar. Code for a class will be generated if any of its members are
not {:extern}. Depending on the target language, making either all or none of the members
{:extern} may be the only options that result in valid target code. Traits with {:extern}
can refer to existing traits or interfaces in the target language, or can refer to the interfaces of
existing classes.
Member variables marked with {:extern} refer to fields or properties in existing target-language
code. Constructors, methods, and functions refer to the equivalent concepts in the target lan-
guage. They can have contracts, which are then assumed to hold for the existing target-language
code. They can also have bodies, but the bodies will not be compiled in the presence of the
{:extern} attribute. Bodies can still 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 some compilers treat it differently.
Detailed description of the dafny build and dafny run commands and the --input option
(needed when dafny run has more than one input file) is contained in the section on command-
line structure.
313
replaceable module Foo {
method Bar() returns (i: int)
ensures i >= 2
}
method Main() {
var x := Foo.Bar();
print x;
}
// At this point, the program can be verified but not run.
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 example dafny run or dafny translate, any program
that contains a placeholder module must also contain a replacement of this placeholder. When
using dafny verify, 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 a A.dfy file into A.cs; the build
step then produces a A.dll, which can be used as a library or as an executable (run using dotnet
A.dll).
It is also possible to run the dafny files as part of a csproj project, with these steps: - create a
dotnet project file with the command dotnet new console - delete the Program.cs file - build
the dafny program: dafny build A.dfy - run the built program dotnet A.dll
The last two steps can be combined: dafny run A.dfy
Note that all input .dfy files and any needed runtime library code are combined into a single
.cs file, which is then compiled by dotnet to a .dll.
Examples of how to integrate C# libraries and source code with Dafny source code are contained
in this separate document.
314
13.8.5. Java
The Dafny-to-Java compiler translation phase writes out the translated files of a file A.dfy to
a directory A-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 a different 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 consistent with the class they contain, but Dafny files do
not, there may be no relation between the Java file names and the Dafny file names. However,
the wrapper class that contains the Java main method is named for the first .dfy file on the
command-line.
The step of compiling Java files (using javac) 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 source are contained in
this 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 using node. (Javascript has no compilation step). The build and run steps are
simply - dafny build --target:js A.dfy - node A.js
Or, in one step, - dafny run A.dfy
Examples of how to integrate Javascript libraries and source code with Dafny source are contained
in this separate document.
13.8.7. Go
The Dafny-to-Go compiler translates all the given .dfy files into a single .go file in
A-go/src/A.go; the output folder can be specified with the -out option. For an input file
A.dfy the default output folder is A-go. Then, Dafny compiles this program and creates an
A.exe executable in the same folder as A.dfy. Some system runtime code is also placed in
A-go/src. The build and run steps are - dafny build --target:go A.dfy - ./A
The uncompiled code can be compiled and run by go itself using - (cd A-go; GO111MODULE=auto
GOPATH=`pwd` go run src/A.go)
The one-step process is - dafny run --target:go A.dfy
The GO111MODULE variable is used because Dafny translates to pre-module Go code. When the
implementation changes to current Go, the above command-line will change, though the ./A
alternative will still be supported.
Examples of how to integrate Go source code and libraries with Dafny source are contained in
this separate document.
315
13.8.8. Python
The Dafny-to-Python compiler is still under development. However, simple Dafny programs can
be built and run as follows. The Dafny-to-Python compiler 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 - dafny build --target:py A.dfy - python A-py/A.py
In one step: - dafny run --target:py A.dfy
Examples of how to integrate Python libraries and source code with Dafny source are contained
in this separate document.
13.8.9. C++
The C++ backend was written assuming that it would primarily support writing C/C++ style
code in Dafny, which leads to some limitations in the current implementation.
• The C++ compiler does not support BigIntegers, so do not use int, or raw instances of
arr.Length, or sequence length, etc. in executable code. You can however, use arr.Length
as uint64 if you can prove your array is an appropriate size. The compiler will report
inappropriate integer use.
• The C++ compiler does not support advanced Dafny features like traits or coinductive
types.
• There is very limited support for higher order functions even for array initialization. Use
extern definitions like newArrayFill (see extern.dfy) or similar. 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 and performantly
implement datatypes.
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
Unbounded X X X X X X
integers
Real X X X X X X
numbers
Ordinals X X X X X X
Function X X X X X X
values
Iterators X X X X X
316
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
Collections X X X X X X
with trait
element
types
External X X X X X X
module
names
with only
under-
scores
Co- X X X X X X
inductive
datatypes
Multisets X X X X X X
Runtime X X X X X X
type de-
scriptors
Multi- X X X X X X
dimensional
arrays
Map X X X X X X
compre-
hensions
Traits X X X X X X
Let-such- X X X X X X
that
expres-
sions
Non- X X X X X X
native
numeric
newtypes
Method X X
synthesis
External X X X X X X
classes
InstantiatingX X X X X X
the
object
type
317
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
forall X X X X X X
state-
ments
that
cannot
be
sequen-
tialized30
Taking X X X X X X
an
array’s
length
m.Items X X X X X X
when m is
a map
The X X X X X X
/runAll-
Tests
option
Integer X X X X X X X
range
con-
straints
in quanti-
fiers
(e.g. a <=
x <= b)
Exact X X X X X X
value
con-
straints
in quanti-
fiers (x
== C)
30 ‘Sequentializing’ a forall statement refers to compiling it directly to a series of nested loops with the state-
ment’s body directly inside. The alternative, default compilation strategy is to calculate the quantified variable
bindings separately as a collection of tuples, and then execute the statement’s body for each tuple. Not all forall
statements can be sequentialized.
318
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
Sequence X X X X X X
displays
of charac-
ters31
Type test X X X X X X
expres-
sions (x
is T)
Type test X
expres-
sions on
subset
types
Quantifiers X X X X X X
Bitvector X X X X X X
Ro-
tateLeft/RotateRight
functions
for loops X X X X X X X
continue X X X X X X X
state-
ments
Assign- X X X X X X X
such-that
state-
ments
with po-
tentially
infinite
bounds32
Sequence X X X X X X X
update
expres-
sions
31 This refers to an expression such as ['H', 'e', 'l', 'l', 'o'], as opposed to a string literal such as
"Hello".
32 This refers to assign-such-that statements with multiple variables, and where at least one variable has poten-
tially infinite bounds. For example, the implementation of the statement var x: nat, y: nat :| 0 < x && 0 <
y && x*x == y*y*y + 1; needs to avoid the naive approach of iterating all possible values of x and y in a nested
loop.
319
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
Sequence X X X X X X X
construc-
tions
with non-
lambda
initializ-
ers33
Externally- X X X X X
implemented
construc-
tors
Auto- X X X X X X X
initialization
of tuple
variables
Subtype X X X X X X X
con-
straints
in quanti-
fiers
Tuples X X X X X
with
more
than 20
argu-
ments
Arrays X X X X X
with
more
than 16
dimen-
sions
Arrow X X X X X
types
with
more
than 16
argu-
ments
33 Sequence construction expressions often use a direct lambda expression, as in seq(10, x => x * x), but they
320
Dafny
Library
Feature C# JavaScript Go Java Python C++ (.doo)
Unicode X X X X X X
chars
Converting X X X X X X
values to
strings
Legacy X X X X X X
CLI
without
com-
mands
Separate X X X X X X
compila-
tion
All X X X X X X
built-in
types in
runtime
library
Execution X
coverage
report
321
• -? - print out the legacy list of command-line options and terminate. All of these options
are also described in this and the following sections.
• -attrHelp - print out the current list of supported attribute declarations 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 option 1 but also print to standard output.
• -wait - wait for the user to press Enter before terminating after a successful execution.
322
• --output, -o - location of output files [translate, build]
• --show-snippets - include with an error message some of the source code text in 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:<configuration
string>) log verification results to the given test result logger. The currently supported
loggers are trx, csv, and text. These are the XML-based formats commonly used for
test results for .NET languages, a custom CSV schema, and a textual format meant for
human consumption, respectively. You can provide configuration using the same string
format as when using the --logger option for dotnet test, such as:
-verificationLogger:trx;LogFileName=<...>
The exact mapping of verification concepts to these formats is experimental and subject
to change!
The trx and csv loggers automatically choose an output file name by default, and print
the name of this file to the console. The text logger prints its output to the console by
default, but can send output to a file given the LogFileName option.
The text logger also includes a more detailed breakdown of what assertions appear in
each assertion batch. When combined with the -vcsSplitOnEveryAssert option, it will
provide approximate time and resource use costs for each assertion, allowing identification
of especially expensive assertions.
Legacy options:
• -stats - print various statistics about the Dafny files supplied on the command line. The
statistics include the number of total functions, recursive functions, total methods, ghost
methods, classes, and modules. They also include the maximum call graph width and the
maximum 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 to include in
the output requested by -dprint or -rprint. The argument 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 the include mechanism
that have the {:verify false} attribute, as well as datatypes and fields included
from other files.
323
– NoGhost - disables printing of functions, ghost methods, and proof statements in
implementation methods. Also disable anything NoIncludes disables.
• -printIncludes:<None|Immediate|Transitive> - select what information from 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 on the command line. Exit
after printing.
– Transitive - print files transitively included by files specified on the command line.
Exit after printing.
• -view:<view1, view2> - this option limits what is printed by /rprint for a module to the
names that are part of the given export set; the option argument is a comma-separated
list of fully-qualified export set names.
• -funcCallGraph - print out the function call graph. Each line has the format
func,mod=callee*, where func is the name of a function, mod is the name of its
containing module, and callee* is a space-separated list of the functions that func calls.
• --show-snippets (was -showSnippets:<n> ) - show a source code snippet for each Dafny
message. The legacy option was -showSnippets with values 0 and 1 for false and true.
• -printTooltips - dump additional positional information (displayed as mouse-over
tooltips by LSP clients) to standard output as Info messages.
• -pmtrace - print debugging information from the pattern-match compiler.
• -titrace - print debugging information during the type inference process.
• -diagnosticsFormat:<text|json> - control how to report errors, warnings, and info
messages. <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, one diagnostic
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": { // Start and (optional) end of diagnostic
"start": {
"pos": 83, // 0-based character offset in input
"line": 6, // 1-based line number
"character": 0 // 0 -based column number
},
324
"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": [ // Additional messages , if any
{
"location": { ... }, // Like above
"message": "...",
}
]
}
325
• --function-syntax:<version> (was -functionSyntax:<version> ) - select what func-
tion syntax to recognize. The syntax for functions is changing from Dafny version 3 to
version 4. This switch gives early access to the new syntax, and also provides a mode to
help with migration. The valid arguments include the following.
– 3 (default) - compiled functions are written function method and predicate method.
Ghost functions are written function and predicate.
– 4 - compiled functions are written function and predicate. Ghost functions are
written ghost function and ghost predicate.
– migration3to4 - compiled functions are written function method and predicate
method. Ghost functions are written ghost function and ghost predicate. To
migrate from version 3 to version 4, use this flag on your version 3 program to flag all
occurrences of function and predicate as parsing errors. These are ghost functions,
so change those into the new syntax ghost function and ghost predicate. Then,
start using
-functionSyntax:4. This will flag all occurrences of function method and
predicate method as parsing errors. So, change those to just function and
predicate. As a result, your program will use version 4 syntax and have the same
meaning as your previous version 3 program.
– experimentalDefaultGhost - like migration3to4, but allow function and
predicate as alternatives to declaring ghost functions and predicates, respectively
– experimentalDefaultCompiled - like migration3to4, but allow function and
predicate as alternatives to declaring compiled functions and predicates, respectively
– experimentalPredicateAlwaysGhost - compiled functions are written function.
Ghost functions are written ghost function. Predicates are always ghost and are
written predicate.
This option can also be set locally (at the module level) using the :options attribute:
module {:options "--function-syntax:4"} M {
predicate CompiledPredicate() { true }
}
326
• -disableScopes - treat all export sets as export reveal * to never hide function bodies
or type definitions during translation.
• -allowsGlobals - allow the implicit class _default to contain fields, instance functions,
and instance methods. These class members are declared at the module scope, outside of
explicit classes. This command-line option is provided to simplify a transition from the
behavior in the language prior to version 1.9.3, from which point onward all functions and
methods declared at the module scope are implicitly static and field declarations are not
allowed at the module scope.
327
Running Dafny with this option on the file containing your main 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 {{:print}}. (default false) The legacy option
was -trackPrintEffects:<n>) with values 0 or 1 for false and true.
• --relax-definite-assignment - control the rules governing definite assignment, the prop-
erty that every variable is eventually assigned a value before it is used.
– if false (default), enforce definite-assignment for all non-yield-parameter variables 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 compiled variables and fields whose types
do not support auto-initialization and for ghost variables and fields whose type is
possibly empty.
• --disable-nonlinear-arithmetic (was -noNLarith) - reduce Z3’s knowledge of non-
linear arithmetic (the operators *, /, and %). Enabling this option will typically require
more manual work to complete proofs (by explicitly applying lemmas about non-linear
operators), but will also result in more predictable behavior, since Z3 can sometimes get
stuck down an unproductive path while attempting to prove things about those operators.
(This option will perhaps be replaced by -arith in the future. 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 mod-
ule keyword. The attribute optionally takes the value false to enable nonlinear arithmetic.
• --manual-lemma-induction - diables automatic inducntion for lemmas
• --isolate-assertions - verify assertions individually
• --extract-counterexample - if verification fails, report a potential counterexample as a
set of assumptions that can be inserted into the code. Note that Danfy cannot guarantee
that the counterexample it reports provably violates the assertion or that the assumptions
are not mutually inconsistent (see 34 ), 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
34 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 what dafny knows.
328
• --verification-time-limit <seconds> - imposes a time limit on each verification at-
tempt
• --verification-error-limit <number> - limits the number of verification errors re-
ported (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. The value 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 module separately, rather
than aggregating them after they are all finished.
• -mimicVerificationOf:<dafny version> - let dafny attempt to mimic the verification
behavior of a previous version of dafny. This can be useful during migration to a newer
version of dafny when a Dafny program has proofs, such as methods or lemmas, that are
highly variable in the sense that their verification may become slower or fail altogether
after logically irrelevant changes are made in the verification input.
Accepted versions are: 3.3. Note that falling back on the behavior of version 3.3 turns off
features that prevent certain classes of verification variability.
• -noCheating:<n> - control whether certain assumptions are allowed. The value of <n>
can be one of the following.
– 0 (default) - allow assume statements and free invariants.
– 1 - treat all assumptions as assert statements, and drop free invariants.
• -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 for heuristically chosen
quantifiers.
– 3 - apply induction as requested, and for heuristically chosen quantifiers 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).
329
– 1, 2, 3, 4, 5 - use an intermediate heuristic, ordered as follows as far as how discrimi-
nating 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 and assume
allocated(x) for various parameters x, local variables x, bound variables x, etc.
Lower <n> may require more manual allocated(x) annotations and thus may be more
difficult to use. The value of <n> can be one of the following.
– 0 - never assume or assert allocated(x) by default.
– 1 - assume allocated(x) only for non-ghost variables and fields. (These assumptions
are free, since non-ghost variables always contain allocated values at run-time.) This
option may speed up verification relative to -allocated:2.
– 2 - assert/assume allocated(x) on all variables, even bound variables in quantifiers.
This option is the easiest to use for code that uses the heap heavily.
– 3 - (default) make frugal use of heap parameters.
– 4 - like 3 but add allocated antecedents when ranges don’t imply allocatedness.
Warning: this option should be chosen consistently across an entire project; it would be
unsound to use different defaults for different files or modules within a project. Further-
more, modes -allocated:0 and -allocated:1 let functions depend on the allocation state,
which is not sound in general.
• -noAutoReq - ignore autoReq attributes, and therefore do not automatically generate
requires clauses.
• -autoReqPrint:<file> - print the requires clauses that were automatically generated by
autoReq to the given <file>.
• -arith:<n> - control how arithmetic is modeled during verification. This is an experimen-
tal switch, and its options may change. The value of <n> can be one of the following.
– 0 - use Boogie/Z3 built-ins for all arithmetic operations.
– 1 (default) - like 0, but introduce symbolic synonyms for *, /, and %, and allow these
operators to be used in triggers.
– 2 - like 1, but also introduce symbolic synonyms for + and -.
– 3 - turn off non-linear arithmetic in the SMT solver. Still use Boogie/Z3 built-in
symbols for all arithmetic operations.
– 4 - like 3, but introduce symbolic synonyms for *, /, and %, and allow these operators
to be used in triggers.
– 5 - like 4, but also introduce symbolic synonyms for + and -.
– 6 - like 5, and introduce axioms that distribute + over *.
– 7 - like 6, and introduce facts about the associativity of literal arguments over *.
330
– 8 - like 7, and introduce axioms for the connection between *, /, and %.
– 9 - like 8, and introduce axioms for sign of multiplication.
– 10 - like 9, and introduce axioms for commutativity and associativity of *.
• -autoTriggers:<n> - control automatic generation of {:trigger} annotations. See trig-
gers. The value of <n> can be one of the following.
– 0 - do not generate {:trigger} annotations for user-level quantifiers.
– 1 (default) - add a {:trigger} annotation to each user-level quantifier. Existing
annotations are preserved.
• -rewriteFocalPredicates:<n> - control rewriting of predicates in the body of prefix
lemmas. See the 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 a focal predicate P to
P#[_k-1].
331
– cpp - C++. The default output of dafny Hello.dfy -compileTarget:cpp is
Hello.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 in dafny run; 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. the C# backend will generate <file>.dll and optionally
<file>.cs with -spillTargetCode). Compilers that generate multiple files use the file name
as a directory name (e.g. the Java backend will generate files in directory <file>-java/). Any
file extension is ignored, so -out:<file> is the same as -out:<file>.<ext> if <file> contains
no periods.
• --include-runtime - include the runtime library for the target language in the 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 to the pre-built DafnyRuntime.dll in the compiled assembly rather than
including DafnyRuntime.cs in the build process.
Legacy options:
• -compile:<n> - [obsolete - use dafny build or dafny run] 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 program
– 1 (default) - upon successful verification, compile the program to the target language.
– 2 - always compile, regardless of verification success.
– 3 - if verification is successful, compile the program (like option 1), and then if there
is a Main method, attempt to run the program.
– 4 - always compile (like option 2), and then if there is a Main method, attempt to run
the program.
• -spillTargetCode:<n> - [obsolete - use dafny translate) control whether to write out
compiled code in the target language (instead of just holding it in internal temporary
memory). The value of <n> can be one of the following.
– 0 (default) - don’t make any extra effort to write the textual target program (but still
compile it, if -compile indicates to do so).
– 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.
332
– 3 - write the compiled program regardless of verification success and the -compile
setting.
Note that some compiler targets may (always or in some situations) write out 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 to use as the executable
entry point. The default is the method with the {:main} attribute, or else the method
named Main.
• -compileVerbose:<n> - control whether to write out compilation progress 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 by the compiler
• -coverage:<file> - emit branch-coverage calls and outputs into <file>, including a leg-
end that gives a description of each source-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 the csc exe-
cutable.
• -optimizeResolution:<n> - control optimization of method target resolution. 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 current verification target.
– 2 (default) - as in 1, but resolve only methods that are defined in the current verifi-
cation target file, not in included files.
• -testContracts:<mode> - test certain function and method contracts at runtime. This
works by generating a wrapper for each function or method to be tested that includes a
sequence of expect statements for each requires clause, a call to the original, and sequence
of expect statements for each ensures clause. This is particularly useful for code marked
with the {:extern} attribute and implemented in the target language instead of Dafny.
Having runtime checks of the contracts on such code makes it possible to gather evidence
that the target-language code satisfies the assumptions made of it during Dafny verification
through mechanisms ranging from manual tests through fuzzing to full verification. For
the latter two use cases, having checks for requires clauses can be helpful, even if the
Dafny calling code will never violate them.
The <mode> parameter can currently be one of the following.
– Externs - insert dynamic checks when calling any function or method marked with
the {:extern} attribute, wherever the call occurs.
– TestedExterns - insert dynamic checks when calling any function or method marked
with the {:extern} attribute directly from a function or method marked with the
333
{:test} attribute.
334
• -rlimit:<n> - set the maximum solver resource count to use while proving a single SMT
query. This can be a more deterministic approach than setting a time limit. To choose an
appropriate value, please refer 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"
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 \
"
335
13.9.11. Controlling test generation
Dafny is capable of generating unit (runtime) tests. It does so by asking the prover to solve
for 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 in a separate document.
336
14. Dafny VSCode extension and the Dafny Language
Server
14.1. Dafny functionality within VSCode
There is a language server for Dafny, which implements the Language 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 for
Dafny 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 con-
figuration. 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.
337
Figure 1: image
338
Figure 2: image
339
Figure 3: image
340
Figure 4: image
341
The red error rectangles occupy only half the horizontal space, to visualise their possible obso-
lescence.
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.
Figure 5: image
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.
342
Figure 6: image
343
Figure 7: image
344
The server is essentially a REPL, which produces output in the same format as the Dafny CLI;
clients thus do 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 the Dafny server. The
cache is blank at that point.
• The editor sends a copy of the buffer for initial verification. This takes some time, after
which the server returns a list of errors.
• The user makes modifications; the editor periodically sends a new copy of the buffer’s
contents to the Dafny server, which quickly returns an updated list of errors.
The client-server protocol is sequential, uses JSON, and works over ASCII pipes by base64-
encoding utf-8 queries. It defines one type of query, and two types of responses:
Queries are of the following form:
verify
<base64 encoded JSON payload>
[[DAFNY-CLIENT: EOM]]
or
<error message>
[FAILURE] [[DAFNY-SERVER: EOM]]
The JSON payload is an utf-8 encoded string resulting of the serialization of a 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 is convenient; for larger files,
however, it is generally better for performance to write the source snapshot to a separate file,
and to pass that to Dafny by setting the ‘sourceIsFile’ flag to true.
For example, if you compile and run DafnyServer.exe, you could paste the following command:
verify
eyJhcmdzIjpbIi9jb21waWxlOjAiLCIvcHJpbnRUb29sdGlwcyIsIi90aW1lTGltaXQ6MjAiXSwi
ZmlsZW5hbWUiOiJ0cmFuc2NyaXB0Iiwic291cmNlIjoibWV0aG9kIEEoYTppbnQpIHJldHVybnMg
KGI6IGludCkge1xuICBiIDo9IGE7XG4gIGFzc2VydCBmYWxzZTtcbn1cbiIsInNvdXJjZUlzRmls
ZSI6ZmFsc2V9
[[DAFNY-CLIENT: EOM]]
345
The interpreter sees the command verify, and then starts reading every line until it sees
[[DAFNY-CLIENT: EOM]] The payload is a base64 encoded string that you could encode or de-
code using JavaScript’s atob and btoa 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}))
=== "eyJhcmdzIjpbIi9jb21waWxlOjAiLCIvcHJpbnRUb29sdGlwcyIsIi90aW1lTGltaXQ6MjAiXSwiZmlsZW5hbWUiOiJ0cm
346
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 used in
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 a Dafny.dll of the same version as the Language Server. A plugin
typically defines:
• Zero or one class extending Microsoft.Dafny.Plugins.PluginConfiguration, which re-
ceives plugins arguments in its method ParseArguments, and
1. Can return a list of Microsoft.Dafny.Plugins.Rewriters when its method
GetRewriters() is called by Dafny,
2. Can return a list of Microsoft.Dafny.Plugins.Compilers when its method
GetCompilers() is called by Dafny,
3. If the configuration extends the subclass Microsoft.Dafny.LanguageServer.Plugins.PluginConfiguratio
1. Can return a list of Microsoft.Dafny.LanguageServer.Plugins.DafnyCodeActionProviders
when its method GetDafnyCodeActionProviders() is called by the Dafny Lan-
guage Server.
2. Can return a modified version of OmniSharp.Extensions.LanguageServer.Server.LanguageServerOpt
when its method WithPluginHandlers() is called by the Dafny Language Server.
• Zero or more classes extending Microsoft.Dafny.Plugins.Rewriter. If a configura-
tion class is provided, it is responsible for instantiating them and returning them in
GetRewriters(). If no configuration class is provided, an automatic configuration will
load every defined Rewriter automatically.
• Zero or more classes extending Microsoft.Dafny.Plugins.Compiler. If a configura-
tion class is provided, it is responsible for instantiating them and returning them in
GetCompilers(). If no configuration class is provided, an automatic configuration will
load every defined Compiler automatically.
• Zero or more classes extending Microsoft.Dafny.LanguageServer.Plugins.DafnyCodeActionProvider.
Only a configuration class of type Microsoft.Dafny.LanguageServer.Plugins.PluginConfiguration
can be responsible for instantiating them and returning them in GetDafnyCodeActionProviders().
The most important methods of the class Rewriter 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 all PostResolve(ModuleDefinition)
347
have been called.
Plugins are typically used to report additional diagnostics such as unsupported constructs for
specific compilers (through the methods Èrror(...) and Warning(...) of the field Reporter
of the class Rewriter)
Note that all plugin errors should use the original program’s expressions’ token and NOT
Token.NoToken, else no error will be displayed in the IDE.
Open the newly created file PluginTutorial.csproj, and add the following after
</PropertyGroup>:
<ItemGroup>
<ProjectReference Include="../dafny/source/DafnyLanguageServer/DafnyLanguageServer.csproj" />
</ItemGroup>
348
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
namespace MyPlugin;
After that, add a PluginConfiguration that will expose all the quickfixers of your plugin. This
class will be discovered and instantiated automatically by Dafny.
public class TestConfiguration : PluginConfiguration {
public override DafnyCodeActionProvider[] GetDafnyCodeActionProviders() {
return new DafnyCodeActionProvider[] { new AddCommentDafnyCodeActionProvider() };
}
}
Note that you could also override the methods GetRewriters() and GetCompilers() for other
purposes, but this is out of scope for this tutorial.
Then, we need to create the quickFixer AddCommentDafnyCodeActionProvider itself:
public class AddCommentDafnyCodeActionProvider : DafnyCodeActionProvider {
public override IEnumerable<DafnyCodeAction> GetDafnyCodeActions(IDafnyCodeActionInput input, Ran
return new DafnyCodeAction[] { };
}
}
For now, this quick fixer returns nothing. input is the program state, and selection is where
the caret is. We replace the return statement with a conditional that tests whether the selection
is on the first line:
var firstTokenRange = input.Program?.GetFirstTopLevelToken()?.GetLspRange();
if(firstTokenRange != null && firstTokenRange.Start.Line == selection.Start.Line) {
return new DafnyCodeAction[] {
// TODO
};
} else {
return new DafnyCodeAction[] { };
}
Every quick fix consists of a title (provided immediately), and zero or more DafnyCodeActionEdit
(computed lazily). A DafnyCodeActionEdit has a Range to remove and some string to insert
instead. All DafnyCodeActionEdits of the same DafnyCodeAction are applied at the same
time if selected.
To create a DafnyCodeAction, we can either use the easy-to-use InstantDafnyCodeAction,
which accepts a title and an array of edits:
return new DafnyCodeAction[] {
new InstantDafnyCodeAction("Insert comment", new DafnyCodeActionEdit[] {
new DafnyCodeActionEdit(firstTokenRange.GetStartRange(), "/*First comment*/")
})
349
};
15.1.2.2. Request handler plugin This request handler plugin enhances the Language
Server to support a request with a TextDocumentIdentifier as parameter, which will return a
bool value denoting whether the provided DocumentUri has any LoopStmt’s in it.
Open the file MyPlugin.cs, remove everything, and write the imports and a namespace:
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Server;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Microsoft.Dafny.LanguageServer.Plugins;
using Microsoft.Dafny.LanguageServer.Workspace;
using MediatR;
using Microsoft.Dafny;
namespace MyPlugin;
After that, add a PluginConfiguration that will add all the request handlers of your plugin.
This class will be discovered and instantiated automatically by Dafny.
public class TestConfiguration : PluginConfiguration {
public override LanguageServerOptions WithPluginHandlers(LanguageServerOptions options) {
return options.WithHandler<DummyHandler>();
}
}
350
[Parallel]
[Method("dafny/request/dummy", Direction.ClientToServer)]
public record DummyParams : TextDocumentIdentifier, IRequest<bool>;
351
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 tokens to 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)(Mössenböck, Löberbauer, and Wöß 2013). See the Coco/R
Reference manual for details. The Dafny input file to Coco/R is the Dafny.atg file in the source
tree.
The grammar is an attributed extended BNF grammar. The attributed adjective indicates that
the BNF productions are parameterized by boolean parameters that control variations of the
production rules, such as whether a particular alternative is permitted or not. Using such
attributes allows combining non-terminals with quite similar production rules, making a simpler,
more compact and more readable grammer.
The grammar rules presented here replicate those in the source code, but omit semantic actions,
error recovery markers, and conflict resolution syntax. Some uses of the attribute parameters
are described informally.
The names of character sets and tokens start with a lower case letter; the names of grammar
non-terminals start with an upper-case letter.
digit = "0123456789"
posDigit = "123456789"
posDigitFrom2 = "23456789"
hexdigit = "0123456789ABCDEFabcdef"
special = "'_?"
cr = '\r'
352
lf = '\n'
tab = '\t'
A nonidchar is any character except those that can be used in an identifier. Here the scanner
generator will interpret ANY as any unicode character. However, nonidchar is used only to mark
the end of the !in token; in this context any character other than whitespace or printable ASCII
will trigger a subsequent scanning or parsing error.
353
"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 | bvToken
escapedChar =
( "\'" | "\"" | "\\" | "\0" | "\n" | "\r" | "\t"
| " \u " hexdigit hexdigit hexdigit hexdigit
| " \U {" hexdigit { hexdigit } "}"
)
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 and greatest are recognized as adjectives to the keyword predicate (cf. Section
12.4).
• older is a modifier for parameters of non-extreme predicates (cf. Section 6.4.6).
354
The \uXXXX form of an escapedChar is only used when the option --unicode-char=false is
set (which is the default for Dafny 3.x); the \U{XXXXXX} form of an escapedChar is only used
when the option --unicode-char=true is set (which is the default for Dafny 4.x).
17.2.1. Programs
(discussion)
Dafny = { IncludeDirective_ } { TopDecl(isTopLevel:true, isAbstract: false) } EOF
355
( SubModuleDecl(isTopLevel)
| ClassDecl
| DatatypeDecl
| NewtypeDecl
| SynonymTypeDecl // includes abstract types
| IteratorDecl
| TraitDecl
| ClassMemberDecl(allowConstructors: false, isValueType: true, moduleLevelDecl: true)
)
17.2.2. Modules
QualifiedModuleExport =
ModuleQualifiedName [ "`" ModuleExportSuffix ]
ModuleExportSuffix =
( ExportId
| "{" ExportId { "," ExportId } "}"
)
356
17.2.2.3. Module Export Definitions (discussion)
ModuleExport =
"export"
[ ExportId ]
[ "..." ]
{
"extends" ExportId { "," ExportId }
| "provides" ( ExportSignature { "," ExportSignature } | "*" )
| "reveals" ( ExportSignature { "," ExportSignature } | "*" )
}
17.2.3. Types
(discussion)
Type = DomainType_ | ArrowType_
DomainType_ =
( BoolType_ | CharType_ | IntType_ | RealType_
| OrdinalType_ | BitVectorType_ | ObjectType_
| FiniteSetType_ | InfiniteSetType_
| MultisetType_
| FiniteMapType_ | InfiniteMapType_
| SequenceType_
| NatType_
| StringType_
| ArrayType_
| TupleType
| NamedType
)
357
17.2.3.2. Generic instantiation (discussion)
GenericInstantiation = "<" Type { "," Type } ">"
StringType_ = "string"
SynonymTypeName = NoUSIdent
SynonymTypeDecl_ =
"type" { Attribute } SynonymTypeName
{ TypeParameterCharacteristics }
[ GenericParameters ]
"=" Type
358
OpaqueTypeDecl_ =
"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"
359
17.2.3.6. Class type (discussion)
ClassDecl = "class" { Attribute } ClassName [ GenericParameters ]
["extends" Type {"," Type} | ellipsis ]
"{" { { DeclModifier }
ClassMemberDecl(modifiers,
allowConstructors: true,
isValueType: false,
moduleLevelDecl: false)
}
"}"
360
| ellipsis
)
IteratorSpec
[ BlockStmt ]
DatatypeMemberDecl =
{ Attribute } DatatypeMemberName [ FormalsOptionalIds ]
If moduleLevelDecl is true, then the static modifier is not permitted (the constant field is
static implicitly).
361
17.2.4.3. Method declarations (discussion)
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) ]
If isWithinAbstractModule is false, then the method must have a body for the program that
contains the declaration to be compiled.
The KType may be specified only for least and greatest lemmas.
362
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) | ellipsis
FunctionSignature_(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)
| ellipsis
363
[ KType ]
Formals(allowGhostKeyword, allowNewKeyword, allowOlderKeyword,
allowDefault: true)
[
":"
( Type
| "(" Ident ":" "bool" ")"
)
]
17.2.5. Specifications
17.2.5.1. Method specifications (discussion)
MethodSpec =
{ ModifiesClause(allowLambda: false)
| RequiresClause(allowLabel: true)
| EnsuresClause(allowLambda: false)
| DecreasesClause(allowWildcard: true, allowLambda: false)
}
364
| [ "yield" ] RequiresClause(allowLabel: !isYield)
| [ "yield" ] EnsuresClause(allowLambda: false)
| DecreasesClause(allowWildcard: false, allowLambda: false)
}
DecreasesList(allowWildcard, allowLambda) =
PossiblyWildExpression(allowLambda, allowWildcard)
{ "," PossiblyWildExpression(allowLambda, allowWildcard) }
PossiblyWildExpression(allowLambda, allowWild) =
( "*" // if allowWild is false, using '*' provokes an error
| Expression(allowLemma: false, allowLambda)
)
365
17.2.5.10. Invariant clauses (discussion)
InvariantClause_ =
"invariant" { Attribute }
Expression(allowLemma: false, allowLambda: true)
17.2.6. Statements
17.2.6.1. Labeled statement (discussion)
Stmt = { "label" LabelName ":" } NonLabeledStmt
366
BreakStmt =
( "break" LabelName ";"
| "continue" LabelName ";"
| { "break" } "break" ";"
| { "break" } "continue" ";"
)
367
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
)
368
|
( 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 }
}
369
17.2.6.15. Match statement (discussion)
MatchStmt =
"match"
Expression(allowLemma: true, allowLambda: true)
( "{" { CaseStmt } "}"
| { CaseStmt }
)
370
17.2.6.20. Reveal statement (discussion)
RevealStmt =
"reveal"
Expression(allowLemma: false, allowLambda: true)
{ "," Expression(allowLemma: false, allowLambda: true) }
";"
CalcOp =
( "==" [ "#" "["
Expression(allowLemma: true, allowLambda: true) "]" ]
| "<" | ">"
| "!=" | "<=" | ">="
| "<==>" | "==>" | "<=="
)
371
OpaqueBlock = "opaque" OpaqueSpec BlockStmt
OpaqueSpec = {
| ModifiesClause(allowLambda: false)
| EnsuresClause(allowLambda: false)
}
17.2.7. Expressions
17.2.7.1. Top-level expression (discussion)
Expression(allowLemma, allowLambda, allowBitwiseOps = true) =
EquivExpression(allowLemma, allowLambda, allowBitwiseOps)
[ ";" Expression(allowLemma, allowLambda, allowBitwiseOps) ]
The “allowLemma” argument says whether or not the expression to be parsed is allowed to
have the form S;E where S is a call to a lemma. “allowLemma” should be passed in as “false”
whenever the expression to be parsed sits in a context that itself is terminated by a semi-colon.
The “allowLambda” says whether or not the expression to be parsed is allowed to be a lambda
expression. More precisely, an identifier or parenthesized, comma-delimited list of identifiers
is allowed to continue as a lambda expression (that is, continue with a reads, requires, or
=>) only if “allowLambda” is true. This affects function/method/iterator specifications, if/while
statements with guarded alternatives, and expressions in the specification of a lambda expression
itself.
372
LogicalExpression(allowLemma, allowLambda, allowBitwiseOps) =
[ "&&" | "||" ]
RelationalExpression(allowLemma, allowLambda, allowBitwiseOps)
{ ( "&&" | "||" )
RelationalExpression(allowLemma, allowLambda, allowBitwiseOps)
}
RelOp =
( "=="
[ "#" "[" Expression(allowLemma: true, allowLambda: true) "]" ]
| "!="
[ "#" "[" Expression(allowLemma: true, allowLambda: true) "]" ]
| "<" | ">" | "<=" | ">="
| "in"
| "!in"
| "!!"
)
373
MulOp = ( "*" | "/" | "%" )
374
"=>"
Expression(allowLemma, allowLambda: true, allowBitwiseOps)
375
| CardinalityExpression_
| ParensExpression
)
Dec = decimaldigits
376
CardinalityExpression_ =
"|" Expression(allowLemma: true, allowLambda: true) "|"
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
}
MapLiteralExpressions =
Expression(allowLemma: true, allowLambda: true)
":="
Expression(allowLemma: true, allowLambda: true)
{ ","
Expression(allowLemma: true, allowLambda: true)
":="
377
Expression(allowLemma: true, allowLambda: true)
}
SingleExtendedPattern =
( PossiblyNegatedLiteralExpression
378
| IdentTypeOptional
| [ Ident ] "(" [ SingleExtendedPattern { "," SingleExtendedPattern } ] ")"
)
ExtendedPattern =
( [ "|" ] SingleExtendedPattern { "|" SingleExtendedPattern } )
PossiblyNegatedLiteralExpression =
( "-" ( Nat | Dec )
| LiteralExpression
)
379
Expression(allowLemma: false, allowLambda: true)
{ "," Expression(allowLemma: false, allowLambda: true) }
|
":-"
Expression(allowLemma: false, allowLambda: true)
)
";"
Expression(allowLemma, allowLambda)
MemberBindingUpdate =
( ident | digits )
":=" Expression(allowLemma: true, allowLambda: true)
380
17.2.7.45. Subsequence Suffix (discussion)
SubsequenceSuffix_ =
"[" [ Expression(allowLemma: true, allowLambda: true) ]
".." [ Expression(allowLemma: true, allowLambda: true) ]
"]"
381
ActualBinding(isGhost = false) =
[ NoUSIdentOrDigits ":=" ]
Expression(allowLemma: true, allowLambda: true)
QuantifierDomain(allowLemma, allowLambda) =
QuantifierVarDecl(allowLemma, allowLambda)
{ "," QuantifierVarDecl(allowLemma, allowLambda) }
QuantifierVarDecl(allowLemma, allowLambda) =
IdentTypeOptional
[ "<-" Expression(allowLemma, allowLambda) ]
{ Attribute }
[ | Expression(allowLemma, allowLambda) ]
Ident = ident
382
IdentType = WildIdent ":" Type
TypeIdentOptional =
{ Attribute }
{ "ghost" | "nameonly" } [ NoUSIdentOrDigits ":" ] Type
[ ":=" Expression(allowLemma: true, allowLambda: true) ]
383
18. Testing syntax rendering
Sample math B: 𝑎 → 𝑏 or
𝑎→𝜋
or ( a �) or [ a →�]
Colors
integer literal: 10
hex literal: 0xDEAD
real literal: 1.1
boolean literal: true false
char literal: 'c'
string literal: "abc"
verbatim string: @"abc"
ident: ijk
type: int
generic type: map<int,T>
operator: <=
punctuation: { }
keyword: while
spec: requires
comment: // comment
attribute {: name }
error: $
384
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 _ab ab? _0
id-label: a@label
literal: true false null
op: - ! ~ x -!~x
op: a + b - c * d / e % f a+b-c*d/e%f
op: <= >= < > == != b&&c || ==> <==> <==
op: !=# !! in !in
op: !in� !iné
not op: !inx
punc: . , :: | :| := ( ) [ ] { }
types: int real string char bool nat ORDINAL
types: object object?
types: bv1 bv10 bv0
types: array array2 array20 array10
types: array? array2? array20? array10?
ids: array1 array0 array02 bv02 bv_1
ids: intx natx int0 int_ int? bv1_ bv1x array2x
types: 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: seq map imap set iset multiset
ids: seqx mapx
no arg: seq < > seq < , > seq <bool , , bool > seq<bool , >
keywords: if while assert assume
spec: requires reads modifies
attribute: {: MyAttribute "asd", 34 }
attribute: {: MyAttribute }
comment: // comment
comment: /* comment */ after
comment: // comment /* asd */ dfg
comment: /* comment /* embedded */ tail */ after
comment: /* comment // embedded */ after
comment: /* comment
/* inner comment
*/
outer comment
*/ after
385
more after
386
19. References
Barnett, Mike, Bor-Yuh Evan Chang, Robert DeLine, Bart Jacobs, and K. Rustan M. Leino.
2006. “Boogie: A Modular Reusable Verifier for Object-Oriented Programs.” In Formal
Methods for Components and Objects: 4th International Symposium, FMCO 2005, edited
by Frank S. de Boer, Marcello M. Bonsangue, Susanne Graf, and Willem-Paul de Roever,
4111:364–87. Lncs. Springer.
Bertot, Yves, and Pierre Castéran. 2004. Interactive Theorem Proving and Program Development
— Coq’Art: The Calculus of Inductive Constructions. Texts in Theoretical Computer Science.
Springer.
Bove, Ana, Peter Dybjer, and Ulf Norell. 2009. “A Brief Overview of Agda — a Functional
Language with Dependent Types.” In Theorem Proving in Higher Order Logics, 22nd Inter-
national Conference, TPHOLs 2009, edited by Stefan Berghofer, Tobias Nipkow, Christian
Urban, and Makarius Wenzel, 5674:73–78. Lecture Notes in Computer Science. Springer.
Camilleri, Juanito, and Tom Melham. 1992. “Reasoning with Inductively Defined Relations in
the HOL Theorem Prover.” 265. University of Cambridge Computer Laboratory.
Harrison, John. 1995. “Inductive Definitions: Automation and Application.” In TPHOLs 1995,
edited by E. Thomas Schubert, Phillip J. Windley, and Jim Alves-Foss, 971:200–213. LNCS.
Springer.
Hoare, C. A. R. 1969. “An Axiomatic Basis for Computer Programming.” Cacm 12 (10): 576–80,
583.
Jacobs, Bart, and Jan Rutten. 2011. “An Introduction to (Co)algebra and (Co)induction.”
In Advanced Topics in Bisimulation and Coinduction, edited by Davide Sangiorgi and Jan
Rutten, 38–99. Cambridge Tracts in Theoretical Computer Science 52. Cambridge University
Press.
Kassios, Ioannis T. 2006. “Dynamic Frames: Support for Framing, Dependencies and Sharing
Without Restrictions.” In FM 2006: Formal Methods, 14th International Symposium on
Formal Methods, edited by Jayadev Misra, Tobias Nipkow, and Emil Sekerinski, 4085:268–83.
Lncs. Springer.
Kaufmann, Matt, Panagiotis Manolios, and J Strother Moore. 2000. Computer-Aided Reasoning:
An Approach. Kluwer Academic Publishers.
Koenig, Jason, and K. Rustan M. Leino. 2012. “Getting Started with Dafny: A Guide.” In
Software Safety and Security: Tools for Analysis and Verification, edited by Tobias Nipkow,
Orna Grumberg, and Benedikt Hauptmann, 33:152–81. NATO Science for Peace and Security
Series d: Information and Communication Security. IOS Press.
Kozen, Dexter, and Alexandra Silva. 2012. “Practical Coinduction.” http://hdl.handle.net/181
3/30510. Comp.; Inf. Science, Cornell Univ.
Krauss, Alexander. 2009. “Automating Recursive Definitions and Termination Proofs in Higher-
Order Logic.” PhD thesis, Technische Universität München.
Leino, K. Rustan M. 2008a. “Main Microsoft Research Dafny Web Page.”
———. 2008b. “This Is Boogie 2.” Manuscript KRML 178.
———. 2009. “Dynamic-Frame Specifications in Dafny.” JML seminar, Dagstuhl, Germany.
———. 2010. “Dafny: An Automatic Program Verifier for Functional Correctness.” In LPAR-16,
edited by Edmund M. Clarke and Andrei Voronkov, 6355:348–70. Lncs. Springer.
———. 2012. “Automating Induction with an SMT Solver.” In Verification, Model Checking,
and Abstract Interpretation — 13th International Conference, VMCAI 2012, edited by Viktor
387
Kuncak and Andrey Rybalchenko, 7148:315–31. Lncs. Springer.
Leino, K. Rustan M., and Michal Moskal. 2014a. “Co-Induction Simply: Automatic Co-
Inductive Proofs in a Program Verifier.” Manuscript KRML 230.
Leino, K. Rustan M., and Michał Moskal. 2014b. “Co-Induction Simply — Automatic Co-
Inductive Proofs in a Program Verifier.” In FM 2014, 8442:382–98. LNCS. Springer.
Leino, K. Rustan M., and Nadia Polikarpova. 2013. “Verified Calculations.” Manuscript KRML
231.
Leino, K. Rustan M., and Philipp Rümmer. 2010. “A Polymorphic Intermediate Verification
Language: Design and Logical Encoding.” In Tools and Algorithms for the Construction and
Analysis of Systems, 16th International Conference, TACAS 2010, edited by Javier Esparza
and Rupak Majumdar, 6015:312–27. Lncs. Springer.
Leino, K. Rustan M., and Valentin Wüstholz. 2015. “Fine-Grained Caching of Verification
Results.” In Computer Aided Verification (CAV), edited by Daniel Kroening and Corina S.
Pasareanu, 9206:380–97. Lecture Notes in Computer Science. Springer.
Leroy, Xavier, and Hervé Grall. 2009. “Coinductive Big-Step Operational Semantics.” Informa-
tion and Computation 207 (2): 284–304.
Manolios, Panagiotis, and J Strother Moore. 2003. “Partial Functions in ACL2.” Journal of
Automated Reasoning 31 (2): 107–27.
Milner, Robin. 1982. A Calculus of Communicating Systems. Springer-Verlag New York, Inc.
Mössenböck, Hanspeter, Markus Löberbauer, and Albrecht Wöß. 2013. “The Compiler Genera-
tor Coco/r.” Open source from University of Linz.
Moura, Leonardo de, and Nikolaj Bjørner. 2008. “Z3: An Efficient SMT Solver.” In Tools
and Algorithms for the Construction and Analysis of Systems, 14th International Conference,
TACAS 2008, edited by C. R. Ramakrishnan and Jakob Rehof, 4963:337–40. Lncs. Springer.
Nipkow, Tobias, and Gerwin Klein. 2014. Concrete Semantics with Isabelle/HOL. Springer.
Paulin-Mohring, Christine. 1993. “Inductive Definitions in the System Coq — Rules and Prop-
erties.” In TLCA ’93, 664:328–45. LNCS. Springer.
Paulson, Lawrence C. 1994. “A Fixedpoint Approach to Implementing (Co)inductive Defini-
tions.” In CADE-12, edited by Alan Bundy, 814:148–61. LNCS. Springer.
Pierce, Benjamin C., Chris Casinghino, Marco Gaboardi, Michael Greenberg, Cătălin Hriţcu,
Vilhelm Sjöberg, and Brent Yorgey. 2015. Software Foundations. Version 3.2. http://www.
cis.upenn.edu/~bcpierce/sf.
Smans, Jan, Bart Jacobs, and Frank Piessens. 2009. “Implicit Dynamic Frames: Combining
Dynamic Frames and Separation Logic.” In ECOOP 2009 — Object-Oriented Programming,
23rd European Conference, edited by Sophia Drossopoulou, 5653:148–72. Lncs. Springer.
Smans, Jan, Bart Jacobs, Frank Piessens, and Wolfram Schulte. 2008. “Automatic Verifier for
Java-Like Programs Based on Dynamic Frames.” In Fundamental Approaches to Software
Engineering, 11th International Conference, FASE 2008, edited by José Luiz Fiadeiro and
Paola Inverardi, 4961:261–75. Lncs. Springer.
Swamy, Nikhil, Juan Chen, Cédric Fournet, Pierre-Yves Strub, Karthikeyan Bhargavan, and
Jean Yang. 2011. “Secure Distributed Programming with Value-Dependent Types.” In
ICFP 2011, 266–78. ACM.
Tarski, Alfred. 1955. “A Lattice-Theoretical Fixpoint Theorem and Its Applications.” Pacific
Journal of Mathematics 5: 285–309.
Winskel, Glynn. 1993. The Formal Semantics of Programming Languages: An Introduction.
MIT Press.
388