github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/nodejs/gen_program_lower.go (about) 1 package nodejs 2 3 import ( 4 "github.com/hashicorp/hcl/v2" 5 "github.com/pulumi/pulumi/pkg/v3/codegen" 6 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" 7 "github.com/pulumi/pulumi/pkg/v3/codegen/pcl" 8 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 9 ) 10 11 func isOutputType(t model.Type) bool { 12 switch t := t.(type) { 13 case *model.OutputType: 14 return true 15 case *model.UnionType: 16 for _, t := range t.ElementTypes { 17 if _, isOutput := t.(*model.OutputType); isOutput { 18 return true 19 } 20 } 21 } 22 return false 23 } 24 25 func isPromiseType(t model.Type) bool { 26 switch t := t.(type) { 27 case *model.PromiseType: 28 return true 29 case *model.UnionType: 30 isPromise := false 31 for _, t := range t.ElementTypes { 32 switch t.(type) { 33 case *model.OutputType: 34 return false 35 case *model.PromiseType: 36 isPromise = true 37 } 38 } 39 return isPromise 40 } 41 return false 42 } 43 44 func isParameterReference(parameters codegen.Set, x model.Expression) bool { 45 scopeTraversal, ok := x.(*model.ScopeTraversalExpression) 46 if !ok { 47 return false 48 } 49 50 return parameters.Has(scopeTraversal.Parts[0]) 51 } 52 53 // canLiftTraversal returns true if this traversal can be lifted. Any traversal that does not traverse 54 // possibly-undefined values can be lifted. 55 func (g *generator) canLiftTraversal(parts []model.Traversable) bool { 56 for _, p := range parts { 57 t := model.GetTraversableType(p) 58 if model.IsOptionalType(t) || isPromiseType(t) { 59 return false 60 } 61 } 62 return true 63 } 64 65 // parseProxyApply attempts to match and rewrite the given parsed apply using the following patterns: 66 // 67 // - __apply(<expr>, eval(x, x[index])) -> <expr>[index] 68 // - __apply(<expr>, eval(x, x.attr))) -> <expr>.attr 69 // - __apply(scope.traversal, eval(x, x.attr)) -> scope.traversal.attr 70 // 71 // Each of these patterns matches an apply that can be handled by `pulumi.Output`'s property access proxy. 72 func (g *generator) parseProxyApply(parameters codegen.Set, args []model.Expression, 73 then model.Expression) (model.Expression, bool) { 74 75 if len(args) != 1 { 76 return nil, false 77 } 78 79 arg := args[0] 80 switch then := then.(type) { 81 case *model.IndexExpression: 82 t := arg.Type() 83 if !isParameterReference(parameters, then.Collection) || model.IsOptionalType(t) || isPromiseType(t) { 84 return nil, false 85 } 86 then.Collection = arg 87 case *model.ScopeTraversalExpression: 88 if !isParameterReference(parameters, then) || isPromiseType(arg.Type()) { 89 return nil, false 90 } 91 if !g.canLiftTraversal(then.Parts) { 92 return nil, false 93 } 94 95 switch arg := arg.(type) { 96 case *model.RelativeTraversalExpression: 97 arg.Traversal = append(arg.Traversal, then.Traversal[1:]...) 98 arg.Parts = append(arg.Parts, then.Parts...) 99 case *model.ScopeTraversalExpression: 100 arg.Traversal = append(arg.Traversal, then.Traversal[1:]...) 101 arg.Parts = append(arg.Parts, then.Parts...) 102 default: 103 return nil, false 104 } 105 default: 106 return nil, false 107 } 108 109 diags := arg.Typecheck(false) 110 contract.Assert(len(diags) == 0) 111 return arg, true 112 } 113 114 func callbackParameterReferences(expr model.Expression, parameters codegen.Set) []*model.Variable { 115 var refs []*model.Variable 116 visitor := func(expr model.Expression) (model.Expression, hcl.Diagnostics) { 117 if expr, isScopeTraversal := expr.(*model.ScopeTraversalExpression); isScopeTraversal { 118 if parameters.Has(expr.Parts[0]) { 119 refs = append(refs, expr.Parts[0].(*model.Variable)) 120 } 121 } 122 return expr, nil 123 } 124 125 _, diags := model.VisitExpression(expr, model.IdentityVisitor, visitor) 126 contract.Assert(len(diags) == 0) 127 return refs 128 } 129 130 // parseInterpolate attempts to match the given parsed apply against a template whose parts are a mix of prompt 131 // expressions and proxyable applies. 132 // 133 // If a match is found, parseInterpolate returns an appropriate call to the __interpolate intrinsic with a mix of 134 // expressions and proxied applies. 135 func (g *generator) parseInterpolate(parameters codegen.Set, args []model.Expression, 136 then *model.AnonymousFunctionExpression) (model.Expression, bool) { 137 138 template, ok := then.Body.(*model.TemplateExpression) 139 if !ok { 140 return nil, false 141 } 142 143 indices := map[*model.Variable]int{} 144 for i, p := range then.Parameters { 145 indices[p] = i 146 } 147 148 exprs := make([]model.Expression, len(template.Parts)) 149 for i, expr := range template.Parts { 150 parameterRefs := callbackParameterReferences(expr, parameters) 151 if len(parameterRefs) == 0 { 152 exprs[i] = expr 153 continue 154 } 155 156 proxyArgs := make([]model.Expression, len(parameterRefs)) 157 for i, p := range parameterRefs { 158 argIndex, ok := indices[p] 159 contract.Assert(ok) 160 161 proxyArgs[i] = args[argIndex] 162 } 163 164 expr, ok := g.parseProxyApply(parameters, proxyArgs, expr) 165 if !ok { 166 return nil, false 167 } 168 exprs[i] = expr 169 } 170 171 return newInterpolateCall(exprs), true 172 } 173 174 // lowerProxyApplies lowers certain calls to the apply intrinsic into proxied property accesses and/or calls to the 175 // pulumi.interpolate function. Concretely, this boils down to rewriting the following shapes 176 // 177 // - __apply(<expr>, eval(x, x[index])) 178 // - __apply(<expr>, eval(x, x.attr)) 179 // - __apply(scope.traversal, eval(x, x.attr)) 180 // - __apply(<proxy-apply>, ..., eval(x, ..., "foo ${<proxy-apply>} bar ${...}")) 181 // 182 // into (respectively) 183 // 184 // - <expr>[index] 185 // - <expr>.attr 186 // - scope.traversal.attr 187 // - __interpolate("foo ", <proxy-apply>, " bar ", ...) 188 // 189 // The first two forms will be generated as proxied applies; the lattermost will be generated as an interpolated string 190 // that uses `pulumi.interpolate`. 191 func (g *generator) lowerProxyApplies(expr model.Expression) (model.Expression, hcl.Diagnostics) { 192 rewriter := func(expr model.Expression) (model.Expression, hcl.Diagnostics) { 193 // Ignore the node if it is not a call to the apply intrinsic. 194 apply, ok := expr.(*model.FunctionCallExpression) 195 if !ok || apply.Name != pcl.IntrinsicApply { 196 return expr, nil 197 } 198 199 // Parse the apply call. 200 args, then := pcl.ParseApplyCall(apply) 201 202 parameters := codegen.Set{} 203 for _, p := range then.Parameters { 204 parameters.Add(p) 205 } 206 207 // Attempt to match (call __apply (rvar) (call __applyArg 0)) 208 if v, ok := g.parseProxyApply(parameters, args, then.Body); ok { 209 return v, nil 210 } 211 212 // Attempt to match (call __apply (rvar 0) ... (rvar n) (output /* mix of literals and calls to __applyArg) 213 if v, ok := g.parseInterpolate(parameters, args, then); ok { 214 return v, nil 215 } 216 217 return expr, nil 218 } 219 return model.VisitExpression(expr, model.IdentityVisitor, rewriter) 220 } 221 222 // awaitInvokes wraps each call to `invoke` with a call to the `await` intrinsic. This rewrite should only be used 223 // if we are generating an async main, in which case the apply rewriter should also be configured not to treat 224 // promises as eventuals. The cumulative effect of these options is to avoid the use of `then` within async contexts. 225 // Note that this depends on the fact that invokes are the only way to introduce promises in to a Pulumi program; if 226 // this changes in the future, this transform will need to be applied in a more general way (e.g. by the apply 227 // rewriter). 228 func (g *generator) awaitInvokes(x model.Expression) model.Expression { 229 contract.Assert(g.asyncMain) 230 231 rewriter := func(x model.Expression) (model.Expression, hcl.Diagnostics) { 232 // Ignore the node if it is not a call to invoke. 233 call, ok := x.(*model.FunctionCallExpression) 234 if !ok || call.Name != pcl.Invoke { 235 return x, nil 236 } 237 238 _, isPromise := call.Type().(*model.PromiseType) 239 contract.Assert(isPromise) 240 241 return newAwaitCall(call), nil 242 } 243 x, diags := model.VisitExpression(x, model.IdentityVisitor, rewriter) 244 contract.Assert(len(diags) == 0) 245 return x 246 }