terraform icon indicating copy to clipboard operation
terraform copied to clipboard

`element types must all match for conversion` when using matching type but from module output

Open theherk opened this issue 2 years ago • 3 comments

Terraform Version

ᐅ terraform -version
Terraform v1.5.7
on darwin_arm64

Terraform Configuration Files

main.tf

locals {
  things = [
    {
      thing = {}
    },
    {
      thing = {} # works
      # thing = module.data.thing # yields: element types must all match for conversion
    }
  ]
}

module "data" {
  source = "./data"
}

module "things" {
  source = "./things"
  in     = local.things
}

output "things" {
  value = module.things
}

output "comparison" {
  value = {
    from_local  = local.things[1].thing
    from_module = module.data.thing
  }
}

data/main.tf

output "thing" {
  value = {}
}

things/main.tf

variable "in" {
  type = list(any)
}

output "out" {
  value = var.in
}

Debug Output

debug output

Expected Behavior

I have tried my best to distill the issue down to a minimal reproducible issue. Essentially, when I'm passing a variable to a module that has a type of list(any) the types of the contained values have to match; completely normal. If I define the value locally, great. However, if I use a value given as an output in another module, terraform complains that the types don't match.

Actual Behavior

As I illustrate in the example, terraform complains that the types don't match, but if you look at the planned output for comparison, they sure look like the same type to me.

ᐅ TF_LOG=trace TF_LOG_PATH=tf-trace-(date +%FT%T+01).log terraform plan -lock=false

Changes to Outputs:
  + comparison = {
      + from_local  = {}
      + from_module = {}
    }
  + things     = {
      + out = [
          + {
              + thing = {}
            },
          + {
              + thing = {}
            },
        ]
    }

Steps to Reproduce

  1. Create these files as given.
  2. terraform init
  3. terraform plan
  4. Comment L7.
  5. Uncomment L8.
  6. Observe the new error.

Additional Context

No response

References

No response

theherk avatar Sep 19 '23 15:09 theherk

Thanks for the example @theherk! I'm not sure how this hasn't cropped up before, but it is a bit of an odd case. The failure is happening during validation, where runtime values are all considered as unknown, so the list is being created with an empty object and a dynamic value, which can't convert to a single type. While the static module output in this example makes the type obvious, in most cases the output type is not going to be known during validation, so deriving a correctly typed value may not be possible, and will probably require output type declarations as well.

In the meantime, using a more precise type for the module input usually will help with the type inference problems. You usually don't want something like list(any), because while that allows the list to contain a single element type which is unknown, it makes the type inference more difficult because that single type could literally be "anything".

jbardin avatar Sep 19 '23 18:09 jbardin

In some cases, there are uses for any. This, I believe is one of them, though I admit that isn't clear in the contrived example. I don't understand what is unknown though, at least in this case. The output is a concrete empty map; nothing dynamic as you point out. So, I'm still unclear on why validation can't be done when the value can be known.

Are output type declarations forthcoming? That is an interesting development. Thank you.

theherk avatar Sep 19 '23 19:09 theherk

Validation is done using unknown values wherever possible, because we are statically validating the configuration, and want to cover all possible inputs and values for variables and resources. Yes, the output is static here, and an exception could probably be added to the configuration to record the type in this particular case; but that is rare in practice, and does not work if the module is expanded or the type is inferred from other dynamic values.

Being a contrived example it's hard to say what may be optimal, but using list(any) is the what sets up the problem. Using that type constraint gives you very little extra assurance since the element type is still unknown and types are invariant, so it may be more useful if you simply use any instead if you can't declare a static type for the value.

Output type declarations are something being considered, but there is no set timeline as of yet.

jbardin avatar Sep 25 '23 16:09 jbardin