mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-12 10:32:27 -06:00
Implement TypesIntersect(a, b *types.Type) bool (#3223)
* Implement `TypesIntersect(a, b *types.Type) bool` fixes: #3203 * Respond to review: - Require at least one key type to match for map intersction - Do parallel scan of field when comparing struct - Add more tests for unions and nested collections * Rename to ContainCommonSupertype
This commit is contained in:
122
go/types/common_supertype.go
Normal file
122
go/types/common_supertype.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package types
|
||||
|
||||
import "github.com/attic-labs/noms/go/d"
|
||||
|
||||
// ContainCommonSupertype returns true if it's possible to synthesize
|
||||
// a non-trivial (i.e. not empty) supertype from types |a| and |b|.
|
||||
//
|
||||
// It is useful for determining whether a subset of values can be extracted
|
||||
// from one object to produce another object.
|
||||
//
|
||||
// The rules for determining whether |a| and |b| intersect are:
|
||||
// - if either type is Value, return true
|
||||
// - if either type is Union, return true iff at least one variant of |a| intersects with one variant of |b|
|
||||
// - if |a| & |b| are not the same kind, return false
|
||||
// - else
|
||||
// - if both are structs, return true iff their names are equal or one name is "", they share a field name
|
||||
// and the type of that field intersects
|
||||
// - if both are refs, sets or lists, return true iff the element type intersects
|
||||
// - if both are maps, return true iff they have a key with the same type and value types that intersect
|
||||
// - else return true
|
||||
func ContainCommonSupertype(a, b *Type) bool {
|
||||
// Avoid cycles internally.
|
||||
return containCommonSupertypeImpl(ToUnresolvedType(a), ToUnresolvedType(b))
|
||||
}
|
||||
|
||||
func containCommonSupertypeImpl(a, b *Type) bool {
|
||||
if a.Kind() == ValueKind || b.Kind() == ValueKind {
|
||||
return true
|
||||
}
|
||||
if a.Kind() == UnionKind || b.Kind() == UnionKind {
|
||||
return unionsIntersect(a, b)
|
||||
}
|
||||
if a.Kind() != b.Kind() {
|
||||
return false
|
||||
}
|
||||
switch k := a.Kind(); k {
|
||||
case StructKind:
|
||||
return structsIntersect(a, b)
|
||||
case ListKind, SetKind, RefKind:
|
||||
return containersIntersect(k, a, b)
|
||||
case MapKind:
|
||||
return mapsIntersect(a, b)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Checks for intersection between types that may be unions. If either or
|
||||
// both is a union, union, tests all types for intersection.
|
||||
func unionsIntersect(a, b *Type) bool {
|
||||
aTypes, bTypes := typeList(a), typeList(b)
|
||||
for _, t := range aTypes {
|
||||
for _, u := range bTypes {
|
||||
if containCommonSupertypeImpl(t, u) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// if |t| is a union, returns all types represented; otherwise returns |t|
|
||||
func typeList(t *Type) typeSlice {
|
||||
if t.Desc.Kind() == UnionKind {
|
||||
return t.Desc.(CompoundDesc).ElemTypes
|
||||
}
|
||||
return typeSlice{t}
|
||||
}
|
||||
|
||||
func containersIntersect(kind NomsKind, a, b *Type) bool {
|
||||
d.Chk.True(kind == a.Desc.Kind() && kind == b.Desc.Kind())
|
||||
return containCommonSupertypeImpl(a.Desc.(CompoundDesc).ElemTypes[0], b.Desc.(CompoundDesc).ElemTypes[0])
|
||||
}
|
||||
|
||||
func mapsIntersect(a, b *Type) bool {
|
||||
// true if a and b are the same or (if either is a union) there is
|
||||
// common type between them.
|
||||
hasCommonType := func(a, b *Type) bool {
|
||||
aTypes, bTypes := typeList(a), typeList(b)
|
||||
for _, t := range aTypes {
|
||||
for _, u := range bTypes {
|
||||
if t == u {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
d.Chk.True(MapKind == a.Desc.Kind() && MapKind == b.Desc.Kind())
|
||||
|
||||
aDesc, bDesc := a.Desc.(CompoundDesc), b.Desc.(CompoundDesc)
|
||||
if !hasCommonType(aDesc.ElemTypes[0], bDesc.ElemTypes[0]) {
|
||||
return false
|
||||
}
|
||||
return containCommonSupertypeImpl(aDesc.ElemTypes[1], bDesc.ElemTypes[1])
|
||||
}
|
||||
|
||||
func structsIntersect(a, b *Type) bool {
|
||||
d.Chk.True(StructKind == a.Kind() && StructKind == b.Kind())
|
||||
aDesc := a.Desc.(StructDesc)
|
||||
bDesc := b.Desc.(StructDesc)
|
||||
// must be either the same name or one has no name
|
||||
if aDesc.Name != bDesc.Name && !(aDesc.Name == "" || bDesc.Name == "") {
|
||||
return false
|
||||
}
|
||||
for i, j := 0, 0; i < len(aDesc.fields) && j < len(bDesc.fields); {
|
||||
aName, bName := aDesc.fields[i].name, bDesc.fields[j].name
|
||||
if aName < bName {
|
||||
i++
|
||||
} else if bName < aName {
|
||||
j++
|
||||
} else if !containCommonSupertypeImpl(aDesc.fields[i].t, bDesc.fields[j].t) {
|
||||
i++
|
||||
j++
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
142
go/types/common_supertype_test.go
Normal file
142
go/types/common_supertype_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/attic-labs/testify/assert"
|
||||
)
|
||||
|
||||
func TestContainCommonSupertype(t *testing.T) {
|
||||
cases := []struct {
|
||||
a, b *Type
|
||||
out bool
|
||||
}{
|
||||
// bool & any -> true
|
||||
{ValueType, StringType, true},
|
||||
// ref<bool> & ref<bool> -> true
|
||||
{MakeRefType(BoolType), MakeRefType(BoolType), true},
|
||||
// ref<number> & ref<string> -> false
|
||||
{MakeRefType(NumberType), MakeRefType(StringType), false},
|
||||
// set<bool> & set<bool> -> true
|
||||
{MakeSetType(BoolType), MakeSetType(BoolType), true},
|
||||
// set<bool> & set<string> -> false
|
||||
{MakeSetType(BoolType), MakeSetType(StringType), false},
|
||||
// list<blob> & list<blob> -> true
|
||||
{MakeListType(BlobType), MakeListType(BlobType), true},
|
||||
// list<blob> & list<string> -> false
|
||||
{MakeListType(BlobType), MakeListType(StringType), false},
|
||||
// list<blob|string|number> & list<string|bool> -> true
|
||||
{MakeListType(MakeUnionType(BlobType, StringType, NumberType)), MakeListType(MakeUnionType(StringType, BoolType)), true},
|
||||
// list<blob|string> & list<number|bool> -> false
|
||||
{MakeListType(MakeUnionType(BlobType, StringType)), MakeListType(MakeUnionType(NumberType, BoolType)), false},
|
||||
|
||||
// map<bool,bool> & map<bool,bool> -> true
|
||||
{MakeMapType(BoolType, BoolType), MakeMapType(BoolType, BoolType), true},
|
||||
// map<bool,bool> & map<bool,string> -> false
|
||||
{MakeMapType(BoolType, BoolType), MakeMapType(BoolType, StringType), false},
|
||||
// map<bool,bool> & map<string,bool> -> false
|
||||
{MakeMapType(BoolType, BoolType), MakeMapType(StringType, BoolType), false},
|
||||
// map<bool,bool> & map<string,bool> -> false
|
||||
{MakeMapType(BoolType, BoolType), MakeMapType(StringType, BoolType), false},
|
||||
// map<struct{foo:string},bool> & map<struct{foo:string,bar:string},bool> -> false
|
||||
{MakeMapType(MakeStructTypeFromFields("", FieldMap{"foo": StringType}), BoolType),
|
||||
MakeMapType(MakeStructTypeFromFields("", FieldMap{"foo": StringType, "bar": StringType}), BoolType), false},
|
||||
// map<string|blob,string> & map<number|string,string> -> true
|
||||
{MakeMapType(MakeUnionType(StringType, BlobType), StringType),
|
||||
MakeMapType(MakeUnionType(NumberType, StringType), StringType), true},
|
||||
// map<blob|bool,string> & map<number|string,string> -> false
|
||||
{MakeMapType(MakeUnionType(BlobType, BoolType), StringType),
|
||||
MakeMapType(MakeUnionType(NumberType, StringType), StringType), false},
|
||||
|
||||
// bool & string|bool|blob -> true
|
||||
{BoolType, MakeUnionType(StringType, BoolType, BlobType), true},
|
||||
// string|bool|blob & blob -> true
|
||||
{MakeUnionType(StringType, BoolType, BlobType), BlobType, true},
|
||||
// string|bool|blob & number|blob|string -> true
|
||||
{MakeUnionType(StringType, BoolType, BlobType), MakeUnionType(NumberType, BlobType, StringType), true},
|
||||
|
||||
// struct{foo:bool} & struct{foo:bool} -> true
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType}), true},
|
||||
// struct{foo:bool} & struct{foo:number} -> false
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": StringType}), false},
|
||||
// struct{foo:bool} & struct{foo:bool,bar:number} -> true
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType, "bar": NumberType}), true},
|
||||
// struct{foo:ref<bool>} & struct{foo:ref<number>} -> false
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(BoolType)}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(NumberType)}), false},
|
||||
// struct{foo:ref<bool>} & struct{foo:ref<number|bool>} -> true
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(BoolType)}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(MakeUnionType(NumberType, BoolType))}), true},
|
||||
// struct A{foo:bool} & struct A{foo:bool, baz:string} -> true
|
||||
{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": BoolType, "baz": StringType}), true},
|
||||
|
||||
// struct A{foo:bool, stuff:set<String|Blob>} & struct A{foo:bool, stuff:set<String>} -> true
|
||||
{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType, "stuff": MakeSetType(MakeUnionType(StringType, BlobType))}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": BoolType, "stuff": MakeSetType(StringType)}), true},
|
||||
// struct A{stuff:set<String|Blob>} & struct A{foo:bool, stuff:set<Number>} -> false
|
||||
{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType, "stuff": MakeSetType(MakeUnionType(StringType, BlobType))}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"stuff": MakeSetType(NumberType)}), false},
|
||||
|
||||
// struct A{foo:bool} & struct {foo:bool} -> true
|
||||
{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType}), true},
|
||||
// struct {foo:bool} & struct A{foo:bool} -> false
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}), true},
|
||||
// struct A{foo:bool} & struct B{foo:bool} -> false
|
||||
{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("B", FieldMap{"foo": BoolType}), false},
|
||||
// map<string, struct A{foo:string}> & map<string, struct A{foo:string, bar:bool}> -> true
|
||||
{MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType})),
|
||||
MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType, "bar": BoolType})), true},
|
||||
|
||||
// struct{foo: string} & struct{foo: string|blob} -> true
|
||||
{MakeStructTypeFromFields("", FieldMap{"foo": StringType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeUnionType(StringType, BlobType)}), true},
|
||||
// struct{foo: string}|struct{foo: blob} & struct{foo: string|blob} -> true
|
||||
{MakeUnionType(
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": StringType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BlobType}),
|
||||
), MakeStructTypeFromFields("", FieldMap{"foo": MakeUnionType(StringType, BlobType)}), true},
|
||||
// struct{foo: string}|struct{foo: blob} & struct{foo: number|bool} -> false
|
||||
{MakeUnionType(
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": StringType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BlobType}),
|
||||
), MakeStructTypeFromFields("", FieldMap{"foo": MakeUnionType(NumberType, BoolType)}), false},
|
||||
|
||||
// map<struct{x:number, y:number}, struct A{foo:string}> & map<struct{x:number, y:number}, struct A{foo:string, bar:bool}> -> true
|
||||
{
|
||||
MakeMapType(
|
||||
MakeStructTypeFromFields("", FieldMap{"x": NumberType, "y": NumberType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType})),
|
||||
MakeMapType(
|
||||
MakeStructTypeFromFields("", FieldMap{"x": NumberType, "y": NumberType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType, "bar": BoolType})),
|
||||
true,
|
||||
},
|
||||
|
||||
// map<struct{x:number, y:number}, struct A{foo:string}> & map<struct{x:number, y:number}, struct A{foo:string, bar:bool}> -> true
|
||||
{
|
||||
MakeMapType(
|
||||
MakeStructTypeFromFields("", FieldMap{"x": NumberType, "y": NumberType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType})),
|
||||
MakeMapType(
|
||||
MakeStructTypeFromFields("", FieldMap{"x": NumberType, "y": NumberType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType, "bar": BoolType})),
|
||||
true,
|
||||
},
|
||||
|
||||
// struct A{self:A} & struct A{self:A, foo:Number} -> true
|
||||
{MakeStructTypeFromFields("A", FieldMap{"self": MakeCycleType(0)}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"self": MakeCycleType(0), "foo": NumberType}), true},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
act := ContainCommonSupertype(c.a, c.b)
|
||||
assert.Equal(t, c.out, act, "Test case at position %d; \n\ta:%s\n\tb:%s", i, c.a.Describe(), c.b.Describe())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user