lib/types/types/fixed_array.rb



# frozen_string_literal: true
# https://jira.corp.stripe.com/browse/RUBYPLAT-1107
# typed: false

module T::Types
  # Takes a list of types. Validates each item in an array using the type in the same position
  # in the list.
  class FixedArray < Base
    def initialize(types)
      @inner_types = types
    end

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

    def build_type
      types
      nil
    end

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

    # overrides 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

    # overrides 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

    # overrides Base
    private def subtype_of_single?(other)
      case other
      when FixedArray
        # Properly speaking, covariance here is unsound since arrays
        # can be mutated, but sorbet implements covariant tuples for
        # ease of adoption.
        types.size == other.types.size && types.zip(other.types).all? do |t1, t2|
          t1.subtype_of?(t2)
        end
      when TypedArray
        # warning: covariant arrays

        value1, value2, *values_rest = types
        value_type = if !value2.nil?
          T::Types::Union::Private::Pool.union_of_types(value1, value2, values_rest)
        elsif value1.nil?
          T.untyped
        else
          value1
        end

        T::Types::TypedArray.new(value_type).subtype_of?(other)
      else
        false
      end
    end

    # This gives us better errors, e.g.:
    # "Expected [String, Symbol], got [String, String]"
    # instead of
    # "Expected [String, Symbol], got Array".
    #
    # overrides Base
    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
  end
end