Skip to content

Commit 742deca

Browse files
lang/funcs: Short-circuit if start or end index is unknown
Previously the type-selection codepath for an input tuple referred unconditionally to the start and end index values. In the Type callback, only the types of the arguments are guaranteed to be known, so any access to the values must be guarded with an .IsKnown check, or else the function will not short-circuit properly when an unknown value is passed. Now we will check the start and end indices are in range when we have enough information to do so, but we'll return an approximate result if either is unknown.
1 parent 02d03d2 commit 742deca

2 files changed

Lines changed: 149 additions & 22 deletions

File tree

lang/funcs/collection.go

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,37 +1064,51 @@ var SliceFunc = function.New(&function.Spec{
10641064
Type: cty.DynamicPseudoType,
10651065
},
10661066
{
1067-
Name: "startIndex",
1067+
Name: "start_index",
10681068
Type: cty.Number,
10691069
},
10701070
{
1071-
Name: "endIndex",
1071+
Name: "end_index",
10721072
Type: cty.Number,
10731073
},
10741074
},
10751075
Type: func(args []cty.Value) (cty.Type, error) {
10761076
arg := args[0]
10771077
argTy := arg.Type()
10781078

1079+
if argTy.IsSetType() {
1080+
return cty.NilType, function.NewArgErrorf(0, "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")
1081+
}
10791082
if !argTy.IsListType() && !argTy.IsTupleType() {
1080-
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")
1083+
return cty.NilType, function.NewArgErrorf(0, "must be a list or tuple value")
1084+
}
1085+
1086+
startIndex, endIndex, idxsKnown, err := sliceIndexes(args)
1087+
if err != nil {
1088+
return cty.NilType, err
10811089
}
10821090

10831091
if argTy.IsListType() {
10841092
return argTy, nil
10851093
}
10861094

1087-
startIndex, endIndex, err := sliceIndexes(args, args[0].LengthInt())
1088-
if err != nil {
1089-
return cty.NilType, err
1095+
if !idxsKnown {
1096+
// If we don't know our start/end indices then we can't predict
1097+
// the result type if we're planning to return a tuple.
1098+
return cty.DynamicPseudoType, nil
10901099
}
1091-
10921100
return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil
10931101
},
10941102
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
10951103
inputList := args[0]
10961104

1097-
startIndex, endIndex, err := sliceIndexes(args, inputList.LengthInt())
1105+
if retType == cty.DynamicPseudoType {
1106+
return cty.DynamicVal, nil
1107+
}
1108+
1109+
// we ignore idxsKnown return value here because the indices are always
1110+
// known here, or else the call would've short-circuited.
1111+
startIndex, endIndex, _, err := sliceIndexes(args)
10981112
if err != nil {
10991113
return cty.NilVal, err
11001114
}
@@ -1116,26 +1130,45 @@ var SliceFunc = function.New(&function.Spec{
11161130
},
11171131
})
11181132

1119-
func sliceIndexes(args []cty.Value, max int) (int, int, error) {
1120-
var startIndex, endIndex int
1133+
func sliceIndexes(args []cty.Value) (int, int, bool, error) {
1134+
var startIndex, endIndex, length int
1135+
var startKnown, endKnown, lengthKnown bool
11211136

1122-
if err := gocty.FromCtyValue(args[1], &startIndex); err != nil {
1123-
return 0, 0, fmt.Errorf("invalid start index: %s", err)
1124-
}
1125-
if err := gocty.FromCtyValue(args[2], &endIndex); err != nil {
1126-
return 0, 0, fmt.Errorf("invalid start index: %s", err)
1137+
if args[0].Type().IsTupleType() || args[0].IsKnown() { // if it's a tuple then we always know the length by the type, but lists must be known
1138+
length = args[0].LengthInt()
1139+
lengthKnown = true
11271140
}
11281141

1129-
if startIndex < 0 {
1130-
return 0, 0, errors.New("from index must be greater than or equal to 0")
1142+
if args[1].IsKnown() {
1143+
if err := gocty.FromCtyValue(args[1], &startIndex); err != nil {
1144+
return 0, 0, false, function.NewArgErrorf(1, "invalid start index: %s", err)
1145+
}
1146+
if startIndex < 0 {
1147+
return 0, 0, false, function.NewArgErrorf(1, "start index must not be less than zero")
1148+
}
1149+
if lengthKnown && startIndex > length {
1150+
return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than the length of the list")
1151+
}
1152+
startKnown = true
11311153
}
1132-
if endIndex > max {
1133-
return 0, 0, errors.New("to index must be less than or equal to the length of the input list")
1154+
if args[2].IsKnown() {
1155+
if err := gocty.FromCtyValue(args[2], &endIndex); err != nil {
1156+
return 0, 0, false, function.NewArgErrorf(2, "invalid end index: %s", err)
1157+
}
1158+
if endIndex < 0 {
1159+
return 0, 0, false, function.NewArgErrorf(2, "end index must not be less than zero")
1160+
}
1161+
if lengthKnown && endIndex > length {
1162+
return 0, 0, false, function.NewArgErrorf(2, "end index must not be greater than the length of the list")
1163+
}
1164+
endKnown = true
11341165
}
1135-
if startIndex > endIndex {
1136-
return 0, 0, errors.New("from index must be less than or equal to index")
1166+
if startKnown && endKnown {
1167+
if startIndex > endIndex {
1168+
return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than end index")
1169+
}
11371170
}
1138-
return startIndex, endIndex, nil
1171+
return startIndex, endIndex, startKnown && endKnown, nil
11391172
}
11401173

11411174
// TransposeFunc contructs a function that takes a map of lists of strings and

lang/funcs/collection_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,6 +2595,16 @@ func TestSlice(t *testing.T) {
25952595
}),
25962596
false,
25972597
},
2598+
{ // unknown tuple slice
2599+
cty.UnknownVal(tuple.Type()),
2600+
cty.NumberIntVal(1),
2601+
cty.NumberIntVal(3),
2602+
cty.UnknownVal(cty.Tuple([]cty.Type{
2603+
cty.Number,
2604+
cty.List(cty.String),
2605+
})),
2606+
false,
2607+
},
25982608
{ // empty list slice
25992609
listOfStrings,
26002610
cty.NumberIntVal(2),
@@ -2609,6 +2619,90 @@ func TestSlice(t *testing.T) {
26092619
cty.EmptyTupleVal,
26102620
false,
26112621
},
2622+
{ // list with unknown start offset
2623+
listOfStrings,
2624+
cty.UnknownVal(cty.Number),
2625+
cty.NumberIntVal(2),
2626+
cty.UnknownVal(cty.List(cty.String)),
2627+
false,
2628+
},
2629+
{ // list with unknown start offset but end out of bounds
2630+
listOfStrings,
2631+
cty.UnknownVal(cty.Number),
2632+
cty.NumberIntVal(200),
2633+
cty.UnknownVal(cty.List(cty.String)),
2634+
true,
2635+
},
2636+
{ // list with unknown start offset but end < 0
2637+
listOfStrings,
2638+
cty.UnknownVal(cty.Number),
2639+
cty.NumberIntVal(-4),
2640+
cty.UnknownVal(cty.List(cty.String)),
2641+
true,
2642+
},
2643+
{ // list with unknown end offset
2644+
listOfStrings,
2645+
cty.UnknownVal(cty.Number),
2646+
cty.NumberIntVal(0),
2647+
cty.UnknownVal(cty.List(cty.String)),
2648+
false,
2649+
},
2650+
{ // list with unknown end offset but start out of bounds
2651+
listOfStrings,
2652+
cty.UnknownVal(cty.Number),
2653+
cty.NumberIntVal(200),
2654+
cty.UnknownVal(cty.List(cty.String)),
2655+
true,
2656+
},
2657+
{ // list with unknown end offset but start < 0
2658+
listOfStrings,
2659+
cty.UnknownVal(cty.Number),
2660+
cty.NumberIntVal(-3),
2661+
cty.UnknownVal(cty.List(cty.String)),
2662+
true,
2663+
},
2664+
{ // tuple slice with unknown start offset
2665+
tuple,
2666+
cty.UnknownVal(cty.Number),
2667+
cty.NumberIntVal(3),
2668+
cty.DynamicVal,
2669+
false,
2670+
},
2671+
{ // tuple slice with unknown start offset but end out of bounds
2672+
tuple,
2673+
cty.UnknownVal(cty.Number),
2674+
cty.NumberIntVal(200),
2675+
cty.DynamicVal,
2676+
true,
2677+
},
2678+
{ // tuple slice with unknown start offset but end < 0
2679+
tuple,
2680+
cty.UnknownVal(cty.Number),
2681+
cty.NumberIntVal(-20),
2682+
cty.DynamicVal,
2683+
true,
2684+
},
2685+
{ // tuple slice with unknown end offset
2686+
tuple,
2687+
cty.NumberIntVal(0),
2688+
cty.UnknownVal(cty.Number),
2689+
cty.DynamicVal,
2690+
false,
2691+
},
2692+
{ // tuple slice with unknown end offset but start < 0
2693+
tuple,
2694+
cty.NumberIntVal(-2),
2695+
cty.UnknownVal(cty.Number),
2696+
cty.DynamicVal,
2697+
true,
2698+
},
2699+
{ // tuple slice with unknown end offset but start out of bounds
2700+
tuple,
2701+
cty.NumberIntVal(200),
2702+
cty.UnknownVal(cty.Number),
2703+
cty.DynamicVal,
2704+
true,
2705+
},
26122706
}
26132707

26142708
for i, test := range tests {

0 commit comments

Comments
 (0)