Introducing Dafny 3.0!
Highlights
Here are some of the exciting changes since Dafny 2.3.0:
-
Full compilation support to target C#, JavaScript, Go, and Java, and some support to target C++. (Fine print: C# and Java targets do not yet support type-parameter variance for datatypes. The Java target does not support Dafny's
iterator
declarations.) -
Datatypes, co-datatypes, newtypes, and opaque types can declare members (methods, functions, constants), just like classes and traits.
-
Traits and classes that extend traits can have type parameters.
-
Types can be empty. Subset types and newtypes can opt out of giving a witness.
-
Type parameters and opaque types can be declared to require auto-initialization or non-emptiness.
-
The
case
s ofmatch
constructs are ordered, andcase
patterns can include simple literal constants. -
expect
statements ("check-or-halt") introduce assumptions that are checked at run time. -
:-
assignments ("assign-or-return") provide abrupt return from a method upon failure (cf. exception handling). Similarly,:-
let-bindings give expressions a way to handle failures (cf. monads). -
There are sequence comprehensions, map comprehensions are more expressive, and operators for map merge and map domain subtraction.
-
Substantial refresh of the Dafny Language Reference Manual.
-
Co-released with a new Dafny plug-in for VS Code.
Details
Here is a detailed list of the major improvements since Dafny 2.3.0. Some of these improvements are not backward compatible with 2.3.0, as noted by "Breaking change" tags.
Language
-
datatype
,codatatype
,newtype
, and opaque types can declare members (just like aclass
and atrait
can declare members). -
There are
expect
statements ("check-or-halt").The statement
expect E, V;
checks boolean expressionE
at run time and halts execution if it evaluates tofalse
. The optional argumentV
is used in the error message produced. The verifier treats the statement asassume E;
.The statement can be used in statement expressions. For example,
expect 0 <= i < a.Length; a[i]
returns elementi
ofa
, but halts ifi
is not in range. -
There are
:-
statements ("assign-or-return") and expressions ("let-or-fail").This new assignment operator extracts a successful value from the right-hand side (RHS) of the assignment, or propagates a failure if the evaluation of the RHS reports a failure.
The
:-
constructs can have multiple LHSs and corresponding RHSs. Only the first value on the RHS is treated specially via its failure-compatible type.The
:-
is optionally be followed byassert
,assume
, orexpect
. Each of these makes a claim about the success of the operation (i.e.,assert !r.IsFailure();
,assume !r.IsFailure();
,expect !r.IsFailure();
). When any of these keywords is used, there is no requirement for the failure-compatible type to have aPropagateFailure()
member, since it will not be used. -
Traits and classes that extend traits can have type parameters.
All occurrences of a trait among the ancestor traits of a class (or trait) must have the same type-parameter instantiations.
-
Traits can extend other traits.
-
Permit
object
in list of parent traits. -
Allow assignments
c := t;
where the type oft
is a parent trait of the type ofc
. This gives a way to cast atrait
to aclass
. However, this is not a type test and the verifier will check that the value oft
really does satisfy the type ofc
. -
The
case
s ofmatch
statements/expressions are matched in the order given.This allows overlaps between
case
s, where an earliercase
gets selected first. Acase
pattern can be just a variable (or_
), which in effect is an "else". (Note, this is a backward compatible change, since no overlaps among cases were allowed before.) A warning is generated if onecase
is already covered by the precedingcase
s. -
Patterns in
match
case
s can use simple literal expressions, such as2
,3.14
, andfalse
. -
Add full verification support for body-less loops.
The loop targets of such a loop are the free mutable variables (including the heap) that occur in the guard,
invariant
, ordecreases
clause. If the loop has amodifies
clause--whatever themodifies
clause says--then the heap is counted as a loop target. -
Breaking change: In
export
declarations, members of types are exported separately from exporting the type.- to export a member, the enclosing type must be provided or revealed
- class constructors and mutable fields can be exported only if the enclosing
class
ortrait
is revealed - mutable fields can no longer be revealed, only provided
- datatype constructors, discriminators, and destructors are exported when the datatype is revealed, and can never be explicitly named in exports
- for an extreme predicate/lemma, the corresponding prefix predicate/lemma is exported, too
-
Breaking change: New rules for exporting classes.
To reveal a class or trait
C
will export:- the fact that this is a reference type
- both types
C
andC?
, and the connection between these two - the anonymous constructor, if
C
is a class and has one
To provide a class or trait C will export:
- the name
C
, as an opaque type - not type
C?
- no constructor or mutable field, and it's an error to explicitly name these in exports
Breaking change: One consequence of these rules is that constructor-less
new
is no longer supported for imported classes. -
The syntactic form
import A.B.C
is allowed, and it stands forimport C = A.B.C
. -
import A.B.C`X
is allowed. -
Allow
import opened A
where moduleA
is in the same scope. -
Breaking change:
const
fields are not allowed in back-tick notation in frames -
Allow bitvectors as indices in single-dimensional arrays and sequences (just like they already were allowed for multi-dimensional arrays).
-
Breaking change: Don't allow the use of bitvectors to state the desired length in array allocation and sequence construction.
-
Allow empty types.
-
A subset type or newtype now supports the following witness clauses:
witness E
says thatE
is a compiled expression that is a value of the type- the absence of a witness defaults to
--witness 0
for types based on integers or bitvectors,
--witness 0.0
for real-based types,
--witness {}
for set-based types,
-- etc. ghost witness E
says thatE
is a ghost expression that is a value of the typewitness *
opts out of witness checking; instead, the type is treated as being possibly empty
-
Update meaning of the
(0)
type characteristic. Each type now falls into one of three categories:- Auto-initializing, denoted by the type characteristic
(0)
. The type is known to have a compilable value. This lets variables in compiled contexts be used before initialization. - Nonempty, denoted by the type characteristic
(00)
. The type is known to have at least one possible value. This lets variables in ghost contexts be used before initialization. In compiled contexts, a variable of this type is subject to definite-assignment rules (which means the variable must be assigned before any use). - Possibly empty, denoted by the absence of
(0)
and(00)
. This restricts use of variables of the type, making them subject to definite-assignment rules in both compiled and ghost contexts.
- Auto-initializing, denoted by the type characteristic
-
Introduce map merge operator (syntax
m0 + m1
), whose keys are the union of the keys ofm0
andm1
, and which for the keys inm1
associates the same values as inm1
and for the other keys associates the values ofm0
. Works on bothmap
andimap
, but both arguments must be the same. -
Introduce map domain subtraction operator (syntax
m - s
), which is like mapm
but without any key in sets
. Operandm
can be either amap
orimap
, buts
can only be aset
(not aniset
). -
Breaking change: Don't support map disjointness, that is,
a !! b
is no longer supported whena
andb
are maps. -
Breaking change: Rename
inductive predicate
,copredicate
,inductive lemma
, andcolemma
toleast predicate
,greatest predicate
,least lemma
, andgreatest lemma
, respecitvely. Deprecation warnings are emitted upon coming across the old syntax. In each of the 4 cases, the new syntax is a reserved keyword phrase. However,least
andgreatest
are not reserved keywords by themselves. -
Fix some inconsistencies in the grammar. For example, be more consistent about where digit names and names beginning with an underscore are allowed.
-
Improved name resolution.
- Imported types with the same name are not considered ambiguous if they refer to the same type .
- Allow
C.
to refer to a datatype constructor, but only ifC
does not resolve to anything else. - Allow
import A.B
even withoutimport A
.
-
Breaking change:
{:
is now one token, not two separate tokens. -
Breaking change: Disallow
modify
statements inforall
statements. -
Breaking change: Removed some deprecated features that have been marked as deprecated for a long time.
int(_)
andreal(_)
conversionsparallel
,comethod
,free
, andprotected
keywords- the keywords
array1
andarray1?
- the optional
;
at the end oftype
andimport
declarations
Resolution, type checking, and type inference
-
Include functions in datatype cyclicity checks.
-
Improve auto-ghost declarations of local or bound variables: if the variable is declared with an initializing RHS and the RHS requires the variable to be ghost, then it is made ghost even if the declaration uses
var
(as opposed to usesghost var
). -
At the module scope, prefer local names over opened-imported names.
-
Through constant folding, detect more constant bounds in type declarations. For example, given
const UINT32_LIMIT := 0x1_0000_0000
, the declarationnewtype uint32 = x | 0 <= x < UINT32_LIMIT
is allowed. -
Allow negative literals for integer- and real-based types. More precisely, a
-
token follows by a digits-token is interpreted as a negative literal for integer- and real-based types.case
-patterns inmatch
constructs can use these negative literals. For bitvectors, such a-
is still considered a unary-minus expression. ForORDINAL
, such a-
would be unary minus, butORDINAL
does not support unary minus, so it's an error. -
Allow a refinement module to extend members of all types that can have members, not just classes.
-
Improve type inference for imported modules.
-
Enforce any declared type-characteristics for type synonyms and opaque types.
Verifier
-
Breaking change: The new
/induction:4
option is the default for automatic induction. This new mode applies automatic induction to lemmas, but not to quantifiers. Use the{:induction}
attribute to apply automatic induction to individual quantifiers. -
Improve representation of heap (as a map of map, rather than as 2-dimensional map).
-
Fix refinement checks for two-state predicates.
-
Don't re-verify code that comes from DLLs.
-
Fix to subset checks for sequences.
-
Add bitvector type axioms.
-
Fix termination check for infinite/unknown types.
Compiler
-
Compiler that targets Java. Does not support
iterator
declarations, and does not yet support variance for (co)datatype type parameters. -
Prototype compiler that targets C++.
-
Improved support for and consistency among the compilers that target C#, JavaScript, and Go.
-
Expanded entry points: The main method (named
Main
, marked with attribute{:main}
, or specified using the/Main
command-line option) can be a module method or astatic
or instance method in a class, trait, (co)datatype, or newtypes (provided the type is auto-initializing or a class with noconstructor
declarations). -
Support type-parameter (co- and contra-)variance for collection types (C#, JavaScript, Go, Java), and support type-parameter variance for (co)datatypes (JavaScript and Go). Traits can be used as type parameters (for JavaScript and Go, everywhere; for C# and Java, as non-variant type parameters.)
-
Compile map comprehensions with multiple bound variables.
-
Compile methods with exactly one out-parameter to the usual result-type methods.
-
Optimize
forall
statements into simple loop when possible. -
Compile tail-recursive functions (not just tail-recursive methods) with tail calls. For functions, the compiler has an auto-accumulator feature that automatically detects cases when an added accumulator variable allows the function to be transformed into being tail-recursive. In such detected cases, the compiler introduces the accumulator variable automatically.
-
There's some rudimentary support for collecting branch-coverage information at run time (see
/coverage
command-line option, and seeTest/comp/BranchCoverage.dfy
for an example). -
Add support for
{:test}
attribute (C# only for now), mapping it to[Xunit.Fact]
. This allows xUnit-like unit testing from Dafny. -
Enlarged support of
{:extern}
declarations, improving the foreign-function-interface to other languages. -
Support
{:nativeType XX}
whereXX
is a list of native types. They are considered in order and the first one supported by the compiler is picked. An error is generated if the type cannot be determined to fit in that native type. (See the/attrHelp
message for details.) Also, native type"number"
is no longer supported outside JavaScript. -
Always use .NET immutable collections (previously, these were used only with the
/optimize
flag). Also,/optimize
flag is now permanently on, which causes/optimize
flag to be passed down to the C# compiler. -
Improved formatting of target code
-
Breaking change: A number of features are now represented differently at run time. This may break any previous external code that depended on the old representations. (Here's one hint: If you handwrite target code that creates datatype values, use the
create_
methods, don't callnew
directly.) -
Use run-time type descriptors for C# and Java, as was already done for JavaScript and Go. This adds uniform support for auto-initialization, and removes from Dafny the previous notion of "zero-initializable types".
-
Fix some incorrect uses of
nil
in Go. -
Fix eta-expansion of instance members.
-
Fix name-capture problems in lambdas, quantifiers, set/map comprehensions, and tail-recursive functions/methods.
-
Make
/compile
and/spillTargetCode
command-line options work consistently for the 5 compilers: C#, JavaScript, Go, Java, C++. The/compile
option controls whether or not an executable target is generated, and also controls whether or not to run the program upon successful compilation.- Option
/compile:0
generates no executable target. - Option
/compile:1
generates an executable target that can be run. - Option
/compile:3
compiles and runs the program, but doesn't generate an executable target unless necessary for running.
The
/spillTargetCode
option controls whether or not textual target code is generated. This can be useful if the Dafny program is to be manually compiled with handwritten code in the target language.The test file
Test/comp/compile1quiet/CompileRunQuietly.dfy
shows how to run the executable target generated by Dafny.The test file
Test/comp/manualcompile/ManualCompile.dfy
shows manual compiler calls that can be applied to the textual target code generated. - Option
Documentation
-
Large refresh of the Dafny Language Reference Manual.
-
Some fixes in the rise4fun tutorial.
-
For Dafny contributors, documentation of compilation of trait/class to C#/JavaScript/Go/Java.
IDE
- End support of the
DafnyExtension
, which was the original Dafny IDE that plugged into Microsoft Visual Studio. (VS Code and Emacs plug-ins are still supported.)
Tool
-
Migrate to .NET 5.0.
If you used to use
mono Dafny.exe
to run Dafny before, usedotnet Dafny.dll
now. If you used thedafny
script before, use the updated version with the same name. If you're building from source, you'll find thedafny
script in theScripts
folder, and you may want to copy it to theBinaries
folder. See INSTALL instructions in the wiki. -
Allow and ignore duplicated filenames on the command line and via
include
directives. Such duplicates are determined using case-sensitive canonical path names. -
Improved error message for missing Z3.
-
Deprecate
-countVerificationErrors
command-line option. -
Retire
/ironDafny
and/dafnycc
command-line options.
Implementation
-
Use Boogie v2.8.26.
-
Use Z3 v4.8.5.
-
Pass fewer options to Z3.
-
Use .NET 5.0 version of Coco/R.
-
Various refactorings to improve code structure, especially across the compilers.
Miscellaneous
- Various bug fixes throughout