[codegen/go] Fix accessors on struct ptr outputs (#4456)

* [codegen/go] Fix accessors on struct ptr outputs

The accesor methods on nestred struct Ptr outputs were previously not accepting pointer typed inputs as they should, and would thus always panic if used.

The "simple" fix would be to just accept the pointer type and blindly dereference it.  But this doesn't seem like the right experience - it would make these accessors very unsafe to use in practice.

Instead, this PR implements the accessors on pointer-typed outputs as nil-coaslescing, always lifting the output type into a pointer type and flowing a nil value into the result type.  This ensures the accessor will not nil-deref, and that user code can handle the `nil` value itself (or use `.Apply` directly to implement more specialized behaviour).

Before:

```go
// Name of your S3 bucket.
func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringOutput {
	return o.ApplyT(func(v BuildStorageLocation) string { return v.Bucket }).(pulumi.StringOutput)
}
```

After:

```go
// Name of your S3 bucket.
func (o BuildStorageLocationPtrOutput) Bucket() pulumi.StringPtrOutput {
	return o.ApplyT(func(v *BuildStorageLocation) *string {
		if v == nil {
			return nil
		}
		return &v.Bucket
	}).(pulumi.StringPtrOutput)
}
```

However, due to the decision to have this more usable behaviour, this is a breaking change, as some/many accessors now return a pointer type when they previously did not.

Fixes pulumi/pulumi-azure#530.

* Mark nested property types as requiring ptr types

* Add CHANGELOG

* More fixes
This commit is contained in:
Luke Hoban 2020-04-21 13:33:38 -07:00 committed by GitHub
parent e96662a48e
commit 6afefe050f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 18 deletions

View file

@ -10,6 +10,10 @@ CHANGELOG
- Protect against panic when unprotecting non-existant resources
[#4441](https://github.com/pulumi/pulumi/pull/4441)
- Ensure Go accessor methods correctly support nested fields of optional outputs
[#4456](https://github.com/pulumi/pulumi/pull/4456)
## 2.0.0 (2020-04-16)
- CLI behavior change. Commands in non-interactive mode (i.e. when `pulumi` has its output piped to

View file

@ -512,10 +512,21 @@ func (pkg *pkgContext) genOutputTypes(w io.Writer, t *schema.ObjectType, details
for _, p := range t.Properties {
printComment(w, p.Comment, false)
outputType, applyType := pkg.outputType(p.Type, !p.IsRequired), pkg.plainType(p.Type, !p.IsRequired)
outputType, applyType := pkg.outputType(p.Type, true), pkg.plainType(p.Type, true)
deref := ""
// If the property was required, but the type it needs to return is an explicit pointer type, then we need
// to derference it.
if p.IsRequired && applyType[0] == '*' {
deref = "&"
}
fmt.Fprintf(w, "func (o %sPtrOutput) %s() %s {\n", name, Title(p.Name), outputType)
fmt.Fprintf(w, "\treturn o.ApplyT(func (v %s) %s { return v.%s }).(%s)\n", name, applyType, Title(p.Name), outputType)
fmt.Fprintf(w, "\treturn o.ApplyT(func (v *%s) %s {\n", name, applyType)
fmt.Fprintf(w, "\t\tif v == nil {\n")
fmt.Fprintf(w, "\t\t\treturn nil\n")
fmt.Fprintf(w, "\t\t}\n")
fmt.Fprintf(w, "\t\treturn %sv.%s\n", deref, Title(p.Name))
fmt.Fprintf(w, "\t}).(%s)\n", outputType)
fmt.Fprintf(w, "}\n\n")
}
}
@ -1064,6 +1075,20 @@ func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackag
_ = getPkg(":config/config:")
}
// For any optional properties, we must generate a pointer type for the corresponding property type.
// In addition, if the optional property's type is itself an object type, we also need to generate pointer
// types corresponding to all of it's nested properties, as our accessor methods will lift `nil` into
// those nested types.
var markOptionalPropertyTypesAsRequiringPtr func(props []*schema.Property, parentOptional bool)
markOptionalPropertyTypesAsRequiringPtr = func(props []*schema.Property, parentOptional bool) {
for _, p := range props {
if obj, ok := p.Type.(*schema.ObjectType); ok && (!p.IsRequired || parentOptional) {
getPkg(obj.Token).details(obj).ptrElement = true
markOptionalPropertyTypesAsRequiringPtr(obj.Properties, true)
}
}
}
for _, t := range pkg.Types {
switch t := t.(type) {
case *schema.ArrayType:
@ -1077,12 +1102,7 @@ func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackag
case *schema.ObjectType:
pkg := getPkg(t.Token)
pkg.types = append(pkg.types, t)
for _, p := range t.Properties {
if obj, ok := p.Type.(*schema.ObjectType); ok && !p.IsRequired {
getPkg(obj.Token).details(obj).ptrElement = true
}
}
markOptionalPropertyTypesAsRequiringPtr(t.Properties, false)
}
}
@ -1100,16 +1120,8 @@ func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackag
pkg.names.add("Get" + resourceName(r))
}
for _, p := range r.InputProperties {
if obj, ok := p.Type.(*schema.ObjectType); ok && (!r.IsProvider || !p.IsRequired) {
getPkg(obj.Token).details(obj).ptrElement = true
}
}
for _, p := range r.Properties {
if obj, ok := p.Type.(*schema.ObjectType); ok && (!r.IsProvider || !p.IsRequired) {
getPkg(obj.Token).details(obj).ptrElement = true
}
}
markOptionalPropertyTypesAsRequiringPtr(r.InputProperties, !r.IsProvider)
markOptionalPropertyTypesAsRequiringPtr(r.Properties, !r.IsProvider)
}
scanResource(pkg.Provider)