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  }