Implement unionTypes(a, b *types.Type) *types.Type (#3204) (#3214)

* 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:
Eric Halpern
2017-02-23 13:17:59 -08:00
committed by GitHub
parent 310742211c
commit 7ae6264782
3 changed files with 205 additions and 36 deletions

View File

@@ -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)

View File

@@ -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())
}
}

View File

@@ -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 {