1package hclsyntax
2
3import (
4	"testing"
5
6	"github.com/hashicorp/hcl/v2"
7	"github.com/zclconf/go-cty/cty"
8	"github.com/zclconf/go-cty/cty/function"
9	"github.com/zclconf/go-cty/cty/function/stdlib"
10)
11
12func TestExpressionParseAndValue(t *testing.T) {
13	// This is a combo test that exercises both the parser and the Value
14	// method, with the focus on the latter but indirectly testing the former.
15	tests := []struct {
16		input     string
17		ctx       *hcl.EvalContext
18		want      cty.Value
19		diagCount int
20	}{
21		{
22			`1`,
23			nil,
24			cty.NumberIntVal(1),
25			0,
26		},
27		{
28			`(1)`,
29			nil,
30			cty.NumberIntVal(1),
31			0,
32		},
33		{
34			`(2+3)`,
35			nil,
36			cty.NumberIntVal(5),
37			0,
38		},
39		{
40			`2*5+1`,
41			nil,
42			cty.NumberIntVal(11),
43			0,
44		},
45		{
46			`9%8`,
47			nil,
48			cty.NumberIntVal(1),
49			0,
50		},
51		{
52			`(2+unk)`,
53			&hcl.EvalContext{
54				Variables: map[string]cty.Value{
55					"unk": cty.UnknownVal(cty.Number),
56				},
57			},
58			cty.UnknownVal(cty.Number),
59			0,
60		},
61		{
62			`(2+unk)`,
63			&hcl.EvalContext{
64				Variables: map[string]cty.Value{
65					"unk": cty.DynamicVal,
66				},
67			},
68			cty.UnknownVal(cty.Number),
69			0,
70		},
71		{
72			`(unk+unk)`,
73			&hcl.EvalContext{
74				Variables: map[string]cty.Value{
75					"unk": cty.DynamicVal,
76				},
77			},
78			cty.UnknownVal(cty.Number),
79			0,
80		},
81		{
82			`(2+true)`,
83			nil,
84			cty.UnknownVal(cty.Number),
85			1, // unsuitable type for right operand
86		},
87		{
88			`(false+true)`,
89			nil,
90			cty.UnknownVal(cty.Number),
91			2, // unsuitable type for each operand
92		},
93		{
94			`(5 == 5)`,
95			nil,
96			cty.True,
97			0,
98		},
99		{
100			`(5 == 4)`,
101			nil,
102			cty.False,
103			0,
104		},
105		{
106			`(1 == true)`,
107			nil,
108			cty.False,
109			0,
110		},
111		{
112			`("true" == true)`,
113			nil,
114			cty.False,
115			0,
116		},
117		{
118			`(true == "true")`,
119			nil,
120			cty.False,
121			0,
122		},
123		{
124			`(true != "true")`,
125			nil,
126			cty.True,
127			0,
128		},
129		{
130			`(- 2)`,
131			nil,
132			cty.NumberIntVal(-2),
133			0,
134		},
135		{
136			`(! true)`,
137			nil,
138			cty.False,
139			0,
140		},
141		{
142			`(
143    1
144)`,
145			nil,
146			cty.NumberIntVal(1),
147			0,
148		},
149		{
150			`(1`,
151			nil,
152			cty.NumberIntVal(1),
153			1, // Unbalanced parentheses
154		},
155		{
156			`true`,
157			nil,
158			cty.True,
159			0,
160		},
161		{
162			`false`,
163			nil,
164			cty.False,
165			0,
166		},
167		{
168			`null`,
169			nil,
170			cty.NullVal(cty.DynamicPseudoType),
171			0,
172		},
173		{
174			`true true`,
175			nil,
176			cty.True,
177			1, // extra characters after expression
178		},
179		{
180			`"hello"`,
181			nil,
182			cty.StringVal("hello"),
183			0,
184		},
185		{
186			"\"hello `backtick` world\"",
187			nil,
188			cty.StringVal("hello `backtick` world"),
189			0,
190		},
191		{
192			`"hello\nworld"`,
193			nil,
194			cty.StringVal("hello\nworld"),
195			0,
196		},
197		{
198			`"unclosed`,
199			nil,
200			cty.StringVal("unclosed"),
201			1, // Unterminated template string
202		},
203		{
204			`"hello ${"world"}"`,
205			nil,
206			cty.StringVal("hello world"),
207			0,
208		},
209		{
210			`"hello ${12.5}"`,
211			nil,
212			cty.StringVal("hello 12.5"),
213			0,
214		},
215		{
216			`"silly ${"${"nesting"}"}"`,
217			nil,
218			cty.StringVal("silly nesting"),
219			0,
220		},
221		{
222			`"silly ${"${true}"}"`,
223			nil,
224			cty.StringVal("silly true"),
225			0,
226		},
227		{
228			`"hello $${escaped}"`,
229			nil,
230			cty.StringVal("hello ${escaped}"),
231			0,
232		},
233		{
234			`"hello $$nonescape"`,
235			nil,
236			cty.StringVal("hello $$nonescape"),
237			0,
238		},
239		{
240			`"$"`,
241			nil,
242			cty.StringVal("$"),
243			0,
244		},
245		{
246			`"%"`,
247			nil,
248			cty.StringVal("%"),
249			0,
250		},
251		{
252			`upper("foo")`,
253			&hcl.EvalContext{
254				Functions: map[string]function.Function{
255					"upper": stdlib.UpperFunc,
256				},
257			},
258			cty.StringVal("FOO"),
259			0,
260		},
261		{
262			`
263upper(
264    "foo"
265)
266`,
267			&hcl.EvalContext{
268				Functions: map[string]function.Function{
269					"upper": stdlib.UpperFunc,
270				},
271			},
272			cty.StringVal("FOO"),
273			0,
274		},
275		{
276			`upper(["foo"]...)`,
277			&hcl.EvalContext{
278				Functions: map[string]function.Function{
279					"upper": stdlib.UpperFunc,
280				},
281			},
282			cty.StringVal("FOO"),
283			0,
284		},
285		{
286			`upper("foo", []...)`,
287			&hcl.EvalContext{
288				Functions: map[string]function.Function{
289					"upper": stdlib.UpperFunc,
290				},
291			},
292			cty.StringVal("FOO"),
293			0,
294		},
295		{
296			`upper("foo", "bar")`,
297			&hcl.EvalContext{
298				Functions: map[string]function.Function{
299					"upper": stdlib.UpperFunc,
300				},
301			},
302			cty.DynamicVal,
303			1, // too many function arguments
304		},
305		{
306			`upper(["foo", "bar"]...)`,
307			&hcl.EvalContext{
308				Functions: map[string]function.Function{
309					"upper": stdlib.UpperFunc,
310				},
311			},
312			cty.DynamicVal,
313			1, // too many function arguments
314		},
315		{
316			`concat([1, null]...)`,
317			&hcl.EvalContext{
318				Functions: map[string]function.Function{
319					"concat": stdlib.ConcatFunc,
320				},
321			},
322			cty.DynamicVal,
323			1, // argument cannot be null
324		},
325		{
326			`concat(var.unknownlist...)`,
327			&hcl.EvalContext{
328				Functions: map[string]function.Function{
329					"concat": stdlib.ConcatFunc,
330				},
331				Variables: map[string]cty.Value{
332					"var": cty.ObjectVal(map[string]cty.Value{
333						"unknownlist": cty.UnknownVal(cty.DynamicPseudoType),
334					}),
335				},
336			},
337			cty.DynamicVal,
338			0,
339		},
340		{
341			`misbehave()`,
342			&hcl.EvalContext{
343				Functions: map[string]function.Function{
344					"misbehave": function.New(&function.Spec{
345						Type: func(args []cty.Value) (cty.Type, error) {
346							// This function misbehaves by indicating an error
347							// on an argument index that is out of range for
348							// its declared parameters. That would always be
349							// a bug in the function, but we want to avoid
350							// panicking in this case and just behave like it
351							// was a normal (non-arg) error.
352							return cty.NilType, function.NewArgErrorf(1, "out of range")
353						},
354					}),
355				},
356			},
357			cty.DynamicVal,
358			1, // Call to function "misbehave" failed: out of range
359		},
360		{
361			`misbehave() /* variadic */`,
362			&hcl.EvalContext{
363				Functions: map[string]function.Function{
364					"misbehave": function.New(&function.Spec{
365						VarParam: &function.Parameter{
366							Name: "foo",
367							Type: cty.String,
368						},
369						Type: func(args []cty.Value) (cty.Type, error) {
370							// This function misbehaves by indicating an error
371							// on an argument index that is out of range for
372							// the given arguments. That would always be a
373							// bug in the function, but to avoid panicking we
374							// just treat it like a problem related to the
375							// declared variadic argument.
376							return cty.NilType, function.NewArgErrorf(1, "out of range")
377						},
378					}),
379				},
380			},
381			cty.DynamicVal,
382			1, // Invalid value for "foo" parameter: out of range
383		},
384		{
385			`misbehave([]...)`,
386			&hcl.EvalContext{
387				Functions: map[string]function.Function{
388					"misbehave": function.New(&function.Spec{
389						VarParam: &function.Parameter{
390							Name: "foo",
391							Type: cty.String,
392						},
393						Type: func(args []cty.Value) (cty.Type, error) {
394							// This function misbehaves by indicating an error
395							// on an argument index that is out of range for
396							// the given arguments. That would always be a
397							// bug in the function, but to avoid panicking we
398							// just treat it like a problem related to the
399							// declared variadic argument.
400							return cty.NilType, function.NewArgErrorf(1, "out of range")
401						},
402					}),
403				},
404			},
405			cty.DynamicVal,
406			1, // Invalid value for "foo" parameter: out of range
407		},
408		{
409			`argerrorexpand(["a", "b"]...)`,
410			&hcl.EvalContext{
411				Functions: map[string]function.Function{
412					"argerrorexpand": function.New(&function.Spec{
413						VarParam: &function.Parameter{
414							Name: "foo",
415							Type: cty.String,
416						},
417						Type: func(args []cty.Value) (cty.Type, error) {
418							// We should be able to indicate an error in
419							// argument 1 because the indices are into the
420							// arguments _after_ "..." expansion. An earlier
421							// HCL version had a bug where it used the
422							// pre-expansion arguments and would thus panic
423							// in this case.
424							return cty.NilType, function.NewArgErrorf(1, "blah blah")
425						},
426					}),
427				},
428			},
429			cty.DynamicVal,
430			1, // Invalid value for "foo" parameter: blah blah
431		},
432		{
433			`[]`,
434			nil,
435			cty.EmptyTupleVal,
436			0,
437		},
438		{
439			`[1]`,
440			nil,
441			cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
442			0,
443		},
444		{
445			`[1,]`,
446			nil,
447			cty.TupleVal([]cty.Value{cty.NumberIntVal(1)}),
448			0,
449		},
450		{
451			`[1,true]`,
452			nil,
453			cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
454			0,
455		},
456		{
457			`[
458  1,
459  true
460]`,
461			nil,
462			cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.True}),
463			0,
464		},
465		{
466			`{}`,
467			nil,
468			cty.EmptyObjectVal,
469			0,
470		},
471		{
472			`{"hello": "world"}`,
473			nil,
474			cty.ObjectVal(map[string]cty.Value{
475				"hello": cty.StringVal("world"),
476			}),
477			0,
478		},
479		{
480			`{"hello" = "world"}`,
481			nil,
482			cty.ObjectVal(map[string]cty.Value{
483				"hello": cty.StringVal("world"),
484			}),
485			0,
486		},
487		{
488			`{hello = "world"}`,
489			nil,
490			cty.ObjectVal(map[string]cty.Value{
491				"hello": cty.StringVal("world"),
492			}),
493			0,
494		},
495		{
496			`{hello: "world"}`,
497			nil,
498			cty.ObjectVal(map[string]cty.Value{
499				"hello": cty.StringVal("world"),
500			}),
501			0,
502		},
503		{
504			`{true: "yes"}`,
505			nil,
506			cty.ObjectVal(map[string]cty.Value{
507				"true": cty.StringVal("yes"),
508			}),
509			0,
510		},
511		{
512			`{false: "yes"}`,
513			nil,
514			cty.ObjectVal(map[string]cty.Value{
515				"false": cty.StringVal("yes"),
516			}),
517			0,
518		},
519		{
520			`{null: "yes"}`,
521			nil,
522			cty.ObjectVal(map[string]cty.Value{
523				"null": cty.StringVal("yes"),
524			}),
525			0,
526		},
527		{
528			`{15: "yes"}`,
529			nil,
530			cty.ObjectVal(map[string]cty.Value{
531				"15": cty.StringVal("yes"),
532			}),
533			0,
534		},
535		{
536			`{[]: "yes"}`,
537			nil,
538			cty.DynamicVal,
539			1, // Incorrect key type; Can't use this value as a key: string required
540		},
541		{
542			`{"centos_7.2_ap-south-1" = "ami-abc123"}`,
543			nil,
544			cty.ObjectVal(map[string]cty.Value{
545				"centos_7.2_ap-south-1": cty.StringVal("ami-abc123"),
546			}),
547			0,
548		},
549		{
550			// This is syntactically valid (it's similar to foo["bar"])
551			// but is rejected during evaluation to force the user to be explicit
552			// about which of the following interpretations they mean:
553			// -{(foo.bar) = "baz"}
554			// -{"foo.bar" = "baz"}
555			// naked traversals as keys are allowed when analyzing an expression
556			// statically so an application can define object-syntax-based
557			// language constructs with looser requirements, but we reject
558			// this during normal expression evaluation.
559			`{foo.bar = "ami-abc123"}`,
560			nil,
561			cty.DynamicVal,
562			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.
563		},
564		{
565			// This is a weird variant of the above where a period is followed
566			// by a digit, causing the parser to interpret it as an index
567			// operator using the legacy HIL/Terraform index syntax.
568			// This one _does_ fail parsing, causing it to be subject to
569			// parser recovery behavior.
570			`{centos_7.2_ap-south-1 = "ami-abc123"}`,
571			nil,
572			cty.EmptyObjectVal, // (due to parser recovery behavior)
573			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.
574		},
575		{
576			`{var.greeting = "world"}`,
577			&hcl.EvalContext{
578				Variables: map[string]cty.Value{
579					"var": cty.ObjectVal(map[string]cty.Value{
580						"greeting": cty.StringVal("hello"),
581					}),
582				},
583			},
584			cty.DynamicVal,
585			1, // Ambiguous attribute key
586		},
587		{
588			`{(var.greeting) = "world"}`,
589			&hcl.EvalContext{
590				Variables: map[string]cty.Value{
591					"var": cty.ObjectVal(map[string]cty.Value{
592						"greeting": cty.StringVal("hello"),
593					}),
594				},
595			},
596			cty.ObjectVal(map[string]cty.Value{
597				"hello": cty.StringVal("world"),
598			}),
599			0,
600		},
601		{
602			// Marked values as object keys
603			`{(var.greeting) = "world", "goodbye" = "earth"}`,
604			&hcl.EvalContext{
605				Variables: map[string]cty.Value{
606					"var": cty.ObjectVal(map[string]cty.Value{
607						"greeting": cty.StringVal("hello").Mark("marked"),
608					}),
609				},
610			},
611			cty.ObjectVal(map[string]cty.Value{
612				"hello":   cty.StringVal("world"),
613				"goodbye": cty.StringVal("earth"),
614			}).Mark("marked"),
615			0,
616		},
617		{
618			`{"${var.greeting}" = "world"}`,
619			&hcl.EvalContext{
620				Variables: map[string]cty.Value{
621					"var": cty.ObjectVal(map[string]cty.Value{
622						"greeting": cty.StringVal("hello"),
623					}),
624				},
625			},
626			cty.ObjectVal(map[string]cty.Value{
627				"hello": cty.StringVal("world"),
628			}),
629			0,
630		},
631		{
632			`{"hello" = "world", "goodbye" = "cruel world"}`,
633			nil,
634			cty.ObjectVal(map[string]cty.Value{
635				"hello":   cty.StringVal("world"),
636				"goodbye": cty.StringVal("cruel world"),
637			}),
638			0,
639		},
640		{
641			`{
642  "hello" = "world"
643}`,
644			nil,
645			cty.ObjectVal(map[string]cty.Value{
646				"hello": cty.StringVal("world"),
647			}),
648			0,
649		},
650		{
651			`{
652  "hello" = "world"
653  "goodbye" = "cruel world"
654}`,
655			nil,
656			cty.ObjectVal(map[string]cty.Value{
657				"hello":   cty.StringVal("world"),
658				"goodbye": cty.StringVal("cruel world"),
659			}),
660			0,
661		},
662		{
663			`{
664  "hello" = "world",
665  "goodbye" = "cruel world"
666}`,
667			nil,
668			cty.ObjectVal(map[string]cty.Value{
669				"hello":   cty.StringVal("world"),
670				"goodbye": cty.StringVal("cruel world"),
671			}),
672			0,
673		},
674		{
675			`{
676  "hello" = "world",
677  "goodbye" = "cruel world",
678}`,
679			nil,
680			cty.ObjectVal(map[string]cty.Value{
681				"hello":   cty.StringVal("world"),
682				"goodbye": cty.StringVal("cruel world"),
683			}),
684			0,
685		},
686
687		{
688			"{\n  for k, v in {hello: \"world\"}:\nk => v\n}",
689			nil,
690			cty.ObjectVal(map[string]cty.Value{
691				"hello": cty.StringVal("world"),
692			}),
693			0,
694		},
695		{
696			// This one is different than the previous because the extra level of
697			// object constructor causes the inner for expression to begin parsing
698			// in newline-sensitive mode, which it must then properly disable in
699			// order to peek the "for" keyword.
700			"{\n  a = {\n  for k, v in {hello: \"world\"}:\nk => v\n  }\n}",
701			nil,
702			cty.ObjectVal(map[string]cty.Value{
703				"a": cty.ObjectVal(map[string]cty.Value{
704					"hello": cty.StringVal("world"),
705				}),
706			}),
707			0,
708		},
709		{
710			`{for k, v in {hello: "world"}: k => v if k == "hello"}`,
711			nil,
712			cty.ObjectVal(map[string]cty.Value{
713				"hello": cty.StringVal("world"),
714			}),
715			0,
716		},
717		{
718			`{for k, v in {hello: "world"}: upper(k) => upper(v) if k == "hello"}`,
719			&hcl.EvalContext{
720				Functions: map[string]function.Function{
721					"upper": stdlib.UpperFunc,
722				},
723			},
724			cty.ObjectVal(map[string]cty.Value{
725				"HELLO": cty.StringVal("WORLD"),
726			}),
727			0,
728		},
729		{
730			`{for k, v in ["world"]: k => v if k == 0}`,
731			nil,
732			cty.ObjectVal(map[string]cty.Value{
733				"0": cty.StringVal("world"),
734			}),
735			0,
736		},
737		{
738			`{for v in ["world"]: v => v}`,
739			nil,
740			cty.ObjectVal(map[string]cty.Value{
741				"world": cty.StringVal("world"),
742			}),
743			0,
744		},
745		{
746			`{for k, v in {hello: "world"}: k => v if k == "foo"}`,
747			nil,
748			cty.EmptyObjectVal,
749			0,
750		},
751		{
752			`{for k, v in {hello: "world"}: 5 => v}`,
753			nil,
754			cty.ObjectVal(map[string]cty.Value{
755				"5": cty.StringVal("world"),
756			}),
757			0,
758		},
759		{
760			`{for k, v in {hello: "world"}: [] => v}`,
761			nil,
762			cty.DynamicVal,
763			1, // key expression has the wrong type
764		},
765		{
766			`{for k, v in {hello: "world"}: k => k if k == "hello"}`,
767			nil,
768			cty.ObjectVal(map[string]cty.Value{
769				"hello": cty.StringVal("hello"),
770			}),
771			0,
772		},
773		{
774			`{for k, v in {hello: "world"}: k => foo}`,
775			&hcl.EvalContext{
776				Variables: map[string]cty.Value{
777					"foo": cty.StringVal("foo"),
778				},
779			},
780			cty.ObjectVal(map[string]cty.Value{
781				"hello": cty.StringVal("foo"),
782			}),
783			0,
784		},
785		{
786			`[for k, v in {hello: "world"}: "${k}=${v}"]`,
787			nil,
788			cty.TupleVal([]cty.Value{
789				cty.StringVal("hello=world"),
790			}),
791			0,
792		},
793		{
794			`[for k, v in {hello: "world"}: k => v]`,
795			nil,
796			cty.ObjectVal(map[string]cty.Value{
797				"hello": cty.StringVal("world"),
798			}),
799			1, // can't have a key expr when producing a tuple
800		},
801		{
802			`{for v in {hello: "world"}: v}`,
803			nil,
804			cty.TupleVal([]cty.Value{
805				cty.StringVal("world"),
806			}),
807			1, // must have a key expr when producing a map
808		},
809		{
810			`{for i, v in ["a", "b", "c", "b", "d"]: v => i...}`,
811			nil,
812			cty.ObjectVal(map[string]cty.Value{
813				"a": cty.TupleVal([]cty.Value{
814					cty.NumberIntVal(0),
815				}),
816				"b": cty.TupleVal([]cty.Value{
817					cty.NumberIntVal(1),
818					cty.NumberIntVal(3),
819				}),
820				"c": cty.TupleVal([]cty.Value{
821					cty.NumberIntVal(2),
822				}),
823				"d": cty.TupleVal([]cty.Value{
824					cty.NumberIntVal(4),
825				}),
826			}),
827			0,
828		},
829		{
830			`{for i, v in ["a", "b", "c", "b", "d"]: v => i... if i <= 2}`,
831			nil,
832			cty.ObjectVal(map[string]cty.Value{
833				"a": cty.TupleVal([]cty.Value{
834					cty.NumberIntVal(0),
835				}),
836				"b": cty.TupleVal([]cty.Value{
837					cty.NumberIntVal(1),
838				}),
839				"c": cty.TupleVal([]cty.Value{
840					cty.NumberIntVal(2),
841				}),
842			}),
843			0,
844		},
845		{
846			`{for i, v in ["a", "b", "c", "b", "d"]: v => i}`,
847			nil,
848			cty.ObjectVal(map[string]cty.Value{
849				"a": cty.NumberIntVal(0),
850				"b": cty.NumberIntVal(1),
851				"c": cty.NumberIntVal(2),
852				"d": cty.NumberIntVal(4),
853			}),
854			1, // duplicate key "b"
855		},
856		{
857			`[for v in {hello: "world"}: v...]`,
858			nil,
859			cty.TupleVal([]cty.Value{
860				cty.StringVal("world"),
861			}),
862			1, // can't use grouping when producing a tuple
863		},
864		{
865			`[for v in "hello": v]`,
866			nil,
867			cty.DynamicVal,
868			1, // can't iterate over a string
869		},
870		{
871			`[for v in null: v]`,
872			nil,
873			cty.DynamicVal,
874			1, // can't iterate over a null value
875		},
876		{
877			`[for v in unk: v]`,
878			&hcl.EvalContext{
879				Variables: map[string]cty.Value{
880					"unk": cty.UnknownVal(cty.List(cty.String)),
881				},
882			},
883			cty.DynamicVal,
884			0,
885		},
886		{
887			`[for v in unk: v]`,
888			&hcl.EvalContext{
889				Variables: map[string]cty.Value{
890					"unk": cty.DynamicVal,
891				},
892			},
893			cty.DynamicVal,
894			0,
895		},
896		{
897			`[for v in unk: v]`,
898			&hcl.EvalContext{
899				Variables: map[string]cty.Value{
900					"unk": cty.UnknownVal(cty.String),
901				},
902			},
903			cty.DynamicVal,
904			1, // can't iterate over a string (even if it's unknown)
905		},
906		{
907			`[for v in ["a", "b"]: v if unkbool]`,
908			&hcl.EvalContext{
909				Variables: map[string]cty.Value{
910					"unkbool": cty.UnknownVal(cty.Bool),
911				},
912			},
913			cty.DynamicVal,
914			0,
915		},
916		{
917			`[for v in ["a", "b"]: v if nullbool]`,
918			&hcl.EvalContext{
919				Variables: map[string]cty.Value{
920					"nullbool": cty.NullVal(cty.Bool),
921				},
922			},
923			cty.DynamicVal,
924			1, // value of if clause must not be null
925		},
926		{
927			`[for v in ["a", "b"]: v if dyn]`,
928			&hcl.EvalContext{
929				Variables: map[string]cty.Value{
930					"dyn": cty.DynamicVal,
931				},
932			},
933			cty.DynamicVal,
934			0,
935		},
936		{
937			`[for v in ["a", "b"]: v if unknum]`,
938			&hcl.EvalContext{
939				Variables: map[string]cty.Value{
940					"unknum": cty.UnknownVal(cty.List(cty.Number)),
941				},
942			},
943			cty.DynamicVal,
944			1, // if expression must be bool
945		},
946		{
947			`[for i, v in ["a", "b"]: v if i + i]`,
948			nil,
949			cty.DynamicVal,
950			1, // if expression must be bool
951		},
952		{
953			`[for v in ["a", "b"]: unkstr]`,
954			&hcl.EvalContext{
955				Variables: map[string]cty.Value{
956					"unkstr": cty.UnknownVal(cty.String),
957				},
958			},
959			cty.TupleVal([]cty.Value{
960				cty.UnknownVal(cty.String),
961				cty.UnknownVal(cty.String),
962			}),
963			0,
964		},
965		{ // Marked sequence results in a marked tuple
966			`[for x in things: x if x != ""]`,
967			&hcl.EvalContext{
968				Variables: map[string]cty.Value{
969					"things": cty.ListVal([]cty.Value{
970						cty.StringVal("a"),
971						cty.StringVal("b"),
972						cty.StringVal(""),
973						cty.StringVal("c"),
974					}).Mark("sensitive"),
975				},
976			},
977			cty.TupleVal([]cty.Value{
978				cty.StringVal("a"),
979				cty.StringVal("b"),
980				cty.StringVal("c"),
981			}).Mark("sensitive"),
982			0,
983		},
984		{ // Marked map results in a marked object
985			`{for k, v in things: k => !v}`,
986			&hcl.EvalContext{
987				Variables: map[string]cty.Value{
988					"things": cty.MapVal(map[string]cty.Value{
989						"a": cty.True,
990						"b": cty.False,
991					}).Mark("sensitive"),
992				},
993			},
994			cty.ObjectVal(map[string]cty.Value{
995				"a": cty.False,
996				"b": cty.True,
997			}).Mark("sensitive"),
998			0,
999		},
1000		{ // Marked map member carries marks through
1001			`{for k, v in things: k => !v}`,
1002			&hcl.EvalContext{
1003				Variables: map[string]cty.Value{
1004					"things": cty.MapVal(map[string]cty.Value{
1005						"a": cty.True.Mark("sensitive"),
1006						"b": cty.False,
1007					}),
1008				},
1009			},
1010			cty.ObjectVal(map[string]cty.Value{
1011				"a": cty.False.Mark("sensitive"),
1012				"b": cty.True,
1013			}),
1014			0,
1015		},
1016		{
1017			// Mark object if keys include marked values, members retain
1018			// their original marks in their values
1019			`{for v in things: v => "${v}-friend"}`,
1020			&hcl.EvalContext{
1021				Variables: map[string]cty.Value{
1022					"things": cty.MapVal(map[string]cty.Value{
1023						"a": cty.StringVal("rosie").Mark("marked"),
1024						"b": cty.StringVal("robin"),
1025						// Check for double-marking when a key val has a duplicate mark
1026						"c": cty.StringVal("rowan").Mark("marked"),
1027						"d": cty.StringVal("ruben").Mark("also-marked"),
1028					}),
1029				},
1030			},
1031			cty.ObjectVal(map[string]cty.Value{
1032				"rosie": cty.StringVal("rosie-friend").Mark("marked"),
1033				"robin": cty.StringVal("robin-friend"),
1034				"rowan": cty.StringVal("rowan-friend").Mark("marked"),
1035				"ruben": cty.StringVal("ruben-friend").Mark("also-marked"),
1036			}).WithMarks(cty.NewValueMarks("marked", "also-marked")),
1037			0,
1038		},
1039		{ // object itself is marked, contains marked value
1040			`{for v in things: v => "${v}-friend"}`,
1041			&hcl.EvalContext{
1042				Variables: map[string]cty.Value{
1043					"things": cty.MapVal(map[string]cty.Value{
1044						"a": cty.StringVal("rosie").Mark("marked"),
1045						"b": cty.StringVal("robin"),
1046					}).Mark("marks"),
1047				},
1048			},
1049			cty.ObjectVal(map[string]cty.Value{
1050				"rosie": cty.StringVal("rosie-friend").Mark("marked"),
1051				"robin": cty.StringVal("robin-friend"),
1052			}).WithMarks(cty.NewValueMarks("marked", "marks")),
1053			0,
1054		},
1055		{ // Sequence for loop with marked conditional expression
1056			`[for x in things: x if x != secret]`,
1057			&hcl.EvalContext{
1058				Variables: map[string]cty.Value{
1059					"things": cty.ListVal([]cty.Value{
1060						cty.StringVal("a"),
1061						cty.StringVal("b"),
1062						cty.StringVal("c"),
1063					}),
1064					"secret": cty.StringVal("b").Mark("sensitive"),
1065				},
1066			},
1067			cty.TupleVal([]cty.Value{
1068				cty.StringVal("a"),
1069				cty.StringVal("c"),
1070			}).Mark("sensitive"),
1071			0,
1072		},
1073		{ // Map for loop with marked conditional expression
1074			`{ for k, v in things: k => v if k != secret }`,
1075			&hcl.EvalContext{
1076				Variables: map[string]cty.Value{
1077					"things": cty.MapVal(map[string]cty.Value{
1078						"a": cty.True,
1079						"b": cty.False,
1080						"c": cty.False,
1081					}),
1082					"secret": cty.StringVal("b").Mark("sensitive"),
1083				},
1084			},
1085			cty.ObjectVal(map[string]cty.Value{
1086				"a": cty.True,
1087				"c": cty.False,
1088			}).Mark("sensitive"),
1089			0,
1090		},
1091		{
1092			`[{name: "Steve"}, {name: "Ermintrude"}].*.name`,
1093			nil,
1094			cty.TupleVal([]cty.Value{
1095				cty.StringVal("Steve"),
1096				cty.StringVal("Ermintrude"),
1097			}),
1098			0,
1099		},
1100		{
1101			`{name: "Steve"}.*.name`,
1102			nil,
1103			cty.TupleVal([]cty.Value{
1104				cty.StringVal("Steve"),
1105			}),
1106			0,
1107		},
1108		{
1109			`null[*]`,
1110			nil,
1111			cty.EmptyTupleVal,
1112			0,
1113		},
1114		{
1115			`{name: "Steve"}[*].name`,
1116			nil,
1117			cty.TupleVal([]cty.Value{
1118				cty.StringVal("Steve"),
1119			}),
1120			0,
1121		},
1122		{
1123			`set.*.name`,
1124			&hcl.EvalContext{
1125				Variables: map[string]cty.Value{
1126					"set": cty.SetVal([]cty.Value{
1127						cty.ObjectVal(map[string]cty.Value{
1128							"name": cty.StringVal("Steve"),
1129						}),
1130					}),
1131				},
1132			},
1133			cty.ListVal([]cty.Value{
1134				cty.StringVal("Steve"),
1135			}),
1136			0,
1137		},
1138		{
1139			`unkstr.*.name`,
1140			&hcl.EvalContext{
1141				Variables: map[string]cty.Value{
1142					"unkstr": cty.UnknownVal(cty.String),
1143				},
1144			},
1145			cty.UnknownVal(cty.Tuple([]cty.Type{cty.DynamicPseudoType})),
1146			1, // a string has no attribute "name"
1147		},
1148		{
1149			`dyn.*.name`,
1150			&hcl.EvalContext{
1151				Variables: map[string]cty.Value{
1152					"dyn": cty.DynamicVal,
1153				},
1154			},
1155			cty.DynamicVal,
1156			0,
1157		},
1158		{
1159			`unkobj.*.name`,
1160			&hcl.EvalContext{
1161				Variables: map[string]cty.Value{
1162					"unkobj": cty.UnknownVal(cty.Object(map[string]cty.Type{
1163						"name": cty.String,
1164					})),
1165				},
1166			},
1167			cty.TupleVal([]cty.Value{
1168				cty.UnknownVal(cty.String),
1169			}),
1170			0,
1171		},
1172		{
1173			`unklistobj.*.name`,
1174			&hcl.EvalContext{
1175				Variables: map[string]cty.Value{
1176					"unklistobj": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
1177						"name": cty.String,
1178					}))),
1179				},
1180			},
1181			cty.UnknownVal(cty.List(cty.String)),
1182			0,
1183		},
1184		{
1185			`unktupleobj.*.name`,
1186			&hcl.EvalContext{
1187				Variables: map[string]cty.Value{
1188					"unktupleobj": cty.UnknownVal(
1189						cty.Tuple([]cty.Type{
1190							cty.Object(map[string]cty.Type{
1191								"name": cty.String,
1192							}),
1193							cty.Object(map[string]cty.Type{
1194								"name": cty.Bool,
1195							}),
1196						}),
1197					),
1198				},
1199			},
1200			cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool})),
1201			0,
1202		},
1203		{
1204			`nullobj.*.name`,
1205			&hcl.EvalContext{
1206				Variables: map[string]cty.Value{
1207					"nullobj": cty.NullVal(cty.Object(map[string]cty.Type{
1208						"name": cty.String,
1209					})),
1210				},
1211			},
1212			cty.TupleVal([]cty.Value{}),
1213			0,
1214		},
1215		{
1216			`nulllist.*.name`,
1217			&hcl.EvalContext{
1218				Variables: map[string]cty.Value{
1219					"nulllist": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
1220						"name": cty.String,
1221					}))),
1222				},
1223			},
1224			cty.DynamicVal,
1225			1, // splat cannot be applied to null sequence
1226		},
1227		{
1228			`["hello", "goodbye"].*`,
1229			nil,
1230			cty.TupleVal([]cty.Value{
1231				cty.StringVal("hello"),
1232				cty.StringVal("goodbye"),
1233			}),
1234			0,
1235		},
1236		{
1237			`"hello".*`,
1238			nil,
1239			cty.TupleVal([]cty.Value{
1240				cty.StringVal("hello"),
1241			}),
1242			0,
1243		},
1244		{
1245			`[["hello"], ["world", "unused"]].*.0`,
1246			nil,
1247			cty.TupleVal([]cty.Value{
1248				cty.StringVal("hello"),
1249				cty.StringVal("world"),
1250			}),
1251			0,
1252		},
1253		{
1254			`[[{name:"foo"}], [{name:"bar"}, {name:"baz"}]].*.0.name`,
1255			nil,
1256			cty.TupleVal([]cty.Value{
1257				cty.StringVal("foo"),
1258				cty.StringVal("bar"),
1259			}),
1260			0,
1261		},
1262		{
1263			`[[[{name:"foo"}]], [[{name:"bar"}], [{name:"baz"}]]].*.0.0.name`,
1264			nil,
1265			cty.TupleVal([]cty.Value{
1266				cty.DynamicVal,
1267				cty.DynamicVal,
1268			}),
1269			1, // can't chain legacy index syntax together, like .0.0 (because 0.0 parses as a single number)
1270		},
1271		{
1272			// For an "attribute-only" splat, an index operator applies to
1273			// the splat result as a whole, rather than being incorporated
1274			// into the splat traversal itself.
1275			`[{name: "Steve"}, {name: "Ermintrude"}].*.name[0]`,
1276			nil,
1277			cty.StringVal("Steve"),
1278			0,
1279		},
1280		{
1281			// For a "full" splat, an index operator is consumed as part
1282			// of the splat's traversal.
1283			`[{names: ["Steve"]}, {names: ["Ermintrude"]}][*].names[0]`,
1284			nil,
1285			cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
1286			0,
1287		},
1288		{
1289			// Another "full" splat, this time with the index first.
1290			`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][0].name`,
1291			nil,
1292			cty.TupleVal([]cty.Value{cty.StringVal("Steve"), cty.StringVal("Ermintrude")}),
1293			0,
1294		},
1295		{
1296			// Full splats can nest, which produces nested tuples.
1297			`[[{name: "Steve"}], [{name: "Ermintrude"}]][*][*].name`,
1298			nil,
1299			cty.TupleVal([]cty.Value{
1300				cty.TupleVal([]cty.Value{cty.StringVal("Steve")}),
1301				cty.TupleVal([]cty.Value{cty.StringVal("Ermintrude")}),
1302			}),
1303			0,
1304		},
1305		{
1306			`[["hello"], ["goodbye"]].*.*`,
1307			nil,
1308			cty.TupleVal([]cty.Value{
1309				cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
1310				cty.TupleVal([]cty.Value{cty.StringVal("goodbye")}),
1311			}),
1312			1,
1313		},
1314		{ // splat with sensitive collection
1315			`maps.*.enabled`,
1316			&hcl.EvalContext{
1317				Variables: map[string]cty.Value{
1318					"maps": cty.ListVal([]cty.Value{
1319						cty.MapVal(map[string]cty.Value{"enabled": cty.True}),
1320						cty.MapVal(map[string]cty.Value{"enabled": cty.False}),
1321					}).Mark("sensitive"),
1322				},
1323			},
1324			cty.ListVal([]cty.Value{
1325				cty.True,
1326				cty.False,
1327			}).Mark("sensitive"),
1328			0,
1329		},
1330		{ // splat with collection with sensitive elements
1331			`maps.*.x`,
1332			&hcl.EvalContext{
1333				Variables: map[string]cty.Value{
1334					"maps": cty.ListVal([]cty.Value{
1335						cty.MapVal(map[string]cty.Value{
1336							"x": cty.StringVal("foo").Mark("sensitive"),
1337						}),
1338						cty.MapVal(map[string]cty.Value{
1339							"x": cty.StringVal("bar"),
1340						}),
1341					}),
1342				},
1343			},
1344			cty.ListVal([]cty.Value{
1345				cty.StringVal("foo").Mark("sensitive"),
1346				cty.StringVal("bar"),
1347			}),
1348			0,
1349		},
1350		{
1351			`["hello"][0]`,
1352			nil,
1353			cty.StringVal("hello"),
1354			0,
1355		},
1356		{
1357			`["hello"].0`,
1358			nil,
1359			cty.StringVal("hello"),
1360			0,
1361		},
1362		{
1363			`[["hello"]].0.0`,
1364			nil,
1365			cty.DynamicVal,
1366			1, // can't chain legacy index syntax together (because 0.0 parses as 0)
1367		},
1368		{
1369			`[{greeting = "hello"}].0.greeting`,
1370			nil,
1371			cty.StringVal("hello"),
1372			0,
1373		},
1374		{
1375			`[][0]`,
1376			nil,
1377			cty.DynamicVal,
1378			1, // invalid index
1379		},
1380		{
1381			`["hello"][negate(0)]`,
1382			&hcl.EvalContext{
1383				Functions: map[string]function.Function{
1384					"negate": stdlib.NegateFunc,
1385				},
1386			},
1387			cty.StringVal("hello"),
1388			0,
1389		},
1390		{
1391			`[][negate(0)]`,
1392			&hcl.EvalContext{
1393				Functions: map[string]function.Function{
1394					"negate": stdlib.NegateFunc,
1395				},
1396			},
1397			cty.DynamicVal,
1398			1, // invalid index
1399		},
1400		{
1401			`["hello"]["0"]`, // key gets converted to number
1402			nil,
1403			cty.StringVal("hello"),
1404			0,
1405		},
1406		{
1407			`["boop"].foo[index]`, // index is a variable to force IndexExpr instead of traversal
1408			&hcl.EvalContext{
1409				Variables: map[string]cty.Value{
1410					"index": cty.NumberIntVal(0),
1411				},
1412			},
1413			cty.DynamicVal,
1414			1, // expression ["boop"] does not have attributes
1415		},
1416
1417		{
1418			`foo`,
1419			&hcl.EvalContext{
1420				Variables: map[string]cty.Value{
1421					"foo": cty.StringVal("hello"),
1422				},
1423			},
1424			cty.StringVal("hello"),
1425			0,
1426		},
1427		{
1428			`bar`,
1429			&hcl.EvalContext{},
1430			cty.DynamicVal,
1431			1, // variables not allowed here
1432		},
1433		{
1434			`foo.bar`,
1435			&hcl.EvalContext{
1436				Variables: map[string]cty.Value{
1437					"foo": cty.StringVal("hello"),
1438				},
1439			},
1440			cty.DynamicVal,
1441			1, // foo does not have attributes
1442		},
1443		{
1444			`foo.baz`,
1445			&hcl.EvalContext{
1446				Variables: map[string]cty.Value{
1447					"foo": cty.ObjectVal(map[string]cty.Value{
1448						"baz": cty.StringVal("hello"),
1449					}),
1450				},
1451			},
1452			cty.StringVal("hello"),
1453			0,
1454		},
1455		{
1456			`foo["baz"]`,
1457			&hcl.EvalContext{
1458				Variables: map[string]cty.Value{
1459					"foo": cty.ObjectVal(map[string]cty.Value{
1460						"baz": cty.StringVal("hello"),
1461					}),
1462				},
1463			},
1464			cty.StringVal("hello"),
1465			0,
1466		},
1467		{
1468			`foo[true]`, // key is converted to string
1469			&hcl.EvalContext{
1470				Variables: map[string]cty.Value{
1471					"foo": cty.ObjectVal(map[string]cty.Value{
1472						"true": cty.StringVal("hello"),
1473					}),
1474				},
1475			},
1476			cty.StringVal("hello"),
1477			0,
1478		},
1479		{
1480			`foo[0].baz`,
1481			&hcl.EvalContext{
1482				Variables: map[string]cty.Value{
1483					"foo": cty.ListVal([]cty.Value{
1484						cty.ObjectVal(map[string]cty.Value{
1485							"baz": cty.StringVal("hello"),
1486						}),
1487					}),
1488				},
1489			},
1490			cty.StringVal("hello"),
1491			0,
1492		},
1493
1494		{
1495			`
1496<<EOT
1497Foo
1498Bar
1499Baz
1500EOT
1501`,
1502			nil,
1503			cty.StringVal("Foo\nBar\nBaz\n"),
1504			0,
1505		},
1506		{
1507			`
1508<<EOT
1509Foo
1510${bar}
1511Baz
1512EOT
1513`,
1514			&hcl.EvalContext{
1515				Variables: map[string]cty.Value{
1516					"bar": cty.StringVal("Bar"),
1517				},
1518			},
1519			cty.StringVal("Foo\nBar\nBaz\n"),
1520			0,
1521		},
1522		{
1523			`
1524<<EOT
1525Foo
1526%{for x in bars}${x}%{endfor}
1527Baz
1528EOT
1529`,
1530			&hcl.EvalContext{
1531				Variables: map[string]cty.Value{
1532					"bars": cty.ListVal([]cty.Value{
1533						cty.StringVal("Bar"),
1534						cty.StringVal("Bar"),
1535						cty.StringVal("Bar"),
1536					}),
1537				},
1538			},
1539			cty.StringVal("Foo\nBarBarBar\nBaz\n"),
1540			0,
1541		},
1542		{
1543			`[
1544  <<EOT
1545  Foo
1546  Bar
1547  Baz
1548  EOT
1549]
1550`,
1551			nil,
1552			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n  Bar\n  Baz\n")}),
1553			0,
1554		},
1555		{
1556			`[
1557  <<-EOT
1558  Foo
1559  Bar
1560  Baz
1561  EOT
1562]
1563`,
1564			nil,
1565			cty.TupleVal([]cty.Value{cty.StringVal("Foo\nBar\nBaz\n")}),
1566			0,
1567		},
1568		{
1569			`[
1570  <<-EOT
1571  Foo
1572    Bar
1573    Baz
1574  EOT
1575]
1576`,
1577			nil,
1578			cty.TupleVal([]cty.Value{cty.StringVal("Foo\n  Bar\n  Baz\n")}),
1579			0,
1580		},
1581		{
1582			`[
1583  <<-EOT
1584    Foo
1585  Bar
1586    Baz
1587  EOT
1588]
1589`,
1590			nil,
1591			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\nBar\n  Baz\n")}),
1592			0,
1593		},
1594		{
1595			`[
1596  <<-EOT
1597    Foo
1598  ${bar}
1599    Baz
1600    EOT
1601]
1602`,
1603			&hcl.EvalContext{
1604				Variables: map[string]cty.Value{
1605					"bar": cty.StringVal("  Bar"), // Spaces in the interpolation result don't affect the outcome
1606				},
1607			},
1608			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n  Bar\n  Baz\n")}),
1609			0,
1610		},
1611		{
1612			`[
1613  <<EOT
1614  Foo
1615
1616  Bar
1617
1618  Baz
1619  EOT
1620]
1621`,
1622			nil,
1623			cty.TupleVal([]cty.Value{cty.StringVal("  Foo\n\n  Bar\n\n  Baz\n")}),
1624			0,
1625		},
1626		{
1627			`[
1628  <<-EOT
1629  Foo
1630
1631  Bar
1632
1633  Baz
1634  EOT
1635]
1636`,
1637			nil,
1638			cty.TupleVal([]cty.Value{cty.StringVal("Foo\n\nBar\n\nBaz\n")}),
1639			0,
1640		},
1641
1642		{
1643			`unk["baz"]`,
1644			&hcl.EvalContext{
1645				Variables: map[string]cty.Value{
1646					"unk": cty.UnknownVal(cty.String),
1647				},
1648			},
1649			cty.DynamicVal,
1650			1, // value does not have indices (because we know it's a string)
1651		},
1652		{
1653			`unk["boop"]`,
1654			&hcl.EvalContext{
1655				Variables: map[string]cty.Value{
1656					"unk": cty.UnknownVal(cty.Map(cty.String)),
1657				},
1658			},
1659			cty.UnknownVal(cty.String), // we know it's a map of string
1660			0,
1661		},
1662		{
1663			`dyn["boop"]`,
1664			&hcl.EvalContext{
1665				Variables: map[string]cty.Value{
1666					"dyn": cty.DynamicVal,
1667				},
1668			},
1669			cty.DynamicVal, // don't know what it is yet
1670			0,
1671		},
1672		{
1673			`nullstr == "foo"`,
1674			&hcl.EvalContext{
1675				Variables: map[string]cty.Value{
1676					"nullstr": cty.NullVal(cty.String),
1677				},
1678			},
1679			cty.False,
1680			0,
1681		},
1682		{
1683			`nullstr == nullstr`,
1684			&hcl.EvalContext{
1685				Variables: map[string]cty.Value{
1686					"nullstr": cty.NullVal(cty.String),
1687				},
1688			},
1689			cty.True,
1690			0,
1691		},
1692		{
1693			`nullstr == null`,
1694			&hcl.EvalContext{
1695				Variables: map[string]cty.Value{
1696					"nullstr": cty.NullVal(cty.String),
1697				},
1698			},
1699			cty.True,
1700			0,
1701		},
1702		{
1703			`nullstr == nullnum`,
1704			&hcl.EvalContext{
1705				Variables: map[string]cty.Value{
1706					"nullstr": cty.NullVal(cty.String),
1707					"nullnum": cty.NullVal(cty.Number),
1708				},
1709			},
1710			cty.True,
1711			0,
1712		},
1713		{
1714			`"" == nulldyn`,
1715			&hcl.EvalContext{
1716				Variables: map[string]cty.Value{
1717					"nulldyn": cty.NullVal(cty.DynamicPseudoType),
1718				},
1719			},
1720			cty.False,
1721			0,
1722		},
1723		{
1724			`true ? var : null`,
1725			&hcl.EvalContext{
1726				Variables: map[string]cty.Value{
1727					"var": cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
1728				},
1729			},
1730			cty.ObjectVal(map[string]cty.Value{"a": cty.StringVal("A")}),
1731			0,
1732		},
1733		{
1734			`true ? var : null`,
1735			&hcl.EvalContext{
1736				Variables: map[string]cty.Value{
1737					"var": cty.UnknownVal(cty.DynamicPseudoType),
1738				},
1739			},
1740			cty.UnknownVal(cty.DynamicPseudoType),
1741			0,
1742		},
1743		{
1744			`true ? ["a", "b"] : null`,
1745			nil,
1746			cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
1747			0,
1748		},
1749		{
1750			`true ? null: ["a", "b"]`,
1751			nil,
1752			cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
1753			0,
1754		},
1755		{
1756			`false ? ["a", "b"] : null`,
1757			nil,
1758			cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
1759			0,
1760		},
1761		{
1762			`false ? null: ["a", "b"]`,
1763			nil,
1764			cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
1765			0,
1766		},
1767		{
1768			`false ? null: null`,
1769			nil,
1770			cty.NullVal(cty.DynamicPseudoType),
1771			0,
1772		},
1773		{
1774			`false ? var: {a = "b"}`,
1775			&hcl.EvalContext{
1776				Variables: map[string]cty.Value{
1777					"var": cty.DynamicVal,
1778				},
1779			},
1780			cty.ObjectVal(map[string]cty.Value{
1781				"a": cty.StringVal("b"),
1782			}),
1783			0,
1784		},
1785		{
1786			`true ? ["a", "b"]: var`,
1787			&hcl.EvalContext{
1788				Variables: map[string]cty.Value{
1789					"var": cty.UnknownVal(cty.DynamicPseudoType),
1790				},
1791			},
1792			cty.TupleVal([]cty.Value{
1793				cty.StringVal("a"),
1794				cty.StringVal("b"),
1795			}),
1796			0,
1797		},
1798		{
1799			`false ? ["a", "b"]: var`,
1800			&hcl.EvalContext{
1801				Variables: map[string]cty.Value{
1802					"var": cty.DynamicVal,
1803				},
1804			},
1805			cty.DynamicVal,
1806			0,
1807		},
1808		{
1809			`false ? ["a", "b"]: var`,
1810			&hcl.EvalContext{
1811				Variables: map[string]cty.Value{
1812					"var": cty.UnknownVal(cty.DynamicPseudoType),
1813				},
1814			},
1815			cty.DynamicVal,
1816			0,
1817		},
1818		{ // marked conditional
1819			`var.foo ? 1 : 0`,
1820			&hcl.EvalContext{
1821				Variables: map[string]cty.Value{
1822					"var": cty.ObjectVal(map[string]cty.Value{
1823						"foo": cty.BoolVal(true),
1824					}).Mark("sensitive"),
1825				},
1826			},
1827			cty.NumberIntVal(1),
1828			0,
1829		},
1830		{ // marked argument expansion
1831			`min(xs...)`,
1832			&hcl.EvalContext{
1833				Functions: map[string]function.Function{
1834					"min": stdlib.MinFunc,
1835				},
1836				Variables: map[string]cty.Value{
1837					"xs": cty.ListVal([]cty.Value{
1838						cty.NumberIntVal(3),
1839						cty.NumberIntVal(1),
1840						cty.NumberIntVal(4),
1841					}).Mark("sensitive"),
1842				},
1843			},
1844			cty.NumberIntVal(1).Mark("sensitive"),
1845			0,
1846		},
1847	}
1848
1849	for _, test := range tests {
1850		t.Run(test.input, func(t *testing.T) {
1851			expr, parseDiags := ParseExpression([]byte(test.input), "", hcl.Pos{Line: 1, Column: 1, Byte: 0})
1852
1853			got, valDiags := expr.Value(test.ctx)
1854
1855			diagCount := len(parseDiags) + len(valDiags)
1856
1857			if diagCount != test.diagCount {
1858				t.Errorf("wrong number of diagnostics %d; want %d", diagCount, test.diagCount)
1859				for _, diag := range parseDiags {
1860					t.Logf(" - %s", diag.Error())
1861				}
1862				for _, diag := range valDiags {
1863					t.Logf(" - %s", diag.Error())
1864				}
1865			}
1866
1867			if !got.RawEquals(test.want) {
1868				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.want)
1869			}
1870		})
1871	}
1872
1873}
1874
1875func TestFunctionCallExprValue(t *testing.T) {
1876	funcs := map[string]function.Function{
1877		"length":     stdlib.StrlenFunc,
1878		"jsondecode": stdlib.JSONDecodeFunc,
1879	}
1880
1881	tests := map[string]struct {
1882		expr      *FunctionCallExpr
1883		ctx       *hcl.EvalContext
1884		want      cty.Value
1885		diagCount int
1886	}{
1887		"valid call with no conversions": {
1888			&FunctionCallExpr{
1889				Name: "length",
1890				Args: []Expression{
1891					&LiteralValueExpr{
1892						Val: cty.StringVal("hello"),
1893					},
1894				},
1895			},
1896			&hcl.EvalContext{
1897				Functions: funcs,
1898			},
1899			cty.NumberIntVal(5),
1900			0,
1901		},
1902		"valid call with arg conversion": {
1903			&FunctionCallExpr{
1904				Name: "length",
1905				Args: []Expression{
1906					&LiteralValueExpr{
1907						Val: cty.BoolVal(true),
1908					},
1909				},
1910			},
1911			&hcl.EvalContext{
1912				Functions: funcs,
1913			},
1914			cty.NumberIntVal(4), // length of string "true"
1915			0,
1916		},
1917		"valid call with unknown arg": {
1918			&FunctionCallExpr{
1919				Name: "length",
1920				Args: []Expression{
1921					&LiteralValueExpr{
1922						Val: cty.UnknownVal(cty.String),
1923					},
1924				},
1925			},
1926			&hcl.EvalContext{
1927				Functions: funcs,
1928			},
1929			cty.UnknownVal(cty.Number),
1930			0,
1931		},
1932		"valid call with unknown arg needing conversion": {
1933			&FunctionCallExpr{
1934				Name: "length",
1935				Args: []Expression{
1936					&LiteralValueExpr{
1937						Val: cty.UnknownVal(cty.Bool),
1938					},
1939				},
1940			},
1941			&hcl.EvalContext{
1942				Functions: funcs,
1943			},
1944			cty.UnknownVal(cty.Number),
1945			0,
1946		},
1947		"valid call with dynamic arg": {
1948			&FunctionCallExpr{
1949				Name: "length",
1950				Args: []Expression{
1951					&LiteralValueExpr{
1952						Val: cty.DynamicVal,
1953					},
1954				},
1955			},
1956			&hcl.EvalContext{
1957				Functions: funcs,
1958			},
1959			cty.UnknownVal(cty.Number),
1960			0,
1961		},
1962		"invalid arg type": {
1963			&FunctionCallExpr{
1964				Name: "length",
1965				Args: []Expression{
1966					&LiteralValueExpr{
1967						Val: cty.ListVal([]cty.Value{cty.StringVal("hello")}),
1968					},
1969				},
1970			},
1971			&hcl.EvalContext{
1972				Functions: funcs,
1973			},
1974			cty.DynamicVal,
1975			1,
1976		},
1977		"function with dynamic return type": {
1978			&FunctionCallExpr{
1979				Name: "jsondecode",
1980				Args: []Expression{
1981					&LiteralValueExpr{
1982						Val: cty.StringVal(`"hello"`),
1983					},
1984				},
1985			},
1986			&hcl.EvalContext{
1987				Functions: funcs,
1988			},
1989			cty.StringVal("hello"),
1990			0,
1991		},
1992		"function with dynamic return type unknown arg": {
1993			&FunctionCallExpr{
1994				Name: "jsondecode",
1995				Args: []Expression{
1996					&LiteralValueExpr{
1997						Val: cty.UnknownVal(cty.String),
1998					},
1999				},
2000			},
2001			&hcl.EvalContext{
2002				Functions: funcs,
2003			},
2004			cty.DynamicVal, // type depends on arg value
2005			0,
2006		},
2007		"error in function": {
2008			&FunctionCallExpr{
2009				Name: "jsondecode",
2010				Args: []Expression{
2011					&LiteralValueExpr{
2012						Val: cty.StringVal("invalid-json"),
2013					},
2014				},
2015			},
2016			&hcl.EvalContext{
2017				Functions: funcs,
2018			},
2019			cty.DynamicVal,
2020			1, // JSON parse error
2021		},
2022		"unknown function": {
2023			&FunctionCallExpr{
2024				Name: "lenth",
2025				Args: []Expression{},
2026			},
2027			&hcl.EvalContext{
2028				Functions: funcs,
2029			},
2030			cty.DynamicVal,
2031			1,
2032		},
2033	}
2034
2035	for name, test := range tests {
2036		t.Run(name, func(t *testing.T) {
2037			got, diags := test.expr.Value(test.ctx)
2038
2039			if len(diags) != test.diagCount {
2040				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
2041				for _, diag := range diags {
2042					t.Logf(" - %s", diag.Error())
2043				}
2044			}
2045
2046			if !got.RawEquals(test.want) {
2047				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.want)
2048			}
2049		})
2050	}
2051}
2052
2053func TestExpressionAsTraversal(t *testing.T) {
2054	expr, _ := ParseExpression([]byte("a.b[0][\"c\"]"), "", hcl.Pos{})
2055	traversal, diags := hcl.AbsTraversalForExpr(expr)
2056	if len(diags) != 0 {
2057		t.Fatalf("unexpected diagnostics:\n%s", diags.Error())
2058	}
2059	if len(traversal) != 4 {
2060		t.Fatalf("wrong traversal %#v; want length 3", traversal)
2061	}
2062	if traversal.RootName() != "a" {
2063		t.Errorf("wrong root name %q; want %q", traversal.RootName(), "a")
2064	}
2065	if step, ok := traversal[1].(hcl.TraverseAttr); ok {
2066		if got, want := step.Name, "b"; got != want {
2067			t.Errorf("wrong name %q for step 1; want %q", got, want)
2068		}
2069	} else {
2070		t.Errorf("wrong type %T for step 1; want %T", traversal[1], step)
2071	}
2072	if step, ok := traversal[2].(hcl.TraverseIndex); ok {
2073		if got, want := step.Key, cty.Zero; !want.RawEquals(got) {
2074			t.Errorf("wrong name %#v for step 2; want %#v", got, want)
2075		}
2076	} else {
2077		t.Errorf("wrong type %T for step 2; want %T", traversal[2], step)
2078	}
2079	if step, ok := traversal[3].(hcl.TraverseIndex); ok {
2080		if got, want := step.Key, cty.StringVal("c"); !want.RawEquals(got) {
2081			t.Errorf("wrong name %#v for step 3; want %#v", got, want)
2082		}
2083	} else {
2084		t.Errorf("wrong type %T for step 3; want %T", traversal[3], step)
2085	}
2086}
2087
2088func TestStaticExpressionList(t *testing.T) {
2089	expr, _ := ParseExpression([]byte("[0, a, true]"), "", hcl.Pos{})
2090	exprs, diags := hcl.ExprList(expr)
2091	if len(diags) != 0 {
2092		t.Fatalf("unexpected diagnostics:\n%s", diags.Error())
2093	}
2094	if len(exprs) != 3 {
2095		t.Fatalf("wrong result %#v; want length 3", exprs)
2096	}
2097	first, ok := exprs[0].(*LiteralValueExpr)
2098	if !ok {
2099		t.Fatalf("first expr has wrong type %T; want *hclsyntax.LiteralValueExpr", exprs[0])
2100	}
2101	if !first.Val.RawEquals(cty.Zero) {
2102		t.Fatalf("wrong first value %#v; want cty.Zero", first.Val)
2103	}
2104}
2105