Overcoming inconsistent types in ternary

Code is at handle presets and custom · GitHub

I’m trying to handle having presets that people can choose from for aws s3 bucket object lifecycle rules, as well as being able to specify custom rules, or have no rules. When I plan the code I get:

Error: Inconsistent conditional result types

  on main.tf line 83, in locals:
  83:   lifecycle_object_rules = length(var.lifecycle_object_rules) > 0 ? var.lifecycle_object_rules : local.lifecycle_presets[var.object_lifecycle_preset]
    ├────────────────
    │ local.lifecycle_presets is object with 2 attributes
    │ var.lifecycle_object_rules is empty list of object
    │ var.object_lifecycle_preset is "default"

The true and false result expressions must have consistent types. The 'true' value is list of object, but the 'false' value is tuple.

I’ve tried several ways to try to overcome this but have run out of ideas. Any advice?

I was curious, and tried out your code. I happened to have Terraform 1.3.2 installed. With that version, it just worked, as is.

I’m running terraform 1.3.4 and i get the error above when running a plan.

Interesting. I tested 1.3.3, it works there too. It seems like this functionality has regressed in 1.3.4. This is probably something best reported via a GitHub issue.

huh, I didn’t try it with older versions. Thanks!

Hi @grimm26,

I’m not sure what’s going on with the latest versions of Terraform – it does seem like this way somehow incorrectly working on the early v1.3 releases, and has now been fixed? – but I think this error is correct because the object values in your local values don’t have the same object types as each other and as the input variable.

For this to work you will need to make all of your objects have consistent attribute names and types. The optional attributes feature allows callers to omit them and have the default values inserted automatically, but the local values are inside your module so you will need to populate the defaults explicitly yourself to make the types match.

There’s something really interesting and bizarre going on with Terraform 1.3.3 and earlier, with this example - the attribute defaults that are applied to the variable in the ‘true’ side of the ternary conditional, are somehow influencing the evaluated result from the ‘false’ side of the ternary conditional!

Like this: handle presets and custom v2 · GitHub

Still doesn’t work:

╷
│ Error: Inconsistent conditional result types
│ 
│   on main.tf line 99, in locals:
│   99:   lifecycle_object_rules = length(var.lifecycle_object_rules) > 0 ? var.lifecycle_object_rules : local.lifecycle_presets[var.object_lifecycle_preset]
│     ├────────────────
│     │ local.lifecycle_presets is object with 2 attributes
│     │ var.object_lifecycle_preset is "default"
│ 
│ The false result value has the wrong type: element types must all match for conversion to list.

To get around this for now I am instead doing lifecycle_object_rules = try(coalescelist(var.lifecycle_object_rules, local.lifecycle_presets[var.object_lifecycle_preset]), [])

I think what’s going on with those early v1.3 patch releases is that there were some bugs where the optional attribute annotations were accidentally “infecting” derived results.

The bugs we already knew about and fixed for that were panics where code elsewhere in the language didn’t understand how to deal with the extra metadata at all. But the conditional operator is internally using the type conversion logic which is the part of the runtime that handles optional attributes and so I suppose in retrospect it makes sense that it would end up inadvertently trying to do something with that unexpected extra metadata, leading to odd results.

The fixes have been to make sure the conversion logic strips out the optional attributes metadata after the conversion is complete, so downstream operations don’t get confused by it. Although we didn’t know yet about this bug, it does make sense that the fixes for the crashes also fixed this misbehavior with the conversion operator and restored the intended type checking rules.

The problem is that, for all intents and purposes, the two data structures that I am using in the true and false sections of the conditional are functionally the same. In my second example, I can’t even figure out what it wants so I had to do a workaround that doesn’t account for all cases.

I think Terraform is having trouble understanding your intention because null alone doesn’t provide any type information, and there isn’t enough context here for Terraform to infer what type is intended.

I think it’ll help to provide some additional type information for the null values so Terraform can understand what object type you are intending:

  lifecycle_rule_template = {
    id                                     = tostring(null)
    prefix                                 = tostring(null)
    tags                                   = tomap({})
    enabled                                = tobool(null)
    transitions                            = tomap({})
    object_size_greater_than               = tonumber(null)
    object_size_less_than                  = tonumber(null)
    abort_incomplete_multipart_upload_days = tonumber(null)
    noncurrent_transitions                 = tomap({})
    transitions                            = tomap({})
  }

Unfortunately tomap({}) only tells Terraform that you intend this to be a map of something, but doesn’t say what the map type is. However, if all of the other types match it will hopefully give Terraform enough information to infer what element types you are intending by unification with the values specified elsewhere.

Other ways to give Terraform more information:

  • Use tomap to explicitly convert lifecycle_presets as a whole to a map.
  • Use tolist around each of the tuples in your lifecycle_presets element to tell Terraform that you intend these to be lists of objects rather than tuples of objects.
  • Use tomap around the object literals that you’re using to populate the map attributes, such as transitions and noncurrent_transitions, to force Terraform to convert them to maps earlier.

There are some other small inconsistencies that may also be adding confusion into the mix. For example, in your input variable you declared transitions and nonconcurrent_transitions as map(string), but the value you assigned uses numbers instead of strings and so tomap would convert it to map(number) instead of map(string), making it not match.

The goal here is to give Terraform as much type information as possible so that the automatic type conversion system doesn’t have so many unknowns to deal with. Terraform is capable of a number of automatic type conversions in isolation, but when given so many to do at the same time it’s often unclear which ones will lead to a viable outcome. Being as explicit and consistent as possible will minimize the unknowns.

1 Like

Thanks for the tips. I’ll play with this a bit more.