Skip to content

Commit

Permalink
function/stdlib: Setproduct mark handling
Browse files Browse the repository at this point in the history
Setproduct will now preserve marks on both the arguments and their elements. Marks that apply to any entire argument will propegate to all return values, while marks applied to a single element will remain attached to individual values.
  • Loading branch information
mildwonkey authored and apparentlymart committed Apr 20, 2021
1 parent 63cbd7f commit 0f69a64
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 8 deletions.
23 changes: 15 additions & 8 deletions cty/function/stdlib/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,9 @@ var ReverseListFunc = function.New(&function.Spec{
var SetProductFunc = function.New(&function.Spec{
Params: []function.Parameter{},
VarParam: &function.Parameter{
Name: "sets",
Type: cty.DynamicPseudoType,
Name: "sets",
Type: cty.DynamicPseudoType,
AllowMarked: true,
},
Type: func(args []cty.Value) (retType cty.Type, err error) {
if len(args) < 2 {
Expand Down Expand Up @@ -931,11 +932,15 @@ var SetProductFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
ety := retType.ElementType()
var retMarks cty.ValueMarks

total := 1
for _, arg := range args {
arg, marks := arg.Unmark()
retMarks = cty.NewValueMarks(retMarks, marks)

if !arg.Length().IsKnown() {
return cty.UnknownVal(retType), nil
return cty.UnknownVal(retType).Mark(marks), nil
}

// Because of our type checking function, we are guaranteed that
Expand All @@ -948,9 +953,9 @@ var SetProductFunc = function.New(&function.Spec{
// If any of the arguments was an empty collection then our result
// is also an empty collection, which we'll short-circuit here.
if retType.IsListType() {
return cty.ListValEmpty(ety), nil
return cty.ListValEmpty(ety).Mark(retMarks), nil
}
return cty.SetValEmpty(ety), nil
return cty.SetValEmpty(ety).Mark(retMarks), nil
}

subEtys := ety.TupleElementTypes()
Expand All @@ -961,6 +966,8 @@ var SetProductFunc = function.New(&function.Spec{
s := 0
argVals := make([][]cty.Value, len(args))
for i, arg := range args {
// We've already stored the marks in retMarks
arg, _ := arg.Unmark()
argVals[i] = arg.AsValueSlice()
}

Expand Down Expand Up @@ -996,13 +1003,13 @@ var SetProductFunc = function.New(&function.Spec{

productVals := make([]cty.Value, total)
for i, vals := range product {
productVals[i] = cty.TupleVal(vals)
productVals[i] = cty.TupleVal(vals).WithMarks(retMarks)
}

if retType.IsListType() {
return cty.ListVal(productVals), nil
return cty.ListVal(productVals).WithMarks(retMarks), nil
}
return cty.SetVal(productVals), nil
return cty.SetVal(productVals).WithMarks(retMarks), nil
},
})

Expand Down
174 changes: 174 additions & 0 deletions cty/function/stdlib/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1927,3 +1927,177 @@ func TestFlatten(t *testing.T) {
})
}
}

func TestSetproduct(t *testing.T) {
tests := []struct {
Collections []cty.Value
Want cty.Value
Err string
}{
{
[]cty.Value{cty.ListValEmpty(cty.String)},
cty.NilVal,
`at least two arguments are required`,
},
{
[]cty.Value{
cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}),
cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}),
},
cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String)})}),
``,
},
{
[]cty.Value{
cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}),
cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}),
},
cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String)})}),
``,
},
{
[]cty.Value{
cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String).Mark("a")}),
cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}),
},
cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String).Mark("a"), cty.ListValEmpty(cty.String)})}),
``,
},
{
[]cty.Value{
cty.TupleVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown"),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("fox"),
cty.NumberIntVal(3),
}),
},
cty.ListVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("3")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("3")}),
}),
``,
},
{
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown"),
}),
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("fox"),
}),
},
cty.SetVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}),
}),
``,
},
{ // The collection itself is not marked, just some elements
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown").Mark("a"),
}),
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("fox").Mark("b"),
}),
},
cty.SetVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox").Mark("b")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("quick")}),
cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("fox").Mark("b")}),
}),
``,
},
{ // The collections are marked
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown"),
}).Mark("a"),
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("fox"),
}).Mark("b"),
},
cty.SetVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}).Mark("a").Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}).Mark("a").Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}).Mark("a").Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}).Mark("a").Mark("b"),
}),
``,
},
{ // One collection is marked
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown"),
}).Mark("a"),
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("fox"),
}),
},
cty.SetVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}).Mark("a"),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}).Mark("a"),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}).Mark("a"),
cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}).Mark("a"),
}),
``,
},
{ // Inner and outer marks
[]cty.Value{
cty.SetVal([]cty.Value{
cty.StringVal("the"),
cty.StringVal("brown").Mark("a"),
}).Mark("b"),
cty.SetVal([]cty.Value{
cty.StringVal("quick"),
cty.StringVal("fox").Mark("c"),
}),
},
cty.SetVal([]cty.Value{
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}).Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox").Mark("a")}).Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("quick")}).Mark("b"),
cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("fox").Mark("c")}).Mark("b"),
}),
``,
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("Setproduct(%#v)", test.Collections), func(t *testing.T) {
got, err := SetProduct(test.Collections...)
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)
}
fmt.Printf("\n%#v\n", got)
})
}
}

0 comments on commit 0f69a64

Please sign in to comment.