mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-09 18:59:12 -06:00
* Implement `unionTypes(a, b *types.Type) *types.Type` Implements type merging building off of makeSimplifiedUnion. The difference between siplified unions and merged types are in how structs are handled. In the former, we produce the intersection of input structs to ensure the simplified type is a supertype of the input types. In the later, we produce the union of the input structs which leads to a type that is a subtype of all inputs. Response to review and discussion - Rename to MakeFullUnion to MakeMergedType - Revert to old file naming - Rename makeSimplifiedType to makeSupertype - MakeUnion delegates to makeSupertype. Should prabably be be renamed, but this touches a lot of files, so keeping same for now. fixes: #3204 * Rename to makeSimplifiedType and makeSimplifiedType2
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
)
|
||||
import "github.com/attic-labs/noms/go/d"
|
||||
|
||||
// makeSimplifiedUnion returns a type that is a supertype of all the input types, but is much
|
||||
// makeSimplifiedType returns a type that is a supertype of all the input types but is much
|
||||
// smaller and less complex than a straight union of all those types would be.
|
||||
//
|
||||
// The resulting type is guaranteed to:
|
||||
@@ -29,12 +27,12 @@ import (
|
||||
// - The map group is collapsed like so:
|
||||
// {Map<K1,V1>|Map<K2,V2>...} -> Map<K1|K2,V1|V2>
|
||||
// - Each struct group is collapsed like so:
|
||||
// {struct{foo:number,bar:string}, struct{bar:bool, baz:blob}} ->
|
||||
// {struct{foo:number,bar:string}, struct{bar:blob, baz:bool}} ->
|
||||
// struct{bar:string|blob}
|
||||
//
|
||||
// Anytime any of the above cases generates a union as output, the same process
|
||||
// is applied to that union recursively.
|
||||
func makeSimplifiedUnion(in ...*Type) *Type {
|
||||
func makeSimplifiedType(in ...*Type) *Type {
|
||||
ts := make(typeset, len(in))
|
||||
for _, t := range in {
|
||||
// De-cycle so that we handle cycles explicitly below. Otherwise, we would implicitly crawl
|
||||
@@ -43,8 +41,32 @@ func makeSimplifiedUnion(in ...*Type) *Type {
|
||||
ts[t] = struct{}{}
|
||||
}
|
||||
|
||||
// makeSimplifiedUnionImpl de-cycles internally.
|
||||
return makeSimplifiedUnionImpl(ts)
|
||||
// Impl de-cycles internally.
|
||||
return makeSimplifiedTypeImpl(ts, false)
|
||||
}
|
||||
|
||||
// makeSimplifiedType2 returns a type that results from merging all input types.
|
||||
//
|
||||
// The result is similar makeSimplifiedType with one exception:
|
||||
//
|
||||
// Each matching Struct found in the input will be merged into a single struct containing all fields
|
||||
// found in the input structs.
|
||||
//
|
||||
// Each struct is expanded like so:
|
||||
// {struct{foo:number,bar:string}, struct{bar:blob, baz:bool}} ->
|
||||
// struct{foo:number, bar:string|blob, baz:bool}
|
||||
//
|
||||
func makeSimplifedType2(in ...*Type) *Type {
|
||||
ts := make(typeset, len(in))
|
||||
for _, t := range in {
|
||||
// De-cycle so that we handle cycles explicitly below. Otherwise, we would implicitly crawl
|
||||
// cycles and recurse forever.
|
||||
t := ToUnresolvedType(t)
|
||||
ts[t] = struct{}{}
|
||||
}
|
||||
|
||||
// Impl de-cycles internally.
|
||||
return makeSimplifiedTypeImpl(ts, true)
|
||||
}
|
||||
|
||||
// typeset is a helper that aggregates the unique set of input types for this algorithm, flattening
|
||||
@@ -70,10 +92,10 @@ func newTypeset(t ...*Type) typeset {
|
||||
return ts
|
||||
}
|
||||
|
||||
// makeSimplifiedUnionImpl is an implementation detail of makeSimplifiedUnion.
|
||||
// makeSimplifiedTypeImpl is an implementation detail.
|
||||
// Warning: Do not call this directly. It assumes its input has been de-cycled using
|
||||
// ToUnresolvedType() and will infinitely recurse on cyclic types otherwise.
|
||||
func makeSimplifiedUnionImpl(in typeset) *Type {
|
||||
func makeSimplifiedTypeImpl(in typeset, merge bool) *Type {
|
||||
type how struct {
|
||||
k NomsKind
|
||||
n string
|
||||
@@ -111,15 +133,15 @@ func makeSimplifiedUnionImpl(in typeset) *Type {
|
||||
var r *Type
|
||||
switch h.k {
|
||||
case RefKind:
|
||||
r = simplifyRefs(ts)
|
||||
r = simplifyRefs(ts, merge)
|
||||
case SetKind:
|
||||
r = simplifySets(ts)
|
||||
r = simplifySets(ts, merge)
|
||||
case ListKind:
|
||||
r = simplifyLists(ts)
|
||||
r = simplifyLists(ts, merge)
|
||||
case MapKind:
|
||||
r = simplifyMaps(ts)
|
||||
r = simplifyMaps(ts, merge)
|
||||
case StructKind:
|
||||
r = simplifyStructs(h.n, ts)
|
||||
r = simplifyStructs(h.n, ts, merge)
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
@@ -138,28 +160,28 @@ func makeSimplifiedUnionImpl(in typeset) *Type {
|
||||
return staticTypeCache.makeUnionType(out...)
|
||||
}
|
||||
|
||||
func simplifyRefs(ts typeset) *Type {
|
||||
return simplifyContainers(RefKind, MakeRefType, ts)
|
||||
func simplifyRefs(ts typeset, merge bool) *Type {
|
||||
return simplifyContainers(RefKind, MakeRefType, ts, merge)
|
||||
}
|
||||
|
||||
func simplifySets(ts typeset) *Type {
|
||||
return simplifyContainers(SetKind, MakeSetType, ts)
|
||||
func simplifySets(ts typeset, merge bool) *Type {
|
||||
return simplifyContainers(SetKind, MakeSetType, ts, merge)
|
||||
}
|
||||
|
||||
func simplifyLists(ts typeset) *Type {
|
||||
return simplifyContainers(ListKind, MakeListType, ts)
|
||||
func simplifyLists(ts typeset, merge bool) *Type {
|
||||
return simplifyContainers(ListKind, MakeListType, ts, merge)
|
||||
}
|
||||
|
||||
func simplifyContainers(expectedKind NomsKind, makeContainer func(elem *Type) *Type, ts typeset) *Type {
|
||||
func simplifyContainers(expectedKind NomsKind, makeContainer func(elem *Type) *Type, ts typeset, merge bool) *Type {
|
||||
elemTypes := make(typeset, len(ts))
|
||||
for t := range ts {
|
||||
d.Chk.True(expectedKind == t.Kind())
|
||||
elemTypes.Add(t.Desc.(CompoundDesc).ElemTypes[0])
|
||||
}
|
||||
return makeContainer(makeSimplifiedUnionImpl(elemTypes))
|
||||
return makeContainer(makeSimplifiedTypeImpl(elemTypes, merge))
|
||||
}
|
||||
|
||||
func simplifyMaps(ts typeset) *Type {
|
||||
func simplifyMaps(ts typeset, merge bool) *Type {
|
||||
keyTypes := make(typeset, len(ts))
|
||||
valTypes := make(typeset, len(ts))
|
||||
for t := range ts {
|
||||
@@ -169,11 +191,14 @@ func simplifyMaps(ts typeset) *Type {
|
||||
valTypes.Add(desc.ElemTypes[1])
|
||||
}
|
||||
return MakeMapType(
|
||||
makeSimplifiedUnionImpl(keyTypes),
|
||||
makeSimplifiedUnionImpl(valTypes))
|
||||
makeSimplifiedTypeImpl(keyTypes, merge),
|
||||
makeSimplifiedTypeImpl(valTypes, merge))
|
||||
}
|
||||
|
||||
func simplifyStructs(expectedName string, ts typeset) *Type {
|
||||
func simplifyStructs(expectedName string, ts typeset, merge bool) *Type {
|
||||
if merge {
|
||||
return mergeStructs(expectedName, ts)
|
||||
}
|
||||
commonFields := map[string]typeset{}
|
||||
|
||||
first := true
|
||||
@@ -202,7 +227,31 @@ func simplifyStructs(expectedName string, ts typeset) *Type {
|
||||
|
||||
fm := make(FieldMap, len(ts))
|
||||
for n, ts := range commonFields {
|
||||
fm[n] = makeSimplifiedUnionImpl(ts)
|
||||
fm[n] = makeSimplifiedTypeImpl(ts, false)
|
||||
}
|
||||
|
||||
return MakeStructTypeFromFields(expectedName, fm)
|
||||
}
|
||||
|
||||
func mergeStructs(expectedName string, ts typeset) *Type {
|
||||
unionFields := map[string]typeset{}
|
||||
for t := range ts {
|
||||
d.Chk.True(StructKind == t.Kind())
|
||||
desc := t.Desc.(StructDesc)
|
||||
d.Chk.True(expectedName == desc.Name)
|
||||
for _, f := range desc.fields {
|
||||
ts, ok := unionFields[f.name]
|
||||
if !ok {
|
||||
ts = typeset{}
|
||||
}
|
||||
ts.Add(f.t)
|
||||
unionFields[f.name] = ts
|
||||
}
|
||||
}
|
||||
|
||||
fm := make(FieldMap, len(ts))
|
||||
for n, ts := range unionFields {
|
||||
fm[n] = makeSimplifiedTypeImpl(ts, true)
|
||||
}
|
||||
|
||||
return MakeStructTypeFromFields(expectedName, fm)
|
||||
|
||||
@@ -8,22 +8,25 @@ import (
|
||||
|
||||
// testing strategy
|
||||
// - test simplifying each kind in isolation, both shallow and deep
|
||||
// - test makeSimplifiedUnion
|
||||
// - test merging structs in isolation, both shallow and deep
|
||||
// - test makeSupertype
|
||||
// - pass one type only
|
||||
// - test that instances are properly deduplicated
|
||||
// - test union flattening
|
||||
// - test grouping of the various kinds
|
||||
// - test cycles
|
||||
// - test makeMergedType
|
||||
// - test structs and structs nested in collections
|
||||
|
||||
func TestSimplifyHelpers(t *testing.T) {
|
||||
structSimplifier := func(n string) func(typeset) *Type {
|
||||
return func(ts typeset) *Type {
|
||||
return simplifyStructs(n, ts)
|
||||
structSimplifier := func(n string) func(typeset, bool) *Type {
|
||||
return func(ts typeset, merge bool) *Type {
|
||||
return simplifyStructs(n, ts, merge)
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
f func(typeset) *Type
|
||||
f func(typeset, bool) *Type
|
||||
in []*Type
|
||||
out *Type
|
||||
}{
|
||||
@@ -129,10 +132,76 @@ func TestSimplifyHelpers(t *testing.T) {
|
||||
[]*Type{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType})},
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": MakeUnionType(BoolType, StringType)})},
|
||||
|
||||
// map<string, struct A{foo:string}>, map<string, struct A{foo:string, bar:bool}>
|
||||
// -> map<string, struct A{foo:string}>
|
||||
{simplifyMaps,
|
||||
[]*Type{MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType})),
|
||||
MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType, "bar": BoolType})),
|
||||
},
|
||||
MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType}))},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
act := c.f(newTypeset(c.in...))
|
||||
act := c.f(newTypeset(c.in...), false /*don't merge*/)
|
||||
assert.True(t, c.out.Equals(act), "Test case as position %d - got %s", i, act.Describe())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeHelpers(t *testing.T) {
|
||||
structSimplifier := func(n string) func(typeset, bool) *Type {
|
||||
return func(ts typeset, merge bool) *Type {
|
||||
return simplifyStructs(n, ts, merge)
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
f func(typeset, bool) *Type
|
||||
in []*Type
|
||||
out *Type
|
||||
}{
|
||||
// struct{foo:bool} -> struct{foo:bool}
|
||||
{structSimplifier(""),
|
||||
[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": BoolType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType})},
|
||||
// struct{foo:bool}|struct{foo:number} -> struct{foo:bool|number}
|
||||
{structSimplifier(""),
|
||||
[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": StringType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeUnionType(BoolType, StringType)})},
|
||||
// struct{foo:bool}|struct{foo:bool,bar:number} -> struct{foo:bool,bar:number}
|
||||
{structSimplifier(""),
|
||||
[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType, "bar": NumberType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType, "bar": NumberType})},
|
||||
// struct{foo:bool}|struct{bar:number} -> struct{foo:bool,bar:number}
|
||||
{structSimplifier(""),
|
||||
[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"bar": NumberType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": BoolType, "bar": NumberType})},
|
||||
// struct{foo:ref<bool>}|struct{foo:ref<number>} -> struct{foo:ref<bool|number>}
|
||||
{structSimplifier(""),
|
||||
[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(BoolType)}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(NumberType)})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeRefType(MakeUnionType(BoolType, NumberType))})},
|
||||
|
||||
// struct A{foo:bool}|struct A{foo:string} -> struct A{foo:bool|string}
|
||||
{structSimplifier("A"),
|
||||
[]*Type{MakeStructTypeFromFields("A", FieldMap{"foo": BoolType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType})},
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": MakeUnionType(BoolType, StringType)})},
|
||||
|
||||
// map<string, struct A{foo:string}>, map<string, struct A{bar:bool}>
|
||||
// -> map<string, struct A{foo:string, bar:bool}>
|
||||
{simplifyMaps,
|
||||
[]*Type{MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType})),
|
||||
MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"bar": BoolType})),
|
||||
},
|
||||
MakeMapType(StringType, MakeStructTypeFromFields("A", FieldMap{"foo": StringType, "bar": BoolType}))},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
act := c.f(newTypeset(c.in...), true /* merge */)
|
||||
assert.True(t, c.out.Equals(act), "Test case as position %d - got %s", i, act.Describe())
|
||||
}
|
||||
}
|
||||
@@ -228,7 +297,54 @@ func TestMakeSimplifiedUnion(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
act := makeSimplifiedUnion(c.in...)
|
||||
act := makeSimplifiedType(c.in...)
|
||||
assert.True(t, c.out.Equals(act), "Test case as position %d - got %s, expected %s", i, act.Describe(), c.out.Describe())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeMergedType(t *testing.T) {
|
||||
cycleType := MakeStructTypeFromFields("", FieldMap{"self": MakeCycleType(0)})
|
||||
|
||||
// TODO: Why is this first step necessary?
|
||||
cycleType = ToUnresolvedType(cycleType)
|
||||
cycleType = resolveStructCycles(cycleType, nil)
|
||||
|
||||
cases := []struct {
|
||||
in []*Type
|
||||
out *Type
|
||||
}{
|
||||
// {struct{foo:number}} -> struct{foo:number}
|
||||
{[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": NumberType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": NumberType})},
|
||||
// {struct{foo:number}, struct{foo:string}} -> struct{foo:number|string}
|
||||
{[]*Type{MakeStructTypeFromFields("", FieldMap{"foo": NumberType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": StringType})},
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": MakeUnionType(NumberType, StringType)})},
|
||||
|
||||
// {bool,string,ref<bool>,ref<string>,ref<set<string>>,ref<set<bool>>,
|
||||
// struct{foo:number},struct{bar:string},struct A{foo:number}} ->
|
||||
// bool|string|ref<bool|string|set<string|bool>>|struct{}|struct A{foo:number}
|
||||
{
|
||||
[]*Type{
|
||||
BoolType, StringType,
|
||||
MakeRefType(BoolType), MakeRefType(StringType),
|
||||
MakeRefType(MakeSetType(BoolType)), MakeRefType(MakeSetType(StringType)),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": NumberType}),
|
||||
MakeStructTypeFromFields("", FieldMap{"bar": StringType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType}),
|
||||
},
|
||||
MakeUnionType(
|
||||
BoolType, StringType,
|
||||
MakeRefType(MakeUnionType(BoolType, StringType,
|
||||
MakeSetType(MakeUnionType(BoolType, StringType)))),
|
||||
MakeStructTypeFromFields("", FieldMap{"foo": NumberType, "bar": StringType}),
|
||||
MakeStructTypeFromFields("A", FieldMap{"foo": StringType}),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
act := makeSimplifedType2(c.in...)
|
||||
assert.True(t, c.out.Equals(act), "Test case as position %d - got %s, expected %s", i, act.Describe(), c.out.Describe())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +534,11 @@ func MakeStructType(name string, fieldNames []string, fieldTypes []*Type) *Type
|
||||
}
|
||||
|
||||
func MakeUnionType(elemTypes ...*Type) *Type {
|
||||
return makeSimplifiedUnion(elemTypes...)
|
||||
return makeSimplifiedType(elemTypes...)
|
||||
}
|
||||
|
||||
func MakeUnionType2(elemTypes ...*Type) *Type {
|
||||
return makeSimplifedType2(elemTypes...)
|
||||
}
|
||||
|
||||
func MakeCycleType(level uint32) *Type {
|
||||
|
||||
Reference in New Issue
Block a user