class T::Types::FixedHash

in the list.
Takes a hash of types. Validates each item in an hash using the type in the same position

def describe_obj(obj)

@override Base

"Expected {a: String}, got Hash".
instead of
"Expected {a: String}, got {a: TrueClass}"
This gives us better errors, e.g.:
def describe_obj(obj)
  if obj.is_a?(Hash)
    "type {#{obj.map {|(k, v)| "#{k}: #{v.class}"}.join(', ')}}"
  else
    super
  end
end

def initialize(types)

def initialize(types)
  @types = types.each_with_object({}) {|(k, v), h| h[k] = T::Utils.coerce(v)}
end

def name

@override Base
def name
  "{#{@types.map {|(k, v)| "#{k}: #{v}"}.join(', ')}}"
end

def recursively_valid?(obj)

@override Base
def recursively_valid?(obj)
  return false unless obj.is_a?(Hash)
  return false if @types.any? {|key, type| !type.recursively_valid?(obj[key])}
  return false if obj.any? {|key, _| !@types[key]}
  true
end

def subtype_of_single?(other)

@override Base
def subtype_of_single?(other)
ther
ixedHash
ing `subtype_of?` here instead of == would be unsound
es == other.types
e

def valid?(obj)

@override Base
def valid?(obj)
  return false unless obj.is_a?(Hash)
  return false if @types.any? {|key, type| !type.valid?(obj[key])}
  return false if obj.any? {|key, _| !@types[key]}
  true
end