sorbet-runtime 0.4.4667 → 0.5.6189
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/sorbet-runtime.rb +15 -3
- data/lib/types/_types.rb +26 -17
- data/lib/types/boolean.rb +1 -1
- data/lib/types/compatibility_patches.rb +65 -10
- data/lib/types/configuration.rb +93 -7
- data/lib/types/enum.rb +371 -0
- data/lib/types/generic.rb +2 -2
- data/lib/types/interface_wrapper.rb +4 -4
- data/lib/types/non_forcing_constants.rb +61 -0
- data/lib/types/private/abstract/data.rb +2 -2
- data/lib/types/private/abstract/declare.rb +3 -0
- data/lib/types/private/abstract/validate.rb +7 -7
- data/lib/types/private/casts.rb +27 -0
- data/lib/types/private/class_utils.rb +8 -5
- data/lib/types/private/methods/_methods.rb +80 -28
- data/lib/types/private/methods/call_validation.rb +5 -47
- data/lib/types/private/methods/decl_builder.rb +14 -56
- data/lib/types/private/methods/modes.rb +5 -7
- data/lib/types/private/methods/signature.rb +32 -18
- data/lib/types/private/methods/signature_validation.rb +29 -35
- data/lib/types/private/retry.rb +10 -0
- data/lib/types/private/sealed.rb +21 -1
- data/lib/types/private/types/type_alias.rb +31 -0
- data/lib/types/private/types/void.rb +4 -3
- data/lib/types/profile.rb +5 -1
- data/lib/types/props/_props.rb +3 -7
- data/lib/types/props/constructor.rb +29 -9
- data/lib/types/props/custom_type.rb +51 -27
- data/lib/types/props/decorator.rb +248 -405
- data/lib/types/props/generated_code_validation.rb +268 -0
- data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
- data/lib/types/props/optional.rb +37 -41
- data/lib/types/props/plugin.rb +23 -1
- data/lib/types/props/pretty_printable.rb +3 -3
- data/lib/types/props/private/apply_default.rb +170 -0
- data/lib/types/props/private/deserializer_generator.rb +165 -0
- data/lib/types/props/private/parser.rb +32 -0
- data/lib/types/props/private/serde_transform.rb +186 -0
- data/lib/types/props/private/serializer_generator.rb +77 -0
- data/lib/types/props/private/setter_factory.rb +139 -0
- data/lib/types/props/serializable.rb +137 -192
- data/lib/types/props/type_validation.rb +19 -6
- data/lib/types/props/utils.rb +3 -7
- data/lib/types/props/weak_constructor.rb +51 -14
- data/lib/types/sig.rb +6 -6
- data/lib/types/types/attached_class.rb +37 -0
- data/lib/types/types/base.rb +26 -2
- data/lib/types/types/fixed_array.rb +28 -2
- data/lib/types/types/fixed_hash.rb +11 -10
- data/lib/types/types/intersection.rb +6 -0
- data/lib/types/types/noreturn.rb +4 -0
- data/lib/types/types/self_type.rb +4 -0
- data/lib/types/types/simple.rb +22 -1
- data/lib/types/types/t_enum.rb +38 -0
- data/lib/types/types/type_parameter.rb +1 -1
- data/lib/types/types/type_variable.rb +1 -1
- data/lib/types/types/typed_array.rb +7 -2
- data/lib/types/types/typed_enumerable.rb +28 -17
- data/lib/types/types/typed_enumerator.rb +7 -2
- data/lib/types/types/typed_hash.rb +8 -3
- data/lib/types/types/typed_range.rb +7 -2
- data/lib/types/types/typed_set.rb +7 -2
- data/lib/types/types/union.rb +37 -5
- data/lib/types/types/untyped.rb +4 -0
- data/lib/types/utils.rb +43 -11
- metadata +103 -11
- data/lib/types/private/error_handler.rb +0 -0
- data/lib/types/runtime_profiled.rb +0 -24
- data/lib/types/types/opus_enum.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '078455e4f5dfee05efee1b1dc5283a572d80e4583699ef18fbd51cc606c1a559'
|
4
|
+
data.tar.gz: 6d77cb7b423ae756a33c04e15a50ff0d7d17a0cbd0e25460c0ce89b25959fa97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 171c564c0b615d126fa8e14bff2dc5567baa53a2c0d702032f8971fd4f83e1cd3c3d888e4c4ac0e9843e0b516fe1563807cd6d8afcb9d8c0dfa15da28ff8758d
|
7
|
+
data.tar.gz: 967a1bb33ed88e50c31bf01dbbfdaad43e7ef7f66723c5648a58bc47d19cb5f5c46b7fd9a3733310cc07eb72743d32bb80341aa7e3f529ea5584def01195591e
|
data/lib/sorbet-runtime.rb
CHANGED
@@ -22,9 +22,7 @@ require_relative 'types/configuration'
|
|
22
22
|
require_relative 'types/profile'
|
23
23
|
require_relative 'types/_types'
|
24
24
|
require_relative 'types/private/decl_state'
|
25
|
-
require_relative 'types/runtime_profiled'
|
26
25
|
require_relative 'types/private/class_utils'
|
27
|
-
require_relative 'types/private/error_handler'
|
28
26
|
require_relative 'types/private/runtime_levels'
|
29
27
|
require_relative 'types/private/methods/_methods'
|
30
28
|
require_relative 'types/sig'
|
@@ -43,9 +41,10 @@ require_relative 'types/types/fixed_hash'
|
|
43
41
|
require_relative 'types/types/intersection'
|
44
42
|
require_relative 'types/types/noreturn'
|
45
43
|
require_relative 'types/types/proc'
|
44
|
+
require_relative 'types/types/attached_class'
|
46
45
|
require_relative 'types/types/self_type'
|
47
46
|
require_relative 'types/types/simple'
|
48
|
-
require_relative 'types/types/
|
47
|
+
require_relative 'types/types/t_enum'
|
49
48
|
require_relative 'types/types/type_parameter'
|
50
49
|
require_relative 'types/types/typed_array'
|
51
50
|
require_relative 'types/types/typed_enumerator'
|
@@ -57,6 +56,7 @@ require_relative 'types/types/untyped'
|
|
57
56
|
require_relative 'types/private/types/not_typed'
|
58
57
|
require_relative 'types/private/types/void'
|
59
58
|
require_relative 'types/private/types/string_holder'
|
59
|
+
require_relative 'types/private/types/type_alias'
|
60
60
|
|
61
61
|
require_relative 'types/types/type_variable'
|
62
62
|
require_relative 'types/types/type_member'
|
@@ -79,6 +79,7 @@ require_relative 'types/private/abstract/hooks'
|
|
79
79
|
require_relative 'types/private/casts'
|
80
80
|
require_relative 'types/private/methods/decl_builder'
|
81
81
|
require_relative 'types/private/methods/signature'
|
82
|
+
require_relative 'types/private/retry'
|
82
83
|
require_relative 'types/utils'
|
83
84
|
require_relative 'types/boolean'
|
84
85
|
|
@@ -91,13 +92,24 @@ require_relative 'types/props/decorator'
|
|
91
92
|
require_relative 'types/props/errors'
|
92
93
|
require_relative 'types/props/plugin'
|
93
94
|
require_relative 'types/props/utils'
|
95
|
+
require_relative 'types/enum'
|
94
96
|
# Props that run sigs statically so have to be after all the others :(
|
97
|
+
require_relative 'types/props/private/setter_factory'
|
98
|
+
require_relative 'types/props/private/apply_default'
|
99
|
+
require_relative 'types/props/has_lazily_specialized_methods'
|
95
100
|
require_relative 'types/props/optional'
|
96
101
|
require_relative 'types/props/weak_constructor'
|
97
102
|
require_relative 'types/props/constructor'
|
98
103
|
require_relative 'types/props/pretty_printable'
|
104
|
+
require_relative 'types/props/private/serde_transform'
|
105
|
+
require_relative 'types/props/private/deserializer_generator'
|
106
|
+
require_relative 'types/props/private/serializer_generator'
|
99
107
|
require_relative 'types/props/serializable'
|
100
108
|
require_relative 'types/props/type_validation'
|
109
|
+
require_relative 'types/props/private/parser'
|
110
|
+
require_relative 'types/props/generated_code_validation'
|
111
|
+
|
101
112
|
require_relative 'types/struct'
|
113
|
+
require_relative 'types/non_forcing_constants'
|
102
114
|
|
103
115
|
require_relative 'types/compatibility_patches'
|
data/lib/types/_types.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# typed: true
|
3
3
|
# This is where we define the shortcuts, so we can't use them here
|
4
|
-
# rubocop:disable PrisonGuard/UseOpusTypesShortcut
|
5
4
|
|
6
5
|
# _____
|
7
6
|
# |_ _| _ _ __ ___ ___
|
@@ -26,23 +25,26 @@
|
|
26
25
|
module T
|
27
26
|
# T.any(<Type>, <Type>, ...) -- matches any of the types listed
|
28
27
|
def self.any(type_a, type_b, *types)
|
29
|
-
T::
|
28
|
+
type_a = T::Utils.coerce(type_a)
|
29
|
+
type_b = T::Utils.coerce(type_b)
|
30
|
+
types = types.map {|t| T::Utils.coerce(t)} if !types.empty?
|
31
|
+
T::Types::Union::Private::Pool.union_of_types(type_a, type_b, types)
|
30
32
|
end
|
31
33
|
|
32
34
|
# Shorthand for T.any(type, NilClass)
|
33
35
|
def self.nilable(type)
|
34
|
-
T::Types::Union.
|
36
|
+
T::Types::Union::Private::Pool.union_of_types(T::Utils.coerce(type), T::Utils::Nilable::NIL_TYPE)
|
35
37
|
end
|
36
38
|
|
37
39
|
# Matches any object. In the static checker, T.untyped allows any
|
38
40
|
# method calls or operations.
|
39
41
|
def self.untyped
|
40
|
-
T::Types::Untyped
|
42
|
+
T::Types::Untyped::Private::INSTANCE
|
41
43
|
end
|
42
44
|
|
43
45
|
# Indicates a function never returns (e.g. "Kernel#raise")
|
44
46
|
def self.noreturn
|
45
|
-
T::Types::NoReturn
|
47
|
+
T::Types::NoReturn::Private::INSTANCE
|
46
48
|
end
|
47
49
|
|
48
50
|
# T.all(<Type>, <Type>, ...) -- matches an object that has all of the types listed
|
@@ -62,7 +64,12 @@ module T
|
|
62
64
|
|
63
65
|
# Matches `self`:
|
64
66
|
def self.self_type
|
65
|
-
T::Types::SelfType
|
67
|
+
T::Types::SelfType::Private::INSTANCE
|
68
|
+
end
|
69
|
+
|
70
|
+
# Matches the instance type in a singleton-class context
|
71
|
+
def self.attached_class
|
72
|
+
T::Types::AttachedClassType::Private::INSTANCE
|
66
73
|
end
|
67
74
|
|
68
75
|
# Matches any class that subclasses or includes the provided class
|
@@ -75,11 +82,11 @@ module T
|
|
75
82
|
## END OF THE METHODS TO PASS TO `sig`.
|
76
83
|
|
77
84
|
|
78
|
-
# Constructs a type alias. Used to create a short name for a larger
|
79
|
-
#
|
80
|
-
# needed for support by the static checker. Example usage:
|
85
|
+
# Constructs a type alias. Used to create a short name for a larger type. In Ruby this returns a
|
86
|
+
# wrapper that contains a proc that is evaluated to get the underlying type. This syntax however
|
87
|
+
# is needed for support by the static checker. Example usage:
|
81
88
|
#
|
82
|
-
# NilableString = T.type_alias
|
89
|
+
# NilableString = T.type_alias {T.nilable(String)}
|
83
90
|
#
|
84
91
|
# sig {params(arg: NilableString, default: String).returns(String)}
|
85
92
|
# def or_else(arg, default)
|
@@ -88,11 +95,17 @@ module T
|
|
88
95
|
#
|
89
96
|
# The name of the type alias is not preserved; Error messages will
|
90
97
|
# be printed with reference to the underlying type.
|
91
|
-
|
92
|
-
|
98
|
+
#
|
99
|
+
# TODO Remove `type` parameter. This was left in to make life easier while migrating.
|
100
|
+
def self.type_alias(type=nil, &blk)
|
101
|
+
if blk
|
102
|
+
T::Private::Types::TypeAlias.new(blk)
|
103
|
+
else
|
104
|
+
T::Utils.coerce(type)
|
105
|
+
end
|
93
106
|
end
|
94
107
|
|
95
|
-
# References a type
|
108
|
+
# References a type parameter which was previously defined with
|
96
109
|
# `type_parameters`.
|
97
110
|
#
|
98
111
|
# This is used for generic methods. Example usage:
|
@@ -270,8 +283,4 @@ module T
|
|
270
283
|
end
|
271
284
|
end
|
272
285
|
end
|
273
|
-
|
274
|
-
# When mixed into a module, indicates that Sorbet may export the CFG for methods in that module
|
275
|
-
module CFGExport; end
|
276
286
|
end
|
277
|
-
# rubocop:enable PrisonGuard/UseOpusTypesShortcut
|
data/lib/types/boolean.rb
CHANGED
@@ -4,5 +4,5 @@
|
|
4
4
|
module T
|
5
5
|
# T::Boolean is a type alias helper for the common `T.any(TrueClass, FalseClass)`.
|
6
6
|
# Defined separately from _types.rb because it has a dependency on T::Types::Union.
|
7
|
-
Boolean = T.type_alias
|
7
|
+
Boolean = T.type_alias {T.any(TrueClass, FalseClass)}
|
8
8
|
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# typed: ignore
|
3
3
|
|
4
|
-
require_relative 'private/methods/_methods'
|
5
|
-
|
6
4
|
# Work around an interaction bug with sorbet-runtime and rspec-mocks,
|
7
|
-
# which occurs when using *_any_instance_of
|
5
|
+
# which occurs when using message expectations (*_any_instance_of,
|
6
|
+
# expect, allow) and and_call_original.
|
8
7
|
#
|
9
8
|
# When a sig is defined, sorbet-runtime will replace the sigged method
|
10
9
|
# with a wrapper that, upon first invocation, re-wraps the method with a faster
|
@@ -22,17 +21,73 @@ require_relative 'private/methods/_methods'
|
|
22
21
|
#
|
23
22
|
# We work around this by forcing re-wrapping before rspec stores a reference
|
24
23
|
# to the method.
|
25
|
-
if defined? ::RSpec::Mocks
|
24
|
+
if defined? ::RSpec::Mocks
|
26
25
|
module T
|
27
26
|
module CompatibilityPatches
|
28
|
-
module
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
module RSpecCompatibility
|
28
|
+
module RecorderExtensions
|
29
|
+
def observe!(method_name)
|
30
|
+
method = @klass.instance_method(method_name.to_sym)
|
31
|
+
T::Private::Methods.maybe_run_sig_block_for_method(method)
|
32
|
+
super(method_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
::RSpec::Mocks::AnyInstance::Recorder.prepend(RecorderExtensions) if defined?(::RSpec::Mocks::AnyInstance::Recorder)
|
36
|
+
|
37
|
+
module MethodDoubleExtensions
|
38
|
+
def initialize(object, method_name, proxy)
|
39
|
+
if ::Kernel.instance_method(:respond_to?).bind(object).call(method_name, true)
|
40
|
+
method = ::RSpec::Support.method_handle_for(object, method_name)
|
41
|
+
T::Private::Methods.maybe_run_sig_block_for_method(method)
|
42
|
+
end
|
43
|
+
super(object, method_name, proxy)
|
44
|
+
end
|
33
45
|
end
|
46
|
+
::RSpec::Mocks::MethodDouble.prepend(MethodDoubleExtensions) if defined?(::RSpec::Mocks::MethodDouble)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Work around for sorbet-runtime wrapped methods.
|
53
|
+
#
|
54
|
+
# When a sig is defined, sorbet-runtime will replace the sigged method
|
55
|
+
# with a wrapper. Those wrapper methods look like `foo(*args, &blk)`
|
56
|
+
# so that wrappers can handle and pass on all the arguments supplied.
|
57
|
+
#
|
58
|
+
# However, that creates a problem with runtime reflection on the methods,
|
59
|
+
# since when a sigged method is introspected, it will always return its
|
60
|
+
# `arity` as `-1`, its `parameters` as `[[:rest, :args], [:block, :blk]]`,
|
61
|
+
# and its `source_location` as `[<some_file_in_sorbet>, <some_line_number>]`.
|
62
|
+
#
|
63
|
+
# This might be a problem for some applications that rely on getting the
|
64
|
+
# correct information from these methods.
|
65
|
+
#
|
66
|
+
# This compatibility module, when prepended to the `Method` class, would fix
|
67
|
+
# the return values of `arity`, `parameters` and `source_location`.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# require 'sorbet-runtime'
|
71
|
+
# ::Method.prepend(T::CompatibilityPatches::MethodExtensions)
|
72
|
+
module T
|
73
|
+
module CompatibilityPatches
|
74
|
+
module MethodExtensions
|
75
|
+
def arity
|
76
|
+
arity = super
|
77
|
+
return arity if arity != -1 || self.is_a?(Proc)
|
78
|
+
sig = T::Private::Methods.signature_for_method(self)
|
79
|
+
sig ? sig.method.arity : arity
|
80
|
+
end
|
81
|
+
|
82
|
+
def source_location
|
83
|
+
sig = T::Private::Methods.signature_for_method(self)
|
84
|
+
sig ? sig.method.source_location : super
|
85
|
+
end
|
86
|
+
|
87
|
+
def parameters
|
88
|
+
sig = T::Private::Methods.signature_for_method(self)
|
89
|
+
sig ? sig.method.parameters : super
|
34
90
|
end
|
35
|
-
::RSpec::Mocks::AnyInstance::Recorder.prepend(RecorderExtensions)
|
36
91
|
end
|
37
92
|
end
|
38
93
|
end
|
data/lib/types/configuration.rb
CHANGED
@@ -42,6 +42,34 @@ module T::Configuration
|
|
42
42
|
T::Private::Methods.set_final_checks_on_hooks(false)
|
43
43
|
end
|
44
44
|
|
45
|
+
@include_value_in_type_errors = true
|
46
|
+
# Whether to include values in TypeError messages.
|
47
|
+
#
|
48
|
+
# Including values is useful for debugging, but can potentially leak
|
49
|
+
# sensitive information to logs.
|
50
|
+
#
|
51
|
+
# @return [T::Boolean]
|
52
|
+
def self.include_value_in_type_errors?
|
53
|
+
@include_value_in_type_errors
|
54
|
+
end
|
55
|
+
|
56
|
+
# Configure if type errors excludes the value of the problematic type.
|
57
|
+
#
|
58
|
+
# The default is to include values in type errors:
|
59
|
+
# TypeError: Expected type Integer, got String with value "foo"
|
60
|
+
#
|
61
|
+
# When values are excluded from type errors:
|
62
|
+
# TypeError: Expected type Integer, got String
|
63
|
+
def self.exclude_value_in_type_errors
|
64
|
+
@include_value_in_type_errors = false
|
65
|
+
end
|
66
|
+
|
67
|
+
# Opposite of exclude_value_in_type_errors.
|
68
|
+
# (Including values in type errors is the default)
|
69
|
+
def self.include_value_in_type_errors
|
70
|
+
@include_value_in_type_errors = true
|
71
|
+
end
|
72
|
+
|
45
73
|
# Configure the default checked level for a sig with no explicit `.checked`
|
46
74
|
# builder. When unset, the default checked level is `:always`.
|
47
75
|
#
|
@@ -248,7 +276,7 @@ module T::Configuration
|
|
248
276
|
end
|
249
277
|
|
250
278
|
private_class_method def self.log_info_handler_default(str, extra)
|
251
|
-
puts "#{str}, extra: #{extra}"
|
279
|
+
puts "#{str}, extra: #{extra}"
|
252
280
|
end
|
253
281
|
|
254
282
|
def self.log_info_handler(str, extra)
|
@@ -282,7 +310,7 @@ module T::Configuration
|
|
282
310
|
end
|
283
311
|
|
284
312
|
private_class_method def self.soft_assert_handler_default(str, extra)
|
285
|
-
puts "#{str}, extra: #{extra}"
|
313
|
+
puts "#{str}, extra: #{extra}"
|
286
314
|
end
|
287
315
|
|
288
316
|
def self.soft_assert_handler(str, extra)
|
@@ -319,7 +347,7 @@ module T::Configuration
|
|
319
347
|
raise str
|
320
348
|
end
|
321
349
|
|
322
|
-
def self.hard_assert_handler(str, extra)
|
350
|
+
def self.hard_assert_handler(str, extra={})
|
323
351
|
if @hard_assert_handler
|
324
352
|
@hard_assert_handler.call(str, extra)
|
325
353
|
else
|
@@ -336,9 +364,9 @@ module T::Configuration
|
|
336
364
|
# T::Configuration.scalar_types = ["NilClass", "TrueClass", "FalseClass", ...]
|
337
365
|
def self.scalar_types=(values)
|
338
366
|
if values.nil?
|
339
|
-
@
|
367
|
+
@scalar_types = values
|
340
368
|
else
|
341
|
-
bad_values = values.
|
369
|
+
bad_values = values.reject {|v| v.class == String}
|
342
370
|
unless bad_values.empty?
|
343
371
|
raise ArgumentError.new("Provided values must all be class name strings.")
|
344
372
|
end
|
@@ -347,7 +375,7 @@ module T::Configuration
|
|
347
375
|
end
|
348
376
|
end
|
349
377
|
|
350
|
-
@default_scalar_types = Set.new(%w
|
378
|
+
@default_scalar_types = Set.new(%w[
|
351
379
|
NilClass
|
352
380
|
TrueClass
|
353
381
|
FalseClass
|
@@ -356,12 +384,34 @@ module T::Configuration
|
|
356
384
|
String
|
357
385
|
Symbol
|
358
386
|
Time
|
359
|
-
|
387
|
+
T::Enum
|
388
|
+
]).freeze
|
360
389
|
|
361
390
|
def self.scalar_types
|
362
391
|
@scalar_types || @default_scalar_types
|
363
392
|
end
|
364
393
|
|
394
|
+
# Guard against overrides of `name` or `to_s`
|
395
|
+
MODULE_NAME = Module.instance_method(:name)
|
396
|
+
private_constant :MODULE_NAME
|
397
|
+
|
398
|
+
@default_module_name_mangler = ->(type) {MODULE_NAME.bind(type).call}
|
399
|
+
@module_name_mangler = nil
|
400
|
+
|
401
|
+
def self.module_name_mangler
|
402
|
+
@module_name_mangler || @default_module_name_mangler
|
403
|
+
end
|
404
|
+
|
405
|
+
# Set to override the default behavior for converting types
|
406
|
+
# to names in generated code. Used by the runtime implementation
|
407
|
+
# associated with `--stripe-packages` mode.
|
408
|
+
#
|
409
|
+
# @param [Lambda, Proc, nil] value Proc that converts a type (Class/Module)
|
410
|
+
# to a String (pass nil to reset to default behavior)
|
411
|
+
def self.module_name_mangler=(handler)
|
412
|
+
@module_name_mangler = handler
|
413
|
+
end
|
414
|
+
|
365
415
|
# Temporarily disable ruby warnings while executing the given block. This is
|
366
416
|
# useful when doing something that would normally cause a warning to be
|
367
417
|
# emitted in Ruby verbose mode ($VERBOSE = true).
|
@@ -382,6 +432,42 @@ module T::Configuration
|
|
382
432
|
end
|
383
433
|
end
|
384
434
|
|
435
|
+
def self.enable_legacy_t_enum_migration_mode
|
436
|
+
@legacy_t_enum_migration_mode = true
|
437
|
+
end
|
438
|
+
def self.disable_legacy_t_enum_migration_mode
|
439
|
+
@legacy_t_enum_migration_mode = false
|
440
|
+
end
|
441
|
+
def self.legacy_t_enum_migration_mode?
|
442
|
+
@legacy_t_enum_migration_mode || false
|
443
|
+
end
|
444
|
+
|
445
|
+
# @param [Array] sealed_violation_whitelist An array of Regexp to validate
|
446
|
+
# whether inheriting /including a sealed module outside the defining module
|
447
|
+
# should be allowed. Useful to whitelist benign violations, like shim files
|
448
|
+
# generated for an autoloader.
|
449
|
+
def self.sealed_violation_whitelist=(sealed_violation_whitelist)
|
450
|
+
if !@sealed_violation_whitelist.nil?
|
451
|
+
raise ArgumentError.new("Cannot overwrite sealed_violation_whitelist after setting it")
|
452
|
+
end
|
453
|
+
|
454
|
+
case sealed_violation_whitelist
|
455
|
+
when Array
|
456
|
+
sealed_violation_whitelist.each do |x|
|
457
|
+
case x
|
458
|
+
when Regexp then nil
|
459
|
+
else raise TypeError.new("sealed_violation_whitelist accepts an Array of Regexp")
|
460
|
+
end
|
461
|
+
end
|
462
|
+
else
|
463
|
+
raise TypeError.new("sealed_violation_whitelist= accepts an Array of Regexp")
|
464
|
+
end
|
465
|
+
|
466
|
+
@sealed_violation_whitelist = sealed_violation_whitelist
|
467
|
+
end
|
468
|
+
def self.sealed_violation_whitelist
|
469
|
+
@sealed_violation_whitelist
|
470
|
+
end
|
385
471
|
|
386
472
|
private_class_method def self.validate_lambda_given!(value)
|
387
473
|
if !value.nil? && !value.respond_to?(:call)
|
data/lib/types/enum.rb
ADDED
@@ -0,0 +1,371 @@
|
|
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 = new
|
16
|
+
# SPADE = new
|
17
|
+
# DIAMOND = new
|
18
|
+
# HEART = new
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example Custom serialization value:
|
23
|
+
# class Status < T::Enum
|
24
|
+
# enums do
|
25
|
+
# READY = new('rdy')
|
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.deserialize('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.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
|
+
# This exists for compatibility with the interface of `Hash` & mostly to support
|
61
|
+
# 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]))}
|
63
|
+
def self.each_value(&blk)
|
64
|
+
if blk
|
65
|
+
values.each(&blk)
|
66
|
+
else
|
67
|
+
values.each
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Convert from serialized value to enum instance
|
72
|
+
#
|
73
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
74
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
75
|
+
sig {params(serialized_val: SerializedVal).returns(T.nilable(T.attached_class)).checked(:never)}
|
76
|
+
def self.try_deserialize(serialized_val)
|
77
|
+
if @mapping.nil?
|
78
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
79
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
80
|
+
end
|
81
|
+
@mapping[serialized_val]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Convert from serialized value to enum instance.
|
85
|
+
#
|
86
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
87
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
88
|
+
#
|
89
|
+
# @return [self]
|
90
|
+
# @raise [KeyError] if serialized value does not match any instance.
|
91
|
+
sig {overridable.params(serialized_val: SerializedVal).returns(T.attached_class).checked(:never)}
|
92
|
+
def self.from_serialized(serialized_val)
|
93
|
+
res = try_deserialize(serialized_val)
|
94
|
+
if res.nil?
|
95
|
+
raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
|
96
|
+
end
|
97
|
+
res
|
98
|
+
end
|
99
|
+
|
100
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
101
|
+
# @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).checked(:never)}
|
103
|
+
def self.has_serialized?(serialized_val)
|
104
|
+
if @mapping.nil?
|
105
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
106
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
107
|
+
end
|
108
|
+
@mapping.include?(serialized_val)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
112
|
+
sig {override.params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
|
113
|
+
def self.serialize(instance)
|
114
|
+
# This is needed otherwise if a Chalk::ODM::Document with a property of the shape
|
115
|
+
# T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
|
116
|
+
# serialized, we throw the error on L102.
|
117
|
+
return nil if instance.nil?
|
118
|
+
|
119
|
+
if self == T::Enum
|
120
|
+
raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
|
121
|
+
end
|
122
|
+
if instance.class != self
|
123
|
+
raise "Cannot call #serialize on a value that is not an instance of #{self}."
|
124
|
+
end
|
125
|
+
instance.serialize
|
126
|
+
end
|
127
|
+
|
128
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
129
|
+
sig {override.params(mongo_value: SerializedVal).returns(T.attached_class).checked(:never)}
|
130
|
+
def self.deserialize(mongo_value)
|
131
|
+
if self == T::Enum
|
132
|
+
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
|
133
|
+
end
|
134
|
+
self.from_serialized(mongo_value)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
## Enum instance methods ##
|
139
|
+
|
140
|
+
|
141
|
+
sig {returns(T.self_type)}
|
142
|
+
def dup
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
sig {returns(T.self_type).checked(:tests)}
|
147
|
+
def clone
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
152
|
+
sig {returns(SerializedVal).checked(:never)}
|
153
|
+
def serialize
|
154
|
+
assert_bound!
|
155
|
+
@serialized_val
|
156
|
+
end
|
157
|
+
|
158
|
+
sig {params(args: T.untyped).returns(T.untyped)}
|
159
|
+
def to_json(*args)
|
160
|
+
serialize.to_json(*args)
|
161
|
+
end
|
162
|
+
|
163
|
+
sig {returns(String)}
|
164
|
+
def to_s
|
165
|
+
inspect
|
166
|
+
end
|
167
|
+
|
168
|
+
sig {returns(String)}
|
169
|
+
def inspect
|
170
|
+
"#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
|
171
|
+
end
|
172
|
+
|
173
|
+
sig {params(other: BasicObject).returns(T.nilable(Integer))}
|
174
|
+
def <=>(other)
|
175
|
+
case other
|
176
|
+
when self.class
|
177
|
+
self.serialize <=> other.serialize
|
178
|
+
else
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
# NB: Do not call this method. This exists to allow for a safe migration path in places where enum
|
185
|
+
# values are compared directly against string values.
|
186
|
+
#
|
187
|
+
# Ruby's string has a weird quirk where `'my_string' == obj` calls obj.==('my_string') if obj
|
188
|
+
# responds to the `to_str` method. It does not actually call `to_str` however.
|
189
|
+
#
|
190
|
+
# See https://ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D
|
191
|
+
sig {returns(String)}
|
192
|
+
def to_str
|
193
|
+
msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
|
194
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
195
|
+
T::Configuration.soft_assert_handler(
|
196
|
+
msg,
|
197
|
+
storytime: {class: self.class.name},
|
198
|
+
)
|
199
|
+
serialize.to_s
|
200
|
+
else
|
201
|
+
raise NoMethodError.new(msg)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
|
206
|
+
def ==(other)
|
207
|
+
case other
|
208
|
+
when String
|
209
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
210
|
+
comparison_assertion_failed(:==, other)
|
211
|
+
self.serialize == other
|
212
|
+
else
|
213
|
+
false
|
214
|
+
end
|
215
|
+
else
|
216
|
+
super(other)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
|
221
|
+
def ===(other)
|
222
|
+
case other
|
223
|
+
when String
|
224
|
+
if T::Configuration.legacy_t_enum_migration_mode?
|
225
|
+
comparison_assertion_failed(:===, other)
|
226
|
+
self.serialize == other
|
227
|
+
else
|
228
|
+
false
|
229
|
+
end
|
230
|
+
else
|
231
|
+
super(other)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
sig {params(method: Symbol, other: T.untyped).void}
|
236
|
+
private def comparison_assertion_failed(method, other)
|
237
|
+
T::Configuration.soft_assert_handler(
|
238
|
+
'Enum to string comparison not allowed. Compare to the Enum instance directly instead. See go/enum-migration',
|
239
|
+
storytime: {
|
240
|
+
class: self.class.name,
|
241
|
+
self: self.inspect,
|
242
|
+
other: other,
|
243
|
+
other_class: other.class.name,
|
244
|
+
method: method,
|
245
|
+
}
|
246
|
+
)
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
## Private implementation ##
|
251
|
+
|
252
|
+
|
253
|
+
sig {params(serialized_val: SerializedVal).void}
|
254
|
+
def initialize(serialized_val=nil)
|
255
|
+
raise 'T::Enum is abstract' if self.class == T::Enum
|
256
|
+
if !self.class.started_initializing?
|
257
|
+
raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
|
258
|
+
end
|
259
|
+
if self.class.fully_initialized?
|
260
|
+
raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
|
261
|
+
end
|
262
|
+
|
263
|
+
serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
|
264
|
+
@serialized_val = T.let(serialized_val, T.nilable(SerializedVal))
|
265
|
+
@const_name = T.let(nil, T.nilable(Symbol))
|
266
|
+
self.class._register_instance(self)
|
267
|
+
end
|
268
|
+
|
269
|
+
sig {returns(NilClass).checked(:never)}
|
270
|
+
private def assert_bound!
|
271
|
+
if @const_name.nil?
|
272
|
+
raise "Attempting to access Enum value on #{self.class} before it has been initialized." \
|
273
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
sig {params(const_name: Symbol).void}
|
278
|
+
def _bind_name(const_name)
|
279
|
+
@const_name = const_name
|
280
|
+
@serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
|
281
|
+
freeze
|
282
|
+
end
|
283
|
+
|
284
|
+
sig {params(const_name: Symbol).returns(String)}
|
285
|
+
private def const_to_serialized_val(const_name)
|
286
|
+
# Historical note: We convert to lowercase names because the majority of existing calls to
|
287
|
+
# `make_accessible` were arrays of lowercase strings. Doing this conversion allowed for the
|
288
|
+
# least amount of repetition in migrated declarations.
|
289
|
+
const_name.to_s.downcase.freeze
|
290
|
+
end
|
291
|
+
|
292
|
+
sig {returns(T::Boolean)}
|
293
|
+
def self.started_initializing?
|
294
|
+
@started_initializing = T.let(@started_initializing, T.nilable(T::Boolean))
|
295
|
+
@started_initializing ||= false
|
296
|
+
end
|
297
|
+
|
298
|
+
sig {returns(T::Boolean)}
|
299
|
+
def self.fully_initialized?
|
300
|
+
@fully_initialized = T.let(@fully_initialized, T.nilable(T::Boolean))
|
301
|
+
@fully_initialized ||= false
|
302
|
+
end
|
303
|
+
|
304
|
+
# Maintains the order in which values are defined
|
305
|
+
sig {params(instance: T.untyped).void}
|
306
|
+
def self._register_instance(instance)
|
307
|
+
@values ||= []
|
308
|
+
@values << T.cast(instance, T.attached_class)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Entrypoint for allowing people to register new enum values.
|
312
|
+
# All enum values must be defined within this block.
|
313
|
+
sig {params(blk: T.proc.void).void}
|
314
|
+
def self.enums(&blk)
|
315
|
+
raise "enums cannot be defined for T::Enum" if self == T::Enum
|
316
|
+
raise "Enum #{self} was already initialized" if @fully_initialized
|
317
|
+
raise "Enum #{self} is still initializing" if @started_initializing
|
318
|
+
|
319
|
+
@started_initializing = true
|
320
|
+
|
321
|
+
@values = T.let(nil, T.nilable(T::Array[T.attached_class]))
|
322
|
+
|
323
|
+
yield
|
324
|
+
|
325
|
+
@mapping = T.let(nil, T.nilable(T::Hash[SerializedVal, T.attached_class]))
|
326
|
+
@mapping = {}
|
327
|
+
|
328
|
+
# Freeze the Enum class and bind the constant names into each of the instances.
|
329
|
+
self.constants(false).each do |const_name|
|
330
|
+
instance = self.const_get(const_name, false)
|
331
|
+
if !instance.is_a?(self)
|
332
|
+
raise "Invalid constant #{self}::#{const_name} on enum. " \
|
333
|
+
"All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
|
334
|
+
end
|
335
|
+
|
336
|
+
instance._bind_name(const_name)
|
337
|
+
serialized = instance.serialize
|
338
|
+
if @mapping.include?(serialized)
|
339
|
+
raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
|
340
|
+
end
|
341
|
+
@mapping[serialized] = instance
|
342
|
+
end
|
343
|
+
@values.freeze
|
344
|
+
@mapping.freeze
|
345
|
+
|
346
|
+
orphaned_instances = T.must(@values) - @mapping.values
|
347
|
+
if !orphaned_instances.empty?
|
348
|
+
raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
|
349
|
+
end
|
350
|
+
|
351
|
+
@fully_initialized = true
|
352
|
+
end
|
353
|
+
|
354
|
+
sig {params(child_class: Module).void}
|
355
|
+
def self.inherited(child_class)
|
356
|
+
super
|
357
|
+
|
358
|
+
raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
|
359
|
+
end
|
360
|
+
|
361
|
+
# Marshal support
|
362
|
+
sig {params(_level: Integer).returns(String)}
|
363
|
+
def _dump(_level)
|
364
|
+
Marshal.dump(serialize)
|
365
|
+
end
|
366
|
+
|
367
|
+
sig {params(args: String).returns(T.attached_class)}
|
368
|
+
def self._load(args)
|
369
|
+
deserialize(Marshal.load(args))
|
370
|
+
end
|
371
|
+
end
|