class T::Types::FixedArray

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

def describe_obj(obj)

@override Base

"Expected [String, Symbol], got Array".
instead of
"Expected [String, Symbol], got [String, String]"
This gives us better errors, e.g.:
def describe_obj(obj)
  if obj.is_a?(Array)
    if obj.length == @types.length
      item_classes = obj.map(&:class).join(', ')
      "type [#{item_classes}]"
    else
      "array of size #{obj.length}"
    end
  else
    super
  end
end

def initialize(types)

def initialize(types)
  @types = types.map {|type| T::Utils.coerce(type)}
end

def name

@override Base
def name
  "[#{@types.join(', ')}]"
end

def recursively_valid?(obj)

@override Base
def recursively_valid?(obj)
  if obj.is_a?(Array) && obj.length == @types.length
    i = 0
    while i < @types.length
      if !@types[i].recursively_valid?(obj[i])
        return false
      end
      i += 1
    end
    true
  else
    false
  end
end

def subtype_of_single?(other)

@override Base
def subtype_of_single?(other)
ther
ixedArray
operly speaking, covariance here is unsound since arrays
n be mutated, but sorbet implements covariant tuples for
se of adoption.
es.size == other.types.size && @types.zip(other.types).all? do |t1, t2|
.subtype_of?(t2)
e

def valid?(obj)

@override Base
def valid?(obj)
  if obj.is_a?(Array) && obj.length == @types.length
    i = 0
    while i < @types.length
      if !@types[i].valid?(obj[i])
        return false
      end
      i += 1
    end
    true
  else
    false
  end
end