diff --git a/cty/function/stdlib/collection.go b/cty/function/stdlib/collection.go index 1f599c85..f50f196e 100644 --- a/cty/function/stdlib/collection.go +++ b/cty/function/stdlib/collection.go @@ -948,12 +948,16 @@ var SetProductFunc = function.New(&function.Spec{ var retMarks cty.ValueMarks total := 1 + var hasUnknownLength bool for _, arg := range args { arg, marks := arg.Unmark() retMarks = cty.NewValueMarks(retMarks, marks) + // Continue processing after we find an argument with unknown + // length to ensure that we cover all the marks if !arg.Length().IsKnown() { - return cty.UnknownVal(retType).Mark(marks), nil + hasUnknownLength = true + continue } // Because of our type checking function, we are guaranteed that @@ -962,6 +966,10 @@ var SetProductFunc = function.New(&function.Spec{ total *= arg.LengthInt() } + if hasUnknownLength { + return cty.UnknownVal(retType).WithMarks(retMarks), nil + } + if total == 0 { // If any of the arguments was an empty collection then our result // is also an empty collection, which we'll short-circuit here. diff --git a/cty/function/stdlib/collection_test.go b/cty/function/stdlib/collection_test.go index 4d9a2163..cfafb4af 100644 --- a/cty/function/stdlib/collection_test.go +++ b/cty/function/stdlib/collection_test.go @@ -2324,6 +2324,36 @@ func TestSetproduct(t *testing.T) { }).Mark("b"), ``, }, + { + // Empty lists with marks should propagate the marks + []cty.Value{ + cty.ListValEmpty(cty.String).Mark("a"), + cty.ListValEmpty(cty.Bool).Mark("b"), + }, + cty.ListValEmpty(cty.Tuple([]cty.Type{cty.String, cty.Bool})).WithMarks(cty.NewValueMarks("a", "b")), + ``, + }, + { + // Empty sets with marks should propagate the marks + []cty.Value{ + cty.SetValEmpty(cty.String).Mark("a"), + cty.SetValEmpty(cty.Bool).Mark("b"), + }, + cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.Bool})).WithMarks(cty.NewValueMarks("a", "b")), + ``, + }, + { + // Arguments which are sets with partially unknown values results + // in unknown length (since the unknown values may already be + // present in the set). This gives an unknown result preserving all + // marks + []cty.Value{ + cty.SetVal([]cty.Value{cty.StringVal("x"), cty.UnknownVal(cty.String)}).Mark("a"), + cty.SetVal([]cty.Value{cty.True, cty.False}).Mark("b"), + }, + cty.UnknownVal(cty.Set(cty.Tuple([]cty.Type{cty.String, cty.Bool}))).WithMarks(cty.NewValueMarks("a", "b")), + ``, + }, } for _, test := range tests {