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

     1  // Copyright 2016-2021, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dotnet
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"path"
    23  	"strings"
    24  
    25  	"github.com/hashicorp/hcl/v2"
    26  	"github.com/pulumi/pulumi/pkg/v3/codegen"
    27  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
    28  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format"
    29  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
    30  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    31  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    35  )
    36  
    37  type GenerateProgramOptions struct {
    38  	// Determines whether ResourceArg types have an implicit name
    39  	// when constructing a resource. For example:
    40  	// when implicitResourceArgsTypeName is set to true,
    41  	// new Bucket("name", new BucketArgs { ... })
    42  	// becomes
    43  	// new Bucket("name", new() { ... });
    44  	// The latter syntax is only available on .NET 6 or later
    45  	implicitResourceArgsTypeName bool
    46  }
    47  
    48  type generator struct {
    49  	// The formatter to use when generating code.
    50  	*format.Formatter
    51  	program *pcl.Program
    52  	// C# namespace map per package.
    53  	namespaces map[string]map[string]string
    54  	// C# codegen compatibility mode per package.
    55  	compatibilities map[string]string
    56  	// A function to convert tokens to module names per package (utilizes the `moduleFormat` setting internally).
    57  	tokenToModules map[string]func(x string) string
    58  	// Type names per invoke function token.
    59  	functionArgs map[string]string
    60  	// keep track of variable identifiers which are the result of an invoke
    61  	// for example "var resourceGroup = GetResourceGroup.Invoke(...)"
    62  	// we will keep track of the reference "resourceGroup"
    63  	//
    64  	// later on when apply a traversal such as resourceGroup.name,
    65  	// we should rewrite it as resourceGroup.Apply(resourceGroupResult => resourceGroupResult.name)
    66  	functionInvokes map[string]*schema.Function
    67  	// Whether awaits are needed, and therefore an async Initialize method should be declared.
    68  	asyncInit            bool
    69  	configCreated        bool
    70  	diagnostics          hcl.Diagnostics
    71  	insideFunctionInvoke bool
    72  	insideAwait          bool
    73  	// Program generation options
    74  	generateOptions GenerateProgramOptions
    75  }
    76  
    77  const pulumiPackage = "pulumi"
    78  
    79  func GenerateProgramWithOptions(
    80  	program *pcl.Program,
    81  	options GenerateProgramOptions) (map[string][]byte, hcl.Diagnostics, error) {
    82  	pcl.MapProvidersAsResources(program)
    83  	// Linearize the nodes into an order appropriate for procedural code generation.
    84  	nodes := pcl.Linearize(program)
    85  
    86  	// Import C#-specific schema info.
    87  	namespaces := make(map[string]map[string]string)
    88  	compatibilities := make(map[string]string)
    89  	tokenToModules := make(map[string]func(x string) string)
    90  	functionArgs := make(map[string]string)
    91  	packages, err := program.PackageSnapshots()
    92  	if err != nil {
    93  		return nil, nil, err
    94  	}
    95  	for _, p := range packages {
    96  		if err := p.ImportLanguages(map[string]schema.Language{"csharp": Importer}); err != nil {
    97  			return make(map[string][]byte), nil, err
    98  		}
    99  
   100  		csharpInfo, hasInfo := p.Language["csharp"].(CSharpPackageInfo)
   101  		if !hasInfo {
   102  			csharpInfo = CSharpPackageInfo{}
   103  		}
   104  		packageNamespaces := csharpInfo.Namespaces
   105  		namespaces[p.Name] = packageNamespaces
   106  		compatibilities[p.Name] = csharpInfo.Compatibility
   107  		tokenToModules[p.Name] = p.TokenToModule
   108  
   109  		for _, f := range p.Functions {
   110  			if f.Inputs != nil {
   111  				functionArgs[f.Inputs.Token] = f.Token
   112  			}
   113  		}
   114  	}
   115  
   116  	g := &generator{
   117  		program:         program,
   118  		namespaces:      namespaces,
   119  		compatibilities: compatibilities,
   120  		tokenToModules:  tokenToModules,
   121  		functionArgs:    functionArgs,
   122  		functionInvokes: map[string]*schema.Function{},
   123  		generateOptions: options,
   124  	}
   125  
   126  	g.Formatter = format.NewFormatter(g)
   127  
   128  	for _, n := range nodes {
   129  		if r, ok := n.(*pcl.Resource); ok && requiresAsyncInit(r) {
   130  			g.asyncInit = true
   131  			break
   132  		}
   133  	}
   134  
   135  	var index bytes.Buffer
   136  	g.genPreamble(&index, program)
   137  
   138  	g.Indented(func() {
   139  		for _, n := range nodes {
   140  			g.genNode(&index, n)
   141  		}
   142  	})
   143  	g.genPostamble(&index, nodes)
   144  
   145  	files := map[string][]byte{
   146  		"Program.cs": index.Bytes(),
   147  	}
   148  	return files, g.diagnostics, nil
   149  }
   150  
   151  func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics, error) {
   152  	defaultOptions := GenerateProgramOptions{
   153  		// by default, we generate C# code that targets .NET 6
   154  		implicitResourceArgsTypeName: true,
   155  	}
   156  
   157  	return GenerateProgramWithOptions(program, defaultOptions)
   158  }
   159  
   160  func GenerateProject(directory string, project workspace.Project, program *pcl.Program) error {
   161  	files, diagnostics, err := GenerateProgram(program)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	if diagnostics.HasErrors() {
   166  		return diagnostics
   167  	}
   168  
   169  	// Set the runtime to "dotnet" then marshal to Pulumi.yaml
   170  	project.Runtime = workspace.NewProjectRuntimeInfo("dotnet", nil)
   171  	projectBytes, err := encoding.YAML.Marshal(project)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	files["Pulumi.yaml"] = projectBytes
   176  
   177  	// Build a .csproj based on the packages used by program
   178  	var csproj bytes.Buffer
   179  	csproj.WriteString(`<Project Sdk="Microsoft.NET.Sdk">
   180  
   181  	<PropertyGroup>
   182  		<OutputType>Exe</OutputType>
   183  		<TargetFramework>net6.0</TargetFramework>
   184  		<Nullable>enable</Nullable>
   185  	</PropertyGroup>
   186  
   187  	<ItemGroup>
   188  		<PackageReference Include="Pulumi" Version="3.*" />
   189  `)
   190  
   191  	// For each package add a PackageReference line
   192  	packages, err := program.PackageSnapshots()
   193  	if err != nil {
   194  		return err
   195  	}
   196  	for _, p := range packages {
   197  		packageTemplate := "		<PackageReference Include=\"%s\" Version=\"%s\" />\n"
   198  
   199  		if err := p.ImportLanguages(map[string]schema.Language{"csharp": Importer}); err != nil {
   200  			return err
   201  		}
   202  		if p.Name == pulumiPackage {
   203  			continue
   204  		}
   205  
   206  		packageName := fmt.Sprintf("Pulumi.%s", namespaceName(map[string]string{}, p.Name))
   207  		if langInfo, found := p.Language["csharp"]; found {
   208  			csharpInfo, ok := langInfo.(CSharpPackageInfo)
   209  			if ok {
   210  				namespace := namespaceName(csharpInfo.Namespaces, p.Name)
   211  				packageName = fmt.Sprintf("%s.%s", csharpInfo.GetRootNamespace(), namespace)
   212  			}
   213  		}
   214  		if p.Version != nil {
   215  			csproj.WriteString(fmt.Sprintf(packageTemplate, packageName, p.Version.String()))
   216  		} else {
   217  			csproj.WriteString(fmt.Sprintf(packageTemplate, packageName, "*"))
   218  		}
   219  	}
   220  
   221  	csproj.WriteString(`	</ItemGroup>
   222  
   223  </Project>`)
   224  
   225  	files[project.Name.String()+".csproj"] = csproj.Bytes()
   226  
   227  	// Add the language specific .gitignore
   228  	files[".gitignore"] = []byte(dotnetGitIgnore)
   229  
   230  	for filename, data := range files {
   231  		outPath := path.Join(directory, filename)
   232  		err := ioutil.WriteFile(outPath, data, 0600)
   233  		if err != nil {
   234  			return fmt.Errorf("could not write output program: %w", err)
   235  		}
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  // genTrivia generates the list of trivia associated with a given token.
   242  func (g *generator) genTrivia(w io.Writer, token syntax.Token) {
   243  	for _, t := range token.LeadingTrivia {
   244  		if c, ok := t.(syntax.Comment); ok {
   245  			g.genComment(w, c)
   246  		}
   247  	}
   248  	for _, t := range token.TrailingTrivia {
   249  		if c, ok := t.(syntax.Comment); ok {
   250  			g.genComment(w, c)
   251  		}
   252  	}
   253  }
   254  
   255  func (g *generator) warnf(location *hcl.Range, reason string, args ...interface{}) {
   256  	g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{
   257  		Severity: hcl.DiagWarning,
   258  		Summary:  fmt.Sprintf(reason, args...),
   259  		Subject:  location,
   260  	})
   261  }
   262  
   263  func (g *generator) findFunctionSchema(function string, location *hcl.Range) (*schema.Function, bool) {
   264  	function = LowerCamelCase(function)
   265  	for _, pkg := range g.program.PackageReferences() {
   266  		for it := pkg.Functions().Range(); it.Next(); {
   267  			if strings.HasSuffix(it.Token(), function) {
   268  				fn, err := it.Function()
   269  
   270  				if err != nil {
   271  					g.warnf(location, "Could not find function schema for '%s'; err %s", function, err.Error())
   272  					return nil, false
   273  				}
   274  
   275  				return fn, true
   276  			}
   277  		}
   278  	}
   279  
   280  	return nil, false
   281  }
   282  
   283  func (g *generator) isFunctionInvoke(localVariable *pcl.LocalVariable) (*schema.Function, bool) {
   284  	value := localVariable.Definition.Value
   285  	switch value.(type) {
   286  	case *model.FunctionCallExpression:
   287  		call := value.(*model.FunctionCallExpression)
   288  		switch call.Name {
   289  		case pcl.Invoke:
   290  			args := call.Args[0]
   291  			_, fullFunctionName := g.functionName(args)
   292  			functionNameParts := strings.Split(fullFunctionName, ".")
   293  			functionName := functionNameParts[len(functionNameParts)-1]
   294  			location := value.SyntaxNode().Range().Ptr()
   295  			return g.findFunctionSchema(functionName, location)
   296  		}
   297  	}
   298  
   299  	return nil, false
   300  }
   301  
   302  // genComment generates a comment into the output.
   303  func (g *generator) genComment(w io.Writer, comment syntax.Comment) {
   304  	for _, l := range comment.Lines {
   305  		g.Fgenf(w, "%s//%s\n", g.Indent, l)
   306  	}
   307  }
   308  
   309  // genPreamble generates using statements, class definition and constructor.
   310  func (g *generator) genPreamble(w io.Writer, program *pcl.Program) {
   311  	// Accumulate other using statements for the various providers and packages. Don't emit them yet, as we need
   312  	// to sort them later on.
   313  	systemUsings := codegen.NewStringSet("System.Collections.Generic")
   314  	pulumiUsings := codegen.NewStringSet()
   315  	preambleHelperMethods := codegen.NewStringSet()
   316  	for _, n := range program.Nodes {
   317  		if r, isResource := n.(*pcl.Resource); isResource {
   318  			pcl.FixupPulumiPackageTokens(r)
   319  			pkg, _, _, _ := r.DecomposeToken()
   320  			if pkg != pulumiPackage {
   321  				namespace := namespaceName(g.namespaces[pkg], pkg)
   322  				var info CSharpPackageInfo
   323  				if r.Schema != nil && r.Schema.Package != nil {
   324  					if csharpinfo, ok := r.Schema.Package.Language["csharp"].(CSharpPackageInfo); ok {
   325  						info = csharpinfo
   326  					}
   327  				}
   328  				pulumiUsings.Add(fmt.Sprintf("%s = %[2]s.%[1]s", namespace, info.GetRootNamespace()))
   329  			}
   330  		}
   331  		diags := n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) {
   332  			if call, ok := n.(*model.FunctionCallExpression); ok {
   333  				for _, i := range g.genFunctionUsings(call) {
   334  					if strings.HasPrefix(i, "System") {
   335  						systemUsings.Add(i)
   336  					} else {
   337  						pulumiUsings.Add(i)
   338  					}
   339  				}
   340  
   341  				// Checking to see if this function call deserves its own dedicated helper method in the preamble
   342  				if helperMethodBody, ok := getHelperMethodIfNeeded(call.Name); ok {
   343  					preambleHelperMethods.Add(helperMethodBody)
   344  				}
   345  			}
   346  			if _, ok := n.(*model.SplatExpression); ok {
   347  				systemUsings.Add("System.Linq")
   348  			}
   349  			return n, nil
   350  		})
   351  		contract.Assert(len(diags) == 0)
   352  	}
   353  
   354  	if g.asyncInit {
   355  		systemUsings.Add("System.Threading.Tasks")
   356  	}
   357  
   358  	for _, pkg := range systemUsings.SortedValues() {
   359  		g.Fprintf(w, "using %v;\n", pkg)
   360  	}
   361  	g.Fprintln(w, `using Pulumi;`)
   362  	for _, pkg := range pulumiUsings.SortedValues() {
   363  		g.Fprintf(w, "using %v;\n", pkg)
   364  	}
   365  
   366  	g.Fprint(w, "\n")
   367  
   368  	// If we collected any helper methods that should be added, write them just before the main func
   369  	for _, preambleHelperMethodBody := range preambleHelperMethods.SortedValues() {
   370  		g.Fprintf(w, "\t%s\n\n", preambleHelperMethodBody)
   371  	}
   372  
   373  	asyncKeywordWhenNeeded := ""
   374  	if g.asyncInit {
   375  		asyncKeywordWhenNeeded = "async"
   376  	}
   377  	g.Fprintf(w, "return await Deployment.RunAsync(%s() => \n", asyncKeywordWhenNeeded)
   378  	g.Fprint(w, "{\n")
   379  }
   380  
   381  // hasOutputVariables checks whether there are any output declarations
   382  func hasOutputVariables(nodes []pcl.Node) bool {
   383  	for _, n := range nodes {
   384  		switch n.(type) {
   385  		case *pcl.OutputVariable:
   386  			return true
   387  		}
   388  	}
   389  
   390  	return false
   391  }
   392  
   393  // genPostamble closes the method and the class and declares stack output statements.
   394  func (g *generator) genPostamble(w io.Writer, nodes []pcl.Node) {
   395  	if hasOutputVariables(nodes) {
   396  		g.Indented(func() {
   397  			g.Fgenf(w, "%sreturn new Dictionary<string, object?>\n", g.Indent)
   398  			g.Fgenf(w, "%s{\n", g.Indent)
   399  			g.Indented(func() {
   400  				// Emit stack output properties
   401  				for _, n := range nodes {
   402  					switch n := n.(type) {
   403  					case *pcl.OutputVariable:
   404  						outputID := fmt.Sprintf(`"%s"`, g.escapeString(n.LogicalName(), false, false))
   405  						g.Fgenf(w, "%s[%s] = %.3v,\n", g.Indent, outputID, g.lowerExpression(n.Value, n.Type()))
   406  					}
   407  				}
   408  			})
   409  			g.Fgenf(w, "%s};\n", g.Indent)
   410  		})
   411  	}
   412  	// Close lambda call expression
   413  	g.Fprintf(w, "});\n\n")
   414  }
   415  
   416  func (g *generator) genNode(w io.Writer, n pcl.Node) {
   417  	switch n := n.(type) {
   418  	case *pcl.Resource:
   419  		g.genResource(w, n)
   420  	case *pcl.ConfigVariable:
   421  		g.genConfigVariable(w, n)
   422  	case *pcl.LocalVariable:
   423  		g.genLocalVariable(w, n)
   424  	}
   425  }
   426  
   427  // requiresAsyncInit returns true if the program requires awaits in the code, and therefore an asynchronous
   428  // method must be declared.
   429  func requiresAsyncInit(r *pcl.Resource) bool {
   430  	if r.Options == nil || r.Options.Range == nil {
   431  		return false
   432  	}
   433  
   434  	return model.ContainsPromises(r.Options.Range.Type())
   435  }
   436  
   437  // resourceTypeName computes the C# class name for the given resource.
   438  func (g *generator) resourceTypeName(r *pcl.Resource) string {
   439  	pcl.FixupPulumiPackageTokens(r)
   440  	// Compute the resource type from the Pulumi type token.
   441  	pkg, module, member, diags := r.DecomposeToken()
   442  	contract.Assert(len(diags) == 0)
   443  
   444  	namespaces := g.namespaces[pkg]
   445  	rootNamespace := namespaceName(namespaces, pkg)
   446  
   447  	namespace := namespaceName(namespaces, module)
   448  	namespaceTokens := strings.Split(namespace, "/")
   449  	for i, name := range namespaceTokens {
   450  		namespaceTokens[i] = Title(name)
   451  	}
   452  	namespace = strings.Join(namespaceTokens, ".")
   453  
   454  	if namespace != "" {
   455  		namespace = "." + namespace
   456  	}
   457  
   458  	qualifiedMemberName := fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member))
   459  	return qualifiedMemberName
   460  }
   461  
   462  func (g *generator) extractInputPropertyNameMap(r *pcl.Resource) map[string]string {
   463  	// Extract language-specific property names from schema
   464  	var csharpInputPropertyNameMap = make(map[string]string)
   465  	if r.Schema != nil {
   466  		for _, inputProperty := range r.Schema.InputProperties {
   467  			if val1, ok := inputProperty.Language["csharp"]; ok {
   468  				if val2, ok := val1.(CSharpPropertyInfo); ok {
   469  					csharpInputPropertyNameMap[inputProperty.Name] = val2.Name
   470  				}
   471  			}
   472  		}
   473  	}
   474  	return csharpInputPropertyNameMap
   475  }
   476  
   477  // resourceArgsTypeName computes the C# arguments class name for the given resource.
   478  func (g *generator) resourceArgsTypeName(r *pcl.Resource) string {
   479  	// Compute the resource type from the Pulumi type token.
   480  	pkg, module, member, diags := r.DecomposeToken()
   481  	contract.Assert(len(diags) == 0)
   482  
   483  	namespaces := g.namespaces[pkg]
   484  	rootNamespace := namespaceName(namespaces, pkg)
   485  	namespace := namespaceName(namespaces, module)
   486  	if g.compatibilities[pkg] == "kubernetes20" && module != "" {
   487  		namespace = fmt.Sprintf("Types.Inputs.%s", namespace)
   488  	}
   489  
   490  	if namespace != "" {
   491  		namespace = "." + namespace
   492  	}
   493  
   494  	return fmt.Sprintf("%s%s.%sArgs", rootNamespace, namespace, Title(member))
   495  }
   496  
   497  // functionName computes the C# namespace and class name for the given function token.
   498  func (g *generator) functionName(tokenArg model.Expression) (string, string) {
   499  	token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
   500  	tokenRange := tokenArg.SyntaxNode().Range()
   501  
   502  	// Compute the resource type from the Pulumi type token.
   503  	pkg, module, member, diags := pcl.DecomposeToken(token, tokenRange)
   504  	contract.Assert(len(diags) == 0)
   505  	namespaces := g.namespaces[pkg]
   506  	rootNamespace := namespaceName(namespaces, pkg)
   507  	namespace := namespaceName(namespaces, module)
   508  
   509  	if namespace != "" {
   510  		namespace = "." + namespace
   511  	}
   512  
   513  	return rootNamespace, fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member))
   514  }
   515  
   516  func (g *generator) toSchemaType(destType model.Type) (schema.Type, bool) {
   517  	schemaType, ok := pcl.GetSchemaForType(destType.(model.Type))
   518  	if !ok {
   519  		return nil, false
   520  	}
   521  	return codegen.UnwrapType(schemaType), true
   522  }
   523  
   524  // argumentTypeName computes the C# argument class name for the given expression and model type.
   525  func (g *generator) argumentTypeName(expr model.Expression, destType model.Type) string {
   526  	suffix := "Args"
   527  	if g.insideFunctionInvoke {
   528  		suffix = "InputArgs"
   529  	}
   530  	return g.argumentTypeNameWithSuffix(expr, destType, suffix)
   531  }
   532  
   533  func (g *generator) argumentTypeNameWithSuffix(expr model.Expression, destType model.Type, suffix string) string {
   534  	schemaType, ok := g.toSchemaType(destType)
   535  	if !ok {
   536  		return ""
   537  	}
   538  
   539  	objType, ok := schemaType.(*schema.ObjectType)
   540  	if !ok {
   541  		return ""
   542  	}
   543  
   544  	token := objType.Token
   545  	tokenRange := expr.SyntaxNode().Range()
   546  	qualifier := "Inputs"
   547  	if f, ok := g.functionArgs[token]; ok {
   548  		token = f
   549  		qualifier = ""
   550  	}
   551  
   552  	pkg, _, member, diags := pcl.DecomposeToken(token, tokenRange)
   553  	contract.Assert(len(diags) == 0)
   554  	module := g.tokenToModules[pkg](token)
   555  	namespaces := g.namespaces[pkg]
   556  	rootNamespace := namespaceName(namespaces, pkg)
   557  	namespace := namespaceName(namespaces, module)
   558  	if strings.ToLower(namespace) == "index" {
   559  		namespace = ""
   560  	}
   561  	if namespace != "" {
   562  		namespace = "." + namespace
   563  	}
   564  	if g.compatibilities[pkg] == "kubernetes20" {
   565  		namespace = ".Types.Inputs" + namespace
   566  	} else if qualifier != "" {
   567  		namespace = namespace + "." + qualifier
   568  	}
   569  	member = member + suffix
   570  
   571  	return fmt.Sprintf("%s%s.%s", rootNamespace, namespace, Title(member))
   572  }
   573  
   574  // makeResourceName returns the expression that should be emitted for a resource's "name" parameter given its base name
   575  // and the count variable name, if any.
   576  func (g *generator) makeResourceName(baseName, count string) string {
   577  	if count == "" {
   578  		return fmt.Sprintf(`"%s"`, baseName)
   579  	}
   580  	return fmt.Sprintf("$\"%s-{%s}\"", baseName, count)
   581  }
   582  
   583  func (g *generator) genResourceOptions(opts *pcl.ResourceOptions) string {
   584  	if opts == nil {
   585  		return ""
   586  	}
   587  	var result bytes.Buffer
   588  	appendOption := func(name string, value model.Expression) {
   589  		if result.Len() == 0 {
   590  			_, err := fmt.Fprintf(&result, ", new CustomResourceOptions\n%s{", g.Indent)
   591  			g.Indent += "    "
   592  			contract.IgnoreError(err)
   593  		}
   594  
   595  		if name == "IgnoreChanges" {
   596  			// ignore changes need to be special cased
   597  			// because new [] { "field" } cannot be implicitly casted to List<string>
   598  			// which is the type of IgnoreChanges
   599  			if changes, isTuple := value.(*model.TupleConsExpression); isTuple {
   600  				g.Fgenf(&result, "\n%sIgnoreChanges =", g.Indent)
   601  				g.Fgenf(&result, "\n%s{", g.Indent)
   602  				g.Indented(func() {
   603  					for _, v := range changes.Expressions {
   604  						g.Fgenf(&result, "\n%s\"%.v\",", g.Indent, v)
   605  					}
   606  				})
   607  				g.Fgenf(&result, "\n%s}", g.Indent)
   608  			} else {
   609  				g.Fgenf(&result, "\n%s%s = %v,", g.Indent, name, g.lowerExpression(value, value.Type()))
   610  			}
   611  		} else {
   612  			g.Fgenf(&result, "\n%s%s = %v,", g.Indent, name, g.lowerExpression(value, value.Type()))
   613  		}
   614  	}
   615  
   616  	if opts.Parent != nil {
   617  		appendOption("Parent", opts.Parent)
   618  	}
   619  	if opts.Provider != nil {
   620  		appendOption("Provider", opts.Provider)
   621  	}
   622  	if opts.DependsOn != nil {
   623  		appendOption("DependsOn", opts.DependsOn)
   624  	}
   625  	if opts.Protect != nil {
   626  		appendOption("Protect", opts.Protect)
   627  	}
   628  	if opts.IgnoreChanges != nil {
   629  		appendOption("IgnoreChanges", opts.IgnoreChanges)
   630  	}
   631  
   632  	if result.Len() != 0 {
   633  		g.Indent = g.Indent[:len(g.Indent)-4]
   634  		_, err := fmt.Fprintf(&result, "\n%s}", g.Indent)
   635  		contract.IgnoreError(err)
   636  	}
   637  
   638  	return result.String()
   639  }
   640  
   641  // genResource handles the generation of instantiations of non-builtin resources.
   642  func (g *generator) genResource(w io.Writer, r *pcl.Resource) {
   643  	qualifiedMemberName := g.resourceTypeName(r)
   644  	csharpInputPropertyNameMap := g.extractInputPropertyNameMap(r)
   645  
   646  	// Add conversions to input properties
   647  	for _, input := range r.Inputs {
   648  		destType, diagnostics := r.InputType.Traverse(hcl.TraverseAttr{Name: input.Name})
   649  		g.diagnostics = append(g.diagnostics, diagnostics...)
   650  		input.Value = g.lowerExpression(input.Value, destType.(model.Type))
   651  		if csharpName, ok := csharpInputPropertyNameMap[input.Name]; ok {
   652  			input.Name = csharpName
   653  		}
   654  	}
   655  
   656  	pcl.AnnotateResourceInputs(r)
   657  
   658  	name := r.LogicalName()
   659  	variableName := makeValidIdentifier(r.Name())
   660  	argsName := g.resourceArgsTypeName(r)
   661  	g.genTrivia(w, r.Definition.Tokens.GetType(""))
   662  	for _, l := range r.Definition.Tokens.GetLabels(nil) {
   663  		g.genTrivia(w, l)
   664  	}
   665  	g.genTrivia(w, r.Definition.Tokens.GetOpenBrace())
   666  
   667  	instantiate := func(resName string) {
   668  		if len(r.Inputs) == 0 && r.Options == nil {
   669  			// only resource name is provided
   670  			g.Fgenf(w, "new %s(%s)", qualifiedMemberName, resName)
   671  		} else {
   672  			if g.generateOptions.implicitResourceArgsTypeName {
   673  				g.Fgenf(w, "new %s(%s, new()\n", qualifiedMemberName, resName)
   674  			} else {
   675  				g.Fgenf(w, "new %s(%s, new %s\n", qualifiedMemberName, resName, argsName)
   676  			}
   677  
   678  			g.Fgenf(w, "%s{\n", g.Indent)
   679  			g.Indented(func() {
   680  				for _, attr := range r.Inputs {
   681  					g.Fgenf(w, "%s%s =", g.Indent, propertyName(attr.Name))
   682  					g.Fgenf(w, " %.v,\n", attr.Value)
   683  				}
   684  			})
   685  			g.Fgenf(w, "%s}%s)", g.Indent, g.genResourceOptions(r.Options))
   686  		}
   687  	}
   688  
   689  	if r.Options != nil && r.Options.Range != nil {
   690  		rangeType := model.ResolveOutputs(r.Options.Range.Type())
   691  		rangeExpr := g.lowerExpression(r.Options.Range, rangeType)
   692  
   693  		g.Fgenf(w, "%svar %s = new List<%s>();\n", g.Indent, variableName, qualifiedMemberName)
   694  
   695  		resKey := "Key"
   696  		if model.InputType(model.NumberType).ConversionFrom(rangeExpr.Type()) != model.NoConversion {
   697  			g.Fgenf(w, "%sfor (var rangeIndex = 0; rangeIndex < %.12o; rangeIndex++)\n", g.Indent, rangeExpr)
   698  			g.Fgenf(w, "%s{\n", g.Indent)
   699  			g.Fgenf(w, "%s    var range = new { Value = rangeIndex };\n", g.Indent)
   700  			resKey = "Value"
   701  		} else {
   702  			rangeExpr := &model.FunctionCallExpression{
   703  				Name: "entries",
   704  				Args: []model.Expression{rangeExpr},
   705  			}
   706  			g.Fgenf(w, "%sforeach (var range in %.v)\n", g.Indent, rangeExpr)
   707  			g.Fgenf(w, "%s{\n", g.Indent)
   708  		}
   709  
   710  		resName := g.makeResourceName(name, "range."+resKey)
   711  		g.Indented(func() {
   712  			g.Fgenf(w, "%s%s.Add(", g.Indent, variableName)
   713  			instantiate(resName)
   714  			g.Fgenf(w, ");\n")
   715  		})
   716  		g.Fgenf(w, "%s}\n", g.Indent)
   717  	} else {
   718  		g.Fgenf(w, "%svar %s = ", g.Indent, variableName)
   719  		instantiate(g.makeResourceName(name, ""))
   720  		g.Fgenf(w, ";\n\n")
   721  	}
   722  
   723  	g.genTrivia(w, r.Definition.Tokens.GetCloseBrace())
   724  }
   725  
   726  func (g *generator) genConfigVariable(w io.Writer, v *pcl.ConfigVariable) {
   727  	if !g.configCreated {
   728  		g.Fprintf(w, "%svar config = new Config();\n", g.Indent)
   729  		g.configCreated = true
   730  	}
   731  
   732  	getType := "Object<dynamic>"
   733  	switch v.Type() {
   734  	case model.StringType:
   735  		getType = ""
   736  	case model.NumberType, model.IntType:
   737  		getType = "Number"
   738  	case model.BoolType:
   739  		getType = "Boolean"
   740  	}
   741  
   742  	getOrRequire := "Get"
   743  	if v.DefaultValue == nil {
   744  		getOrRequire = "Require"
   745  	}
   746  
   747  	name := makeValidIdentifier(v.Name())
   748  	if v.DefaultValue != nil {
   749  		typ := v.DefaultValue.Type()
   750  		if _, ok := typ.(*model.PromiseType); ok {
   751  			g.Fgenf(w, "%svar %s = Output.Create(config.%s%s(\"%s\"))",
   752  				g.Indent, name, getOrRequire, getType, v.LogicalName())
   753  		} else {
   754  			g.Fgenf(w, "%svar %s = config.%s%s(\"%s\")",
   755  				g.Indent, name, getOrRequire, getType, v.LogicalName())
   756  		}
   757  		expr := g.lowerExpression(v.DefaultValue, v.DefaultValue.Type())
   758  		g.Fgenf(w, " ?? %.v", expr)
   759  	} else {
   760  		g.Fgenf(w, "%svar %s = config.%s%s(\"%s\")",
   761  			g.Indent, name, getOrRequire, getType, v.LogicalName())
   762  	}
   763  	g.Fgenf(w, ";\n")
   764  }
   765  
   766  func (g *generator) genLocalVariable(w io.Writer, localVariable *pcl.LocalVariable) {
   767  	variableName := makeValidIdentifier(localVariable.Name())
   768  	value := localVariable.Definition.Value
   769  	functionSchema, isInvokeCall := g.isFunctionInvoke(localVariable)
   770  	if isInvokeCall {
   771  		result := g.lowerExpressionWithoutApplies(value, value.Type())
   772  		g.functionInvokes[variableName] = functionSchema
   773  		g.Fgenf(w, "%svar %s = %v;\n\n", g.Indent, variableName, result)
   774  	} else {
   775  		result := g.lowerExpression(value, value.Type())
   776  		g.Fgenf(w, "%svar %s = %v;\n\n", g.Indent, variableName, result)
   777  	}
   778  }
   779  
   780  func (g *generator) genNYI(w io.Writer, reason string, vs ...interface{}) {
   781  	message := fmt.Sprintf("not yet implemented: %s", fmt.Sprintf(reason, vs...))
   782  	g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{
   783  		Severity: hcl.DiagError,
   784  		Summary:  message,
   785  		Detail:   message,
   786  	})
   787  	g.Fgenf(w, "\"TODO: %s\"", fmt.Sprintf(reason, vs...))
   788  }