github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/hcl2/model/binder_expression_test.go (about) 1 // Copyright 2016-2020, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "fmt" 19 "testing" 20 21 "github.com/hashicorp/hcl/v2" 22 "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax" 23 "github.com/stretchr/testify/assert" 24 "github.com/zclconf/go-cty/cty" 25 ) 26 27 func assertConvertibleFrom(t *testing.T, to, from Type) { 28 assert.NotEqual(t, NoConversion, to.ConversionFrom(from)) 29 } 30 31 func TestBindLiteral(t *testing.T) { 32 t.Parallel() 33 34 expr, diags := BindExpressionText("false", nil, hcl.Pos{}) 35 assert.Len(t, diags, 0) 36 assertConvertibleFrom(t, BoolType, expr.Type()) 37 lit, ok := expr.(*LiteralValueExpression) 38 assert.True(t, ok) 39 assert.Equal(t, cty.False, lit.Value) 40 assert.Equal(t, "false", fmt.Sprintf("%v", expr)) 41 42 expr, diags = BindExpressionText("true", nil, hcl.Pos{}) 43 assert.Len(t, diags, 0) 44 assertConvertibleFrom(t, BoolType, expr.Type()) 45 lit, ok = expr.(*LiteralValueExpression) 46 assert.True(t, ok) 47 assert.Equal(t, cty.True, lit.Value) 48 assert.Equal(t, "true", fmt.Sprintf("%v", expr)) 49 50 expr, diags = BindExpressionText("0", nil, hcl.Pos{}) 51 assert.Len(t, diags, 0) 52 assertConvertibleFrom(t, NumberType, expr.Type()) 53 lit, ok = expr.(*LiteralValueExpression) 54 assert.True(t, ok) 55 assert.True(t, cty.NumberIntVal(0).RawEquals(lit.Value)) 56 assert.Equal(t, "0", fmt.Sprintf("%v", expr)) 57 58 expr, diags = BindExpressionText("3.14", nil, hcl.Pos{}) 59 assert.Len(t, diags, 0) 60 assertConvertibleFrom(t, NumberType, expr.Type()) 61 lit, ok = expr.(*LiteralValueExpression) 62 assert.True(t, ok) 63 assert.True(t, cty.MustParseNumberVal("3.14").RawEquals(lit.Value)) 64 assert.Equal(t, "3.14", fmt.Sprintf("%v", expr)) 65 66 expr, diags = BindExpressionText(`"foo"`, nil, hcl.Pos{}) 67 assert.Len(t, diags, 0) 68 assertConvertibleFrom(t, StringType, expr.Type()) 69 template, ok := expr.(*TemplateExpression) 70 assert.True(t, ok) 71 assert.Len(t, template.Parts, 1) 72 lit, ok = template.Parts[0].(*LiteralValueExpression) 73 assert.True(t, ok) 74 assert.Equal(t, cty.StringVal("foo"), lit.Value) 75 assert.Equal(t, "\"foo\"", fmt.Sprintf("%v", expr)) 76 } 77 78 type environment map[string]interface{} 79 80 func (e environment) scope() *Scope { 81 s := NewRootScope(syntax.None) 82 for name, typeOrFunction := range e { 83 switch typeOrFunction := typeOrFunction.(type) { 84 case *Function: 85 s.DefineFunction(name, typeOrFunction) 86 case Type: 87 s.Define(name, &Variable{Name: name, VariableType: typeOrFunction}) 88 } 89 } 90 return s 91 } 92 93 type exprTestCase struct { 94 x string 95 t Type 96 xt Expression 97 } 98 99 func TestBindBinaryOp(t *testing.T) { 100 t.Parallel() 101 102 env := environment(map[string]interface{}{ 103 "a": NewOutputType(BoolType), 104 "b": NewPromiseType(BoolType), 105 "c": NewOutputType(NumberType), 106 "d": NewPromiseType(NumberType), 107 }) 108 scope := env.scope() 109 110 cases := []exprTestCase{ 111 // Comparisons 112 {x: "0 == 0", t: BoolType}, 113 {x: "0 != 0", t: BoolType}, 114 {x: "0 < 0", t: BoolType}, 115 {x: "0 > 0", t: BoolType}, 116 {x: "0 <= 0", t: BoolType}, 117 {x: "0 >= 0", t: BoolType}, 118 119 // Arithmetic 120 {x: "0 + 0", t: NumberType}, 121 {x: "0 - 0", t: NumberType}, 122 {x: "0 * 0", t: NumberType}, 123 {x: "0 / 0", t: NumberType}, 124 {x: "0 % 0", t: NumberType}, 125 126 // Logical 127 {x: "false && false", t: BoolType}, 128 {x: "false || false", t: BoolType}, 129 130 // Lifted operations 131 {x: "a == true", t: NewOutputType(BoolType)}, 132 {x: "b == true", t: NewPromiseType(BoolType)}, 133 {x: "c + 0", t: NewOutputType(NumberType)}, 134 {x: "d + 0", t: NewPromiseType(NumberType)}, 135 {x: "a && true", t: NewOutputType(BoolType)}, 136 {x: "b && true", t: NewPromiseType(BoolType)}, 137 } 138 for _, c := range cases { 139 c := c 140 t.Run(c.x, func(t *testing.T) { 141 t.Parallel() 142 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 143 assert.Len(t, diags, 0) 144 assertConvertibleFrom(t, c.t, expr.Type()) 145 _, ok := expr.(*BinaryOpExpression) 146 assert.True(t, ok) 147 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 148 }) 149 } 150 } 151 152 func TestBindConditional(t *testing.T) { 153 t.Parallel() 154 155 env := environment(map[string]interface{}{ 156 "a": NewOutputType(BoolType), 157 "b": NewPromiseType(BoolType), 158 }) 159 scope := env.scope() 160 161 cases := []exprTestCase{ 162 {x: "true ? 0 : 1", t: NumberType}, 163 {x: "true ? 0 : false", t: NewUnionType(NumberType, BoolType)}, 164 {x: "true ? a : b", t: NewOutputType(BoolType)}, 165 166 // Lifted operations 167 {x: "a ? 0 : 1", t: NewOutputType(NumberType)}, 168 {x: "b ? 0 : 1", t: NewPromiseType(NumberType)}, 169 {x: "a ? 0 : false", t: NewOutputType(NewUnionType(NumberType, BoolType))}, 170 {x: "b ? 0 : false", t: NewPromiseType(NewUnionType(NumberType, BoolType))}, 171 {x: "a ? a : b", t: NewOutputType(BoolType)}, 172 {x: "b ? b : b", t: NewPromiseType(BoolType)}, 173 } 174 for _, c := range cases { 175 c := c 176 t.Run(c.x, func(t *testing.T) { 177 t.Parallel() 178 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 179 assert.Len(t, diags, 0) 180 assertConvertibleFrom(t, c.t, expr.Type()) 181 _, ok := expr.(*ConditionalExpression) 182 assert.True(t, ok) 183 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 184 }) 185 } 186 } 187 188 func TestBindFor(t *testing.T) { 189 t.Parallel() 190 191 // TODO: union collection types 192 193 env := environment(map[string]interface{}{ 194 "a": NewMapType(StringType), 195 "aa": NewMapType(NewOutputType(StringType)), 196 "b": NewOutputType(NewMapType(StringType)), 197 "c": NewPromiseType(NewMapType(StringType)), 198 "d": NewListType(StringType), 199 "dd": NewListType(NewOutputType(StringType)), 200 "e": NewOutputType(NewListType(StringType)), 201 "f": NewPromiseType(NewListType(StringType)), 202 "g": BoolType, 203 "h": NewOutputType(BoolType), 204 "i": NewPromiseType(BoolType), 205 "j": StringType, 206 "k": NewOutputType(StringType), 207 "l": NewPromiseType(StringType), 208 }) 209 scope := env.scope() 210 211 cases := []exprTestCase{ 212 // Object for 213 {x: `{for k, v in {}: k => v}`, t: NewMapType(NoneType)}, 214 {x: `{for k, v in {foo = "bar"}: k => v}`, t: NewMapType(StringType)}, 215 {x: `{for k, v in {foo = "bar"}: k => 0}`, t: NewMapType(NumberType)}, 216 {x: `{for k, v in {foo = 0}: k => v}`, t: NewMapType(NumberType)}, 217 {x: `{for k, v in a: k => v}`, t: NewMapType(StringType)}, 218 {x: `{for k, v in aa: k => v}`, t: NewMapType(NewOutputType(StringType))}, 219 {x: `{for k, v in a: k => 0}`, t: NewMapType(NumberType)}, 220 {x: `{for k, v in d: v => k}`, t: NewMapType(NumberType)}, 221 {x: `{for k, v in d: v => k...}`, t: NewMapType(NewListType(NumberType))}, 222 {x: `{for k, v in d: v => k if k > 10}`, t: NewMapType(NumberType)}, 223 224 // List for 225 {x: `[for k, v in {}: [k, v]]`, t: NewListType(NewTupleType(StringType, NoneType))}, 226 {x: `[for k, _ in {}: k]`, t: NewListType(StringType)}, 227 {x: `[for v in []: v]`, t: NewListType(NoneType)}, 228 229 // Lifted operations 230 {x: `{for k, v in b: k => v}`, t: NewOutputType(NewMapType(StringType))}, 231 {x: `{for k, v in c: k => v}`, t: NewPromiseType(NewMapType(StringType))}, 232 {x: `{for k, v in {}: k => v if h}`, t: NewOutputType(NewMapType(NoneType))}, 233 {x: `{for k, v in {}: k => v if i}`, t: NewPromiseType(NewMapType(NoneType))}, 234 {x: `[for v in e: v]`, t: NewOutputType(NewListType(StringType))}, 235 {x: `[for v in f: v]`, t: NewPromiseType(NewListType(StringType))}, 236 {x: `[for v in []: v if h]`, t: NewOutputType(NewListType(NoneType))}, 237 {x: `[for v in []: v if i]`, t: NewPromiseType(NewListType(NoneType))}, 238 } 239 for _, c := range cases { 240 c := c 241 t.Run(c.x, func(t *testing.T) { 242 t.Parallel() 243 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 244 assert.Len(t, diags, 0) 245 assertConvertibleFrom(t, c.t, expr.Type()) 246 _, ok := expr.(*ForExpression) 247 assert.True(t, ok) 248 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 249 }) 250 } 251 } 252 253 func TestBindFunctionCall(t *testing.T) { 254 t.Parallel() 255 256 env := environment(map[string]interface{}{ 257 "f0": NewFunction(StaticFunctionSignature{ 258 Parameters: []Parameter{ 259 {Name: "foo", Type: StringType}, 260 {Name: "bar", Type: IntType}, 261 }, 262 ReturnType: BoolType, 263 }), 264 "f1": NewFunction(StaticFunctionSignature{ 265 Parameters: []Parameter{ 266 {Name: "foo", Type: StringType}, 267 }, 268 VarargsParameter: &Parameter{ 269 Name: "bar", Type: IntType, 270 }, 271 ReturnType: BoolType, 272 }), 273 "a": NewOutputType(StringType), 274 "b": NewPromiseType(StringType), 275 "c": NewOutputType(IntType), 276 "d": NewPromiseType(IntType), 277 }) 278 scope := env.scope() 279 280 cases := []exprTestCase{ 281 // Standard calls 282 {x: `f0("foo", 0)`, t: BoolType}, 283 {x: `f1("foo")`, t: BoolType}, 284 {x: `f1("foo", 1, 2, 3)`, t: BoolType}, 285 286 // Lifted calls 287 {x: `f0(a, 0)`, t: NewOutputType(BoolType)}, 288 {x: `f0(b, 0)`, t: NewPromiseType(BoolType)}, 289 {x: `f0("foo", c)`, t: NewOutputType(BoolType)}, 290 {x: `f0("foo", d)`, t: NewPromiseType(BoolType)}, 291 {x: `f0(a, d)`, t: NewOutputType(BoolType)}, 292 {x: `f0(b, c)`, t: NewOutputType(BoolType)}, 293 {x: `f1(a)`, t: NewOutputType(BoolType)}, 294 {x: `f1(b)`, t: NewPromiseType(BoolType)}, 295 {x: `f1("foo", c)`, t: NewOutputType(BoolType)}, 296 {x: `f1("foo", d)`, t: NewPromiseType(BoolType)}, 297 {x: `f1("foo", c, d)`, t: NewOutputType(BoolType)}, 298 } 299 for _, c := range cases { 300 c := c 301 t.Run(c.x, func(t *testing.T) { 302 t.Parallel() 303 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 304 assert.Len(t, diags, 0) 305 assertConvertibleFrom(t, c.t, expr.Type()) 306 _, ok := expr.(*FunctionCallExpression) 307 assert.True(t, ok) 308 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 309 }) 310 } 311 } 312 313 func TestBindIndex(t *testing.T) { 314 t.Parallel() 315 316 env := environment(map[string]interface{}{ 317 "a": StringType, 318 "b": IntType, 319 "c": NewOutputType(StringType), 320 "d": NewOutputType(IntType), 321 "e": NewPromiseType(StringType), 322 "f": NewPromiseType(IntType), 323 "g": NewListType(BoolType), 324 "h": NewMapType(BoolType), 325 "i": NewObjectType(map[string]Type{"foo": BoolType}), 326 "j": NewOutputType(NewListType(BoolType)), 327 "k": NewOutputType(NewMapType(BoolType)), 328 "l": NewOutputType(NewObjectType(map[string]Type{"foo": BoolType})), 329 "m": NewPromiseType(NewListType(BoolType)), 330 "n": NewPromiseType(NewMapType(BoolType)), 331 "o": NewPromiseType(NewObjectType(map[string]Type{"foo": BoolType})), 332 }) 333 scope := env.scope() 334 335 cases := []exprTestCase{ 336 // Standard operations 337 {x: "g[a]", t: BoolType}, 338 {x: "g[b]", t: BoolType}, 339 {x: "h[a]", t: BoolType}, 340 {x: "h[b]", t: BoolType}, 341 {x: "i[a]", t: BoolType}, 342 {x: "i[b]", t: BoolType}, 343 344 // Lifted operations 345 {x: "g[c]", t: NewOutputType(BoolType)}, 346 {x: "g[d]", t: NewOutputType(BoolType)}, 347 {x: "h[c]", t: NewOutputType(BoolType)}, 348 {x: "h[d]", t: NewOutputType(BoolType)}, 349 {x: "i[c]", t: NewOutputType(BoolType)}, 350 {x: "i[d]", t: NewOutputType(BoolType)}, 351 {x: "g[e]", t: NewPromiseType(BoolType)}, 352 {x: "g[f]", t: NewPromiseType(BoolType)}, 353 {x: "h[e]", t: NewPromiseType(BoolType)}, 354 {x: "h[f]", t: NewPromiseType(BoolType)}, 355 {x: "i[e]", t: NewPromiseType(BoolType)}, 356 {x: "i[f]", t: NewPromiseType(BoolType)}, 357 {x: "j[a]", t: NewOutputType(BoolType)}, 358 {x: "j[b]", t: NewOutputType(BoolType)}, 359 {x: "k[a]", t: NewOutputType(BoolType)}, 360 {x: "k[b]", t: NewOutputType(BoolType)}, 361 {x: "l[a]", t: NewOutputType(BoolType)}, 362 {x: "l[b]", t: NewOutputType(BoolType)}, 363 {x: "m[a]", t: NewPromiseType(BoolType)}, 364 {x: "m[b]", t: NewPromiseType(BoolType)}, 365 {x: "n[a]", t: NewPromiseType(BoolType)}, 366 {x: "n[b]", t: NewPromiseType(BoolType)}, 367 {x: "o[a]", t: NewPromiseType(BoolType)}, 368 {x: "o[b]", t: NewPromiseType(BoolType)}, 369 } 370 for _, c := range cases { 371 c := c 372 t.Run(c.x, func(t *testing.T) { 373 t.Parallel() 374 375 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 376 assert.Len(t, diags, 0) 377 assertConvertibleFrom(t, c.t, expr.Type()) 378 _, ok := expr.(*IndexExpression) 379 assert.True(t, ok) 380 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 381 }) 382 } 383 } 384 385 func TestBindObjectCons(t *testing.T) { 386 t.Parallel() 387 388 env := environment(map[string]interface{}{ 389 "a": StringType, 390 "b": NumberType, 391 "c": BoolType, 392 "d": NewOutputType(StringType), 393 "e": NewOutputType(NumberType), 394 "f": NewOutputType(BoolType), 395 "g": NewPromiseType(StringType), 396 "h": NewPromiseType(NumberType), 397 "i": NewPromiseType(BoolType), 398 }) 399 scope := env.scope() 400 401 ot := NewObjectType(map[string]Type{"foo": StringType, "0": NumberType, "false": BoolType}) 402 mt := NewMapType(StringType) 403 cases := []exprTestCase{ 404 // Standard operations 405 {x: `{"foo": "oof", 0: 42, false: true}`, t: ot}, 406 {x: `{(a): a, (b): b, (c): c}`, t: mt}, 407 408 // Lifted operations 409 {x: `{(d): a, (b): b, (c): c}`, t: NewOutputType(mt)}, 410 {x: `{(a): a, (e): b, (c): c}`, t: NewOutputType(mt)}, 411 {x: `{(a): a, (b): b, (f): c}`, t: NewOutputType(mt)}, 412 {x: `{(g): a, (b): b, (c): c}`, t: NewPromiseType(mt)}, 413 {x: `{(a): a, (h): b, (c): c}`, t: NewPromiseType(mt)}, 414 {x: `{(a): a, (b): b, (i): c}`, t: NewPromiseType(mt)}, 415 } 416 for _, c := range cases { 417 c := c 418 t.Run(c.x, func(t *testing.T) { 419 t.Parallel() 420 421 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 422 assert.Len(t, diags, 0) 423 assertConvertibleFrom(t, c.t, expr.Type()) 424 _, ok := expr.(*ObjectConsExpression) 425 assert.True(t, ok) 426 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 427 }) 428 } 429 } 430 431 func TestBindRelativeTraversal(t *testing.T) { 432 t.Parallel() 433 434 env := environment(map[string]interface{}{ 435 "a": NewMapType(StringType), 436 "aa": NewMapType(NewOutputType(StringType)), 437 "b": NewOutputType(NewMapType(StringType)), 438 "c": NewPromiseType(NewMapType(StringType)), 439 "d": NewListType(StringType), 440 "dd": NewListType(NewOutputType(StringType)), 441 "e": NewOutputType(NewListType(StringType)), 442 "f": NewPromiseType(NewListType(StringType)), 443 "g": BoolType, 444 "h": NewOutputType(BoolType), 445 "i": NewPromiseType(BoolType), 446 "j": StringType, 447 "k": NewOutputType(StringType), 448 "l": NewPromiseType(StringType), 449 }) 450 scope := env.scope() 451 452 cases := []exprTestCase{ 453 // Object for 454 {x: `{for k, v in {foo: "bar"}: k => v}.foo`, t: StringType}, 455 {x: `{for k, v in {foo: "bar"}: k => 0}.foo`, t: NumberType}, 456 {x: `{for k, v in {foo: 0}: k => v}.foo`, t: NumberType}, 457 {x: `{for k, v in a: k => v}.foo`, t: StringType}, 458 {x: `{for k, v in aa: k => v}.foo`, t: NewOutputType(StringType)}, 459 {x: `{for k, v in a: k => 0}.foo`, t: NumberType}, 460 {x: `{for k, v in d: v => k}.foo`, t: NumberType}, 461 {x: `{for k, v in d: v => k...}.foo[0]`, t: NumberType}, 462 {x: `{for k, v in d: v => k if k > 10}.foo`, t: NumberType}, 463 464 // List for 465 {x: `[for k, v in {}: [k, v]].0`, t: NewTupleType(StringType, NoneType)}, 466 {x: `[for k, _ in {}: k].0`, t: StringType}, 467 468 // Lifted operations 469 {x: `{for k, v in b: k => v}.foo`, t: NewOutputType(StringType)}, 470 {x: `{for k, v in c: k => v}.foo`, t: NewPromiseType(StringType)}, 471 {x: `[for v in e: v].foo`, t: NewOutputType(StringType)}, 472 {x: `[for v in f: v].foo`, t: NewPromiseType(StringType)}, 473 } 474 for _, c := range cases { 475 c := c 476 t.Run(c.x, func(t *testing.T) { 477 t.Parallel() 478 479 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 480 assert.Len(t, diags, 0) 481 assertConvertibleFrom(t, c.t, expr.Type()) 482 _, ok := expr.(*RelativeTraversalExpression) 483 assert.True(t, ok) 484 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 485 }) 486 } 487 } 488 489 func TestBindScopeTraversal(t *testing.T) { 490 t.Parallel() 491 492 ot := NewObjectType(map[string]Type{ 493 "foo": NewListType(StringType), 494 "bar": NewObjectType(map[string]Type{ 495 "baz": StringType, 496 }), 497 }) 498 env := environment(map[string]interface{}{ 499 "a": StringType, 500 "b": IntType, 501 "c": NewListType(BoolType), 502 "d": NewMapType(BoolType), 503 "e": ot, 504 "f": NewOutputType(StringType), 505 "g": NewOutputType(IntType), 506 "h": NewOutputType(NewListType(BoolType)), 507 "i": NewOutputType(NewMapType(BoolType)), 508 "j": NewOutputType(ot), 509 "k": NewPromiseType(StringType), 510 "l": NewPromiseType(IntType), 511 "m": NewPromiseType(NewListType(BoolType)), 512 "n": NewPromiseType(NewMapType(BoolType)), 513 "o": NewPromiseType(ot), 514 }) 515 scope := env.scope() 516 517 cases := []exprTestCase{ 518 // Standard traversals 519 {x: `a`, t: StringType}, 520 {x: `b`, t: IntType}, 521 {x: `c`, t: NewListType(BoolType)}, 522 {x: `d`, t: NewMapType(BoolType)}, 523 {x: `e`, t: ot}, 524 {x: `f`, t: NewOutputType(StringType)}, 525 {x: `g`, t: NewOutputType(IntType)}, 526 {x: `k`, t: NewPromiseType(StringType)}, 527 {x: `l`, t: NewPromiseType(IntType)}, 528 {x: `c.0`, t: BoolType}, 529 {x: `d.foo`, t: BoolType}, 530 {x: `e.foo`, t: NewListType(StringType)}, 531 {x: `e.foo.0`, t: StringType}, 532 {x: `e.bar`, t: ot.Properties["bar"]}, 533 {x: `e.bar.baz`, t: StringType}, 534 535 // Lifted traversals 536 {x: `h.0`, t: NewOutputType(BoolType)}, 537 {x: `i.foo`, t: NewOutputType(BoolType)}, 538 {x: `j.foo`, t: NewOutputType(NewListType(StringType))}, 539 {x: `j.foo.0`, t: NewOutputType(StringType)}, 540 {x: `j.bar`, t: NewOutputType(ot.Properties["bar"])}, 541 {x: `j.bar.baz`, t: NewOutputType(StringType)}, 542 {x: `m.0`, t: NewPromiseType(BoolType)}, 543 {x: `n.foo`, t: NewPromiseType(BoolType)}, 544 {x: `o.foo`, t: NewPromiseType(NewListType(StringType))}, 545 {x: `o.foo.0`, t: NewPromiseType(StringType)}, 546 {x: `o.bar`, t: NewPromiseType(ot.Properties["bar"])}, 547 {x: `o.bar.baz`, t: NewPromiseType(StringType)}, 548 } 549 for _, c := range cases { 550 c := c 551 t.Run(c.x, func(t *testing.T) { 552 t.Parallel() 553 554 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 555 assert.Len(t, diags, 0) 556 assertConvertibleFrom(t, c.t, expr.Type()) 557 _, ok := expr.(*ScopeTraversalExpression) 558 assert.True(t, ok) 559 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 560 }) 561 } 562 } 563 564 func TestBindSplat(t *testing.T) { 565 t.Parallel() 566 567 ot := NewObjectType(map[string]Type{ 568 "foo": NewListType(StringType), 569 "bar": NewObjectType(map[string]Type{ 570 "baz": StringType, 571 }), 572 }) 573 env := environment(map[string]interface{}{ 574 "a": NewListType(NewListType(StringType)), 575 "b": NewListType(ot), 576 "c": NewSetType(NewListType(StringType)), 577 "d": NewSetType(ot), 578 // "e": NewTupleType(NewListType(StringType)), 579 // "f": NewTupleType(ot), 580 "g": NewListType(NewListType(NewOutputType(StringType))), 581 "h": NewListType(NewListType(NewPromiseType(StringType))), 582 // "i": NewSetType(NewListType(NewOutputType(StringType))), 583 // "j": NewSetType(NewListType(NewPromiseType(StringType))), 584 // "k": NewTupleType(NewListType(NewOutputType(StringType))), 585 // "l": NewTupleType(NewListType(NewPromiseType(StringType))), 586 "m": NewOutputType(NewListType(ot)), 587 "n": NewPromiseType(NewListType(ot)), 588 // "o": NewOutputType(NewSetType(ot)), 589 // "p": NewPromiseType(NewSetType(ot)), 590 // "q": NewOutputType(NewTupleType(ot)), 591 // "r": NewPromiseType(NewTupleType(ot)), 592 }) 593 scope := env.scope() 594 595 cases := []exprTestCase{ 596 // Standard operations 597 {x: `a[*][0]`, t: NewListType(StringType)}, 598 {x: `b[*].bar.baz`, t: NewListType(StringType)}, 599 {x: `b.*.bar.baz`, t: NewListType(StringType)}, 600 // {x: `c[*][0]`, t: NewSetType(StringType)}, 601 // {x: `d[*].bar.baz`, t: NewSetType(StringType)}, 602 // {x: `e[*][0]`, t: NewTupleType(StringType)}, 603 // {x: `f[*].bar.baz`, t: NewTupleType(StringType)}, 604 {x: `g[*][0]`, t: NewListType(NewOutputType(StringType))}, 605 {x: `h[*][0]`, t: NewListType(NewPromiseType(StringType))}, 606 // {x: `i[*][0]`, t: NewListType(NewOutputType(StringType))}, 607 // {x: `j[*][0]`, t: NewListType(NewPromiseType(StringType))}, 608 // {x: `k[*][0]`, t: NewTupleType(NewOutputType(StringType))}, 609 // {x: `l[*][0]`, t: NewTupleType(NewPromiseType(StringType))}, 610 611 // Lifted operations 612 {x: `m[*].bar.baz`, t: NewOutputType(NewListType(StringType))}, 613 {x: `n[*].bar.baz`, t: NewPromiseType(NewListType(StringType))}, 614 {x: `m.*.bar.baz`, t: NewOutputType(NewListType(StringType))}, 615 {x: `n.*.bar.baz`, t: NewPromiseType(NewListType(StringType))}, 616 // {x: `o[*].bar.baz`, t: NewOutputType(NewListType(StringType))}, 617 // {x: `p[*].bar.baz`, t: NewPromiseType(NewListType(StringType))}, 618 // {x: `q[*].bar.baz`, t: NewOutputType(NewTupleType(StringType))}, 619 // {x: `r[*].bar.baz`, t: NewPromiseType(NewTupleType(StringType))}, 620 } 621 for _, c := range cases { 622 c := c 623 t.Run(c.x, func(t *testing.T) { 624 t.Parallel() 625 626 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 627 assert.Len(t, diags, 0) 628 assertConvertibleFrom(t, c.t, expr.Type()) 629 _, ok := expr.(*SplatExpression) 630 assert.True(t, ok) 631 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 632 }) 633 } 634 } 635 636 func TestBindTemplate(t *testing.T) { 637 t.Parallel() 638 639 env := environment(map[string]interface{}{ 640 "a": StringType, 641 "b": NumberType, 642 "c": BoolType, 643 "d": NewListType(StringType), 644 "e": NewOutputType(StringType), 645 "f": NewOutputType(NumberType), 646 "g": NewOutputType(BoolType), 647 "h": NewOutputType(NewListType(StringType)), 648 "i": NewPromiseType(StringType), 649 "j": NewPromiseType(NumberType), 650 "k": NewPromiseType(BoolType), 651 "l": NewPromiseType(NewListType(StringType)), 652 }) 653 scope := env.scope() 654 655 cases := []exprTestCase{ 656 // Unwrapped interpolations 657 {x: `"${0}"`, t: NumberType, xt: &LiteralValueExpression{}}, 658 {x: `"${true}"`, t: BoolType, xt: &LiteralValueExpression{}}, 659 {x: `"${d}"`, t: NewListType(StringType), xt: &ScopeTraversalExpression{}}, 660 {x: `"${e}"`, t: NewOutputType(StringType), xt: &ScopeTraversalExpression{}}, 661 {x: `"${i}"`, t: NewPromiseType(StringType), xt: &ScopeTraversalExpression{}}, 662 663 // Simple interpolations 664 {x: `"v: ${a}"`, t: StringType}, 665 {x: `"v: ${b}"`, t: StringType}, 666 {x: `"v: ${c}"`, t: StringType}, 667 {x: `"v: ${d}"`, t: StringType}, 668 669 // Template control expressions 670 {x: `"%{if c} v: ${a} %{endif}"`, t: StringType}, 671 {x: `"%{for v in d} v: ${v} %{endfor}"`, t: StringType}, 672 673 // Lifted operations 674 {x: `"v: ${e}"`, t: NewOutputType(StringType)}, 675 {x: `"v: ${f}"`, t: NewOutputType(StringType)}, 676 {x: `"v: ${g}"`, t: NewOutputType(StringType)}, 677 {x: `"v: ${h}"`, t: NewOutputType(StringType)}, 678 {x: `"%{if g} v: ${a} %{endif}"`, t: NewOutputType(StringType)}, 679 {x: `"%{for v in h} v: ${v} %{endfor}"`, t: NewOutputType(StringType)}, 680 {x: `"v: ${i}"`, t: NewPromiseType(StringType)}, 681 {x: `"v: ${j}"`, t: NewPromiseType(StringType)}, 682 {x: `"v: ${k}"`, t: NewPromiseType(StringType)}, 683 {x: `"v: ${l}"`, t: NewPromiseType(StringType)}, 684 {x: `"%{if k} v: ${a} %{endif}"`, t: NewPromiseType(StringType)}, 685 {x: `"%{for v in l} v: ${v} %{endfor}"`, t: NewPromiseType(StringType)}, 686 } 687 for _, c := range cases { 688 c := c 689 t.Run(c.x, func(t *testing.T) { 690 t.Parallel() 691 692 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 693 assert.Len(t, diags, 0) 694 assertConvertibleFrom(t, c.t, expr.Type()) 695 696 var ok bool 697 switch c.xt.(type) { 698 case *LiteralValueExpression: 699 _, ok = expr.(*LiteralValueExpression) 700 case *ScopeTraversalExpression: 701 _, ok = expr.(*ScopeTraversalExpression) 702 default: 703 _, ok = expr.(*TemplateExpression) 704 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 705 } 706 assert.True(t, ok) 707 }) 708 } 709 } 710 711 func TestBindTupleCons(t *testing.T) { 712 t.Parallel() 713 714 env := environment(map[string]interface{}{ 715 "a": NewOutputType(StringType), 716 "b": NewPromiseType(StringType), 717 "c": NewUnionType(StringType, BoolType), 718 }) 719 scope := env.scope() 720 721 cases := []exprTestCase{ 722 {x: `["foo", "bar", "baz"]`, t: NewTupleType(StringType, StringType, StringType)}, 723 {x: `[0, "foo", true]`, t: NewTupleType(NumberType, StringType, BoolType)}, 724 {x: `[a, b, c]`, t: NewTupleType(env["a"].(Type), env["b"].(Type), env["c"].(Type))}, 725 {x: `[{"foo": "bar"}]`, t: NewTupleType(NewObjectType(map[string]Type{"foo": StringType}))}, 726 } 727 for _, c := range cases { 728 c := c 729 t.Run(c.x, func(t *testing.T) { 730 t.Parallel() 731 732 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 733 assert.Len(t, diags, 0) 734 assertConvertibleFrom(t, c.t, expr.Type()) 735 _, ok := expr.(*TupleConsExpression) 736 assert.True(t, ok) 737 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 738 }) 739 } 740 } 741 742 func TestBindUnaryOp(t *testing.T) { 743 t.Parallel() 744 745 env := environment(map[string]interface{}{ 746 "a": NumberType, 747 "b": BoolType, 748 "c": NewOutputType(NumberType), 749 "d": NewOutputType(BoolType), 750 "e": NewPromiseType(NumberType), 751 "f": NewPromiseType(BoolType), 752 }) 753 scope := env.scope() 754 755 cases := []exprTestCase{ 756 // Standard operations 757 {x: `-a`, t: NumberType}, 758 {x: `!b`, t: BoolType}, 759 760 // Lifted operations 761 {x: `-c`, t: NewOutputType(NumberType)}, 762 {x: `-e`, t: NewPromiseType(NumberType)}, 763 {x: `!d`, t: NewOutputType(BoolType)}, 764 {x: `!f`, t: NewPromiseType(BoolType)}, 765 } 766 for _, c := range cases { 767 c := c 768 t.Run(c.x, func(t *testing.T) { 769 t.Parallel() 770 771 expr, diags := BindExpressionText(c.x, scope, hcl.Pos{}) 772 assert.Len(t, diags, 0) 773 assertConvertibleFrom(t, c.t, expr.Type()) 774 _, ok := expr.(*UnaryOpExpression) 775 assert.True(t, ok) 776 assert.Equal(t, c.x, fmt.Sprintf("%v", expr)) 777 }) 778 } 779 }