| package convert |
| |
| import ( |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // compareTypes implements a preference order for unification. |
| // |
| // The result of this method is not useful for anything other than unification |
| // preferences, since it assumes that the caller will verify that any suggested |
| // conversion is actually possible and it is thus able to to make certain |
| // optimistic assumptions. |
| func compareTypes(a cty.Type, b cty.Type) int { |
| |
| // DynamicPseudoType always has lowest preference, because anything can |
| // convert to it (it acts as a placeholder for "any type") and we want |
| // to optimistically assume that any dynamics will converge on matching |
| // their neighbors. |
| if a == cty.DynamicPseudoType || b == cty.DynamicPseudoType { |
| if a != cty.DynamicPseudoType { |
| return -1 |
| } |
| if b != cty.DynamicPseudoType { |
| return 1 |
| } |
| return 0 |
| } |
| |
| if a.IsPrimitiveType() && b.IsPrimitiveType() { |
| // String is a supertype of all primitive types, because we can |
| // represent all primitive values as specially-formatted strings. |
| if a == cty.String || b == cty.String { |
| if a != cty.String { |
| return 1 |
| } |
| if b != cty.String { |
| return -1 |
| } |
| return 0 |
| } |
| } |
| |
| if a.IsListType() && b.IsListType() { |
| return compareTypes(a.ElementType(), b.ElementType()) |
| } |
| if a.IsSetType() && b.IsSetType() { |
| return compareTypes(a.ElementType(), b.ElementType()) |
| } |
| if a.IsMapType() && b.IsMapType() { |
| return compareTypes(a.ElementType(), b.ElementType()) |
| } |
| |
| // From this point on we may have swapped the two items in order to |
| // simplify our cases. Therefore any non-zero return after this point |
| // must be multiplied by "swap" to potentially invert the return value |
| // if needed. |
| swap := 1 |
| switch { |
| case a.IsTupleType() && b.IsListType(): |
| fallthrough |
| case a.IsObjectType() && b.IsMapType(): |
| fallthrough |
| case a.IsSetType() && b.IsTupleType(): |
| fallthrough |
| case a.IsSetType() && b.IsListType(): |
| a, b = b, a |
| swap = -1 |
| } |
| |
| if b.IsSetType() && (a.IsTupleType() || a.IsListType()) { |
| // We'll just optimistically assume that the element types are |
| // unifyable/convertible, and let a second recursive pass |
| // figure out how to make that so. |
| return -1 * swap |
| } |
| |
| if a.IsListType() && b.IsTupleType() { |
| // We'll just optimistically assume that the tuple's element types |
| // can be unified into something compatible with the list's element |
| // type. |
| return -1 * swap |
| } |
| |
| if a.IsMapType() && b.IsObjectType() { |
| // We'll just optimistically assume that the object's attribute types |
| // can be unified into something compatible with the map's element |
| // type. |
| return -1 * swap |
| } |
| |
| // For object and tuple types, comparing two types doesn't really tell |
| // the whole story because it may be possible to construct a new type C |
| // that is the supertype of both A and B by unifying each attribute/element |
| // separately. That possibility is handled by Unify as a follow-up if |
| // type sorting is insufficient to produce a valid result. |
| // |
| // Here we will take care of the simple possibilities where no new type |
| // is needed. |
| if a.IsObjectType() && b.IsObjectType() { |
| atysA := a.AttributeTypes() |
| atysB := b.AttributeTypes() |
| |
| if len(atysA) != len(atysB) { |
| return 0 |
| } |
| |
| hasASuper := false |
| hasBSuper := false |
| for k := range atysA { |
| if _, has := atysB[k]; !has { |
| return 0 |
| } |
| |
| cmp := compareTypes(atysA[k], atysB[k]) |
| if cmp < 0 { |
| hasASuper = true |
| } else if cmp > 0 { |
| hasBSuper = true |
| } |
| } |
| |
| switch { |
| case hasASuper && hasBSuper: |
| return 0 |
| case hasASuper: |
| return -1 * swap |
| case hasBSuper: |
| return 1 * swap |
| default: |
| return 0 |
| } |
| } |
| if a.IsTupleType() && b.IsTupleType() { |
| etysA := a.TupleElementTypes() |
| etysB := b.TupleElementTypes() |
| |
| if len(etysA) != len(etysB) { |
| return 0 |
| } |
| |
| hasASuper := false |
| hasBSuper := false |
| for i := range etysA { |
| cmp := compareTypes(etysA[i], etysB[i]) |
| if cmp < 0 { |
| hasASuper = true |
| } else if cmp > 0 { |
| hasBSuper = true |
| } |
| } |
| |
| switch { |
| case hasASuper && hasBSuper: |
| return 0 |
| case hasASuper: |
| return -1 * swap |
| case hasBSuper: |
| return 1 * swap |
| default: |
| return 0 |
| } |
| } |
| |
| return 0 |
| } |