sorbet-runtime 0.6.13298 → 0.6.13299
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/sorbet-runtime.rb +4 -1
- data/lib/types/_types.rb +37 -1
- data/lib/types/enum.rb +43 -21
- data/lib/types/private/casts.rb +23 -2
- data/lib/types/private/class_utils.rb +21 -15
- data/lib/types/private/methods/_methods.rb +5 -2
- data/lib/types/private/methods/signature.rb +2 -1
- data/lib/types/types/base.rb +4 -1
- data/lib/types/types/fixed_array.rb +14 -10
- data/lib/types/types/fixed_hash.rb +18 -4
- data/lib/types/types/intersection.rb +14 -2
- data/lib/types/types/simple.rb +9 -1
- data/lib/types/types/typed_enumerable.rb +6 -4
- data/lib/types/types/typed_hash.rb +12 -1
- data/lib/types/types/union.rb +71 -12
- data/lib/types/utils.rb +7 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 346fdbb1a44759ff22aa86b6583474a790fb38fea59464ecee830b515521a4cf
|
|
4
|
+
data.tar.gz: 5a3a3fb1fcd5b8801ea0070631caa24a1decb95df7c9d09e506f16910ffb819b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87b79a12e41334bee22330e734b8613515da22e3d7d1843d60584e20ff41dd0fb5ab896265e7d1434cb1d09c681ed51c603b0ae4c6c976afabbf8c18ec7021e2
|
|
7
|
+
data.tar.gz: aa62c20849e24e1453150fd7e4cf354bf36d5ee24c355a3b35a4e39d0263a2821e3c1f9cbec4521245e45b2639000441ccb85567a5558525896faae3ab62b720
|
data/lib/sorbet-runtime.rb
CHANGED
|
@@ -51,7 +51,6 @@ require_relative 'types/types/type_parameter'
|
|
|
51
51
|
require_relative 'types/types/typed_enumerator'
|
|
52
52
|
require_relative 'types/types/typed_enumerator_chain'
|
|
53
53
|
require_relative 'types/types/typed_enumerator_lazy'
|
|
54
|
-
require_relative 'types/types/typed_hash'
|
|
55
54
|
require_relative 'types/types/typed_range'
|
|
56
55
|
require_relative 'types/types/typed_set'
|
|
57
56
|
require_relative 'types/types/union'
|
|
@@ -87,6 +86,10 @@ require_relative 'types/utils'
|
|
|
87
86
|
require_relative 'types/boolean'
|
|
88
87
|
|
|
89
88
|
# Depends on types/utils
|
|
89
|
+
# typed_hash must load after untyped + utils so TypedHash::Untyped::Private::INSTANCE
|
|
90
|
+
# (a frozen, shared T::Hash[T.untyped, T.untyped]) can be built at load time, the same
|
|
91
|
+
# way TypedArray::Untyped::Private::INSTANCE is.
|
|
92
|
+
require_relative 'types/types/typed_hash'
|
|
90
93
|
require_relative 'types/types/typed_array'
|
|
91
94
|
require_relative 'types/types/typed_module'
|
|
92
95
|
require_relative 'types/types/typed_class'
|
data/lib/types/_types.rb
CHANGED
|
@@ -134,6 +134,18 @@ module T
|
|
|
134
134
|
def self.cast(value, type, checked: true)
|
|
135
135
|
return value unless checked
|
|
136
136
|
|
|
137
|
+
# Happy paths for the two dominant type shapes at inline-cast sites,
|
|
138
|
+
# duplicated from Private::Casts.cast to skip its call frame: Module
|
|
139
|
+
# literals (e.g. `T.cast(x, Foo)`) and the SimplePairUnion that
|
|
140
|
+
# `T.nilable(SomeModule)` produces. Failures and other type shapes take
|
|
141
|
+
# the full path below.
|
|
142
|
+
case type
|
|
143
|
+
when ::Module
|
|
144
|
+
return value if value.is_a?(type)
|
|
145
|
+
when T::Private::Types::SimplePairUnion
|
|
146
|
+
return value if type.valid?(value)
|
|
147
|
+
end
|
|
148
|
+
|
|
137
149
|
Private::Casts.cast(value, type, "T.cast")
|
|
138
150
|
end
|
|
139
151
|
|
|
@@ -149,6 +161,14 @@ module T
|
|
|
149
161
|
def self.let(value, type, checked: true)
|
|
150
162
|
return value unless checked
|
|
151
163
|
|
|
164
|
+
# Happy paths for Module literals and T.nilable; see T.cast.
|
|
165
|
+
case type
|
|
166
|
+
when ::Module
|
|
167
|
+
return value if value.is_a?(type)
|
|
168
|
+
when T::Private::Types::SimplePairUnion
|
|
169
|
+
return value if type.valid?(value)
|
|
170
|
+
end
|
|
171
|
+
|
|
152
172
|
Private::Casts.cast(value, type, "T.let")
|
|
153
173
|
end
|
|
154
174
|
|
|
@@ -168,6 +188,14 @@ module T
|
|
|
168
188
|
def self.bind(value, type, checked: true)
|
|
169
189
|
return value unless checked
|
|
170
190
|
|
|
191
|
+
# Happy paths for Module literals and T.nilable; see T.cast.
|
|
192
|
+
case type
|
|
193
|
+
when ::Module
|
|
194
|
+
return value if value.is_a?(type)
|
|
195
|
+
when T::Private::Types::SimplePairUnion
|
|
196
|
+
return value if type.valid?(value)
|
|
197
|
+
end
|
|
198
|
+
|
|
171
199
|
Private::Casts.cast(value, type, "T.bind")
|
|
172
200
|
end
|
|
173
201
|
|
|
@@ -178,6 +206,14 @@ module T
|
|
|
178
206
|
def self.assert_type!(value, type, checked: true)
|
|
179
207
|
return value unless checked
|
|
180
208
|
|
|
209
|
+
# Happy paths for Module literals and T.nilable; see T.cast.
|
|
210
|
+
case type
|
|
211
|
+
when ::Module
|
|
212
|
+
return value if value.is_a?(type)
|
|
213
|
+
when T::Private::Types::SimplePairUnion
|
|
214
|
+
return value if type.valid?(value)
|
|
215
|
+
end
|
|
216
|
+
|
|
181
217
|
Private::Casts.cast(value, type, "T.assert_type!")
|
|
182
218
|
end
|
|
183
219
|
|
|
@@ -293,7 +329,7 @@ module T
|
|
|
293
329
|
module Hash
|
|
294
330
|
def self.[](keys, values)
|
|
295
331
|
if keys.is_a?(T::Types::Untyped) && values.is_a?(T::Types::Untyped)
|
|
296
|
-
T::Types::TypedHash::Untyped
|
|
332
|
+
T::Types::TypedHash::Untyped::Private::INSTANCE
|
|
297
333
|
else
|
|
298
334
|
T::Types::TypedHash.new(keys: keys, values: values)
|
|
299
335
|
end
|
data/lib/types/enum.rb
CHANGED
|
@@ -48,18 +48,22 @@ class T::Enum
|
|
|
48
48
|
private_constant :SerializedVal
|
|
49
49
|
|
|
50
50
|
### Enum class methods ###
|
|
51
|
-
|
|
51
|
+
# WithoutRuntime on the hot accessors below installs no method wrapper at all
|
|
52
|
+
# (not even the one-time wrap a `checked(:never)` sig still pays on first
|
|
53
|
+
# call). Their bodies are type-safe by construction, so the wrapper validated
|
|
54
|
+
# nothing useful; Sorbet still reads these sigs statically. The
|
|
55
|
+
# override/overridable annotations remain as static-only signal.
|
|
56
|
+
T::Sig::WithoutRuntime.sig { returns(T::Array[T.attached_class]) }
|
|
52
57
|
def self.values
|
|
53
58
|
if @values.nil?
|
|
54
|
-
raise
|
|
55
|
-
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
59
|
+
raise(UNBOUND_VALUES_MESSAGE % self.class)
|
|
56
60
|
end
|
|
57
61
|
@values
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
# This exists for compatibility with the interface of `Hash` & mostly to support
|
|
61
65
|
# the HashEachMethods Rubocop.
|
|
62
|
-
sig { params(blk: T.nilable(T.proc.params(arg0: T.attached_class).void)).returns(T.any(T::Enumerator[T.attached_class], T::Array[T.attached_class])) }
|
|
66
|
+
T::Sig::WithoutRuntime.sig { params(blk: T.nilable(T.proc.params(arg0: T.attached_class).void)).returns(T.any(T::Enumerator[T.attached_class], T::Array[T.attached_class])) }
|
|
63
67
|
def self.each_value(&blk)
|
|
64
68
|
if blk
|
|
65
69
|
values.each(&blk)
|
|
@@ -72,11 +76,10 @@ class T::Enum
|
|
|
72
76
|
#
|
|
73
77
|
# Note: It would have been nice to make this method final before people started overriding it.
|
|
74
78
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
75
|
-
sig { params(serialized_val: SerializedVal).returns(T.nilable(T.attached_class))
|
|
79
|
+
T::Sig::WithoutRuntime.sig { params(serialized_val: SerializedVal).returns(T.nilable(T.attached_class)) }
|
|
76
80
|
def self.try_deserialize(serialized_val)
|
|
77
81
|
if @mapping.nil?
|
|
78
|
-
raise
|
|
79
|
-
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
82
|
+
raise(UNBOUND_SERIALIZATION_MAP_MESSAGE % self.class)
|
|
80
83
|
end
|
|
81
84
|
@mapping[serialized_val]
|
|
82
85
|
end
|
|
@@ -88,7 +91,7 @@ class T::Enum
|
|
|
88
91
|
#
|
|
89
92
|
# @return [self]
|
|
90
93
|
# @raise [KeyError] if serialized value does not match any instance.
|
|
91
|
-
sig { overridable.params(serialized_val: SerializedVal).returns(T.attached_class)
|
|
94
|
+
T::Sig::WithoutRuntime.sig { overridable.params(serialized_val: SerializedVal).returns(T.attached_class) }
|
|
92
95
|
def self.from_serialized(serialized_val)
|
|
93
96
|
res = try_deserialize(serialized_val)
|
|
94
97
|
if res.nil?
|
|
@@ -99,17 +102,16 @@ class T::Enum
|
|
|
99
102
|
|
|
100
103
|
# Note: It would have been nice to make this method final before people started overriding it.
|
|
101
104
|
# @return [Boolean] Does the given serialized value correspond with any of this enum's values.
|
|
102
|
-
sig { overridable.params(serialized_val: SerializedVal).returns(T::Boolean)
|
|
105
|
+
T::Sig::WithoutRuntime.sig { overridable.params(serialized_val: SerializedVal).returns(T::Boolean) }
|
|
103
106
|
def self.has_serialized?(serialized_val)
|
|
104
107
|
if @mapping.nil?
|
|
105
|
-
raise
|
|
106
|
-
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
108
|
+
raise(UNBOUND_SERIALIZATION_MAP_MESSAGE % self.class)
|
|
107
109
|
end
|
|
108
110
|
@mapping.include?(serialized_val)
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
112
|
-
sig { override.params(instance: T.nilable(T::Enum)).returns(SerializedVal)
|
|
114
|
+
T::Sig::WithoutRuntime.sig { override.params(instance: T.nilable(T::Enum)).returns(SerializedVal) }
|
|
113
115
|
def self.serialize(instance)
|
|
114
116
|
# This is needed otherwise if a Chalk::ODM::Document with a property of the shape
|
|
115
117
|
# T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
|
|
@@ -126,7 +128,7 @@ class T::Enum
|
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
129
|
-
sig { override.params(mongo_value: SerializedVal).returns(T.attached_class)
|
|
131
|
+
T::Sig::WithoutRuntime.sig { override.params(mongo_value: SerializedVal).returns(T.attached_class) }
|
|
130
132
|
def self.deserialize(mongo_value)
|
|
131
133
|
if self == T::Enum
|
|
132
134
|
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
|
|
@@ -146,10 +148,28 @@ class T::Enum
|
|
|
146
148
|
self
|
|
147
149
|
end
|
|
148
150
|
|
|
151
|
+
# Format strings (`%s` is the class) for the "accessed before initialized"
|
|
152
|
+
# errors, so the various raise sites can't drift from each other.
|
|
153
|
+
UNBOUND_VALUE_MESSAGE = "Attempting to access Enum value on %s before it has been initialized." \
|
|
154
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
155
|
+
private_constant :UNBOUND_VALUE_MESSAGE
|
|
156
|
+
|
|
157
|
+
UNBOUND_VALUES_MESSAGE = "Attempting to access values of %s before it has been initialized." \
|
|
158
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
159
|
+
private_constant :UNBOUND_VALUES_MESSAGE
|
|
160
|
+
|
|
161
|
+
UNBOUND_SERIALIZATION_MAP_MESSAGE = "Attempting to access serialization map of %s before it has been initialized." \
|
|
162
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
163
|
+
private_constant :UNBOUND_SERIALIZATION_MAP_MESSAGE
|
|
164
|
+
|
|
149
165
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
150
|
-
sig { returns(SerializedVal)
|
|
166
|
+
T::Sig::WithoutRuntime.sig { returns(SerializedVal) }
|
|
151
167
|
def serialize
|
|
152
|
-
assert_bound
|
|
168
|
+
# Same check and message as assert_bound!, open-coded to avoid the extra
|
|
169
|
+
# method frame on this hot path.
|
|
170
|
+
if @const_name.nil?
|
|
171
|
+
raise(UNBOUND_VALUE_MESSAGE % self.class)
|
|
172
|
+
end
|
|
153
173
|
@serialized_val
|
|
154
174
|
end
|
|
155
175
|
|
|
@@ -165,17 +185,20 @@ class T::Enum
|
|
|
165
185
|
serialized_val.as_json(*args)
|
|
166
186
|
end
|
|
167
187
|
|
|
168
|
-
|
|
188
|
+
# `to_s` keeps delegating to `inspect` rather than `alias_method :to_s,
|
|
189
|
+
# :inspect` so a subclass that overrides `inspect` still has its `to_s`
|
|
190
|
+
# reflect the override.
|
|
191
|
+
T::Sig::WithoutRuntime.sig { returns(String) }
|
|
169
192
|
def to_s
|
|
170
193
|
inspect
|
|
171
194
|
end
|
|
172
195
|
|
|
173
|
-
sig { returns(String) }
|
|
196
|
+
T::Sig::WithoutRuntime.sig { returns(String) }
|
|
174
197
|
def inspect
|
|
175
198
|
"#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
|
|
176
199
|
end
|
|
177
200
|
|
|
178
|
-
sig { params(other: BasicObject).returns(T.nilable(Integer)) }
|
|
201
|
+
T::Sig::WithoutRuntime.sig { params(other: BasicObject).returns(T.nilable(Integer)) }
|
|
179
202
|
def <=>(other)
|
|
180
203
|
case other
|
|
181
204
|
when self.class
|
|
@@ -292,11 +315,10 @@ class T::Enum
|
|
|
292
315
|
self.class._register_instance(self)
|
|
293
316
|
end
|
|
294
317
|
|
|
295
|
-
sig { returns(NilClass)
|
|
318
|
+
T::Sig::WithoutRuntime.sig { returns(NilClass) }
|
|
296
319
|
private def assert_bound!
|
|
297
320
|
if @const_name.nil?
|
|
298
|
-
raise
|
|
299
|
-
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
|
321
|
+
raise(UNBOUND_VALUE_MESSAGE % self.class)
|
|
300
322
|
end
|
|
301
323
|
end
|
|
302
324
|
|
data/lib/types/private/casts.rb
CHANGED
|
@@ -5,8 +5,29 @@ module T::Private
|
|
|
5
5
|
module Casts
|
|
6
6
|
def self.cast(value, type, cast_method)
|
|
7
7
|
begin
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Every caller (T.cast/let/bind/assert_type! in _types.rb) inlines the
|
|
9
|
+
# value check for the two dominant shapes -- a Module literal and the
|
|
10
|
+
# SimplePairUnion that `T.nilable(SomeModule)` produces -- and only
|
|
11
|
+
# falls through to here when that check already failed. So skip
|
|
12
|
+
# straight to coercing: re-checking `value.is_a?(type)` /
|
|
13
|
+
# `type.valid?(value)` here would always be false.
|
|
14
|
+
case type
|
|
15
|
+
when ::Module
|
|
16
|
+
coerced_type = T::Types::Simple::Private::Pool.type_for_module(type)
|
|
17
|
+
when T::Types::Base
|
|
18
|
+
# Mirrors the T::Types::Base branch of coerce_and_check_module_types,
|
|
19
|
+
# kept inline to avoid its call frame for every already-coerced type.
|
|
20
|
+
coerced_type =
|
|
21
|
+
case type
|
|
22
|
+
when T::Private::Types::TypeAlias
|
|
23
|
+
type.aliased_type
|
|
24
|
+
else
|
|
25
|
+
type
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
coerced_type = T::Utils::Private.coerce_and_check_module_types(type, value, true)
|
|
29
|
+
return value unless coerced_type
|
|
30
|
+
end
|
|
10
31
|
|
|
11
32
|
error = coerced_type.error_message_for_obj(value)
|
|
12
33
|
return value unless error
|
|
@@ -44,9 +44,10 @@ module T::Private::ClassUtils
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
if block && block.arity < 0 && respond_to?(:ruby2_keywords, true)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
# Fetch .parameters once: each call builds a fresh array.
|
|
48
|
+
parameters = instance_method(name).parameters
|
|
49
|
+
has_rest = parameters.any? { |kind, _| kind == :rest }
|
|
50
|
+
has_keywords = parameters.any? { |kind, _| kind == :keyrest || kind == :keyreq || kind == :key }
|
|
50
51
|
ruby2_keywords(name) if has_rest && !has_keywords
|
|
51
52
|
end
|
|
52
53
|
end
|
|
@@ -67,18 +68,23 @@ module T::Private::ClassUtils
|
|
|
67
68
|
original_visibility = visibility_method_name(mod, name)
|
|
68
69
|
original_owner = original_method.owner
|
|
69
70
|
|
|
70
|
-
mod
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
# In the common case the method is owned by `mod` itself, in which case the loop below
|
|
72
|
+
# would always `break` before the raise could match (`mod` precedes any non-prepended
|
|
73
|
+
# ancestor in its own ancestor chain), so skip computing `mod.ancestors` entirely.
|
|
74
|
+
if original_owner != mod
|
|
75
|
+
mod.ancestors.each do |ancestor|
|
|
76
|
+
break if ancestor == mod
|
|
77
|
+
if ancestor == original_owner
|
|
78
|
+
# If we get here, that means the method we're trying to replace exists on a *prepended*
|
|
79
|
+
# mixin, which means in order to supersede it, we'd need to create a method on a new
|
|
80
|
+
# module that we'd prepend before `ancestor`. The problem with that approach is there'd
|
|
81
|
+
# be no way to remove that new module after prepending it, so we'd be left with these
|
|
82
|
+
# empty anonymous modules in the ancestor chain after calling `restore`.
|
|
83
|
+
#
|
|
84
|
+
# That's not necessarily a deal breaker, but for now, we're keeping it as unsupported.
|
|
85
|
+
raise "You're trying to replace `#{name}` on `#{mod}`, but that method exists in a " \
|
|
86
|
+
"prepended module (#{ancestor}), which we don't currently support."
|
|
87
|
+
end
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
|
|
@@ -299,11 +299,14 @@ module T::Private::Methods
|
|
|
299
299
|
|
|
300
300
|
# Only public because it needs to get called below inside the replace_method blocks below.
|
|
301
301
|
def self._on_method_added(hook_mod, mod, method_name)
|
|
302
|
-
|
|
302
|
+
# The thread-local DeclState object is stable for the duration of this call
|
|
303
|
+
# (nothing reassigns `DeclState.current=`), so fetch it once.
|
|
304
|
+
decl_state = T::Private::DeclState.current
|
|
305
|
+
if decl_state.skip_on_method_added
|
|
303
306
|
return
|
|
304
307
|
end
|
|
305
308
|
|
|
306
|
-
current_declaration =
|
|
309
|
+
current_declaration = decl_state.consume!
|
|
307
310
|
|
|
308
311
|
if T::Private::Final.final_module?(mod) && (current_declaration.nil? || !current_declaration.final)
|
|
309
312
|
raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
|
|
@@ -80,7 +80,8 @@ class T::Private::Methods::Signature
|
|
|
80
80
|
# If sig params are declared but there is a single parameter with a missing name
|
|
81
81
|
# **and** the method ends with a "=", assume it is a writer method generated
|
|
82
82
|
# by attr_writer or attr_accessor
|
|
83
|
-
|
|
83
|
+
# (Checks are ordered so that the common, non-writer case short-circuits without allocating.)
|
|
84
|
+
writer_method = method_name.end_with?("=") && parameters == UNNAMED_REQUIRED_PARAMETERS && !(raw_arg_types.size == 1 && raw_arg_types.key?(nil))
|
|
84
85
|
# For writer methods, map the single parameter to the method name without the "=" at the end
|
|
85
86
|
parameters = [[:req, method_name[0...-1].to_sym]] if writer_method
|
|
86
87
|
is_name_missing = parameters.any? { |_, name| !raw_arg_types.key?(name) }
|
data/lib/types/types/base.rb
CHANGED
|
@@ -181,7 +181,10 @@ module T::Types
|
|
|
181
181
|
def ==(other)
|
|
182
182
|
case other
|
|
183
183
|
when T::Types::Base
|
|
184
|
-
|
|
184
|
+
# Performance fast path: pooled and memoized type instances (e.g. the
|
|
185
|
+
# results of repeated T.nilable(X) calls) are the same object, so they
|
|
186
|
+
# can compare equal without computing and comparing their names.
|
|
187
|
+
other.equal?(self) || other.name == self.name
|
|
185
188
|
else
|
|
186
189
|
false
|
|
187
190
|
end
|
|
@@ -26,13 +26,15 @@ module T::Types
|
|
|
26
26
|
|
|
27
27
|
# overrides Base
|
|
28
28
|
def recursively_valid?(obj)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
element_types = types
|
|
30
|
+
length = element_types.length
|
|
31
|
+
if obj.is_a?(Array) && obj.length == length
|
|
32
|
+
index = 0
|
|
33
|
+
while index < length
|
|
34
|
+
if !element_types.fetch(index).recursively_valid?(obj[index])
|
|
33
35
|
return false
|
|
34
36
|
end
|
|
35
|
-
|
|
37
|
+
index += 1
|
|
36
38
|
end
|
|
37
39
|
true
|
|
38
40
|
else
|
|
@@ -42,13 +44,15 @@ module T::Types
|
|
|
42
44
|
|
|
43
45
|
# overrides Base
|
|
44
46
|
def valid?(obj)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
element_types = types
|
|
48
|
+
length = element_types.length
|
|
49
|
+
if obj.is_a?(Array) && obj.length == length
|
|
50
|
+
index = 0
|
|
51
|
+
while index < length
|
|
52
|
+
if !element_types.fetch(index).valid?(obj[index])
|
|
49
53
|
return false
|
|
50
54
|
end
|
|
51
|
-
|
|
55
|
+
index += 1
|
|
52
56
|
end
|
|
53
57
|
true
|
|
54
58
|
else
|
|
@@ -26,16 +26,30 @@ module T::Types
|
|
|
26
26
|
# overrides Base
|
|
27
27
|
def recursively_valid?(obj)
|
|
28
28
|
return false unless obj.is_a?(Hash)
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
field_types = types
|
|
30
|
+
field_types.each_pair do |key, type|
|
|
31
|
+
return false unless type.recursively_valid?(obj[key])
|
|
32
|
+
end
|
|
33
|
+
# Pigeonhole: more entries than declared keys guarantees an undeclared key.
|
|
34
|
+
return false if obj.size > field_types.size
|
|
35
|
+
obj.each_key do |key|
|
|
36
|
+
return false unless field_types.key?(key)
|
|
37
|
+
end
|
|
31
38
|
true
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
# overrides Base
|
|
35
42
|
def valid?(obj)
|
|
36
43
|
return false unless obj.is_a?(Hash)
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
field_types = types
|
|
45
|
+
field_types.each_pair do |key, type|
|
|
46
|
+
return false unless type.valid?(obj[key])
|
|
47
|
+
end
|
|
48
|
+
# Pigeonhole: more entries than declared keys guarantees an undeclared key.
|
|
49
|
+
return false if obj.size > field_types.size
|
|
50
|
+
obj.each_key do |key|
|
|
51
|
+
return false unless field_types.key?(key)
|
|
52
|
+
end
|
|
39
53
|
true
|
|
40
54
|
end
|
|
41
55
|
|
|
@@ -32,12 +32,24 @@ module T::Types
|
|
|
32
32
|
|
|
33
33
|
# overrides Base
|
|
34
34
|
def recursively_valid?(obj)
|
|
35
|
-
|
|
35
|
+
members = types
|
|
36
|
+
index = 0
|
|
37
|
+
while index < members.length
|
|
38
|
+
return false unless members.fetch(index).recursively_valid?(obj)
|
|
39
|
+
index += 1
|
|
40
|
+
end
|
|
41
|
+
true
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
# overrides Base
|
|
39
45
|
def valid?(obj)
|
|
40
|
-
|
|
46
|
+
members = types
|
|
47
|
+
index = 0
|
|
48
|
+
while index < members.length
|
|
49
|
+
return false unless members.fetch(index).valid?(obj)
|
|
50
|
+
index += 1
|
|
51
|
+
end
|
|
52
|
+
true
|
|
41
53
|
end
|
|
42
54
|
|
|
43
55
|
# overrides Base
|
data/lib/types/types/simple.rb
CHANGED
|
@@ -31,6 +31,14 @@ module T::Types
|
|
|
31
31
|
# rubocop:enable Performance/BindCall
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# overrides Base
|
|
35
|
+
#
|
|
36
|
+
# Identical to valid?; defined directly so the leaf-type hot path (every
|
|
37
|
+
# element check in a typed collection walk) skips the Base delegator frame.
|
|
38
|
+
def recursively_valid?(obj)
|
|
39
|
+
obj.is_a?(@raw_type)
|
|
40
|
+
end
|
|
41
|
+
|
|
34
42
|
# overrides Base
|
|
35
43
|
def valid?(obj)
|
|
36
44
|
obj.is_a?(@raw_type)
|
|
@@ -93,7 +101,7 @@ module T::Types
|
|
|
93
101
|
type = if mod == ::Array
|
|
94
102
|
TypedArray::Untyped::Private::INSTANCE
|
|
95
103
|
elsif mod == ::Hash
|
|
96
|
-
TypedHash::Untyped
|
|
104
|
+
TypedHash::Untyped::Private::INSTANCE
|
|
97
105
|
elsif mod == ::Enumerable
|
|
98
106
|
TypedEnumerable::Untyped.new
|
|
99
107
|
elsif mod == ::Enumerator
|
|
@@ -38,10 +38,12 @@ module T::Types
|
|
|
38
38
|
return false unless obj.is_a?(Enumerable)
|
|
39
39
|
case obj
|
|
40
40
|
when Array
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
element_type = type
|
|
42
|
+
length = obj.count
|
|
43
|
+
index = 0
|
|
44
|
+
while index < length
|
|
45
|
+
return false unless element_type.recursively_valid?(obj[index])
|
|
46
|
+
index += 1
|
|
45
47
|
end
|
|
46
48
|
true
|
|
47
49
|
when Hash
|
|
@@ -47,12 +47,23 @@ module T::Types
|
|
|
47
47
|
|
|
48
48
|
class Untyped < TypedHash
|
|
49
49
|
def initialize
|
|
50
|
-
|
|
50
|
+
# Use the INSTANCE constant directly (rather than `T.untyped`) so this
|
|
51
|
+
# can be built at load time, mirroring TypedArray::Untyped.
|
|
52
|
+
super(keys: T::Types::Untyped::Private::INSTANCE, values: T::Types::Untyped::Private::INSTANCE)
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def valid?(obj)
|
|
54
56
|
obj.is_a?(Hash)
|
|
55
57
|
end
|
|
58
|
+
|
|
59
|
+
def freeze
|
|
60
|
+
build_type # force lazy initialization before freezing the object
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module Private
|
|
65
|
+
INSTANCE = Untyped.new.freeze
|
|
66
|
+
end
|
|
56
67
|
end
|
|
57
68
|
end
|
|
58
69
|
end
|
data/lib/types/types/union.rb
CHANGED
|
@@ -11,15 +11,30 @@ module T::Types
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def types
|
|
14
|
-
@types ||=
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
@types ||= begin
|
|
15
|
+
flattened = @inner_types.flat_map do |type|
|
|
16
|
+
type = T::Utils.coerce(type)
|
|
17
|
+
if type.is_a?(Union)
|
|
18
|
+
# Simplify nested unions (mostly so `name` returns a nicer value)
|
|
19
|
+
type.types
|
|
20
|
+
else
|
|
21
|
+
type
|
|
22
|
+
end
|
|
23
|
+
end.uniq
|
|
24
|
+
# When every member is a plain Simple (whose valid? is exactly
|
|
25
|
+
# `obj.is_a?(raw_type)`), precompute the members' raw modules so
|
|
26
|
+
# valid? can skip per-member dispatch. instance_of? (the is_a? only
|
|
27
|
+
# narrows for static checking) so that any Simple subclass overriding
|
|
28
|
+
# valid? would be excluded. Note this snapshots the members at build
|
|
29
|
+
# time.
|
|
30
|
+
member_modules = []
|
|
31
|
+
all_simple = flattened.all? do |type|
|
|
32
|
+
type.is_a?(T::Types::Simple) && type.instance_of?(T::Types::Simple) &&
|
|
33
|
+
member_modules << type.raw_type
|
|
21
34
|
end
|
|
22
|
-
|
|
35
|
+
@member_modules = all_simple ? member_modules.freeze : false
|
|
36
|
+
flattened
|
|
37
|
+
end
|
|
23
38
|
end
|
|
24
39
|
|
|
25
40
|
def build_type
|
|
@@ -58,12 +73,52 @@ module T::Types
|
|
|
58
73
|
|
|
59
74
|
# overrides Base
|
|
60
75
|
def recursively_valid?(obj)
|
|
61
|
-
|
|
76
|
+
member_modules = @member_modules
|
|
77
|
+
if member_modules.nil?
|
|
78
|
+
# Force the lazy types builder, which also computes @member_modules
|
|
79
|
+
types
|
|
80
|
+
member_modules = @member_modules
|
|
81
|
+
end
|
|
82
|
+
index = 0
|
|
83
|
+
if member_modules
|
|
84
|
+
# For an all-Simple union, recursively_valid? and valid? coincide
|
|
85
|
+
# (Simple's recursively_valid? is exactly `obj.is_a?(raw_type)`).
|
|
86
|
+
while index < member_modules.length
|
|
87
|
+
return true if obj.is_a?(member_modules[index])
|
|
88
|
+
index += 1
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
members = types
|
|
92
|
+
while index < members.length
|
|
93
|
+
return true if members.fetch(index).recursively_valid?(obj)
|
|
94
|
+
index += 1
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
false
|
|
62
98
|
end
|
|
63
99
|
|
|
64
100
|
# overrides Base
|
|
65
101
|
def valid?(obj)
|
|
66
|
-
|
|
102
|
+
member_modules = @member_modules
|
|
103
|
+
if member_modules.nil?
|
|
104
|
+
# Force the lazy types builder, which also computes @member_modules
|
|
105
|
+
types
|
|
106
|
+
member_modules = @member_modules
|
|
107
|
+
end
|
|
108
|
+
index = 0
|
|
109
|
+
if member_modules
|
|
110
|
+
while index < member_modules.length
|
|
111
|
+
return true if obj.is_a?(member_modules[index])
|
|
112
|
+
index += 1
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
members = types
|
|
116
|
+
while index < members.length
|
|
117
|
+
return true if members.fetch(index).valid?(obj)
|
|
118
|
+
index += 1
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
false
|
|
67
122
|
end
|
|
68
123
|
|
|
69
124
|
# overrides Base
|
|
@@ -109,9 +164,13 @@ module T::Types
|
|
|
109
164
|
end
|
|
110
165
|
|
|
111
166
|
begin
|
|
112
|
-
|
|
167
|
+
# The `equal?` checks are an identity fast path: NIL_TYPE is the pooled
|
|
168
|
+
# `coerce(NilClass)` instance, so it hits on every normal `T.nilable` call.
|
|
169
|
+
# The `==` fallbacks preserve semantics for hand-constructed
|
|
170
|
+
# `Simple.new(NilClass)` instances that bypass the pool.
|
|
171
|
+
if type_b.equal?(T::Utils::Nilable::NIL_TYPE) || type_b == T::Utils::Nilable::NIL_TYPE
|
|
113
172
|
type_a.to_nilable
|
|
114
|
-
elsif type_a == T::Utils::Nilable::NIL_TYPE
|
|
173
|
+
elsif type_a.equal?(T::Utils::Nilable::NIL_TYPE) || type_a == T::Utils::Nilable::NIL_TYPE
|
|
115
174
|
type_b.to_nilable
|
|
116
175
|
else
|
|
117
176
|
T::Private::Types::SimplePairUnion.new(type_a, type_b)
|
data/lib/types/utils.rb
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
module T::Utils
|
|
5
5
|
module Private
|
|
6
|
+
# NOTE: the Module and SimplePairUnion branches of this method are inlined
|
|
7
|
+
# for speed in several hot paths. The `T.cast` / `T.let` / `T.bind` /
|
|
8
|
+
# `T.assert_type!` happy paths in `_types.rb` inline the value check and
|
|
9
|
+
# return early on success; `T::Private::Casts.cast` is only reached after
|
|
10
|
+
# that check has already failed, so it reproduces the coercion branches but
|
|
11
|
+
# skips the (now always-false) value check. If you change the behavior
|
|
12
|
+
# here, update those callers too.
|
|
6
13
|
def self.coerce_and_check_module_types(val, check_val, check_module_type)
|
|
7
14
|
# rubocop:disable Style/CaseLikeIf
|
|
8
15
|
if val.is_a?(T::Types::Base)
|