Skip to content

Commit a28ea5f

Browse files
author
Rustan Leino
committed
Allow sharing of names of destructors among the constructors of a (co)datatype.
1 parent b0ef025 commit a28ea5f

12 files changed

+421
-44
lines changed

Source/Dafny/Compiler.cs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,14 @@ void CompileDatatypeStruct(DatatypeDecl dt, int indent, TextWriter wr) {
566566
// public bool is_Ctor0 { get { return _D is Dt_Ctor0; } }
567567
// ...
568568
//
569-
// public T0 dtor_Dtor0 { get { return ((DT_Ctor)_D).@Dtor0; } }
569+
// public T0 dtor_Dtor0 { get { return ((DT_Ctor)_D).@Dtor0; } } // This is in essence what gets generated for the case where the destructor is used in one use constructor
570+
// public T0 dtor_Dtor0 { get { var d = _D; // This is the general case
571+
// if (d is DT_Ctor0) { return ((DT_Ctor0)d).@Dtor0; }
572+
// if (d is DT_Ctor1) { return ((DT_Ctor1)d).@Dtor0; }
573+
// ...
574+
// if (d is DT_Ctor(n-2)) { return ((DT_Ctor(n-2))d).@Dtor0; }
575+
// return ((DT_Ctor(n-1))d).@Dtor0;
576+
// }}
570577
// ...
571578
// }
572579
string DtT = dt.CompileName;
@@ -683,11 +690,28 @@ void CompileDatatypeStruct(DatatypeDecl dt, int indent, TextWriter wr) {
683690

684691
// destructors
685692
foreach (var ctor in dt.Ctors) {
686-
foreach (var arg in ctor.Formals) {
687-
if (!arg.IsGhost && arg.HasName) {
688-
// public T0 @Dtor0 { get { return ((DT_Ctor)_D).@Dtor0; } }
689-
Indent(ind, wr);
690-
wr.WriteLine("public {0} dtor_{1} {{ get {{ return (({2}_{3}{4})_D).@{1}; }} }}", TypeName(arg.Type, wr), arg.CompileName, dt.CompileName, ctor.CompileName, DtT_TypeArgs);
693+
foreach (var dtor in ctor.Destructors) {
694+
if (dtor.EnclosingCtors[0] == ctor) {
695+
var arg = dtor.CorrespondingFormals[0];
696+
if (!arg.IsGhost && arg.HasName) {
697+
Indent(ind, wr);
698+
// public T0 dtor_Dtor0 { get { var d = _D;
699+
// if (d is DT_Ctor0) { return ((DT_Ctor0)d).@Dtor0; }
700+
// if (d is DT_Ctor1) { return ((DT_Ctor1)d).@Dtor0; }
701+
// ...
702+
// if (d is DT_Ctor(n-2)) { return ((DT_Ctor(n-2))d).@Dtor0; }
703+
// return ((DT_Ctor(n-1))d).@Dtor0;
704+
// }}
705+
wr.Write("public {0} dtor_{1} {{ get {{ var d = _D; ", TypeName(arg.Type, wr), arg.CompileName);
706+
var n = dtor.EnclosingCtors.Count;
707+
for (int i = 0; i < n-1; i++) {
708+
var ctor_i = dtor.EnclosingCtors[i];
709+
Contract.Assert(arg.CompileName == dtor.CorrespondingFormals[i].CompileName);
710+
wr.Write("if (d is {0}_{1}{2}) {{ return (({0}_{1}{2})d).@{3}; }} ", dt.CompileName, ctor_i.CompileName, DtT_TypeArgs, arg.CompileName);
711+
}
712+
Contract.Assert(arg.CompileName == dtor.CorrespondingFormals[n-1].CompileName);
713+
wr.WriteLine("return (({0}_{1}{2})d).@{3}; }} }}", dt.CompileName, dtor.EnclosingCtors[n-1].CompileName, DtT_TypeArgs, arg.CompileName);
714+
}
691715
}
692716
}
693717
}

Source/Dafny/DafnyAst.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3866,8 +3866,15 @@ public override string FullCompileName {
38663866

38673867
public class DatatypeDestructor : SpecialField
38683868
{
3869-
public readonly DatatypeCtor EnclosingCtor;
3870-
public readonly Formal CorrespondingFormal;
3869+
public readonly List<DatatypeCtor> EnclosingCtors = new List<DatatypeCtor>(); // is always a nonempty list
3870+
public readonly List<Formal> CorrespondingFormals = new List<Formal>(); // is always a nonempty list
3871+
[ContractInvariantMethod]
3872+
void ObjectInvariant() {
3873+
Contract.Invariant(EnclosingCtors != null);
3874+
Contract.Invariant(CorrespondingFormals != null);
3875+
Contract.Invariant(EnclosingCtors.Count > 0);
3876+
Contract.Invariant(EnclosingCtors.Count == CorrespondingFormals.Count);
3877+
}
38713878

38723879
public DatatypeDestructor(IToken tok, DatatypeCtor enclosingCtor, Formal correspondingFormal, string name, string compiledName, string preString, string postString, bool isGhost, Type type, Attributes attributes)
38733880
: base(tok, name, compiledName, preString, postString, isGhost, false, false, type, attributes)
@@ -3880,8 +3887,35 @@ public DatatypeDestructor(IToken tok, DatatypeCtor enclosingCtor, Formal corresp
38803887
Contract.Requires(preString != null);
38813888
Contract.Requires(postString != null);
38823889
Contract.Requires(type != null);
3883-
EnclosingCtor = enclosingCtor;
3884-
CorrespondingFormal = correspondingFormal;
3890+
EnclosingCtors.Add(enclosingCtor); // more enclosing constructors may be added later during resolution
3891+
CorrespondingFormals.Add(correspondingFormal); // more corresponding formals may be added later during resolution
3892+
}
3893+
3894+
/// <summary>
3895+
/// To be called only by the resolver. Called to share this datatype destructor between multiple constructors
3896+
/// of the same datatype.
3897+
/// </summary>
3898+
internal void AddAnotherEnclosingCtor(DatatypeCtor ctor, Formal formal) {
3899+
Contract.Requires(ctor != null);
3900+
Contract.Requires(formal != null);
3901+
EnclosingCtors.Add(ctor); // more enclosing constructors may be added later during resolution
3902+
CorrespondingFormals.Add(formal); // more corresponding formals may be added later during resolution
3903+
}
3904+
3905+
internal string EnclosingCtorNames(string grammaticalConjunction) {
3906+
Contract.Requires(grammaticalConjunction != null);
3907+
var n = EnclosingCtors.Count;
3908+
if (n == 1) {
3909+
return string.Format("'{0}'", EnclosingCtors[0].Name);
3910+
} else if (n == 2) {
3911+
return string.Format("'{0}' {1} '{2}'", EnclosingCtors[0].Name, grammaticalConjunction, EnclosingCtors[1].Name);
3912+
} else {
3913+
var s = "";
3914+
for (int i = 0; i < n - 1; i++) {
3915+
s += string.Format("'{0}', ", EnclosingCtors[i].Name);
3916+
}
3917+
return s + string.Format("{0} '{1}'", grammaticalConjunction, EnclosingCtors[n - 1].Name);
3918+
}
38853919
}
38863920
}
38873921

Source/Dafny/Resolver.cs

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,17 +1732,37 @@ ModuleSignature RegisterTopLevelDecls(ModuleDefinition moduleDef, bool useImport
17321732
}
17331733
// add deconstructors now (that is, after the query methods have been added)
17341734
foreach (DatatypeCtor ctor in dt.Ctors) {
1735+
var formalsUsedInThisCtor = new HashSet<string>();
17351736
foreach (var formal in ctor.Formals) {
1736-
bool nameError = false;
1737-
if (formal.HasName && members.ContainsKey(formal.Name)) {
1738-
reporter.Error(MessageSource.Resolver, ctor, "Name of deconstructor is used by another member of the datatype: {0}", formal.Name);
1739-
nameError = true;
1737+
MemberDecl previousMember = null;
1738+
var localDuplicate = false;
1739+
if (formal.HasName) {
1740+
if (members.TryGetValue(formal.Name, out previousMember)) {
1741+
localDuplicate = formalsUsedInThisCtor.Contains(formal.Name);
1742+
if (localDuplicate) {
1743+
reporter.Error(MessageSource.Resolver, ctor, "Duplicate use of deconstructor name in the same constructor: {0}", formal.Name);
1744+
} else if (previousMember is DatatypeDestructor) {
1745+
// this is okay, if the destructor has the appropriate type; this will be checked later, after type checking
1746+
} else {
1747+
reporter.Error(MessageSource.Resolver, ctor, "Name of deconstructor is used by another member of the datatype: {0}", formal.Name);
1748+
}
1749+
}
1750+
formalsUsedInThisCtor.Add(formal.Name);
17401751
}
1741-
var dtor = new DatatypeDestructor(formal.tok, ctor, formal, formal.Name, "dtor_" + formal.CompileName, "", "", formal.IsGhost, formal.Type, null);
1742-
dtor.InheritVisibility(dt);
1743-
dtor.EnclosingClass = dt; // resolve here
1744-
if (!nameError && formal.HasName) {
1745-
members.Add(formal.Name, dtor);
1752+
DatatypeDestructor dtor;
1753+
if (!localDuplicate && previousMember is DatatypeDestructor) {
1754+
// a destructor with this name already existed in (a different constructor in) the datatype
1755+
dtor = (DatatypeDestructor)previousMember;
1756+
dtor.AddAnotherEnclosingCtor(ctor, formal);
1757+
} else {
1758+
// either the destructor has no explicit name, or this constructor declared another destructor with this name, or no previous destructor had this name
1759+
dtor = new DatatypeDestructor(formal.tok, ctor, formal, formal.Name, "dtor_" + formal.CompileName, "", "", formal.IsGhost, formal.Type, null);
1760+
dtor.InheritVisibility(dt);
1761+
dtor.EnclosingClass = dt; // resolve here
1762+
if (formal.HasName && !localDuplicate && previousMember == null) {
1763+
// the destructor has an explict name and there was no member at all with this name before
1764+
members.Add(formal.Name, dtor);
1765+
}
17461766
}
17471767
ctor.Destructors.Add(dtor);
17481768
}
@@ -2119,6 +2139,7 @@ public void ResolveTopLevelDecls_Core(List<TopLevelDecl/*!*/>/*!*/ declarations,
21192139
// ---------------------------------- Pass 1 ----------------------------------
21202140
// This pass:
21212141
// * checks that type inference was able to determine all types
2142+
// * check that shared destructors in datatypes are in agreement
21222143
// * fills in the .ResolvedOp field of binary expressions
21232144
// * discovers bounds for:
21242145
// - forall statements
@@ -2140,6 +2161,7 @@ public void ResolveTopLevelDecls_Core(List<TopLevelDecl/*!*/>/*!*/ declarations,
21402161
}
21412162
if (reporter.Count(ErrorLevel.Error) == prevErrorCount) {
21422163
// Check that type inference went well everywhere; this will also fill in the .ResolvedOp field in binary expressions
2164+
// Also, for each datatype, check that shared destructors are in agreement
21432165
foreach (TopLevelDecl d in declarations) {
21442166
if (d is IteratorDecl) {
21452167
var iter = (IteratorDecl)d;
@@ -2191,6 +2213,28 @@ public void ResolveTopLevelDecls_Core(List<TopLevelDecl/*!*/>/*!*/ declarations,
21912213
CheckExpression(dd.Constraint, this, dd);
21922214
}
21932215
FigureOutNativeType(dd, nativeTypeMap);
2216+
} else if (d is DatatypeDecl) {
2217+
var dd = (DatatypeDecl)d;
2218+
foreach (var member in datatypeMembers[dd].Values) {
2219+
var dtor = member as DatatypeDestructor;
2220+
if (dtor != null) {
2221+
var rolemodel = dtor.CorrespondingFormals[0];
2222+
for (int i = 1; i < dtor.CorrespondingFormals.Count; i++) {
2223+
var other = dtor.CorrespondingFormals[i];
2224+
if (!rolemodel.Type.ExactlyEquals(other.Type)) {
2225+
reporter.Error(MessageSource.Resolver, other,
2226+
"shared destructors must have the same type, but '{0}' has type '{1}' in constructor '{2}' and type '{3}' in constructor '{4}'",
2227+
rolemodel.Name, rolemodel.Type, dtor.EnclosingCtors[0].Name, other.Type, dtor.EnclosingCtors[i].Name);
2228+
} else if (rolemodel.IsGhost != other.IsGhost) {
2229+
reporter.Error(MessageSource.Resolver, other,
2230+
"shared destructors must agree on whether or not they are ghost, but '{0}' is {1} in constructor '{2}' and {3} in constructor '{4}'",
2231+
rolemodel.Name,
2232+
rolemodel.IsGhost ? "ghost" : "non-ghost", dtor.EnclosingCtors[0].Name,
2233+
other.IsGhost ? "ghost" : "non-ghost", dtor.EnclosingCtors[i].Name);
2234+
}
2235+
}
2236+
}
2237+
}
21942238
}
21952239
}
21962240
}
@@ -9885,7 +9929,7 @@ public void ResolveExpressionX(Expression expr, ResolveOpts opts) {
98859929
if (!ty.IsDatatype) {
98869930
reporter.Error(MessageSource.Resolver, expr, "datatype update expression requires a root expression of a datatype (got {0})", ty);
98879931
} else {
9888-
var let = ResolveDatatypeUpdate(expr.tok, e.Root, ty.AsDatatype, e.Updates, false, opts);
9932+
var let = ResolveDatatypeUpdate(expr.tok, e.Root, ty.AsDatatype, e.Updates, opts);
98899933
if (let != null) {
98909934
e.ResolvedExpression = let;
98919935
expr.Type = ty;
@@ -10409,7 +10453,7 @@ private void AddXConstraint(IToken tok, string constraintName, Type type, Expres
1040910453
/// <summary>
1041010454
/// Attempts to produce a let expression from the given datatype updates. Returns that let expression upon success, and null otherwise.
1041110455
/// </summary>
10412-
Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, List<Tuple<IToken, string, Expression>> memberUpdates, bool sequentialUpdates, ResolveOpts opts) {
10456+
Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, List<Tuple<IToken, string, Expression>> memberUpdates, ResolveOpts opts) {
1041310457
Contract.Requires(tok != null);
1041410458
Contract.Requires(root != null);
1041510459
Contract.Requires(dt != null);
@@ -10427,28 +10471,29 @@ Expression ResolveDatatypeUpdate(IToken tok, Expression root, DatatypeDecl dt, L
1042710471
foreach (var entry in memberUpdates) {
1042810472
var destructor_str = entry.Item2;
1042910473
if (memberNames.Contains(destructor_str)) {
10430-
if (sequentialUpdates) {
10431-
reporter.Warning(MessageSource.Resolver, entry.Item1, "update to '{0}' overwritten by another update to '{0}'", destructor_str);
10432-
} else {
10433-
reporter.Error(MessageSource.Resolver, entry.Item1, "duplicate update member '{0}'", destructor_str);
10434-
}
10474+
reporter.Error(MessageSource.Resolver, entry.Item1, "duplicate update member '{0}'", destructor_str);
1043510475
} else {
1043610476
memberNames.Add(destructor_str);
1043710477
MemberDecl member;
1043810478
if (!datatypeMembers[dt].TryGetValue(destructor_str, out member)) {
1043910479
reporter.Error(MessageSource.Resolver, entry.Item1, "member '{0}' does not exist in datatype '{1}'", destructor_str, dt.Name);
10480+
} else if (!(member is DatatypeDestructor)) {
10481+
reporter.Error(MessageSource.Resolver, entry.Item1, "member '{0}' is not a destructor in datatype '{1}'", destructor_str, dt.Name);
1044010482
} else {
1044110483
var destructor = (DatatypeDestructor)member;
10442-
if (ctor != null && ctor != destructor.EnclosingCtor) {
10443-
if (sequentialUpdates) {
10444-
// This would eventually give rise to a verification error. However, since the 'sequentialUpdates' case is being depricated,
10445-
// we don't mind giving resolution error about this now.
10446-
}
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]) {
1044710492
reporter.Error(MessageSource.Resolver, entry.Item1, "updated datatype members must belong to the same constructor " +
10448-
"('{0}' belongs to '{1}' and '{2}' belongs to '{3}'", entry.Item2, destructor.EnclosingCtor.Name, ctorSource.Item2, ctor.Name);
10493+
"('{0}' belongs to '{1}' and '{2}' belongs to '{3}')", entry.Item2, destructor.EnclosingCtors[0].Name, ctorSource.Item2, ctor.Name);
1044910494
} else {
10450-
updates.Add(destructor.CorrespondingFormal, entry.Item3);
10451-
ctor = destructor.EnclosingCtor;
10495+
updates.Add(destructor.CorrespondingFormals[0], entry.Item3);
10496+
ctor = destructor.EnclosingCtors[0];
1045210497
ctorSource = entry;
1045310498
}
1045410499
}

Source/Dafny/Translator.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,9 +1339,12 @@ void AddDatatype(DatatypeDecl dt) {
13391339
var sf = ctor.Destructors[i];
13401340
Contract.Assert(sf != null);
13411341
fn = GetReadonlyField(sf);
1342-
if (fn != predef.Tuple2Destructors0 && fn != predef.Tuple2Destructors1) {
1342+
if (fn == predef.Tuple2Destructors0 || fn == predef.Tuple2Destructors1) {
13431343
// the two destructors for 2-tuples are predefined in Prelude for use
13441344
// by the Map#Items axiom
1345+
} else if (sf.EnclosingCtors[0] != ctor) {
1346+
// this special field, which comes from a shared destructor, is being declared in a different iteration of this loop
1347+
} else {
13451348
sink.AddTopLevelDeclaration(fn);
13461349
}
13471350
// axiom (forall params :: ##dt.ctor#i(#dt.ctor(params)) == params_i);
@@ -5349,9 +5352,10 @@ Bpl.Expr CanCallAssumption(Expression expr, ExpressionTranslator etran) {
53495352
var r = CanCallAssumption(e.Obj, etran);
53505353
if (e.Member is DatatypeDestructor) {
53515354
var dtor = (DatatypeDestructor)e.Member;
5352-
if (dtor.EnclosingCtor.EnclosingDatatype.Ctors.Count == 1) {
5353-
var correctConstructor = FunctionCall(e.tok, dtor.EnclosingCtor.QueryField.FullSanitizedName, Bpl.Type.Bool, etran.TrExpr(e.Obj));
5354-
// There is only one constructor, so the value must be been constructed by it; might as well assume that here.
5355+
if (dtor.EnclosingCtors.Count == dtor.EnclosingCtors[0].EnclosingDatatype.Ctors.Count) {
5356+
// Every constructor has this destructor; might as well assume that here.
5357+
var correctConstructor = BplOr(dtor.EnclosingCtors.ConvertAll(
5358+
ctor => FunctionCall(e.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, etran.TrExpr(e.Obj))));
53555359
r = BplAnd(r, correctConstructor);
53565360
}
53575361
}
@@ -5949,13 +5953,14 @@ void CheckWellformedWithResult(Expression expr, WFOptions options, Bpl.Expr resu
59495953
}
59505954
} else if (e.Member is DatatypeDestructor) {
59515955
var dtor = (DatatypeDestructor)e.Member;
5952-
var correctConstructor = FunctionCall(e.tok, dtor.EnclosingCtor.QueryField.FullSanitizedName, Bpl.Type.Bool, etran.TrExpr(e.Obj));
5953-
if (dtor.EnclosingCtor.EnclosingDatatype.Ctors.Count == 1) {
5954-
// There is only one constructor, so the value must be been constructed by it; might as well assume that here.
5956+
var correctConstructor = BplOr(dtor.EnclosingCtors.ConvertAll(
5957+
ctor => FunctionCall(e.tok, ctor.QueryField.FullSanitizedName, Bpl.Type.Bool, etran.TrExpr(e.Obj))));
5958+
if (dtor.EnclosingCtors.Count == dtor.EnclosingCtors[0].EnclosingDatatype.Ctors.Count) {
5959+
// Every constructor has this destructor; might as well assume that here.
59555960
builder.Add(TrAssumeCmd(expr.tok, correctConstructor));
59565961
} else {
59575962
builder.Add(Assert(expr.tok, correctConstructor,
5958-
string.Format("destructor '{0}' can only be applied to datatype values constructed by '{1}'", dtor.Name, dtor.EnclosingCtor.Name)));
5963+
string.Format("destructor '{0}' can only be applied to datatype values constructed by {1}", dtor.Name, dtor.EnclosingCtorNames("or"))));
59595964
}
59605965
}
59615966
if (options.DoReadsChecks && e.Member is Field && ((Field)e.Member).IsMutable) {

Test/dafny0/DatatypeUpdateResolution.dfy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ method test(foo:MyDataType, x:int) returns (abc:MyDataType, def:MyDataType, ghi:
1818
ghi := MyConstructor(2, false).(otherbool := true); // allowed, and will generate verification error
1919
jkl := foo.(42 := 7, otherbool := true); // error: members are from different constructors
2020
}
21+
22+
datatype Dt = Make(x: int)
23+
24+
method Main()
25+
{
26+
var d := Make(5);
27+
d := d.(x := 20);
28+
d := d.(Make? := d); // error: Make? is not a destructor (this previously crashed the resolver)
29+
}

0 commit comments

Comments
 (0)