github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/go/gen_program_optionals.go (about)

     1  package gen
     2  
     3  import (
     4  	"github.com/hashicorp/hcl/v2"
     5  	"github.com/hashicorp/hcl/v2/hclsyntax"
     6  	"github.com/pulumi/pulumi/pkg/v3/codegen"
     7  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
     8  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
     9  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    10  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    11  )
    12  
    13  type optionalTemp struct {
    14  	Name  string
    15  	Value model.Expression
    16  }
    17  
    18  func (ot *optionalTemp) Type() model.Type {
    19  	return ot.Value.Type()
    20  }
    21  
    22  func (ot *optionalTemp) Traverse(traverser hcl.Traverser) (model.Traversable, hcl.Diagnostics) {
    23  	return ot.Type().Traverse(traverser)
    24  }
    25  
    26  func (ot *optionalTemp) SyntaxNode() hclsyntax.Node {
    27  	return syntax.None
    28  }
    29  
    30  type optionalSpiller struct {
    31  	invocation         *model.FunctionCallExpression
    32  	intrinsicConvertTo *model.Type
    33  }
    34  
    35  func (os *optionalSpiller) preVisitor(x model.Expression) (model.Expression, hcl.Diagnostics) {
    36  	switch x := x.(type) {
    37  	case *model.FunctionCallExpression:
    38  		if x.Name == "invoke" {
    39  			// recurse into invoke args
    40  			isOutputInvoke, _, _ := pcl.RecognizeOutputVersionedInvoke(x)
    41  			// ignore output-versioned invokes as they do not need converting
    42  			if !isOutputInvoke {
    43  				os.invocation = x
    44  			}
    45  			os.intrinsicConvertTo = nil
    46  			return x, nil
    47  		}
    48  		if x.Name == pcl.IntrinsicConvert {
    49  			if os.invocation != nil {
    50  				os.intrinsicConvertTo = &x.Signature.ReturnType
    51  			}
    52  			return x, nil
    53  		}
    54  	case *model.ObjectConsExpression:
    55  		if os.invocation == nil {
    56  			return x, nil
    57  		}
    58  		destType := x.Type()
    59  		if os.intrinsicConvertTo != nil {
    60  			destType = *os.intrinsicConvertTo
    61  		}
    62  		if schemaType, ok := pcl.GetSchemaForType(destType); ok {
    63  			if schemaType, ok := schemaType.(*schema.ObjectType); ok {
    64  				// map of item name to optional type wrapper fn
    65  				optionalPrimitives := make(map[string]schema.Type)
    66  				for _, v := range schemaType.Properties {
    67  					if !v.IsRequired() {
    68  						ty := codegen.UnwrapType(v.Type)
    69  						switch ty {
    70  						case schema.NumberType, schema.BoolType, schema.IntType, schema.StringType:
    71  							optionalPrimitives[v.Name] = ty
    72  						}
    73  					}
    74  				}
    75  				for i, item := range x.Items {
    76  					// keys for schematized objects should be simple strings
    77  					if key, ok := item.Key.(*model.LiteralValueExpression); ok {
    78  						if model.StringType.AssignableFrom(key.Type()) {
    79  							strKey := key.Value.AsString()
    80  							if schemaType, isOptional := optionalPrimitives[strKey]; isOptional {
    81  								functionName := os.getOptionalConversion(schemaType)
    82  								expectedModelType := os.getExpectedModelType(schemaType)
    83  
    84  								x.Items[i].Value = &model.FunctionCallExpression{
    85  									Name: functionName,
    86  									Signature: model.StaticFunctionSignature{
    87  										Parameters: []model.Parameter{{
    88  											Name: "val",
    89  											Type: expectedModelType,
    90  										}},
    91  										ReturnType: model.NewOptionalType(expectedModelType),
    92  									},
    93  									Args: []model.Expression{item.Value},
    94  								}
    95  							}
    96  						}
    97  					}
    98  				}
    99  			}
   100  		}
   101  		// Clear before visiting children, require another __convert call to set again
   102  		os.intrinsicConvertTo = nil
   103  		return x, nil
   104  	default:
   105  		// Ditto
   106  		os.intrinsicConvertTo = nil
   107  		return x, nil
   108  	}
   109  	return x, nil
   110  }
   111  
   112  func (os *optionalSpiller) postVisitor(x model.Expression) (model.Expression, hcl.Diagnostics) {
   113  	switch x := x.(type) {
   114  	case *model.FunctionCallExpression:
   115  		if x.Name == "invoke" {
   116  			if x == os.invocation {
   117  				// Clear invocation flag once we're done traversing children.
   118  				os.invocation = nil
   119  			}
   120  		}
   121  	}
   122  	return x, nil
   123  }
   124  
   125  func (*optionalSpiller) getOptionalConversion(ty schema.Type) string {
   126  	switch ty {
   127  	case schema.NumberType:
   128  		return "goOptionalFloat64"
   129  	case schema.BoolType:
   130  		return "goOptionalBool"
   131  	case schema.IntType:
   132  		return "goOptionalInt"
   133  	case schema.StringType:
   134  		return "goOptionalString"
   135  	default:
   136  		return ""
   137  	}
   138  }
   139  
   140  func (*optionalSpiller) getExpectedModelType(ty schema.Type) model.Type {
   141  	switch ty {
   142  	case schema.NumberType:
   143  		return model.NumberType
   144  	case schema.BoolType:
   145  		return model.BoolType
   146  	case schema.IntType:
   147  		return model.IntType
   148  	case schema.StringType:
   149  		return model.StringType
   150  	default:
   151  		return nil
   152  	}
   153  }
   154  
   155  func (g *generator) rewriteOptionals(
   156  	x model.Expression,
   157  	spiller *optionalSpiller,
   158  ) (model.Expression, []*optionalTemp, hcl.Diagnostics) {
   159  	// We want to recurse but we only want to use the previsitor, if post visitor is nil we don't
   160  	// recurse.
   161  	x, diags := model.VisitExpression(x, spiller.preVisitor, spiller.postVisitor)
   162  
   163  	return x, nil, diags
   164  }