lang/funcs: zipmap accepts tuple of values and produces object
Now that our language supports tuple/object types in addition to list/map types, it's convenient for zipmap to be able to produce an object type given a tuple, since this makes it symmetrical with "keys" and "values" such the the following identity holds for any map or object value "a" a == zipmap(keys(a), values(a)) When the values sequence is a tuple, the result has an object type whose attribute types correspond to the given tuple types. Since an object type has attribute names as part of its definition, there is the additional constraint here that the result has an unknown type (represented by the dynamic pseudo-type) if the given values is a tuple and the given keys contains any unknown values. This isn't true for values as a list because we can predict the resulting map element type using just the list element type.
This commit is contained in:
parent
093cfacbcf
commit
30497bbfb7
|
@ -986,29 +986,57 @@ var ZipmapFunc = function.New(&function.Spec{
|
|||
},
|
||||
{
|
||||
Name: "values",
|
||||
Type: cty.List(cty.DynamicPseudoType),
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
||||
keys := args[0]
|
||||
values := args[1]
|
||||
valuesTy := values.Type()
|
||||
|
||||
if !keys.IsKnown() || !values.IsKnown() || keys.LengthInt() == 0 {
|
||||
return cty.Map(cty.DynamicPseudoType), nil
|
||||
}
|
||||
switch {
|
||||
case valuesTy.IsListType():
|
||||
return cty.Map(values.Type().ElementType()), nil
|
||||
case valuesTy.IsTupleType():
|
||||
if !keys.IsWhollyKnown() {
|
||||
// Since zipmap with a tuple produces an object, we need to know
|
||||
// all of the key names before we can predict our result type.
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
|
||||
if keys.LengthInt() != values.LengthInt() {
|
||||
return cty.NilType, fmt.Errorf("count of keys (%d) does not match count of values (%d)",
|
||||
keys.LengthInt(), values.LengthInt())
|
||||
keysRaw := keys.AsValueSlice()
|
||||
valueTypesRaw := valuesTy.TupleElementTypes()
|
||||
if len(keysRaw) != len(valueTypesRaw) {
|
||||
return cty.NilType, fmt.Errorf("number of keys (%d) does not match number of values (%d)", len(keysRaw), len(valueTypesRaw))
|
||||
}
|
||||
atys := make(map[string]cty.Type, len(valueTypesRaw))
|
||||
for i, keyVal := range keysRaw {
|
||||
if keyVal.IsNull() {
|
||||
return cty.NilType, fmt.Errorf("keys list has null value at index %d", i)
|
||||
}
|
||||
key := keyVal.AsString()
|
||||
atys[key] = valueTypesRaw[i]
|
||||
}
|
||||
return cty.Object(atys), nil
|
||||
|
||||
default:
|
||||
return cty.NilType, fmt.Errorf("values argument must be a list or tuple value")
|
||||
}
|
||||
return cty.Map(values.Type().ElementType()), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
keys := args[0]
|
||||
values := args[1]
|
||||
|
||||
if !keys.IsKnown() || !values.IsKnown() || keys.LengthInt() == 0 {
|
||||
return cty.MapValEmpty(cty.DynamicPseudoType), nil
|
||||
if !keys.IsWhollyKnown() {
|
||||
// Unknown map keys and object attributes are not supported, so
|
||||
// our entire result must be unknown in this case.
|
||||
return cty.UnknownVal(retType), nil
|
||||
}
|
||||
|
||||
// both keys and values are guaranteed to be shallowly-known here,
|
||||
// because our declared params above don't allow unknown or null values.
|
||||
if keys.LengthInt() != values.LengthInt() {
|
||||
return cty.NilVal, fmt.Errorf("number of keys (%d) does not match number of values (%d)", keys.LengthInt(), values.LengthInt())
|
||||
}
|
||||
|
||||
output := make(map[string]cty.Value)
|
||||
|
@ -1021,7 +1049,19 @@ var ZipmapFunc = function.New(&function.Spec{
|
|||
i++
|
||||
}
|
||||
|
||||
return cty.MapVal(output), nil
|
||||
switch {
|
||||
case retType.IsMapType():
|
||||
if len(output) == 0 {
|
||||
return cty.MapValEmpty(retType.ElementType()), nil
|
||||
}
|
||||
return cty.MapVal(output), nil
|
||||
case retType.IsObjectType():
|
||||
return cty.ObjectVal(output), nil
|
||||
default:
|
||||
// Should never happen because the type-check function should've
|
||||
// caught any other case.
|
||||
return cty.NilVal, fmt.Errorf("internally selected incorrect result type %s (this is a bug)", retType.FriendlyName())
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -2204,13 +2204,88 @@ func TestZipmap(t *testing.T) {
|
|||
}),
|
||||
false,
|
||||
},
|
||||
{ // tuple values produce object
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
cty.StringVal("world"),
|
||||
}),
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("bar"),
|
||||
cty.UnknownVal(cty.Bool),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"hello": cty.StringVal("bar"),
|
||||
"world": cty.UnknownVal(cty.Bool),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{ // empty tuple produces empty object
|
||||
cty.ListValEmpty(cty.String),
|
||||
cty.EmptyTupleVal,
|
||||
cty.EmptyObjectVal,
|
||||
false,
|
||||
},
|
||||
{ // tuple with any unknown keys produces DynamicVal
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
cty.UnknownVal(cty.String),
|
||||
}),
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("bar"),
|
||||
cty.True,
|
||||
}),
|
||||
cty.DynamicVal,
|
||||
false,
|
||||
},
|
||||
{ // tuple with all keys unknown produces DynamicVal
|
||||
cty.UnknownVal(cty.List(cty.String)),
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("bar"),
|
||||
cty.True,
|
||||
}),
|
||||
cty.DynamicVal,
|
||||
false,
|
||||
},
|
||||
{ // list with all keys unknown produces correctly-typed unknown map
|
||||
cty.UnknownVal(cty.List(cty.String)),
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("bar"),
|
||||
cty.StringVal("baz"),
|
||||
}),
|
||||
cty.UnknownVal(cty.Map(cty.String)),
|
||||
false,
|
||||
},
|
||||
{ // unknown tuple as values produces correctly-typed unknown object
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
cty.StringVal("world"),
|
||||
}),
|
||||
cty.UnknownVal(cty.Tuple([]cty.Type{
|
||||
cty.String,
|
||||
cty.Bool,
|
||||
})),
|
||||
cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"hello": cty.String,
|
||||
"world": cty.Bool,
|
||||
})),
|
||||
false,
|
||||
},
|
||||
{ // unknown list as values produces correctly-typed unknown map
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
cty.StringVal("world"),
|
||||
}),
|
||||
cty.UnknownVal(cty.List(cty.String)),
|
||||
cty.UnknownVal(cty.Map(cty.String)),
|
||||
false,
|
||||
},
|
||||
{ // empty input returns an empty map
|
||||
cty.ListValEmpty(cty.String),
|
||||
cty.ListValEmpty(cty.String),
|
||||
cty.MapValEmpty(cty.DynamicPseudoType),
|
||||
cty.MapValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{ // keys cannot be a list
|
||||
{ // keys cannot be a list of lists
|
||||
list5,
|
||||
list1,
|
||||
cty.NilVal,
|
||||
|
@ -2232,7 +2307,7 @@ func TestZipmap(t *testing.T) {
|
|||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
t.Errorf("wrong result\n\nkeys: %#v\nvalues: %#v\ngot: %#v\nwant: %#v", test.Keys, test.Values, got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue