diff options
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.go | 254 |
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 | |||
98 | func 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 | |||
175 | func 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 | |||
209 | func 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 | |||
271 | func 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 | |||
306 | func 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 | } | ||