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  }