aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/convert/compare_types.go
blob: d84f6ac1049f6a85b8d0165df8c00f544bcad837 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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
}