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