hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.35k stars 9.49k forks source link

Call to function "merge" failed: panic #33310

Open tony-kerz opened 1 year ago

tony-kerz commented 1 year ago

Terraform Version

⋊> ~ terraform version                                                                                                                                                                20:02:16
Terraform v1.4.6
on darwin_amd64

Terraform Configuration Files

merge(false ? {a={}} : null)

Debug Output

see output below

Expected Behavior

{}

Actual Behavior

│ Error: Error in function call
│
│   on <console-input> line 1:
│   (source code not available)
│
│ Call to function "merge" failed: panic in function implementation: returned value cty.EmptyObjectVal does not conform to expected return type
│ cty.Object(map[string]cty.Type{"a":cty.EmptyObject}): missing required attribute "a"
│ goroutine 1 [running]:
│ runtime/debug.Stack()
│   /Users/runner/hostedtoolcache/go/1.19.6/x64/src/runtime/debug/stack.go:24 +0x65
│ github.com/zclconf/go-cty/cty/function.errorForPanic(...)
│   /Users/runner/go/pkg/mod/github.com/zclconf/go-cty@v1.12.1/cty/function/error.go:44
│ github.com/zclconf/go-cty/cty/function.Function.Call.func1()
│   /Users/runner/go/pkg/mod/github.com/zclconf/go-cty@v1.12.1/cty/function/function.go:294 +0x9f
│ panic({0x2f30ec0, 0xc0007ab560})
│   /Users/runner/hostedtoolcache/go/1.19.6/x64/src/runtime/panic.go:884 +0x212
│ github.com/zclconf/go-cty/cty/function.Function.Call({0x3983c40?}, {0xc000052c60?, 0x1, 0x1})
│   /Users/runner/go/pkg/mod/github.com/zclconf/go-cty@v1.12.1/cty/function/function.go:310 +0x5e5
│ github.com/hashicorp/hcl/v2/hclsyntax.(*FunctionCallExpr).Value(0xc000598870, 0xc000012c48)
│   /Users/runner/go/pkg/mod/github.com/hashicorp/hcl/v2@v2.16.2/hclsyntax/expression.go:456 +0x1c85
│ github.com/hashicorp/terraform/internal/lang.(*Scope).EvalExpr(0xc00012d5c0, {0x3982938?, 0xc000598870}, {{0x3983b98?, 0x4df6cb8?}})
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/internal/lang/eval.go:171
│ +0x148
│ github.com/hashicorp/terraform/internal/repl.(*Session).handleEval(0xc000b379e8, {0xc000056680?, 0x0?})
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/internal/repl/session.go:55
│ +0x16c
│ github.com/hashicorp/terraform/internal/repl.(*Session).Handle(0xc0002f0ff0?, {0xc000056680, 0x1c})
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/internal/repl/session.go:40
│ +0xcc
│ github.com/hashicorp/terraform/internal/command.(*ConsoleCommand).modeInteractive(0xc000ab8fc0, 0xc00055b9f8?, {0x39871d0, 0xc0002f0ff0})
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/internal/command/console_interactive.go:52
│ +0x2bc
│ github.com/hashicorp/terraform/internal/command.(*ConsoleCommand).Run(0xc000ab8fc0, {0xc000132010?, 0xffffffffffffffff?, 0x0?})
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/internal/command/console.go:163
│ +0xdfd
│ github.com/mitchellh/cli.(*CLI).Run(0xc0006c1400)
│   /Users/runner/go/pkg/mod/github.com/mitchellh/cli@v1.1.5/cli.go:262 +0x5f8
│ main.realMain()
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/main.go:315
│ +0x1614
│ main.main()
│   /Users/runner/work/_temp/actions-go-build/0.1.7/b712d316a0484806ec3fa2c744c064f8874ceff1/verification/af7ecbd19916af909cc37be5db40af0b5b375d92028f49b059a863eebacc3587/cache/source/terraform/terraform/6c2c6cfa1b55bd6ff4cf4e26ef86d8d7ab0465ec/main.go:58
│ +0x19
│ .
╵

Steps to Reproduce

terraform console
merge(false ? {a={}} : null)

Additional Context

No response

References

No response

bartier commented 1 year ago

It seems the expression.go in the hashicorp/hcl file is maybe the root cause of this:

https://github.com/hashicorp/hcl/blob/7208bce57fadb72db3a328ebc9aa86489cd06fce/hclsyntax/expression.go#L641-L665

      switch {
      // If either case is a dynamic null value (which would result from a
      // literal null in the config), we know that it can convert to the expected
      // type of the opposite case, and we don't need to speculatively reduce the
      // final result type to DynamicPseudoType.

      // If we know that either Type is a DynamicPseudoType, we can be certain
      // that the other value can convert since it's a pass-through, and we don't
      // need to unify the types. If the final evaluation results in the dynamic
      // value being returned, there's no conversion we can do, so we return the
      // value directly.
      case trueResult.RawEquals(cty.NullVal(cty.DynamicPseudoType)):
              resultType = falseResult.Type()
              convs[0] = convert.GetConversionUnsafe(cty.DynamicPseudoType, resultType)
      case falseResult.RawEquals(cty.NullVal(cty.DynamicPseudoType)):
              resultType = trueResult.Type()
              convs[1] = convert.GetConversionUnsafe(cty.DynamicPseudoType, resultType)
      case trueResult.Type() == cty.DynamicPseudoType, falseResult.Type() == cty.DynamicPseudoType:
              // the final resultType type is still unknown
              // we don't need to get the conversion, because both are a noop.

      default:
              // Try to find a type that both results can be converted to.
              resultType, convs = convert.UnifyUnsafe([]cty.Type{trueResult.Type(), falseResult.Type()})
      }

It is going to get into the second case statement and the resultType will not match null, because the true result type has the a key in it. e.g.: merge(false ? {a={}} : null)

Also, it seems to me that if the resultType is falseResult.Type() then this should not through the error anymore. Not sure if this approach would cause undesired side-effects.

I was just looking for the codebase to learn more Golang. @jbardin If you think this could be a issue for outsiders of Core Team, just let me know more context so I can try to help.