Add a rewriter that reifies implicit conversions into a call to the `__convert` intrinsic. Code generators can recognize this intrinsic and use it to generate appropriate conversion code. Part of this work involves redesigning the type annotations system. Annotations are now only applicable to opaque and object types. Instead of inspecting annotations directly, code generators should use `hcl2.GetSchemaForType` to extract the `schema.Type` for a `model.Type`.
124 lines
3.8 KiB
Go
124 lines
3.8 KiB
Go
package python
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen"
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func isParameterReference(parameters codegen.Set, x model.Expression) bool {
|
|
scopeTraversal, ok := x.(*model.ScopeTraversalExpression)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return parameters.Has(scopeTraversal.Parts[0])
|
|
}
|
|
|
|
// parseProxyApply attempts to match and rewrite the given parsed apply using the following patterns:
|
|
//
|
|
// - __apply(<expr>, eval(x, x[index])) -> <expr>[index]
|
|
// - __apply(<expr>, eval(x, x.attr))) -> <expr>.attr
|
|
// - __apply(traversal, eval(x, x.attr)) -> traversal.attr
|
|
//
|
|
// Each of these patterns matches an apply that can be handled by `pulumi.Output`'s `__getitem__` or `__getattr__`
|
|
// method. The rewritten expressions will use those methods rather than calling `apply`.
|
|
func (g *generator) parseProxyApply(parameters codegen.Set, args []model.Expression,
|
|
then model.Expression) (model.Expression, bool) {
|
|
|
|
if len(args) != 1 {
|
|
return nil, false
|
|
}
|
|
|
|
arg := args[0]
|
|
switch then := then.(type) {
|
|
case *model.IndexExpression:
|
|
// Rewrite `__apply(<expr>, eval(x, x[index]))` to `<expr>[index]`.
|
|
if !isParameterReference(parameters, then.Collection) {
|
|
return nil, false
|
|
}
|
|
then.Collection = arg
|
|
case *model.ScopeTraversalExpression:
|
|
if !isParameterReference(parameters, then) {
|
|
return nil, false
|
|
}
|
|
|
|
switch arg := arg.(type) {
|
|
case *model.RelativeTraversalExpression:
|
|
arg.Traversal = append(arg.Traversal, then.Traversal[1:]...)
|
|
arg.Parts = append(arg.Parts, then.Parts...)
|
|
case *model.ScopeTraversalExpression:
|
|
arg.Traversal = append(arg.Traversal, then.Traversal[1:]...)
|
|
arg.Parts = append(arg.Parts, then.Parts...)
|
|
}
|
|
default:
|
|
return nil, false
|
|
}
|
|
|
|
diags := arg.Typecheck(false)
|
|
contract.Assert(len(diags) == 0)
|
|
return arg, true
|
|
}
|
|
|
|
// lowerProxyApplies lowers certain calls to the apply intrinsic into proxied property accesses. Concretely, this
|
|
// boils down to rewriting the following shapes
|
|
//
|
|
// - __apply(<expr>, eval(x, x[index]))
|
|
// - __apply(<expr>, eval(x, x.attr)))
|
|
// - __apply(scope.traversal, eval(x, x.attr))
|
|
//
|
|
// into (respectively)
|
|
//
|
|
// - <expr>[index]
|
|
// - <expr>.attr
|
|
// - scope.traversal.attr
|
|
//
|
|
// These forms will use `pulumi.Output`'s `__getitem__` and `__getattr__` instead of calling `apply`.
|
|
func (g *generator) lowerProxyApplies(expr model.Expression) (model.Expression, hcl.Diagnostics) {
|
|
rewriter := func(expr model.Expression) (model.Expression, hcl.Diagnostics) {
|
|
// Ignore the node if it is not a call to the apply intrinsic.
|
|
apply, ok := expr.(*model.FunctionCallExpression)
|
|
if !ok || apply.Name != hcl2.IntrinsicApply {
|
|
return expr, nil
|
|
}
|
|
|
|
// Parse the apply call.
|
|
args, then := hcl2.ParseApplyCall(apply)
|
|
|
|
parameters := codegen.Set{}
|
|
for _, p := range then.Parameters {
|
|
parameters.Add(p)
|
|
}
|
|
|
|
// Attempt to match (call __apply (rvar) (call __applyArg 0))
|
|
if v, ok := g.parseProxyApply(parameters, args, then.Body); ok {
|
|
return v, nil
|
|
}
|
|
|
|
return expr, nil
|
|
}
|
|
return model.VisitExpression(expr, model.IdentityVisitor, rewriter)
|
|
}
|
|
|
|
func (g *generator) lowerObjectKeys(expr model.Expression, camelCaseToSnakeCase map[string]string) {
|
|
switch expr := expr.(type) {
|
|
case *model.ObjectConsExpression:
|
|
for _, item := range expr.Items {
|
|
// Ignore non-literal keys
|
|
if key, ok := item.Key.(*model.LiteralValueExpression); ok && key.Value.Type().Equals(cty.String) {
|
|
if keyVal, ok := camelCaseToSnakeCase[key.Value.AsString()]; ok {
|
|
key.Value = cty.StringVal(keyVal)
|
|
}
|
|
}
|
|
|
|
g.lowerObjectKeys(item.Value, camelCaseToSnakeCase)
|
|
}
|
|
case *model.TupleConsExpression:
|
|
for _, element := range expr.Expressions {
|
|
g.lowerObjectKeys(element, camelCaseToSnakeCase)
|
|
}
|
|
}
|
|
}
|