aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/convert/unify.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/zclconf/go-cty/cty/convert/unify.go')
-rw-r--r--vendor/github.com/zclconf/go-cty/cty/convert/unify.go254
1 files changed, 251 insertions, 3 deletions
diff --git a/vendor/github.com/zclconf/go-cty/cty/convert/unify.go b/vendor/github.com/zclconf/go-cty/cty/convert/unify.go
index bd6736b..53ebbfe 100644
--- a/vendor/github.com/zclconf/go-cty/cty/convert/unify.go
+++ b/vendor/github.com/zclconf/go-cty/cty/convert/unify.go
@@ -21,6 +21,39 @@ func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
21 return cty.NilType, nil 21 return cty.NilType, nil
22 } 22 }
23 23
24 // If all of the given types are of the same structural kind, we may be
25 // able to construct a new type that they can all be unified to, even if
26 // that is not one of the given types. We must try this before the general
27 // behavior below because in unsafe mode we can convert an object type to
28 // a subset of that type, which would be a much less useful conversion for
29 // unification purposes.
30 {
31 objectCt := 0
32 tupleCt := 0
33 dynamicCt := 0
34 for _, ty := range types {
35 switch {
36 case ty.IsObjectType():
37 objectCt++
38 case ty.IsTupleType():
39 tupleCt++
40 case ty == cty.DynamicPseudoType:
41 dynamicCt++
42 default:
43 break
44 }
45 }
46 switch {
47 case objectCt > 0 && (objectCt+dynamicCt) == len(types):
48 return unifyObjectTypes(types, unsafe, dynamicCt > 0)
49 case tupleCt > 0 && (tupleCt+dynamicCt) == len(types):
50 return unifyTupleTypes(types, unsafe, dynamicCt > 0)
51 case objectCt > 0 && tupleCt > 0:
52 // Can never unify object and tuple types since they have incompatible kinds
53 return cty.NilType, nil
54 }
55 }
56
24 prefOrder := sortTypes(types) 57 prefOrder := sortTypes(types)
25 58
26 // sortTypes gives us an order where earlier items are preferable as 59 // sortTypes gives us an order where earlier items are preferable as
@@ -58,9 +91,224 @@ Preferences:
58 return wantType, conversions 91 return wantType, conversions
59 } 92 }
60 93
61 // TODO: For structural types, try to invent a new type that they
62 // can all be unified to, by unifying their respective attributes.
63
64 // If we fall out here, no unification is possible 94 // If we fall out here, no unification is possible
65 return cty.NilType, nil 95 return cty.NilType, nil
66} 96}
97
98func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
99 // If we had any dynamic types in the input here then we can't predict
100 // what path we'll take through here once these become known types, so
101 // we'll conservatively produce DynamicVal for these.
102 if hasDynamic {
103 return unifyAllAsDynamic(types)
104 }
105
106 // There are two different ways we can succeed here:
107 // - If all of the given object types have the same set of attribute names
108 // and the corresponding types are all unifyable, then we construct that
109 // type.
110 // - If the given object types have different attribute names or their
111 // corresponding types are not unifyable, we'll instead try to unify
112 // all of the attribute types together to produce a map type.
113 //
114 // Our unification behavior is intentionally stricter than our conversion
115 // behavior for subset object types because user intent is different with
116 // unification use-cases: it makes sense to allow {"foo":true} to convert
117 // to emptyobjectval, but unifying an object with an attribute with the
118 // empty object type should be an error because unifying to the empty
119 // object type would be suprising and useless.
120
121 firstAttrs := types[0].AttributeTypes()
122 for _, ty := range types[1:] {
123 thisAttrs := ty.AttributeTypes()
124 if len(thisAttrs) != len(firstAttrs) {
125 // If number of attributes is different then there can be no
126 // object type in common.
127 return unifyObjectTypesToMap(types, unsafe)
128 }
129 for name := range thisAttrs {
130 if _, ok := firstAttrs[name]; !ok {
131 // If attribute names don't exactly match then there can be
132 // no object type in common.
133 return unifyObjectTypesToMap(types, unsafe)
134 }
135 }
136 }
137
138 // If we get here then we've proven that all of the given object types
139 // have exactly the same set of attribute names, though the types may
140 // differ.
141 retAtys := make(map[string]cty.Type)
142 atysAcross := make([]cty.Type, len(types))
143 for name := range firstAttrs {
144 for i, ty := range types {
145 atysAcross[i] = ty.AttributeType(name)
146 }
147 retAtys[name], _ = unify(atysAcross, unsafe)
148 if retAtys[name] == cty.NilType {
149 // Cannot unify this attribute alone, which means that unification
150 // of everything down to a map type can't be possible either.
151 return cty.NilType, nil
152 }
153 }
154 retTy := cty.Object(retAtys)
155
156 conversions := make([]Conversion, len(types))
157 for i, ty := range types {
158 if ty.Equals(retTy) {
159 continue
160 }
161 if unsafe {
162 conversions[i] = GetConversionUnsafe(ty, retTy)
163 } else {
164 conversions[i] = GetConversion(ty, retTy)
165 }
166 if conversions[i] == nil {
167 // Shouldn't be reachable, since we were able to unify
168 return unifyObjectTypesToMap(types, unsafe)
169 }
170 }
171
172 return retTy, conversions
173}
174
175func unifyObjectTypesToMap(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
176 // This is our fallback case for unifyObjectTypes, where we see if we can
177 // construct a map type that can accept all of the attribute types.
178
179 var atys []cty.Type
180 for _, ty := range types {
181 for _, aty := range ty.AttributeTypes() {
182 atys = append(atys, aty)
183 }
184 }
185
186 ety, _ := unify(atys, unsafe)
187 if ety == cty.NilType {
188 return cty.NilType, nil
189 }
190
191 retTy := cty.Map(ety)
192 conversions := make([]Conversion, len(types))
193 for i, ty := range types {
194 if ty.Equals(retTy) {
195 continue
196 }
197 if unsafe {
198 conversions[i] = GetConversionUnsafe(ty, retTy)
199 } else {
200 conversions[i] = GetConversion(ty, retTy)
201 }
202 if conversions[i] == nil {
203 return cty.NilType, nil
204 }
205 }
206 return retTy, conversions
207}
208
209func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
210 // If we had any dynamic types in the input here then we can't predict
211 // what path we'll take through here once these become known types, so
212 // we'll conservatively produce DynamicVal for these.
213 if hasDynamic {
214 return unifyAllAsDynamic(types)
215 }
216
217 // There are two different ways we can succeed here:
218 // - If all of the given tuple types have the same sequence of element types
219 // and the corresponding types are all unifyable, then we construct that
220 // type.
221 // - If the given tuple types have different element types or their
222 // corresponding types are not unifyable, we'll instead try to unify
223 // all of the elements types together to produce a list type.
224
225 firstEtys := types[0].TupleElementTypes()
226 for _, ty := range types[1:] {
227 thisEtys := ty.TupleElementTypes()
228 if len(thisEtys) != len(firstEtys) {
229 // If number of elements is different then there can be no
230 // tuple type in common.
231 return unifyTupleTypesToList(types, unsafe)
232 }
233 }
234
235 // If we get here then we've proven that all of the given tuple types
236 // have the same number of elements, though the types may differ.
237 retEtys := make([]cty.Type, len(firstEtys))
238 atysAcross := make([]cty.Type, len(types))
239 for idx := range firstEtys {
240 for tyI, ty := range types {
241 atysAcross[tyI] = ty.TupleElementTypes()[idx]
242 }
243 retEtys[idx], _ = unify(atysAcross, unsafe)
244 if retEtys[idx] == cty.NilType {
245 // Cannot unify this element alone, which means that unification
246 // of everything down to a map type can't be possible either.
247 return cty.NilType, nil
248 }
249 }
250 retTy := cty.Tuple(retEtys)
251
252 conversions := make([]Conversion, len(types))
253 for i, ty := range types {
254 if ty.Equals(retTy) {
255 continue
256 }
257 if unsafe {
258 conversions[i] = GetConversionUnsafe(ty, retTy)
259 } else {
260 conversions[i] = GetConversion(ty, retTy)
261 }
262 if conversions[i] == nil {
263 // Shouldn't be reachable, since we were able to unify
264 return unifyTupleTypesToList(types, unsafe)
265 }
266 }
267
268 return retTy, conversions
269}
270
271func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
272 // This is our fallback case for unifyTupleTypes, where we see if we can
273 // construct a list type that can accept all of the element types.
274
275 var etys []cty.Type
276 for _, ty := range types {
277 for _, ety := range ty.TupleElementTypes() {
278 etys = append(etys, ety)
279 }
280 }
281
282 ety, _ := unify(etys, unsafe)
283 if ety == cty.NilType {
284 return cty.NilType, nil
285 }
286
287 retTy := cty.List(ety)
288 conversions := make([]Conversion, len(types))
289 for i, ty := range types {
290 if ty.Equals(retTy) {
291 continue
292 }
293 if unsafe {
294 conversions[i] = GetConversionUnsafe(ty, retTy)
295 } else {
296 conversions[i] = GetConversion(ty, retTy)
297 }
298 if conversions[i] == nil {
299 // Shouldn't be reachable, since we were able to unify
300 return unifyObjectTypesToMap(types, unsafe)
301 }
302 }
303 return retTy, conversions
304}
305
306func unifyAllAsDynamic(types []cty.Type) (cty.Type, []Conversion) {
307 conversions := make([]Conversion, len(types))
308 for i := range conversions {
309 conversions[i] = func(cty.Value) (cty.Value, error) {
310 return cty.DynamicVal, nil
311 }
312 }
313 return cty.DynamicPseudoType, conversions
314}