pulumi/pkg/codegen/python/gen_program_lower.go
Pat Gavlin 2e9499a000
[codegen/hcl2] Fix the apply rewriter. (#4486)
Some of the apply rewriter's assumptions were broken by the richer
expressions available in HCL2. These changes fix those broken
assumptions, in particular the assumption that only scope traversal
expressions are sources of eventual values.
2020-04-24 22:04:24 -07:00

141 lines
4 KiB
Go

package python
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/zclconf/go-cty/cty"
)
// parseProxyApply attempts to match the given parsed apply against the pattern (call __applyArg 0). If the call
// matches, it returns the ScopeTraversalExpression that corresponds to argument zero, which can then be generated as a
// proxied apply call.
func (g *generator) parseProxyApply(args []model.Expression,
then *model.AnonymousFunctionExpression) (*model.ScopeTraversalExpression, bool) {
if len(args) != 1 {
return nil, false
}
arg, ok := args[0].(*model.ScopeTraversalExpression)
if !ok {
return nil, false
}
thenTraversal, ok := then.Body.(*model.ScopeTraversalExpression)
if !ok || thenTraversal.Parts[0] != then.Parameters[0] {
return nil, false
}
traversal := hcl.TraversalJoin(arg.Traversal, thenTraversal.Traversal[1:])
expr, diags := g.program.BindExpression(&hclsyntax.ScopeTraversalExpr{
Traversal: traversal,
SrcRange: traversal.SourceRange(),
})
contract.Assert(len(diags) == 0)
return expr.(*model.ScopeTraversalExpression), true
}
// lowerProxyApplies lowers certain calls to the apply intrinsic into proxied property accesses. Concretely, this boils
// down to rewriting the following shape:
// - (call __apply (resource variable access) (call __applyArg 0))
// into
// - (resource variable access)
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)
// Attempt to match (call __apply (rvar) (call __applyArg 0))
if v, ok := g.parseProxyApply(args, then); ok {
return v, nil
}
return expr, nil
}
return model.VisitExpression(expr, model.IdentityVisitor, rewriter)
}
func (g *generator) mapObjectKey(key string, obj *schema.ObjectType) string {
if obj == nil {
return key
}
prop, ok := obj.Property(key)
if !ok {
return key
}
mapCase := true
if info, ok := prop.Language["python"]; ok {
mapCase = info.(PropertyInfo).MapCase
}
if mapCase {
return PyName(key)
}
return key
}
func (g *generator) getObjectSchema(typ model.Type) *schema.ObjectType {
typ = model.ResolveOutputs(typ)
if union, ok := typ.(*model.UnionType); ok {
for _, t := range union.ElementTypes {
if obj := g.getObjectSchema(t); obj != nil {
return obj
}
}
return nil
}
annotations := typ.GetAnnotations()
if len(annotations) == 1 {
return annotations[0].(*schema.ObjectType)
}
return nil
}
func (g *generator) lowerObjectKeys(expr model.Expression, destType model.Type) {
rewriter := func(expr model.Expression) (model.Expression, hcl.Diagnostics) {
switch expr := expr.(type) {
case *model.ObjectConsExpression:
obj := g.getObjectSchema(destType)
for _, item := range expr.Items {
// Ignore non-literal keys
valueType := model.Type(model.DynamicType)
if key, ok := item.Key.(*model.LiteralValueExpression); ok && key.Value.Type().Equals(cty.String) {
k := key.Value.AsString()
vt, diags := destType.Traverse(hcl.TraverseAttr{Name: k})
contract.Ignore(diags)
valueType = vt.(model.Type)
key.Value = cty.StringVal(g.mapObjectKey(k, obj))
}
g.lowerObjectKeys(item.Value, valueType)
}
case *model.TupleConsExpression:
valueType, diags := destType.Traverse(hcl.TraverseIndex{Key: cty.NumberIntVal(0)})
contract.Ignore(diags)
for _, element := range expr.Expressions {
g.lowerObjectKeys(element, valueType.(model.Type))
}
}
return expr, nil
}
_, diags := model.VisitExpression(expr, rewriter, nil)
contract.Assert(len(diags) == 0)
}