2021-11-17 20:27:50 +00:00
|
|
|
// Copyright 2016-2021, Pulumi Corporation.
|
2020-03-17 03:04:32 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2021-09-30 03:11:56 +00:00
|
|
|
package pcl
|
2020-03-17 03:04:32 +00:00
|
|
|
|
|
|
|
import (
|
2021-11-17 20:27:50 +00:00
|
|
|
"fmt"
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
2021-11-17 20:27:50 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
2020-03-17 03:04:32 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
2020-05-11 17:21:56 +00:00
|
|
|
const Invoke = "invoke"
|
|
|
|
|
2020-03-17 03:04:32 +00:00
|
|
|
func getInvokeToken(call *hclsyntax.FunctionCallExpr) (string, hcl.Range, bool) {
|
2020-05-11 17:21:56 +00:00
|
|
|
if call.Name != Invoke || len(call.Args) < 1 {
|
2020-03-17 03:04:32 +00:00
|
|
|
return "", hcl.Range{}, false
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
template, ok := call.Args[0].(*hclsyntax.TemplateExpr)
|
|
|
|
if !ok || len(template.Parts) != 1 {
|
|
|
|
return "", hcl.Range{}, false
|
|
|
|
}
|
|
|
|
literal, ok := template.Parts[0].(*hclsyntax.LiteralValueExpr)
|
2020-03-17 03:04:32 +00:00
|
|
|
if !ok {
|
|
|
|
return "", hcl.Range{}, false
|
|
|
|
}
|
|
|
|
if literal.Val.Type() != cty.String {
|
|
|
|
return "", hcl.Range{}, false
|
|
|
|
}
|
|
|
|
return literal.Val.AsString(), call.Args[0].Range(), true
|
|
|
|
}
|
2020-04-07 02:43:16 +00:00
|
|
|
|
2022-12-15 09:20:49 +00:00
|
|
|
// annotateObjectProperties annotates the properties of an object expression with the
|
|
|
|
// types of the corresponding properties in the schema. This is used to provide type
|
|
|
|
// information for invoke calls that didn't have type annotations.
|
2022-10-07 12:02:12 +00:00
|
|
|
//
|
2022-12-15 09:20:49 +00:00
|
|
|
// This function will recursively annotate the properties of objects that are nested
|
|
|
|
// within the object expression type.
|
2022-10-08 12:46:25 +00:00
|
|
|
func annotateObjectProperties(modelType model.Type, schemaType schema.Type) {
|
2022-12-15 09:20:49 +00:00
|
|
|
if optionalType, ok := schemaType.(*schema.OptionalType); ok && optionalType != nil {
|
2022-10-07 12:02:12 +00:00
|
|
|
schemaType = optionalType.ElementType
|
|
|
|
}
|
|
|
|
|
2022-10-08 12:46:25 +00:00
|
|
|
switch arg := modelType.(type) {
|
|
|
|
case *model.ObjectType:
|
2022-12-15 09:20:49 +00:00
|
|
|
if schemaObjectType, ok := schemaType.(*schema.ObjectType); ok && schemaObjectType != nil {
|
2022-10-07 12:02:12 +00:00
|
|
|
schemaProperties := make(map[string]schema.Type)
|
|
|
|
for _, schemaProperty := range schemaObjectType.Properties {
|
|
|
|
schemaProperties[schemaProperty.Name] = schemaProperty.Type
|
|
|
|
}
|
|
|
|
|
2022-10-08 12:46:25 +00:00
|
|
|
// top-level annotation for the type itself
|
|
|
|
arg.Annotations = append(arg.Annotations, schemaType)
|
2022-10-07 12:02:12 +00:00
|
|
|
// now for each property, annotate it with the associated type from the schema
|
2022-10-08 12:46:25 +00:00
|
|
|
for propertyName, propertyType := range arg.Properties {
|
2022-10-07 12:02:12 +00:00
|
|
|
if associatedType, ok := schemaProperties[propertyName]; ok {
|
2022-10-08 12:46:25 +00:00
|
|
|
annotateObjectProperties(propertyType, associatedType)
|
2022-10-07 12:02:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-08 12:46:25 +00:00
|
|
|
case *model.ListType:
|
|
|
|
underlyingArrayType := arg.ElementType
|
2022-12-15 09:20:49 +00:00
|
|
|
if schemaArrayType, ok := schemaType.(*schema.ArrayType); ok && schemaArrayType != nil {
|
2022-10-07 12:02:12 +00:00
|
|
|
underlyingSchemaArrayType := schemaArrayType.ElementType
|
2022-10-08 12:46:25 +00:00
|
|
|
annotateObjectProperties(underlyingArrayType, underlyingSchemaArrayType)
|
2022-10-07 12:02:12 +00:00
|
|
|
}
|
|
|
|
|
2022-10-08 12:46:25 +00:00
|
|
|
case *model.TupleType:
|
2022-12-15 09:20:49 +00:00
|
|
|
if schemaArrayType, ok := schemaType.(*schema.ArrayType); ok && schemaArrayType != nil {
|
2022-10-07 12:02:12 +00:00
|
|
|
underlyingSchemaArrayType := schemaArrayType.ElementType
|
2022-10-08 12:46:25 +00:00
|
|
|
elementTypes := arg.ElementTypes
|
2022-10-07 12:02:12 +00:00
|
|
|
for _, elemType := range elementTypes {
|
2022-10-08 12:46:25 +00:00
|
|
|
annotateObjectProperties(elemType, underlyingSchemaArrayType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *model.UnionType:
|
2022-10-11 17:25:10 +00:00
|
|
|
// sometimes optional schema types are represented as unions: None | T
|
|
|
|
// in this case, we want to collapse the union and annotate the underlying type T
|
|
|
|
if len(arg.ElementTypes) == 2 && arg.ElementTypes[0] == model.NoneType {
|
|
|
|
annotateObjectProperties(arg.ElementTypes[1], schemaType)
|
|
|
|
} else if len(arg.ElementTypes) == 2 && arg.ElementTypes[1] == model.NoneType {
|
|
|
|
annotateObjectProperties(arg.ElementTypes[0], schemaType)
|
golangci-lint: Enable staticcheck
Remove staticcheck from the list of disabled linters.
It's enabled by default in golangci-lint.
This also fixes minor remaining staticcheck issues
that don't merit their own pull requests,
or opts out of those that cannot be fixed yet.
Notably, we're opting out of:
- Resource.Name is deprecated (#9469)
- github.com/golang/protobuf is deprecated (#11869)
- strings.Title has been deprecated (#11870)
Besides that, other issues addressed in this change are:
```
// all issues are in pkg
codegen/schema/docs_parser.go:103:4: SA4006: this value of `text` is never used (staticcheck)
codegen/schema/loader.go:253:3: SA9003: empty branch (staticcheck)
resource/deploy/step_executor.go:328:12: SA9003: empty branch (staticcheck)
resource/deploy/step_generator.go:141:10: SA9003: empty branch (staticcheck)
codegen/pcl/invoke.go:97:10: SA9003: empty branch (staticcheck)
codegen/hcl2/model/type_const.go:57:2: SA9003: empty branch (staticcheck)
codegen/hcl2/model/type_enum.go:99:9: SA4001: &*x will be simplified to x. It will not copy x. (staticcheck)
codegen/go/gen_test.go:399:19: SA4017: HasPrefix is a pure function but its return value is ignored (staticcheck)
```
Depends on #11857, #11858, #11859, #11860, #11862, #11865, #11866, #11867, #11868
Resolves #11808
2023-01-11 19:53:41 +00:00
|
|
|
} else { //nolint:staticcheck // TODO https://github.com/pulumi/pulumi/issues/10993
|
2022-10-11 17:25:10 +00:00
|
|
|
// We need to handle the case where the schema type is a union type.
|
|
|
|
}
|
2022-10-07 12:02:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:43:16 +00:00
|
|
|
func (b *binder) bindInvokeSignature(args []model.Expression) (model.StaticFunctionSignature, hcl.Diagnostics) {
|
|
|
|
if len(args) < 1 {
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.zeroSignature(), nil
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template, ok := args[0].(*model.TemplateExpression)
|
|
|
|
if !ok || len(template.Parts) != 1 {
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.zeroSignature(), hcl.Diagnostics{tokenMustBeStringLiteral(args[0])}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
lit, ok := template.Parts[0].(*model.LiteralValueExpression)
|
2021-06-24 16:17:55 +00:00
|
|
|
if !ok || model.StringType.ConversionFrom(lit.Type()) == model.NoConversion {
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.zeroSignature(), hcl.Diagnostics{tokenMustBeStringLiteral(args[0])}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
token, tokenRange := lit.Value.AsString(), args[0].SyntaxNode().Range()
|
|
|
|
pkg, _, _, diagnostics := DecomposeToken(token, tokenRange)
|
|
|
|
if diagnostics.HasErrors() {
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.zeroSignature(), diagnostics
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2022-07-20 22:12:02 +00:00
|
|
|
pkgInfo := PackageInfo{
|
|
|
|
name: pkg,
|
|
|
|
}
|
|
|
|
pkgSchema, ok := b.options.packageCache.entries[pkgInfo]
|
2020-04-07 02:43:16 +00:00
|
|
|
if !ok {
|
2023-07-10 13:05:18 +00:00
|
|
|
if b.options.skipInvokeTypecheck {
|
|
|
|
return b.zeroSignature(), nil
|
|
|
|
}
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.zeroSignature(), hcl.Diagnostics{unknownPackage(pkg, tokenRange)}
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2023-11-21 15:16:13 +00:00
|
|
|
fn, tk, ok, err := pkgSchema.LookupFunction(token)
|
|
|
|
if err != nil {
|
2023-07-10 13:05:18 +00:00
|
|
|
if b.options.skipInvokeTypecheck {
|
|
|
|
return b.zeroSignature(), nil
|
|
|
|
}
|
|
|
|
|
2022-05-23 22:44:35 +00:00
|
|
|
return b.zeroSignature(), hcl.Diagnostics{functionLoadError(token, err, tokenRange)}
|
|
|
|
} else if !ok {
|
2023-07-10 13:05:18 +00:00
|
|
|
if b.options.skipInvokeTypecheck {
|
|
|
|
return b.zeroSignature(), nil
|
|
|
|
}
|
|
|
|
|
2022-05-23 22:44:35 +00:00
|
|
|
return b.zeroSignature(), hcl.Diagnostics{unknownFunction(token, tokenRange)}
|
2021-11-17 20:27:50 +00:00
|
|
|
}
|
|
|
|
|
2023-11-21 15:16:13 +00:00
|
|
|
lit.Value = cty.StringVal(tk)
|
|
|
|
|
2022-01-24 20:59:06 +00:00
|
|
|
if len(args) < 2 {
|
|
|
|
return b.zeroSignature(), hcl.Diagnostics{errorf(tokenRange, "missing second arg")}
|
|
|
|
}
|
2021-11-17 20:27:50 +00:00
|
|
|
sig, err := b.signatureForArgs(fn, args[1])
|
|
|
|
if err != nil {
|
|
|
|
diag := hcl.Diagnostics{errorf(tokenRange, "Invoke binding error: %v", err)}
|
|
|
|
return b.zeroSignature(), diag
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-07 12:02:12 +00:00
|
|
|
// annotate the input args on the expression with the input type of the function
|
|
|
|
if argsObject, isObjectExpression := args[1].(*model.ObjectConsExpression); isObjectExpression {
|
2022-11-14 23:06:10 +00:00
|
|
|
if fn.Inputs != nil {
|
2022-11-09 19:20:01 +00:00
|
|
|
annotateObjectProperties(argsObject.Type(), fn.Inputs)
|
|
|
|
}
|
2022-10-07 12:02:12 +00:00
|
|
|
}
|
|
|
|
|
2023-01-11 22:17:14 +00:00
|
|
|
sig.MultiArgumentInputs = fn.MultiArgumentInputs
|
2021-11-17 20:27:50 +00:00
|
|
|
return sig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) makeSignature(argsType, returnType model.Type) model.StaticFunctionSignature {
|
|
|
|
return model.StaticFunctionSignature{
|
|
|
|
Parameters: []model.Parameter{
|
|
|
|
{
|
|
|
|
Name: "token",
|
|
|
|
Type: model.StringType,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "args",
|
|
|
|
Type: argsType,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "provider",
|
|
|
|
Type: model.NewOptionalType(model.StringType),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ReturnType: returnType,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) zeroSignature() model.StaticFunctionSignature {
|
|
|
|
return b.makeSignature(model.NewOptionalType(model.DynamicType), model.DynamicType)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) signatureForArgs(fn *schema.Function, args model.Expression) (model.StaticFunctionSignature, error) {
|
2022-01-24 20:59:06 +00:00
|
|
|
if args != nil && b.useOutputVersion(fn, args) {
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.outputVersionSignature(fn)
|
|
|
|
}
|
|
|
|
return b.regularSignature(fn), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Heuristic to decide when to use `fnOutput` form of a function. Will
|
2023-06-23 00:42:18 +00:00
|
|
|
// conservatively prefer `false` unless bind option choose to prefer otherwise.
|
|
|
|
// It decides to return `true` if doing so avoids the need to introduce an `apply` form to
|
2021-11-17 20:27:50 +00:00
|
|
|
// accommodate `Output` args (`Promise` args do not count).
|
|
|
|
func (b *binder) useOutputVersion(fn *schema.Function, args model.Expression) bool {
|
2023-10-02 12:53:06 +00:00
|
|
|
if fn.ReturnType == nil {
|
2021-11-17 20:27:50 +00:00
|
|
|
// No code emitted for an `fnOutput` form, impossible.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-06-23 00:42:18 +00:00
|
|
|
if b.options.preferOutputVersionedInvokes {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-10-02 12:53:06 +00:00
|
|
|
if fn.Inputs == nil || len(fn.Inputs.Properties) == 0 {
|
|
|
|
// use the output version when there are actual args to use
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-11-17 20:27:50 +00:00
|
|
|
outputFormParamType := b.schemaTypeToType(fn.Inputs.InputShape)
|
|
|
|
regularFormParamType := b.schemaTypeToType(fn.Inputs)
|
|
|
|
argsType := args.Type()
|
|
|
|
|
|
|
|
if regularFormParamType.ConversionFrom(argsType) == model.NoConversion &&
|
|
|
|
outputFormParamType.ConversionFrom(argsType) == model.SafeConversion &&
|
|
|
|
model.ContainsOutputs(argsType) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) regularSignature(fn *schema.Function) model.StaticFunctionSignature {
|
|
|
|
var argsType model.Type
|
2020-04-07 02:43:16 +00:00
|
|
|
if fn.Inputs == nil {
|
2021-11-17 20:27:50 +00:00
|
|
|
argsType = model.NewOptionalType(model.NewObjectType(map[string]model.Type{}))
|
2020-04-07 02:43:16 +00:00
|
|
|
} else {
|
2021-11-17 20:27:50 +00:00
|
|
|
argsType = b.schemaTypeToType(fn.Inputs)
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-11-17 20:27:50 +00:00
|
|
|
var returnType model.Type
|
2023-01-11 22:17:14 +00:00
|
|
|
if fn.ReturnType == nil {
|
2021-11-17 20:27:50 +00:00
|
|
|
returnType = model.NewObjectType(map[string]model.Type{})
|
2020-04-07 02:43:16 +00:00
|
|
|
} else {
|
2023-01-11 22:17:14 +00:00
|
|
|
returnType = b.schemaTypeToType(fn.ReturnType)
|
2021-11-17 20:27:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return b.makeSignature(argsType, model.NewPromiseType(returnType))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binder) outputVersionSignature(fn *schema.Function) (model.StaticFunctionSignature, error) {
|
|
|
|
if !fn.NeedsOutputVersion() {
|
|
|
|
return model.StaticFunctionSignature{}, fmt.Errorf("Function %s does not have an Output version", fn.Token)
|
|
|
|
}
|
|
|
|
|
2023-10-12 16:23:18 +00:00
|
|
|
// Given `fn.NeedsOutputVersion()==true` `fn.ReturnType != nil`.
|
|
|
|
var argsType model.Type
|
|
|
|
if fn.Inputs != nil {
|
|
|
|
argsType = b.schemaTypeToType(fn.Inputs.InputShape)
|
|
|
|
} else {
|
|
|
|
argsType = model.NewObjectType(map[string]model.Type{})
|
|
|
|
}
|
2023-01-11 22:17:14 +00:00
|
|
|
returnType := b.schemaTypeToType(fn.ReturnType)
|
2021-11-17 20:27:50 +00:00
|
|
|
return b.makeSignature(argsType, model.NewOutputType(returnType)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detects invoke calls that use an output version of a function.
|
|
|
|
func IsOutputVersionInvokeCall(call *model.FunctionCallExpression) bool {
|
|
|
|
if call.Name == Invoke {
|
|
|
|
// Currently binder.bindInvokeSignature will assign
|
|
|
|
// either DynamicType, a Promise<T>, or an Output<T>
|
|
|
|
// for the return type of an invoke. Output<T> implies
|
|
|
|
// that an output version has been picked.
|
|
|
|
_, returnsOutput := call.Signature.ReturnType.(*model.OutputType)
|
|
|
|
return returnsOutput
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pattern matches to recognize `__convert(objCons(..))` pattern that
|
|
|
|
// is used to annotate object constructors with appropriate nominal
|
|
|
|
// types. If the expression matches, returns true followed by the
|
|
|
|
// constructor expression and the appropriate type.
|
|
|
|
func RecognizeTypedObjectCons(theExpr model.Expression) (bool, *model.ObjectConsExpression, model.Type) {
|
|
|
|
expr, isFunc := theExpr.(*model.FunctionCallExpression)
|
|
|
|
if !isFunc {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if expr.Name != IntrinsicConvert {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(expr.Args) != 1 {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
objCons, isObjCons := expr.Args[0].(*model.ObjectConsExpression)
|
|
|
|
if !isObjCons {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, objCons, expr.Type()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pattern matches to recognize an encoded call to an output-versioned
|
|
|
|
// invoke, such as `invoke(token, __convert(objCons(..)))`. If
|
|
|
|
// matching, returns the `args` expression and its schema-bound type.
|
|
|
|
func RecognizeOutputVersionedInvoke(
|
|
|
|
expr *model.FunctionCallExpression,
|
|
|
|
) (bool, *model.ObjectConsExpression, model.Type) {
|
|
|
|
if !IsOutputVersionInvokeCall(expr) {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(expr.Args) < 2 {
|
|
|
|
return false, nil, nil
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|
|
|
|
|
2021-11-17 20:27:50 +00:00
|
|
|
return RecognizeTypedObjectCons(expr.Args[1])
|
2020-04-07 02:43:16 +00:00
|
|
|
}
|