diff --git a/cty/convert/conversion_collection.go b/cty/convert/conversion_collection.go index 33c4df87..5b52d02d 100644 --- a/cty/convert/conversion_collection.go +++ b/cty/convert/conversion_collection.go @@ -39,6 +39,11 @@ func conversionCollectionToList(ety cty.Type, conv conversion) conversion { return cty.NilVal, err } } + + if val.IsNull() { + val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) + } + elems = append(elems, val) i++ @@ -88,6 +93,11 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion { return cty.NilVal, err } } + + if val.IsNull() { + val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) + } + elems = append(elems, val) i++ @@ -242,6 +252,11 @@ func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conv return cty.NilVal, err } } + + if val.IsNull() { + val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) + } + elems = append(elems, val) i++ @@ -524,6 +539,10 @@ func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conv return cty.NilVal, path.NewErrorf("map element type is incompatible with attribute %q: %s", name.AsString(), MismatchMessage(val.Type(), objType.AttributeType(name.AsString()))) } + if val.IsNull() { + val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) + } + elems[name.AsString()] = val } diff --git a/cty/convert/public.go b/cty/convert/public.go index af19bdc5..aab0d0ec 100644 --- a/cty/convert/public.go +++ b/cty/convert/public.go @@ -40,7 +40,7 @@ func GetConversionUnsafe(in cty.Type, out cty.Type) Conversion { // This is a convenience wrapper around calling GetConversionUnsafe and then // immediately passing the given value to the resulting function. func Convert(in cty.Value, want cty.Type) (cty.Value, error) { - if in.Type().Equals(want) { + if in.Type().Equals(want.WithoutOptionalAttributesDeep()) { return in, nil } diff --git a/cty/convert/public_test.go b/cty/convert/public_test.go index 602fc40c..a980fd35 100644 --- a/cty/convert/public_test.go +++ b/cty/convert/public_test.go @@ -1268,6 +1268,148 @@ func TestConvert(t *testing.T) { })}, ), }, + // We should strip optional attributes out of null values in sets, maps, + // lists and tuples. + { + Value: cty.ListVal([]cty.Value{ + cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.SetVal([]cty.Value{ + cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.TupleVal([]cty.Value{ + cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.SetVal([]cty.Value{ + cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.SetVal([]cty.Value{ + cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.ListVal([]cty.Value{ + cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.TupleVal([]cty.Value{ + cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.ListVal([]cty.Value{ + cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.ObjectVal(map[string]cty.Value{ + "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.Object(map[string]cty.Type{ + "object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"}), + }), + Want: cty.ObjectVal(map[string]cty.Value{ + "object": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.Number, + }, []string{"a"})), + }), + Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + { + Value: cty.TupleVal([]cty.Value{ + cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.Number, + }, []string{"a"})), + }), + Type: cty.Tuple([]cty.Type{ + cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"}), + }), + Want: cty.TupleVal([]cty.Value{ + cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, + // We should strip optional attributes out of types even if they match. + { + Value: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + }), + Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + }, []string{"a"})), + Want: cty.MapVal(map[string]cty.Value{ + "object": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + })), + }), + }, } for _, test := range tests {