Avoid stack overflow in Azure NextGen program examples

This commit is contained in:
Mikhail Shilkov 2021-02-04 15:44:20 +01:00
parent 4c8e2c222d
commit c007f1f2f8
12 changed files with 176 additions and 103 deletions

View file

@ -41,7 +41,8 @@ type Type interface {
String() string
equals(other Type, seen map[Type]struct{}) bool
conversionFrom(src Type, unifying bool) ConversionKind
conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind
string(seen map[Type]struct{}) string
unify(other Type) (Type, ConversionKind)
isType()
}
@ -65,12 +66,12 @@ func assignableFrom(dest, src Type, assignableFrom func() bool) bool {
return dest.Equals(src) || dest == DynamicType || assignableFrom()
}
func conversionFrom(dest, src Type, unifying bool, conversionFrom func() ConversionKind) ConversionKind {
func conversionFrom(dest, src Type, unifying bool, seen map[Type]struct{}, conversionFrom func() ConversionKind) ConversionKind {
if dest.Equals(src) || dest == DynamicType {
return SafeConversion
}
if src, isUnion := src.(*UnionType); isUnion {
return src.conversionTo(dest, unifying)
return src.conversionTo(dest, unifying, seen)
}
if src == DynamicType {
return UnsafeConversion
@ -93,7 +94,7 @@ func unify(t0, t1 Type, unify func() (Type, ConversionKind)) (Type, ConversionKi
// The dynamic type unifies with any other type by selecting that other type.
return t0, UnsafeConversion
default:
conversionFrom, conversionTo := t0.conversionFrom(t1, true), t1.conversionFrom(t0, true)
conversionFrom, conversionTo := t0.conversionFrom(t1, true, nil), t1.conversionFrom(t0, true, nil)
switch {
case conversionFrom < conversionTo:
return t1, conversionTo

View file

@ -88,20 +88,20 @@ func (t *ListType) AssignableFrom(src Type) bool {
// to T. If any element type is unsafely convertible to T and no element type is safely convertible to T, the
// conversion is unsafe. Otherwise, no conversion exists.
func (t *ListType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *ListType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *ListType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *ListType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
case *SetType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
case *TupleType:
conversionKind := SafeConversion
for _, src := range src.ElementTypes {
if ck := t.ElementType.conversionFrom(src, unifying); ck < conversionKind {
if ck := t.ElementType.conversionFrom(src, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -112,7 +112,11 @@ func (t *ListType) conversionFrom(src Type, unifying bool) ConversionKind {
}
func (t *ListType) String() string {
return fmt.Sprintf("list(%v)", t.ElementType)
return t.string(nil)
}
func (t *ListType) string(seen map[Type]struct{}) string {
return fmt.Sprintf("list(%s)", t.ElementType.string(seen))
}
func (t *ListType) unify(other Type) (Type, ConversionKind) {
@ -139,7 +143,7 @@ func (t *ListType) unify(other Type) (Type, ConversionKind) {
return NewListType(elementType), conversionKind
default:
// Prefer the list type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -89,18 +89,18 @@ func (t *MapType) AssignableFrom(src Type) bool {
// convertible to T. If any element type is unsafely convertible to T and no element type is safely convertible to T,
// the conversion is unsafe. Otherwise, no conversion exists.
func (t *MapType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *MapType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *MapType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *MapType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
case *ObjectType:
conversionKind := SafeConversion
for _, src := range src.Properties {
if ck := t.ElementType.conversionFrom(src, unifying); ck < conversionKind {
if ck := t.ElementType.conversionFrom(src, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -111,7 +111,11 @@ func (t *MapType) conversionFrom(src Type, unifying bool) ConversionKind {
}
func (t *MapType) String() string {
return fmt.Sprintf("map(%v)", t.ElementType)
return t.string(nil)
}
func (t *MapType) string(seen map[Type]struct{}) string {
return fmt.Sprintf("map(%s)", t.ElementType.string(seen))
}
func (t *MapType) unify(other Type) (Type, ConversionKind) {
@ -134,7 +138,7 @@ func (t *MapType) unify(other Type) (Type, ConversionKind) {
return NewMapType(elementType), conversionKind
default:
// Prefer the map type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -45,11 +45,11 @@ func (noneType) AssignableFrom(src Type) bool {
}
func (noneType) ConversionFrom(src Type) ConversionKind {
return NoneType.conversionFrom(src, false)
return NoneType.conversionFrom(src, false, nil)
}
func (noneType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(NoneType, src, unifying, func() ConversionKind {
func (noneType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(NoneType, src, unifying, seen, func() ConversionKind {
return NoConversion
})
}
@ -58,6 +58,10 @@ func (noneType) String() string {
return "none"
}
func (noneType) string(_ map[Type]struct{}) string {
return "none"
}
func (noneType) unify(other Type) (Type, ConversionKind) {
return unify(NoneType, other, func() (Type, ConversionKind) {
return NoneType, other.ConversionFrom(NoneType)

View file

@ -179,13 +179,22 @@ func (u *objectTypeUnifier) unify(t *ObjectType) {
// This conversion is always unsafe, and may fail if the map does not contain an appropriate set of keys for the
// destination type.
func (t *ObjectType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *ObjectType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *ObjectType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *ObjectType:
if seen != nil {
if _, ok := seen[t]; ok {
return NoConversion
}
} else {
seen = map[Type]struct{}{}
}
seen[t] = struct{}{}
if unifying {
var unifier objectTypeUnifier
unifier.unify(t)
@ -199,7 +208,7 @@ func (t *ObjectType) conversionFrom(src Type, unifying bool) ConversionKind {
if !ok {
src = NoneType
}
if ck := dst.conversionFrom(src, unifying); ck < conversionKind {
if ck := dst.conversionFrom(src, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -207,7 +216,7 @@ func (t *ObjectType) conversionFrom(src Type, unifying bool) ConversionKind {
case *MapType:
conversionKind := UnsafeConversion
for _, dst := range t.Properties {
if ck := dst.conversionFrom(src.ElementType, unifying); ck < conversionKind {
if ck := dst.conversionFrom(src.ElementType, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -218,20 +227,35 @@ func (t *ObjectType) conversionFrom(src Type, unifying bool) ConversionKind {
}
func (t *ObjectType) String() string {
if t.s == "" {
var properties []string
for k, v := range t.Properties {
properties = append(properties, fmt.Sprintf("%s = %v", k, v))
}
sort.Strings(properties)
return t.string(nil)
}
annotations := ""
if len(t.Annotations) != 0 {
annotations = fmt.Sprintf(", annotated(%p)", t)
}
t.s = fmt.Sprintf("object({%s}%v)", strings.Join(properties, ", "), annotations)
func (t *ObjectType) string(seen map[Type]struct{}) string {
if t.s != "" {
return t.s
}
if seen != nil {
if _, ok := seen[t]; ok {
return "..."
}
} else {
seen = map[Type]struct{}{}
}
seen[t] = struct{}{}
var properties []string
for k, v := range t.Properties {
properties = append(properties, fmt.Sprintf("%s = %s", k, v.string(seen)))
}
sort.Strings(properties)
annotations := ""
if len(t.Annotations) != 0 {
annotations = fmt.Sprintf(", annotated(%p)", t)
}
t.s = fmt.Sprintf("object({%s}%v)", strings.Join(properties, ", "), annotations)
return t.s
}
@ -258,7 +282,7 @@ func (t *ObjectType) unify(other Type) (Type, ConversionKind) {
return NewObjectType(unifier.properties), unifier.conversionKind
default:
// Otherwise, prefer the object type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -95,34 +95,34 @@ func (t *OpaqueType) AssignableFrom(src Type) bool {
})
}
func (t *OpaqueType) conversionFromImpl(src Type, unifying, checkUnsafe bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *OpaqueType) conversionFromImpl(src Type, unifying, checkUnsafe bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch {
case t == NumberType:
// src == NumberType is handled by t == src above
contract.Assert(src != NumberType)
cki := IntType.conversionFromImpl(src, unifying, false)
cki := IntType.conversionFromImpl(src, unifying, false, seen)
if cki == SafeConversion {
return SafeConversion
}
if cki == UnsafeConversion || checkUnsafe && StringType.conversionFromImpl(src, unifying, false).Exists() {
if cki == UnsafeConversion || checkUnsafe && StringType.conversionFromImpl(src, unifying, false, seen).Exists() {
return UnsafeConversion
}
return NoConversion
case t == IntType:
if checkUnsafe && NumberType.conversionFromImpl(src, unifying, true).Exists() {
if checkUnsafe && NumberType.conversionFromImpl(src, unifying, true, seen).Exists() {
return UnsafeConversion
}
return NoConversion
case t == BoolType:
if checkUnsafe && StringType.conversionFromImpl(src, unifying, false).Exists() {
if checkUnsafe && StringType.conversionFromImpl(src, unifying, false, seen).Exists() {
return UnsafeConversion
}
return NoConversion
case t == StringType:
ckb := BoolType.conversionFromImpl(src, unifying, false)
ckn := NumberType.conversionFromImpl(src, unifying, false)
ckb := BoolType.conversionFromImpl(src, unifying, false, seen)
ckn := NumberType.conversionFromImpl(src, unifying, false, seen)
if ckb == SafeConversion || ckn == SafeConversion {
return SafeConversion
}
@ -136,8 +136,8 @@ func (t *OpaqueType) conversionFromImpl(src Type, unifying, checkUnsafe bool) Co
})
}
func (t *OpaqueType) conversionFrom(src Type, unifying bool) ConversionKind {
return t.conversionFromImpl(src, unifying, true)
func (t *OpaqueType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return t.conversionFromImpl(src, unifying, true, seen)
}
// ConversionFrom returns the kind of conversion (if any) that is possible from the source type to this type.
@ -152,7 +152,7 @@ func (t *OpaqueType) conversionFrom(src Type, unifying bool) ConversionKind {
// - The bool type is unsafely convertible from string
//
func (t *OpaqueType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *OpaqueType) String() string {
@ -177,6 +177,10 @@ func (t *OpaqueType) String() string {
return t.s
}
func (t *OpaqueType) string(_ map[Type]struct{}) string {
return t.String()
}
var opaquePrecedence = []*OpaqueType{StringType, NumberType, IntType, BoolType}
func (t *OpaqueType) unify(other Type) (Type, ConversionKind) {
@ -189,10 +193,10 @@ func (t *OpaqueType) unify(other Type) (Type, ConversionKind) {
for _, goal := range opaquePrecedence {
if t == goal {
return goal, goal.conversionFrom(other, true)
return goal, goal.conversionFrom(other, true, nil)
}
if other == goal {
return goal, goal.conversionFrom(t, true)
return goal, goal.conversionFrom(t, true, nil)
}
}

View file

@ -77,23 +77,27 @@ func (t *OutputType) AssignableFrom(src Type) bool {
// output(T) is convertible from a type U, output(U), or promise(U) if U is convertible to T. If the conversion from
// U to T is unsafe, the entire conversion is unsafe. Otherwise, the conversion is safe.
func (t *OutputType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *OutputType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *OutputType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *OutputType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
case *PromiseType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
}
return t.ElementType.conversionFrom(src, unifying)
return t.ElementType.conversionFrom(src, unifying, seen)
})
}
func (t *OutputType) String() string {
return fmt.Sprintf("output(%v)", t.ElementType)
return t.string(nil)
}
func (t *OutputType) string(seen map[Type]struct{}) string {
return fmt.Sprintf("output(%s)", t.ElementType.string(seen))
}
func (t *OutputType) unify(other Type) (Type, ConversionKind) {
@ -109,7 +113,7 @@ func (t *OutputType) unify(other Type) (Type, ConversionKind) {
return NewOutputType(elementType), conversionKind
default:
// Prefer the output type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -74,20 +74,24 @@ func (t *PromiseType) AssignableFrom(src Type) bool {
// promise(T) is convertible from a type U or promise(U) if U is convertible to T. If the conversion from U to T is
// unsafe, the entire conversion is unsafe. Otherwise, the conversion is safe.
func (t *PromiseType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *PromiseType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *PromiseType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
if src, ok := src.(*PromiseType); ok {
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
}
return t.ElementType.conversionFrom(src, unifying)
return t.ElementType.conversionFrom(src, unifying, seen)
})
}
func (t *PromiseType) String() string {
return fmt.Sprintf("promise(%v)", t.ElementType)
return t.string(nil)
}
func (t *PromiseType) string(seen map[Type]struct{}) string {
return fmt.Sprintf("promise(%s)", t.ElementType.string(seen))
}
func (t *PromiseType) unify(other Type) (Type, ConversionKind) {
@ -103,7 +107,7 @@ func (t *PromiseType) unify(other Type) (Type, ConversionKind) {
return NewOutputType(elementType), conversionKind
default:
// Prefer the promise type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -72,21 +72,21 @@ func (t *SetType) AssignableFrom(src Type) bool {
// the entire conversion is unsafe; otherwise the conversion is safe. An unsafe conversion exists from list(U) or
// or tuple(U_0 ... U_N) to set(T) if a conversion exists from each U to T.
func (t *SetType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *SetType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *SetType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *SetType:
return t.ElementType.conversionFrom(src.ElementType, unifying)
return t.ElementType.conversionFrom(src.ElementType, unifying, seen)
case *ListType:
if conversionKind := t.ElementType.conversionFrom(src.ElementType, unifying); conversionKind == NoConversion {
if conversionKind := t.ElementType.conversionFrom(src.ElementType, unifying, seen); conversionKind == NoConversion {
return NoConversion
}
return UnsafeConversion
case *TupleType:
if conversionKind := NewListType(t.ElementType).conversionFrom(src, unifying); conversionKind == NoConversion {
if conversionKind := NewListType(t.ElementType).conversionFrom(src, unifying, seen); conversionKind == NoConversion {
return NoConversion
}
return UnsafeConversion
@ -96,7 +96,11 @@ func (t *SetType) conversionFrom(src Type, unifying bool) ConversionKind {
}
func (t *SetType) String() string {
return fmt.Sprintf("set(%v)", t.ElementType)
return t.string(nil)
}
func (t *SetType) string(seen map[Type]struct{}) string {
return fmt.Sprintf("set(%s)", t.ElementType.string(seen))
}
func (t *SetType) unify(other Type) (Type, ConversionKind) {
@ -123,7 +127,7 @@ func (t *SetType) unify(other Type) (Type, ConversionKind) {
return NewSetType(elementType), conversionKind
default:
// Prefer the set type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -668,20 +668,32 @@ func TestUnifyType(t *testing.T) {
}
func TestRecursiveObjectType(t *testing.T) {
props := map[string]Type{
"data": NewOutputType(IntType),
makeType := func(prop string) Type {
props := map[string]Type{
prop: NewOutputType(IntType),
}
objType := NewObjectType(props)
props["sibling"] = objType
listType := NewListType(objType)
linkedListType := NewOptionalType(listType)
props["next"] = linkedListType
return linkedListType
}
linkedListType := NewOptionalType(NewObjectType(props))
props["next"] = linkedListType
propsOther := map[string]Type{
"data": NewOutputType(IntType),
}
linkedListTypeOther := NewOptionalType(NewObjectType(propsOther))
propsOther["next"] = linkedListTypeOther
linkedListType := makeType("data")
linkedListTypeEqual := makeType("data")
linkedListTypeNonEqual := makeType("data1")
// Equals
assert.True(t, linkedListType.Equals(linkedListTypeOther))
assert.True(t, linkedListType.Equals(linkedListTypeEqual))
assert.False(t, linkedListType.Equals(linkedListTypeNonEqual))
// String conversion
// Note: 'next' property is not visible because the string value is memoized at the time of Optional creation.
assert.Equal(t, "union(list(object({data = output(int), sibling = ...})), none)", linkedListType.String())
// Convert from another type
assert.Equal(t, UnsafeConversion, linkedListType.ConversionFrom(linkedListTypeNonEqual))
// Contains eventuals
hasOutputs, hasPromises := ContainsEventuals(linkedListType)
@ -690,7 +702,7 @@ func TestRecursiveObjectType(t *testing.T) {
// Resolving eventuals
resolvedLinkedListType := ResolveOutputs(linkedListType)
data := resolvedLinkedListType.(*UnionType).ElementTypes[1].(*ObjectType).Properties["data"]
data := resolvedLinkedListType.(*UnionType).ElementTypes[0].(*ListType).ElementType.(*ObjectType).Properties["data"]
assert.True(t, data.Equals(IntType))
hasOutputs, _ = ContainsEventuals(resolvedLinkedListType)
assert.False(t, hasOutputs)

View file

@ -148,11 +148,11 @@ func (u *tupleElementUnifier) unify(t *TupleType) {
}
func (t *TupleType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *TupleType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *TupleType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
switch src := src.(type) {
case *TupleType:
// When unifying, we will unify two tuples of different length to a new tuple, where elements with matching
@ -170,14 +170,14 @@ func (t *TupleType) conversionFrom(src Type, unifying bool) ConversionKind {
conversionKind := SafeConversion
for i, dst := range t.ElementTypes {
if ck := dst.conversionFrom(src.ElementTypes[i], unifying); ck < conversionKind {
if ck := dst.conversionFrom(src.ElementTypes[i], unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
// When unifying, the conversion kind of two tuple types is the lesser of the conversion in each direction.
if unifying {
conversionTo := src.conversionFrom(t, false)
conversionTo := src.conversionFrom(t, false, seen)
if conversionTo < conversionKind {
conversionKind = conversionTo
}
@ -187,7 +187,7 @@ func (t *TupleType) conversionFrom(src Type, unifying bool) ConversionKind {
case *ListType:
conversionKind := UnsafeConversion
for _, t := range t.ElementTypes {
if ck := t.conversionFrom(src.ElementType, unifying); ck < conversionKind {
if ck := t.conversionFrom(src.ElementType, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -195,7 +195,7 @@ func (t *TupleType) conversionFrom(src Type, unifying bool) ConversionKind {
case *SetType:
conversionKind := UnsafeConversion
for _, t := range t.ElementTypes {
if ck := t.conversionFrom(src.ElementType, unifying); ck < conversionKind {
if ck := t.conversionFrom(src.ElementType, unifying, seen); ck < conversionKind {
conversionKind = ck
}
}
@ -206,10 +206,14 @@ func (t *TupleType) conversionFrom(src Type, unifying bool) ConversionKind {
}
func (t *TupleType) String() string {
return t.string(nil)
}
func (t *TupleType) string(seen map[Type]struct{}) string {
if t.s == "" {
elements := make([]string, len(t.ElementTypes))
for i, e := range t.ElementTypes {
elements[i] = e.String()
elements[i] = e.string(seen)
}
t.s = fmt.Sprintf("tuple(%s)", strings.Join(elements, ", "))
}
@ -250,7 +254,7 @@ func (t *TupleType) unify(other Type) (Type, ConversionKind) {
return NewSetType(elementType), conversionKind
default:
// Otherwise, prefer the tuple type.
return t, t.conversionFrom(other, true)
return t, t.conversionFrom(other, true, nil)
}
})
}

View file

@ -155,14 +155,14 @@ func (t *UnionType) AssignableFrom(src Type) bool {
// type is safely convertible, the conversion is safe; if no element is safely convertible but some element is unsafely
// convertible, the conversion is unsafe.
func (t *UnionType) ConversionFrom(src Type) ConversionKind {
return t.conversionFrom(src, false)
return t.conversionFrom(src, false, nil)
}
func (t *UnionType) conversionFrom(src Type, unifying bool) ConversionKind {
return conversionFrom(t, src, unifying, func() ConversionKind {
func (t *UnionType) conversionFrom(src Type, unifying bool, seen map[Type]struct{}) ConversionKind {
return conversionFrom(t, src, unifying, seen, func() ConversionKind {
var conversionKind ConversionKind
for _, t := range t.ElementTypes {
if ck := t.conversionFrom(src, unifying); ck > conversionKind {
if ck := t.conversionFrom(src, unifying, seen); ck > conversionKind {
conversionKind = ck
}
}
@ -173,10 +173,10 @@ func (t *UnionType) conversionFrom(src Type, unifying bool) ConversionKind {
// If all conversions to a dest type from a union type are safe, the conversion is safe.
// If no conversions to a dest type from a union type exist, the conversion does not exist.
// Otherwise, the conversion is unsafe.
func (t *UnionType) conversionTo(dest Type, unifying bool) ConversionKind {
func (t *UnionType) conversionTo(dest Type, unifying bool, seen map[Type]struct{}) ConversionKind {
conversionKind, exists := SafeConversion, false
for _, t := range t.ElementTypes {
switch dest.conversionFrom(t, unifying) {
switch dest.conversionFrom(t, unifying, seen) {
case SafeConversion:
exists = true
case UnsafeConversion:
@ -192,10 +192,14 @@ func (t *UnionType) conversionTo(dest Type, unifying bool) ConversionKind {
}
func (t *UnionType) String() string {
return t.string(nil)
}
func (t *UnionType) string(seen map[Type]struct{}) string {
if t.s == "" {
elements := make([]string, len(t.ElementTypes))
for i, e := range t.ElementTypes {
elements[i] = e.String()
elements[i] = e.string(seen)
}
t.s = fmt.Sprintf("union(%s)", strings.Join(elements, ", "))
}