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