sorbet-runtime 0.4.4969 → 0.4.4972
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 +2 -0
- data/lib/types/configuration.rb +10 -0
- data/lib/types/enum.rb +334 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53211ac8c4d54178673a7fb13f499fea6e0808239266f249027303fedab70fb7
|
4
|
+
data.tar.gz: b5952024c57f88f975fb689e8b921b283eb4a20bf56b04380b6c8cf3236a968e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0aed40e7e7617d289e77544f30a2ed8335ea1a4480159b75194622cd777897c64630f417b9f35dc99571e9c8818e3e76f9227ea0ed90ba3625463524e3fc75a
|
7
|
+
data.tar.gz: 2f21287bacccc1557d1fb5c4c257c7c34259724e2306a5db27c98620841af4a643e004913a79f421ca2d760003cdf1b88081346c4c2b85184eeb5eef8582b90b
|
data/lib/sorbet-runtime.rb
CHANGED
@@ -99,6 +99,8 @@ require_relative 'types/props/constructor'
|
|
99
99
|
require_relative 'types/props/pretty_printable'
|
100
100
|
require_relative 'types/props/serializable'
|
101
101
|
require_relative 'types/props/type_validation'
|
102
|
+
|
102
103
|
require_relative 'types/struct'
|
104
|
+
require_relative 'types/enum'
|
103
105
|
|
104
106
|
require_relative 'types/compatibility_patches'
|
data/lib/types/configuration.rb
CHANGED
@@ -356,6 +356,7 @@ module T::Configuration
|
|
356
356
|
String
|
357
357
|
Symbol
|
358
358
|
Time
|
359
|
+
T::Enum
|
359
360
|
}).freeze
|
360
361
|
|
361
362
|
def self.scalar_types
|
@@ -382,6 +383,15 @@ module T::Configuration
|
|
382
383
|
end
|
383
384
|
end
|
384
385
|
|
386
|
+
def self.enable_legacy_t_enum_migration_mode
|
387
|
+
@legacy_t_enum_migration_mode = true
|
388
|
+
end
|
389
|
+
def self.disable_legacy_t_enum_migration_mode
|
390
|
+
@legacy_t_enum_migration_mode = false
|
391
|
+
end
|
392
|
+
def self.legacy_t_enum_migration_mode?
|
393
|
+
@legacy_t_enum_migration_mode || false
|
394
|
+
end
|
385
395
|
|
386
396
|
private_class_method def self.validate_lambda_given!(value)
|
387
397
|
if !value.nil? && !value.respond_to?(:call)
|
data/lib/types/enum.rb
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
# Enumerations allow for type-safe declarations of a fixed set of values.
|
5
|
+
#
|
6
|
+
# Every value is a singleton instance of the class (i.e. `Suit::SPADE.is_a?(Suit) == true`).
|
7
|
+
#
|
8
|
+
# Each value has a corresponding serialized value. By default this is the constant's name converted
|
9
|
+
# to lowercase (e.g. `Suit::Club.serialize == 'club'`); however a custom value may be passed to the
|
10
|
+
# constructor. Enum will `freeze` the serialized value.
|
11
|
+
#
|
12
|
+
# @example Declaring an Enum:
|
13
|
+
# class Suit < T::Enum
|
14
|
+
# enums do
|
15
|
+
# CLUB = T.let(new, Suit)
|
16
|
+
# SPADE = T.let(new, Suit)
|
17
|
+
# DIAMOND = T.let(new, Suit)
|
18
|
+
# HEART = T.let(new, Suit)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example Custom serialization value:
|
23
|
+
# class Status < T::Enum
|
24
|
+
# enums do
|
25
|
+
# READY = T.let(new('rdy'), Status)
|
26
|
+
# ...
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example Accessing values:
|
31
|
+
# Suit::SPADE
|
32
|
+
#
|
33
|
+
# @example Converting from serialized value to enum instance:
|
34
|
+
# Suit.from_serialized('club') == Suit::CLUB
|
35
|
+
#
|
36
|
+
# @example Using enums in type signatures:
|
37
|
+
# sig {params(suit: Suit).returns(Boolean)}
|
38
|
+
# def is_red?(suit); ...; end
|
39
|
+
#
|
40
|
+
# WARNING: Enum instances are singletons that are shared among all their users. Their internals
|
41
|
+
# should be kept immutable to avoid unpredictable action at a distance.
|
42
|
+
class T::Enum
|
43
|
+
extend T::Sig
|
44
|
+
extend T::Props::CustomType
|
45
|
+
|
46
|
+
# TODO(jez) Might want to restrict this, or make subclasses provide this type
|
47
|
+
SerializedVal = T.type_alias {T.untyped}
|
48
|
+
private_constant :SerializedVal
|
49
|
+
|
50
|
+
## Enum class methods ##
|
51
|
+
sig {returns(T::Array[T.experimental_attached_class])}
|
52
|
+
def self.values
|
53
|
+
if @values.nil?
|
54
|
+
raise "Attempting to access values of #{self.class} before it has been initialized." \
|
55
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
56
|
+
end
|
57
|
+
@values
|
58
|
+
end
|
59
|
+
|
60
|
+
# Convert from serialized value to enum instance.
|
61
|
+
#
|
62
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
63
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
64
|
+
#
|
65
|
+
# @return [self]
|
66
|
+
# @raise [KeyError] if serialized value does not match any instance.
|
67
|
+
sig {overridable.params(serialized_val: SerializedVal).returns(T.experimental_attached_class).checked(:never)}
|
68
|
+
def self.from_serialized(serialized_val)
|
69
|
+
if @mapping.nil?
|
70
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
71
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
72
|
+
end
|
73
|
+
res = @mapping[serialized_val]
|
74
|
+
if res.nil?
|
75
|
+
raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
|
76
|
+
end
|
77
|
+
res
|
78
|
+
end
|
79
|
+
|
80
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
81
|
+
# @return [Boolean] Does the given serialized value correspond with any of this enum's values.
|
82
|
+
sig {overridable.params(serialized_val: SerializedVal).returns(T::Boolean)}
|
83
|
+
def self.has_serialized?(serialized_val)
|
84
|
+
if @mapping.nil?
|
85
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
86
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
87
|
+
end
|
88
|
+
@mapping.include?(serialized_val)
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
## T::Props::CustomType
|
93
|
+
|
94
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
95
|
+
sig {params(value: T.untyped).returns(T::Boolean).checked(:never)}
|
96
|
+
def self.instance?(value)
|
97
|
+
value.is_a?(self)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
101
|
+
sig {params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
|
102
|
+
def self.serialize(instance)
|
103
|
+
# This is needed otherwise if a Chalk::ODM::Document with a property of the shape
|
104
|
+
# T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
|
105
|
+
# serialized, we throw the error on L102.
|
106
|
+
return nil if instance.nil?
|
107
|
+
|
108
|
+
if self == T::Enum
|
109
|
+
raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
|
110
|
+
end
|
111
|
+
if instance.class != self
|
112
|
+
raise "Cannot call #serialize on a value that is not an instance of #{self}."
|
113
|
+
end
|
114
|
+
instance.serialize
|
115
|
+
end
|
116
|
+
|
117
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
118
|
+
sig {params(mongo_value: SerializedVal).returns(T.experimental_attached_class).checked(:never)}
|
119
|
+
def self.deserialize(mongo_value)
|
120
|
+
if self == T::Enum
|
121
|
+
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
|
122
|
+
end
|
123
|
+
self.from_serialized(mongo_value)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
## Enum instance methods ##
|
128
|
+
|
129
|
+
|
130
|
+
sig {returns(T.self_type)}
|
131
|
+
def dup
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
sig {returns(T.self_type).checked(:tests)}
|
136
|
+
def clone
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
141
|
+
sig {returns(SerializedVal).checked(:never)}
|
142
|
+
def serialize
|
143
|
+
assert_bound!
|
144
|
+
@serialized_val
|
145
|
+
end
|
146
|
+
|
147
|
+
sig {params(args: T.untyped).returns(T.untyped)}
|
148
|
+
def to_json(*args)
|
149
|
+
serialize.to_json(*args)
|
150
|
+
end
|
151
|
+
|
152
|
+
sig {returns(String)}
|
153
|
+
def to_s
|
154
|
+
inspect
|
155
|
+
end
|
156
|
+
|
157
|
+
sig {returns(String)}
|
158
|
+
def inspect
|
159
|
+
"#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
|
160
|
+
end
|
161
|
+
|
162
|
+
sig {params(other: BasicObject).returns(T.nilable(Integer))}
|
163
|
+
def <=>(other)
|
164
|
+
case other
|
165
|
+
when self.class
|
166
|
+
self.serialize <=> other.serialize
|
167
|
+
else
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# NB: Do not call this method. This exists to allow for a safe migration path in places where enum
|
174
|
+
# values are compared directly against string values.
|
175
|
+
#
|
176
|
+
# Ruby's string has a weird quirk where `'my_string' == obj` calls obj.==('my_string') if obj
|
177
|
+
# responds to the `to_str` method. It does not actually call `to_str` however.
|
178
|
+
#
|
179
|
+
# See https://ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D
|
180
|
+
sig {returns(String)}
|
181
|
+
def to_str
|
182
|
+
msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
|
183
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
184
|
+
T::Configuration.soft_assert_handler(
|
185
|
+
msg,
|
186
|
+
storytime: {class: self.class.name},
|
187
|
+
)
|
188
|
+
serialize.to_s
|
189
|
+
else
|
190
|
+
raise NoMethodError.new(msg)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
|
195
|
+
def ==(other)
|
196
|
+
case other
|
197
|
+
when String
|
198
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
199
|
+
comparison_assertion_failed(:==, other)
|
200
|
+
self.serialize == other
|
201
|
+
else
|
202
|
+
false
|
203
|
+
end
|
204
|
+
else
|
205
|
+
super(other)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
|
210
|
+
def ===(other)
|
211
|
+
case other
|
212
|
+
when String
|
213
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
214
|
+
comparison_assertion_failed(:===, other)
|
215
|
+
self.serialize == other
|
216
|
+
else
|
217
|
+
false
|
218
|
+
end
|
219
|
+
else
|
220
|
+
super(other)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
sig {params(method: Symbol, other: T.untyped).void}
|
225
|
+
private def comparison_assertion_failed(method, other)
|
226
|
+
T::Configuration.soft_assert_handler(
|
227
|
+
'Enum to string comparison not allowed. Compare to the Enum instance directly instead. See go/enum-migration',
|
228
|
+
storytime: {
|
229
|
+
class: self.class.name,
|
230
|
+
self: self.inspect,
|
231
|
+
other: other,
|
232
|
+
other_class: other.class.name,
|
233
|
+
method: method,
|
234
|
+
}
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
## Private implementation ##
|
240
|
+
|
241
|
+
|
242
|
+
sig {params(serialized_val: SerializedVal).void}
|
243
|
+
private def initialize(serialized_val=nil)
|
244
|
+
raise 'T::Enum is abstract' if self.class == T::Enum
|
245
|
+
if !self.class.started_initializing?
|
246
|
+
raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
|
247
|
+
end
|
248
|
+
if self.class.fully_initialized?
|
249
|
+
raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
|
250
|
+
end
|
251
|
+
|
252
|
+
serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
|
253
|
+
@serialized_val = T.let(serialized_val, T.nilable(SerializedVal))
|
254
|
+
@const_name = T.let(nil, T.nilable(Symbol))
|
255
|
+
end
|
256
|
+
|
257
|
+
sig {returns(NilClass).checked(:never)}
|
258
|
+
private def assert_bound!
|
259
|
+
if @const_name.nil?
|
260
|
+
raise "Attempting to access Enum value on #{self.class} before it has been initialized." \
|
261
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
sig {params(const_name: Symbol).void}
|
266
|
+
def _bind_name(const_name)
|
267
|
+
@const_name = const_name
|
268
|
+
@serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
|
269
|
+
freeze
|
270
|
+
end
|
271
|
+
|
272
|
+
sig {params(const_name: Symbol).returns(String)}
|
273
|
+
private def const_to_serialized_val(const_name)
|
274
|
+
# Historical note: We convert to lowercase names because the majority of existing calls to
|
275
|
+
# `make_accessible` were arrays of lowercase strings. Doing this conversion allowed for the
|
276
|
+
# least amount of repetition in migrated declarations.
|
277
|
+
const_name.to_s.downcase.freeze
|
278
|
+
end
|
279
|
+
|
280
|
+
sig {returns(T::Boolean)}
|
281
|
+
def self.started_initializing?
|
282
|
+
@started_initializing = T.let(@started_initializing, T.nilable(T::Boolean))
|
283
|
+
@started_initializing ||= false
|
284
|
+
end
|
285
|
+
|
286
|
+
sig {returns(T::Boolean)}
|
287
|
+
def self.fully_initialized?
|
288
|
+
@fully_initialized = T.let(@fully_initialized, T.nilable(T::Boolean))
|
289
|
+
@fully_initialized ||= false
|
290
|
+
end
|
291
|
+
|
292
|
+
# Entrypoint for allowing people to register new enum values.
|
293
|
+
# All enum values must be defined within this block.
|
294
|
+
sig {params(blk: T.proc.void).void}
|
295
|
+
def self.enums(&blk)
|
296
|
+
raise "enums cannot be defined for T::Enum" if self == T::Enum
|
297
|
+
raise "Enum #{self} was already initialized" if @fully_initialized
|
298
|
+
raise "Enum #{self} is still initializing" if @started_initializing
|
299
|
+
|
300
|
+
@started_initializing = true
|
301
|
+
|
302
|
+
yield
|
303
|
+
|
304
|
+
@values = T.let(nil, T.nilable(T::Array[T.experimental_attached_class]))
|
305
|
+
@mapping = T.let(nil, T.nilable(T::Hash[SerializedVal, T.experimental_attached_class]))
|
306
|
+
|
307
|
+
# Freeze the Enum class and bind the constant names into each of the instances.
|
308
|
+
@mapping = {}
|
309
|
+
self.constants(false).each do |const_name|
|
310
|
+
instance = self.const_get(const_name, false)
|
311
|
+
if !instance.is_a?(self)
|
312
|
+
raise "Invalid constant #{self}::#{const_name} on enum. " \
|
313
|
+
"All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
|
314
|
+
end
|
315
|
+
|
316
|
+
instance._bind_name(const_name)
|
317
|
+
serialized = instance.serialize
|
318
|
+
if @mapping.include?(serialized)
|
319
|
+
raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
|
320
|
+
end
|
321
|
+
@mapping[serialized] = instance
|
322
|
+
end
|
323
|
+
@values = @mapping.values.sort.freeze
|
324
|
+
@mapping.freeze
|
325
|
+
@fully_initialized = true
|
326
|
+
end
|
327
|
+
|
328
|
+
sig {params(child_class: Module).void}
|
329
|
+
def self.inherited(child_class)
|
330
|
+
super
|
331
|
+
|
332
|
+
raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
|
333
|
+
end
|
334
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorbet-runtime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4972
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stripe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-11-
|
11
|
+
date: 2019-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/types/boolean.rb
|
79
79
|
- lib/types/compatibility_patches.rb
|
80
80
|
- lib/types/configuration.rb
|
81
|
+
- lib/types/enum.rb
|
81
82
|
- lib/types/generic.rb
|
82
83
|
- lib/types/helpers.rb
|
83
84
|
- lib/types/interface_wrapper.rb
|