Open MikaelMayer opened 1 year ago
@atomb @alex-chew @fabiomadge and myself discussed this today. Here is the fruit of our discussion.
In a first phase, we will solve this issue by implementing the following decisions:
{:scoped}
so that it can have suitable antonym and is memorable.by {...}
construct is nice because it enables to state something before proving it. However, we don't have a good solution for the ambiguity resolution with the existing assert ... by {}
.{:split_here}
{:focus}
and {:subsumption 0}
(to not assume an assertion after proving it){:here}
or {:local}
that don't have suitable antonyms are not preferred, but {:scoped}
is suitable, we could have {:unscoped}
for example.After having implementing the decisions, we will be able to consider the following extensions
{:scoped}
on lemmas calls as well.{:scoped-assumptions}
attribute to make assertions scoped by default.--scoped-assumptions true
We will then be able to experiment on our codebase and of selected Dafny users, to determine how much it is desirable and a hassle that scoped assumptions become the default. In the case we consider making scoped assumptions the default (e.g. in Dafny 5),
{:scoped-assumptions false}
{:unscoped}
or {:scoped false}
{:unscoped}
(similar to a function returning a value or the current mechanism of forall .... {}
statements that ensures the last assertion if none provided.{:scoped-assumptions false}
.If we do not consider making it the default:
{:opaque}
became a keyword.Thanks for sharing the notes! Sounds good.
In case you're looking for another opinion about how to continue after doing more experimentation, I'm in favor of the breaking change, but with the option of getting the old behavior using a CLI flag, so existing customers can use that flag for as long as they want, and new customers get the new behavior.
Summary
This feature requests consists in adding a way in Dafny to ensure that some assertions are visible only within a certain scope.
Background and Motivation
Proof durability is one of the major concerns of some of our high-level Dafny customers. Basically, it all boils down to ensure that the verifier is not provided with too many facts that could blow up verification time. One very practical way to reduce the number of facts known to the verifier is to use opaque labelled assertions associated with reveal statements. Unfortunately, this is system is not very robust. Some efforts has been made but problems still remain (#2260, #3719, #3506, #2941, #3805 ...). One of the most emblematic ones is #1382, that indicates that revealing functions inside functions extends past the function definition itself.
We need better mechanisms to make things opaque and not blow up
Context
We cannot just state that assertions don't leak outside of their scope. In Dafny, assertions and reveals inside blocks always "leak" to outer scopes, and for good reasons. It makes it possible to develop appropriate proofs in different assumption contexts.
So, whereas the following works
Which poses the question: Don't we want the same for functions? Since in functions, there is no "after" an if statement, the best equivalent we can think of is an assignment:
And indeed this one works the same. Hence if we want assertions/reveal that are local in their block, we need to either do one of these two things:
In the following alternatives, we are going to only consider at first ways to make assertions in expressions local in their block.
Alternative A: Redesign the semantics of an existing language feature
assert
,reveal
andassume
are only visible to the expressions that they are next to. However, the assumptions of the expressions themselves are still visible from the outside. In a sense, an expression likeassert A; x
is the same as "assume PrecondX; x", + an unrelated proof of "assert A; assume A; assert PrecondX" where PreCondX is the precondition to be able to build X.So for example,
This should be the same for
assume
andassert
StmtExpr.Pro
Cons
Alternative B: New prefix language construct
We could repurpose a keyword so that Alternative A is implemented but only for assert/reveal/assume prefixed with that keyword, e.g.
opaque assert
,opaque reveal
,opaque assume
locally assert
,locally reveal
,locally assume
local assert
,local reveal
,local assume
assert here
,reveal here
,assume here
Pro
Cons
opaque reveal
sounds really weird, no one will guess what it means.Alternative C: Season assert/assume/reveal with attribute
We don't reinvent a keyword for now, but we introduce an attribute..
assert {:local}
,reveal {:local}
,assume {:local}
assert {:locally}
,reveal {:locally}
,assume {:locally}
assert {:here}
,reveal {:here}
,assume {:here}
assert {:scoped}
,reveal {:scoped}
,assume {:scoped}
assert {:opaque}
,reveal {:opaque}
,assume {:opaque}
assert {:only-in-scope}
,reveal {:only-in-scope}
,assume {:only-in-scope}
Pro
{:split_here}
Cons
Alternative D: Expand the
assert
statementWe could invent the following expression and statement
Pro
Cons
for
: Ambiguity in statements if an assert ... by was followed by a for loop.in
: Impossible due to ambiguity with the existing keyword "in"Alternative E. Invent a new "by" construct.
An expression
E
could be affixed withby { Proof }
in which case all assert/assume/reveal in Proof are accessible to prove E, but not outside of the entire expression. It could be prefixed or suffixed:Pro
Cons
assert X by { Proof } E
the visual sub expressionby { Proof } E
could be interpreted visually as an expression on which Proof is visible to E. This is clearly not desirable.assert X by { Proof } E
, does it meanassert (X by { Proof }) E
? If not, That means we would need to forbid the parsing of "by" suffix when parsing the expression X, which is cumbersome both to implement and to understand.Why bothering?
Not having this feature prevents some Dafny customers to scale up their designs and their proofs. Workaround include more refactoring, which is not always practical or desirable, or increasing the number of cores.
Other test cases to consider:
Other alternatives
Other solutions we considered included: