github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/go/gen_program_expressions.go (about) 1 package gen 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "math/big" 8 "strings" 9 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 "github.com/pulumi/pulumi/pkg/v3/codegen" 13 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" 14 "github.com/pulumi/pulumi/pkg/v3/codegen/pcl" 15 "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 16 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 const keywordRange = "range" 21 22 func (g *generator) GetPrecedence(expr model.Expression) int { 23 // TODO: Current values copied from Node, update based on 24 // https://golang.org/ref/spec 25 switch expr := expr.(type) { 26 case *model.ConditionalExpression: 27 return 4 28 case *model.BinaryOpExpression: 29 switch expr.Operation { 30 case hclsyntax.OpLogicalOr: 31 return 5 32 case hclsyntax.OpLogicalAnd: 33 return 6 34 case hclsyntax.OpEqual, hclsyntax.OpNotEqual: 35 return 11 36 case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan, 37 hclsyntax.OpLessThanOrEqual: 38 return 12 39 case hclsyntax.OpAdd, hclsyntax.OpSubtract: 40 return 14 41 case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo: 42 return 15 43 default: 44 contract.Failf("unexpected binary expression %v", expr) 45 } 46 case *model.UnaryOpExpression: 47 return 17 48 case *model.FunctionCallExpression: 49 switch expr.Name { 50 default: 51 return 20 52 } 53 case *model.ForExpression, *model.IndexExpression, *model.RelativeTraversalExpression, *model.SplatExpression, 54 *model.TemplateJoinExpression: 55 return 20 56 case *model.AnonymousFunctionExpression, *model.LiteralValueExpression, *model.ObjectConsExpression, 57 *model.ScopeTraversalExpression, *model.TemplateExpression, *model.TupleConsExpression: 58 return 22 59 default: 60 contract.Failf("unexpected expression %v of type %T", expr, expr) 61 } 62 return 0 63 } 64 65 // GenAnonymousFunctionExpression generates code for an AnonymousFunctionExpression. 66 func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) { 67 g.genAnonymousFunctionExpression(w, expr, nil, false) 68 } 69 70 func (g *generator) genAnonymousFunctionExpression( 71 w io.Writer, 72 expr *model.AnonymousFunctionExpression, 73 bodyPreamble []string, 74 inApply bool, 75 ) { 76 g.Fgenf(w, "func(") 77 leadingSep := "" 78 for _, param := range expr.Signature.Parameters { 79 isInput := isInputty(param.Type) 80 g.Fgenf(w, "%s%s %s", leadingSep, makeValidIdentifier(param.Name), g.argumentTypeName(nil, param.Type, isInput)) 81 leadingSep = ", " 82 } 83 84 retType := expr.Signature.ReturnType 85 if inApply { 86 retType = model.ResolveOutputs(retType) 87 } 88 89 retTypeName := g.argumentTypeName(nil, retType, false) 90 g.Fgenf(w, ") (%s, error) {\n", retTypeName) 91 92 for _, decl := range bodyPreamble { 93 g.Fgenf(w, "%s\n", decl) 94 } 95 96 body, temps := g.lowerExpression(expr.Body, retType) 97 g.genTempsMultiReturn(w, temps, retTypeName) 98 99 // g.Fgenf(w, "return %v, nil", body) 100 101 // fromBase64 special case 102 if b, ok := body.(*model.FunctionCallExpression); ok && b.Name == fromBase64Fn { 103 g.Fgenf(w, "value, _ := %v\n", b) 104 g.Fgenf(w, "return pulumi.String(value), nil") 105 } else if strings.HasPrefix(retTypeName, "pulumi") { 106 g.Fgenf(w, "return %s(%v), nil", retTypeName, body) 107 } else { 108 g.Fgenf(w, "return %v, nil", body) 109 } 110 g.Fgenf(w, "\n}") 111 } 112 113 func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) { 114 opstr, precedence := "", g.GetPrecedence(expr) 115 switch expr.Operation { 116 case hclsyntax.OpAdd: 117 opstr = "+" 118 case hclsyntax.OpDivide: 119 opstr = "/" 120 case hclsyntax.OpEqual: 121 opstr = "==" 122 case hclsyntax.OpGreaterThan: 123 opstr = ">" 124 case hclsyntax.OpGreaterThanOrEqual: 125 opstr = ">=" 126 case hclsyntax.OpLessThan: 127 opstr = "<" 128 case hclsyntax.OpLessThanOrEqual: 129 opstr = "<=" 130 case hclsyntax.OpLogicalAnd: 131 opstr = "&&" 132 case hclsyntax.OpLogicalOr: 133 opstr = "||" 134 case hclsyntax.OpModulo: 135 opstr = "%" 136 case hclsyntax.OpMultiply: 137 opstr = "*" 138 case hclsyntax.OpNotEqual: 139 opstr = "!=" 140 case hclsyntax.OpSubtract: 141 opstr = "-" 142 default: 143 opstr, precedence = ",", 1 144 } 145 146 g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand) 147 } 148 149 func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) { 150 // Ternary expressions are not supported in go so we need to allocate temp variables in the parent scope. 151 // This is handled by lower expression and rewriteTernaries 152 contract.Failf("unlowered conditional expression @ %v", expr.SyntaxNode().Range()) 153 } 154 155 // GenForExpression generates code for a ForExpression. 156 func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) { 157 g.genNYI(w, "For expression") 158 } 159 160 func (g *generator) genSafeEnum(w io.Writer, to *model.EnumType) func(member *schema.Enum) { 161 return func(member *schema.Enum) { 162 // We know the enum value at the call site, so we can directly stamp in a 163 // valid enum instance. We don't need to convert. 164 enumName := tokenToName(to.Token) 165 memberTag := member.Name 166 if memberTag == "" { 167 memberTag = member.Value.(string) 168 } 169 memberTag, err := makeSafeEnumName(memberTag, enumName) 170 contract.AssertNoErrorf(err, "Enum is invalid") 171 namespace := tokenToModule(to.Token) 172 g.Fgenf(w, "%s.%s", namespace, memberTag) 173 } 174 } 175 176 func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) { 177 //nolint:goconst 178 switch expr.Name { 179 case pcl.IntrinsicConvert: 180 from := expr.Args[0] 181 to := pcl.LowerConversion(from, expr.Signature.ReturnType) 182 output, isOutput := to.(*model.OutputType) 183 if isOutput { 184 to = output.ElementType 185 } 186 switch to := to.(type) { 187 case *model.EnumType: 188 var underlyingType string 189 switch { 190 case to.Type.Equals(model.StringType): 191 underlyingType = "string" 192 default: 193 panic(fmt.Sprintf( 194 "Unsafe enum conversions from type %s not implemented yet: %s => %s", 195 from.Type(), from, to)) 196 } 197 pkg, mod, typ, _ := pcl.DecomposeToken(to.Token, to.SyntaxNode().Range()) 198 mod = g.getModOrAlias(pkg, mod, mod) 199 enumTag := fmt.Sprintf("%s.%s", mod, typ) 200 if isOutput { 201 g.Fgenf(w, 202 "%.v.ApplyT(func(x *%[3]s) %[2]s { return %[2]s(*x) }).(%[2]sOutput)", 203 from, enumTag, underlyingType) 204 return 205 } 206 pcl.GenEnum(to, from, g.genSafeEnum(w, to), func(from model.Expression) { 207 g.Fgenf(w, "%s(%v)", enumTag, from) 208 }) 209 return 210 } 211 switch arg := from.(type) { 212 case *model.TupleConsExpression: 213 g.genTupleConsExpression(w, arg, expr.Type()) 214 case *model.ObjectConsExpression: 215 isInput := false 216 g.genObjectConsExpression(w, arg, expr.Type(), isInput) 217 case *model.LiteralValueExpression: 218 g.genLiteralValueExpression(w, arg, expr.Type()) 219 case *model.TemplateExpression: 220 g.genTemplateExpression(w, arg, expr.Type()) 221 case *model.ScopeTraversalExpression: 222 g.genScopeTraversalExpression(w, arg, expr.Type()) 223 default: 224 g.Fgenf(w, "%.v", expr.Args[0]) 225 } 226 case pcl.IntrinsicApply: 227 g.genApply(w, expr) 228 case "element": 229 g.genNYI(w, "element") 230 case "entries": 231 g.genNYI(w, "call %v", expr.Name) 232 // switch model.ResolveOutputs(expr.Args[0].Type()).(type) { 233 // case *model.ListType, *model.TupleType: 234 // if call, ok := expr.Args[0].(*model.FunctionCallExpression); ok && call.Name == "range" { 235 // g.genRange(w, call, true) 236 // return 237 // } 238 // g.Fgenf(w, "%.20v.Select((v, k)", expr.Args[0]) 239 // case *model.MapType, *model.ObjectType: 240 // g.genNYI(w, "MapOrObjectEntries") 241 // } 242 // g.Fgenf(w, " => new { Key = k, Value = v })") 243 case "fileArchive": 244 g.Fgenf(w, "pulumi.NewFileArchive(%.v)", expr.Args[0]) 245 case "remoteArchive": 246 g.Fgenf(w, "pulumi.NewRemoteArchive(%.v)", expr.Args[0]) 247 case "assetArchive": 248 g.Fgenf(w, "pulumi.NewAssetArchive(%.v)", expr.Args[0]) 249 case "fileAsset": 250 g.Fgenf(w, "pulumi.NewFileAsset(%.v)", expr.Args[0]) 251 case "stringAsset": 252 g.Fgenf(w, "pulumi.NewStringAsset(%.v)", expr.Args[0]) 253 case "remoteAsset": 254 g.Fgenf(w, "pulumi.NewRemoteAsset(%.v)", expr.Args[0]) 255 case "filebase64": 256 // Assuming the existence of the following helper method 257 g.Fgenf(w, "filebase64OrPanic(%v)", expr.Args[0]) 258 case "filebase64sha256": 259 // Assuming the existence of the following helper method 260 g.Fgenf(w, "filebase64sha256OrPanic(%v)", expr.Args[0]) 261 case pcl.Invoke: 262 263 pkg, module, fn, diags := g.functionName(expr.Args[0]) 264 contract.Assertf(len(diags) == 0, "We don't allow problems getting the function name") 265 if module == "" { 266 module = pkg 267 } 268 isOut, outArgs, outArgsType := pcl.RecognizeOutputVersionedInvoke(expr) 269 if isOut { 270 outTypeName, err := outputVersionFunctionArgTypeName(outArgsType, g.externalCache) 271 if err != nil { 272 // We create a diag instead of panicking since panics are caught in go 273 // format expressions. 274 g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{ 275 Severity: hcl.DiagError, 276 Summary: "Error when generating an output-versioned Invoke", 277 Detail: fmt.Sprintf("underlying error: %v", err), 278 Subject: &hcl.Range{}, 279 Context: &hcl.Range{}, 280 Expression: nil, 281 EvalContext: &hcl.EvalContext{}, 282 }) 283 g.Fgenf(w, "%q", "failed") // Write a value to avoid syntax errors 284 return 285 } 286 g.Fgenf(w, "%s.%sOutput(ctx, ", module, fn) 287 g.genObjectConsExpressionWithTypeName(w, outArgs, outArgsType, outTypeName) 288 } else { 289 g.Fgenf(w, "%s.%s(ctx, ", module, fn) 290 g.Fgenf(w, "%.v", expr.Args[1]) 291 } 292 293 optionsBag := "" 294 var buf bytes.Buffer 295 if len(expr.Args) == 3 { 296 g.Fgenf(&buf, ", %.v", expr.Args[2]) 297 } else { 298 g.Fgenf(&buf, ", nil") 299 } 300 optionsBag = buf.String() 301 g.Fgenf(w, "%v)", optionsBag) 302 case "join": 303 g.Fgenf(w, "strings.Join(%v, %v)", expr.Args[1], expr.Args[0]) 304 case "length": 305 g.genNYI(w, "call %v", expr.Name) 306 // g.Fgenf(w, "%.20v.Length", expr.Args[0]) 307 case "lookup": 308 g.genNYI(w, "Lookup") 309 case keywordRange: 310 g.genNYI(w, "call %v", expr.Name) 311 // g.genRange(w, expr, false) 312 case "readFile": 313 // Assuming the existence of the following helper method located earlier in the preamble 314 g.Fgenf(w, "readFileOrPanic(%v)", expr.Args[0]) 315 case "readDir": 316 contract.Failf("unlowered readDir function expression @ %v", expr.SyntaxNode().Range()) 317 case "secret": 318 outputTypeName := "pulumi.Any" 319 if model.ResolveOutputs(expr.Type()) != model.DynamicType { 320 outputTypeName = g.argumentTypeName(nil, expr.Type(), false) 321 } 322 g.Fgenf(w, "pulumi.ToSecret(%v).(%sOutput)", expr.Args[0], outputTypeName) 323 case "split": 324 g.genNYI(w, "call %v", expr.Name) 325 // g.Fgenf(w, "%.20v.Split(%v)", expr.Args[1], expr.Args[0]) 326 case "toBase64": 327 g.Fgenf(w, "base64.StdEncoding.EncodeToString([]byte(%v))", expr.Args[0]) 328 case fromBase64Fn: 329 g.Fgenf(w, "base64.StdEncoding.DecodeString(%v)", expr.Args[0]) 330 case "toJSON": 331 contract.Failf("unlowered toJSON function expression @ %v", expr.SyntaxNode().Range()) 332 case "mimeType": 333 g.Fgenf(w, "mime.TypeByExtension(path.Ext(%.v))", expr.Args[0]) 334 case "sha1": 335 g.Fgenf(w, "sha1Hash(%v)", expr.Args[0]) 336 case "goOptionalFloat64": 337 g.Fgenf(w, "pulumi.Float64Ref(%.v)", expr.Args[0]) 338 case "goOptionalBool": 339 g.Fgenf(w, "pulumi.BoolRef(%.v)", expr.Args[0]) 340 case "goOptionalInt": 341 g.Fgenf(w, "pulumi.IntRef(%.v)", expr.Args[0]) 342 case "goOptionalString": 343 g.Fgenf(w, "pulumi.StringRef(%.v)", expr.Args[0]) 344 case "stack": 345 g.Fgen(w, "ctx.Stack()") 346 case "project": 347 g.Fgen(w, "ctx.Project()") 348 case "cwd": 349 g.Fgen(w, "func(cwd string, err error) string { if err != nil { panic(err) }; return cwd }(os.Getwd())") 350 default: 351 g.genNYI(w, "call %v", expr.Name) 352 } 353 } 354 355 // Currently args type for output-versioned invokes are named 356 // `FOutputArgs`, but this is not yet understood by `tokenToType`. Use 357 // this function to compensate. 358 func outputVersionFunctionArgTypeName(t model.Type, cache *Cache) (string, error) { 359 schemaType, ok := pcl.GetSchemaForType(t) 360 if !ok { 361 return "", fmt.Errorf("No schema.Type type found for the given model.Type") 362 } 363 364 objType, ok := schemaType.(*schema.ObjectType) 365 if !ok { 366 return "", fmt.Errorf("Expected a schema.ObjectType, got %s", schemaType.String()) 367 } 368 369 pkg := &pkgContext{pkg: &schema.Package{Name: "main"}, externalPackages: cache} 370 371 var ty string 372 if pkg.isExternalReference(objType) { 373 extPkg, _ := pkg.contextForExternalReference(objType) 374 ty = extPkg.tokenToType(objType.Token) 375 } else { 376 ty = pkg.tokenToType(objType.Token) 377 } 378 379 return fmt.Sprintf("%sOutputArgs", strings.TrimSuffix(ty, "Args")), nil 380 } 381 382 func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) { 383 g.Fgenf(w, "%.20v[%.v]", expr.Collection, expr.Key) 384 } 385 386 func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) { 387 g.genLiteralValueExpression(w, expr, expr.Type()) 388 } 389 390 func (g *generator) genLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression, destType model.Type) { 391 exprType := expr.Type() 392 if cns, ok := exprType.(*model.ConstType); ok { 393 exprType = cns.Type 394 } 395 396 if exprType == model.NoneType { 397 g.Fgen(w, "nil") 398 return 399 } 400 401 argTypeName := g.argumentTypeName(expr, destType, false) 402 isPulumiType := strings.HasPrefix(argTypeName, "pulumi.") 403 404 switch exprType { 405 case model.BoolType: 406 if isPulumiType { 407 g.Fgenf(w, "%s(%v)", argTypeName, expr.Value.True()) 408 } else { 409 g.Fgenf(w, "%v", expr.Value.True()) 410 } 411 case model.NumberType, model.IntType: 412 bf := expr.Value.AsBigFloat() 413 if i, acc := bf.Int64(); acc == big.Exact { 414 if isPulumiType { 415 g.Fgenf(w, "%s(%d)", argTypeName, i) 416 } else { 417 g.Fgenf(w, "%d", i) 418 } 419 420 } else { 421 f, _ := bf.Float64() 422 if isPulumiType { 423 g.Fgenf(w, "%s(%g)", argTypeName, f) 424 } else { 425 g.Fgenf(w, "%g", f) 426 } 427 } 428 case model.StringType: 429 strVal := expr.Value.AsString() 430 if isPulumiType { 431 g.Fgenf(w, "%s(", argTypeName) 432 g.genStringLiteral(w, strVal) 433 g.Fgenf(w, ")") 434 } else { 435 g.genStringLiteral(w, strVal) 436 } 437 default: 438 contract.Failf("unexpected opaque type in GenLiteralValueExpression: %v (%v)", destType, 439 expr.SyntaxNode().Range()) 440 } 441 } 442 443 func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) { 444 isInput := false 445 g.genObjectConsExpression(w, expr, expr.Type(), isInput) 446 } 447 448 func (g *generator) genObjectConsExpression( 449 w io.Writer, 450 expr *model.ObjectConsExpression, 451 destType model.Type, 452 isInput bool) { 453 454 isInput = isInput || isInputty(destType) 455 456 typeName := g.argumentTypeName(expr, destType, isInput) 457 if schemaType, ok := pcl.GetSchemaForType(destType); ok { 458 if obj, ok := codegen.UnwrapType(schemaType).(*schema.ObjectType); ok { 459 if g.useLookupInvokeForm(obj.Token) { 460 typeName = strings.Replace(typeName, ".Get", ".Lookup", 1) 461 } 462 } 463 } 464 465 g.genObjectConsExpressionWithTypeName(w, expr, destType, typeName) 466 } 467 468 func (g *generator) genObjectConsExpressionWithTypeName( 469 w io.Writer, 470 expr *model.ObjectConsExpression, 471 destType model.Type, 472 typeName string) { 473 474 if len(expr.Items) == 0 { 475 g.Fgenf(w, "nil") 476 return 477 } 478 479 var temps []interface{} 480 // TODO: @pgavlin --- ineffectual assignment, was there some work in flight here? 481 // if strings.HasSuffix(typeName, "Args") { 482 // isInput = true 483 // } 484 // // invokes are not inputty 485 // if strings.Contains(typeName, ".Lookup") || strings.Contains(typeName, ".Get") { 486 // isInput = false 487 // } 488 isMap := strings.HasPrefix(typeName, "map[") 489 490 // TODO: retrieve schema and propagate optionals to emit bool ptr, etc. 491 492 // first lower all inner expressions and emit temps 493 for i, item := range expr.Items { 494 // don't treat keys as inputs 495 //nolint: revive 496 k, kTemps := g.lowerExpression(item.Key, item.Key.Type()) 497 temps = append(temps, kTemps...) 498 item.Key = k 499 x, xTemps := g.lowerExpression(item.Value, item.Value.Type()) 500 temps = append(temps, xTemps...) 501 item.Value = x 502 expr.Items[i] = item 503 } 504 g.genTemps(w, temps) 505 506 if isMap || !strings.HasSuffix(typeName, "Args") || strings.HasSuffix(typeName, "OutputArgs") { 507 g.Fgenf(w, "%s", typeName) 508 } else { 509 g.Fgenf(w, "&%s", typeName) 510 } 511 g.Fgenf(w, "{\n") 512 513 for _, item := range expr.Items { 514 if lit, ok := g.literalKey(item.Key); ok { 515 if isMap || strings.HasSuffix(typeName, "Map") { 516 g.Fgenf(w, "\"%s\"", lit) 517 } else { 518 g.Fgenf(w, "%s", Title(lit)) 519 } 520 } else { 521 g.Fgenf(w, "%.v", item.Key) 522 } 523 524 g.Fgenf(w, ": %.v,\n", item.Value) 525 } 526 527 g.Fgenf(w, "}") 528 } 529 530 func (g *generator) genRelativeTraversalExpression( 531 w io.Writer, expr *model.RelativeTraversalExpression, isInput bool) { 532 533 if _, ok := expr.Parts[0].(*model.PromiseType); ok { 534 isInput = false 535 } 536 if _, ok := expr.Parts[0].(*pcl.Resource); ok { 537 isInput = false 538 } 539 if isInput { 540 g.Fgenf(w, "%s(", g.argumentTypeName(expr, expr.Type(), isInput)) 541 } 542 g.GenRelativeTraversalExpression(w, expr) 543 if isInput { 544 g.Fgenf(w, ")") 545 } 546 } 547 548 func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) { 549 g.Fgenf(w, "%.20v", expr.Source) 550 isRootResource := false 551 if ie, ok := expr.Source.(*model.IndexExpression); ok { 552 if se, ok := ie.Collection.(*model.ScopeTraversalExpression); ok { 553 if _, ok := se.Parts[0].(*pcl.Resource); ok { 554 isRootResource = true 555 } 556 } 557 } 558 g.genRelativeTraversal(w, expr.Traversal, expr.Parts, isRootResource) 559 } 560 561 func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) { 562 g.genScopeTraversalExpression(w, expr, expr.Type()) 563 } 564 565 func (g *generator) genScopeTraversalExpression( 566 w io.Writer, expr *model.ScopeTraversalExpression, destType model.Type) { 567 rootName := expr.RootName 568 569 if _, ok := expr.Parts[0].(*model.SplatVariable); ok { 570 rootName = "val0" 571 } 572 573 genIDCall := false 574 575 isInput := false 576 if schemaType, ok := pcl.GetSchemaForType(destType); ok { 577 _, isInput = schemaType.(*schema.InputType) 578 } 579 580 if resource, ok := expr.Parts[0].(*pcl.Resource); ok { 581 isInput = false 582 if _, ok := pcl.GetSchemaForType(resource.InputType); ok { 583 // convert .id into .ID() 584 last := expr.Traversal[len(expr.Traversal)-1] 585 if attr, ok := last.(hcl.TraverseAttr); ok && attr.Name == "id" { 586 genIDCall = true 587 expr.Traversal = expr.Traversal[:len(expr.Traversal)-1] 588 } 589 } 590 } 591 592 // TODO if it's an array type, we need a lowering step to turn []string -> pulumi.StringArray 593 if isInput { 594 argType := g.argumentTypeName(expr, expr.Type(), isInput) 595 if strings.HasSuffix(argType, "Array") { 596 destTypeName := g.argumentTypeName(expr, destType, isInput) 597 if argType != destTypeName { 598 // use a helper to transform prompt arrays into inputty arrays 599 var helper *promptToInputArrayHelper 600 if h, ok := g.arrayHelpers[argType]; ok { 601 helper = h 602 } else { 603 // helpers are emitted at the end in the postamble step 604 helper = &promptToInputArrayHelper{ 605 destType: argType, 606 } 607 g.arrayHelpers[argType] = helper 608 } 609 g.Fgenf(w, "%s(", helper.getFnName()) 610 defer g.Fgenf(w, ")") 611 } 612 } else { 613 g.Fgenf(w, "%s(", g.argumentTypeName(expr, expr.Type(), isInput)) 614 defer g.Fgenf(w, ")") 615 } 616 } 617 618 // TODO: this isn't exhaustively correct as "range" could be a legit var name 619 // instead we should probably use a fn call expression here for entries/range 620 // similar to other languages 621 if rootName == keywordRange { 622 part := expr.Traversal[1].(hcl.TraverseAttr).Name 623 switch part { 624 case "value": 625 g.Fgenf(w, "val0") 626 case "key": 627 g.Fgenf(w, "key0") 628 default: 629 contract.Failf("unexpected traversal on range expression: %s", part) 630 } 631 } else { 632 g.Fgen(w, makeValidIdentifier(rootName)) 633 isRootResource := false 634 g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts[1:], isRootResource) 635 } 636 637 if genIDCall { 638 g.Fgenf(w, ".ID()") 639 } 640 } 641 642 // GenSplatExpression generates code for a SplatExpression. 643 func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) { 644 contract.Failf("unlowered splat expression @ %v", expr.SyntaxNode().Range()) 645 } 646 647 // GenTemplateExpression generates code for a TemplateExpression. 648 func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) { 649 g.genTemplateExpression(w, expr, expr.Type()) 650 } 651 652 func (g *generator) genTemplateExpression(w io.Writer, expr *model.TemplateExpression, destType model.Type) { 653 if len(expr.Parts) == 1 { 654 if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) { 655 g.genLiteralValueExpression(w, lit, destType) 656 } 657 658 // If we have a template expression that doesn't start with a string, it indicates 659 // an invalid *pcl.Program. Instead of crashing, we continue. 660 return 661 } 662 argTypeName := g.argumentTypeName(expr, destType, false) 663 isPulumiType := strings.HasPrefix(argTypeName, "pulumi.") 664 if isPulumiType { 665 g.Fgenf(w, "%s(", argTypeName) 666 defer g.Fgenf(w, ")") 667 } 668 669 var fmtStr strings.Builder 670 args := new(bytes.Buffer) 671 canBeRaw := true 672 for _, v := range expr.Parts { 673 if lit, ok := v.(*model.LiteralValueExpression); ok && lit.Value.Type().Equals(cty.String) { 674 str := lit.Value.AsString() 675 // We don't want to accidentally embed a formatting directive in our 676 // formatting string. 677 if !strings.ContainsRune(str, '%') { 678 if canBeRaw && strings.ContainsRune(str, '`') { 679 canBeRaw = false 680 } 681 // Build the formatting string 682 fmtStr.WriteString(str) 683 continue 684 } 685 } 686 // v cannot be directly inserted into the formatting string, so put it in the 687 // argument list. 688 fmtStr.WriteString("%v") 689 g.Fgenf(args, ", %.v", v) 690 } 691 g.Fgenf(w, "fmt.Sprintf(") 692 str := fmtStr.String() 693 if canBeRaw && len(str) > 50 && strings.Count(str, "\n") > 5 { 694 fmt.Fprintf(w, "`%s`", str) 695 } else { 696 g.genStringLiteral(w, fmtStr.String()) 697 } 698 _, err := args.WriteTo(w) 699 contract.AssertNoError(err) 700 g.Fgenf(w, ")") 701 } 702 703 // GenTemplateJoinExpression generates code for a TemplateJoinExpression. 704 func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) { /*TODO*/ 705 } 706 707 func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) { 708 g.genTupleConsExpression(w, expr, expr.Type()) 709 } 710 711 // GenTupleConsExpression generates code for a TupleConsExpression. 712 func (g *generator) genTupleConsExpression(w io.Writer, expr *model.TupleConsExpression, destType model.Type) { 713 isInput := isInputty(destType) 714 715 var temps []interface{} 716 for i, item := range expr.Expressions { 717 item, itemTemps := g.lowerExpression(item, item.Type()) 718 temps = append(temps, itemTemps...) 719 expr.Expressions[i] = item 720 } 721 g.genTemps(w, temps) 722 argType := g.argumentTypeName(expr, destType, isInput) 723 g.Fgenf(w, "%s{\n", argType) 724 switch len(expr.Expressions) { 725 case 0: 726 // empty array 727 break 728 default: 729 for _, v := range expr.Expressions { 730 g.Fgenf(w, "%v,\n", v) 731 } 732 } 733 g.Fgenf(w, "}") 734 } 735 736 func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) { 737 opstr, precedence := "", g.GetPrecedence(expr) 738 switch expr.Operation { 739 case hclsyntax.OpLogicalNot: 740 opstr = "!" 741 case hclsyntax.OpNegate: 742 opstr = "-" 743 } 744 g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand) 745 } 746 747 var typeNameID = 0 748 749 // argumentTypeName computes the go type for the given expression and model type. 750 func (g *generator) argumentTypeName(expr model.Expression, destType model.Type, isInput bool) (result string) { 751 // defer func(id int, t model.Type) { 752 // schemaType, _ := pcl.GetSchemaForType(destType) 753 // log.Printf("%v: argumentTypeName(%v, %v, %v) = %v", id, t, isInput, schemaType, result) 754 // }(typeNameID, destType) 755 typeNameID++ 756 757 if cns, ok := destType.(*model.ConstType); ok { 758 destType = cns.Type 759 } 760 761 // This can happen with null literals. 762 if destType == model.NoneType { 763 return "" 764 } 765 766 if schemaType, ok := pcl.GetSchemaForType(destType); ok { 767 pkg := &pkgContext{pkg: &schema.Package{Name: "main"}, externalPackages: g.externalCache} 768 return pkg.argsType(schemaType) 769 } 770 771 switch destType := destType.(type) { 772 case *model.OpaqueType: 773 switch *destType { 774 case *model.IntType: 775 if isInput { 776 return "pulumi.Int" 777 } 778 return "int" 779 case *model.NumberType: 780 if isInput { 781 return "pulumi.Float64" 782 } 783 return "float64" 784 case *model.StringType: 785 if isInput { 786 return "pulumi.String" 787 } 788 return "string" 789 case *model.BoolType: 790 if isInput { 791 return "pulumi.Bool" 792 } 793 return "bool" 794 case *model.DynamicType: 795 if isInput { 796 return "pulumi.Any" 797 } 798 return "interface{}" 799 default: 800 return string(*destType) 801 } 802 case *model.ObjectType: 803 804 if isInput { 805 // check for element type uniformity and return appropriate type if so 806 allSameType := true 807 var elmType string 808 for _, v := range destType.Properties { 809 valType := g.argumentTypeName(nil, v, true) 810 if elmType != "" && elmType != valType { 811 allSameType = false 812 break 813 } 814 elmType = valType 815 } 816 if allSameType && elmType != "" { 817 return fmt.Sprintf("%sMap", elmType) 818 } 819 return "pulumi.Map" 820 } 821 return "map[string]interface{}" 822 case *model.MapType: 823 valType := g.argumentTypeName(nil, destType.ElementType, isInput) 824 if isInput { 825 return fmt.Sprintf("pulumi.%sMap", Title(valType)) 826 } 827 return fmt.Sprintf("map[string]%s", valType) 828 case *model.ListType: 829 argTypeName := g.argumentTypeName(nil, destType.ElementType, isInput) 830 if strings.HasPrefix(argTypeName, "pulumi.") && argTypeName != "pulumi.Resource" { 831 return fmt.Sprintf("%sArray", argTypeName) 832 } 833 return fmt.Sprintf("[]%s", argTypeName) 834 case *model.TupleType: 835 // attempt to collapse tuple types. intentionally does not use model.UnifyTypes 836 // correct go code requires all types to match, or use of interface{} 837 var elmType model.Type 838 for i, t := range destType.ElementTypes { 839 if i == 0 { 840 elmType = t 841 if cns, ok := elmType.(*model.ConstType); ok { 842 elmType = cns.Type 843 } 844 continue 845 } 846 847 if !elmType.AssignableFrom(t) { 848 elmType = nil 849 break 850 } 851 } 852 853 if elmType != nil { 854 argTypeName := g.argumentTypeName(nil, elmType, isInput) 855 if strings.HasPrefix(argTypeName, "pulumi.") && argTypeName != "pulumi.Resource" { 856 return fmt.Sprintf("%sArray", argTypeName) 857 } 858 return fmt.Sprintf("[]%s", argTypeName) 859 } 860 861 if isInput { 862 return "pulumi.Array" 863 } 864 return "[]interface{}" 865 case *model.OutputType: 866 isInput = true 867 return g.argumentTypeName(expr, destType.ElementType, isInput) 868 case *model.UnionType: 869 for _, ut := range destType.ElementTypes { 870 switch ut := ut.(type) { 871 case *model.OpaqueType: 872 return g.argumentTypeName(expr, ut, isInput) 873 case *model.ConstType: 874 return g.argumentTypeName(expr, ut.Type, isInput) 875 case *model.TupleType: 876 return g.argumentTypeName(expr, ut, isInput) 877 } 878 } 879 return "interface{}" 880 case *model.PromiseType: 881 return g.argumentTypeName(expr, destType.ElementType, isInput) 882 default: 883 contract.Failf("unexpected destType type %T", destType) 884 } 885 return "" 886 } 887 888 func (g *generator) genRelativeTraversal(w io.Writer, 889 traversal hcl.Traversal, parts []model.Traversable, isRootResource bool) { 890 891 for i, part := range traversal { 892 var key cty.Value 893 switch part := part.(type) { 894 case hcl.TraverseAttr: 895 key = cty.StringVal(part.Name) 896 case hcl.TraverseIndex: 897 key = part.Key 898 default: 899 contract.Failf("unexpected traversal part of type %T (%v)", part, part.SourceRange()) 900 } 901 902 // TODO handle optionals in go 903 // if model.IsOptionalType(model.GetTraversableType(parts[i])) { 904 // g.Fgen(w, "?") 905 // } 906 907 switch key.Type() { 908 case cty.String: 909 shouldConvert := isRootResource 910 if _, ok := parts[i].(*model.OutputType); ok { 911 shouldConvert = true 912 } 913 if key.AsString() == "id" && shouldConvert { 914 g.Fgenf(w, ".ID()") 915 } else { 916 g.Fgenf(w, ".%s", Title(key.AsString())) 917 } 918 case cty.Number: 919 idx, _ := key.AsBigFloat().Int64() 920 g.Fgenf(w, "[%d]", idx) 921 default: 922 contract.Failf("unexpected traversal key of type %T (%v)", key, key.AsString()) 923 } 924 } 925 } 926 927 type nameInfo int 928 929 func (nameInfo) Format(name string) string { 930 // TODO 931 return name 932 } 933 934 // lowerExpression amends the expression with intrinsics for Go generation. 935 func (g *generator) lowerExpression(expr model.Expression, typ model.Type) ( 936 model.Expression, []interface{}) { 937 expr = pcl.RewritePropertyReferences(expr) 938 expr, diags := pcl.RewriteApplies(expr, nameInfo(0), false /*TODO*/) 939 expr, convertDiags := pcl.RewriteConversions(expr, typ) 940 expr, tTemps, ternDiags := g.rewriteTernaries(expr, g.ternaryTempSpiller) 941 expr, jTemps, jsonDiags := g.rewriteToJSON(expr) 942 expr, rTemps, readDirDiags := g.rewriteReadDir(expr, g.readDirTempSpiller) 943 expr, sTemps, splatDiags := g.rewriteSplat(expr, g.splatSpiller) 944 expr, oTemps, optDiags := g.rewriteOptionals(expr, g.optionalSpiller) 945 946 var temps []interface{} 947 for _, t := range tTemps { 948 temps = append(temps, t) 949 } 950 for _, t := range jTemps { 951 temps = append(temps, t) 952 } 953 for _, t := range rTemps { 954 temps = append(temps, t) 955 } 956 for _, t := range sTemps { 957 temps = append(temps, t) 958 } 959 for _, t := range oTemps { 960 temps = append(temps, t) 961 } 962 diags = append(diags, convertDiags...) 963 diags = append(diags, ternDiags...) 964 diags = append(diags, jsonDiags...) 965 diags = append(diags, readDirDiags...) 966 diags = append(diags, splatDiags...) 967 diags = append(diags, optDiags...) 968 g.diagnostics = g.diagnostics.Extend(diags) 969 return expr, temps 970 } 971 972 func (g *generator) genNYI(w io.Writer, reason string, vs ...interface{}) { 973 message := fmt.Sprintf("not yet implemented: %s", fmt.Sprintf(reason, vs...)) 974 g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{ 975 Severity: hcl.DiagError, 976 Summary: message, 977 Detail: message, 978 }) 979 g.Fgenf(w, "\"TODO: %s\"", fmt.Sprintf(reason, vs...)) 980 } 981 982 func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) { 983 // Extract the list of outputs and the continuation expression from the `__apply` arguments. 984 applyArgs, then := pcl.ParseApplyCall(expr) 985 isInput := false 986 retType := g.argumentTypeName(nil, then.Signature.ReturnType, isInput) 987 // TODO account for outputs in other namespaces like aws 988 // TODO[pulumi/pulumi#8453] incomplete pattern code below. 989 var typeAssertion string 990 if retType == "[]string" { 991 typeAssertion = ".(pulumi.StringArrayOutput)" 992 } else { 993 typeAssertion = fmt.Sprintf(".(%sOutput)", retType) 994 if !strings.Contains(retType, ".") { 995 typeAssertion = fmt.Sprintf(".(pulumi.%sOutput)", Title(retType)) 996 } 997 } 998 999 if len(applyArgs) == 1 { 1000 // If we only have a single output, just generate a normal `.Apply` 1001 g.Fgenf(w, "%.v.ApplyT(%.v)%s", applyArgs[0], then, typeAssertion) 1002 } else { 1003 g.Fgenf(w, "pulumi.All(%.v", applyArgs[0]) 1004 applyArgs = applyArgs[1:] 1005 for _, a := range applyArgs { 1006 g.Fgenf(w, ",%.v", a) 1007 } 1008 allApplyThen, typeConvDecls := g.rewriteThenForAllApply(then) 1009 g.Fgenf(w, ").ApplyT(") 1010 g.genAnonymousFunctionExpression(w, allApplyThen, typeConvDecls, true) 1011 g.Fgenf(w, ")%s", typeAssertion) 1012 } 1013 } 1014 1015 // rewriteThenForAllApply rewrites an apply func after a .All replacing params with []interface{} 1016 // other languages like javascript take advantage of destructuring to simplify All.Apply 1017 // by generating something like [a1, a2, a3] 1018 // Go doesn't support this syntax so we create a set of var decls with type assertions 1019 // to prepend to the body: a1 := _args[0].(string) ... etc. 1020 func (g *generator) rewriteThenForAllApply( 1021 then *model.AnonymousFunctionExpression, 1022 ) (*model.AnonymousFunctionExpression, []string) { 1023 var typeConvDecls []string 1024 for i, v := range then.Parameters { 1025 typ := g.argumentTypeName(nil, v.VariableType, false) 1026 decl := fmt.Sprintf("%s := _args[%d].(%s)", v.Name, i, typ) 1027 typeConvDecls = append(typeConvDecls, decl) 1028 } 1029 1030 // dummy type that will produce []interface{} for argumentTypeName 1031 interfaceArrayType := &model.TupleType{ 1032 ElementTypes: []model.Type{ 1033 model.BoolType, model.StringType, model.IntType, 1034 }, 1035 } 1036 1037 then.Parameters = []*model.Variable{{ 1038 Name: "_args", 1039 VariableType: interfaceArrayType, 1040 }} 1041 then.Signature.Parameters = []model.Parameter{{ 1042 Name: "_args", 1043 Type: interfaceArrayType, 1044 }} 1045 1046 return then, typeConvDecls 1047 } 1048 1049 func (g *generator) genStringLiteral(w io.Writer, v string) { 1050 g.Fgen(w, "\"") 1051 g.Fgen(w, g.escapeString(v)) 1052 g.Fgen(w, "\"") 1053 } 1054 1055 func (g *generator) escapeString(v string) string { 1056 builder := strings.Builder{} 1057 for _, c := range v { 1058 if c == '"' || c == '\\' { 1059 builder.WriteRune('\\') 1060 } 1061 if c == '\n' { 1062 builder.WriteRune('\\') 1063 builder.WriteRune('n') 1064 continue 1065 } 1066 builder.WriteRune(c) 1067 } 1068 return builder.String() 1069 } 1070 1071 // nolint: lll 1072 func isInputty(destType model.Type) bool { 1073 // TODO this needs to be more robust, likely the inverse of: 1074 // https://github.com/pulumi/pulumi/blob/5330c97684cad78bcc60d8867f1b28704bd8a555/pkg/codegen/hcl2/model/type_eventuals.go#L244 1075 switch destType := destType.(type) { 1076 case *model.UnionType: 1077 for _, t := range destType.ElementTypes { 1078 if _, ok := t.(*model.OutputType); ok { 1079 return true 1080 } 1081 } 1082 case *model.OutputType: 1083 return true 1084 } 1085 return false 1086 } 1087 1088 func (g *generator) literalKey(x model.Expression) (string, bool) { 1089 strKey := "" 1090 switch x := x.(type) { 1091 case *model.LiteralValueExpression: 1092 if model.StringType.AssignableFrom(x.Type()) { 1093 strKey = x.Value.AsString() 1094 break 1095 } 1096 var buf bytes.Buffer 1097 g.GenLiteralValueExpression(&buf, x) 1098 return buf.String(), true 1099 case *model.TemplateExpression: 1100 if len(x.Parts) == 1 { 1101 if lit, ok := x.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) { 1102 strKey = lit.Value.AsString() 1103 break 1104 } 1105 } 1106 return "", false 1107 default: 1108 return "", false 1109 } 1110 1111 return strKey, true 1112 } 1113 1114 // functionName computes the go package, module, and name for the given function token. 1115 func (g *generator) functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) { 1116 token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString() 1117 tokenRange := tokenArg.SyntaxNode().Range() 1118 1119 // Compute the resource type from the Pulumi type token. 1120 pkg, module, member, diagnostics := pcl.DecomposeToken(token, tokenRange) 1121 if strings.HasPrefix(member, "get") { 1122 if g.useLookupInvokeForm(token) { 1123 member = strings.Replace(member, "get", "lookup", 1) 1124 } 1125 } 1126 modOrAlias := g.getModOrAlias(pkg, module, module) 1127 mod := strings.Replace(modOrAlias, "/", ".", -1) 1128 return pkg, mod, Title(member), diagnostics 1129 } 1130 1131 var functionPackages = map[string][]string{ 1132 "join": {"strings"}, 1133 "mimeType": {"mime", "path"}, 1134 "readDir": {"os"}, 1135 "readFile": {"io/ioutil"}, 1136 "filebase64": {"io/ioutil", "encoding/base64"}, 1137 "toBase64": {"encoding/base64"}, 1138 "fromBase64": {"encoding/base64"}, 1139 "toJSON": {"encoding/json"}, 1140 "sha1": {"fmt", "crypto/sha1"}, 1141 "filebase64sha256": {"fmt", "io/ioutil", "crypto/sha256"}, 1142 "cwd": {"os"}, 1143 } 1144 1145 func (g *generator) genFunctionPackages(x *model.FunctionCallExpression) []string { 1146 return functionPackages[x.Name] 1147 }