more precise type handling in flatten

FlattenFunc can return lists and tuples when individual elements are
unknown. Only return an unknown tuple if the number of elements cannot
be determined because it contains an unknown series.

Make sure flatten can handle non-series elements, which were previously
lost due to passing a slice value as the argument.
This commit is contained in:
James Bardin 2019-05-01 14:22:50 -04:00
parent 81e04c3050
commit 93ef015336
2 changed files with 87 additions and 17 deletions

View File

@ -441,8 +441,11 @@ var FlattenFunc = function.New(&function.Spec{
return cty.NilType, fmt.Errorf("can only flatten lists, sets and tuples")
}
outputList := make([]cty.Value, 0)
retVal := flattener(outputList, args[0])
retVal, known := flattener(args[0])
if !known {
return cty.DynamicPseudoType, nil
}
tys := make([]cty.Type, len(retVal))
for i, ty := range retVal {
tys[i] = ty.Type()
@ -451,30 +454,41 @@ var FlattenFunc = function.New(&function.Spec{
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
inputList := args[0]
if !inputList.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
if inputList.LengthInt() == 0 {
return cty.EmptyTupleVal, nil
}
outputList := make([]cty.Value, 0)
return cty.TupleVal(flattener(outputList, inputList)), nil
out, known := flattener(inputList)
if !known {
return cty.UnknownVal(retType), nil
}
return cty.TupleVal(out), nil
},
})
// Flatten until it's not a cty.List
func flattener(finalList []cty.Value, flattenList cty.Value) []cty.Value {
// Flatten until it's not a cty.List, and return whether the value is known.
// We can flatten lists with unknown values, as long as they are not
// lists themselves.
func flattener(flattenList cty.Value) ([]cty.Value, bool) {
out := make([]cty.Value, 0)
for it := flattenList.ElementIterator(); it.Next(); {
_, val := it.Element()
if val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType() {
finalList = flattener(finalList, val)
if !val.IsKnown() {
return out, false
}
res, known := flattener(val)
if !known {
return res, known
}
out = append(out, res...)
} else {
finalList = append(finalList, val)
out = append(out, val)
}
}
return finalList
return out, true
}
// KeysFunc contructs a function that takes a map and returns a sorted list of the map keys.

View File

@ -1043,6 +1043,44 @@ func TestFlatten(t *testing.T) {
}),
false,
},
// handle single elements as arguments
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.StringVal("c"),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}), false,
},
// handle single elements and mixed primitive types as arguments
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.StringVal("c"),
cty.TupleVal([]cty.Value{
cty.StringVal("x"),
cty.NumberIntVal(1),
}),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
cty.StringVal("x"),
cty.NumberIntVal(1),
}),
false,
},
// Primitive unknowns should still be flattened to a tuple
{
cty.ListVal([]cty.Value{
cty.ListVal([]cty.Value{
@ -1054,8 +1092,26 @@ func TestFlatten(t *testing.T) {
cty.StringVal("d"),
}),
}),
cty.DynamicVal,
false,
cty.TupleVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.UnknownVal(cty.String),
cty.StringVal("d"),
}), false,
},
// An unknown series should return an unknown dynamic value
{
cty.TupleVal([]cty.Value{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
}),
cty.TupleVal([]cty.Value{
cty.UnknownVal(cty.List(cty.String)),
cty.StringVal("d"),
}),
}),
cty.UnknownVal(cty.DynamicPseudoType), false,
},
{
cty.ListValEmpty(cty.String),
@ -1102,8 +1158,8 @@ func TestFlatten(t *testing.T) {
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("flatten(%#v)", test.List), func(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("%d-flatten(%#v)", i, test.List), func(t *testing.T) {
got, err := Flatten(test.List)
if test.Err {