diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index 5422a0f9d..d299b4b7a 100644 --- a/lang/funcs/collection.go +++ b/lang/funcs/collection.go @@ -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) { diff --git a/lang/funcs/collection_test.go b/lang/funcs/collection_test.go index 4339d3af4..dd112388f 100644 --- a/lang/funcs/collection_test.go +++ b/lang/funcs/collection_test.go @@ -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 { diff --git a/lang/functions_test.go b/lang/functions_test.go index 9233d0ff9..70383f38c 100644 --- a/lang/functions_test.go +++ b/lang/functions_test.go @@ -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": {