Skip to content

Commit

Permalink
function/stdlib: ChunklistFunc precise mark handling
Browse files Browse the repository at this point in the history
Will now preserve markings on individual input elements, without
aggregating them on the returned top-level list.

Marks on the list or on the chunk size will still propagate to the
top-level return value.

Chunklist previously didn't have any tests, so I've added some here in
addition to the coverage for the mark handling. I also did some light
reorganization so that the argument error handling happens before any of
the short-circuit return paths.
  • Loading branch information
apparentlymart committed Apr 20, 2021
1 parent 3564a30 commit fa1aa85
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 16 deletions.
39 changes: 23 additions & 16 deletions cty/function/stdlib/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,48 +398,55 @@ var DistinctFunc = function.New(&function.Spec{
var ChunklistFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
Name: "list",
Type: cty.List(cty.DynamicPseudoType),
AllowMarked: true,
},
{
Name: "size",
Type: cty.Number,
Name: "size",
Type: cty.Number,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
return cty.List(args[0].Type()), nil
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
listVal := args[0]
if !listVal.IsKnown() {
return cty.UnknownVal(retType), nil
}

if listVal.LengthInt() == 0 {
return cty.ListValEmpty(listVal.Type()), nil
}
sizeVal := args[1]
listVal, listMarks := listVal.Unmark()
sizeVal, sizeMarks := sizeVal.Unmark()
// All return paths below must include .WithMarks(retMarks) to propagate
// the top-level marks into the return value. Deep marks inside the
// list will just propagate naturally because we treat those values
// as opaque here.
retMarks := cty.NewValueMarks(listMarks, sizeMarks)

var size int
err = gocty.FromCtyValue(args[1], &size)
err = gocty.FromCtyValue(sizeVal, &size)
if err != nil {
return cty.NilVal, fmt.Errorf("invalid index: %s", err)
return cty.NilVal, fmt.Errorf("invalid size: %s", err)
}

if size < 0 {
return cty.NilVal, errors.New("the size argument must be positive")
}

if listVal.LengthInt() == 0 {
return cty.ListValEmpty(listVal.Type()).WithMarks(retMarks), nil
}

output := make([]cty.Value, 0)

// if size is 0, returns a list made of the initial list
if size == 0 {
output = append(output, listVal)
return cty.ListVal(output), nil
return cty.ListVal(output).WithMarks(retMarks), nil
}

chunk := make([]cty.Value, 0)

l := args[0].LengthInt()
l := listVal.LengthInt()
i := 0

for it := listVal.ElementIterator(); it.Next(); {
Expand All @@ -454,7 +461,7 @@ var ChunklistFunc = function.New(&function.Spec{
i++
}

return cty.ListVal(output), nil
return cty.ListVal(output).WithMarks(retMarks), nil
},
})

Expand Down
227 changes: 227 additions & 0 deletions cty/function/stdlib/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,233 @@ func TestHasIndex(t *testing.T) {
}
}

func TestChunklist(t *testing.T) {
tests := []struct {
List cty.Value
Len cty.Value
Want cty.Value
Err string
}{
{
cty.ListValEmpty(cty.String),
cty.NumberIntVal(2),
cty.ListValEmpty(cty.List(cty.String)),
``,
},
{
cty.UnknownVal(cty.List(cty.String)),
cty.NumberIntVal(2),
cty.UnknownVal(cty.List(cty.List(cty.String))),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}),
}),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}).Mark("a"),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}).Mark("a"),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}).Mark("a"),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}),
}).Mark("a"),
``,
},
{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.UnknownVal(cty.String),
}),
}),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
}),
``,
},
{ // Multiple result elements, one shorter
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("c"),
}),
}),
``,
},
{ // Multiple result elements, all "full"
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
cty.StringVal("d"),
cty.StringVal("e"),
cty.StringVal("f"),
}),
cty.NumberIntVal(2),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("c"),
cty.StringVal("d"),
}),
cty.ListVal([]cty.Value{
cty.StringVal("e"),
cty.StringVal("f"),
}),
}),
``,
},
{ // We treat length zero as infinite length
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.Zero,
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}).Mark("a"),
cty.Zero,
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}).Mark("a"),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
cty.Zero.Mark("a"),
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
}),
}).Mark("a"),
``,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}),
cty.Zero,
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a").Mark("b"),
}),
}),
``,
},
{
cty.ListValEmpty(cty.String),
cty.NumberIntVal(-1),
cty.NilVal,
`the size argument must be positive`,
},
{
cty.ListValEmpty(cty.String),
cty.PositiveInfinity,
cty.NilVal,
`invalid size: value must be a whole number, between -9223372036854775808 and 9223372036854775807`,
},
{
cty.ListValEmpty(cty.String),
cty.NumberFloatVal(1.5),
cty.NilVal,
`invalid size: value must be a whole number, between -9223372036854775808 and 9223372036854775807`,
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("Chunklist(%#v, %#v)", test.List, test.Len), func(t *testing.T) {
got, err := Chunklist(test.List, test.Len)
if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}

func TestContains(t *testing.T) {
listOfStrings := cty.ListVal([]cty.Value{
cty.StringVal("the"),
Expand Down

0 comments on commit fa1aa85

Please sign in to comment.