From 7b73cce468e8021d933cfb7990356837c6348146 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 10 Jun 2024 13:48:05 -0700 Subject: [PATCH] function/stdlib: element accepts negative indices The abstraction offered by this function is that of an infinitely-long list consisting of repeating copies of the input list, but previously we restricted it only to positive indices. Now we'll also treat the indices before zero as a negative extension of the infinite list, for consistency. Technically the list is not actually infinite but rather constrained by the range of an int64 in Go. Ideally we'd relax that too and allow any whole number that's in the full range of cty.Number, but there's no clear benefit to that beyond a theoretical idea of correctness and so we'll save that one for a later change if we discover a real use-case for it. --- CHANGELOG.md | 1 + cty/function/stdlib/collection.go | 13 ++------ cty/function/stdlib/collection_test.go | 42 ++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f21c462..0dfd367f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 1.14.5 (Unreleased) +* `function/stdlib`: The `element` function now accepts negative indices, extending the illusion of an infinitely-long list into the negative direction too. # 1.14.4 (March 20, 2024) diff --git a/cty/function/stdlib/collection.go b/cty/function/stdlib/collection.go index 25df19b0..d1df73de 100644 --- a/cty/function/stdlib/collection.go +++ b/cty/function/stdlib/collection.go @@ -147,12 +147,6 @@ var ElementFunc = function.New(&function.Spec{ }, Type: func(args []cty.Value) (cty.Type, error) { list := args[0] - index := args[1] - if index.IsKnown() { - if index.LessThan(cty.NumberIntVal(0)).True() { - return cty.DynamicPseudoType, fmt.Errorf("cannot use element function with a negative index") - } - } listTy := list.Type() switch { @@ -189,10 +183,6 @@ var ElementFunc = function.New(&function.Spec{ return cty.DynamicVal, fmt.Errorf("invalid index: %s", err) } - if args[1].LessThan(cty.NumberIntVal(0)).True() { - return cty.DynamicVal, fmt.Errorf("cannot use element function with a negative index") - } - input, marks := args[0].Unmark() if !input.IsKnown() { return cty.UnknownVal(retType), nil @@ -203,6 +193,9 @@ var ElementFunc = function.New(&function.Spec{ return cty.DynamicVal, errors.New("cannot use element function with an empty list") } index = index % l + if index < 0 { + index += l + } // We did all the necessary type checks in the type function above, // so this is guaranteed not to fail. diff --git a/cty/function/stdlib/collection_test.go b/cty/function/stdlib/collection_test.go index b6b9b6b8..f4d06757 100644 --- a/cty/function/stdlib/collection_test.go +++ b/cty/function/stdlib/collection_test.go @@ -1157,6 +1157,30 @@ func TestElement(t *testing.T) { cty.StringVal("quick"), false, }, + { // negative index counts from the end of the list + listOfStrings, + cty.NumberIntVal(-1), + cty.StringVal("fox"), + false, + }, + { // negative index can be out of bounds too + listOfStrings, + cty.NumberIntVal(-6), + cty.StringVal("brown"), + false, + }, + { // minimum valid index + listOfStrings, + cty.NumberIntVal(-9223372036854775808), + cty.StringVal("the"), + false, + }, + { // maximum valid index + listOfStrings, + cty.NumberIntVal(9223372036854775807), + cty.StringVal("fox"), + false, + }, { // list of lists cty.ListVal([]cty.Value{listOfStrings, listOfStrings}), cty.NumberIntVal(0), @@ -1207,16 +1231,28 @@ func TestElement(t *testing.T) { }, { listOfStrings, - cty.NumberIntVal(-1), + cty.StringVal("brown"), // definitely not an index cty.DynamicVal, - true, // index cannot be a negative number + true, }, { listOfStrings, - cty.StringVal("brown"), // definitely not an index + cty.NumberFloatVal(0.5), cty.DynamicVal, true, }, + { // index out of bounds of int64 + listOfStrings, + cty.MustParseNumberVal("-9223372036854775809"), + cty.StringVal("the"), + true, + }, + { // index out of bounds of int64 + listOfStrings, + cty.MustParseNumberVal("9223372036854775808"), + cty.StringVal("fox"), + true, + }, } for _, test := range tests {