github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/expression_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclsyntax
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/function"
    14  	"github.com/zclconf/go-cty/cty/function/stdlib"
    15  )
    16  
    17  func TestExpressionParseAndValue(t *testing.T) {
    18  	// This is a combo test that exercises both the parser and the Value
    19  	// method, with the focus on the latter but indirectly testing the former.
    20  	tests := []struct {
    21  		input     string
    22  		ctx       *hcl.EvalContext
    23  		want      cty.Value
    24  		diagCount int
    25  	}{
    26  		{
    27  			`1`,
    28  			nil,
    29  			cty.NumberIntVal(1),
    30  			0,
    31  		},
    32  		{
    33  			`(1)`,
    34  			nil,
    35  			cty.NumberIntVal(1),
    36  			0,
    37  		},
    38  		{
    39  			`(2+3)`,
    40  			nil,
    41  			cty.NumberIntVal(5),
    42  			0,
    43  		},
    44  		{
    45  			`2*5+1`,
    46  			nil,
    47  			cty.NumberIntVal(11),
    48  			0,
    49  		},
    50  		{
    51  			`9%8`,
    52  			nil,
    53  			cty.NumberIntVal(1),
    54  			0,
    55  		},
    56  		{
    57  			`(2+unk)`,
    58  			&hcl.EvalContext{
    59  				Variables: map[string]cty.Value{
    60  					"unk": cty.UnknownVal(cty.Number),
    61  				},
    62  			},
    63  			cty.UnknownVal(cty.Number).RefineNotNull(),
    64  			0,
    65  		},
    66  		{
    67  			`(2+unk)`,
    68  			&hcl.EvalContext{
    69  				Variables: map[string]cty.Value{
    70  					"unk": cty.DynamicVal,
    71  				},
    72  			},
    73  			cty.UnknownVal(cty.Number).RefineNotNull(),
    74  			0,
    75  		},
    76  		{
    77  			`(unk+unk)`,
    78  			&hcl.EvalContext{
    79  				Variables: map[string]cty.Value{
    80  					"unk": cty.DynamicVal,
    81  				},
    82  			},
    83  			cty.UnknownVal(cty.Number).RefineNotNull(),
    84  			0,
    85  		},
    86  		{
    87  			`(2+true)`,
    88  			nil,
    89  			cty.UnknownVal(cty.Number),
    90  			1, // unsuitable type for right operand
    91  		},
    92  		{
    93  			`(false+true)`,
    94  			nil,
    95  			cty.UnknownVal(cty.Number),
    96  			2, // unsuitable type for each operand
    97  		},
    98  		{
    99  			`(5 == 5)`,
   100  			nil,
   101  			cty.True,
   102  			0,
   103  		},
   104  		{
   105  			`(5 == 4)`,
   106  			nil,
   107  			cty.False,
   108  			0,
   109  		},
   110  		{
   111  			`(1 == true)`,
   112  			nil,
   113  			cty.False,
   114  			0,
   115  		},
   116  		{
   117  			`("true" == true)`,
   118  			nil,
   119  			cty.False,
   120  			0,
   121  		},
   122  		{
   123  			`(true == "true")`,
   124  			nil,
   125  			cty.False,
   126  			0,
   127  		},
   128  		{
   129  			`(true != "true")`,
   130  			nil,
   131  			cty.True,
   132  			0,
   133  		},
   134  		{
   135  			`(- 2)`,
   136  			nil,
   137  			cty.NumberIntVal(-2),
   138  			0,
   139  		},
   140  		{
   141  			`(! true)`,
   142  			nil,
   143  			cty.False,
   144  			0,
   145  		},
   146  		{
   147  			`(
   148      1
   149  )`,
   150  			nil,
   151  			cty.NumberIntVal(1),
   152  			0,
   153  		},
   154  		{
   155  			`(1`,
   156  			nil,
   157  			cty.NumberIntVal(1),
   158  			1, // Unbalanced parentheses
   159  		},
   160  		{
   161  			`true`,
   162  			nil,
   163  			cty.True,
   164  			0,
   165  		},
   166  		{
   167  			`false`,
   168  			nil,
   169  			cty.False,
   170  			0,
   171  		},
   172  		{
   173  			`null`,
   174  			nil,
   175  			cty.NullVal(cty.DynamicPseudoType),
   176  			0,
   177  		},
   178  		{
   179  			`true true`,
   180  			nil,
   181  			cty.True,
   182  			1, // extra characters after expression
   183  		},
   184  		{
   185  			`"hello"`,
   186  			nil,
   187  			cty.StringVal("hello"),
   188  			0,
   189  		},
   190  		{
   191  			"\"hello `backtick` world\"",
   192  			nil,
   193  			cty.StringVal("hello `backtick` world"),
   194  			0,
   195  		},
   196  		{
   197  			`"hello\nworld"`,
   198  			nil,
   199  			cty.StringVal("hello\nworld"),
   200  			0,
   201  		},
   202  		{
   203  			`"unclosed`,
   204  			nil,
   205  			cty.StringVal("unclosed"),
   206  			1, // Unterminated template string
   207  		},
   208  		{
   209  			`"hello ${"world"}"`,
   210  			nil,
   211  			cty.StringVal("hello world"),
   212  			0,
   213  		},
   214  		{
   215  			`"hello ${12.5}"`,
   216  			nil,
   217  			cty.StringVal("hello 12.5"),
   218  			0,
   219  		},
   220  		{
   221  			`"silly ${"${"nesting"}"}"`,
   222  			nil,
   223  			cty.StringVal("silly nesting"),
   224  			0,
   225  		},
   226  		{
   227  			`"silly ${"${true}"}"`,
   228  			nil,
   229  			cty.StringVal("silly true"),
   230  			0,
   231  		},
   232  		{
   233  			`"hello $${escaped}"`,
   234  			nil,
   235  			cty.StringVal("hello ${escaped}"),
   236  			0,
   237  		},
   238  		{
   239  			`"hello $$nonescape"`,
   240  			nil,
   241  			cty.StringVal("hello $$nonescape"),
   242  			0,
   243  		},
   244  		{
   245  			`"$"`,
   246  			nil,
   247  			cty.StringVal("$"),
   248  			0,
   249  		},
   250  		{
   251  			`"%"`,
   252  			nil,
   253  			cty.StringVal("%"),
   254  			0,
   255  		},
   256  		{
   257  			`upper("foo")`,
   258  			&hcl.EvalContext{
   259  				Functions: map[string]function.Function{
   260  					"upper": stdlib.UpperFunc,
   261  				},
   262  			},
   263  			cty.StringVal("FOO"),
   264  			0,
   265  		},
   266  		{
   267  			`
   268  upper(
   269      "foo"
   270  )
   271  `,
   272  			&hcl.EvalContext{
   273  				Functions: map[string]function.Function{
   274  					"upper": stdlib.UpperFunc,
   275  				},
   276  			},
   277  			cty.StringVal("FOO"),
   278  			0,
   279  		},
   280  		{
   281  			`upper(["foo"]...)`,
   282  			&hcl.EvalContext{
   283  				Functions: map[string]function.Function{
   284  					"upper": stdlib.UpperFunc,
   285  				},
   286  			},
   287  			cty.StringVal("FOO"),
   288  			0,
   289  		},
   290  		{
   291  			`upper("foo", []...)`,
   292  			&hcl.EvalContext{
   293  				Functions: map[string]function.Function{
   294  					"upper": stdlib.UpperFunc,
   295  				},
   296  			},
   297  			cty.StringVal("FOO"),
   298  			0,
   299  		},
   300  		{
   301  			`upper("foo", "bar")`,
   302  			&hcl.EvalContext{
   303  				Functions: map[string]function.Function{
   304  					"upper": stdlib.UpperFunc,
   305  				},
   306  			},
   307  			cty.DynamicVal,
   308  			1, // too many function arguments
   309  		},
   310  		{
   311  			`upper(["foo", "bar"]...)`,
   312  			&hcl.EvalContext{
   313  				Functions: map[string]function.Function{
   314  					"upper": stdlib.UpperFunc,
   315  				},
   316  			},
   317  			cty.DynamicVal,
   318  			1, // too many function arguments
   319  		},
   320  		{
   321  			`concat([1, null]...)`,
   322  			&hcl.EvalContext{
   323  				Functions: map[string]function.Function{
   324  					"concat": stdlib.ConcatFunc,
   325  				},
   326  			},
   327  			cty.DynamicVal,
   328  			1, // argument cannot be null
   329  		},
   330  		{
   331  			`concat(var.unknownlist...)`,
   332  			&hcl.EvalContext{
   333  				Functions: map[string]function.Function{
   334  					"concat": stdlib.ConcatFunc,
   335  				},
   336  				Variables: map[string]cty.Value{
   337  					"var": cty.ObjectVal(map[string]cty.Value{
   338  						"unknownlist": cty.UnknownVal(cty.DynamicPseudoType),
   339  					}),
   340  				},
   341  			},
   342  			cty.DynamicVal,
   343  			0,
   344  		},
   345  		{
   346  			`foo::upper("foo")`,
   347  			&hcl.EvalContext{
   348  				Functions: map[string]function.Function{
   349  					"foo::upper": stdlib.UpperFunc,
   350  				},
   351  			},
   352  			cty.StringVal("FOO"),
   353  			0,
   354  		},
   355  		{
   356  			`foo :: upper("foo")`, // spaces are non-idomatic, but valid
   357  			&hcl.EvalContext{
   358  				Functions: map[string]function.Function{
   359  					"foo::upper": stdlib.UpperFunc,
   360  				},
   361  			},
   362  			cty.StringVal("FOO"),
   363  			0,
   364  		},
   365  		{
   366  			`::upper("foo")`, // :: is still not a valid identifier
   367  			&hcl.EvalContext{
   368  				Functions: map[string]function.Function{
   369  					"::upper": stdlib.UpperFunc,
   370  				},
   371  			},
   372  			cty.DynamicVal,
   373  			1,
   374  		},
   375  		{
   376  			`double::::upper("foo")`, // missing name after ::
   377  			&hcl.EvalContext{
   378  				Functions: map[string]function.Function{
   379  					"double::::upper": stdlib.UpperFunc,
   380  				},
   381  			},
   382  			cty.NilVal,
   383  			1,
   384  		},
   385  		{
   386  			`missing::("foo")`, // missing name after ::
   387  			&hcl.EvalContext{
   388  				Functions: map[string]function.Function{
   389  					"missing::": stdlib.UpperFunc,
   390  				},
   391  			},
   392  			cty.NilVal,
   393  			1,
   394  		},
   395  		{
   396  			`misbehave()`,
   397  			&hcl.EvalContext{
   398  				Functions: map[string]function.Function{
   399  					"misbehave": function.New(&function.Spec{
   400  						Type: func(args []cty.Value) (cty.Type, error) {
   401  							// This function misbehaves by indicating an error
   402  							// on an argument index that is out of range for
   403  							// its declared parameters. That would always be
   404  							// a bug in the function, but we want to avoid
   405  							// panicking in this case and just behave like it
   406  							// was a normal (non-arg) error.
   407  							return cty.NilType, function.NewArgErrorf(1, "out of range")
   408  						},
   409  					}),
   410  				},
   411  			},
   412  			cty.DynamicVal,
   413  			1, // Call to function "misbehave" failed: out of range
   414  		},
   415  		{
   416  			`misbehave() /* variadic */`,
   417  			&hcl.EvalContext{
   418  				Functions: map[string]function.Function{
   419  					"misbehave": function.New(&function.Spec{
   420  						VarParam: &function.Parameter{
   421  							Name: "foo",
   422  							Type: cty.String,
   423  						},
   424  						Type: func(args []cty.Value) (cty.Type, error) {
   425  							// This function misbehaves by indicating an error
   426  							// on an argument index that is out of range for
   427  							// the given arguments. That would always be a
   428  							// bug in the function, but to avoid panicking we
   429  							// just treat it like a problem related to the
   430  							// declared variadic argument.
   431  							return cty.NilType, function.NewArgErrorf(1, "out of range")
   432  						},
   433  					}),
   434  				},
   435  			},
   436  			cty.DynamicVal,
   437  			1, // Invalid value for "foo" parameter: out of range
   438  		},
   439  		{
   440  			`misbehave([]...)`,
   441  			&hcl.EvalContext{
   442  				Functions: map[string]function.Function{
   443  					"misbehave": function.New(&function.Spec{
   444  						VarParam: &function.Parameter{
   445  							Name: "foo",
   446  							Type: cty.String,
   447  						},
   448  						Type: func(args []cty.Value) (cty.Type, error) {
   449  							// This function misbehaves by indicating an error
   450  							// on an argument index that is out of range for
   451  							// the given arguments. That would always be a
   452  							// bug in the function, but to avoid panicking we
   453  							// just treat it like a problem related to the
   454  							// declared variadic argument.
   455  							return cty.NilType, function.NewArgErrorf(1, "out of range")
   456  						},
   457  					}),
   458  				},
   459  			},
   460  			cty.DynamicVal,
   461  			1, // Invalid value for "foo" parameter: out of range
   462  		},
   463  		{
   464  			`argerrorexpand(["a", "b"]...)`,
   465  			&hcl.EvalContext{
   466  				Functions: map[string]function.Function{
   467  					"argerrorexpand": function.New(&function.Spec{
   468  						VarParam: &function.Parameter{
   469  							Name: "foo",
   470  							Type: cty.String,
   471  						},
   472  						Type: func(args []cty.Value) (cty.Type, error) {
   473  							// We should be able to indicate an error in
   474  							// argument 1 because the indices are into the
   475  							// arguments _after_ "..." expansion. An earlier
   476  							// HCL version had a bug where it used the
   477  							// pre-expansion arguments and would thus panic
   478  							// in this case.
   479  							return cty.NilType, function.NewArgErrorf(1, "blah blah")
   480  						},
   481  					}),
   482  				},
   483  			},
   484  			cty.DynamicVal,
   485  			1, // Invalid value for "foo" parameter: blah blah
   486  		},
   487  		{
   488  			`[]`,
   489  			nil,
   490  			cty.EmptyTupleVal,
   491  			0,
   492  		},
   493  		{
   494  			`[1]`,
   495  			nil,
   496  			cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
   497  			0,
   498  		},
   499  		{
   500  			`[1,]`,
   501  			nil,
   502  			cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
   503  			0,
   504  		},
   505  		{
   506  			`[1,true]`,
   507  			nil,
   508  			cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
   509  			0,
   510  		},
   511  		{
   512  			`[
   513    1,
   514    true
   515  ]`,
   516  			nil,
   517  			cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
   518  			0,
   519  		},
   520  		{
   521  			`{}`,
   522  			nil,
   523  			cty.EmptyObjectVal,
   524  			0,
   525  		},
   526  		{
   527  			`{"hello": "world"}`,
   528  			nil,
   529  			cty.ObjectVal(map[string]cty.Value{
   530  				"hello": cty.StringVal("world"),
   531  			}),
   532  			0,
   533  		},
   534  		{
   535  			`{"hello" = "world"}`,
   536  			nil,
   537  			cty.ObjectVal(map[string]cty.Value{
   538  				"hello": cty.StringVal("world"),
   539  			}),
   540  			0,
   541  		},
   542  		{
   543  			`{hello = "world"}`,
   544  			nil,
   545  			cty.ObjectVal(map[string]cty.Value{
   546  				"hello": cty.StringVal("world"),
   547  			}),
   548  			0,
   549  		},
   550  		{
   551  			`{hello: "world"}`,
   552  			nil,
   553  			cty.ObjectVal(map[string]cty.Value{
   554  				"hello": cty.StringVal("world"),
   555  			}),
   556  			0,
   557  		},
   558  		{
   559  			`{true: "yes"}`,
   560  			nil,
   561  			cty.ObjectVal(map[string]cty.Value{
   562  				"true": cty.StringVal("yes"),
   563  			}),
   564  			0,
   565  		},
   566  		{
   567  			`{false: "yes"}`,
   568  			nil,
   569  			cty.ObjectVal(map[string]cty.Value{
   570  				"false": cty.StringVal("yes"),
   571  			}),
   572  			0,
   573  		},
   574  		{
   575  			`{null: "yes"}`,
   576  			nil,
   577  			cty.ObjectVal(map[string]cty.Value{
   578  				"null": cty.StringVal("yes"),
   579  			}),
   580  			0,
   581  		},
   582  		{
   583  			`{15: "yes"}`,
   584  			nil,
   585  			cty.ObjectVal(map[string]cty.Value{
   586  				"15": cty.StringVal("yes"),
   587  			}),
   588  			0,
   589  		},
   590  		{
   591  			`{[]: "yes"}`,
   592  			nil,
   593  			cty.DynamicVal,
   594  			1, // Incorrect key type; Can't use this value as a key: string required
   595  		},
   596  		{
   597  			`{"centos_7.2_ap-south-1" = "ami-abc123"}`,
   598  			nil,
   599  			cty.ObjectVal(map[string]cty.Value{
   600  				"centos_7.2_ap-south-1": cty.StringVal("ami-abc123"),
   601  			}),
   602  			0,
   603  		},
   604  		{
   605  			// This is syntactically valid (it's similar to foo["bar"])
   606  			// but is rejected during evaluation to force the user to be explicit
   607  			// about which of the following interpretations they mean:
   608  			// -{(foo.bar) = "baz"}
   609  			// -{"foo.bar" = "baz"}
   610  			// naked traversals as keys are allowed when analyzing an expression
   611  			// statically so an application can define object-syntax-based
   612  			// language constructs with looser requirements, but we reject
   613  			// this during normal expression evaluation.
   614  			`{foo.bar = "ami-abc123"}`,
   615  			nil,
   616  			cty.DynamicVal,
   617  			1, // Ambiguous attribute key; If this expression is intended to be a reference, wrap it in parentheses. If it's instead intended as a literal name containing periods, wrap it in quotes to create a string literal.
   618  		},
   619  		{
   620  			// This is a weird variant of the above where a period is followed
   621  			// by a digit, causing the parser to interpret it as an index
   622  			// operator using the legacy HIL/Terraform index syntax.
   623  			// This one _does_ fail parsing, causing it to be subject to
   624  			// parser recovery behavior.
   625  			`{centos_7.2_ap-south-1 = "ami-abc123"}`,
   626  			nil,
   627  			cty.EmptyObjectVal, // (due to parser recovery behavior)
   628  			1,                  // Missing key/value separator; Expected an equals sign ("=") to mark the beginning of the attribute value. If you intended to given an attribute name containing periods or spaces, write the name in quotes to create a string literal.
   629  		},
   630  		{
   631  			`{var.greeting = "world"}`,
   632  			&hcl.EvalContext{
   633  				Variables: map[string]cty.Value{
   634  					"var": cty.ObjectVal(map[string]cty.Value{
   635  						"greeting": cty.StringVal("hello"),
   636  					}),
   637  				},
   638  			},
   639  			cty.DynamicVal,
   640  			1, // Ambiguous attribute key
   641  		},
   642  		{
   643  			`{(var.greeting) = "world"}`,
   644  			&hcl.EvalContext{
   645  				Variables: map[string]cty.Value{
   646  					"var": cty.ObjectVal(map[string]cty.Value{
   647  						"greeting": cty.StringVal("hello"),
   648  					}),
   649  				},
   650  			},
   651  			cty.ObjectVal(map[string]cty.Value{
   652  				"hello": cty.StringVal("world"),
   653  			}),
   654  			0,
   655  		},
   656  		{
   657  			// Marked values as object keys
   658  			`{(var.greeting) = "world", "goodbye" = "earth"}`,
   659  			&hcl.EvalContext{
   660  				Variables: map[string]cty.Value{
   661  					"var": cty.ObjectVal(map[string]cty.Value{
   662  						"greeting": cty.StringVal("hello").Mark("marked"),
   663  					}),
   664  				},
   665  			},
   666  			cty.ObjectVal(map[string]cty.Value{
   667  				"hello":   cty.StringVal("world"),
   668  				"goodbye": cty.StringVal("earth"),
   669  			}).Mark("marked"),
   670  			0,
   671  		},
   672  		{
   673  			`{"${var.greeting}" = "world"}`,
   674  			&hcl.EvalContext{
   675  				Variables: map[string]cty.Value{
   676  					"var": cty.ObjectVal(map[string]cty.Value{
   677  						"greeting": cty.StringVal("hello"),
   678  					}),
   679  				},
   680  			},
   681  			cty.ObjectVal(map[string]cty.Value{
   682  				"hello": cty.StringVal("world"),
   683  			}),
   684  			0,
   685  		},
   686  		{
   687  			`{"hello" = "world", "goodbye" = "cruel world"}`,
   688  			nil,
   689  			cty.ObjectVal(map[string]cty.Value{
   690  				"hello":   cty.StringVal("world"),
   691  				"goodbye": cty.StringVal("cruel world"),
   692  			}),
   693  			0,
   694  		},
   695  		{
   696  			`{
   697    "hello" = "world"
   698  }`,
   699  			nil,
   700  			cty.ObjectVal(map[string]cty.Value{
   701  				"hello": cty.StringVal("world"),
   702  			}),
   703  			0,
   704  		},
   705  		{
   706  			`{
   707    "hello" = "world"
   708    "goodbye" = "cruel world"
   709  }`,
   710  			nil,
   711  			cty.ObjectVal(map[string]cty.Value{
   712  				"hello":   cty.StringVal("world"),
   713  				"goodbye": cty.StringVal("cruel world"),
   714  			}),
   715  			0,
   716  		},
   717  		{
   718  			`{
   719    "hello" = "world",
   720    "goodbye" = "cruel world"
   721  }`,
   722  			nil,
   723  			cty.ObjectVal(map[string]cty.Value{
   724  				"hello":   cty.StringVal("world"),
   725  				"goodbye": cty.StringVal("cruel world"),
   726  			}),
   727  			0,
   728  		},
   729  		{
   730  			`{
   731    "hello" = "world",
   732    "goodbye" = "cruel world",
   733  }`,
   734  			nil,
   735  			cty.ObjectVal(map[string]cty.Value{
   736  				"hello":   cty.StringVal("world"),
   737  				"goodbye": cty.StringVal("cruel world"),
   738  			}),
   739  			0,
   740  		},
   741  
   742  		{
   743  			"{\n  for k, v in {hello: \"world\"}:\nk => v\n}",
   744  			nil,
   745  			cty.ObjectVal(map[string]cty.Value{
   746  				"hello": cty.StringVal("world"),
   747  			}),
   748  			0,
   749  		},
   750  		{
   751  			// This one is different than the previous because the extra level of
   752  			// object constructor causes the inner for expression to begin parsing
   753  			// in newline-sensitive mode, which it must then properly disable in
   754  			// order to peek the "for" keyword.
   755  			"{\n  a = {\n  for k, v in {hello: \"world\"}:\nk => v\n  }\n}",
   756  			nil,
   757  			cty.ObjectVal(map[string]cty.Value{
   758  				"a": cty.ObjectVal(map[string]cty.Value{
   759  					"hello": cty.StringVal("world"),
   760  				}),
   761  			}),
   762  			0,
   763  		},
   764  		{
   765  			`{for k, v in {hello: "world"}: k => v if k == "hello"}`,
   766  			nil,
   767  			cty.ObjectVal(map[string]cty.Value{
   768  				"hello": cty.StringVal("world"),
   769  			}),
   770  			0,
   771  		},
   772  		{
   773  			`{for k, v in {hello: "world"}: upper(k) => upper(v) if k == "hello"}`,
   774  			&hcl.EvalContext{
   775  				Functions: map[string]function.Function{
   776  					"upper": stdlib.UpperFunc,
   777  				},
   778  			},
   779  			cty.ObjectVal(map[string]cty.Value{
   780  				"HELLO": cty.StringVal("WORLD"),
   781  			}),
   782  			0,
   783  		},
   784  		{
   785  			`{for k, v in ["world"]: k => v if k == 0}`,
   786  			nil,
   787  			cty.ObjectVal(map[string]cty.Value{
   788  				"0": cty.StringVal("world"),
   789  			}),
   790  			0,
   791  		},
   792  		{
   793  			`{for v in ["world"]: v => v}`,
   794  			nil,
   795  			cty.ObjectVal(map[string]cty.Value{
   796  				"world": cty.StringVal("world"),
   797  			}),
   798  			0,
   799  		},
   800  		{
   801  			`{for k, v in {hello: "world"}: k => v if k == "foo"}`,
   802  			nil,
   803  			cty.EmptyObjectVal,
   804  			0,
   805  		},
   806  		{
   807  			`{for k, v in {hello: "world"}: 5 => v}`,
   808  			nil,
   809  			cty.ObjectVal(map[string]cty.Value{
   810  				"5": cty.StringVal("world"),
   811  			}),
   812  			0,
   813  		},
   814  		{
   815  			`{for k, v in {hello: "world"}: [] => v}`,
   816  			nil,
   817  			cty.DynamicVal,
   818  			1, // key expression has the wrong type
   819  		},
   820  		{
   821  			`{for k, v in {hello: "world"}: k => k if k == "hello"}`,
   822  			nil,
   823  			cty.ObjectVal(map[string]cty.Value{
   824  				"hello": cty.StringVal("hello"),
   825  			}),
   826  			0,
   827  		},
   828  		{
   829  			`{for k, v in {hello: "world"}: k => foo}`,
   830  			&hcl.EvalContext{
   831  				Variables: map[string]cty.Value{
   832  					"foo": cty.StringVal("foo"),
   833  				},
   834  			},
   835  			cty.ObjectVal(map[string]cty.Value{
   836  				"hello": cty.StringVal("foo"),
   837  			}),
   838  			0,
   839  		},
   840  		{
   841  			`[for k, v in {hello: "world"}: "${k}=${v}"]`,
   842  			nil,
   843  			cty.TupleVal([]cty.Value{
   844  				cty.StringVal("hello=world"),
   845  			}),
   846  			0,
   847  		},
   848  		{
   849  			`[for k, v in {hello: "world"}: k => v]`,
   850  			nil,
   851  			cty.ObjectVal(map[string]cty.Value{
   852  				"hello": cty.StringVal("world"),
   853  			}),
   854  			1, // can't have a key expr when producing a tuple
   855  		},
   856  		{
   857  			`{for v in {hello: "world"}: v}`,
   858  			nil,
   859  			cty.TupleVal([]cty.Value{
   860  				cty.StringVal("world"),
   861  			}),
   862  			1, // must have a key expr when producing a map
   863  		},
   864  		{
   865  			`{for i, v in ["a", "b", "c", "b", "d"]: v => i...}`,
   866  			nil,
   867  			cty.ObjectVal(map[string]cty.Value{
   868  				"a": cty.TupleVal([]cty.Value{
   869  					cty.NumberIntVal(0),
   870  				}),
   871  				"b": cty.TupleVal([]cty.Value{
   872  					cty.NumberIntVal(1),
   873  					cty.NumberIntVal(3),
   874  				}),
   875  				"c": cty.TupleVal([]cty.Value{
   876  					cty.NumberIntVal(2),
   877  				}),
   878  				"d": cty.TupleVal([]cty.Value{
   879  					cty.NumberIntVal(4),
   880  				}),
   881  			}),
   882  			0,
   883  		},
   884  		{
   885  			`{for i, v in ["a", "b", "c", "b", "d"]: v => i... if i <= 2}`,
   886  			nil,
   887  			cty.ObjectVal(map[string]cty.Value{
   888  				"a": cty.TupleVal([]cty.Value{
   889  					cty.NumberIntVal(0),
   890  				}),
   891  				"b": cty.TupleVal([]cty.Value{
   892  					cty.NumberIntVal(1),
   893  				}),
   894  				"c": cty.TupleVal([]cty.Value{
   895  					cty.NumberIntVal(2),
   896  				}),
   897  			}),
   898  			0,
   899  		},
   900  		{
   901  			`{for i, v in ["a", "b", "c", "b", "d"]: v => i}`,
   902  			nil,
   903  			cty.ObjectVal(map[string]cty.Value{
   904  				"a": cty.NumberIntVal(0),
   905  				"b": cty.NumberIntVal(1),
   906  				"c": cty.NumberIntVal(2),
   907  				"d": cty.NumberIntVal(4),
   908  			}),
   909  			1, // duplicate key "b"
   910  		},
   911  		{
   912  			`[for v in {hello: "world"}: v...]`,
   913  			nil,
   914  			cty.TupleVal([]cty.Value{
   915  				cty.StringVal("world"),
   916  			}),
   917  			1, // can't use grouping when producing a tuple
   918  		},
   919  		{
   920  			`[for v in "hello": v]`,
   921  			nil,
   922  			cty.DynamicVal,
   923  			1, // can't iterate over a string
   924  		},
   925  		{
   926  			`[for v in null: v]`,
   927  			nil,
   928  			cty.DynamicVal,
   929  			1, // can't iterate over a null value
   930  		},
   931  		{
   932  			`[for v in unk: v]`,
   933  			&hcl.EvalContext{
   934  				Variables: map[string]cty.Value{
   935  					"unk": cty.UnknownVal(cty.List(cty.String)),
   936  				},
   937  			},
   938  			cty.DynamicVal,
   939  			0,
   940  		},
   941  		{
   942  			`[for v in unk: v]`,
   943  			&hcl.EvalContext{
   944  				Variables: map[string]cty.Value{
   945  					"unk": cty.DynamicVal,
   946  				},
   947  			},
   948  			cty.DynamicVal,
   949  			0,
   950  		},
   951  		{
   952  			`[for v in unk: v]`,
   953  			&hcl.EvalContext{
   954  				Variables: map[string]cty.Value{
   955  					"unk": cty.UnknownVal(cty.String),
   956  				},
   957  			},
   958  			cty.DynamicVal,
   959  			1, // can't iterate over a string (even if it's unknown)
   960  		},
   961  		{
   962  			`[for v in ["a", "b"]: v if unkbool]`,
   963  			&hcl.EvalContext{
   964  				Variables: map[string]cty.Value{
   965  					"unkbool": cty.UnknownVal(cty.Bool),
   966  				},
   967  			},
   968  			cty.DynamicVal,
   969  			0,
   970  		},
   971  		{
   972  			`[for v in ["a", "b"]: v if nullbool]`,
   973  			&hcl.EvalContext{
   974  				Variables: map[string]cty.Value{
   975  					"nullbool": cty.NullVal(cty.Bool),
   976  				},
   977  			},
   978  			cty.DynamicVal,
   979  			1, // value of if clause must not be null
   980  		},
   981  		{
   982  			`[for v in ["a", "b"]: v if dyn]`,
   983  			&hcl.EvalContext{
   984  				Variables: map[string]cty.Value{
   985  					"dyn": cty.DynamicVal,
   986  				},
   987  			},
   988  			cty.DynamicVal,
   989  			0,
   990  		},
   991  		{
   992  			`[for v in ["a", "b"]: v if unknum]`,
   993  			&hcl.EvalContext{
   994  				Variables: map[string]cty.Value{
   995  					"unknum": cty.UnknownVal(cty.List(cty.Number)),
   996  				},
   997  			},
   998  			cty.DynamicVal,
   999  			1, // if expression must be bool
  1000  		},
  1001  		{
  1002  			`[for i, v in ["a", "b"]: v if i + i]`,
  1003  			nil,
  1004  			cty.DynamicVal,
  1005  			1, // if expression must be bool
  1006  		},
  1007  		{
  1008  			`[for v in ["a", "b"]: unkstr]`,
  1009  			&hcl.EvalContext{
  1010  				Variables: map[string]cty.Value{
  1011  					"unkstr": cty.UnknownVal(cty.String),
  1012  				},
  1013  			},
  1014  			cty.TupleVal([]cty.Value{
  1015  				cty.UnknownVal(cty.String),
  1016  				cty.UnknownVal(cty.String),
  1017  			}),
  1018  			0,
  1019  		},
  1020  		{ // Marked sequence results in a marked tuple
  1021  			`[for x in things: x if x != ""]`,
  1022  			&hcl.EvalContext{
  1023  				Variables: map[string]cty.Value{
  1024  					"things": cty.ListVal([]cty.Value{
  1025  						cty.StringVal("a"),
  1026  						cty.StringVal("b"),
  1027  						cty.StringVal(""),
  1028  						cty.StringVal("c"),
  1029  					}).Mark("sensitive"),
  1030  				},
  1031  			},
  1032  			cty.TupleVal([]cty.Value{
  1033  				cty.StringVal("a"),
  1034  				cty.StringVal("b"),
  1035  				cty.StringVal("c"),
  1036  			}).Mark("sensitive"),
  1037  			0,
  1038  		},
  1039  		{ // Marked map results in a marked object
  1040  			`{for k, v in things: k => !v}`,
  1041  			&hcl.EvalContext{
  1042  				Variables: map[string]cty.Value{
  1043  					"things": cty.MapVal(map[string]cty.Value{
  1044  						"a": cty.True,
  1045  						"b": cty.False,
  1046  					}).Mark("sensitive"),
  1047  				},
  1048  			},
  1049  			cty.ObjectVal(map[string]cty.Value{
  1050  				"a": cty.False,
  1051  				"b": cty.True,
  1052  			}).Mark("sensitive"),
  1053  			0,
  1054  		},
  1055  		{ // Marked map member carries marks through
  1056  			`{for k, v in things: k => !v}`,
  1057  			&hcl.EvalContext{
  1058  				Variables: map[string]cty.Value{
  1059  					"things": cty.MapVal(map[string]cty.Value{
  1060  						"a": cty.True.Mark("sensitive"),
  1061  						"b": cty.False,
  1062  					}),
  1063  				},
  1064  			},
  1065  			cty.ObjectVal(map[string]cty.Value{
  1066  				"a": cty.False.Mark("sensitive"),
  1067  				"b": cty.True,
  1068  			}),
  1069  			0,
  1070  		},
  1071  		{
  1072  			// Mark object if keys include marked values, members retain
  1073  			// their original marks in their values
  1074  			`{for v in things: v => "${v}-friend"}`,
  1075  			&hcl.EvalContext{
  1076  				Variables: map[string]cty.Value{
  1077  					"things": cty.MapVal(map[string]cty.Value{
  1078  						"a": cty.StringVal("rosie").Mark("marked"),
  1079  						"b": cty.StringVal("robin"),
  1080  						// Check for double-marking when a key val has a duplicate mark
  1081  						"c": cty.StringVal("rowan").Mark("marked"),
  1082  						"d": cty.StringVal("ruben").Mark("also-marked"),
  1083  					}),
  1084  				},
  1085  			},
  1086  			cty.ObjectVal(map[string]cty.Value{
  1087  				"rosie": cty.StringVal("rosie-friend").Mark("marked"),
  1088  				"robin": cty.StringVal("robin-friend"),
  1089  				"rowan": cty.StringVal("rowan-friend").Mark("marked"),
  1090  				"ruben": cty.StringVal("ruben-friend").Mark("also-marked"),
  1091  			}).WithMarks(cty.NewValueMarks("marked", "also-marked")),
  1092  			0,
  1093  		},
  1094  		{ // object itself is marked, contains marked value
  1095  			`{for v in things: v => "${v}-friend"}`,
  1096  			&hcl.EvalContext{
  1097  				Variables: map[string]cty.Value{
  1098  					"things": cty.MapVal(map[string]cty.Value{
  1099  						"a": cty.StringVal("rosie").Mark("marked"),
  1100  						"b": cty.StringVal("robin"),
  1101  					}).Mark("marks"),
  1102  				},
  1103  			},
  1104  			cty.ObjectVal(map[string]cty.Value{
  1105  				"rosie": cty.StringVal("rosie-friend").Mark("marked"),
  1106  				"robin": cty.StringVal("robin-friend"),
  1107  			}).WithMarks(cty.NewValueMarks("marked", "marks")),
  1108  			0,
  1109  		},
  1110  		{ // Sequence for loop with marked conditional expression
  1111  			`[for x in things: x if x != secret]`,
  1112  			&hcl.EvalContext{
  1113  				Variables: map[string]cty.Value{
  1114  					"things": cty.ListVal([]cty.Value{
  1115  						cty.StringVal("a"),
  1116  						cty.StringVal("b"),
  1117  						cty.StringVal("c"),
  1118  					}),
  1119  					"secret": cty.StringVal("b").Mark("sensitive"),
  1120  				},
  1121  			},
  1122  			cty.TupleVal([]cty.Value{
  1123  				cty.StringVal("a"),
  1124  				cty.StringVal("c"),
  1125  			}).Mark("sensitive"),
  1126  			0,
  1127  		},
  1128  		{ // Map for loop with marked conditional expression
  1129  			`{ for k, v in things: k => v if k != secret }`,
  1130  			&hcl.EvalContext{
  1131  				Variables: map[string]cty.Value{
  1132  					"things": cty.MapVal(map[string]cty.Value{
  1133  						"a": cty.True,
  1134  						"b": cty.False,
  1135  						"c": cty.False,
  1136  					}),
  1137  					"secret": cty.StringVal("b").Mark("sensitive"),
  1138  				},
  1139  			},
  1140  			cty.ObjectVal(map[string]cty.Value{
  1141  				"a": cty.True,
  1142  				"c": cty.False,
  1143  			}).Mark("sensitive"),
  1144  			0,
  1145  		},
  1146  		{
  1147  			`[{name: "Steve"}, {name: "Ermintrude"}].*.name`,
  1148  			nil,
  1149  			cty.TupleVal([]cty.Value{
  1150  				cty.StringVal("Steve"),
  1151  				cty.StringVal("Ermintrude"),
  1152  			}),
  1153  			0,
  1154  		},
  1155  		{
  1156  			`{name: "Steve"}.*.name`,
  1157  			nil,
  1158  			cty.TupleVal([]cty.Value{
  1159  				cty.StringVal("Steve"),
  1160  			}),
  1161  			0,
  1162  		},
  1163  		{
  1164  			`null[*]`,
  1165  			nil,
  1166  			cty.EmptyTupleVal,
  1167  			0,
  1168  		},
  1169  		{
  1170  			`{name: "Steve"}[*].name`,
  1171  			nil,
  1172  			cty.TupleVal([]cty.Value{
  1173  				cty.StringVal("Steve"),
  1174  			}),
  1175  			0,
  1176  		},
  1177  		{
  1178  			`set.*.name`,
  1179  			&hcl.EvalContext{
  1180  				Variables: map[string]cty.Value{
  1181  					"set": cty.SetVal([]cty.Value{
  1182  						cty.ObjectVal(map[string]cty.Value{
  1183  							"name": cty.StringVal("Steve"),
  1184  						}),
  1185  					}),
  1186  				},
  1187  			},
  1188  			cty.ListVal([]cty.Value{
  1189  				cty.StringVal("Steve"),
  1190  			}),
  1191  			0,
  1192  		},
  1193  		{
  1194  			`unkstr[*]`,
  1195  			&hcl.EvalContext{
  1196  				Variables: map[string]cty.Value{
  1197  					"unkstr": cty.UnknownVal(cty.String),
  1198  				},
  1199  			},
  1200  			cty.DynamicVal,
  1201  			0,
  1202  		},
  1203  		{
  1204  			`unkstr[*]`,
  1205  			&hcl.EvalContext{
  1206  				Variables: map[string]cty.Value{
  1207  					"unkstr": cty.UnknownVal(cty.String).RefineNotNull(),
  1208  				},
  1209  			},
  1210  			// If the unknown string is definitely not null then we already
  1211  			// know that the result will be a single-element tuple.
  1212  			cty.TupleVal([]cty.Value{
  1213  				cty.UnknownVal(cty.String).RefineNotNull(),
  1214  			}),
  1215  			0,
  1216  		},
  1217  		{
  1218  			`unkstr.*.name`,
  1219  			&hcl.EvalContext{
  1220  				Variables: map[string]cty.Value{
  1221  					"unkstr": cty.UnknownVal(cty.String),
  1222  				},
  1223  			},
  1224  			cty.DynamicVal,
  1225  			1, // a string has no attribute "name"
  1226  		},
  1227  		{
  1228  			`dyn.*.name`,
  1229  			&hcl.EvalContext{
  1230  				Variables: map[string]cty.Value{
  1231  					"dyn": cty.DynamicVal,
  1232  				},
  1233  			},
  1234  			cty.DynamicVal,
  1235  			0,
  1236  		},
  1237  		{
  1238  			`unkobj.*.name`,
  1239  			&hcl.EvalContext{
  1240  				Variables: map[string]cty.Value{
  1241  					"unkobj": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1242  						"name": cty.String,
  1243  					})),
  1244  				},
  1245  			},
  1246  			cty.DynamicVal,
  1247  			0,
  1248  		},
  1249  		{
  1250  			`unkobj.*.name`,
  1251  			&hcl.EvalContext{
  1252  				Variables: map[string]cty.Value{
  1253  					"unkobj": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1254  						"name": cty.String,
  1255  					})).RefineNotNull(),
  1256  				},
  1257  			},
  1258  			cty.TupleVal([]cty.Value{
  1259  				cty.UnknownVal(cty.String),
  1260  			}),
  1261  			0,
  1262  		},
  1263  		{
  1264  			`unkobj.*.names`,
  1265  			&hcl.EvalContext{
  1266  				Variables: map[string]cty.Value{
  1267  					"unkobj": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1268  						"names": cty.List(cty.String),
  1269  					})),
  1270  				},
  1271  			},
  1272  			cty.DynamicVal,
  1273  			0,
  1274  		},
  1275  		{
  1276  			`unklistobj.*.name`,
  1277  			&hcl.EvalContext{
  1278  				Variables: map[string]cty.Value{
  1279  					"unklistobj": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  1280  						"name": cty.String,
  1281  					}))),
  1282  				},
  1283  			},
  1284  			cty.UnknownVal(cty.List(cty.String)).RefineNotNull(),
  1285  			0,
  1286  		},
  1287  		{
  1288  			`unklistobj.*.name`,
  1289  			&hcl.EvalContext{
  1290  				Variables: map[string]cty.Value{
  1291  					"unklistobj": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  1292  						"name": cty.String,
  1293  					}))).Refine().
  1294  						CollectionLengthUpperBound(5).
  1295  						NewValue(),
  1296  				},
  1297  			},
  1298  			cty.UnknownVal(cty.List(cty.String)).Refine().
  1299  				NotNull().
  1300  				CollectionLengthUpperBound(5).
  1301  				NewValue(),
  1302  			0,
  1303  		},
  1304  		{
  1305  			`unktupleobj.*.name`,
  1306  			&hcl.EvalContext{
  1307  				Variables: map[string]cty.Value{
  1308  					"unktupleobj": cty.UnknownVal(
  1309  						cty.Tuple([]cty.Type{
  1310  							cty.Object(map[string]cty.Type{
  1311  								"name": cty.String,
  1312  							}),
  1313  							cty.Object(map[string]cty.Type{
  1314  								"name": cty.Bool,
  1315  							}),
  1316  						}),
  1317  					),
  1318  				},
  1319  			},
  1320  			cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool})).RefineNotNull(),
  1321  			0,
  1322  		},
  1323  		{
  1324  			`nullobj.*.name`,
  1325  			&hcl.EvalContext{
  1326  				Variables: map[string]cty.Value{
  1327  					"nullobj": cty.NullVal(cty.Object(map[string]cty.Type{
  1328  						"name": cty.String,
  1329  					})),
  1330  				},
  1331  			},
  1332  			cty.TupleVal([]cty.Value{}),
  1333  			0,
  1334  		},
  1335  		{
  1336  			`nulllist.*.name`,
  1337  			&hcl.EvalContext{
  1338  				Variables: map[string]cty.Value{
  1339  					"nulllist": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1340  						"name": cty.String,
  1341  					}))),
  1342  				},
  1343  			},
  1344  			cty.DynamicVal,
  1345  			1, // splat cannot be applied to null sequence
  1346  		},
  1347  		{
  1348  			`["hello", "goodbye"].*`,
  1349  			nil,
  1350  			cty.TupleVal([]cty.Value{
  1351  				cty.StringVal("hello"),
  1352  				cty.StringVal("goodbye"),
  1353  			}),
  1354  			0,
  1355  		},
  1356  		{
  1357  			`"hello".*`,
  1358  			nil,
  1359  			cty.TupleVal([]cty.Value{
  1360  				cty.StringVal("hello"),
  1361  			}),
  1362  			0,
  1363  		},
  1364  		{
  1365  			`[["hello"], ["world", "unused"]].*.0`,
  1366  			nil,
  1367  			cty.TupleVal([]cty.Value{
  1368  				cty.StringVal("hello"),
  1369  				cty.StringVal("world"),
  1370  			}),
  1371  			0,
  1372  		},
  1373  		{
  1374  			`[[{name:"foo"}], [{name:"bar"}, {name:"baz"}]].*.0.name`,
  1375  			nil,
  1376  			cty.TupleVal([]cty.Value{
  1377  				cty.StringVal("foo"),
  1378  				cty.StringVal("bar"),
  1379  			}),
  1380  			0,
  1381  		},
  1382  		{
  1383  			`[[[{name:"foo"}]], [[{name:"bar"}], [{name:"baz"}]]].*.0.0.name`,
  1384  			nil,
  1385  			cty.TupleVal([]cty.Value{
  1386  				cty.DynamicVal,
  1387  				cty.DynamicVal,
  1388  			}),
  1389  			1, // can't chain legacy index syntax together, like .0.0 (because 0.0 parses as a single number)
  1390  		},
  1391  		{
  1392  			// For an "attribute-only" splat, an index operator applies to
  1393  			// the splat result as a whole, rather than being incorporated
  1394  			// into the splat traversal itself.
  1395  			`[{name: "Steve"}, {name: "Ermintrude"}].*.name[0]`,
  1396  			nil,
  1397  			cty.StringVal("Steve"),
  1398  			0,
  1399  		},
  1400  		{
  1401  			// For a "full" splat, an index operator is consumed as part
  1402  			// of the splat's traversal.
  1403  			`[{names: ["Steve"]}, {names: ["Ermintrude"]}][*].names[0]`,
  1404  			nil,
  1405  			cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
  1406  			0,
  1407  		},
  1408  		{
  1409  			// Another "full" splat, this time with the index first.
  1410  			`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][0].name`,
  1411  			nil,
  1412  			cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
  1413  			0,
  1414  		},
  1415  		{
  1416  			// Full splats can nest, which produces nested tuples.
  1417  			`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][*].name`,
  1418  			nil,
  1419  			cty.TupleVal([]cty.Value{
  1420  				cty.TupleVal([]cty.Value{cty.StringVal("Steve")}),
  1421  				cty.TupleVal([]cty.Value{cty.StringVal("Ermintrude")}),
  1422  			}),
  1423  			0,
  1424  		},
  1425  		{
  1426  			`[["hello"], ["goodbye"]].*.*`,
  1427  			nil,
  1428  			cty.TupleVal([]cty.Value{
  1429  				cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
  1430  				cty.TupleVal([]cty.Value{cty.StringVal("goodbye")}),
  1431  			}),
  1432  			1,
  1433  		},
  1434  		{ // splat with sensitive collection
  1435  			`maps.*.enabled`,
  1436  			&hcl.EvalContext{
  1437  				Variables: map[string]cty.Value{
  1438  					"maps": cty.ListVal([]cty.Value{
  1439  						cty.MapVal(map[string]cty.Value{"enabled": cty.True}),
  1440  						cty.MapVal(map[string]cty.Value{"enabled": cty.False}),
  1441  					}).Mark("sensitive"),
  1442  				},
  1443  			},
  1444  			cty.ListVal([]cty.Value{
  1445  				cty.True,
  1446  				cty.False,
  1447  			}).Mark("sensitive"),
  1448  			0,
  1449  		},
  1450  		{ // splat with sensitive collection that's unknown
  1451  			`maps.*.enabled`,
  1452  			&hcl.EvalContext{
  1453  				Variables: map[string]cty.Value{
  1454  					"maps": cty.UnknownVal(cty.List(cty.Map(cty.Bool))).Mark("sensitive"),
  1455  				},
  1456  			},
  1457  			cty.UnknownVal(cty.List(cty.Bool)).RefineNotNull().Mark("sensitive"),
  1458  			0,
  1459  		},
  1460  		{ // splat with sensitive collection that's unknown and not null
  1461  			`maps.*.enabled`,
  1462  			&hcl.EvalContext{
  1463  				Variables: map[string]cty.Value{
  1464  					"maps": cty.UnknownVal(cty.List(cty.Map(cty.Bool))).RefineNotNull().Mark("sensitive"),
  1465  				},
  1466  			},
  1467  			cty.UnknownVal(cty.List(cty.Bool)).RefineNotNull().Mark("sensitive"),
  1468  			0,
  1469  		},
  1470  		{ // splat with collection with sensitive elements
  1471  			`maps.*.x`,
  1472  			&hcl.EvalContext{
  1473  				Variables: map[string]cty.Value{
  1474  					"maps": cty.ListVal([]cty.Value{
  1475  						cty.MapVal(map[string]cty.Value{
  1476  							"x": cty.StringVal("foo").Mark("sensitive"),
  1477  						}),
  1478  						cty.MapVal(map[string]cty.Value{
  1479  							"x": cty.StringVal("bar"),
  1480  						}),
  1481  					}),
  1482  				},
  1483  			},
  1484  			cty.ListVal([]cty.Value{
  1485  				cty.StringVal("foo").Mark("sensitive"),
  1486  				cty.StringVal("bar"),
  1487  			}),
  1488  			0,
  1489  		},
  1490  		{
  1491  			`["hello"][0]`,
  1492  			nil,
  1493  			cty.StringVal("hello"),
  1494  			0,
  1495  		},
  1496  		{
  1497  			`["hello"].0`,
  1498  			nil,
  1499  			cty.StringVal("hello"),
  1500  			0,
  1501  		},
  1502  		{
  1503  			`[["hello"]].0.0`,
  1504  			nil,
  1505  			cty.DynamicVal,
  1506  			1, // can't chain legacy index syntax together (because 0.0 parses as 0)
  1507  		},
  1508  		{
  1509  			`[{greeting = "hello"}].0.greeting`,
  1510  			nil,
  1511  			cty.StringVal("hello"),
  1512  			0,
  1513  		},
  1514  		{
  1515  			`[][0]`,
  1516  			nil,
  1517  			cty.DynamicVal,
  1518  			1, // invalid index
  1519  		},
  1520  		{
  1521  			`["hello"][negate(0)]`,
  1522  			&hcl.EvalContext{
  1523  				Functions: map[string]function.Function{
  1524  					"negate": stdlib.NegateFunc,
  1525  				},
  1526  			},
  1527  			cty.StringVal("hello"),
  1528  			0,
  1529  		},
  1530  		{
  1531  			`[][negate(0)]`,
  1532  			&hcl.EvalContext{
  1533  				Functions: map[string]function.Function{
  1534  					"negate": stdlib.NegateFunc,
  1535  				},
  1536  			},
  1537  			cty.DynamicVal,
  1538  			1, // invalid index
  1539  		},
  1540  		{
  1541  			`["hello"]["0"]`, // key gets converted to number
  1542  			nil,
  1543  			cty.StringVal("hello"),
  1544  			0,
  1545  		},
  1546  		{
  1547  			`["boop"].foo[index]`, // index is a variable to force IndexExpr instead of traversal
  1548  			&hcl.EvalContext{
  1549  				Variables: map[string]cty.Value{
  1550  					"index": cty.NumberIntVal(0),
  1551  				},
  1552  			},
  1553  			cty.DynamicVal,
  1554  			1, // expression ["boop"] does not have attributes
  1555  		},
  1556  
  1557  		{
  1558  			`foo`,
  1559  			&hcl.EvalContext{
  1560  				Variables: map[string]cty.Value{
  1561  					"foo": cty.StringVal("hello"),
  1562  				},
  1563  			},
  1564  			cty.StringVal("hello"),
  1565  			0,
  1566  		},
  1567  		{
  1568  			`bar`,
  1569  			&hcl.EvalContext{},
  1570  			cty.DynamicVal,
  1571  			1, // variables not allowed here
  1572  		},
  1573  		{
  1574  			`foo.bar`,
  1575  			&hcl.EvalContext{
  1576  				Variables: map[string]cty.Value{
  1577  					"foo": cty.StringVal("hello"),
  1578  				},
  1579  			},
  1580  			cty.DynamicVal,
  1581  			1, // foo does not have attributes
  1582  		},
  1583  		{
  1584  			`foo.baz`,
  1585  			&hcl.EvalContext{
  1586  				Variables: map[string]cty.Value{
  1587  					"foo": cty.ObjectVal(map[string]cty.Value{
  1588  						"baz": cty.StringVal("hello"),
  1589  					}),
  1590  				},
  1591  			},
  1592  			cty.StringVal("hello"),
  1593  			0,
  1594  		},
  1595  		{
  1596  			`foo["baz"]`,
  1597  			&hcl.EvalContext{
  1598  				Variables: map[string]cty.Value{
  1599  					"foo": cty.ObjectVal(map[string]cty.Value{
  1600  						"baz": cty.StringVal("hello"),
  1601  					}),
  1602  				},
  1603  			},
  1604  			cty.StringVal("hello"),
  1605  			0,
  1606  		},
  1607  		{
  1608  			`foo[true]`, // key is converted to string
  1609  			&hcl.EvalContext{
  1610  				Variables: map[string]cty.Value{
  1611  					"foo": cty.ObjectVal(map[string]cty.Value{
  1612  						"true": cty.StringVal("hello"),
  1613  					}),
  1614  				},
  1615  			},
  1616  			cty.StringVal("hello"),
  1617  			0,
  1618  		},
  1619  		{
  1620  			`foo[0].baz`,
  1621  			&hcl.EvalContext{
  1622  				Variables: map[string]cty.Value{
  1623  					"foo": cty.ListVal([]cty.Value{
  1624  						cty.ObjectVal(map[string]cty.Value{
  1625  							"baz": cty.StringVal("hello"),
  1626  						}),
  1627  					}),
  1628  				},
  1629  			},
  1630  			cty.StringVal("hello"),
  1631  			0,
  1632  		},
  1633  
  1634  		{
  1635  			`
  1636  <<EOT
  1637  Foo
  1638  Bar
  1639  Baz
  1640  EOT
  1641  `,
  1642  			nil,
  1643  			cty.StringVal("Foo\nBar\nBaz\n"),
  1644  			0,
  1645  		},
  1646  		{
  1647  			`
  1648  <<EOT
  1649  Foo
  1650  ${bar}
  1651  Baz
  1652  EOT
  1653  `,
  1654  			&hcl.EvalContext{
  1655  				Variables: map[string]cty.Value{
  1656  					"bar": cty.StringVal("Bar"),
  1657  				},
  1658  			},
  1659  			cty.StringVal("Foo\nBar\nBaz\n"),
  1660  			0,
  1661  		},
  1662  		{
  1663  			`
  1664  <<EOT
  1665  Foo
  1666  %{for x in bars}${x}%{endfor}
  1667  Baz
  1668  EOT
  1669  `,
  1670  			&hcl.EvalContext{
  1671  				Variables: map[string]cty.Value{
  1672  					"bars": cty.ListVal([]cty.Value{
  1673  						cty.StringVal("Bar"),
  1674  						cty.StringVal("Bar"),
  1675  						cty.StringVal("Bar"),
  1676  					}),
  1677  				},
  1678  			},
  1679  			cty.StringVal("Foo\nBarBarBar\nBaz\n"),
  1680  			0,
  1681  		},
  1682  		{
  1683  			`[
  1684    <<EOT
  1685    Foo
  1686    Bar
  1687    Baz
  1688    EOT
  1689  ]
  1690  `,
  1691  			nil,
  1692  			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n  Bar\n  Baz\n")}),
  1693  			0,
  1694  		},
  1695  		{
  1696  			`[
  1697    <<-EOT
  1698    Foo
  1699    Bar
  1700    Baz
  1701    EOT
  1702  ]
  1703  `,
  1704  			nil,
  1705  			cty.TupleVal([]cty.Value{cty.StringVal("Foo\nBar\nBaz\n")}),
  1706  			0,
  1707  		},
  1708  		{
  1709  			`[
  1710    <<-EOT
  1711    Foo
  1712      Bar
  1713      Baz
  1714    EOT
  1715  ]
  1716  `,
  1717  			nil,
  1718  			cty.TupleVal([]cty.Value{cty.StringVal("Foo\n  Bar\n  Baz\n")}),
  1719  			0,
  1720  		},
  1721  		{
  1722  			`[
  1723    <<-EOT
  1724      Foo
  1725    Bar
  1726      Baz
  1727    EOT
  1728  ]
  1729  `,
  1730  			nil,
  1731  			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\nBar\n  Baz\n")}),
  1732  			0,
  1733  		},
  1734  		{
  1735  			`[
  1736    <<-EOT
  1737      Foo
  1738    ${bar}
  1739      Baz
  1740      EOT
  1741  ]
  1742  `,
  1743  			&hcl.EvalContext{
  1744  				Variables: map[string]cty.Value{
  1745  					"bar": cty.StringVal("  Bar"), // Spaces in the interpolation result don't affect the outcome
  1746  				},
  1747  			},
  1748  			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n  Bar\n  Baz\n")}),
  1749  			0,
  1750  		},
  1751  		{
  1752  			`[
  1753    <<EOT
  1754    Foo
  1755  
  1756    Bar
  1757  
  1758    Baz
  1759    EOT
  1760  ]
  1761  `,
  1762  			nil,
  1763  			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n\n  Bar\n\n  Baz\n")}),
  1764  			0,
  1765  		},
  1766  		{
  1767  			`[
  1768    <<-EOT
  1769    Foo
  1770  
  1771    Bar
  1772  
  1773    Baz
  1774    EOT
  1775  ]
  1776  `,
  1777  			nil,
  1778  			cty.TupleVal([]cty.Value{cty.StringVal("Foo\n\nBar\n\nBaz\n")}),
  1779  			0,
  1780  		},
  1781  
  1782  		{
  1783  			`unk["baz"]`,
  1784  			&hcl.EvalContext{
  1785  				Variables: map[string]cty.Value{
  1786  					"unk": cty.UnknownVal(cty.String),
  1787  				},
  1788  			},
  1789  			cty.DynamicVal,
  1790  			1, // value does not have indices (because we know it's a string)
  1791  		},
  1792  		{
  1793  			`unk["boop"]`,
  1794  			&hcl.EvalContext{
  1795  				Variables: map[string]cty.Value{
  1796  					"unk": cty.UnknownVal(cty.Map(cty.String)),
  1797  				},
  1798  			},
  1799  			cty.UnknownVal(cty.String), // we know it's a map of string
  1800  			0,
  1801  		},
  1802  		{
  1803  			`dyn["boop"]`,
  1804  			&hcl.EvalContext{
  1805  				Variables: map[string]cty.Value{
  1806  					"dyn": cty.DynamicVal,
  1807  				},
  1808  			},
  1809  			cty.DynamicVal, // don't know what it is yet
  1810  			0,
  1811  		},
  1812  		{
  1813  			`nullstr == "foo"`,
  1814  			&hcl.EvalContext{
  1815  				Variables: map[string]cty.Value{
  1816  					"nullstr": cty.NullVal(cty.String),
  1817  				},
  1818  			},
  1819  			cty.False,
  1820  			0,
  1821  		},
  1822  		{
  1823  			`nullstr == nullstr`,
  1824  			&hcl.EvalContext{
  1825  				Variables: map[string]cty.Value{
  1826  					"nullstr": cty.NullVal(cty.String),
  1827  				},
  1828  			},
  1829  			cty.True,
  1830  			0,
  1831  		},
  1832  		{
  1833  			`nullstr == null`,
  1834  			&hcl.EvalContext{
  1835  				Variables: map[string]cty.Value{
  1836  					"nullstr": cty.NullVal(cty.String),
  1837  				},
  1838  			},
  1839  			cty.True,
  1840  			0,
  1841  		},
  1842  		{
  1843  			`nullstr == nullnum`,
  1844  			&hcl.EvalContext{
  1845  				Variables: map[string]cty.Value{
  1846  					"nullstr": cty.NullVal(cty.String),
  1847  					"nullnum": cty.NullVal(cty.Number),
  1848  				},
  1849  			},
  1850  			cty.True,
  1851  			0,
  1852  		},
  1853  		{
  1854  			`"" == nulldyn`,
  1855  			&hcl.EvalContext{
  1856  				Variables: map[string]cty.Value{
  1857  					"nulldyn": cty.NullVal(cty.DynamicPseudoType),
  1858  				},
  1859  			},
  1860  			cty.False,
  1861  			0,
  1862  		},
  1863  		{
  1864  			`true ? var : null`,
  1865  			&hcl.EvalContext{
  1866  				Variables: map[string]cty.Value{
  1867  					"var": cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
  1868  				},
  1869  			},
  1870  			cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
  1871  			0,
  1872  		},
  1873  		{
  1874  			`true ? var : null`,
  1875  			&hcl.EvalContext{
  1876  				Variables: map[string]cty.Value{
  1877  					"var": cty.UnknownVal(cty.DynamicPseudoType),
  1878  				},
  1879  			},
  1880  			cty.UnknownVal(cty.DynamicPseudoType),
  1881  			0,
  1882  		},
  1883  		{
  1884  			`true ? ["a", "b"] : null`,
  1885  			nil,
  1886  			cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
  1887  			0,
  1888  		},
  1889  		{
  1890  			`true ? null: ["a", "b"]`,
  1891  			nil,
  1892  			cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
  1893  			0,
  1894  		},
  1895  		{
  1896  			`false ? ["a", "b"] : null`,
  1897  			nil,
  1898  			cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
  1899  			0,
  1900  		},
  1901  		{
  1902  			`false ? null: ["a", "b"]`,
  1903  			nil,
  1904  			cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
  1905  			0,
  1906  		},
  1907  		{
  1908  			`false ? null: null`,
  1909  			nil,
  1910  			cty.NullVal(cty.DynamicPseudoType),
  1911  			0,
  1912  		},
  1913  		{
  1914  			`false ? var: {a = "b"}`,
  1915  			&hcl.EvalContext{
  1916  				Variables: map[string]cty.Value{
  1917  					"var": cty.DynamicVal,
  1918  				},
  1919  			},
  1920  			cty.ObjectVal(map[string]cty.Value{
  1921  				"a": cty.StringVal("b"),
  1922  			}),
  1923  			0,
  1924  		},
  1925  		{
  1926  			`true ? ["a", "b"]: var`,
  1927  			&hcl.EvalContext{
  1928  				Variables: map[string]cty.Value{
  1929  					"var": cty.UnknownVal(cty.DynamicPseudoType),
  1930  				},
  1931  			},
  1932  			cty.TupleVal([]cty.Value{
  1933  				cty.StringVal("a"),
  1934  				cty.StringVal("b"),
  1935  			}),
  1936  			0,
  1937  		},
  1938  		{
  1939  			`false ? ["a", "b"]: var`,
  1940  			&hcl.EvalContext{
  1941  				Variables: map[string]cty.Value{
  1942  					"var": cty.DynamicVal,
  1943  				},
  1944  			},
  1945  			cty.DynamicVal,
  1946  			0,
  1947  		},
  1948  		{
  1949  			`false ? ["a", "b"]: var`,
  1950  			&hcl.EvalContext{
  1951  				Variables: map[string]cty.Value{
  1952  					"var": cty.UnknownVal(cty.DynamicPseudoType),
  1953  				},
  1954  			},
  1955  			cty.DynamicVal,
  1956  			0,
  1957  		},
  1958  		{
  1959  			`unknown ? 1 : 0`,
  1960  			&hcl.EvalContext{
  1961  				Variables: map[string]cty.Value{
  1962  					"unknown": cty.UnknownVal(cty.Bool),
  1963  				},
  1964  			},
  1965  			cty.UnknownVal(cty.Number).Refine().
  1966  				NotNull().
  1967  				NumberRangeLowerBound(cty.Zero, true).
  1968  				NumberRangeUpperBound(cty.NumberIntVal(1), true).
  1969  				NewValue(),
  1970  			0,
  1971  		},
  1972  		{
  1973  			`unknown ? 0 : 1`,
  1974  			&hcl.EvalContext{
  1975  				Variables: map[string]cty.Value{
  1976  					"unknown": cty.UnknownVal(cty.Bool),
  1977  				},
  1978  			},
  1979  			cty.UnknownVal(cty.Number).Refine().
  1980  				NotNull().
  1981  				NumberRangeLowerBound(cty.Zero, true).
  1982  				NumberRangeUpperBound(cty.NumberIntVal(1), true).
  1983  				NewValue(),
  1984  			0,
  1985  		},
  1986  		{
  1987  			`unknown ? i : j`,
  1988  			&hcl.EvalContext{
  1989  				Variables: map[string]cty.Value{
  1990  					"unknown": cty.UnknownVal(cty.Bool),
  1991  					"i":       cty.NullVal(cty.Number),
  1992  					"j":       cty.NullVal(cty.Number),
  1993  				},
  1994  			},
  1995  			cty.NullVal(cty.Number),
  1996  			0,
  1997  		},
  1998  		{
  1999  			`unknown ? im : jm`,
  2000  			&hcl.EvalContext{
  2001  				Variables: map[string]cty.Value{
  2002  					"unknown": cty.UnknownVal(cty.Bool),
  2003  					"im":      cty.NullVal(cty.Number).Mark("a"),
  2004  					"jm":      cty.NullVal(cty.Number).Mark("b"),
  2005  				},
  2006  			},
  2007  			cty.NullVal(cty.Number).Mark("a").Mark("b"),
  2008  			0,
  2009  		},
  2010  		{
  2011  			`unknown ? im : jm`,
  2012  			&hcl.EvalContext{
  2013  				Variables: map[string]cty.Value{
  2014  					"unknown": cty.UnknownVal(cty.Bool).Mark("a"),
  2015  					"im":      cty.UnknownVal(cty.Number),
  2016  					"jm":      cty.UnknownVal(cty.Number).Mark("b"),
  2017  				},
  2018  			},
  2019  			// the empty refinement may eventually be removed, but does nothing here
  2020  			cty.UnknownVal(cty.Number).Refine().NewValue().Mark("a").Mark("b"),
  2021  			0,
  2022  		},
  2023  		{
  2024  			`unknown ? ix : jx`,
  2025  			&hcl.EvalContext{
  2026  				Variables: map[string]cty.Value{
  2027  					"unknown": cty.UnknownVal(cty.Bool),
  2028  					"ix":      cty.UnknownVal(cty.Number),
  2029  					"jx":      cty.UnknownVal(cty.Number),
  2030  				},
  2031  			},
  2032  			// the empty refinement may eventually be removed, but does nothing here
  2033  			cty.UnknownVal(cty.Number).Refine().NewValue(),
  2034  			0,
  2035  		},
  2036  		{
  2037  			`unknown ? ir : jr`,
  2038  			&hcl.EvalContext{
  2039  				Variables: map[string]cty.Value{
  2040  					"unknown": cty.UnknownVal(cty.Bool),
  2041  					"ir": cty.UnknownVal(cty.Number).Refine().
  2042  						NumberRangeLowerBound(cty.NumberIntVal(1), false).
  2043  						NumberRangeUpperBound(cty.NumberIntVal(3), false).NewValue(),
  2044  					"jr": cty.UnknownVal(cty.Number).Refine().
  2045  						NumberRangeLowerBound(cty.NumberIntVal(2), true).
  2046  						NumberRangeUpperBound(cty.NumberIntVal(4), true).NewValue(),
  2047  				},
  2048  			},
  2049  			cty.UnknownVal(cty.Number).Refine().
  2050  				NumberRangeLowerBound(cty.NumberIntVal(1), false).
  2051  				NumberRangeUpperBound(cty.NumberIntVal(4), true).NewValue(),
  2052  			0,
  2053  		},
  2054  		{
  2055  			`unknown ? a : b`,
  2056  			&hcl.EvalContext{
  2057  				Variables: map[string]cty.Value{
  2058  					"unknown": cty.UnknownVal(cty.Bool),
  2059  					"a":       cty.UnknownVal(cty.Bool).RefineNotNull(),
  2060  					"b":       cty.UnknownVal(cty.Bool).RefineNotNull(),
  2061  				},
  2062  			},
  2063  			cty.UnknownVal(cty.Bool).RefineNotNull(),
  2064  			0,
  2065  		},
  2066  		{
  2067  			`unknown ? al : bl`,
  2068  			&hcl.EvalContext{
  2069  				Variables: map[string]cty.Value{
  2070  					"unknown": cty.UnknownVal(cty.Bool),
  2071  					"al":      cty.ListValEmpty(cty.String),
  2072  					"bl":      cty.ListValEmpty(cty.String),
  2073  				},
  2074  			},
  2075  			cty.ListValEmpty(cty.String), // deduced through refinements
  2076  			0,
  2077  		},
  2078  		{
  2079  			`unknown ? am : bm`,
  2080  			&hcl.EvalContext{
  2081  				Variables: map[string]cty.Value{
  2082  					"unknown": cty.UnknownVal(cty.Bool),
  2083  					"am":      cty.MapValEmpty(cty.String),
  2084  					"bm":      cty.MapValEmpty(cty.String).Mark("test"),
  2085  				},
  2086  			},
  2087  			cty.MapValEmpty(cty.String).Mark("test"), // deduced through refinements
  2088  			0,
  2089  		},
  2090  		{
  2091  			`unknown ? ar : br`,
  2092  			&hcl.EvalContext{
  2093  				Variables: map[string]cty.Value{
  2094  					"unknown": cty.UnknownVal(cty.Bool),
  2095  					"ar": cty.UnknownVal(cty.Set(cty.String)).Refine().
  2096  						CollectionLengthLowerBound(1).CollectionLengthUpperBound(3).NewValue(),
  2097  					"br": cty.UnknownVal(cty.Set(cty.String)).Refine().
  2098  						CollectionLengthLowerBound(2).CollectionLengthUpperBound(4).NewValue(),
  2099  				},
  2100  			},
  2101  			cty.UnknownVal(cty.Set(cty.String)).Refine().CollectionLengthLowerBound(1).CollectionLengthUpperBound(4).NewValue(), // deduced through refinements
  2102  			0,
  2103  		},
  2104  		{
  2105  			`unknown ? arn : brn`,
  2106  			&hcl.EvalContext{
  2107  				Variables: map[string]cty.Value{
  2108  					"unknown": cty.UnknownVal(cty.Bool),
  2109  					"arn": cty.UnknownVal(cty.Set(cty.String)).Refine().NotNull().
  2110  						CollectionLengthLowerBound(1).CollectionLengthUpperBound(2).NewValue(),
  2111  					"brn": cty.UnknownVal(cty.Set(cty.String)).Refine().NotNull().
  2112  						CollectionLengthLowerBound(3).CollectionLengthUpperBound(4).NewValue(),
  2113  				},
  2114  			},
  2115  			cty.UnknownVal(cty.Set(cty.String)).Refine().NotNull().CollectionLengthLowerBound(1).CollectionLengthUpperBound(4).NewValue(), // deduced through refinements
  2116  			0,
  2117  		},
  2118  		{
  2119  			`unknown ? amr : bmr`,
  2120  			&hcl.EvalContext{
  2121  				Variables: map[string]cty.Value{
  2122  					"unknown": cty.UnknownVal(cty.Bool),
  2123  					"amr": cty.UnknownVal(cty.Set(cty.String)).Mark("test").Refine().
  2124  						CollectionLengthLowerBound(1).CollectionLengthUpperBound(2).NewValue(),
  2125  					"bmr": cty.UnknownVal(cty.Set(cty.String)).Mark("test").Refine().
  2126  						CollectionLengthLowerBound(3).CollectionLengthUpperBound(4).NewValue(),
  2127  				},
  2128  			},
  2129  			cty.UnknownVal(cty.Set(cty.String)).Refine().CollectionLengthLowerBound(1).CollectionLengthUpperBound(4).NewValue().Mark("test"), // deduced through refinements
  2130  			0,
  2131  		},
  2132  		{
  2133  			`unknown ? a : b`,
  2134  			&hcl.EvalContext{
  2135  				Variables: map[string]cty.Value{
  2136  					"unknown": cty.UnknownVal(cty.Bool),
  2137  					"a":       cty.ListValEmpty(cty.String),
  2138  					"b":       cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
  2139  				},
  2140  			},
  2141  			cty.UnknownVal(cty.List(cty.String)).Refine().
  2142  				NotNull().
  2143  				CollectionLengthUpperBound(1).
  2144  				NewValue(),
  2145  			0,
  2146  		},
  2147  		{
  2148  			`unknown ? a : b`,
  2149  			&hcl.EvalContext{
  2150  				Variables: map[string]cty.Value{
  2151  					"unknown": cty.UnknownVal(cty.Bool),
  2152  					"a":       cty.ListVal([]cty.Value{cty.StringVal("hello")}),
  2153  					"b":       cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
  2154  				},
  2155  			},
  2156  			cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}), // deduced through refinements
  2157  			0,
  2158  		},
  2159  		{ // marked conditional
  2160  			`var.foo ? 1 : 0`,
  2161  			&hcl.EvalContext{
  2162  				Variables: map[string]cty.Value{
  2163  					"var": cty.ObjectVal(map[string]cty.Value{
  2164  						"foo": cty.BoolVal(true),
  2165  					}).Mark("sensitive"),
  2166  				},
  2167  			},
  2168  			cty.NumberIntVal(1),
  2169  			0,
  2170  		},
  2171  		{ // auto-converts collection types
  2172  			`true ? listOf1Tuple : listOf0Tuple`,
  2173  			&hcl.EvalContext{
  2174  				Variables: map[string]cty.Value{
  2175  					"listOf1Tuple": cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True})}),
  2176  					"listOf0Tuple": cty.ListVal([]cty.Value{cty.EmptyTupleVal}),
  2177  				},
  2178  			},
  2179  			cty.ListVal([]cty.Value{cty.ListVal([]cty.Value{cty.True})}),
  2180  			0,
  2181  		},
  2182  		{
  2183  			`true ? setOf1Tuple : setOf0Tuple`,
  2184  			&hcl.EvalContext{
  2185  				Variables: map[string]cty.Value{
  2186  					"setOf1Tuple": cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True})}),
  2187  					"setOf0Tuple": cty.SetVal([]cty.Value{cty.EmptyTupleVal}),
  2188  				},
  2189  			},
  2190  			cty.SetVal([]cty.Value{cty.ListVal([]cty.Value{cty.True})}),
  2191  			0,
  2192  		},
  2193  		{ // marked argument expansion
  2194  			`min(xs...)`,
  2195  			&hcl.EvalContext{
  2196  				Functions: map[string]function.Function{
  2197  					"min": stdlib.MinFunc,
  2198  				},
  2199  				Variables: map[string]cty.Value{
  2200  					"xs": cty.ListVal([]cty.Value{
  2201  						cty.NumberIntVal(3),
  2202  						cty.NumberIntVal(1),
  2203  						cty.NumberIntVal(4),
  2204  					}).Mark("sensitive"),
  2205  				},
  2206  			},
  2207  			cty.NumberIntVal(1).Mark("sensitive"),
  2208  			0,
  2209  		},
  2210  		{
  2211  			`test ? sensitiveString : ""`,
  2212  			&hcl.EvalContext{
  2213  				Functions: map[string]function.Function{},
  2214  				Variables: map[string]cty.Value{
  2215  					"test":            cty.UnknownVal(cty.Bool),
  2216  					"sensitiveString": cty.StringVal("test").Mark("sensitive"),
  2217  				},
  2218  			},
  2219  			cty.UnknownVal(cty.String).RefineNotNull().Mark("sensitive"),
  2220  			0,
  2221  		},
  2222  	}
  2223  
  2224  	for _, test := range tests {
  2225  		t.Run(test.input, func(t *testing.T) {
  2226  			expr, parseDiags := ParseExpression([]byte(test.input), "", hcl.Pos{Line: 1, Column: 1, Byte: 0})
  2227  			var got cty.Value
  2228  			var valDiags hcl.Diagnostics
  2229  
  2230  			if expr != nil {
  2231  				got, valDiags = expr.Value(test.ctx)
  2232  			}
  2233  
  2234  			diagCount := len(parseDiags) + len(valDiags)
  2235  
  2236  			if diagCount != test.diagCount {
  2237  				t.Errorf("wrong number of diagnostics %d; want %d", diagCount, test.diagCount)
  2238  				for _, diag := range parseDiags {
  2239  					t.Logf(" - %s", diag.Error())
  2240  				}
  2241  				for _, diag := range valDiags {
  2242  					t.Logf(" - %s", diag.Error())
  2243  				}
  2244  			}
  2245  
  2246  			if !got.RawEquals(test.want) {
  2247  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.want)
  2248  			}
  2249  		})
  2250  	}
  2251  
  2252  }
  2253  
  2254  func TestExpressionErrorMessages(t *testing.T) {
  2255  	tests := []struct {
  2256  		input       string
  2257  		ctx         *hcl.EvalContext
  2258  		wantSummary string
  2259  		wantDetail  string
  2260  	}{
  2261  		// Error messages describing inconsistent result types for conditional expressions.
  2262  		{
  2263  			"true ? 1 : true",
  2264  			nil,
  2265  			"Inconsistent conditional result types",
  2266  			"The true and false result expressions must have consistent types. The 'true' value is number, but the 'false' value is bool.",
  2267  		},
  2268  		{
  2269  			"true ? [1] : [true]",
  2270  			nil,
  2271  			"Inconsistent conditional result types",
  2272  			"The true and false result expressions must have consistent types. Type mismatch for tuple element 0: The 'true' value is number, but the 'false' value is bool.",
  2273  		},
  2274  		{
  2275  			"true ? [1] : [1, true]",
  2276  			nil,
  2277  			"Inconsistent conditional result types",
  2278  			"The true and false result expressions must have consistent types. The 'true' tuple has length 1, but the 'false' tuple has length 2.",
  2279  		},
  2280  		{
  2281  			"true ? { a = 1 } : { a = true }",
  2282  			nil,
  2283  			"Inconsistent conditional result types",
  2284  			"The true and false result expressions must have consistent types. Type mismatch for object attribute \"a\": The 'true' value is number, but the 'false' value is bool.",
  2285  		},
  2286  		{
  2287  			"true ? { a = true, b = 1 } : { a = true }",
  2288  			nil,
  2289  			"Inconsistent conditional result types",
  2290  			"The true and false result expressions must have consistent types. The 'true' value includes object attribute \"b\", which is absent in the 'false' value.",
  2291  		},
  2292  		{
  2293  			"true ? { a = true } : { a = true, b = 1 }",
  2294  			nil,
  2295  			"Inconsistent conditional result types",
  2296  			"The true and false result expressions must have consistent types. The 'false' value includes object attribute \"b\", which is absent in the 'true' value.",
  2297  		},
  2298  		{
  2299  			// Failing cases for automatic collection conversions. HCL and cty
  2300  			// will attempt to unify tuples into lists. We have to make sure
  2301  			// the tuple inner types have no common base type, so we mix and
  2302  			// match booleans and numbers and validate the error messages.
  2303  			"true ? listOf2Tuple : listOf1Tuple",
  2304  			&hcl.EvalContext{
  2305  				Variables: map[string]cty.Value{
  2306  					"listOf2Tuple": cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True, cty.Zero})}),
  2307  					"listOf1Tuple": cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True})}),
  2308  				},
  2309  			},
  2310  			"Inconsistent conditional result types",
  2311  			"The true and false result expressions must have consistent types. Mismatched list element types: The 'true' tuple has length 2, but the 'false' tuple has length 1.",
  2312  		},
  2313  		{
  2314  			"true ? setOf2Tuple : setOf1Tuple",
  2315  			&hcl.EvalContext{
  2316  				Variables: map[string]cty.Value{
  2317  					"setOf2Tuple": cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True, cty.Zero})}),
  2318  					"setOf1Tuple": cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True})}),
  2319  				},
  2320  			},
  2321  			"Inconsistent conditional result types",
  2322  			"The true and false result expressions must have consistent types. Mismatched set element types: The 'true' tuple has length 2, but the 'false' tuple has length 1.",
  2323  		},
  2324  		{
  2325  			"true ? mapOf1Tuple : mapOf2Tuple",
  2326  			&hcl.EvalContext{
  2327  				Variables: map[string]cty.Value{
  2328  					"mapOf1Tuple": cty.MapVal(map[string]cty.Value{"a": cty.TupleVal([]cty.Value{cty.True})}),
  2329  					"mapOf2Tuple": cty.MapVal(map[string]cty.Value{"a": cty.TupleVal([]cty.Value{cty.True, cty.Zero})}),
  2330  				},
  2331  			},
  2332  			"Inconsistent conditional result types",
  2333  			"The true and false result expressions must have consistent types. Mismatched map element types: The 'true' tuple has length 1, but the 'false' tuple has length 2.",
  2334  		},
  2335  		{
  2336  			"true ? listOfListOf2Tuple : listOfListOf1Tuple",
  2337  			&hcl.EvalContext{
  2338  				Variables: map[string]cty.Value{
  2339  					"listOfListOf2Tuple": cty.ListVal([]cty.Value{cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True, cty.Zero})})}),
  2340  					"listOfListOf1Tuple": cty.ListVal([]cty.Value{cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.True})})}),
  2341  				},
  2342  			},
  2343  			"Inconsistent conditional result types",
  2344  			// This is our totally non-specific last-resort of an error message,
  2345  			// for situations that are too complex for any of our rules to
  2346  			// describe coherently.
  2347  			"The true and false result expressions must have consistent types. At least one deeply-nested attribute or element is not compatible across both the 'true' and the 'false' value.",
  2348  		},
  2349  	}
  2350  
  2351  	for _, test := range tests {
  2352  		t.Run(test.input, func(t *testing.T) {
  2353  			var diags hcl.Diagnostics
  2354  			expr, parseDiags := ParseExpression([]byte(test.input), "", hcl.Pos{Line: 1, Column: 1, Byte: 0})
  2355  			diags = append(diags, parseDiags...)
  2356  			_, valDiags := expr.Value(test.ctx)
  2357  			diags = append(diags, valDiags...)
  2358  
  2359  			if !diags.HasErrors() {
  2360  				t.Fatalf("unexpected success\nwant error:\n%s; %s", test.wantSummary, test.wantDetail)
  2361  			}
  2362  
  2363  			for _, diag := range diags {
  2364  				if diag.Severity != hcl.DiagError {
  2365  					continue
  2366  				}
  2367  				if diag.Summary == test.wantSummary && diag.Detail == test.wantDetail {
  2368  					// Success! We'll return early to conclude this test case.
  2369  					return
  2370  				}
  2371  			}
  2372  			// If we fall out here then we didn't find the diagnostic
  2373  			// we were looking for.
  2374  			t.Fatalf("missing expected error\ngot:\n%s\n\nwant error:\n%s; %s", diags.Error(), test.wantSummary, test.wantDetail)
  2375  		})
  2376  	}
  2377  }
  2378  
  2379  func TestFunctionCallExprValue(t *testing.T) {
  2380  	funcs := map[string]function.Function{
  2381  		"length":     stdlib.StrlenFunc,
  2382  		"jsondecode": stdlib.JSONDecodeFunc,
  2383  	}
  2384  
  2385  	tests := map[string]struct {
  2386  		expr      *FunctionCallExpr
  2387  		ctx       *hcl.EvalContext
  2388  		want      cty.Value
  2389  		diagCount int
  2390  	}{
  2391  		"valid call with no conversions": {
  2392  			&FunctionCallExpr{
  2393  				Name: "length",
  2394  				Args: []Expression{
  2395  					&LiteralValueExpr{
  2396  						Val: cty.StringVal("hello"),
  2397  					},
  2398  				},
  2399  			},
  2400  			&hcl.EvalContext{
  2401  				Functions: funcs,
  2402  			},
  2403  			cty.NumberIntVal(5),
  2404  			0,
  2405  		},
  2406  		"valid call with arg conversion": {
  2407  			&FunctionCallExpr{
  2408  				Name: "length",
  2409  				Args: []Expression{
  2410  					&LiteralValueExpr{
  2411  						Val: cty.BoolVal(true),
  2412  					},
  2413  				},
  2414  			},
  2415  			&hcl.EvalContext{
  2416  				Functions: funcs,
  2417  			},
  2418  			cty.NumberIntVal(4), // length of string "true"
  2419  			0,
  2420  		},
  2421  		"valid call with unknown arg": {
  2422  			&FunctionCallExpr{
  2423  				Name: "length",
  2424  				Args: []Expression{
  2425  					&LiteralValueExpr{
  2426  						Val: cty.UnknownVal(cty.String),
  2427  					},
  2428  				},
  2429  			},
  2430  			&hcl.EvalContext{
  2431  				Functions: funcs,
  2432  			},
  2433  			cty.UnknownVal(cty.Number).Refine().NotNull().NumberRangeLowerBound(cty.NumberIntVal(0), true).NewValue(),
  2434  			0,
  2435  		},
  2436  		"valid call with unknown arg needing conversion": {
  2437  			&FunctionCallExpr{
  2438  				Name: "length",
  2439  				Args: []Expression{
  2440  					&LiteralValueExpr{
  2441  						Val: cty.UnknownVal(cty.Bool),
  2442  					},
  2443  				},
  2444  			},
  2445  			&hcl.EvalContext{
  2446  				Functions: funcs,
  2447  			},
  2448  			cty.UnknownVal(cty.Number).Refine().NotNull().NumberRangeLowerBound(cty.NumberIntVal(0), true).NewValue(),
  2449  			0,
  2450  		},
  2451  		"valid call with dynamic arg": {
  2452  			&FunctionCallExpr{
  2453  				Name: "length",
  2454  				Args: []Expression{
  2455  					&LiteralValueExpr{
  2456  						Val: cty.DynamicVal,
  2457  					},
  2458  				},
  2459  			},
  2460  			&hcl.EvalContext{
  2461  				Functions: funcs,
  2462  			},
  2463  			cty.UnknownVal(cty.Number).Refine().NotNull().NumberRangeLowerBound(cty.NumberIntVal(0), true).NewValue(),
  2464  			0,
  2465  		},
  2466  		"invalid arg type": {
  2467  			&FunctionCallExpr{
  2468  				Name: "length",
  2469  				Args: []Expression{
  2470  					&LiteralValueExpr{
  2471  						Val: cty.ListVal([]cty.Value{cty.StringVal("hello")}),
  2472  					},
  2473  				},
  2474  			},
  2475  			&hcl.EvalContext{
  2476  				Functions: funcs,
  2477  			},
  2478  			cty.DynamicVal,
  2479  			1,
  2480  		},
  2481  		"function with dynamic return type": {
  2482  			&FunctionCallExpr{
  2483  				Name: "jsondecode",
  2484  				Args: []Expression{
  2485  					&LiteralValueExpr{
  2486  						Val: cty.StringVal(`"hello"`),
  2487  					},
  2488  				},
  2489  			},
  2490  			&hcl.EvalContext{
  2491  				Functions: funcs,
  2492  			},
  2493  			cty.StringVal("hello"),
  2494  			0,
  2495  		},
  2496  		"function with dynamic return type unknown arg": {
  2497  			&FunctionCallExpr{
  2498  				Name: "jsondecode",
  2499  				Args: []Expression{
  2500  					&LiteralValueExpr{
  2501  						Val: cty.UnknownVal(cty.String),
  2502  					},
  2503  				},
  2504  			},
  2505  			&hcl.EvalContext{
  2506  				Functions: funcs,
  2507  			},
  2508  			cty.DynamicVal, // type depends on arg value
  2509  			0,
  2510  		},
  2511  		"error in function": {
  2512  			&FunctionCallExpr{
  2513  				Name: "jsondecode",
  2514  				Args: []Expression{
  2515  					&LiteralValueExpr{
  2516  						Val: cty.StringVal("invalid-json"),
  2517  					},
  2518  				},
  2519  			},
  2520  			&hcl.EvalContext{
  2521  				Functions: funcs,
  2522  			},
  2523  			cty.DynamicVal,
  2524  			1, // JSON parse error
  2525  		},
  2526  		"unknown function": {
  2527  			&FunctionCallExpr{
  2528  				Name: "lenth",
  2529  				Args: []Expression{},
  2530  			},
  2531  			&hcl.EvalContext{
  2532  				Functions: funcs,
  2533  			},
  2534  			cty.DynamicVal,
  2535  			1,
  2536  		},
  2537  	}
  2538  
  2539  	for name, test := range tests {
  2540  		t.Run(name, func(t *testing.T) {
  2541  			got, diags := test.expr.Value(test.ctx)
  2542  
  2543  			if len(diags) != test.diagCount {
  2544  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
  2545  				for _, diag := range diags {
  2546  					t.Logf(" - %s", diag.Error())
  2547  				}
  2548  			}
  2549  
  2550  			if !got.RawEquals(test.want) {
  2551  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.want)
  2552  			}
  2553  		})
  2554  	}
  2555  }
  2556  
  2557  func TestExpressionAsTraversal(t *testing.T) {
  2558  	expr, _ := ParseExpression([]byte("a.b[0][\"c\"]"), "", hcl.Pos{})
  2559  	traversal, diags := hcl.AbsTraversalForExpr(expr)
  2560  	if len(diags) != 0 {
  2561  		t.Fatalf("unexpected diagnostics:\n%s", diags.Error())
  2562  	}
  2563  	if len(traversal) != 4 {
  2564  		t.Fatalf("wrong traversal %#v; want length 3", traversal)
  2565  	}
  2566  	if traversal.RootName() != "a" {
  2567  		t.Errorf("wrong root name %q; want %q", traversal.RootName(), "a")
  2568  	}
  2569  	if step, ok := traversal[1].(hcl.TraverseAttr); ok {
  2570  		if got, want := step.Name, "b"; got != want {
  2571  			t.Errorf("wrong name %q for step 1; want %q", got, want)
  2572  		}
  2573  	} else {
  2574  		t.Errorf("wrong type %T for step 1; want %T", traversal[1], step)
  2575  	}
  2576  	if step, ok := traversal[2].(hcl.TraverseIndex); ok {
  2577  		if got, want := step.Key, cty.Zero; !want.RawEquals(got) {
  2578  			t.Errorf("wrong name %#v for step 2; want %#v", got, want)
  2579  		}
  2580  	} else {
  2581  		t.Errorf("wrong type %T for step 2; want %T", traversal[2], step)
  2582  	}
  2583  	if step, ok := traversal[3].(hcl.TraverseIndex); ok {
  2584  		if got, want := step.Key, cty.StringVal("c"); !want.RawEquals(got) {
  2585  			t.Errorf("wrong name %#v for step 3; want %#v", got, want)
  2586  		}
  2587  	} else {
  2588  		t.Errorf("wrong type %T for step 3; want %T", traversal[3], step)
  2589  	}
  2590  }
  2591  
  2592  func TestStaticExpressionList(t *testing.T) {
  2593  	expr, _ := ParseExpression([]byte("[0, a, true]"), "", hcl.Pos{})
  2594  	exprs, diags := hcl.ExprList(expr)
  2595  	if len(diags) != 0 {
  2596  		t.Fatalf("unexpected diagnostics:\n%s", diags.Error())
  2597  	}
  2598  	if len(exprs) != 3 {
  2599  		t.Fatalf("wrong result %#v; want length 3", exprs)
  2600  	}
  2601  	first, ok := exprs[0].(*LiteralValueExpr)
  2602  	if !ok {
  2603  		t.Fatalf("first expr has wrong type %T; want *hclsyntax.LiteralValueExpr", exprs[0])
  2604  	}
  2605  	if !first.Val.RawEquals(cty.Zero) {
  2606  		t.Fatalf("wrong first value %#v; want cty.Zero", first.Val)
  2607  	}
  2608  }
  2609  
  2610  // Check that function call w/ incomplete argument still reports correct range
  2611  func TestParseExpression_incompleteFunctionCall(t *testing.T) {
  2612  	tests := []struct {
  2613  		cfg           string
  2614  		expectedRange hcl.Range
  2615  	}{
  2616  		{
  2617  			`object({ foo = })`,
  2618  			hcl.Range{
  2619  				Filename: "test.hcl",
  2620  				Start:    hcl.InitialPos,
  2621  				End:      hcl.Pos{Line: 1, Column: 18, Byte: 17},
  2622  			},
  2623  		},
  2624  		{
  2625  			`object({
  2626    foo =
  2627  })`,
  2628  			hcl.Range{
  2629  				Filename: "test.hcl",
  2630  				Start:    hcl.InitialPos,
  2631  				End:      hcl.Pos{Line: 3, Column: 3, Byte: 19},
  2632  			},
  2633  		},
  2634  		{
  2635  			`object({ foo = }`,
  2636  			hcl.Range{
  2637  				Filename: "test.hcl",
  2638  				Start:    hcl.InitialPos,
  2639  				End:      hcl.Pos{Line: 0, Column: 0, Byte: 0},
  2640  			},
  2641  		},
  2642  		{
  2643  			`object({
  2644    foo =
  2645  }`,
  2646  			hcl.Range{
  2647  				Filename: "test.hcl",
  2648  				Start:    hcl.InitialPos,
  2649  				End:      hcl.Pos{Line: 0, Column: 0, Byte: 0},
  2650  			},
  2651  		},
  2652  		{
  2653  			`object({
  2654    foo =
  2655  `,
  2656  			hcl.Range{
  2657  				Filename: "test.hcl",
  2658  				Start:    hcl.InitialPos,
  2659  				End:      hcl.Pos{Line: 0, Column: 0, Byte: 0},
  2660  			},
  2661  		},
  2662  		{
  2663  			`object({
  2664    foo =
  2665  )`,
  2666  			hcl.Range{
  2667  				Filename: "test.hcl",
  2668  				Start:    hcl.InitialPos,
  2669  				End:      hcl.Pos{Line: 0, Column: 0, Byte: 0},
  2670  			},
  2671  		},
  2672  	}
  2673  
  2674  	for i, tc := range tests {
  2675  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  2676  			expr, _ := ParseExpression([]byte(tc.cfg), "test.hcl", hcl.InitialPos)
  2677  			if diff := cmp.Diff(tc.expectedRange, expr.Range()); diff != "" {
  2678  				t.Fatalf("range mismatch: %s", diff)
  2679  			}
  2680  		})
  2681  	}
  2682  }