4 "github.com/zclconf/go-cty/cty"
7 // The current unify implementation is somewhat inefficient, but we accept this
8 // under the assumption that it will generally be used with small numbers of
9 // types and with types of reasonable complexity. However, it does have a
10 // "happy path" where all of the given types are equal.
12 // This function is likely to have poor performance in cases where any given
13 // types are very complex (lots of deeply-nested structures) or if the list
14 // of types itself is very large. In particular, it will walk the nested type
15 // structure under the given types several times, especially when given a
16 // list of types for which unification is not possible, since each permutation
17 // will be tried to determine that result.
18 func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
21 return cty.NilType, nil
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.
34 for _, ty := range types {
36 case ty.IsObjectType():
38 case ty.IsTupleType():
40 case ty == cty.DynamicPseudoType:
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
57 prefOrder := sortTypes(types)
59 // sortTypes gives us an order where earlier items are preferable as
60 // our result type. We'll now walk through these and choose the first
61 // one we encounter for which conversions exist for all source types.
62 conversions := make([]Conversion, len(types))
64 for _, wantTypeIdx := range prefOrder {
65 wantType := types[wantTypeIdx]
66 for i, tryType := range types {
68 // Don't need to convert our wanted type to itself
73 if tryType.Equals(wantType) {
79 conversions[i] = GetConversionUnsafe(tryType, wantType)
81 conversions[i] = GetConversion(tryType, wantType)
84 if conversions[i] == nil {
85 // wantType is not a suitable unification type, so we'll
86 // try the next one in our preference order.
91 return wantType, conversions
94 // If we fall out here, no unification is possible
95 return cty.NilType, nil
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.
103 return unifyAllAsDynamic(types)
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
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.
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.
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)
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)
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
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)
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
154 retTy := cty.Object(retAtys)
156 conversions := make([]Conversion, len(types))
157 for i, ty := range types {
158 if ty.Equals(retTy) {
162 conversions[i] = GetConversionUnsafe(ty, retTy)
164 conversions[i] = GetConversion(ty, retTy)
166 if conversions[i] == nil {
167 // Shouldn't be reachable, since we were able to unify
168 return unifyObjectTypesToMap(types, unsafe)
172 return retTy, conversions
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.
180 for _, ty := range types {
181 for _, aty := range ty.AttributeTypes() {
182 atys = append(atys, aty)
186 ety, _ := unify(atys, unsafe)
187 if ety == cty.NilType {
188 return cty.NilType, nil
191 retTy := cty.Map(ety)
192 conversions := make([]Conversion, len(types))
193 for i, ty := range types {
194 if ty.Equals(retTy) {
198 conversions[i] = GetConversionUnsafe(ty, retTy)
200 conversions[i] = GetConversion(ty, retTy)
202 if conversions[i] == nil {
203 return cty.NilType, nil
206 return retTy, conversions
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.
214 return unifyAllAsDynamic(types)
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
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.
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)
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]
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
250 retTy := cty.Tuple(retEtys)
252 conversions := make([]Conversion, len(types))
253 for i, ty := range types {
254 if ty.Equals(retTy) {
258 conversions[i] = GetConversionUnsafe(ty, retTy)
260 conversions[i] = GetConversion(ty, retTy)
262 if conversions[i] == nil {
263 // Shouldn't be reachable, since we were able to unify
264 return unifyTupleTypesToList(types, unsafe)
268 return retTy, conversions
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.
276 for _, ty := range types {
277 for _, ety := range ty.TupleElementTypes() {
278 etys = append(etys, ety)
282 ety, _ := unify(etys, unsafe)
283 if ety == cty.NilType {
284 return cty.NilType, nil
287 retTy := cty.List(ety)
288 conversions := make([]Conversion, len(types))
289 for i, ty := range types {
290 if ty.Equals(retTy) {
294 conversions[i] = GetConversionUnsafe(ty, retTy)
296 conversions[i] = GetConversion(ty, retTy)
298 if conversions[i] == nil {
299 // Shouldn't be reachable, since we were able to unify
300 return unifyObjectTypesToMap(types, unsafe)
303 return retTy, conversions
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
313 return cty.DynamicPseudoType, conversions