pulumi/pkg/codegen/go/gen_program_expressions.go

1288 lines
39 KiB
Go
Raw Normal View History

// Copyright 2020-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2020-05-21 17:23:33 +00:00
package gen
import (
"bytes"
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
"errors"
2020-05-21 17:23:33 +00:00
"fmt"
"io"
"math/big"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/pulumi/pulumi/pkg/v3/codegen"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
2020-05-21 17:23:33 +00:00
"github.com/zclconf/go-cty/cty"
)
const keywordRange = "range"
2020-05-21 17:23:33 +00:00
func (g *generator) GetPrecedence(expr model.Expression) int {
// TODO: Current values copied from Node, update based on
// https://golang.org/ref/spec
switch expr := expr.(type) {
case *model.ConditionalExpression:
return 4
case *model.BinaryOpExpression:
switch expr.Operation {
case hclsyntax.OpLogicalOr:
return 5
case hclsyntax.OpLogicalAnd:
return 6
case hclsyntax.OpEqual, hclsyntax.OpNotEqual:
return 11
case hclsyntax.OpGreaterThan, hclsyntax.OpGreaterThanOrEqual, hclsyntax.OpLessThan,
hclsyntax.OpLessThanOrEqual:
return 12
case hclsyntax.OpAdd, hclsyntax.OpSubtract:
return 14
case hclsyntax.OpMultiply, hclsyntax.OpDivide, hclsyntax.OpModulo:
return 15
default:
contract.Failf("unexpected binary expression %v", expr)
}
case *model.UnaryOpExpression:
return 17
case *model.FunctionCallExpression:
switch expr.Name {
default:
return 20
}
case *model.ForExpression, *model.IndexExpression, *model.RelativeTraversalExpression, *model.SplatExpression,
*model.TemplateJoinExpression:
return 20
case *model.AnonymousFunctionExpression, *model.LiteralValueExpression, *model.ObjectConsExpression,
*model.ScopeTraversalExpression, *model.TemplateExpression, *model.TupleConsExpression:
return 22
default:
contract.Failf("unexpected expression %v of type %T", expr, expr)
}
return 0
}
// GenAnonymousFunctionExpression generates code for an AnonymousFunctionExpression.
func (g *generator) GenAnonymousFunctionExpression(w io.Writer, expr *model.AnonymousFunctionExpression) {
g.genAnonymousFunctionExpression(w, expr, nil, false)
2020-06-19 00:17:51 +00:00
}
func (g *generator) genAnonymousFunctionExpression(
w io.Writer,
expr *model.AnonymousFunctionExpression,
bodyPreamble []string,
inApply bool,
2020-06-19 00:17:51 +00:00
) {
g.Fgenf(w, "func(")
leadingSep := ""
for _, param := range expr.Signature.Parameters {
isInput := isInputty(param.Type)
g.Fgenf(w, "%s%s %s", leadingSep, makeValidIdentifier(param.Name), g.argumentTypeName(param.Type, isInput))
leadingSep = ", "
}
retType := expr.Signature.ReturnType
if inApply {
retType = model.ResolveOutputs(retType)
}
retTypeName := g.argumentTypeName(retType, false)
g.Fgenf(w, ") (%s, error) {\n", retTypeName)
2020-06-19 00:17:51 +00:00
for _, decl := range bodyPreamble {
g.Fgenf(w, "%s\n", decl)
}
body, temps := g.lowerExpression(expr.Body, retType)
g.genTempsMultiReturn(w, temps, retTypeName)
2022-09-16 23:12:29 +00:00
// g.Fgenf(w, "return %v, nil", body)
// fromBase64 special case
if b, ok := body.(*model.FunctionCallExpression); ok && b.Name == fromBase64Fn {
g.Fgenf(w, "value, _ := %v\n", b)
g.Fgenf(w, "return pulumi.String(value), nil")
} else if strings.HasPrefix(retTypeName, "pulumi") {
g.Fgenf(w, "return %s(%v), nil", retTypeName, body)
} else {
g.Fgenf(w, "return %v, nil", body)
}
g.Fgenf(w, "\n}")
2020-05-21 17:23:33 +00:00
}
func (g *generator) GenBinaryOpExpression(w io.Writer, expr *model.BinaryOpExpression) {
Enable some more linting rules (#17456) Issue #10659 lists a number of extra linting checks that we could enable in order to make our Go code more robust. This commit implements as many as seem sensible: * `durationcheck`, which checks for multiplication of `time.Duration`s, which can lead to unexpected behaviour (e.g. `time.Second * time.Second` is *not* one second) * `goprintffuncname`, which checks that `Printf`-like functions are appropriately suffixed with `f` to indicate as such * `tenv`, which checks for `os.Setenv` in tests where `t.Setenv` is generally a better solution * `wastedassign`, which checks for assignments whose values are never used (such as initial values before an `if` where both branches then overwrite the value) * `whitespace`, which checks for blank lines at the beginning and end of blocks such as functions, `if`s, `for`s and so on. This commit does *not* enable the following checks listed in #10659: * `wrapcheck`, which insists that third-party library errors are always `%w`rapped -- we have a lot of cases where we don't do this and it's probably a bit more involved than "just wrap them" in terms of making sure we don't break anything (maybe) * `predeclared`, which checks for shadowing of existing Go identifiers -- we use `old` and `new` a lot, especially in step generation, so this is probably a slightly bigger clean-up/one we might want to opt out of * `mnd` (magic number detection) -- we have a lot of failures on this * `nilnil` -- we only have a couple of failures on this; these could probably be handled with `//nolint` but for now I've opted not to take this route.
2024-10-03 17:37:13 +00:00
var opstr string
precedence := g.GetPrecedence(expr)
switch expr.Operation {
case hclsyntax.OpAdd:
opstr = "+"
case hclsyntax.OpDivide:
opstr = "/"
case hclsyntax.OpEqual:
opstr = "=="
case hclsyntax.OpGreaterThan:
opstr = ">"
case hclsyntax.OpGreaterThanOrEqual:
opstr = ">="
case hclsyntax.OpLessThan:
opstr = "<"
case hclsyntax.OpLessThanOrEqual:
opstr = "<="
case hclsyntax.OpLogicalAnd:
opstr = "&&"
case hclsyntax.OpLogicalOr:
opstr = "||"
case hclsyntax.OpModulo:
opstr = "%"
case hclsyntax.OpMultiply:
opstr = "*"
case hclsyntax.OpNotEqual:
opstr = "!="
case hclsyntax.OpSubtract:
opstr = "-"
default:
opstr, precedence = ",", 1
}
2020-05-21 17:23:33 +00:00
g.Fgenf(w, "%.[1]*[2]v %[3]v %.[1]*[4]o", precedence, expr.LeftOperand, opstr, expr.RightOperand)
}
func (g *generator) GenConditionalExpression(w io.Writer, expr *model.ConditionalExpression) {
// Ternary expressions are not supported in go so we need to allocate temp variables in the parent scope.
// This is handled by lower expression and rewriteTernaries
contract.Failf("unlowered conditional expression @ %v", expr.SyntaxNode().Range())
}
2020-05-21 17:23:33 +00:00
// GenForExpression generates code for a ForExpression.
func (g *generator) GenForExpression(w io.Writer, expr *model.ForExpression) {
g.genNYI(w, "For expression")
}
[program-gen] Fix enum resolution from types of the form Union[string, Enum] and emit fully qualified enum cases (#15696) # Description This PR improves enum type resolution from strings. When we try to resolve `Union[string, Enum]` for a string expression, we choose `string` because it is the more general type since not every string is assignable to `Enum`. However, here we spacial case strings that are actually part of that `Enum`. The result is that `pcl.LowerConversion` will choose `Enum` from `Union[string, Enum]` when the value of the input string is compatible with the enum. This greatly improves program-gen for all of typescript, python, csharp and go which now will emit the fully qualified enum cases instead of emitting strings. Closes https://github.com/pulumi/pulumi-dotnet/issues/41 which is supposed to be a duplicate of https://github.com/pulumi/pulumi-azure-native/issues/2616 but that is not the case (the former is about unions of objects, the latter is unions of enums and strings) ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-15 17:49:12 +00:00
func (g *generator) genSafeEnum(w io.Writer, to *model.EnumType, dest model.Type) func(member *schema.Enum) {
return func(member *schema.Enum) {
// We know the enum value at the call site, so we can directly stamp in a
// valid enum instance. We don't need to convert.
enumName := tokenToName(to.Token)
memberTag := member.Name
if memberTag == "" {
memberTag = member.Value.(string)
}
memberTag, err := makeSafeEnumName(memberTag, enumName)
contract.AssertNoErrorf(err, "Enum is invalid")
pkg, mod, _, _ := pcl.DecomposeToken(to.Token, to.SyntaxNode().Range())
mod = g.getModOrAlias(pkg, mod, mod)
[program-gen] Fix enum resolution from types of the form Union[string, Enum] and emit fully qualified enum cases (#15696) # Description This PR improves enum type resolution from strings. When we try to resolve `Union[string, Enum]` for a string expression, we choose `string` because it is the more general type since not every string is assignable to `Enum`. However, here we spacial case strings that are actually part of that `Enum`. The result is that `pcl.LowerConversion` will choose `Enum` from `Union[string, Enum]` when the value of the input string is compatible with the enum. This greatly improves program-gen for all of typescript, python, csharp and go which now will emit the fully qualified enum cases instead of emitting strings. Closes https://github.com/pulumi/pulumi-dotnet/issues/41 which is supposed to be a duplicate of https://github.com/pulumi/pulumi-azure-native/issues/2616 but that is not the case (the former is about unions of objects, the latter is unions of enums and strings) ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-15 17:49:12 +00:00
if union, isUnion := dest.(*model.UnionType); isUnion && len(union.Annotations) > 0 {
if input, ok := union.Annotations[0].(schema.Type); ok {
if _, ok := codegen.ResolvedType(input).(*schema.UnionType); ok {
g.Fgenf(w, "pulumi.String(%s.%s)", mod, memberTag)
return
}
}
}
g.Fgenf(w, "%s.%s", mod, memberTag)
}
}
2020-05-21 17:23:33 +00:00
func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) {
switch expr.Name {
case pcl.IntrinsicConvert:
from := expr.Args[0]
to := pcl.LowerConversion(from, expr.Signature.ReturnType)
output, isOutput := to.(*model.OutputType)
originalTo := to
if isOutput {
to = output.ElementType
}
_, isFromOutput := from.Type().(*model.OutputType)
switch to := to.(type) {
case *model.EnumType:
var underlyingType string
switch {
case to.Type.Equals(model.StringType):
underlyingType = "string"
case to.Type.Equals(model.IntType):
underlyingType = "int"
default:
underlyingType = "float64"
}
pkg, mod, typ, _ := pcl.DecomposeToken(to.Token, to.SyntaxNode().Range())
mod = g.getModOrAlias(pkg, mod, mod)
enumTag := fmt.Sprintf("%s.%s", mod, typ)
if isOutput {
g.Fgenf(w,
"%.v.ApplyT(func(x *%[3]s) %[2]s { return %[2]s(*x) }).(%[2]sOutput)",
from, enumTag, underlyingType)
return
}
[program-gen] Fix enum resolution from types of the form Union[string, Enum] and emit fully qualified enum cases (#15696) # Description This PR improves enum type resolution from strings. When we try to resolve `Union[string, Enum]` for a string expression, we choose `string` because it is the more general type since not every string is assignable to `Enum`. However, here we spacial case strings that are actually part of that `Enum`. The result is that `pcl.LowerConversion` will choose `Enum` from `Union[string, Enum]` when the value of the input string is compatible with the enum. This greatly improves program-gen for all of typescript, python, csharp and go which now will emit the fully qualified enum cases instead of emitting strings. Closes https://github.com/pulumi/pulumi-dotnet/issues/41 which is supposed to be a duplicate of https://github.com/pulumi/pulumi-azure-native/issues/2616 but that is not the case (the former is about unions of objects, the latter is unions of enums and strings) ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-15 17:49:12 +00:00
diag := pcl.GenEnum(to, from, g.genSafeEnum(w, to, expr.Signature.ReturnType), func(from model.Expression) {
g.Fgenf(w, "%s(%v)", enumTag, from)
})
if diag != nil {
g.diagnostics = append(g.diagnostics, diag)
}
return
}
switch arg := from.(type) {
2020-05-21 17:23:33 +00:00
case *model.TupleConsExpression:
g.genTupleConsExpression(w, arg, expr.Type())
case *model.ObjectConsExpression:
isInput := false
g.genObjectConsExpression(w, arg, expr.Type(), isInput)
case *model.LiteralValueExpression:
g.genLiteralValueExpression(w, arg, expr.Type())
case *model.TemplateExpression:
g.genTemplateExpression(w, arg, expr.Type())
case *model.ScopeTraversalExpression:
g.genScopeTraversalExpression(w, arg, expr.Type())
2020-05-21 17:23:33 +00:00
default:
// Add a cast to the type we expect if needed
if originalTo.AssignableFrom(from.Type()) && (isOutput == isFromOutput) {
g.Fgenf(w, "%.v", from)
} else {
typeName := g.argumentTypeName(to, isOutput)
// IDOutput has a special case where it can be converted to a string
var isID bool
switch expr := from.(type) {
case *model.ScopeTraversalExpression:
last := expr.Traversal[len(expr.Traversal)-1]
if attr, ok := last.(hcl.TraverseAttr); ok && attr.Name == "id" {
isID = true
}
case *model.RelativeTraversalExpression:
last := expr.Traversal[len(expr.Traversal)-1]
if attr, ok := last.(hcl.TraverseAttr); ok && attr.Name == "id" {
isID = true
}
}
if typeName == "" {
g.Fgenf(w, "%.v", from)
} else if typeName == "pulumi.String" && isID {
g.Fgenf(w, "%.v", from)
} else {
g.Fgenf(w, "%s(%.v)", typeName, from)
}
}
2020-05-21 17:23:33 +00:00
}
case pcl.IntrinsicApply:
g.genApply(w, expr)
2020-05-21 17:23:33 +00:00
case "fileArchive":
g.Fgenf(w, "pulumi.NewFileArchive(%.v)", expr.Args[0])
case "remoteArchive":
g.Fgenf(w, "pulumi.NewRemoteArchive(%.v)", expr.Args[0])
case "assetArchive":
g.Fgenf(w, "pulumi.NewAssetArchive(%.v)", expr.Args[0])
2020-05-21 17:23:33 +00:00
case "fileAsset":
g.Fgenf(w, "pulumi.NewFileAsset(%.v)", expr.Args[0])
case "stringAsset":
g.Fgenf(w, "pulumi.NewStringAsset(%.v)", expr.Args[0])
case "remoteAsset":
g.Fgenf(w, "pulumi.NewRemoteAsset(%.v)", expr.Args[0])
case "filebase64":
// Assuming the existence of the following helper method
g.Fgenf(w, "filebase64OrPanic(%v)", expr.Args[0])
case "filebase64sha256":
// Assuming the existence of the following helper method
g.Fgenf(w, "filebase64sha256OrPanic(%v)", expr.Args[0])
case "notImplemented":
g.Fgenf(w, "notImplemented(%v)", expr.Args[0])
case "singleOrNone":
g.Fgenf(w, "singleOrNone(%v)", expr.Args[0])
case pcl.Invoke:
if expr.Signature.MultiArgumentInputs {
panic(fmt.Errorf("go program-gen does not implement MultiArgumentInputs for function '%v'",
expr.Args[0]))
}
pkg, module, fn, diags := g.functionName(expr.Args[0])
contract.Assertf(len(diags) == 0, "We don't allow problems getting the function name")
if module == "" || module == "index" {
module = pkg
}
isOut, outArgs, outArgsType := pcl.RecognizeOutputVersionedInvoke(expr)
if isOut {
outTypeName, err := outputVersionFunctionArgTypeName(outArgsType, g.externalCache)
if err != nil {
// We create a diag instead of panicking since panics are caught in go
// format expressions.
g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Error when generating an output-versioned Invoke",
Detail: fmt.Sprintf("underlying error: %v", err),
Subject: &hcl.Range{},
Context: &hcl.Range{},
Expression: nil,
EvalContext: &hcl.EvalContext{},
})
g.Fgenf(w, "%q", "failed") // Write a value to avoid syntax errors
return
}
g.Fgenf(w, "%s.%sOutput(ctx, ", module, fn)
g.genObjectConsExpressionWithTypeName(w, outArgs, outArgsType, outTypeName)
} else {
g.Fgenf(w, "%s.%s(ctx, ", module, fn)
g.Fgenf(w, "%.v", expr.Args[1])
}
Enable some more linting rules (#17456) Issue #10659 lists a number of extra linting checks that we could enable in order to make our Go code more robust. This commit implements as many as seem sensible: * `durationcheck`, which checks for multiplication of `time.Duration`s, which can lead to unexpected behaviour (e.g. `time.Second * time.Second` is *not* one second) * `goprintffuncname`, which checks that `Printf`-like functions are appropriately suffixed with `f` to indicate as such * `tenv`, which checks for `os.Setenv` in tests where `t.Setenv` is generally a better solution * `wastedassign`, which checks for assignments whose values are never used (such as initial values before an `if` where both branches then overwrite the value) * `whitespace`, which checks for blank lines at the beginning and end of blocks such as functions, `if`s, `for`s and so on. This commit does *not* enable the following checks listed in #10659: * `wrapcheck`, which insists that third-party library errors are always `%w`rapped -- we have a lot of cases where we don't do this and it's probably a bit more involved than "just wrap them" in terms of making sure we don't break anything (maybe) * `predeclared`, which checks for shadowing of existing Go identifiers -- we use `old` and `new` a lot, especially in step generation, so this is probably a slightly bigger clean-up/one we might want to opt out of * `mnd` (magic number detection) -- we have a lot of failures on this * `nilnil` -- we only have a couple of failures on this; these could probably be handled with `//nolint` but for now I've opted not to take this route.
2024-10-03 17:37:13 +00:00
var optionsBag string
var buf bytes.Buffer
if len(expr.Args) == 3 {
if invokeOptions, ok := expr.Args[2].(*model.ObjectConsExpression); ok {
g.Fgen(&buf, ", ")
for i, item := range invokeOptions.Items {
last := i == len(invokeOptions.Items)-1
switch pcl.LiteralValueString(item.Key) {
case "provider":
g.Fgenf(&buf, "pulumi.Provider(%v)", item.Value)
case "parent":
g.Fgenf(&buf, "pulumi.Parent(%v)", item.Value)
case "version":
g.Fgenf(&buf, "pulumi.Version(%v)", item.Value)
case "pluginDownloadUrl":
g.Fgenf(&buf, "pulumi.PluginDownloadURL(%v)", item.Value)
}
if !last {
g.Fgen(&buf, ", ")
}
}
}
} else {
g.Fgenf(&buf, ", nil")
}
optionsBag = buf.String()
g.Fgenf(w, "%v)", optionsBag)
case "join":
g.Fgenf(w, "strings.Join(%v, %v)", expr.Args[1], expr.Args[0])
2020-05-21 17:23:33 +00:00
case "length":
g.Fgenf(w, "len(%.20v)", expr.Args[0])
2020-05-21 17:23:33 +00:00
case "readFile":
// Assuming the existence of the following helper method located earlier in the preamble
g.Fgenf(w, "readFileOrPanic(%v)", expr.Args[0])
case "secret":
outputTypeName := "pulumi.Any"
if model.ResolveOutputs(expr.Type()) != model.DynamicType {
outputTypeName = g.argumentTypeName(expr.Type(), false)
}
g.Fgenf(w, "pulumi.ToSecret(%v).(%sOutput)", expr.Args[0], outputTypeName)
2023-01-31 12:31:00 +00:00
case "unsecret":
outputTypeName := "pulumi.Any"
if model.ResolveOutputs(expr.Type()) != model.DynamicType {
outputTypeName = g.argumentTypeName(expr.Type(), false)
2023-01-31 12:31:00 +00:00
}
g.Fgenf(w, "pulumi.Unsecret(%v).(%sOutput)", expr.Args[0], outputTypeName)
case "toBase64":
g.Fgenf(w, "base64.StdEncoding.EncodeToString([]byte(%v))", expr.Args[0])
2022-09-16 23:12:29 +00:00
case fromBase64Fn:
g.Fgenf(w, "base64.StdEncoding.DecodeString(%v)", expr.Args[0])
case "mimeType":
g.Fgenf(w, "mime.TypeByExtension(path.Ext(%.v))", expr.Args[0])
case "sha1":
g.Fgenf(w, "sha1Hash(%v)", expr.Args[0])
case "goOptionalFloat64":
g.Fgenf(w, "pulumi.Float64Ref(%.v)", expr.Args[0])
case "goOptionalBool":
g.Fgenf(w, "pulumi.BoolRef(%.v)", expr.Args[0])
case "goOptionalInt":
g.Fgenf(w, "pulumi.IntRef(%.v)", expr.Args[0])
case "goOptionalString":
g.Fgenf(w, "pulumi.StringRef(%.v)", expr.Args[0])
case "stack":
g.Fgen(w, "ctx.Stack()")
case "project":
g.Fgen(w, "ctx.Project()")
case "organization":
g.Fgen(w, "ctx.Organization()")
case "cwd":
g.Fgen(w, "func(cwd string, err error) string { if err != nil { panic(err) }; return cwd }(os.Getwd())")
case "getOutput":
g.Fgenf(w, "%v.GetOutput(pulumi.String(%v))", expr.Args[0], expr.Args[1])
2020-05-21 17:23:33 +00:00
default:
[program-gen/go] Fix required config variables of type bool and number (#14958) # Description While covering more parts of go codegen, I've seen that config variables are broken 😓 specifically when requiring config variables using `RequireFloat` it should be `RequireFloat64` and `RequireBoolean` should be `RequireBool`. Moreover, it seems that `RequireObject` doesn't work at all since the function signature doesn't match the way it was generated (see #14957) C# has a similar issue with optional untyped objects as config variables. For now have skipped compilation for those. ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-20 13:16:37 +00:00
// toJSON and readDir are reduced away, shouldn't see them here
reducedFunctions := codegen.NewStringSet("toJSON", "readDir")
contract.Assertf(!reducedFunctions.Has(expr.Name), "unlowered function %s", expr.Name)
// TODO: implement "element", "entries", "lookup", "split" and "range"
2020-05-21 17:23:33 +00:00
g.genNYI(w, "call %v", expr.Name)
}
}
// Currently args type for output-versioned invokes are named
// `FOutputArgs`, but this is not yet understood by `tokenToType`. Use
// this function to compensate.
func outputVersionFunctionArgTypeName(t model.Type, cache *Cache) (string, error) {
schemaType, ok := pcl.GetSchemaForType(t)
if !ok {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return "", errors.New("No schema.Type type found for the given model.Type")
}
objType, ok := schemaType.(*schema.ObjectType)
if !ok {
return "", fmt.Errorf("Expected a schema.ObjectType, got %s", schemaType.String())
}
pkg := &pkgContext{
pkg: (&schema.Package{Name: "main"}).Reference(),
externalPackages: cache,
}
var ty string
if pkg.isExternalReference(objType) {
extPkg, _ := pkg.contextForExternalReference(objType)
ty = extPkg.tokenToType(objType.Token)
} else {
ty = pkg.tokenToType(objType.Token)
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return strings.TrimSuffix(ty, "Args") + "OutputArgs", nil
}
func (g *generator) GenIndexExpression(w io.Writer, expr *model.IndexExpression) {
g.Fgenf(w, "%.20v[%.v]", expr.Collection, expr.Key)
}
2020-05-21 17:23:33 +00:00
func (g *generator) GenLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression) {
g.genLiteralValueExpression(w, expr, expr.Type())
}
func (g *generator) genLiteralValueExpression(w io.Writer, expr *model.LiteralValueExpression, destType model.Type) {
exprType := expr.Type()
if cns, ok := exprType.(*model.ConstType); ok {
exprType = cns.Type
}
if exprType == model.NoneType {
g.Fgen(w, "nil")
return
}
argTypeName := g.argumentTypeName(destType, false)
isPulumiType := strings.HasPrefix(argTypeName, "pulumi.")
switch exprType {
case model.BoolType:
if isPulumiType {
g.Fgenf(w, "%s(%v)", argTypeName, expr.Value.True())
} else {
g.Fgenf(w, "%v", expr.Value.True())
}
case model.NumberType, model.IntType:
bf := expr.Value.AsBigFloat()
if i, acc := bf.Int64(); acc == big.Exact {
if isPulumiType {
g.Fgenf(w, "%s(%d)", argTypeName, i)
} else {
g.Fgenf(w, "%d", i)
}
} else {
f, _ := bf.Float64()
if isPulumiType {
g.Fgenf(w, "%s(%g)", argTypeName, f)
} else {
g.Fgenf(w, "%g", f)
}
}
case model.StringType:
strVal := expr.Value.AsString()
if isPulumiType {
g.Fgenf(w, "%s(", argTypeName)
g.genStringLiteral(w, strVal, true /* allow raw */)
g.Fgenf(w, ")")
} else {
g.genStringLiteral(w, strVal, true /* allow raw */)
2020-05-21 17:23:33 +00:00
}
default:
contract.Failf("unexpected opaque type in GenLiteralValueExpression: %v (%v)", destType,
2020-05-21 17:23:33 +00:00
expr.SyntaxNode().Range())
}
}
func (g *generator) GenObjectConsExpression(w io.Writer, expr *model.ObjectConsExpression) {
switch argType := expr.Type().(type) {
case *model.ObjectType:
if len(argType.Annotations) > 0 {
if configMetadata, ok := argType.Annotations[0].(*ObjectTypeFromConfigMetadata); ok {
g.genObjectConsExpressionWithTypeName(w, expr, expr.Type(), configMetadata.TypeName)
return
}
}
}
isInput := false
g.genObjectConsExpression(w, expr, expr.Type(), isInput)
2020-05-21 17:23:33 +00:00
}
func (g *generator) genObjectConsExpression(
w io.Writer,
expr *model.ObjectConsExpression,
destType model.Type,
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
isInput bool,
) {
isInput = isInput || isInputty(destType)
typeName := g.argumentTypeName(destType, isInput)
if schemaType, ok := pcl.GetSchemaForType(destType); ok {
if obj, ok := codegen.UnwrapType(schemaType).(*schema.ObjectType); ok {
if g.useLookupInvokeForm(obj.Token) {
typeName = strings.Replace(typeName, ".Get", ".Lookup", 1)
}
}
}
[program-gen/go,dotnet] Fixes emited code for object expressions assigned to properties of type Any (#15770) # Description Fixes #15769 by specifically handling object expressions that are annotated as `Any` and emitting the correct code. - In case of Go, expression `X` is emitted as `pulumi.Any(X)` where the type name here is `map[string]interface{}`. - In case of C#, we generate `Dictionary<string, object?>` expressions (similar to what we do inside `toJSON` calls) - Updates the test aws-native schema from 0.13.0 to 0.99.0 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-24 00:06:57 +00:00
if schemaType, ok := g.toSchemaType(destType); ok {
if codegen.ResolvedType(schemaType) == schema.AnyType {
g.Fgenf(w, "pulumi.Any(")
g.genObjectConsExpressionWithTypeName(w, expr, destType, "map[string]interface{}")
g.Fgenf(w, ")")
return
}
}
g.genObjectConsExpressionWithTypeName(w, expr, destType, typeName)
}
[program-gen/go,dotnet] Fixes emited code for object expressions assigned to properties of type Any (#15770) # Description Fixes #15769 by specifically handling object expressions that are annotated as `Any` and emitting the correct code. - In case of Go, expression `X` is emitted as `pulumi.Any(X)` where the type name here is `map[string]interface{}`. - In case of C#, we generate `Dictionary<string, object?>` expressions (similar to what we do inside `toJSON` calls) - Updates the test aws-native schema from 0.13.0 to 0.99.0 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-24 00:06:57 +00:00
func (g *generator) toSchemaType(destType model.Type) (schema.Type, bool) {
schemaType, ok := pcl.GetSchemaForType(destType)
if !ok {
return nil, false
}
return codegen.UnwrapType(schemaType), true
}
func (g *generator) genObjectConsExpressionWithTypeName(
w io.Writer,
expr *model.ObjectConsExpression,
destType model.Type,
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
typeName string,
) {
// TODO: @pgavlin --- ineffectual assignment, was there some work in flight here?
// if strings.HasSuffix(typeName, "Args") {
// isInput = true
// }
// // invokes are not inputty
// if strings.Contains(typeName, ".Lookup") || strings.Contains(typeName, ".Get") {
// isInput = false
// }
isMap := strings.HasPrefix(typeName, "map[")
// TODO: retrieve schema and propagate optionals to emit bool ptr, etc.
if g.inGenTupleConExprListArgs {
if g.isPtrArg {
g.Fgenf(w, "&%s", typeName)
}
} else if isMap || !strings.HasSuffix(typeName, "Args") || strings.HasSuffix(typeName, "OutputArgs") {
g.Fgenf(w, "%s", typeName)
} else {
g.Fgenf(w, "&%s", typeName)
}
g.Fgenf(w, "{\n")
for _, item := range expr.Items {
if lit, ok := g.literalKey(item.Key); ok {
if isMap || strings.HasSuffix(typeName, "Map") {
g.Fgenf(w, "\"%s\"", lit)
} else {
g.Fgenf(w, "%s", Title(lit))
2020-05-21 17:23:33 +00:00
}
} else {
g.Fgenf(w, "%.v", item.Key)
2020-05-21 17:23:33 +00:00
}
g.Fgenf(w, ": %.v,\n", item.Value)
}
g.Fgenf(w, "}")
}
2020-05-21 17:23:33 +00:00
func (g *generator) GenRelativeTraversalExpression(w io.Writer, expr *model.RelativeTraversalExpression) {
g.Fgenf(w, "%.20v", expr.Source)
isRootResource := false
if ie, ok := expr.Source.(*model.IndexExpression); ok {
if se, ok := ie.Collection.(*model.ScopeTraversalExpression); ok {
if _, ok := se.Parts[0].(*pcl.Resource); ok {
isRootResource = true
}
}
}
g.genRelativeTraversal(w, expr.Traversal, expr.Parts, isRootResource)
2020-05-21 17:23:33 +00:00
}
func (g *generator) GenScopeTraversalExpression(w io.Writer, expr *model.ScopeTraversalExpression) {
g.genScopeTraversalExpression(w, expr, expr.Type())
}
func (g *generator) genScopeTraversalExpression(
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
w io.Writer, expr *model.ScopeTraversalExpression, destType model.Type,
) {
2020-05-21 17:23:33 +00:00
rootName := expr.RootName
if _, ok := expr.Parts[0].(*model.SplatVariable); ok {
rootName = "val0"
}
2020-05-21 17:23:33 +00:00
genIDCall := false
2020-05-21 17:23:33 +00:00
isInput := false
if schemaType, ok := pcl.GetSchemaForType(destType); ok {
_, isInput = schemaType.(*schema.InputType)
}
var sourceIsPlain bool
switch root := expr.Parts[0].(type) {
case *pcl.Resource:
isInput = false
if _, ok := pcl.GetSchemaForType(root.InputType); ok {
// convert .id into .ID()
last := expr.Traversal[len(expr.Traversal)-1]
if attr, ok := last.(hcl.TraverseAttr); ok && attr.Name == "id" {
genIDCall = true
expr.Traversal = expr.Traversal[:len(expr.Traversal)-1]
}
2020-05-21 17:23:33 +00:00
}
case *pcl.LocalVariable:
if root, ok := root.Definition.Value.(*model.FunctionCallExpression); ok && !pcl.IsOutputVersionInvokeCall(root) {
sourceIsPlain = true
}
case *pcl.ConfigVariable:
if g.isComponent {
// config variables of components are always of type Input<T>
// these shouldn't be wrapped in a pulumi.String(...), pulumi.Int(...) etc. functions
g.Fgenf(w, "args.%s", Title(rootName))
isRootResource := false
g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts[1:], isRootResource)
return
}
2020-05-21 17:23:33 +00:00
}
// TODO if it's an array type, we need a lowering step to turn []string -> pulumi.StringArray
if isInput {
argTypeName := g.argumentTypeName(expr.Type(), isInput)
if strings.HasSuffix(argTypeName, "Array") {
destTypeName := g.argumentTypeName(destType, isInput)
// `argTypeName` == `destTypeName` and `argTypeName` ends with `Array`, we
// know that `destType` is an outputty type. If the source is plain (and thus
// not outputty), then the types can never line up and we will need a
// conversion helper method.
if argTypeName != destTypeName || sourceIsPlain {
// use a helper to transform prompt arrays into inputty arrays
var helper *promptToInputArrayHelper
if h, ok := g.arrayHelpers[argTypeName]; ok {
helper = h
} else {
// helpers are emitted at the end in the postamble step
helper = &promptToInputArrayHelper{
destType: argTypeName,
}
g.arrayHelpers[argTypeName] = helper
}
// Wrap the emitted expression in a call to the generated helper function.
g.Fgenf(w, "%s(", helper.getFnName())
defer g.Fgenf(w, ")")
}
} else {
// Wrap the emitted expression in a type conversion.
g.Fgenf(w, "%s(", g.argumentTypeName(expr.Type(), isInput))
defer g.Fgenf(w, ")")
}
}
// TODO: this isn't exhaustively correct as "range" could be a legit var name
// instead we should probably use a fn call expression here for entries/range
// similar to other languages
if rootName == keywordRange {
part := expr.Traversal[1].(hcl.TraverseAttr).Name
switch part {
case "value":
g.Fgenf(w, "val0")
case "key":
g.Fgenf(w, "key0")
default:
contract.Failf("unexpected traversal on range expression: %s", part)
}
} else {
g.Fgen(w, makeValidIdentifier(rootName))
isRootResource := false
g.genRelativeTraversal(w, expr.Traversal.SimpleSplit().Rel, expr.Parts[1:], isRootResource)
}
if genIDCall {
g.Fgenf(w, ".ID()")
}
2020-05-21 17:23:33 +00:00
}
// GenSplatExpression generates code for a SplatExpression.
func (g *generator) GenSplatExpression(w io.Writer, expr *model.SplatExpression) {
contract.Failf("unlowered splat expression @ %v", expr.SyntaxNode().Range())
}
2020-05-21 17:23:33 +00:00
// GenTemplateExpression generates code for a TemplateExpression.
func (g *generator) GenTemplateExpression(w io.Writer, expr *model.TemplateExpression) {
g.genTemplateExpression(w, expr, expr.Type())
}
func (g *generator) genTemplateExpression(w io.Writer, expr *model.TemplateExpression, destType model.Type) {
if len(expr.Parts) == 1 {
if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
g.genLiteralValueExpression(w, lit, destType)
}
// If we have a template expression that doesn't start with a string, it indicates
// an invalid *pcl.Program. Instead of crashing, we continue.
return
}
argTypeName := g.argumentTypeName(destType, false)
isPulumiType := strings.HasPrefix(argTypeName, "pulumi.")
isPulumiStr := argTypeName == "pulumi.String"
if isPulumiType && !isPulumiStr {
g.Fgenf(w, "%s(", argTypeName)
defer g.Fgenf(w, ")")
}
var fmtStr strings.Builder
args := new(bytes.Buffer)
canBeRaw := true
for _, v := range expr.Parts {
if lit, ok := v.(*model.LiteralValueExpression); ok && lit.Value.Type().Equals(cty.String) {
str := lit.Value.AsString()
// We don't want to accidentally embed a formatting directive in our
// formatting string.
if !strings.ContainsRune(str, '%') {
if canBeRaw && strings.ContainsRune(str, '`') {
canBeRaw = false
}
// Build the formatting string
fmtStr.WriteString(str)
continue
}
}
// v cannot be directly inserted into the formatting string, so put it in the
// argument list.
fmtStr.WriteString("%v")
g.Fgenf(args, ", %.v", v)
}
if isPulumiStr {
g.Fgenf(w, "pulumi.Sprintf(")
} else {
g.Fgenf(w, "fmt.Sprintf(")
}
g.genStringLiteral(w, fmtStr.String(), canBeRaw)
_, err := args.WriteTo(w)
contract.AssertNoErrorf(err, "Failed to write arguments")
g.Fgenf(w, ")")
}
2020-05-21 17:23:33 +00:00
// GenTemplateJoinExpression generates code for a TemplateJoinExpression.
func (g *generator) GenTemplateJoinExpression(w io.Writer, expr *model.TemplateJoinExpression) { /*TODO*/
}
func (g *generator) GenTupleConsExpression(w io.Writer, expr *model.TupleConsExpression) {
g.genTupleConsExpression(w, expr, expr.Type())
}
// GenTupleConsExpression generates code for a TupleConsExpression.
func (g *generator) genTupleConsExpression(w io.Writer, expr *model.TupleConsExpression, destType model.Type) {
isInput := isInputty(destType)
argType := g.argumentTypeName(destType, isInput)
// don't need to generate type for list args if not a pointer, i.e. []ec2.SubnetSpecArgs{ {Type: ...} }
// unless it contains an interface, i.e. []map[string]interface{ map[string]interface{"key": "val"} }
if strings.HasPrefix(argType, "[]") && !strings.Contains(argType, "interface{}") {
defer func(b bool) { g.inGenTupleConExprListArgs = b }(g.inGenTupleConExprListArgs)
g.inGenTupleConExprListArgs = true
if strings.HasPrefix(argType, "[]*") {
defer func(b bool) { g.isPtrArg = b }(g.isPtrArg)
g.isPtrArg = true
}
}
2020-05-21 17:23:33 +00:00
g.Fgenf(w, "%s{\n", argType)
[docs/go-program-gen] Fix generating constructor syntax examples for kubernetes (#16574) In this PR we fix generating constructor syntax examples for Kubernetes. The problem initially was due to generating a PCL program with syntax errors for the kubernetes schema. The syntax error was happening because we emitted properties `$ref` and `$schema` which are not valid identifiers in PCL. Fixing this was simple enough, we only needed to quote these properties that start with dollar signs and we get a valid PCL program. Full PCL program for kubernetes: https://gist.github.com/Zaid-Ajaj/abe899430a0b5f99a428934dcac75d52 However, the valid PCL program wouldn't convert to Go with program-gen and it would hang without showing any errors or stack traces. I wrote a script to split the programs for each resource and convert each separately. This way I narrowed down the problem to this program: ``` resource "def" "kubernetes:apiextensions.k8s.io/v1:CustomResourceDefinition" { apiVersion = "string" kind = "string" spec = { versions = [{}] } } ``` Debugging this in Go program-gen didn't show an obvious error and it kept on hanging. However I noticed that we lowering expressions twice: once for the entirety of resource inputs when generating resources and again when generating the expressions separately. Lowering expressions has been the source of many bugs and it seems to trip up program-gen a lot and suspected something wrong was going on here, so I simplified it to lower the expressions once when we generate resource inputs. This fixed the hang issue as well as a few of our test programs (see diffs for test programs and tests schemas) Tested this against the full kubernetes schema and we can now generate the full Go program: https://gist.github.com/Zaid-Ajaj/3ac734536969a989edae92219968d51f > The second time we lower expressions not only is redundant but also can generate invalid code if the lowered expressions have temporary variables because program-gen would just emit these variables inside inline expressions like objects and lists which gives invalid Go code. Resolves part of #16463
2024-07-05 12:42:41 +00:00
for _, v := range expr.Expressions {
g.Fgenf(w, "%v,\n", v)
2020-05-21 17:23:33 +00:00
}
g.Fgenf(w, "}")
}
func (g *generator) GenUnaryOpExpression(w io.Writer, expr *model.UnaryOpExpression) {
Enable some more linting rules (#17456) Issue #10659 lists a number of extra linting checks that we could enable in order to make our Go code more robust. This commit implements as many as seem sensible: * `durationcheck`, which checks for multiplication of `time.Duration`s, which can lead to unexpected behaviour (e.g. `time.Second * time.Second` is *not* one second) * `goprintffuncname`, which checks that `Printf`-like functions are appropriately suffixed with `f` to indicate as such * `tenv`, which checks for `os.Setenv` in tests where `t.Setenv` is generally a better solution * `wastedassign`, which checks for assignments whose values are never used (such as initial values before an `if` where both branches then overwrite the value) * `whitespace`, which checks for blank lines at the beginning and end of blocks such as functions, `if`s, `for`s and so on. This commit does *not* enable the following checks listed in #10659: * `wrapcheck`, which insists that third-party library errors are always `%w`rapped -- we have a lot of cases where we don't do this and it's probably a bit more involved than "just wrap them" in terms of making sure we don't break anything (maybe) * `predeclared`, which checks for shadowing of existing Go identifiers -- we use `old` and `new` a lot, especially in step generation, so this is probably a slightly bigger clean-up/one we might want to opt out of * `mnd` (magic number detection) -- we have a lot of failures on this * `nilnil` -- we only have a couple of failures on this; these could probably be handled with `//nolint` but for now I've opted not to take this route.
2024-10-03 17:37:13 +00:00
var opstr string
precedence := g.GetPrecedence(expr)
switch expr.Operation {
case hclsyntax.OpLogicalNot:
opstr = "!"
case hclsyntax.OpNegate:
opstr = "-"
}
g.Fgenf(w, "%[2]v%.[1]*[3]v", precedence, opstr, expr.Operand)
}
2020-05-21 17:23:33 +00:00
// argumentTypeName computes the go type for the given model type.
func (g *generator) argumentTypeName(destType model.Type, isInput bool) (result string) {
if cns, ok := destType.(*model.ConstType); ok {
destType = cns.Type
}
// This can happen with null literals.
if destType == model.NoneType {
return ""
}
if schemaType, ok := pcl.GetSchemaForType(destType); ok {
return (&pkgContext{
pkg: (&schema.Package{Name: "main"}).Reference(),
externalPackages: g.externalCache,
}).argsType(schemaType)
2020-05-21 17:23:33 +00:00
}
switch destType := destType.(type) {
case *model.OpaqueType:
switch *destType {
case *model.IntType:
if isInput {
return "pulumi.Int"
}
return "int"
case *model.NumberType:
if isInput {
return "pulumi.Float64"
}
return "float64"
case *model.StringType:
if isInput {
return "pulumi.String"
}
return "string"
case *model.BoolType:
if isInput {
return "pulumi.Bool"
}
return "bool"
case *model.DynamicType:
if isInput {
return "pulumi.Any"
}
return "interface{}"
default:
return string(*destType)
}
case *model.ObjectType:
if isInput {
// check for element type uniformity and return appropriate type if so
allSameType := true
var elmType string
for _, v := range destType.Properties {
valType := g.argumentTypeName(v, true)
if elmType != "" && elmType != valType {
allSameType = false
break
}
elmType = valType
}
if allSameType && elmType != "" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return elmType + "Map"
}
return "pulumi.Map"
}
return "map[string]interface{}"
case *model.MapType:
valType := g.argumentTypeName(destType.ElementType, isInput)
if isInput {
trimmedType := strings.TrimPrefix(valType, "pulumi.")
return fmt.Sprintf("pulumi.%sMap", Title(trimmedType))
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return "map[string]" + valType
case *model.ListType:
argTypeName := g.argumentTypeName(destType.ElementType, isInput)
if strings.HasPrefix(argTypeName, "pulumi.") && argTypeName != "pulumi.Resource" {
if argTypeName == "pulumi.Any" {
return "pulumi.Array"
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return argTypeName + "Array"
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return "[]" + argTypeName
case *model.TupleType:
// attempt to collapse tuple types. intentionally does not use model.UnifyTypes
// correct go code requires all types to match, or use of interface{}
var elmType model.Type
for i, t := range destType.ElementTypes {
if i == 0 {
elmType = t
if cns, ok := elmType.(*model.ConstType); ok {
elmType = cns.Type
}
continue
}
if !elmType.AssignableFrom(t) {
elmType = nil
break
}
}
if elmType != nil {
argTypeName := g.argumentTypeName(elmType, isInput)
if strings.HasPrefix(argTypeName, "pulumi.") && argTypeName != "pulumi.Resource" {
if argTypeName == "pulumi.Any" {
return "pulumi.Array"
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return argTypeName + "Array"
}
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
return "[]" + argTypeName
}
if isInput {
return "pulumi.Array"
}
return "[]interface{}"
case *model.OutputType:
isInput = true
return g.argumentTypeName(destType.ElementType, isInput)
case *model.UnionType:
for _, ut := range destType.ElementTypes {
isOptional := false
// check if the union contains none, which indicates this is an optional value
for _, ut := range destType.ElementTypes {
if ut.Equals(model.NoneType) {
isOptional = true
}
}
switch ut := ut.(type) {
case *model.OpaqueType:
if isOptional {
return g.argumentTypeNamePtr(ut, isInput)
}
return g.argumentTypeName(ut, isInput)
case *model.ConstType:
return g.argumentTypeName(ut.Type, isInput)
case *model.TupleType:
return g.argumentTypeName(ut, isInput)
case *model.MapType:
return g.argumentTypeName(ut, isInput)
}
}
return "interface{}"
case *model.PromiseType:
return g.argumentTypeName(destType.ElementType, isInput)
default:
contract.Failf("unexpected destType type %T", destType)
}
2020-05-21 17:23:33 +00:00
return ""
}
func (g *generator) argumentTypeNamePtr(destType model.Type, isInput bool) (result string) {
res := g.argumentTypeName(destType, isInput)
[program-gen/go] Fix optional primitive values being derefrenced (#15592) # Description Fixes #15524 where the primitive optional types were being derefrenced. It took me a while to figure where the problem was because I couldn't really reproduce the original issue with a new PCL program. The `aws-optionals-pp` test case program in PCL would repro the issue when converted from the CLI via `convert --from pcl` but not from the unit tests even when using the exact same schema 🤔 this was really weird. That said, the fix turned out straightforward and it does fix the same issue in generated component resources. ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-03-10 04:49:06 +00:00
if !strings.HasPrefix(res, "pulumi.") {
return "*" + res
}
return res
}
2020-05-21 17:23:33 +00:00
func (g *generator) genRelativeTraversal(w io.Writer,
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
traversal hcl.Traversal, parts []model.Traversable, isRootResource bool,
) {
for i, part := range traversal {
2020-05-21 17:23:33 +00:00
var key cty.Value
switch part := part.(type) {
case hcl.TraverseAttr:
key = cty.StringVal(part.Name)
case hcl.TraverseIndex:
key = part.Key
default:
contract.Failf("unexpected traversal part of type %T (%v)", part, part.SourceRange())
}
// TODO handle optionals in go
// if model.IsOptionalType(model.GetTraversableType(parts[i])) {
// g.Fgen(w, "?")
// }
switch key.Type() {
case cty.String:
shouldConvert := isRootResource
if _, ok := parts[i].(*model.OutputType); ok {
shouldConvert = true
}
if key.AsString() == "id" && shouldConvert {
g.Fgenf(w, ".ID()")
} else {
g.Fgenf(w, ".%s", Title(key.AsString()))
}
2020-05-21 17:23:33 +00:00
case cty.Number:
idx, _ := key.AsBigFloat().Int64()
g.Fgenf(w, "[%d]", idx)
default:
contract.Failf("unexpected traversal key of type %T (%v)", key, key.AsString())
}
}
}
type nameInfo int
func (nameInfo) Format(name string) string {
// TODO
return name
}
// lowerExpression amends the expression with intrinsics for Go generation.
func (g *generator) lowerExpression(expr model.Expression, typ model.Type) (
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
model.Expression, []interface{},
) {
expr = pcl.RewritePropertyReferences(expr)
expr, diags := pcl.RewriteApplies(expr, nameInfo(0), false /*TODO*/)
expr, sTemps, splatDiags := g.rewriteSplat(expr, g.splatSpiller)
[go/program-gen] Fix using inline invoke expressions inside resources, objects and arrays (#14484) # Description This PR fixes an issue in Go program where if users are writing invoke expressions inline inside other expressions, then that invoke is extracted into a temporary variable and then later referenced the same way it was used. This is because non-output-versioned invokes cannot be used directly since they return a tuple (InvokeResult, err) so we need to check for the error before proceeding. Fixes #14064 ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-11-03 14:22:43 +00:00
expr, convertDiags := pcl.RewriteConversions(expr, typ)
expr, tTemps, ternDiags := g.rewriteTernaries(expr, g.ternaryTempSpiller)
expr, jTemps, jsonDiags := g.rewriteToJSON(expr)
expr, rTemps, readDirDiags := g.rewriteReadDir(expr, g.readDirTempSpiller)
expr, oTemps, optDiags := g.rewriteOptionals(expr, g.optionalSpiller)
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
bufferSize := len(tTemps) + len(jTemps) + len(rTemps) + len(sTemps) + len(oTemps)
temps := slice.Prealloc[interface{}](bufferSize)
for _, t := range tTemps {
temps = append(temps, t)
}
for _, t := range jTemps {
temps = append(temps, t)
}
for _, t := range rTemps {
temps = append(temps, t)
}
for _, t := range sTemps {
temps = append(temps, t)
}
for _, t := range oTemps {
temps = append(temps, t)
}
diags = append(diags, convertDiags...)
diags = append(diags, ternDiags...)
diags = append(diags, jsonDiags...)
diags = append(diags, readDirDiags...)
diags = append(diags, splatDiags...)
diags = append(diags, optDiags...)
g.diagnostics = g.diagnostics.Extend(diags)
return expr, temps
2020-05-21 17:23:33 +00:00
}
func (g *generator) genNYI(w io.Writer, reason string, vs ...interface{}) {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
message := "not yet implemented: " + fmt.Sprintf(reason, vs...)
2023-06-02 15:17:59 +00:00
g.diagnostics = append(g.diagnostics, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: message,
Detail: message,
})
2020-05-21 17:23:33 +00:00
g.Fgenf(w, "\"TODO: %s\"", fmt.Sprintf(reason, vs...))
}
func (g *generator) genApply(w io.Writer, expr *model.FunctionCallExpression) {
// Extract the list of outputs and the continuation expression from the `__apply` arguments.
applyArgs, then := pcl.ParseApplyCall(expr)
isInput := false
retType := g.argumentTypeName(then.Signature.ReturnType, isInput)
// TODO account for outputs in other namespaces like aws
// TODO[pulumi/pulumi#8453] incomplete pattern code below.
var typeAssertion string
if retType == "[]string" {
typeAssertion = ".(pulumi.StringArrayOutput)"
} else {
if strings.HasPrefix(retType, "*") {
retType = Title(strings.TrimPrefix(retType, "*")) + "Ptr"
switch then.Body.(type) {
case *model.ScopeTraversalExpression:
traversal := then.Body.(*model.ScopeTraversalExpression)
traversal.RootName = "&" + traversal.RootName
then.Body = traversal
}
}
typeAssertion = fmt.Sprintf(".(%sOutput)", retType)
if !strings.Contains(retType, ".") {
typeAssertion = fmt.Sprintf(".(pulumi.%sOutput)", Title(retType))
}
}
if len(applyArgs) == 1 {
// If we only have a single output, just generate a normal `.Apply`
g.Fgenf(w, "%.v.ApplyT(%.v)%s", applyArgs[0], then, typeAssertion)
} else {
g.Fgenf(w, "pulumi.All(%.v", applyArgs[0])
applyArgs = applyArgs[1:]
for _, a := range applyArgs {
g.Fgenf(w, ",%.v", a)
}
2020-06-19 00:17:51 +00:00
allApplyThen, typeConvDecls := g.rewriteThenForAllApply(then)
g.Fgenf(w, ").ApplyT(")
g.genAnonymousFunctionExpression(w, allApplyThen, typeConvDecls, true)
2020-06-19 00:17:51 +00:00
g.Fgenf(w, ")%s", typeAssertion)
}
}
2020-06-19 00:17:51 +00:00
// rewriteThenForAllApply rewrites an apply func after a .All replacing params with []interface{}
// other languages like javascript take advantage of destructuring to simplify All.Apply
// by generating something like [a1, a2, a3]
// Go doesn't support this syntax so we create a set of var decls with type assertions
// to prepend to the body: a1 := _args[0].(string) ... etc.
func (g *generator) rewriteThenForAllApply(
then *model.AnonymousFunctionExpression,
) (*model.AnonymousFunctionExpression, []string) {
typeConvDecls := slice.Prealloc[string](len(then.Parameters))
2020-06-19 00:17:51 +00:00
for i, v := range then.Parameters {
typ := g.argumentTypeName(v.VariableType, false)
2020-06-19 00:17:51 +00:00
decl := fmt.Sprintf("%s := _args[%d].(%s)", v.Name, i, typ)
typeConvDecls = append(typeConvDecls, decl)
}
// dummy type that will produce []interface{} for argumentTypeName
interfaceArrayType := &model.TupleType{
ElementTypes: []model.Type{
model.BoolType, model.StringType, model.IntType,
},
}
then.Parameters = []*model.Variable{{
Name: "_args",
VariableType: interfaceArrayType,
}}
then.Signature.Parameters = []model.Parameter{{
Name: "_args",
Type: interfaceArrayType,
}}
return then, typeConvDecls
}
// Writes a Go string literal.
// The literal will be a raw string literal if allowRaw is true
// and the string is long enough to benefit from it.
func (g *generator) genStringLiteral(w io.Writer, v string, allowRaw bool) {
// If the string is longer than 50 characters,
// contains at least 5 newlines,
// and does not contain a backtick,
// use a backtick string literal for readability.
canBeRaw := len(v) > 50 &&
strings.Count(v, "\n") >= 5 &&
!strings.Contains(v, "`")
if allowRaw && canBeRaw {
fmt.Fprintf(w, "`%s`", v)
return
}
g.Fgen(w, "\"")
g.Fgen(w, g.escapeString(v))
g.Fgen(w, "\"")
}
func (g *generator) escapeString(v string) string {
builder := strings.Builder{}
for _, c := range v {
if c == '\x00' {
// escape NUL bytes
builder.WriteString(fmt.Sprintf("\\u%04x", c))
continue
}
if c == '"' || c == '\\' {
builder.WriteRune('\\')
}
if c == '\n' {
builder.WriteRune('\\')
builder.WriteRune('n')
continue
}
builder.WriteRune(c)
}
return builder.String()
}
sdk/go: Remove 'nolint' directives from package docs Go treats comments that match the following regex as directives. //[a-z0-9]+:[a-z0-9] Comments that are directives don't show in an entity's documentation. https://github.com/golang/go/commit/5a550b695117f07a4f2454039a4871250cd3ed09#diff-f56160fd9fcea272966a8a1d692ad9f49206fdd8dbcbfe384865a98cd9bc2749R165 Our code has `//nolint` directives that now show in the API Reference. This is because these directives are in one of the following forms, which don't get this special treatment. // nolint:foo //nolint: foo This change fixes all such directives found by the regex: `// nolint|//nolint: `. See bottom of commit for command used for the fix. Verification: Here's the output of `go doc` on some entities before and after this change. Before ``` % go doc github.com/pulumi/pulumi/sdk/v3/go/pulumi | head -n8 package pulumi // import "github.com/pulumi/pulumi/sdk/v3/go/pulumi" nolint: lll, interfacer nolint: lll, interfacer const EnvOrganization = "PULUMI_ORGANIZATION" ... var ErrPlugins = errors.New("pulumi: plugins requested") ``` After ``` % go doc github.com/pulumi/pulumi/sdk/v3/go/pulumi | head -n8 package pulumi // import "github.com/pulumi/pulumi/sdk/v3/go/pulumi" const EnvOrganization = "PULUMI_ORGANIZATION" ... var ErrPlugins = errors.New("pulumi: plugins requested") func BoolRef(v bool) *bool func Float64Ref(v float64) *float64 func IntRef(v int) *int func IsSecret(o Output) bool ``` Before ``` % go doc github.com/pulumi/pulumi/sdk/v3/go/pulumi URN_ package pulumi // import "github.com/pulumi/pulumi/sdk/v3/go/pulumi" func URN_(o string) ResourceOption URN_ is an optional URN of a previously-registered resource of this type to read from the engine. nolint: revive ``` After: ``` % go doc github.com/pulumi/pulumi/sdk/v3/go/pulumi URN_ package pulumi // import "github.com/pulumi/pulumi/sdk/v3/go/pulumi" func URN_(o string) ResourceOption URN_ is an optional URN of a previously-registered resource of this type to read from the engine. ``` Note that golangci-lint offers a 'nolintlint' linter that finds such miuses of nolint, but it also finds other issues so I've deferred that to a follow up PR. Resolves #11785 Related: https://github.com/golangci/golangci-lint/issues/892 [git-generate] FILES=$(mktemp) rg -l '// nolint|//nolint: ' | tee "$FILES" | xargs perl -p -i -e ' s|// nolint|//nolint|g; s|//nolint: |//nolint:|g; ' rg '.go$' < "$FILES" | xargs gofmt -w -s
2023-01-06 00:07:45 +00:00
//nolint:lll
func isInputty(destType model.Type) bool {
// TODO this needs to be more robust, likely the inverse of:
// https://github.com/pulumi/pulumi/blob/5330c97684cad78bcc60d8867f1b28704bd8a555/pkg/codegen/hcl2/model/type_eventuals.go#L244
switch destType := destType.(type) {
case *model.UnionType:
for _, t := range destType.ElementTypes {
if _, ok := t.(*model.OutputType); ok {
return true
}
}
case *model.OutputType:
return true
}
return false
}
func (g *generator) literalKey(x model.Expression) (string, bool) {
Enable some more linting rules (#17456) Issue #10659 lists a number of extra linting checks that we could enable in order to make our Go code more robust. This commit implements as many as seem sensible: * `durationcheck`, which checks for multiplication of `time.Duration`s, which can lead to unexpected behaviour (e.g. `time.Second * time.Second` is *not* one second) * `goprintffuncname`, which checks that `Printf`-like functions are appropriately suffixed with `f` to indicate as such * `tenv`, which checks for `os.Setenv` in tests where `t.Setenv` is generally a better solution * `wastedassign`, which checks for assignments whose values are never used (such as initial values before an `if` where both branches then overwrite the value) * `whitespace`, which checks for blank lines at the beginning and end of blocks such as functions, `if`s, `for`s and so on. This commit does *not* enable the following checks listed in #10659: * `wrapcheck`, which insists that third-party library errors are always `%w`rapped -- we have a lot of cases where we don't do this and it's probably a bit more involved than "just wrap them" in terms of making sure we don't break anything (maybe) * `predeclared`, which checks for shadowing of existing Go identifiers -- we use `old` and `new` a lot, especially in step generation, so this is probably a slightly bigger clean-up/one we might want to opt out of * `mnd` (magic number detection) -- we have a lot of failures on this * `nilnil` -- we only have a couple of failures on this; these could probably be handled with `//nolint` but for now I've opted not to take this route.
2024-10-03 17:37:13 +00:00
var strKey string
switch x := x.(type) {
case *model.LiteralValueExpression:
if model.StringType.AssignableFrom(x.Type()) {
strKey = x.Value.AsString()
break
}
var buf bytes.Buffer
g.GenLiteralValueExpression(&buf, x)
return buf.String(), true
case *model.TemplateExpression:
if len(x.Parts) == 1 {
if lit, ok := x.Parts[0].(*model.LiteralValueExpression); ok && model.StringType.AssignableFrom(lit.Type()) {
strKey = lit.Value.AsString()
break
}
}
return "", false
default:
return "", false
}
return strKey, true
}
// functionName computes the go package, module, and name for the given function token.
func (g *generator) functionName(tokenArg model.Expression) (string, string, string, hcl.Diagnostics) {
token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
tokenRange := tokenArg.SyntaxNode().Range()
// Compute the resource type from the Pulumi type token.
pkg, module, member, diagnostics := pcl.DecomposeToken(token, tokenRange)
if strings.HasPrefix(member, "get") {
if g.useLookupInvokeForm(token) {
member = strings.Replace(member, "get", "lookup", 1)
}
}
modOrAlias := g.getModOrAlias(pkg, module, module)
mod := strings.ReplaceAll(modOrAlias, "/", ".")
return goPackage(pkg), mod, Title(member), diagnostics
}
var functionPackages = map[string][]string{
"join": {"strings"},
"mimeType": {"mime", "path"},
"readDir": {"os"},
"readFile": {"os"},
"filebase64": {"encoding/base64", "os"},
"toBase64": {"encoding/base64"},
2022-09-16 23:12:29 +00:00
"fromBase64": {"encoding/base64"},
"toJSON": {"encoding/json"},
[program-gen] Fix generated utility functions for filebase64, filebase64sha256, sha1 and mimeType (#14857) # Description While writing program tests for generated helper utility functions `filebase64`, `filebase64sha256`, `sha1` and `mimeType` with the idea to increase code coverage, it turned out that those are completely broken in all of the languages containing syntax errors, missing imports and wrong indentation. This PR fixes them and extends the `functions` program to show how they now look like and to show that they compile. Also adding example usage of `stack()`, `project()` and `cwd()` in the test program. ## Checklist - [ ] I have run `make tidy` to update any new dependencies - [ ] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-15 11:26:00 +00:00
"sha1": {"crypto/sha1", "encoding/hex"},
"filebase64sha256": {"crypto/sha256", "os"},
"cwd": {"os"},
"singleOrNone": {"fmt"},
}
func (g *generator) genFunctionPackages(x *model.FunctionCallExpression) []string {
return functionPackages[x.Name]
}