sorbet-runtime 0.5.10439 → 0.5.11120
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sorbet-runtime.rb +7 -1
- data/lib/types/_types.rb +57 -3
- data/lib/types/compatibility_patches.rb +4 -2
- data/lib/types/enum.rb +6 -1
- data/lib/types/generic.rb +2 -0
- data/lib/types/private/abstract/declare.rb +10 -9
- data/lib/types/private/casts.rb +4 -1
- data/lib/types/private/class_utils.rb +13 -6
- data/lib/types/private/methods/_methods.rb +37 -12
- data/lib/types/private/methods/call_validation.rb +104 -5
- data/lib/types/private/methods/call_validation_2_6.rb +68 -60
- data/lib/types/private/methods/call_validation_2_7.rb +68 -60
- data/lib/types/private/methods/decl_builder.rb +21 -6
- data/lib/types/private/methods/signature.rb +63 -38
- data/lib/types/private/methods/signature_validation.rb +68 -6
- data/lib/types/private/runtime_levels.rb +19 -0
- data/lib/types/private/types/not_typed.rb +2 -0
- data/lib/types/private/types/simple_pair_union.rb +55 -0
- data/lib/types/private/types/void.rb +29 -23
- data/lib/types/props/_props.rb +2 -2
- data/lib/types/props/custom_type.rb +2 -2
- data/lib/types/props/decorator.rb +41 -36
- data/lib/types/props/has_lazily_specialized_methods.rb +2 -2
- data/lib/types/props/pretty_printable.rb +45 -83
- data/lib/types/props/private/setter_factory.rb +1 -1
- data/lib/types/props/serializable.rb +17 -9
- data/lib/types/props/type_validation.rb +5 -2
- data/lib/types/struct.rb +2 -2
- data/lib/types/types/anything.rb +31 -0
- data/lib/types/types/base.rb +15 -1
- data/lib/types/types/class_of.rb +11 -0
- data/lib/types/types/enum.rb +1 -1
- data/lib/types/types/fixed_array.rb +13 -0
- data/lib/types/types/fixed_hash.rb +22 -0
- data/lib/types/types/intersection.rb +1 -1
- data/lib/types/types/noreturn.rb +0 -1
- data/lib/types/types/simple.rb +27 -4
- data/lib/types/types/type_parameter.rb +19 -0
- data/lib/types/types/typed_array.rb +29 -0
- data/lib/types/types/typed_class.rb +85 -0
- data/lib/types/types/typed_enumerable.rb +7 -0
- data/lib/types/types/typed_enumerator_chain.rb +41 -0
- data/lib/types/types/union.rb +50 -14
- data/lib/types/utils.rb +41 -33
- metadata +14 -10
@@ -16,6 +16,7 @@ module T::Props::TypeValidation
|
|
16
16
|
super || key == :DEPRECATED_underspecified_type
|
17
17
|
end
|
18
18
|
|
19
|
+
# checked(:never) - Rules hash is expensive to check
|
19
20
|
sig do
|
20
21
|
params(
|
21
22
|
name: T.any(Symbol, String),
|
@@ -24,12 +25,13 @@ module T::Props::TypeValidation
|
|
24
25
|
type: T.any(T::Types::Base, Module)
|
25
26
|
)
|
26
27
|
.void
|
28
|
+
.checked(:never)
|
27
29
|
end
|
28
30
|
def prop_validate_definition!(name, _cls, rules, type)
|
29
31
|
super
|
30
32
|
|
31
33
|
if !rules[:DEPRECATED_underspecified_type]
|
32
|
-
validate_type(type,
|
34
|
+
validate_type(type, name)
|
33
35
|
elsif rules[:DEPRECATED_underspecified_type] && find_invalid_subtype(type).nil?
|
34
36
|
raise ArgumentError.new("DEPRECATED_underspecified_type set unnecessarily for #{@class.name}.#{name} - #{type} is a valid type")
|
35
37
|
end
|
@@ -41,8 +43,9 @@ module T::Props::TypeValidation
|
|
41
43
|
field_name: T.any(Symbol, String),
|
42
44
|
)
|
43
45
|
.void
|
46
|
+
.checked(:never)
|
44
47
|
end
|
45
|
-
private def validate_type(type, field_name
|
48
|
+
private def validate_type(type, field_name)
|
46
49
|
if (invalid_subtype = find_invalid_subtype(type))
|
47
50
|
raise UnderspecifiedType.new(type_error_message(invalid_subtype, field_name, type))
|
48
51
|
end
|
data/lib/types/struct.rb
CHANGED
@@ -10,7 +10,7 @@ end
|
|
10
10
|
class T::Struct < T::InexactStruct
|
11
11
|
def self.inherited(subclass)
|
12
12
|
super(subclass)
|
13
|
-
T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited) do |s|
|
13
|
+
T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited, true) do |s|
|
14
14
|
super(s)
|
15
15
|
raise "#{self.name} is a subclass of T::Struct and cannot be subclassed"
|
16
16
|
end
|
@@ -23,7 +23,7 @@ class T::ImmutableStruct < T::InexactStruct
|
|
23
23
|
def self.inherited(subclass)
|
24
24
|
super(subclass)
|
25
25
|
|
26
|
-
T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited) do |s|
|
26
|
+
T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited, true) do |s|
|
27
27
|
super(s)
|
28
28
|
raise "#{self.name} is a subclass of T::ImmutableStruct and cannot be subclassed"
|
29
29
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module T::Types
|
5
|
+
# The top type
|
6
|
+
class Anything < Base
|
7
|
+
def initialize; end
|
8
|
+
|
9
|
+
# overrides Base
|
10
|
+
def name
|
11
|
+
"T.anything"
|
12
|
+
end
|
13
|
+
|
14
|
+
# overrides Base
|
15
|
+
def valid?(obj)
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
# overrides Base
|
20
|
+
private def subtype_of_single?(other)
|
21
|
+
case other
|
22
|
+
when T::Types::Anything then true
|
23
|
+
else false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Private
|
28
|
+
INSTANCE = Anything.new.freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/types/types/base.rb
CHANGED
@@ -50,10 +50,20 @@ module T::Types
|
|
50
50
|
t2 = t2.aliased_type
|
51
51
|
end
|
52
52
|
|
53
|
+
if t2.is_a?(T::Types::Anything)
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
53
57
|
if t1.is_a?(T::Private::Types::TypeAlias)
|
54
58
|
return t1.aliased_type.subtype_of?(t2)
|
55
59
|
end
|
56
60
|
|
61
|
+
if t1.is_a?(T::Types::TypeVariable) || t2.is_a?(T::Types::TypeVariable)
|
62
|
+
# Generics are erased at runtime. Let's treat them like `T.untyped` for
|
63
|
+
# the purpose of things like override checking.
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
|
57
67
|
# pairs to cover: 1 (_, _)
|
58
68
|
# 2 (_, And)
|
59
69
|
# 3 (_, Or)
|
@@ -163,8 +173,12 @@ module T::Types
|
|
163
173
|
# Type equivalence, defined by serializing the type to a string (with
|
164
174
|
# `#name`) and comparing the resulting strings for equality.
|
165
175
|
def ==(other)
|
166
|
-
|
176
|
+
case other
|
177
|
+
when T::Types::Base
|
167
178
|
other.name == self.name
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
168
182
|
end
|
169
183
|
|
170
184
|
alias_method :eql?, :==
|
data/lib/types/types/class_of.rb
CHANGED
@@ -27,6 +27,8 @@ module T::Types
|
|
27
27
|
@type <= other.type
|
28
28
|
when Simple
|
29
29
|
@type.is_a?(other.raw_type)
|
30
|
+
when TypedClass
|
31
|
+
true
|
30
32
|
else
|
31
33
|
false
|
32
34
|
end
|
@@ -36,5 +38,14 @@ module T::Types
|
|
36
38
|
def describe_obj(obj)
|
37
39
|
obj.inspect
|
38
40
|
end
|
41
|
+
|
42
|
+
# So that `T.class_of(...)[...]` syntax is valid.
|
43
|
+
# Mirrors the definition of T::Generic#[] (generics are erased).
|
44
|
+
#
|
45
|
+
# We avoid simply writing `include T::Generic` because we don't want any of
|
46
|
+
# the other methods to appear (`T.class_of(A).type_member` doesn't make sense)
|
47
|
+
def [](*types)
|
48
|
+
self
|
49
|
+
end
|
39
50
|
end
|
40
51
|
end
|
data/lib/types/types/enum.rb
CHANGED
@@ -59,6 +59,19 @@ module T::Types
|
|
59
59
|
@types.size == other.types.size && @types.zip(other.types).all? do |t1, t2|
|
60
60
|
t1.subtype_of?(t2)
|
61
61
|
end
|
62
|
+
when TypedArray
|
63
|
+
# warning: covariant arrays
|
64
|
+
|
65
|
+
value1, value2, *values_rest = types
|
66
|
+
value_type = if !value2.nil?
|
67
|
+
T::Types::Union::Private::Pool.union_of_types(value1, value2, values_rest)
|
68
|
+
elsif value1.nil?
|
69
|
+
T.untyped
|
70
|
+
else
|
71
|
+
value1
|
72
|
+
end
|
73
|
+
|
74
|
+
T::Types::TypedArray.new(value_type).subtype_of?(other)
|
62
75
|
else
|
63
76
|
false
|
64
77
|
end
|
@@ -38,6 +38,28 @@ module T::Types
|
|
38
38
|
when FixedHash
|
39
39
|
# Using `subtype_of?` here instead of == would be unsound
|
40
40
|
@types == other.types
|
41
|
+
when TypedHash
|
42
|
+
# warning: covariant hashes
|
43
|
+
|
44
|
+
key1, key2, *keys_rest = types.keys.map {|key| T::Utils.coerce(key.class)}
|
45
|
+
key_type = if !key2.nil?
|
46
|
+
T::Types::Union::Private::Pool.union_of_types(key1, key2, keys_rest)
|
47
|
+
elsif key1.nil?
|
48
|
+
T.untyped
|
49
|
+
else
|
50
|
+
key1
|
51
|
+
end
|
52
|
+
|
53
|
+
value1, value2, *values_rest = types.values
|
54
|
+
value_type = if !value2.nil?
|
55
|
+
T::Types::Union::Private::Pool.union_of_types(value1, value2, values_rest)
|
56
|
+
elsif value1.nil?
|
57
|
+
T.untyped
|
58
|
+
else
|
59
|
+
value1
|
60
|
+
end
|
61
|
+
|
62
|
+
T::Types::TypedHash.new(keys: key_type, values: value_type).subtype_of?(other)
|
41
63
|
else
|
42
64
|
false
|
43
65
|
end
|
data/lib/types/types/noreturn.rb
CHANGED
data/lib/types/types/simple.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
module T::Types
|
5
5
|
# Validates that an object belongs to the specified class.
|
6
6
|
class Simple < Base
|
7
|
+
NAME_METHOD = Module.instance_method(:name)
|
8
|
+
private_constant(:NAME_METHOD)
|
9
|
+
|
7
10
|
attr_reader :raw_type
|
8
11
|
|
9
12
|
def initialize(raw_type)
|
@@ -16,7 +19,7 @@ module T::Types
|
|
16
19
|
#
|
17
20
|
# `name` isn't normally a hot path for types, but it is used in initializing a T::Types::Union,
|
18
21
|
# and so in `T.nilable`, and so in runtime constructions like `x = T.let(nil, T.nilable(Integer))`.
|
19
|
-
@name ||= @raw_type.name.freeze
|
22
|
+
@name ||= (NAME_METHOD.bind(@raw_type).call || @raw_type.name).freeze
|
20
23
|
end
|
21
24
|
|
22
25
|
# overrides Base
|
@@ -29,6 +32,11 @@ module T::Types
|
|
29
32
|
case other
|
30
33
|
when Simple
|
31
34
|
@raw_type <= other.raw_type
|
35
|
+
when TypedClass
|
36
|
+
# This case is a bit odd--we would have liked to solve this like we do
|
37
|
+
# for `T::Array` et al., but don't for backwards compatibility.
|
38
|
+
# See `type_for_module` below.
|
39
|
+
@raw_type <= other.underlying_class
|
32
40
|
else
|
33
41
|
false
|
34
42
|
end
|
@@ -52,11 +60,21 @@ module T::Types
|
|
52
60
|
end
|
53
61
|
|
54
62
|
def to_nilable
|
55
|
-
@nilable ||= T::Types::
|
63
|
+
@nilable ||= T::Private::Types::SimplePairUnion.new(
|
64
|
+
self,
|
65
|
+
T::Utils::Nilable::NIL_TYPE,
|
66
|
+
)
|
56
67
|
end
|
57
68
|
|
58
69
|
module Private
|
59
70
|
module Pool
|
71
|
+
CACHE_FROZEN_OBJECTS = begin
|
72
|
+
ObjectSpace::WeakMap.new[1] = 1
|
73
|
+
true # Ruby 2.7 and newer
|
74
|
+
rescue ArgumentError # Ruby 2.6 and older
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
60
78
|
@cache = ObjectSpace::WeakMap.new
|
61
79
|
|
62
80
|
def self.type_for_module(mod)
|
@@ -76,16 +94,21 @@ module T::Types
|
|
76
94
|
elsif !Object.autoload?(:Set) && Object.const_defined?(:Set) && mod == ::Set
|
77
95
|
T::Set[T.untyped]
|
78
96
|
else
|
97
|
+
# ideally we would have a case mapping from ::Class -> T::Class here
|
98
|
+
# but for backwards compatibility we don't have that, and instead
|
99
|
+
# have a special case in subtype_of_single?
|
79
100
|
Simple.new(mod)
|
80
101
|
end
|
81
102
|
|
82
103
|
# Unfortunately, we still need to check if the module is frozen,
|
83
|
-
# since WeakMap adds a finalizer to the key that is added
|
104
|
+
# since on 2.6 and older WeakMap adds a finalizer to the key that is added
|
84
105
|
# to the map, so that it can clear the map entry when the key is
|
85
106
|
# garbage collected.
|
86
107
|
# For a frozen object, though, adding a finalizer is not a valid
|
87
108
|
# operation, so this still raises if `mod` is frozen.
|
88
|
-
|
109
|
+
if CACHE_FROZEN_OBJECTS || (!mod.frozen? && !type.frozen?)
|
110
|
+
@cache[mod] = type
|
111
|
+
end
|
89
112
|
type
|
90
113
|
end
|
91
114
|
end
|
@@ -3,11 +3,30 @@
|
|
3
3
|
|
4
4
|
module T::Types
|
5
5
|
class TypeParameter < Base
|
6
|
+
module Private
|
7
|
+
@pool = {}
|
8
|
+
|
9
|
+
def self.cached_entry(name)
|
10
|
+
@pool[name]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.set_entry_for(name, type)
|
14
|
+
@pool[name] = type
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
6
18
|
def initialize(name)
|
7
19
|
raise ArgumentError.new("not a symbol: #{name}") unless name.is_a?(Symbol)
|
8
20
|
@name = name
|
9
21
|
end
|
10
22
|
|
23
|
+
def self.make(name)
|
24
|
+
cached = Private.cached_entry(name)
|
25
|
+
return cached if cached
|
26
|
+
|
27
|
+
Private.set_entry_for(name, new(name))
|
28
|
+
end
|
29
|
+
|
11
30
|
def valid?(obj)
|
12
31
|
true
|
13
32
|
end
|
@@ -26,6 +26,31 @@ module T::Types
|
|
26
26
|
Array.new(*T.unsafe(args))
|
27
27
|
end
|
28
28
|
|
29
|
+
module Private
|
30
|
+
module Pool
|
31
|
+
CACHE_FROZEN_OBJECTS = begin
|
32
|
+
ObjectSpace::WeakMap.new[1] = 1
|
33
|
+
true # Ruby 2.7 and newer
|
34
|
+
rescue ArgumentError # Ruby 2.6 and older
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
@cache = ObjectSpace::WeakMap.new
|
39
|
+
|
40
|
+
def self.type_for_module(mod)
|
41
|
+
cached = @cache[mod]
|
42
|
+
return cached if cached
|
43
|
+
|
44
|
+
type = TypedArray.new(mod)
|
45
|
+
|
46
|
+
if CACHE_FROZEN_OBJECTS || (!mod.frozen? && !type.frozen?)
|
47
|
+
@cache[mod] = type
|
48
|
+
end
|
49
|
+
type
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
29
54
|
class Untyped < TypedArray
|
30
55
|
def initialize
|
31
56
|
super(T.untyped)
|
@@ -34,6 +59,10 @@ module T::Types
|
|
34
59
|
def valid?(obj)
|
35
60
|
obj.is_a?(Array)
|
36
61
|
end
|
62
|
+
|
63
|
+
module Private
|
64
|
+
INSTANCE = Untyped.new.freeze
|
65
|
+
end
|
37
66
|
end
|
38
67
|
end
|
39
68
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module T::Types
|
5
|
+
class TypedClass < T::Types::Base
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize(type)
|
9
|
+
@type = T::Utils.coerce(type)
|
10
|
+
end
|
11
|
+
|
12
|
+
# overrides Base
|
13
|
+
def name
|
14
|
+
"T::Class[#{@type.name}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def underlying_class
|
18
|
+
Class
|
19
|
+
end
|
20
|
+
|
21
|
+
# overrides Base
|
22
|
+
def valid?(obj)
|
23
|
+
Class.===(obj)
|
24
|
+
end
|
25
|
+
|
26
|
+
# overrides Base
|
27
|
+
private def subtype_of_single?(type)
|
28
|
+
case type
|
29
|
+
when TypedClass
|
30
|
+
# treat like generics are erased
|
31
|
+
true
|
32
|
+
when Simple
|
33
|
+
Class <= type.raw_type
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Private
|
40
|
+
module Pool
|
41
|
+
CACHE_FROZEN_OBJECTS =
|
42
|
+
begin
|
43
|
+
ObjectSpace::WeakMap.new[1] = 1
|
44
|
+
true # Ruby 2.7 and newer
|
45
|
+
rescue ArgumentError
|
46
|
+
false # Ruby 2.6 and older
|
47
|
+
end
|
48
|
+
|
49
|
+
@cache = ObjectSpace::WeakMap.new
|
50
|
+
|
51
|
+
def self.type_for_module(mod)
|
52
|
+
cached = @cache[mod]
|
53
|
+
return cached if cached
|
54
|
+
|
55
|
+
type = TypedClass.new(mod)
|
56
|
+
|
57
|
+
if CACHE_FROZEN_OBJECTS || (!mod.frozen? && !type.frozen?)
|
58
|
+
@cache[mod] = type
|
59
|
+
end
|
60
|
+
type
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Untyped < TypedClass
|
66
|
+
def initialize
|
67
|
+
super(T.untyped)
|
68
|
+
end
|
69
|
+
|
70
|
+
module Private
|
71
|
+
INSTANCE = Untyped.new.freeze
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Anything < TypedClass
|
76
|
+
def initialize
|
77
|
+
super(T.anything)
|
78
|
+
end
|
79
|
+
|
80
|
+
module Private
|
81
|
+
INSTANCE = Anything.new.freeze
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -54,6 +54,9 @@ module T::Types
|
|
54
54
|
when Enumerator::Lazy
|
55
55
|
# Enumerators can be unbounded: see `[:foo, :bar].cycle`
|
56
56
|
true
|
57
|
+
when Enumerator::Chain
|
58
|
+
# Enumerators can be unbounded: see `[:foo, :bar].cycle`
|
59
|
+
true
|
57
60
|
when Enumerator
|
58
61
|
# Enumerators can be unbounded: see `[:foo, :bar].cycle`
|
59
62
|
true
|
@@ -88,6 +91,8 @@ module T::Types
|
|
88
91
|
# both reading and writing. However, Sorbet treats *all*
|
89
92
|
# Enumerable subclasses as covariant for ease of adoption.
|
90
93
|
@type.subtype_of?(other.type)
|
94
|
+
elsif other.class <= Simple
|
95
|
+
underlying_class <= other.raw_type
|
91
96
|
else
|
92
97
|
false
|
93
98
|
end
|
@@ -145,6 +150,8 @@ module T::Types
|
|
145
150
|
end
|
146
151
|
when Enumerator::Lazy
|
147
152
|
T::Enumerator::Lazy[type_from_instances(obj)]
|
153
|
+
when Enumerator::Chain
|
154
|
+
T::Enumerator::Chain[type_from_instances(obj)]
|
148
155
|
when Enumerator
|
149
156
|
T::Enumerator[type_from_instances(obj)]
|
150
157
|
when Set
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module T::Types
|
5
|
+
class TypedEnumeratorChain < TypedEnumerable
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def underlying_class
|
9
|
+
Enumerator::Chain
|
10
|
+
end
|
11
|
+
|
12
|
+
# overrides Base
|
13
|
+
def name
|
14
|
+
"T::Enumerator::Chain[#{@type.name}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
# overrides Base
|
18
|
+
def recursively_valid?(obj)
|
19
|
+
obj.is_a?(Enumerator::Chain) && super
|
20
|
+
end
|
21
|
+
|
22
|
+
# overrides Base
|
23
|
+
def valid?(obj)
|
24
|
+
obj.is_a?(Enumerator::Chain)
|
25
|
+
end
|
26
|
+
|
27
|
+
def new(*args, &blk)
|
28
|
+
T.unsafe(Enumerator::Chain).new(*args, &blk)
|
29
|
+
end
|
30
|
+
|
31
|
+
class Untyped < TypedEnumeratorChain
|
32
|
+
def initialize
|
33
|
+
super(T.untyped)
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid?(obj)
|
37
|
+
obj.is_a?(Enumerator::Chain)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/types/types/union.rb
CHANGED
@@ -6,6 +6,8 @@ module T::Types
|
|
6
6
|
class Union < Base
|
7
7
|
attr_reader :types
|
8
8
|
|
9
|
+
# Don't use Union.new directly, use `Private::Pool.union_of_types`
|
10
|
+
# inside sorbet-runtime and `T.any` elsewhere.
|
9
11
|
def initialize(types)
|
10
12
|
@types = types.flat_map do |type|
|
11
13
|
type = T::Utils.coerce(type)
|
@@ -20,11 +22,16 @@ module T::Types
|
|
20
22
|
|
21
23
|
# overrides Base
|
22
24
|
def name
|
23
|
-
|
25
|
+
# Use the attr_reader here so we can override it in SimplePairUnion
|
26
|
+
type_shortcuts(types)
|
24
27
|
end
|
25
28
|
|
26
29
|
private def type_shortcuts(types)
|
27
30
|
if types.size == 1
|
31
|
+
# We shouldn't generally get here but it's possible if initializing the type
|
32
|
+
# evades Sorbet's static check and we end up on the slow path, or if someone
|
33
|
+
# is using the T:Types::Union constructor directly (the latter possibility
|
34
|
+
# is why we don't just move the `uniq` into `Private::Pool.union_of_types`).
|
28
35
|
return types[0].name
|
29
36
|
end
|
30
37
|
nilable = T::Utils.coerce(NilClass)
|
@@ -57,32 +64,61 @@ module T::Types
|
|
57
64
|
raise "This should never be reached if you're going through `subtype_of?` (and you should be)"
|
58
65
|
end
|
59
66
|
|
67
|
+
def unwrap_nilable
|
68
|
+
non_nil_types = types.reject {|t| t == T::Utils::Nilable::NIL_TYPE}
|
69
|
+
return nil if types.length == non_nil_types.length
|
70
|
+
case non_nil_types.length
|
71
|
+
when 0 then nil
|
72
|
+
when 1 then non_nil_types.first
|
73
|
+
else
|
74
|
+
T::Types::Union::Private::Pool.union_of_types(non_nil_types[0], non_nil_types[1], non_nil_types[2..-1])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
60
78
|
module Private
|
61
79
|
module Pool
|
62
80
|
EMPTY_ARRAY = [].freeze
|
63
81
|
private_constant :EMPTY_ARRAY
|
64
82
|
|
83
|
+
# Try to use `to_nilable` on a type to get memoization, or failing that
|
84
|
+
# try to at least use SimplePairUnion to get faster init and typechecking.
|
85
|
+
#
|
86
|
+
# We aren't guaranteed to detect a simple `T.nilable(<Module>)` type here
|
87
|
+
# in cases where there are duplicate types, nested unions, etc.
|
88
|
+
#
|
89
|
+
# That's ok, because returning is SimplePairUnion an optimization which
|
90
|
+
# isn't necessary for correctness.
|
91
|
+
#
|
65
92
|
# @param type_a [T::Types::Base]
|
66
93
|
# @param type_b [T::Types::Base]
|
67
94
|
# @param types [Array] optional array of additional T::Types::Base instances
|
68
95
|
def self.union_of_types(type_a, type_b, types=EMPTY_ARRAY)
|
69
|
-
if types.empty?
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
|
75
|
-
|
96
|
+
if !types.empty?
|
97
|
+
# Slow path
|
98
|
+
return Union.new([type_a, type_b] + types)
|
99
|
+
elsif !type_a.is_a?(T::Types::Simple) || !type_b.is_a?(T::Types::Simple)
|
100
|
+
# Slow path
|
101
|
+
return Union.new([type_a, type_b])
|
102
|
+
end
|
103
|
+
|
104
|
+
begin
|
105
|
+
if type_b == T::Utils::Nilable::NIL_TYPE
|
76
106
|
type_a.to_nilable
|
77
|
-
elsif type_a == T::Utils::Nilable::NIL_TYPE
|
107
|
+
elsif type_a == T::Utils::Nilable::NIL_TYPE
|
78
108
|
type_b.to_nilable
|
79
109
|
else
|
80
|
-
|
110
|
+
T::Private::Types::SimplePairUnion.new(type_a, type_b)
|
81
111
|
end
|
82
|
-
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
112
|
+
rescue T::Private::Types::SimplePairUnion::DuplicateType
|
113
|
+
# Slow path
|
114
|
+
#
|
115
|
+
# This shouldn't normally be possible due to static checks,
|
116
|
+
# but we can get here if we're constructing a type dynamically.
|
117
|
+
#
|
118
|
+
# Relying on the duplicate check in the constructor has the
|
119
|
+
# advantage that we avoid it when we hit the memoized case
|
120
|
+
# of `to_nilable`.
|
121
|
+
type_a
|
86
122
|
end
|
87
123
|
end
|
88
124
|
end
|