class T::Types::TypedEnumerable
error messages.
‘case` statement below in `describe_obj` in order to get better
Note: All subclasses of Enumerable should add themselves to the
def build_type
def build_type type nil end
def describe_obj(obj)
def describe_obj(obj) return super unless obj.is_a?(Enumerable) type_from_instance(obj).name end
def initialize(type)
def initialize(type) @inner_type = type end
def name
def name "T::Enumerable[#{type.name}]" end
def recursively_valid?(obj)
def recursively_valid?(obj) return false unless obj.is_a?(Enumerable) case obj when Array begin it = 0 while it < obj.count return false unless type.recursively_valid?(obj[it]) it += 1 end true end when Hash type_ = self.type return false unless type_.is_a?(FixedArray) key_type, value_type = type_.types return false if key_type.nil? || value_type.nil? || type_.types.size > 2 obj.each_pair do |key, val| # Some objects (I'm looking at you Rack::Utils::HeaderHash) don't # iterate over a [key, value] array, so we can't just use the type.recursively_valid?(v) return false if !key_type.recursively_valid?(key) || !value_type.recursively_valid?(val) end true when Enumerator::Lazy # Enumerators can be unbounded: see `[:foo, :bar].cycle` true when Enumerator::Chain # Enumerators can be unbounded: see `[:foo, :bar].cycle` true when Enumerator # Enumerators can be unbounded: see `[:foo, :bar].cycle` true when Range # A nil beginning or a nil end does not provide any type information. That is, nil in a range represents # boundlessness, it does not express a type. For example `(nil...nil)` is not a T::Range[NilClass], its a range # of unknown types (T::Range[T.untyped]). # Similarly, `(nil...1)` is not a `T::Range[T.nilable(Integer)]`, it's a boundless range of Integer. (obj.begin.nil? || type.recursively_valid?(obj.begin)) && (obj.end.nil? || type.recursively_valid?(obj.end)) when Set obj.each do |item| return false unless type.recursively_valid?(item) end true else # We don't check the enumerable since it isn't guaranteed to be # rewindable (e.g. STDIN) and it may be expensive to enumerate # (e.g. an enumerator that makes an HTTP request)" true end end
def subtype_of_single?(other)
def subtype_of_single?(other) er.class <= TypedEnumerable && erlying_class <= other.underlying_class umerables are covariant because they are read only operly speaking, many Enumerable subtypes (e.g. Set) ould be invariant because they are mutable and support th reading and writing. However, Sorbet treats *all* umerable subclasses as covariant for ease of adoption. .subtype_of?(other.type) other.class <= Simple rlying_class <= other.raw_type e
def type
def type @type ||= T::Utils.coerce(@inner_type) end
def type_from_instance(obj)
def type_from_instance(obj) ue, false].include?(obj) rn T::Boolean !obj.is_a?(Enumerable) rn obj.class bj rray rray[type_from_instances(obj)] ash rred_key = type_from_instances(obj.keys) rred_val = type_from_instances(obj.values) ash[inferred_key, inferred_val] ange can't get any information from `NilClass` in ranges (since nil is used to represent boundlessness). able_objects = [obj.begin, obj.end].compact ypeable_objects.empty? :Range[T.untyped] :Range[type_from_instances(typeable_objects)] numerator::Lazy numerator::Lazy[type_from_instances(obj)] numerator::Chain numerator::Chain[type_from_instances(obj)] numerator numerator[type_from_instances(obj)] et et[type_from_instances(obj)] O ort circuit for anything IO-like (File, etc.). In these cases, umerating the object is a destructive operation and might hang. class is is a specialized enumerable type, just return the class. ::Configuration::AT_LEAST_RUBY_2_7 ject.instance_method(:class).bind_call(obj) ject.instance_method(:class).bind(obj).call # rubocop:disable Performance/BindCall
def type_from_instances(objs)
def type_from_instances(objs) objs.class unless objs.is_a?(Enumerable) ed_types = [] .each do |x| tained_types << type_from_instance(x) rn T.untyped # all we can do is go with the types we have so far ained_types.count > 1 ltiple kinds of bad types showed up, we'll suggest a union pe you might want. n.new(obtained_types) obtained_types.empty? return erything was the same bad type, lets just show that ined_types.first
def underlying_class
def underlying_class Enumerable end
def valid?(obj)
def valid?(obj) obj.is_a?(Enumerable) end