lang: types: Improve ComplexCmp function

This improves the ComplexCmp function so that it can compare partial
types to variant types. As a result of this improvement, it actually
ended up simplifying the code significantly. This also added a test
suite for this function. This fix was important for tricky type
unification problems.
This commit is contained in:
James Shubin
2019-06-02 16:48:41 -04:00
parent 99d3ef42e9
commit 4c6d304e60
2 changed files with 219 additions and 117 deletions

View File

@@ -902,22 +902,29 @@ func (obj *Type) HasVariant() bool {
// a possibility against a partial type, the status will be set to the "partial"
// string, and if it is compatible with the variant type it will be "variant"...
// Comparing to a partial can only match "impossible" (error) or possible (nil).
// This now also supports comparing a partial type to a variant type as well...
func (obj *Type) ComplexCmp(typ *Type) (string, error) {
// match simple "placeholder" variants... skip variants w/ sub types
isVariant := func(t *Type) bool { return t.Kind == KindVariant && t.Var == nil }
isVariant := func(t *Type) bool { return t != nil && t.Kind == KindVariant && t.Var == nil }
if obj == nil {
return "", fmt.Errorf("can't cmp from a nil type")
}
// XXX: can we relax this to allow variants matching against partials?
if obj.HasVariant() {
return "", fmt.Errorf("only input can contain variants")
}
if typ == nil { // match
if obj == nil && typ == nil {
return "partial", nil // compatible :)
}
if isVariant(typ) { // match
if isVariant(obj) && isVariant(typ) {
return "variant", nil // compatible :)
}
if obj == nil && isVariant(typ) { // partial vs variant
return "both", nil // compatible :)
}
if isVariant(obj) && typ == nil { // variant vs partial
return "both", nil // compatible :)
}
if obj == nil || typ == nil { // at least one is partial
return "partial", nil // compatible :)
}
if isVariant(obj) || isVariant(typ) { // at least one is variant
return "variant", nil // compatible :)
}
@@ -937,30 +944,9 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
return "", nil
case KindList:
if obj.Val == nil {
panic("malformed list type")
}
if typ.Val == nil {
return "partial", nil
}
return obj.Val.ComplexCmp(typ.Val)
case KindMap:
if obj.Key == nil || obj.Val == nil {
panic("malformed map type")
}
if typ.Key == nil && typ.Val == nil {
return "partial", nil
}
if typ.Key == nil {
return obj.Val.ComplexCmp(typ.Val)
}
if typ.Val == nil {
return obj.Key.ComplexCmp(typ.Key)
}
kstatus, kerr := obj.Key.ComplexCmp(typ.Key)
vstatus, verr := obj.Val.ComplexCmp(typ.Val)
if kerr != nil && verr != nil {
@@ -973,19 +959,6 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
return "", verr
}
if kstatus == "" && vstatus == "" {
return "", nil
} else if kstatus != "" && vstatus == "" {
return kstatus, nil
} else if vstatus != "" && kstatus == "" {
return vstatus, nil
}
// optimization, redundant
//if kstatus == vstatus { // both partial or both variant...
// return kstatus, nil
//}
var isVariant, isPartial bool
if kstatus == "variant" || vstatus == "variant" {
isVariant = true
@@ -1001,24 +974,16 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
if !isVariant && !isPartial {
return "", nil
}
if isVariant {
if isVariant && !isPartial {
return "variant", nil
}
if isPartial {
if isPartial && !isVariant {
return "partial", nil
}
//return "", fmt.Errorf("matches as both partial and variant")
return "both", nil
case KindStruct: // {a bool; b int}
if obj.Map == nil {
panic("malformed struct type")
}
if typ.Map == nil {
return "partial", nil
}
if len(obj.Ord) != len(typ.Ord) {
return "", fmt.Errorf("struct field count differs")
}
@@ -1037,13 +1002,6 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
if !ok {
panic("malformed struct order")
}
if t1 == nil {
panic("malformed struct field")
}
if t2 == nil {
isPartial = true
continue
}
status, err := t1.ComplexCmp(t2)
if err != nil {
@@ -1064,24 +1022,16 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
if !isVariant && !isPartial {
return "", nil
}
if isVariant {
if isVariant && !isPartial {
return "variant", nil
}
if isPartial {
if isPartial && !isVariant {
return "partial", nil
}
//return "", fmt.Errorf("matches as both partial and variant")
return "both", nil
case KindFunc:
if obj.Map == nil {
panic("malformed func type")
}
if typ.Map == nil {
return "partial", nil
}
if len(obj.Ord) != len(typ.Ord) {
return "", fmt.Errorf("func arg count differs")
}
@@ -1102,13 +1052,6 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
// if !ok {
// panic("malformed func order")
// }
// if t1 == nil {
// panic("malformed func arg")
// }
// if t2 == nil {
// isPartial = true
// continue
// }
//
// status, err := t1.ComplexCmp(t2)
// if err != nil {
@@ -1129,14 +1072,13 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
//if !isVariant && !isPartial {
// return "", nil
//}
//if isVariant {
//if isVariant && !isPartial {
// return "variant", nil
//}
//if isPartial {
//if isPartial && !isVariant {
// return "partial", nil
//}
//
////return "", fmt.Errorf("matches as both partial and variant")
//return "both", nil
// if we're not comparing arg names, get the two lists of types
@@ -1146,18 +1088,10 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
if !ok {
panic("malformed func order")
}
if t1 == nil {
panic("malformed func arg")
}
t2, ok := typ.Map[typ.Ord[i]]
if !ok {
panic("malformed func order")
}
if t2 == nil {
isPartial = true
continue
}
status, err := t1.ComplexCmp(t2)
if err != nil {
@@ -1175,45 +1109,36 @@ func (obj *Type) ComplexCmp(typ *Type) (string, error) {
}
}
//if obj.Out != nil && typ.Out != nil { // let a nil obj.Out in
if typ.Out != nil { // let a nil obj.Out in
status, err := obj.Out.ComplexCmp(typ.Out)
if err != nil {
return "", err
}
if status == "variant" {
isVariant = true
}
if status == "partial" {
isPartial = true
}
if status == "both" {
isVariant = true
isPartial = true
}
// NOTE: Technically, .Out could be unspecified, then this is a
// Cmp fail, not an isPartial = true, but then we'd have to
// support functions without a return value. Since we are
// functional, it is not a major problem...
} else if obj.Out != nil {
// TODO: technically, typ.Out could be unspecified, then
// this is a Cmp fail, not an isPartial = true, but then
// we'd have to support functions without a return value
// since we are functional, it is not a major problem...
status, err := obj.Out.ComplexCmp(typ.Out)
if err != nil {
return "", err
}
if status == "variant" {
isVariant = true
}
if status == "partial" {
isPartial = true
}
if status == "both" {
isVariant = true
isPartial = true
}
//} else if typ.Out != nil { // solve this in the above ComplexCmp instead!
// return "", fmt.Errorf("can't cmp from a nil type")
//}
if !isVariant && !isPartial {
return "", nil
}
if isVariant {
if isVariant && !isPartial {
return "variant", nil
}
if isPartial {
if isPartial && !isVariant {
return "partial", nil
}
//return "", fmt.Errorf("matches as both partial and variant")
return "both", nil
}