sorbet-runtime 0.4.4969 → 0.4.4972
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 +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
|