class T::Types::Base
def self.method_added(method_name)
def self.method_added(method_name) super(method_name) # What is now `subtype_of_single?` used to be named `subtype_of?`. Make sure people don't # override the wrong thing. # # NB: Outside of T::Types, we would enforce this by using `sig` and not declaring the method # as overridable, but doing so here would result in a dependency cycle. if method_name == :subtype_of? && self != T::Types::Base raise "`subtype_of?` should not be overridden. You probably want to override " \ "`subtype_of_single?` instead." end end
def ==(other)
def ==(other) (T::Utils.resolve_alias(other).class == T::Utils.resolve_alias(self).class) && other.name == self.name end
def describe_obj(obj)
def describe_obj(obj) # Would be redundant to print class and value in these common cases. case obj when nil, true, false return "type #{obj.class}" end # In rare cases, obj.inspect may fail, or be undefined, so rescue. begin # Default inspect behavior of, eg; `#<Object:0x0...>` is ugly; just print the hash instead, which is more concise/readable. if obj.method(:inspect).owner == Kernel "type #{obj.class} with hash #{obj.hash}" elsif T::Configuration.include_value_in_type_errors? "type #{obj.class} with value #{T::Utils.string_truncate_middle(obj.inspect, 30, 30)}" else "type #{obj.class}" end rescue StandardError, SystemStackError "type #{obj.class} with unprintable value" end end
def error_message_for_obj(obj)
def error_message_for_obj(obj) if valid?(obj) nil else "Expected type #{self.name}, got #{describe_obj(obj)}" end end
def error_message_for_obj_recursive(obj)
def error_message_for_obj_recursive(obj) if recursively_valid?(obj) nil else "Expected type #{self.name}, got #{describe_obj(obj)}" end end
def hash
def hash name.hash end
def name
def name raise NotImplementedError end
def recursively_valid?(obj)
def recursively_valid?(obj) valid?(obj) end
def subtype_of?(t2)
This method cannot be overridden (see `method_added` above).
See https://git.corp.stripe.com/stripe-internal/ruby-typer/blob/9fc8ed998c04ac0b96592ae6bb3493b8a925c5c1/core/types/subtyping.cc#L912-L950
Mirrors ruby_typer::core::Types::isSubType
def subtype_of?(t2) t1 = self if t2.is_a?(T::Private::Types::TypeAlias) t2 = t2.aliased_type end if t1.is_a?(T::Private::Types::TypeAlias) return t1.aliased_type.subtype_of?(t2) end # pairs to cover: 1 (_, _) # 2 (_, And) # 3 (_, Or) # 4 (And, _) # 5 (And, And) # 6 (And, Or) # 7 (Or, _) # 8 (Or, And) # 9 (Or, Or) # Note: order of cases here matters! if t1.is_a?(T::Types::Union) # 7, 8, 9 # this will be incorrect if/when we have Type members return t1.types.all? {|t1_member| t1_member.subtype_of?(t2)} end if t2.is_a?(T::Types::Intersection) # 2, 5 # this will be incorrect if/when we have Type members return t2.types.all? {|t2_member| t1.subtype_of?(t2_member)} end if t2.is_a?(T::Types::Union) if t1.is_a?(T::Types::Intersection) # 6 # dropping either of parts eagerly make subtype test be too strict. # we have to try both cases, when we normally try only one return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} || t1.types.any? {|t1_member| t1_member.subtype_of?(t2)} end return t2.types.any? {|t2_member| t1.subtype_of?(t2_member)} # 3 end if t1.is_a?(T::Types::Intersection) # 4 # this will be incorrect if/when we have Type members return t1.types.any? {|t1_member| t1_member.subtype_of?(t2)} end # 1; Start with some special cases if t1.is_a?(T::Private::Types::Void) return t2.is_a?(T::Private::Types::Void) end if t1.is_a?(T::Types::Untyped) || t2.is_a?(T::Types::Untyped) return true end # Rest of (1) subtype_of_single?(t2) end
def subtype_of_single?(type)
-
(T::Boolean)
- This method must be implemented to return whether the subclass is a subtype
def subtype_of_single?(type) NotImplementedError
def to_s
def to_s name end
def valid?(obj)
def valid?(obj) raise NotImplementedError end
def validate!(obj)
def validate!(obj) err = error_message_for_obj(obj) raise TypeError.new(err) if err end