-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
/
union.rb
134 lines (122 loc) 路 4.49 KB
/
union.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true
# typed: true
module T::Types
# Takes a list of types. Validates that an object matches at least one of the types.
class Union < Base
# Don't use Union.new directly, use `Private::Pool.union_of_types`
# inside sorbet-runtime and `T.any` elsewhere.
def initialize(types)
@inner_types = types
end
def types
@types ||= @inner_types.flat_map do |type|
type = T::Utils.coerce(type)
if type.is_a?(Union)
# Simplify nested unions (mostly so `name` returns a nicer value)
type.types
else
type
end
end.uniq
end
def build_type
types
nil
end
# overrides Base
def name
# Use the attr_reader here so we can override it in SimplePairUnion
type_shortcuts(types)
end
private def type_shortcuts(types)
if types.size == 1
# We shouldn't generally get here but it's possible if initializing the type
# evades Sorbet's static check and we end up on the slow path, or if someone
# is using the T:Types::Union constructor directly (the latter possibility
# is why we don't just move the `uniq` into `Private::Pool.union_of_types`).
return types[0].name
end
nilable = T::Utils.coerce(NilClass)
trueclass = T::Utils.coerce(TrueClass)
falseclass = T::Utils.coerce(FalseClass)
if types.any? {|t| t == nilable}
remaining_types = types.reject {|t| t == nilable}
"T.nilable(#{type_shortcuts(remaining_types)})"
elsif types.any? {|t| t == trueclass} && types.any? {|t| t == falseclass}
remaining_types = types.reject {|t| t == trueclass || t == falseclass}
type_shortcuts([T::Private::Types::StringHolder.new("T::Boolean")] + remaining_types)
else
names = types.map(&:name).compact.sort
"T.any(#{names.join(', ')})"
end
end
# overrides Base
def recursively_valid?(obj)
types.any? {|type| type.recursively_valid?(obj)}
end
# overrides Base
def valid?(obj)
types.any? {|type| type.valid?(obj)}
end
# overrides Base
private def subtype_of_single?(other)
raise "This should never be reached if you're going through `subtype_of?` (and you should be)"
end
def unwrap_nilable
non_nil_types = types.reject {|t| t == T::Utils::Nilable::NIL_TYPE}
return nil if types.length == non_nil_types.length
case non_nil_types.length
when 0 then nil
when 1 then non_nil_types.first
else
T::Types::Union::Private::Pool.union_of_types(non_nil_types[0], non_nil_types[1], non_nil_types[2..-1])
end
end
module Private
module Pool
EMPTY_ARRAY = [].freeze
private_constant :EMPTY_ARRAY
# Try to use `to_nilable` on a type to get memoization, or failing that
# try to at least use SimplePairUnion to get faster init and typechecking.
#
# We aren't guaranteed to detect a simple `T.nilable(<Module>)` type here
# in cases where there are duplicate types, nested unions, etc.
#
# That's ok, because returning is SimplePairUnion an optimization which
# isn't necessary for correctness.
#
# @param type_a [T::Types::Base]
# @param type_b [T::Types::Base]
# @param types [Array] optional array of additional T::Types::Base instances
def self.union_of_types(type_a, type_b, types=EMPTY_ARRAY)
if !types.empty?
# Slow path
return Union.new([type_a, type_b] + types)
elsif !type_a.is_a?(T::Types::Simple) || !type_b.is_a?(T::Types::Simple)
# Slow path
return Union.new([type_a, type_b])
end
begin
if type_b == T::Utils::Nilable::NIL_TYPE
type_a.to_nilable
elsif type_a == T::Utils::Nilable::NIL_TYPE
type_b.to_nilable
else
T::Private::Types::SimplePairUnion.new(type_a, type_b)
end
rescue T::Private::Types::SimplePairUnion::DuplicateType
# Slow path
#
# This shouldn't normally be possible due to static checks,
# but we can get here if we're constructing a type dynamically.
#
# Relying on the duplicate check in the constructor has the
# advantage that we avoid it when we hit the memoized case
# of `to_nilable`.
type_a
end
end
end
end
end
end