hashicorp/terraform · error

keys and searchset must be of the same type

Error message

keys and searchset must be of the same type

What it means

Thrown by the `matchkeys()` function (MatchkeysFunc) in its Type callback when the element types of `keys` (2nd arg) and `searchset` (3rd arg) cannot be unified via convert.UnifyUnsafe. matchkeys joins keys against searchset by equality, so their element types must be compatible; if UnifyUnsafe returns NilType the type check fails before evaluation.

Solutions

  1. Make keys and searchset the same element type, or convertible types (e.g. both list(string)).
  2. Add explicit conversion: `matchkeys(var.values, tolist(var.keys), [tostring(var.target)])`.
  3. Re-type the upstream variables so both sides agree.

Example fix

// before
locals { r = matchkeys(var.values, var.keys, [var.target_id]) }  # keys=list(string), target_id=number

// after
locals { r = matchkeys(var.values, var.keys, [tostring(var.target_id)]) }
Defensive patterns

Strategy: validation

Validate before calling

# HCL: unify types before matchkeys
locals {
  keys2      = [for k in var.keys : tostring(k)]
  searchset2 = [for s in var.searchset : tostring(s)]
  r = matchkeys(var.values, local.keys2, local.searchset2)
}

Type guard

# HCL: assert same element type
locals { ok = length(var.keys) == 0 || try(var.keys[0], null) == try(var.searchset[0], null) }

Prevention

When it happens

Trigger: Calling `matchkeys(values, keys, searchset)` where keys and searchset hold different element types, e.g. keys is list(string) and searchset is list(number).

Common situations: Building a lookup where keys were typed as strings (e.g. "1") but searchset as numbers (1), or vice versa. Inconsistent typing between two data sources joined by matchkeys.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/c2ee41ee8fd074a1. Report an issue: GitHub.

Appendix: source

Thrown at internal/lang/funcs/collection.go:352

var MatchkeysFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "values",
			Type: cty.List(cty.DynamicPseudoType),
		},
		{
			Name: "keys",
			Type: cty.List(cty.DynamicPseudoType),
		},
		{
			Name: "searchset",
			Type: cty.List(cty.DynamicPseudoType),
		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		ty, _ := convert.UnifyUnsafe([]cty.Type{args[1].Type(), args[2].Type()})
		if ty == cty.NilType {
			return cty.NilType, errors.New("keys and searchset must be of the same type")
		}

		// the return type is based on args[0] (values)
		return args[0].Type(), nil
	},
	RefineResult: refineNotNull,
	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
		if !args[0].IsKnown() {
			return cty.UnknownVal(cty.List(retType.ElementType())), nil
		}

		if args[0].LengthInt() != args[1].LengthInt() {
			return cty.ListValEmpty(retType.ElementType()), errors.New("length of keys and values should be equal")
		}

		output := make([]cty.Value, 0)
		values := args[0]

View on GitHub (pinned to d32a084675)