Merge pull request #21174 from hashicorp/jbardin/func-types

lang/funcs: better type handling in interpolation funcs
This commit is contained in:
James Bardin 2019-05-02 09:24:36 -04:00 committed by GitHub
commit d2bc9ca406
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 359 additions and 106 deletions

View File

@ -1,6 +1,7 @@
package funcs package funcs
import ( import (
"errors"
"fmt" "fmt"
"sort" "sort"
@ -43,7 +44,7 @@ var ElementFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err) return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err)
} }
if len(etys) == 0 { if len(etys) == 0 {
return cty.DynamicPseudoType, fmt.Errorf("cannot use element function with an empty list") return cty.DynamicPseudoType, errors.New("cannot use element function with an empty list")
} }
index = index % len(etys) index = index % len(etys)
return etys[index], nil return etys[index], nil
@ -65,7 +66,7 @@ var ElementFunc = function.New(&function.Spec{
l := args[0].LengthInt() l := args[0].LengthInt()
if l == 0 { if l == 0 {
return cty.DynamicVal, fmt.Errorf("cannot use element function with an empty list") return cty.DynamicVal, errors.New("cannot use element function with an empty list")
} }
index = index % l index = index % l
@ -90,7 +91,7 @@ var LengthFunc = function.New(&function.Spec{
case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType: case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType:
return cty.Number, nil return cty.Number, nil
default: default:
return cty.Number, fmt.Errorf("argument must be a string, a collection type, or a structural type") return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
} }
}, },
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
@ -114,7 +115,7 @@ var LengthFunc = function.New(&function.Spec{
return coll.Length(), nil return coll.Length(), nil
default: default:
// Should never happen, because of the checks in our Type func above // Should never happen, because of the checks in our Type func above
return cty.UnknownVal(cty.Number), fmt.Errorf("impossible value type for length(...)") return cty.UnknownVal(cty.Number), errors.New("impossible value type for length(...)")
} }
}, },
}) })
@ -139,7 +140,7 @@ var CoalesceFunc = function.New(&function.Spec{
} }
retType, _ := convert.UnifyUnsafe(argTypes) retType, _ := convert.UnifyUnsafe(argTypes)
if retType == cty.NilType { if retType == cty.NilType {
return cty.NilType, fmt.Errorf("all arguments must have the same type") return cty.NilType, errors.New("all arguments must have the same type")
} }
return retType, nil return retType, nil
}, },
@ -159,7 +160,7 @@ var CoalesceFunc = function.New(&function.Spec{
return argVal, nil return argVal, nil
} }
return cty.NilVal, fmt.Errorf("no non-null, non-empty-string arguments") return cty.NilVal, errors.New("no non-null, non-empty-string arguments")
}, },
}) })
@ -169,32 +170,43 @@ var CoalesceListFunc = function.New(&function.Spec{
Params: []function.Parameter{}, Params: []function.Parameter{},
VarParam: &function.Parameter{ VarParam: &function.Parameter{
Name: "vals", Name: "vals",
Type: cty.List(cty.DynamicPseudoType), Type: cty.DynamicPseudoType,
AllowUnknown: true, AllowUnknown: true,
AllowDynamicType: true, AllowDynamicType: true,
AllowNull: true, AllowNull: true,
}, },
Type: func(args []cty.Value) (ret cty.Type, err error) { Type: func(args []cty.Value) (ret cty.Type, err error) {
if len(args) == 0 { if len(args) == 0 {
return cty.NilType, fmt.Errorf("at least one argument is required") return cty.NilType, errors.New("at least one argument is required")
} }
argTypes := make([]cty.Type, len(args)) argTypes := make([]cty.Type, len(args))
for i, arg := range args { for i, arg := range args {
// if any argument is unknown, we can't be certain know which type we will return
if !arg.IsKnown() {
return cty.DynamicPseudoType, nil
}
ty := arg.Type()
if !ty.IsListType() && !ty.IsTupleType() {
return cty.NilType, errors.New("coalescelist arguments must be lists or tuples")
}
argTypes[i] = arg.Type() argTypes[i] = arg.Type()
} }
retType, _ := convert.UnifyUnsafe(argTypes) last := argTypes[0]
if retType == cty.NilType { // If there are mixed types, we have to return a dynamic type.
return cty.NilType, fmt.Errorf("all arguments must have the same type") for _, next := range argTypes[1:] {
if !next.Equals(last) {
return cty.DynamicPseudoType, nil
}
} }
return retType, nil return last, nil
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
vals := make([]cty.Value, 0, len(args))
for _, arg := range args { for _, arg := range args {
if !arg.IsKnown() { if !arg.IsKnown() {
// If we run into an unknown list at some point, we can't // If we run into an unknown list at some point, we can't
@ -203,21 +215,12 @@ var CoalesceListFunc = function.New(&function.Spec{
return cty.UnknownVal(retType), nil return cty.UnknownVal(retType), nil
} }
// We already know this will succeed because of the checks in our Type func above if arg.LengthInt() > 0 {
arg, _ = convert.Convert(arg, retType) return arg, nil
it := arg.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, v)
}
if len(vals) > 0 {
return cty.ListVal(vals), nil
} }
} }
return cty.NilVal, fmt.Errorf("no non-null arguments") return cty.NilVal, errors.New("no non-null arguments")
}, },
}) })
@ -265,7 +268,7 @@ var ContainsFunc = function.New(&function.Spec{
Params: []function.Parameter{ Params: []function.Parameter{
{ {
Name: "list", Name: "list",
Type: cty.List(cty.DynamicPseudoType), Type: cty.DynamicPseudoType,
}, },
{ {
Name: "value", Name: "value",
@ -274,8 +277,14 @@ var ContainsFunc = function.New(&function.Spec{
}, },
Type: function.StaticReturnType(cty.Bool), Type: function.StaticReturnType(cty.Bool),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
arg := args[0]
ty := arg.Type()
_, err = Index(args[0], args[1]) if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() {
return cty.NilVal, errors.New("argument must be list, tuple, or set")
}
_, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1])
if err != nil { if err != nil {
return cty.False, nil return cty.False, nil
} }
@ -299,7 +308,7 @@ var IndexFunc = function.New(&function.Spec{
Type: function.StaticReturnType(cty.Number), Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) { if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
return cty.NilVal, fmt.Errorf("argument must be a list or tuple") return cty.NilVal, errors.New("argument must be a list or tuple")
} }
if !args[0].IsKnown() { if !args[0].IsKnown() {
@ -307,7 +316,7 @@ var IndexFunc = function.New(&function.Spec{
} }
if args[0].LengthInt() == 0 { // Easy path if args[0].LengthInt() == 0 { // Easy path
return cty.NilVal, fmt.Errorf("cannot search an empty list") return cty.NilVal, errors.New("cannot search an empty list")
} }
for it := args[0].ElementIterator(); it.Next(); { for it := args[0].ElementIterator(); it.Next(); {
@ -323,7 +332,7 @@ var IndexFunc = function.New(&function.Spec{
return i, nil return i, nil
} }
} }
return cty.NilVal, fmt.Errorf("item not found") return cty.NilVal, errors.New("item not found")
}, },
}) })
@ -378,7 +387,7 @@ var ChunklistFunc = function.New(&function.Spec{
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0] listVal := args[0]
if !listVal.IsWhollyKnown() { if !listVal.IsKnown() {
return cty.UnknownVal(retType), nil return cty.UnknownVal(retType), nil
} }
@ -389,7 +398,7 @@ var ChunklistFunc = function.New(&function.Spec{
} }
if size < 0 { if size < 0 {
return cty.NilVal, fmt.Errorf("the size argument must be positive") return cty.NilVal, errors.New("the size argument must be positive")
} }
output := make([]cty.Value, 0) output := make([]cty.Value, 0)
@ -437,11 +446,14 @@ var FlattenFunc = function.New(&function.Spec{
argTy := args[0].Type() argTy := args[0].Type()
if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() { if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() {
return cty.NilType, fmt.Errorf("can only flatten lists, sets and tuples") return cty.NilType, errors.New("can only flatten lists, sets and tuples")
}
retVal, known := flattener(args[0])
if !known {
return cty.DynamicPseudoType, nil
} }
outputList := make([]cty.Value, 0)
retVal := flattener(outputList, args[0])
tys := make([]cty.Type, len(retVal)) tys := make([]cty.Type, len(retVal))
for i, ty := range retVal { for i, ty := range retVal {
tys[i] = ty.Type() tys[i] = ty.Type()
@ -450,30 +462,41 @@ var FlattenFunc = function.New(&function.Spec{
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0] inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if inputList.LengthInt() == 0 { if inputList.LengthInt() == 0 {
return cty.EmptyTupleVal, nil return cty.EmptyTupleVal, nil
} }
outputList := make([]cty.Value, 0)
return cty.TupleVal(flattener(outputList, inputList)), nil out, known := flattener(inputList)
if !known {
return cty.UnknownVal(retType), nil
}
return cty.TupleVal(out), nil
}, },
}) })
// Flatten until it's not a cty.List // Flatten until it's not a cty.List, and return whether the value is known.
func flattener(finalList []cty.Value, flattenList cty.Value) []cty.Value { // We can flatten lists with unknown values, as long as they are not
// lists themselves.
func flattener(flattenList cty.Value) ([]cty.Value, bool) {
out := make([]cty.Value, 0)
for it := flattenList.ElementIterator(); it.Next(); { for it := flattenList.ElementIterator(); it.Next(); {
_, val := it.Element() _, val := it.Element()
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() { if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
finalList = flattener(finalList, val) if !val.IsKnown() {
return out, false
}
res, known := flattener(val)
if !known {
return res, known
}
out = append(out, res...)
} else { } else {
finalList = append(finalList, val) out = append(out, val)
} }
} }
return finalList return out, true
} }
// KeysFunc contructs a function that takes a map and returns a sorted list of the map keys. // KeysFunc contructs a function that takes a map and returns a sorted list of the map keys.
@ -561,7 +584,7 @@ var ListFunc = function.New(&function.Spec{
}, },
Type: func(args []cty.Value) (ret cty.Type, err error) { Type: func(args []cty.Value) (ret cty.Type, err error) {
if len(args) == 0 { if len(args) == 0 {
return cty.NilType, fmt.Errorf("at least one argument is required") return cty.NilType, errors.New("at least one argument is required")
} }
argTypes := make([]cty.Type, len(args)) argTypes := make([]cty.Type, len(args))
@ -572,7 +595,7 @@ var ListFunc = function.New(&function.Spec{
retType, _ := convert.UnifyUnsafe(argTypes) retType, _ := convert.UnifyUnsafe(argTypes)
if retType == cty.NilType { if retType == cty.NilType {
return cty.NilType, fmt.Errorf("all arguments must have the same type") return cty.NilType, errors.New("all arguments must have the same type")
} }
return cty.List(retType), nil return cty.List(retType), nil
@ -666,7 +689,7 @@ var LookupFunc = function.New(&function.Spec{
case ty.Equals(cty.Number): case ty.Equals(cty.Number):
return cty.NumberVal(v.AsBigFloat()), nil return cty.NumberVal(v.AsBigFloat()), nil
default: default:
return cty.NilVal, fmt.Errorf("lookup() can only be used with flat lists") return cty.NilVal, errors.New("lookup() can only be used with flat lists")
} }
} }
} }
@ -712,7 +735,7 @@ var MapFunc = function.New(&function.Spec{
valType, _ := convert.UnifyUnsafe(argTypes) valType, _ := convert.UnifyUnsafe(argTypes)
if valType == cty.NilType { if valType == cty.NilType {
return cty.NilType, fmt.Errorf("all arguments must have the same type") return cty.NilType, errors.New("all arguments must have the same type")
} }
return cty.Map(valType), nil return cty.Map(valType), nil
@ -777,7 +800,7 @@ var MatchkeysFunc = function.New(&function.Spec{
}, },
Type: func(args []cty.Value) (cty.Type, error) { Type: func(args []cty.Value) (cty.Type, error) {
if !args[1].Type().Equals(args[2].Type()) { if !args[1].Type().Equals(args[2].Type()) {
return cty.NilType, fmt.Errorf("lists must be of the same type") return cty.NilType, errors.New("lists must be of the same type")
} }
return args[0].Type(), nil return args[0].Type(), nil
@ -788,7 +811,7 @@ var MatchkeysFunc = function.New(&function.Spec{
} }
if args[0].LengthInt() != args[1].LengthInt() { if args[0].LengthInt() != args[1].LengthInt() {
return cty.ListValEmpty(retType.ElementType()), fmt.Errorf("length of keys and values should be equal") return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal")
} }
output := make([]cty.Value, 0) output := make([]cty.Value, 0)
@ -923,7 +946,7 @@ var SetProductFunc = function.New(&function.Spec{
}, },
Type: func(args []cty.Value) (retType cty.Type, err error) { Type: func(args []cty.Value) (retType cty.Type, err error) {
if len(args) < 2 { if len(args) < 2 {
return cty.NilType, fmt.Errorf("at least two arguments are required") return cty.NilType, errors.New("at least two arguments are required")
} }
listCount := 0 listCount := 0
@ -1040,7 +1063,7 @@ var SliceFunc = function.New(&function.Spec{
Params: []function.Parameter{ Params: []function.Parameter{
{ {
Name: "list", Name: "list",
Type: cty.List(cty.DynamicPseudoType), Type: cty.DynamicPseudoType,
}, },
{ {
Name: "startIndex", Name: "startIndex",
@ -1052,50 +1075,71 @@ var SliceFunc = function.New(&function.Spec{
}, },
}, },
Type: func(args []cty.Value) (cty.Type, error) { Type: func(args []cty.Value) (cty.Type, error) {
return args[0].Type(), nil arg := args[0]
argTy := arg.Type()
if !argTy.IsListType() && !argTy.IsTupleType() {
return cty.NilType, errors.New("cannot slice a set, because its elements do not have indices; use the tolist function to force conversion to list if the ordering of the result is not important")
}
if argTy.IsListType() {
return argTy, nil
}
startIndex, endIndex, err := sliceIndexes(args, args[0].LengthInt())
if err != nil {
return cty.NilType, err
}
return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0] inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var startIndex, endIndex int
if err = gocty.FromCtyValue(args[1], &startIndex); err != nil { startIndex, endIndex, err := sliceIndexes(args, inputList.LengthInt())
return cty.NilVal, fmt.Errorf("invalid start index: %s", err) if err != nil {
} return cty.NilVal, err
if err = gocty.FromCtyValue(args[2], &endIndex); err != nil {
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
} }
if startIndex < 0 { if endIndex-startIndex == 0 {
return cty.NilVal, fmt.Errorf("from index must be >= 0") if retType.IsTupleType() {
} return cty.EmptyTupleVal, nil
if endIndex > inputList.LengthInt() {
return cty.NilVal, fmt.Errorf("to index must be <= length of the input list")
}
if startIndex > endIndex {
return cty.NilVal, fmt.Errorf("from index must be <= to index")
}
var outputList []cty.Value
i := 0
for it := inputList.ElementIterator(); it.Next(); {
_, v := it.Element()
if i >= startIndex && i < endIndex {
outputList = append(outputList, v)
} }
i++
}
if len(outputList) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil return cty.ListValEmpty(retType.ElementType()), nil
} }
outputList := inputList.AsValueSlice()[startIndex:endIndex]
if retType.IsTupleType() {
return cty.TupleVal(outputList), nil
}
return cty.ListVal(outputList), nil return cty.ListVal(outputList), nil
}, },
}) })
func sliceIndexes(args []cty.Value, max int) (int, int, error) {
var startIndex, endIndex int
if err := gocty.FromCtyValue(args[1], &startIndex); err != nil {
return 0, 0, fmt.Errorf("invalid start index: %s", err)
}
if err := gocty.FromCtyValue(args[2], &endIndex); err != nil {
return 0, 0, fmt.Errorf("invalid start index: %s", err)
}
if startIndex < 0 {
return 0, 0, errors.New("from index must be greater than or equal to 0")
}
if endIndex > max {
return 0, 0, errors.New("to index must be less than or equal to the length of the input list")
}
if startIndex > endIndex {
return 0, 0, errors.New("from index must be less than or equal to index")
}
return startIndex, endIndex, nil
}
// TransposeFunc contructs a function that takes a map of lists of strings and // TransposeFunc contructs a function that takes a map of lists of strings and
// swaps the keys and values to produce a new map of lists of strings. // swaps the keys and values to produce a new map of lists of strings.
var TransposeFunc = function.New(&function.Spec{ var TransposeFunc = function.New(&function.Spec{
@ -1120,7 +1164,7 @@ var TransposeFunc = function.New(&function.Spec{
for iter := inVal.ElementIterator(); iter.Next(); { for iter := inVal.ElementIterator(); iter.Next(); {
_, val := iter.Element() _, val := iter.Element()
if !val.Type().Equals(cty.String) { if !val.Type().Equals(cty.String) {
return cty.MapValEmpty(cty.List(cty.String)), fmt.Errorf("input must be a map of lists of strings") return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings")
} }
outKey := val.AsString() outKey := val.AsString()
@ -1180,7 +1224,7 @@ var ValuesFunc = function.New(&function.Spec{
} }
return cty.Tuple(tys), nil return cty.Tuple(tys), nil
} }
return cty.NilType, fmt.Errorf("values() requires a map as the first argument") return cty.NilType, errors.New("values() requires a map as the first argument")
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
mapVar := args[0] mapVar := args[0]
@ -1247,7 +1291,7 @@ var ZipmapFunc = function.New(&function.Spec{
return cty.Object(atys), nil return cty.Object(atys), nil
default: default:
return cty.NilType, fmt.Errorf("values argument must be a list or tuple value") return cty.NilType, errors.New("values argument must be a list or tuple value")
} }
}, },
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {

View File

@ -417,7 +417,7 @@ func TestCoalesceList(t *testing.T) {
}), }),
}, },
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.StringVal("1"), cty.StringVal("2"), cty.NumberIntVal(1), cty.NumberIntVal(2),
}), }),
false, false,
}, },
@ -476,7 +476,7 @@ func TestCoalesceList(t *testing.T) {
cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String),
cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.List(cty.String)),
}, },
cty.UnknownVal(cty.List(cty.String)), cty.DynamicVal,
false, false,
}, },
{ // unknown list { // unknown list
@ -486,13 +486,63 @@ func TestCoalesceList(t *testing.T) {
cty.StringVal("third"), cty.StringVal("fourth"), cty.StringVal("third"), cty.StringVal("fourth"),
}), }),
}, },
cty.UnknownVal(cty.List(cty.String)), cty.DynamicVal,
false, false,
}, },
{ // unknown tuple
[]cty.Value{
cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})),
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.DynamicVal,
false,
},
{ // empty tuple
[]cty.Value{
cty.EmptyTupleVal,
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
false,
},
{ // tuple value
[]cty.Value{
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.NumberIntVal(2),
}),
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.NumberIntVal(2),
}),
false,
},
{ // reject set value
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.NilVal,
true,
},
} }
for _, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("coalescelist(%#v)", test.Values), func(t *testing.T) { t.Run(fmt.Sprintf("%d-coalescelist(%#v)", i, test.Values), func(t *testing.T) {
got, err := CoalesceList(test.Values...) got, err := CoalesceList(test.Values...)
if test.Err { if test.Err {
@ -671,6 +721,26 @@ func TestContains(t *testing.T) {
cty.BoolVal(true), cty.BoolVal(true),
false, false,
}, },
{ // set val
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("brown"),
cty.StringVal("fox"),
}),
cty.StringVal("quick"),
cty.BoolVal(true),
false,
},
{ // tuple val
cty.TupleVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("brown"),
cty.NumberIntVal(3),
}),
cty.NumberIntVal(3),
cty.BoolVal(true),
false,
},
} }
for _, test := range tests { for _, test := range tests {
@ -993,13 +1063,29 @@ func TestChunklist(t *testing.T) {
cty.UnknownVal(cty.String), cty.UnknownVal(cty.String),
}), }),
cty.NumberIntVal(1), cty.NumberIntVal(1),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("b"),
}),
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
}),
false,
},
{
cty.UnknownVal(cty.List(cty.String)),
cty.NumberIntVal(1),
cty.UnknownVal(cty.List(cty.List(cty.String))), cty.UnknownVal(cty.List(cty.List(cty.String))),
false, false,
}, },
} }
for _, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("chunklist(%#v, %#v)", test.List, test.Size), func(t *testing.T) { t.Run(fmt.Sprintf("%d-chunklist(%#v, %#v)", i, test.List, test.Size), func(t *testing.T) {
got, err := Chunklist(test.List, test.Size) got, err := Chunklist(test.List, test.Size)
if test.Err { if test.Err {
@ -1043,6 +1129,44 @@ func TestFlatten(t *testing.T) {
}), }),
false, false,
}, },
// handle single elements as arguments
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.StringVal("c"),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}), false,
},
// handle single elements and mixed primitive types as arguments
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.StringVal("c"),
cty.TupleVal([]cty.Value{
cty.StringVal("x"),
cty.NumberIntVal(1),
}),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
cty.StringVal("x"),
cty.NumberIntVal(1),
}),
false,
},
// Primitive unknowns should still be flattened to a tuple
{ {
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
@ -1054,8 +1178,26 @@ func TestFlatten(t *testing.T) {
cty.StringVal("d"), cty.StringVal("d"),
}), }),
}), }),
cty.DynamicVal, cty.TupleVal([]cty.Value{
false, cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
cty.StringVal("d"),
}), false,
},
// An unknown series should return an unknown dynamic value
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.List(cty.String)),
cty.StringVal("d"),
}),
}),
cty.UnknownVal(cty.DynamicPseudoType), false,
}, },
{ {
cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String),
@ -1102,8 +1244,8 @@ func TestFlatten(t *testing.T) {
}, },
} }
for _, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("flatten(%#v)", test.List), func(t *testing.T) { t.Run(fmt.Sprintf("%d-flatten(%#v)", i, test.List), func(t *testing.T) {
got, err := Flatten(test.List) got, err := Flatten(test.List)
if test.Err { if test.Err {
@ -2354,6 +2496,11 @@ func TestSlice(t *testing.T) {
cty.StringVal("a"), cty.StringVal("a"),
cty.UnknownVal(cty.String), cty.UnknownVal(cty.String),
}) })
tuple := cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.NumberIntVal(1),
cty.UnknownVal(cty.List(cty.String)),
})
tests := []struct { tests := []struct {
List cty.Value List cty.Value
StartIndex cty.Value StartIndex cty.Value
@ -2370,10 +2517,24 @@ func TestSlice(t *testing.T) {
}), }),
false, false,
}, },
{ // unknowns in the list { // slice only an unknown value
listWithUnknowns, listWithUnknowns,
cty.NumberIntVal(1), cty.NumberIntVal(1),
cty.NumberIntVal(2), cty.NumberIntVal(2),
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
false,
},
{ // slice multiple values, which contain an unknown
listWithUnknowns,
cty.NumberIntVal(0),
cty.NumberIntVal(2),
listWithUnknowns,
false,
},
{ // an unknown list should be slicable, returning an unknown list
cty.UnknownVal(cty.List(cty.String)),
cty.NumberIntVal(0),
cty.NumberIntVal(2),
cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.List(cty.String)),
false, false,
}, },
@ -2414,10 +2575,44 @@ func TestSlice(t *testing.T) {
cty.NilVal, cty.NilVal,
true, true,
}, },
{ // sets are not slice-able
cty.SetVal([]cty.Value{
cty.StringVal("x"),
cty.StringVal("y"),
}),
cty.NumberIntVal(0),
cty.NumberIntVal(0),
cty.NilVal,
true,
},
{ // tuple slice
tuple,
cty.NumberIntVal(1),
cty.NumberIntVal(3),
cty.TupleVal([]cty.Value{
cty.NumberIntVal(1),
cty.UnknownVal(cty.List(cty.String)),
}),
false,
},
{ // empty list slice
listOfStrings,
cty.NumberIntVal(2),
cty.NumberIntVal(2),
cty.ListValEmpty(cty.String),
false,
},
{ // empty tuple slice
tuple,
cty.NumberIntVal(3),
cty.NumberIntVal(3),
cty.EmptyTupleVal,
false,
},
} }
for _, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("slice(%#v, %#v, %#v)", test.List, test.StartIndex, test.EndIndex), func(t *testing.T) { t.Run(fmt.Sprintf("%d-slice(%#v, %#v, %#v)", i, test.List, test.StartIndex, test.EndIndex), func(t *testing.T) {
got, err := Slice(test.List, test.StartIndex, test.EndIndex) got, err := Slice(test.List, test.StartIndex, test.EndIndex)
if test.Err { if test.Err {

View File

@ -155,7 +155,7 @@ func TestFunctions(t *testing.T) {
{ {
`coalescelist(["first", "second"], ["third", "fourth"])`, `coalescelist(["first", "second"], ["third", "fourth"])`,
cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{
cty.StringVal("first"), cty.StringVal("second"), cty.StringVal("first"), cty.StringVal("second"),
}), }),
}, },
@ -163,12 +163,19 @@ func TestFunctions(t *testing.T) {
"coalescelist": { "coalescelist": {
{ {
`coalescelist(["a", "b"], ["c", "d"])`, `coalescelist(list("a", "b"), list("c", "d"))`,
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.StringVal("a"), cty.StringVal("a"),
cty.StringVal("b"), cty.StringVal("b"),
}), }),
}, },
{
`coalescelist(["a", "b"], ["c", "d"])`,
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
},
}, },
"compact": { "compact": {
@ -592,11 +599,18 @@ func TestFunctions(t *testing.T) {
"slice": { "slice": {
{ {
`slice(["a", "b", "c", "d"], 1, 3)`, // force a list type here for testing
`slice(list("a", "b", "c", "d"), 1, 3)`,
cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{
cty.StringVal("b"), cty.StringVal("c"), cty.StringVal("b"), cty.StringVal("c"),
}), }),
}, },
{
`slice(["a", "b", 3, 4], 1, 3)`,
cty.TupleVal([]cty.Value{
cty.StringVal("b"), cty.NumberIntVal(3),
}),
},
}, },
"sort": { "sort": {