-
Notifications
You must be signed in to change notification settings - Fork 267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Account for the optional equality support of map/seq inside newtypes #5980
Changes from 1 commit
07fd118
8dcde42
59b1df8
caad6dd
a66a597
f8842cb
52c304a
fe10780
9e03366
3082e36
c938b2b
d7b7f07
ec2ab42
f3cbb81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2884,8 +2884,7 @@ void MarkSCCAsNotSupportingEquality() { | |
} | ||
foreach (var ctor in dt.Ctors) { | ||
foreach (var arg in ctor.Formals) { | ||
var type = arg.Type; | ||
DetermineEqualitySupportType(type, ref thingsChanged); | ||
DetermineEqualitySupportType(arg.Type, ref thingsChanged); | ||
} | ||
} | ||
} | ||
|
@@ -2929,26 +2928,43 @@ private static void DetermineEqualitySupportType(Type type, ref bool thingsChang | |
if (type.AsTypeParameter is { } typeArg) { | ||
typeArg.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} else if (type.AsMapType is { } typeMap) { | ||
DetermineEqualitySupportType(typeMap.Range, ref thingsChanged); | ||
} else if (type.AsSeqType is { } typeSeq) { | ||
DetermineEqualitySupportType(typeSeq.Arg, ref thingsChanged); | ||
} else if (type.AsIndDatatype is { } otherDt) { | ||
if (otherDt.EqualitySupport == IndDatatypeDecl.ES.ConsultTypeArguments) { // datatype is in a different SCC | ||
var otherUdt = (UserDefinedType)type.NormalizeExpand(); | ||
var i = 0; | ||
foreach (var otherTp in otherDt.TypeArgs) { | ||
if (otherTp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype) { | ||
var tp = otherUdt.TypeArgs[i].AsTypeParameter; | ||
if (tp != null) { | ||
tp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} else if (type.SupportsEquality) { | ||
// nothing to do | ||
} else if (type.Normalize() is UserDefinedType userDefinedType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need this normalization? I thought that AsMapType and AsSeqType would actually do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a type synonym does not have an explicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An explicit |
||
if (userDefinedType.ResolvedClass is TypeSynonymDeclBase typeSynonymDecl) { | ||
if (typeSynonymDecl.IsRevealedInScope(Type.GetScope())) { | ||
DetermineEqualitySupportType(typeSynonymDecl.RhsWithArgument(userDefinedType.TypeArgs), ref thingsChanged); | ||
} | ||
} else if (userDefinedType.ResolvedClass is NewtypeDecl newtypeDecl) { | ||
if (newtypeDecl.IsRevealedInScope(Type.GetScope())) { | ||
DetermineEqualitySupportType(newtypeDecl.RhsWithArgument(userDefinedType.TypeArgs), ref thingsChanged); | ||
} | ||
} else if (userDefinedType.ResolvedClass is IndDatatypeDecl otherDt) { | ||
if (otherDt.EqualitySupport == IndDatatypeDecl.ES.ConsultTypeArguments) { | ||
// datatype is in a different SCC | ||
var otherUdt = (UserDefinedType)type.NormalizeExpand(); | ||
var i = 0; | ||
foreach (var otherTp in otherDt.TypeArgs) { | ||
if (otherTp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype) { | ||
var tp = otherUdt.TypeArgs[i].AsTypeParameter; | ||
if (tp != null) { | ||
tp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} | ||
} | ||
} | ||
|
||
i++; | ||
i++; | ||
} | ||
} | ||
} | ||
} else if (type.AsMapType is { } typeMap) { | ||
// A map type's Domain type is required to support equality (just like the argument type for sets and multisets), but | ||
// it is optional for the map type's Range type to support equality. Thus, we need to determine the equality support for just the Range type. | ||
DetermineEqualitySupportType(typeMap.Range, ref thingsChanged); | ||
} else if (type.AsSeqType is { } typeSeq) { | ||
// Like the Range type of a map, it is optional for a sequence type's argument type to support equality. So, we make a call | ||
// to determine it. | ||
DetermineEqualitySupportType(typeSeq.Arg, ref thingsChanged); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,240 @@ | ||
// RUN: %exits-with 2 %baredafny verify %args "%s" > "%t" | ||
// RUN: %diff "%s.expect" "%t" | ||
// RUN: %testDafnyForEachResolver --expect-exit-code=2 "%s" | ||
|
||
datatype M<T, U> = M(m: map<T, U>) | ||
module Issue5972 { | ||
datatype M<T, U> = M(m: map<T, U>) | ||
|
||
method CompareAnything<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := M(map[0 := f]); | ||
var m2 := M(map[0 := g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
method CompareAnything<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := M(map[0 := f]); | ||
var m2 := M(map[0 := g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.m[0] == m2.m[0]; | ||
} | ||
} | ||
if m1 == m2 { | ||
assert m1.m[0] == m2.m[0]; | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
|
||
datatype S<T> = S(s: seq<T>) | ||
|
||
method CompareAnythingS<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := S([f]); | ||
var m2 := S([g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.s[0] == m2.s[0]; | ||
} | ||
} | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
|
||
|
||
codatatype Stream = Stream(head: int, tail: Stream) | ||
{ | ||
static function From(i: int): Stream { | ||
Stream(i, From(i + 1)) | ||
} | ||
} | ||
|
||
method Main() { | ||
var s1 := Stream.From(0); | ||
var s2 := Stream.From(0); | ||
var b := CompareAnything(s1, s2); | ||
var b2 := CompareAnythingS(s1, s2); | ||
if !b || !b2 { | ||
assert false; | ||
print 1/0; | ||
} | ||
} | ||
return m1 == m2; | ||
} | ||
|
||
datatype S<T> = S(s: seq<T>) | ||
// This module is like Issue5972 above, but uses type synonyms around the map/seq types | ||
module TypeSynonyms { | ||
type MyMap<T, U> = map<T, U> | ||
type MySeq<T> = seq<T> | ||
|
||
datatype M<T, U> = M(m: MyMap<T, U>) | ||
|
||
method CompareAnythingS<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := S([f]); | ||
var m2 := S([g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
method CompareAnything<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := M(map[0 := f]); | ||
var m2 := M(map[0 := g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.m[0] == m2.m[0]; | ||
} | ||
} | ||
if m1 == m2 { | ||
assert m1.s[0] == m2.s[0]; | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
|
||
datatype S<T> = S(s: MySeq<T>) | ||
|
||
method CompareAnythingS<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := S([f]); | ||
var m2 := S([g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.s[0] == m2.s[0]; | ||
} | ||
} | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
|
||
codatatype Stream = Stream(head: int, tail: Stream) | ||
{ | ||
static function From(i: int): Stream { | ||
Stream(i, From(i + 1)) | ||
} | ||
} | ||
|
||
method Test() { | ||
var s1 := Stream.From(0); | ||
var s2 := Stream.From(0); | ||
var b := CompareAnything(s1, s2); | ||
var b2 := CompareAnythingS(s1, s2); | ||
if !b || !b2 { | ||
assert false; | ||
print 1/0; | ||
} | ||
} | ||
return m1 == m2; | ||
} | ||
|
||
// This module is like TypeSynonyms above, but the two type synonyms are declared to definitely support equality. | ||
// The fact that their RHSs don't gives rise to errors, but in the process of fixing issue 5972, this program | ||
// had caused a crash, so it seems good to include as a test case. | ||
module TypeSynonymsWithGuaranteedEqualitySupport { | ||
type MyMap(==)<T, U> = map<T, U> // error: RHS does not support equality as promised | ||
type MySeq(==)<T> = seq<T> // error: RHS does not support equality as promised | ||
|
||
datatype M<T, U> = M(m: MyMap<T, U>) | ||
|
||
method CompareAnything<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := M(map[0 := f]); | ||
var m2 := M(map[0 := g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.m[0] == m2.m[0]; | ||
} | ||
} | ||
return m1 == m2; | ||
} | ||
|
||
datatype S<T> = S(s: MySeq<T>) | ||
|
||
method CompareAnythingS<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := S([f]); | ||
var m2 := S([g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.s[0] == m2.s[0]; | ||
} | ||
} | ||
return m1 == m2; | ||
} | ||
|
||
codatatype Stream = Stream(head: int, tail: Stream) | ||
{ | ||
static function From(i: int): Stream { | ||
Stream(i, From(i + 1)) | ||
} | ||
} | ||
|
||
codatatype Stream = Stream(head: int, tail: Stream) | ||
{ | ||
static function From(i: int): Stream { | ||
Stream(i, From(i + 1)) | ||
method Test() { | ||
var s1 := Stream.From(0); | ||
var s2 := Stream.From(0); | ||
var b := CompareAnything(s1, s2); | ||
var b2 := CompareAnythingS(s1, s2); | ||
if !b || !b2 { | ||
assert false; | ||
print 1/0; | ||
} | ||
} | ||
} | ||
|
||
method Main() { | ||
var s1 := Stream.From(0); | ||
var s2 := Stream.From(0); | ||
var b := CompareAnything(s1, s2); | ||
var b2 := CompareAnythingS(s1, s2); | ||
if !b || !b2 { | ||
assert false; | ||
print 1/0; | ||
// This module is like TypeSynonyms above, but uses subset types around the map/seq types | ||
module SubsetTypes { | ||
type MyMap<T, U> = m: map<T, U> | true | ||
type MySeq<T> = s: seq<T> | true | ||
|
||
datatype M<T, U> = M(m: MyMap<T, U>) | ||
|
||
method CompareAnything<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := M(map[0 := f]); | ||
var m2 := M(map[0 := g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.m[0] == m2.m[0]; | ||
} | ||
} | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
} | ||
|
||
datatype S<T> = S(s: MySeq<T>) | ||
|
||
method CompareAnythingS<A>(f: A, g: A) returns (b: bool) | ||
ensures b <==> f == g | ||
{ | ||
var m1 := S([f]); | ||
var m2 := S([g]); | ||
assert m1 == m2 <==> f == g by { | ||
if f == g { | ||
assert m1 == m2; | ||
} | ||
if m1 == m2 { | ||
assert m1.s[0] == m2.s[0]; | ||
} | ||
} | ||
return m1 == m2; // error: the types of m1 and m2 do not support equality | ||
} | ||
|
||
codatatype Stream = Stream(head: int, tail: Stream) | ||
{ | ||
static function From(i: int): Stream { | ||
Stream(i, From(i + 1)) | ||
} | ||
} | ||
|
||
method Test() { | ||
var s1 := Stream.From(0); | ||
var s2 := Stream.From(0); | ||
var b := CompareAnything(s1, s2); | ||
var b2 := CompareAnythingS(s1, s2); | ||
if !b || !b2 { | ||
assert false; | ||
print 1/0; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
git-issue-5972.dfy(19,9): Error: == can only be applied to expressions of types that support equality (got M<int, A>) | ||
git-issue-5972.dfy(37,9): Error: == can only be applied to expressions of types that support equality (got S<A>) | ||
2 resolution/type errors detected in git-issue-5972.dfy | ||
git-issue-5972.dfy(19,11): Error: == can only be applied to expressions of types that support equality (got M<int, A>) | ||
git-issue-5972.dfy(37,11): Error: == can only be applied to expressions of types that support equality (got S<A>) | ||
git-issue-5972.dfy(80,11): Error: == can only be applied to expressions of types that support equality (got M<int, A>) | ||
git-issue-5972.dfy(98,11): Error: == can only be applied to expressions of types that support equality (got S<A>) | ||
git-issue-5972.dfy(124,7): Error: type 'MyMap' declared as supporting equality, but the RHS type (map<T, U>) might not | ||
git-issue-5972.dfy(125,7): Error: type 'MySeq' declared as supporting equality, but the RHS type (seq<T>) might not | ||
git-issue-5972.dfy(202,11): Error: == can only be applied to expressions of types that support equality (got M<int, A>) | ||
git-issue-5972.dfy(220,11): Error: == can only be applied to expressions of types that support equality (got S<A>) | ||
8 resolution/type errors detected in git-issue-5972.dfy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of my change, we could also do the same treatment as for the datatypes, if you preferred. I'm fine with both approaches and to switch if you prefer.