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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43f204f6d778bf65a3c81518f5ac3c2160b74f4bcd0ea73654285fa9561d7884
4
- data.tar.gz: d897c48135338d0edaaf7562ca4de202c0c139e124d041bc29fb08485162107f
3
+ metadata.gz: 53211ac8c4d54178673a7fb13f499fea6e0808239266f249027303fedab70fb7
4
+ data.tar.gz: b5952024c57f88f975fb689e8b921b283eb4a20bf56b04380b6c8cf3236a968e
5
5
  SHA512:
6
- metadata.gz: ce7b1756acfbc8a10b38550642a5f2af8bade745b80bb88dfe6278659f2a8d48589cd8b56d6a537f799d94098d91f817d3a96227223d1432f5ec342ded73c51d
7
- data.tar.gz: dde92e7a96749903b11bd41485859200c06bed34de58b0cba887204560b9602838592baeb0964675ed5216e6f0887b400cee9f2248367dca945c2999086196fe
6
+ metadata.gz: f0aed40e7e7617d289e77544f30a2ed8335ea1a4480159b75194622cd777897c64630f417b9f35dc99571e9c8818e3e76f9227ea0ed90ba3625463524e3fc75a
7
+ data.tar.gz: 2f21287bacccc1557d1fb5c4c257c7c34259724e2306a5db27c98620841af4a643e004913a79f421ca2d760003cdf1b88081346c4c2b85184eeb5eef8582b90b
@@ -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'
@@ -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.4969
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-02 00:00:00.000000000 Z
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