]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blobdiff - vendor/github.com/zclconf/go-cty/cty/convert/unify.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / zclconf / go-cty / cty / convert / unify.go
index bd6736b4dc2c044588fbff5df587ed06bd5954b1..53ebbfe08a15fa20666fb7a2939f2941616ce5c7 100644 (file)
@@ -21,6 +21,39 @@ func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
                return cty.NilType, nil
        }
 
+       // If all of the given types are of the same structural kind, we may be
+       // able to construct a new type that they can all be unified to, even if
+       // that is not one of the given types. We must try this before the general
+       // behavior below because in unsafe mode we can convert an object type to
+       // a subset of that type, which would be a much less useful conversion for
+       // unification purposes.
+       {
+               objectCt := 0
+               tupleCt := 0
+               dynamicCt := 0
+               for _, ty := range types {
+                       switch {
+                       case ty.IsObjectType():
+                               objectCt++
+                       case ty.IsTupleType():
+                               tupleCt++
+                       case ty == cty.DynamicPseudoType:
+                               dynamicCt++
+                       default:
+                               break
+                       }
+               }
+               switch {
+               case objectCt > 0 && (objectCt+dynamicCt) == len(types):
+                       return unifyObjectTypes(types, unsafe, dynamicCt > 0)
+               case tupleCt > 0 && (tupleCt+dynamicCt) == len(types):
+                       return unifyTupleTypes(types, unsafe, dynamicCt > 0)
+               case objectCt > 0 && tupleCt > 0:
+                       // Can never unify object and tuple types since they have incompatible kinds
+                       return cty.NilType, nil
+               }
+       }
+
        prefOrder := sortTypes(types)
 
        // sortTypes gives us an order where earlier items are preferable as
@@ -58,9 +91,224 @@ Preferences:
                return wantType, conversions
        }
 
-       // TODO: For structural types, try to invent a new type that they
-       // can all be unified to, by unifying their respective attributes.
-
        // If we fall out here, no unification is possible
        return cty.NilType, nil
 }
+
+func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
+       // If we had any dynamic types in the input here then we can't predict
+       // what path we'll take through here once these become known types, so
+       // we'll conservatively produce DynamicVal for these.
+       if hasDynamic {
+               return unifyAllAsDynamic(types)
+       }
+
+       // There are two different ways we can succeed here:
+       // - If all of the given object types have the same set of attribute names
+       //   and the corresponding types are all unifyable, then we construct that
+       //   type.
+       // - If the given object types have different attribute names or their
+       //   corresponding types are not unifyable, we'll instead try to unify
+       //   all of the attribute types together to produce a map type.
+       //
+       // Our unification behavior is intentionally stricter than our conversion
+       // behavior for subset object types because user intent is different with
+       // unification use-cases: it makes sense to allow {"foo":true} to convert
+       // to emptyobjectval, but unifying an object with an attribute with the
+       // empty object type should be an error because unifying to the empty
+       // object type would be suprising and useless.
+
+       firstAttrs := types[0].AttributeTypes()
+       for _, ty := range types[1:] {
+               thisAttrs := ty.AttributeTypes()
+               if len(thisAttrs) != len(firstAttrs) {
+                       // If number of attributes is different then there can be no
+                       // object type in common.
+                       return unifyObjectTypesToMap(types, unsafe)
+               }
+               for name := range thisAttrs {
+                       if _, ok := firstAttrs[name]; !ok {
+                               // If attribute names don't exactly match then there can be
+                               // no object type in common.
+                               return unifyObjectTypesToMap(types, unsafe)
+                       }
+               }
+       }
+
+       // If we get here then we've proven that all of the given object types
+       // have exactly the same set of attribute names, though the types may
+       // differ.
+       retAtys := make(map[string]cty.Type)
+       atysAcross := make([]cty.Type, len(types))
+       for name := range firstAttrs {
+               for i, ty := range types {
+                       atysAcross[i] = ty.AttributeType(name)
+               }
+               retAtys[name], _ = unify(atysAcross, unsafe)
+               if retAtys[name] == cty.NilType {
+                       // Cannot unify this attribute alone, which means that unification
+                       // of everything down to a map type can't be possible either.
+                       return cty.NilType, nil
+               }
+       }
+       retTy := cty.Object(retAtys)
+
+       conversions := make([]Conversion, len(types))
+       for i, ty := range types {
+               if ty.Equals(retTy) {
+                       continue
+               }
+               if unsafe {
+                       conversions[i] = GetConversionUnsafe(ty, retTy)
+               } else {
+                       conversions[i] = GetConversion(ty, retTy)
+               }
+               if conversions[i] == nil {
+                       // Shouldn't be reachable, since we were able to unify
+                       return unifyObjectTypesToMap(types, unsafe)
+               }
+       }
+
+       return retTy, conversions
+}
+
+func unifyObjectTypesToMap(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
+       // This is our fallback case for unifyObjectTypes, where we see if we can
+       // construct a map type that can accept all of the attribute types.
+
+       var atys []cty.Type
+       for _, ty := range types {
+               for _, aty := range ty.AttributeTypes() {
+                       atys = append(atys, aty)
+               }
+       }
+
+       ety, _ := unify(atys, unsafe)
+       if ety == cty.NilType {
+               return cty.NilType, nil
+       }
+
+       retTy := cty.Map(ety)
+       conversions := make([]Conversion, len(types))
+       for i, ty := range types {
+               if ty.Equals(retTy) {
+                       continue
+               }
+               if unsafe {
+                       conversions[i] = GetConversionUnsafe(ty, retTy)
+               } else {
+                       conversions[i] = GetConversion(ty, retTy)
+               }
+               if conversions[i] == nil {
+                       return cty.NilType, nil
+               }
+       }
+       return retTy, conversions
+}
+
+func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
+       // If we had any dynamic types in the input here then we can't predict
+       // what path we'll take through here once these become known types, so
+       // we'll conservatively produce DynamicVal for these.
+       if hasDynamic {
+               return unifyAllAsDynamic(types)
+       }
+
+       // There are two different ways we can succeed here:
+       // - If all of the given tuple types have the same sequence of element types
+       //   and the corresponding types are all unifyable, then we construct that
+       //   type.
+       // - If the given tuple types have different element types or their
+       //   corresponding types are not unifyable, we'll instead try to unify
+       //   all of the elements types together to produce a list type.
+
+       firstEtys := types[0].TupleElementTypes()
+       for _, ty := range types[1:] {
+               thisEtys := ty.TupleElementTypes()
+               if len(thisEtys) != len(firstEtys) {
+                       // If number of elements is different then there can be no
+                       // tuple type in common.
+                       return unifyTupleTypesToList(types, unsafe)
+               }
+       }
+
+       // If we get here then we've proven that all of the given tuple types
+       // have the same number of elements, though the types may differ.
+       retEtys := make([]cty.Type, len(firstEtys))
+       atysAcross := make([]cty.Type, len(types))
+       for idx := range firstEtys {
+               for tyI, ty := range types {
+                       atysAcross[tyI] = ty.TupleElementTypes()[idx]
+               }
+               retEtys[idx], _ = unify(atysAcross, unsafe)
+               if retEtys[idx] == cty.NilType {
+                       // Cannot unify this element alone, which means that unification
+                       // of everything down to a map type can't be possible either.
+                       return cty.NilType, nil
+               }
+       }
+       retTy := cty.Tuple(retEtys)
+
+       conversions := make([]Conversion, len(types))
+       for i, ty := range types {
+               if ty.Equals(retTy) {
+                       continue
+               }
+               if unsafe {
+                       conversions[i] = GetConversionUnsafe(ty, retTy)
+               } else {
+                       conversions[i] = GetConversion(ty, retTy)
+               }
+               if conversions[i] == nil {
+                       // Shouldn't be reachable, since we were able to unify
+                       return unifyTupleTypesToList(types, unsafe)
+               }
+       }
+
+       return retTy, conversions
+}
+
+func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
+       // This is our fallback case for unifyTupleTypes, where we see if we can
+       // construct a list type that can accept all of the element types.
+
+       var etys []cty.Type
+       for _, ty := range types {
+               for _, ety := range ty.TupleElementTypes() {
+                       etys = append(etys, ety)
+               }
+       }
+
+       ety, _ := unify(etys, unsafe)
+       if ety == cty.NilType {
+               return cty.NilType, nil
+       }
+
+       retTy := cty.List(ety)
+       conversions := make([]Conversion, len(types))
+       for i, ty := range types {
+               if ty.Equals(retTy) {
+                       continue
+               }
+               if unsafe {
+                       conversions[i] = GetConversionUnsafe(ty, retTy)
+               } else {
+                       conversions[i] = GetConversion(ty, retTy)
+               }
+               if conversions[i] == nil {
+                       // Shouldn't be reachable, since we were able to unify
+                       return unifyObjectTypesToMap(types, unsafe)
+               }
+       }
+       return retTy, conversions
+}
+
+func unifyAllAsDynamic(types []cty.Type) (cty.Type, []Conversion) {
+       conversions := make([]Conversion, len(types))
+       for i := range conversions {
+               conversions[i] = func(cty.Value) (cty.Value, error) {
+                       return cty.DynamicVal, nil
+               }
+       }
+       return cty.DynamicPseudoType, conversions
+}