Fix subtype issue with unions as elem types (#2506)

For compound types (List, Set, Map, Ref) the concrete type may be a
union. If that is the case all the types in the union must be a
subtype of the concrete type's element type.

`C<T | V>` is a subtype of `C<S>` iff T is a subtype of S and V is a
subtype of S.
This commit is contained in:
Erik Arvidsson
2016-09-01 18:40:51 -07:00
committed by GitHub
parent e5fcfd6ebf
commit 017d599b1d
3 changed files with 38 additions and 2 deletions
+14
View File
@@ -65,6 +65,20 @@ func TestNewCommit(t *testing.T) {
types.StringType,
})
assertTypeEquals(et4, at4)
// Merge-commit with different parent types
commit5 := NewCommit(types.String("Hi"), types.NewSet(types.NewRef(commit2), types.NewRef(commit3)), types.EmptyStruct)
at5 := commit5.Type()
et5 := types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeStructType("Commit", commitFieldNames, []*types.Type{
types.EmptyStructType,
types.MakeSetType(types.MakeRefType(types.MakeCycleType(0))),
types.MakeUnionType(types.NumberType, types.StringType),
}))),
types.StringType,
})
assertTypeEquals(et5, at5)
}
func TestCommitWithoutMetaField(t *testing.T) {
+9 -2
View File
@@ -87,9 +87,16 @@ func isSubtype(requiredType, concreteType *Type, parentStructTypes []*Type) bool
panic("unreachable")
}
// compoundSubtype is called when comparing the element types of two compound types. This is the only case
// where a concrete type may have be a union type.
func compoundSubtype(requiredType, concreteType *Type, parentStructTypes []*Type) bool {
// In a compound type it is OK to have an empty union.
if concreteType.Kind() == UnionKind && len(concreteType.Desc.(CompoundDesc).ElemTypes) == 0 {
// If the concrete type is a union then all the types in the union must be subtypes of the required typ. This also means that a compound type with an empty union is going to be a subtype of all compounds, List<> is a subtype of List<T> for all T.
if concreteType.Kind() == UnionKind {
for _, ct := range concreteType.Desc.(CompoundDesc).ElemTypes {
if !isSubtype(requiredType, ct, parentStructTypes) {
return false
}
}
return true
}
return isSubtype(requiredType, concreteType, parentStructTypes)
+15
View File
@@ -340,3 +340,18 @@ func TestIsSubtypeEmptySruct(tt *testing.T) {
assert.False(tt, IsSubtype(t1, t2))
assert.True(tt, IsSubtype(t2, t1))
}
func TestIsSubtypeCompundUnion(tt *testing.T) {
rt := MakeListType(EmptyStructType)
st1 := MakeStructType("One", []string{"a"}, []*Type{NumberType})
st2 := MakeStructType("Two", []string{"b"}, []*Type{StringType})
ct := MakeListType(MakeUnionType(st1, st2))
assert.True(tt, IsSubtype(rt, ct))
assert.False(tt, IsSubtype(ct, rt))
ct2 := MakeListType(MakeUnionType(st1, st2, NumberType))
assert.False(tt, IsSubtype(rt, ct2))
assert.False(tt, IsSubtype(ct2, rt))
}