Skip to content

Commit 8351447

Browse files
author
Rustan Leino
committed
Allow datatype updates to update shared destructors
1 parent a28ea5f commit 8351447

10 files changed

+174
-66
lines changed

Source/Dafny/DafnyAst.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3904,21 +3904,27 @@ internal void AddAnotherEnclosingCtor(DatatypeCtor ctor, Formal formal) {
39043904

39053905
internal string EnclosingCtorNames(string grammaticalConjunction) {
39063906
Contract.Requires(grammaticalConjunction != null);
3907-
var n = EnclosingCtors.Count;
3907+
return PrintableCtorNameList(EnclosingCtors, grammaticalConjunction);
3908+
}
3909+
3910+
static internal string PrintableCtorNameList(List<DatatypeCtor> ctors, string grammaticalConjunction) {
3911+
Contract.Requires(ctors != null && ctors.Count != 1);
3912+
Contract.Requires(grammaticalConjunction != null);
3913+
var n = ctors.Count;
39083914
if (n == 1) {
3909-
return string.Format("'{0}'", EnclosingCtors[0].Name);
3915+
return string.Format("'{0}'", ctors[0].Name);
39103916
} else if (n == 2) {
3911-
return string.Format("'{0}' {1} '{2}'", EnclosingCtors[0].Name, grammaticalConjunction, EnclosingCtors[1].Name);
3917+
return string.Format("'{0}' {1} '{2}'", ctors[0].Name, grammaticalConjunction, ctors[1].Name);
39123918
} else {
39133919
var s = "";
39143920
for (int i = 0; i < n - 1; i++) {
3915-
s += string.Format("'{0}', ", EnclosingCtors[i].Name);
3921+
s += string.Format("'{0}', ", ctors[i].Name);
39163922
}
3917-
return s + string.Format("{0} '{1}'", grammaticalConjunction, EnclosingCtors[n - 1].Name);
3923+
return s + string.Format("{0} '{1}'", grammaticalConjunction, ctors[n - 1].Name);
39183924
}
39193925
}
39203926
}
3921-
3927+
39223928
public class ConstantField : SpecialField, ICallable
39233929
{
39243930
public override string WhatKind { get { return "const field"; } }

Source/Dafny/Resolver.cs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10464,10 +10464,10 @@ Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, L
1046410464
// We're currently looking at e = e(n-1)[en.Index := en.Value]
1046510465
// Let e(n-2) = e(n-1).Seq, e(n-3) = e(n-2).Seq, etc.
1046610466
// Let's extract e = e0[e1.Index := e1.Value]...[en.Index := en.Value]
10467-
DatatypeCtor ctor = null;
10468-
Tuple<IToken, string, Expression> ctorSource = default(Tuple<IToken, string, Expression>); // relevant only if "ctor" is non-null
10467+
var possibleCtors = dt.Ctors; // list of constructors that have all the so-far-mentioned destructors
1046910468
var memberNames = new HashSet<string>();
10470-
var updates = new Dictionary<Formal, Expression>();
10469+
var updates = new Dictionary<DatatypeDestructor, Expression>();
10470+
var suppressUniquenessCheck = false;
1047110471
foreach (var entry in memberUpdates) {
1047210472
var destructor_str = entry.Item2;
1047310473
if (memberNames.Contains(destructor_str)) {
@@ -10477,35 +10477,51 @@ Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, L
1047710477
MemberDecl member;
1047810478
if (!datatypeMembers[dt].TryGetValue(destructor_str, out member)) {
1047910479
reporter.Error(MessageSource.Resolver, entry.Item1, "member '{0}' does not exist in datatype '{1}'", destructor_str, dt.Name);
10480+
suppressUniquenessCheck = true;
1048010481
} else if (!(member is DatatypeDestructor)) {
1048110482
reporter.Error(MessageSource.Resolver, entry.Item1, "member '{0}' is not a destructor in datatype '{1}'", destructor_str, dt.Name);
10483+
suppressUniquenessCheck = true;
1048210484
} else {
1048310485
var destructor = (DatatypeDestructor)member;
10484-
if (destructor.EnclosingCtors.Count > 1) {
10485-
// Note: This restriction could be relaxed. For example, thing would still be okay if the intersection of constructors of
10486-
// the indicated memberUpdates names indicate a unique constructor. In addition, the syntax could be extended to allow an
10487-
// update member to have the form ctor.x:=E where ctor would be used to disambiguate which constructor the x is supposed to
10488-
// be looked up in.
10489-
reporter.Error(MessageSource.Resolver, entry.Item1, "datatype member update is supported only for uniquely named destructors " +
10490-
"('{0}' belongs to '{1}')", entry.Item2, destructor.EnclosingCtorNames("and"));
10491-
} else if (ctor != null && ctor != destructor.EnclosingCtors[0]) {
10492-
reporter.Error(MessageSource.Resolver, entry.Item1, "updated datatype members must belong to the same constructor " +
10493-
"('{0}' belongs to '{1}' and '{2}' belongs to '{3}')", entry.Item2, destructor.EnclosingCtors[0].Name, ctorSource.Item2, ctor.Name);
10486+
var intersection = new List<DatatypeCtor>(possibleCtors.Intersect(destructor.EnclosingCtors));
10487+
if (intersection.Count != 0) {
10488+
possibleCtors = intersection;
10489+
updates.Add(destructor, entry.Item3);
1049410490
} else {
10495-
updates.Add(destructor.CorrespondingFormals[0], entry.Item3);
10496-
ctor = destructor.EnclosingCtors[0];
10497-
ctorSource = entry;
10491+
reporter.Error(MessageSource.Resolver, entry.Item1, "updated datatype members must belong to the same constructor " +
10492+
"(unlike the previously mentioned destructors, '{0}' does not belong to {1})", entry.Item2, DatatypeDestructor.PrintableCtorNameList(possibleCtors, "or"));
10493+
suppressUniquenessCheck = true;
1049810494
}
1049910495
}
1050010496
}
1050110497
}
1050210498

10503-
if (ctor != null) {
10499+
if (possibleCtors.Count != 1) {
10500+
if (!suppressUniquenessCheck) {
10501+
var which = DatatypeDestructor.PrintableCtorNameList(possibleCtors, "and");
10502+
string explanation;
10503+
if (memberUpdates.Count == 1) {
10504+
explanation = string.Format("destructor '{0}' belongs to constructors {1}", memberUpdates[0].Item2, which);
10505+
} else {
10506+
explanation = string.Format("the given destructors all belong to constructors {0}", which);
10507+
}
10508+
reporter.Error(MessageSource.Resolver, tok, "the updated datatype members must uniquely determine the resulting constructor ({0})", explanation);
10509+
}
10510+
} else {
10511+
var ctor = possibleCtors[0];
1050410512
// Rewrite an update of the form "dt[dtor := E]" to be "let d' := dt in dtCtr(E, d'.dtor2, d'.dtor3,...)"
1050510513
// Wrapping it in a let expr avoids exponential growth in the size of the expression
1050610514
// More generally, rewrite "E0[dtor1 := E1][dtor2 := E2]...[dtorn := En]" where "E0" is "root" to
1050710515
// "let d' := E0 in dtCtr(...mixtures of Ek and d'.dtorj...)"
1050810516

10517+
// Now that we know which constructor we're talking about, we can pick out the formal corresponding to each destructor
10518+
var fUpdates = new Dictionary<Formal, Expression>();
10519+
foreach (var entry in updates) {
10520+
var i = entry.Key.EnclosingCtors.IndexOf(ctor);
10521+
Contract.Assert(0 <= i && i < entry.Key.CorrespondingFormals.Count);
10522+
fUpdates.Add(entry.Key.CorrespondingFormals[i], entry.Value);
10523+
}
10524+
1050910525
// Create a unique name for d', the variable we introduce in the let expression
1051010526
var tmpName = FreshTempVarName("dt_update_tmp#", opts.codeContext);
1051110527
var tmpVarIdExpr = new IdentifierExpr(new AutoGeneratedToken(tok), tmpName);
@@ -10515,7 +10531,7 @@ Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, L
1051510531
var ctor_args = new List<Expression>();
1051610532
foreach (Formal d in ctor.Formals) {
1051710533
Expression v;
10518-
if (updates.TryGetValue(d, out v)) {
10534+
if (fUpdates.TryGetValue(d, out v)) {
1051910535
ctor_args.Add(v);
1052010536
} else {
1052110537
ctor_args.Add(new ExprDotName(tok, tmpVarIdExpr, d.Name, null));

Test/allocated1/dafny0/DatatypeUpdateResolution.dfy.expect

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ DatatypeUpdateResolution.dfy(3,8): Error: the included file DatatypeUpdateResolu
22
DatatypeUpdateResolution.dfy(12,22): Error: member 'non_destructor' does not exist in datatype 'MyDataType'
33
DatatypeUpdateResolution.dfy(13,38): Error: member '40' does not exist in datatype 'MyDataType'
44
DatatypeUpdateResolution.dfy(17,61): Error: duplicate update member 'otherbool'
5-
DatatypeUpdateResolution.dfy(19,23): Error: updated datatype members must belong to the same constructor ('otherbool' belongs to 'MyOtherConstructor' and '42' belongs to 'MyNumericConstructor'
6-
5 resolution/type errors detected in DatatypeUpdateResolution.dfy
5+
DatatypeUpdateResolution.dfy(19,23): Error: updated datatype members must belong to the same constructor (unlike the previously mentioned destructors, 'otherbool' does not belong to 'MyNumericConstructor')
6+
DatatypeUpdateResolution.dfy(28,10): Error: member 'Make?' is not a destructor in datatype 'Dt'
7+
6 resolution/type errors detected in DatatypeUpdateResolution.dfy
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
DatatypeUpdateResolution.dfy(12,22): Error: member 'non_destructor' does not exist in datatype 'MyDataType'
22
DatatypeUpdateResolution.dfy(13,38): Error: member '40' does not exist in datatype 'MyDataType'
33
DatatypeUpdateResolution.dfy(17,61): Error: duplicate update member 'otherbool'
4-
DatatypeUpdateResolution.dfy(19,23): Error: updated datatype members must belong to the same constructor ('otherbool' belongs to 'MyOtherConstructor' and '42' belongs to 'MyNumericConstructor')
4+
DatatypeUpdateResolution.dfy(19,23): Error: updated datatype members must belong to the same constructor (unlike the previously mentioned destructors, 'otherbool' does not belong to 'MyNumericConstructor')
55
DatatypeUpdateResolution.dfy(28,10): Error: member 'Make?' is not a destructor in datatype 'Dt'
66
5 resolution/type errors detected in DatatypeUpdateResolution.dfy

Test/dafny0/SharedDestructors.dfy

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ method M(d: Dt) returns (r: int, s: real)
3030
var h := d.h; // error: d might not have a member .h (1 name in error msg)
3131
case d.B? =>
3232
var h := d.h;
33+
case true =>
34+
var n := d.(y := 2.7, x := 3);
35+
assert n.A?;
3336
}
3437

3538
datatype Record = Record(t: int)
@@ -59,19 +62,21 @@ datatype Quirky = PP(id: int, a: int) | QQ(b: int, id: int) | RR(id: int, c: int
5962

6063
method UpdateA(y: Quirky) returns (y': Quirky)
6164
{
62-
// The semantics of datatype field update is somewhat quirky. It's perhaps a design
63-
// bug, but here is what it requires and what it does:
64-
// - Each field given must be a non-shared destructor. That is, each field mentioned
65-
// must be a destructor in a unique constructor. (An alternative to this design
66-
// would be to just insist that the intersection of the constructors that contain
67-
// the given fields must be a single constructor.)
68-
// This unique constructor is the constructor that will be used for the result.
69-
// - Every field "g" of the chosen constructor must either be given in the update
70-
// expression or be available in the source expression. This does not necessarily
71-
// mean that the source expression must already be of the chosen constructor; it
72-
// suffices that every such "g" is available in all of the constructors. A special
73-
// case of this is that there is no "g", in which case the value of the source
74-
// expression does not matter at all.
65+
// Datatype field update works as follows:
66+
// 0- The fields given in the update must uniquely determine a constructor.
67+
// The result will be of this constructor.
68+
// 1- For any pair "f := E" given in the update, the field "f" of the result will
69+
// have the value "E".
70+
// 2- For any field "f" that is not given in the update "E.(x := X, y := Y, ...)",
71+
// "E" must be a value with an "f" field and the value of "f" in the result will
72+
// be "E.f".
73+
// Note, in point 2, the requirement that "E" have an "f" field is satisfied
74+
// if "E" is constructed by the same constructor as is determined in point 0.
75+
// However, it is also possible to satisfy this requirement if "f" is a shared
76+
// destructor and "E" is of a constructor that has that shared field.
77+
// Note also, that a degenerate case of the update expression is that all fields
78+
// of the by-point-0 determined constructor are given explicitly. In that case,
79+
// the source of the update (that is, the "E" in "E.(...)") can be any value.
7580
if
7681
case true =>
7782
// make a PP, get .id from anywhere
@@ -89,3 +94,20 @@ method UpdateA(y: Quirky) returns (y': Quirky)
8994
// make an RR, but where does .d come from?
9095
y' := y.(c := 10); // error: this would require y to be an RR
9196
}
97+
98+
datatype Klef =
99+
| C0(0: int, 1: int, 2: int, c0: int)
100+
| C1(1: int, 2: int, 3: int, c1: int)
101+
| C2(2: int, 3: int, 0: int, c2: int)
102+
| C3(3: int, 0: int, 1: int, c3: int)
103+
104+
method TroubleKlef(k: Klef) returns (k': Klef)
105+
ensures k'.C3?
106+
{
107+
if
108+
case k.C1? || k.C3? =>
109+
k' := k.(0 := 100, c3 := 200); // makes a C3
110+
assert k' == C3(k.3, 100, k.1, 200);
111+
case true =>
112+
k' := k.(0 := 100, c3 := 200); // error (x2): k might not have .1 and .3
113+
}
Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
SharedDestructors.dfy(16,11): Error: destructor 'x' can only be applied to datatype values constructed by 'A' or 'B'
22
Execution trace:
33
(0,0): anon0
4-
(0,0): anon13_Then
4+
(0,0): anon14_Then
55
SharedDestructors.dfy(22,11): Error: destructor 'x' can only be applied to datatype values constructed by 'A' or 'B'
66
Execution trace:
77
(0,0): anon0
8-
(0,0): anon16_Then
8+
(0,0): anon17_Then
99
SharedDestructors.dfy(24,11): Error: destructor 'y' can only be applied to datatype values constructed by 'A', 'C', or 'D'
1010
Execution trace:
1111
(0,0): anon0
12-
(0,0): anon17_Then
12+
(0,0): anon18_Then
1313
SharedDestructors.dfy(30,15): Error: destructor 'h' can only be applied to datatype values constructed by 'B'
1414
Execution trace:
1515
(0,0): anon0
16-
(0,0): anon21_Then
17-
SharedDestructors.dfy(48,14): Error: destructor 'a' can only be applied to datatype values constructed by 'AA'
16+
(0,0): anon22_Then
17+
SharedDestructors.dfy(51,14): Error: destructor 'a' can only be applied to datatype values constructed by 'AA'
1818
Execution trace:
1919
(0,0): anon0
2020
(0,0): anon8_Else
2121
(0,0): anon9_Then
22-
SharedDestructors.dfy(54,14): Error: destructor 'c' can only be applied to datatype values constructed by 'AA'
22+
SharedDestructors.dfy(57,14): Error: destructor 'c' can only be applied to datatype values constructed by 'AA'
2323
Execution trace:
2424
(0,0): anon0
2525
(0,0): anon8_Else
2626
(0,0): anon9_Else
2727
(0,0): anon10_Else
28-
SharedDestructors.dfy(90,12): Error: destructor 'd' can only be applied to datatype values constructed by 'RR'
28+
SharedDestructors.dfy(95,12): Error: destructor 'd' can only be applied to datatype values constructed by 'RR'
2929
Execution trace:
3030
(0,0): anon0
3131
(0,0): anon11_Then
32+
SharedDestructors.dfy(112,12): Error: destructor '1' can only be applied to datatype values constructed by 'C0', 'C1', or 'C3'
33+
Execution trace:
34+
(0,0): anon0
35+
(0,0): anon8_Then
36+
SharedDestructors.dfy(112,12): Error: destructor '3' can only be applied to datatype values constructed by 'C1', 'C2', or 'C3'
37+
Execution trace:
38+
(0,0): anon0
39+
(0,0): anon8_Then
3240

33-
Dafny program verifier finished with 0 verified, 7 errors
41+
Dafny program verifier finished with 0 verified, 9 errors

Test/dafny0/SharedDestructorsCompile.dfy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,20 @@ method Main()
3232
assert d.x == 71;
3333
i := i + 1;
3434
}
35+
36+
BaseKlef(C1(44, 55, 66, 77));
37+
}
38+
39+
datatype Klef =
40+
| C0(0: int, 1: int, 2: int, c0: int)
41+
| C1(1: int, 2: int, 3: int, c1: int)
42+
| C2(2: int, 3: int, 0: int, c2: int)
43+
| C3(3: int, 0: int, 1: int, c3: int)
44+
45+
method BaseKlef(k: Klef)
46+
requires !k.C0? && !k.C2?
47+
{
48+
var k' := k.(0 := 100, c3 := 200); // makes a C3
49+
assert k' == C3(k.3, 100, k.1, 200);
50+
print k', "\n";
3551
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
Dafny program verifier finished with 1 verified, 0 errors
2+
Dafny program verifier finished with 2 verified, 0 errors
33
Program compiled successfully
44
Running...
55

@@ -8,3 +8,4 @@ Dt.B(__default.MyClass, 6): h=__default.MyClass x=6
88
Dt.C((314.0 / 100.0)): y=(314.0 / 100.0)
99
Dt.C((314.0 / 100.0))
1010
Dt.A(71, (1.0 / 10.0))
11+
Klef.C3(66, 100, 44, 200)

Test/dafny0/SharedDestructorsResolution.dfy

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,37 @@ module Module0 {
1313
method Update(d: Dt) returns (d': Dt)
1414
{
1515
if d.A? {
16-
d' := d.(x := 6); // error: the destructor to be updated must be uniquely named
16+
d' := d.(x := 6); // error: this destructor alone does not uniquely determine a resulting constructor
1717
} else if d.B? {
1818
d' := d.(h := null);
1919
} else {
20-
d' := d.(y := 0.0); // error: the destructor to be updated must be uniquely named
20+
d' := d.(y := 0.0); // error: this destructor alone does not uniquely determine a resulting constructor
2121
}
22+
d' := d.(x := 100, h := null); // yes, this does unique determine the constructor
23+
}
24+
25+
datatype Klef =
26+
| C0(0: int, 1: int, 2: int, c0: int)
27+
| C1(1: int, 2: int, 3: int, c1: int)
28+
| C2(2: int, 3: int, 0: int, c2: int)
29+
| C3(3: int, 0: int, 1: int, c3: int)
30+
31+
method UK(k: Klef, x: int) returns (k': Klef)
32+
{
33+
k' := k.(2 := x, 3 := x, 0 := x); // this makes a C2
34+
k' := k.(0 := x, 1 := x); // error: ambiguous
35+
k' := k.(c0 := x, 3 := x); // error: no constructor has both "c0" and "3"
36+
k' := k.(3 := x, c0 := x); // error: ditto
37+
k' := k.(3 := x, 2 := x, c0 := x); // error: no constructor has all these destructors
38+
k' := k.(3 := x, 2 := x, 1 := x, c0 := x); // error: no constructor has all these destructors
39+
k' := k.(3 := x, 1 := x, 0 := x); // this makes a C3
40+
k' := k.(1 := x, c3 := x); // this makes a C3
41+
// multiples errors:
42+
k' := k.(C0? := x); // error: C0? is not a destructor
43+
k' := k.(C0? := x, c0 := x); // error: ditto
44+
k' := k.(c0 := x, c0 := x, c2 := x); // error (2x): c0 duplicate, c2 never with c0
45+
k' := k.(c0 := x, c1 := x, c2 := x); // error (2x): c1 and c2 never with c0
46+
k' := k.(0 := x, 0 := x, 1 := x); // error (2x): c0 duplicate, ambiguous C0 or C3
2247
}
2348
}
2449

0 commit comments

Comments
 (0)