diff --git a/cty/convert/conversion_object.go b/cty/convert/conversion_object.go index 098c109b..51958ef4 100644 --- a/cty/convert/conversion_object.go +++ b/cty/convert/conversion_object.go @@ -80,13 +80,19 @@ func conversionObjectToObject(in, out cty.Type, unsafe bool) conversion { } } + if val.IsNull() { + // Strip optional attributes out of the embedded type for null + // values. + val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) + } + attrVals[name] = val } for name := range outOptionals { if _, exists := attrVals[name]; !exists { wantTy := outAtys[name] - attrVals[name] = cty.NullVal(wantTy) + attrVals[name] = cty.NullVal(wantTy.WithoutOptionalAttributesDeep()) } } diff --git a/cty/convert/public_test.go b/cty/convert/public_test.go index 33158471..602fc40c 100644 --- a/cty/convert/public_test.go +++ b/cty/convert/public_test.go @@ -1066,6 +1066,103 @@ func TestConvert(t *testing.T) { }, []string{"b", "c"}), WantError: `map element type is incompatible with attribute "c": object required`, }, + { + Value: cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(10)), + "c": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("foo"), + "b": cty.BoolVal(true), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(5)), + "c": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + "b": cty.Bool, + }, []string{"b"})), + }), + }), + Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "c": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + "b": cty.Bool, + }, []string{"b"}), + "d": cty.Number, + }, []string{"c"})), + Want: cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(10)), + "c": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("foo"), + "b": cty.BoolVal(true), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(5)), + "c": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + "b": cty.Bool, + })), + }), + }), + }, + { + Value: cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(10)), + "c": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("foo"), + "b": cty.BoolVal(true), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(5)), + }), + }), + Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "c": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + "b": cty.Bool, + }, []string{"b"}), + "d": cty.Number, + }, []string{"c"})), + Want: cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(10)), + "c": cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("foo"), + "b": cty.BoolVal(true), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "d": cty.NumberVal(big.NewFloat(5)), + "c": cty.NullVal(cty.Object(map[string]cty.Type{ + "a": cty.String, + "b": cty.Bool, + })), + }), + }), + }, + { + Value: cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("boop"), + }), + Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ + "a": cty.String, + "b": cty.String, + "c": cty.Object(map[string]cty.Type{ + "d": cty.String, + }), + }, []string{"b", "c"}), + Want: cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("boop"), + "b": cty.NullVal(cty.String), + "c": cty.NullVal(cty.Object(map[string]cty.Type{ + "d": cty.String, + })), + }), + }, { Value: cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{