puppetlabs/puppet · error · TypeConversionError

Value of type %{type} cannot be converted to Hash

Error message

Value of type %{type} cannot be converted to Hash

What it means

Hash.new converts a Hash directly, an Array via flattening, and any other value only if it is iterable (PIterableType::DEFAULT.instance?(from)). A scalar such as an Integer, Boolean or Undef cannot become a hash, so the conversion raises TypeConversionError naming the inferred type of the value. The guard runs before any partial result is produced.

Source

Thrown at lib/puppet/pops/types/types.rb:2861

        case from
        when Array
          if from.size == 0
            {}
          else
            unless from.size.even?
              raise TypeConversionError, _('odd number of arguments for Hash')
            end

            Hash[*from]
          end
        when Hash
          from
        else
          if PIterableType::DEFAULT.instance?(from)
            Hash[*Iterable.on(from).to_a]
          else
            t = TypeCalculator.singleton.infer(from).generalize
            raise TypeConversionError, _("Value of type %{type} cannot be converted to Hash") % { type: t }
          end
        end
      end
    end
  end

  DEFAULT = PHashType.new(nil, nil)
  KEY_PAIR_TUPLE_SIZE = PIntegerType.new(2, 2)
  DEFAULT_KEY_PAIR_TUPLE = PTupleType.new([PUnitType::DEFAULT, PUnitType::DEFAULT], KEY_PAIR_TUPLE_SIZE)
  EMPTY = PHashType.new(PUnitType::DEFAULT, PUnitType::DEFAULT, PIntegerType.new(0, 0))

  protected

  # Hash is assignable if o is a Hash and o's key and element types are assignable
  # @api private
  def _assignable?(o, guard)
    case o
    when PHashType

View on GitHub (pinned to e227c27540)

Solutions

  1. Declare the parameter as Hash in the class/defined type so validation fails at compile time with a clear message.
  2. Give the parameter a Hash default and merge instead of converting: $opts = {}.merge($options).
  3. Convert explicitly only after checking the input is a Hash or an even-length Array.

Example fix

# before (Puppet DSL)
define foo($options) {
  $h = Hash.new($options)   # TypeConversionError when $options is a scalar
}

# after
define foo(Hash $options = {}) {
  $h = $options   # bad shapes are rejected at compile time
}
Defensive patterns

Strategy: type-guard

Validate before calling

# Ruby
ok = value.is_a?(Hash) || (value.is_a?(Array) && value.size.even?)

# Puppet DSL
unless $x =~ Variant[Hash, Array] { fail('expected Hash or Array') }

Type guard

def hash_convertible?(v)
  v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(Enumerable)
end

Try / catch

begin
  Hash.new($x)
rescue Puppet::Error => e
  fail("cannot convert ${x} to Hash: ${e.message}")
end

Prevention

When it happens

Trigger: Hash.new(42), Hash.new(true) or Hash.new(undef) in a manifest; a variable that is sometimes a scalar flowing into a hash-conversion chain; Ruby-side conversion of a non-iterable object via the Hash type's new function.

Common situations: Unvalidated module parameters where the user supplies a string or number where a hash was expected; Hiera lookups returning scalars on fallback paths; data-shape assumptions breaking across module versions.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/7c12a4fc92889758. Report an issue: GitHub.