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
import (
"errors"
"fmt"
"sort"
@ -43,7 +44,7 @@ var ElementFunc = function.New(&function.Spec{
return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err)
}
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)
return etys[index], nil
@ -65,7 +66,7 @@ var ElementFunc = function.New(&function.Spec{
l := args[0].LengthInt()
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
@ -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:
return cty.Number, nil
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) {
@ -114,7 +115,7 @@ var LengthFunc = function.New(&function.Spec{
return coll.Length(), nil
default:
// 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)
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
},
@ -159,7 +160,7 @@ var CoalesceFunc = function.New(&function.Spec{
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{},
VarParam: &function.Parameter{
Name: "vals",
Type: cty.List(cty.DynamicPseudoType),
Type: cty.DynamicPseudoType,
AllowUnknown: true,
AllowDynamicType: true,
AllowNull: true,
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
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))
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()
}
retType, _ := convert.UnifyUnsafe(argTypes)
if retType == cty.NilType {
return cty.NilType, fmt.Errorf("all arguments must have the same type")
last := argTypes[0]
// If there are mixed types, we have to return a dynamic 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) {
vals := make([]cty.Value, 0, len(args))
for _, arg := range args {
if !arg.IsKnown() {
// 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
}
// We already know this will succeed because of the checks in our Type func above
arg, _ = convert.Convert(arg, retType)
it := arg.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, v)
}
if len(vals) > 0 {
return cty.ListVal(vals), nil
if arg.LengthInt() > 0 {
return arg, 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{
{
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
Type: cty.DynamicPseudoType,
},
{
Name: "value",
@ -274,8 +277,14 @@ var ContainsFunc = function.New(&function.Spec{
},
Type: function.StaticReturnType(cty.Bool),
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 {
return cty.False, nil
}
@ -299,7 +308,7 @@ var IndexFunc = function.New(&function.Spec{
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
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() {
@ -307,7 +316,7 @@ var IndexFunc = function.New(&function.Spec{
}
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(); {
@ -323,7 +332,7 @@ var IndexFunc = function.New(&function.Spec{
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) {
listVal := args[0]
if !listVal.IsWhollyKnown() {
if !listVal.IsKnown() {
return cty.UnknownVal(retType), nil
}
@ -389,7 +398,7 @@ var ChunklistFunc = function.New(&function.Spec{
}
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)
@ -437,11 +446,14 @@ var FlattenFunc = function.New(&function.Spec{
argTy := args[0].Type()
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))
for i, ty := range retVal {
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) {
inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if inputList.LengthInt() == 0 {
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
func flattener(finalList []cty.Value, flattenList cty.Value) []cty.Value {
// Flatten until it's not a cty.List, and return whether the value is known.
// 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(); {
_, val := it.Element()
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 {
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.
@ -561,7 +584,7 @@ var ListFunc = function.New(&function.Spec{
},
Type: func(args []cty.Value) (ret cty.Type, err error) {
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))
@ -572,7 +595,7 @@ var ListFunc = function.New(&function.Spec{
retType, _ := convert.UnifyUnsafe(argTypes)
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
@ -666,7 +689,7 @@ var LookupFunc = function.New(&function.Spec{
case ty.Equals(cty.Number):
return cty.NumberVal(v.AsBigFloat()), nil
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)
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
@ -777,7 +800,7 @@ var MatchkeysFunc = function.New(&function.Spec{
},
Type: func(args []cty.Value) (cty.Type, error) {
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
@ -788,7 +811,7 @@ var MatchkeysFunc = function.New(&function.Spec{
}
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)
@ -923,7 +946,7 @@ var SetProductFunc = function.New(&function.Spec{
},
Type: func(args []cty.Value) (retType cty.Type, err error) {
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
@ -1040,7 +1063,7 @@ var SliceFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
Type: cty.DynamicPseudoType,
},
{
Name: "startIndex",
@ -1052,50 +1075,71 @@ var SliceFunc = function.New(&function.Spec{
},
},
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) {
inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
var startIndex, endIndex int
if err = gocty.FromCtyValue(args[1], &startIndex); err != nil {
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
}
if err = gocty.FromCtyValue(args[2], &endIndex); err != nil {
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
startIndex, endIndex, err := sliceIndexes(args, inputList.LengthInt())
if err != nil {
return cty.NilVal, err
}
if startIndex < 0 {
return cty.NilVal, fmt.Errorf("from index must be >= 0")
}
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)
if endIndex-startIndex == 0 {
if retType.IsTupleType() {
return cty.EmptyTupleVal, nil
}
i++
}
if len(outputList) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil
}
outputList := inputList.AsValueSlice()[startIndex:endIndex]
if retType.IsTupleType() {
return cty.TupleVal(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
// swaps the keys and values to produce a new map of lists of strings.
var TransposeFunc = function.New(&function.Spec{
@ -1120,7 +1164,7 @@ var TransposeFunc = function.New(&function.Spec{
for iter := inVal.ElementIterator(); iter.Next(); {
_, val := iter.Element()
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()
@ -1180,7 +1224,7 @@ var ValuesFunc = function.New(&function.Spec{
}
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) {
mapVar := args[0]
@ -1247,7 +1291,7 @@ var ZipmapFunc = function.New(&function.Spec{
return cty.Object(atys), nil
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) {

View File

@ -417,7 +417,7 @@ func TestCoalesceList(t *testing.T) {
}),
},
cty.ListVal([]cty.Value{
cty.StringVal("1"), cty.StringVal("2"),
cty.NumberIntVal(1), cty.NumberIntVal(2),
}),
false,
},
@ -476,7 +476,7 @@ func TestCoalesceList(t *testing.T) {
cty.ListValEmpty(cty.String),
cty.UnknownVal(cty.List(cty.String)),
},
cty.UnknownVal(cty.List(cty.String)),
cty.DynamicVal,
false,
},
{ // unknown list
@ -486,13 +486,63 @@ func TestCoalesceList(t *testing.T) {
cty.StringVal("third"), cty.StringVal("fourth"),
}),
},
cty.UnknownVal(cty.List(cty.String)),
cty.DynamicVal,
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 {
t.Run(fmt.Sprintf("coalescelist(%#v)", test.Values), func(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("%d-coalescelist(%#v)", i, test.Values), func(t *testing.T) {
got, err := CoalesceList(test.Values...)
if test.Err {
@ -671,6 +721,26 @@ func TestContains(t *testing.T) {
cty.BoolVal(true),
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 {
@ -993,13 +1063,29 @@ func TestChunklist(t *testing.T) {
cty.UnknownVal(cty.String),
}),
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))),
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("chunklist(%#v, %#v)", test.List, test.Size), func(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("%d-chunklist(%#v, %#v)", i, test.List, test.Size), func(t *testing.T) {
got, err := Chunklist(test.List, test.Size)
if test.Err {
@ -1043,6 +1129,44 @@ func TestFlatten(t *testing.T) {
}),
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{
@ -1054,8 +1178,26 @@ func TestFlatten(t *testing.T) {
cty.StringVal("d"),
}),
}),
cty.DynamicVal,
false,
cty.TupleVal([]cty.Value{
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),
@ -1102,8 +1244,8 @@ func TestFlatten(t *testing.T) {
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("flatten(%#v)", test.List), func(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("%d-flatten(%#v)", i, test.List), func(t *testing.T) {
got, err := Flatten(test.List)
if test.Err {
@ -2354,6 +2496,11 @@ func TestSlice(t *testing.T) {
cty.StringVal("a"),
cty.UnknownVal(cty.String),
})
tuple := cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.NumberIntVal(1),
cty.UnknownVal(cty.List(cty.String)),
})
tests := []struct {
List cty.Value
StartIndex cty.Value
@ -2370,10 +2517,24 @@ func TestSlice(t *testing.T) {
}),
false,
},
{ // unknowns in the list
{ // slice only an unknown value
listWithUnknowns,
cty.NumberIntVal(1),
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)),
false,
},
@ -2414,10 +2575,44 @@ func TestSlice(t *testing.T) {
cty.NilVal,
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 {
t.Run(fmt.Sprintf("slice(%#v, %#v, %#v)", test.List, test.StartIndex, test.EndIndex), func(t *testing.T) {
for i, test := range tests {
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)
if test.Err {

View File

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