sorbet-runtime 0.5.5429 → 0.5.5794
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/_types.rb +1 -1
- data/lib/types/compatibility_patches.rb +65 -8
- data/lib/types/configuration.rb +1 -1
- data/lib/types/enum.rb +2 -11
- data/lib/types/non_forcing_constants.rb +51 -0
- data/lib/types/private/abstract/declare.rb +3 -0
- data/lib/types/private/class_utils.rb +3 -0
- data/lib/types/private/methods/call_validation.rb +3 -0
- data/lib/types/private/retry.rb +10 -0
- data/lib/types/props/_props.rb +1 -5
- data/lib/types/props/custom_type.rb +44 -32
- data/lib/types/props/decorator.rb +36 -202
- data/lib/types/props/generated_code_validation.rb +22 -5
- data/lib/types/props/has_lazily_specialized_methods.rb +4 -1
- data/lib/types/props/optional.rb +0 -16
- data/lib/types/props/private/deserializer_generator.rb +27 -2
- data/lib/types/props/private/serde_transform.rb +67 -11
- data/lib/types/props/private/setter_factory.rb +8 -13
- data/lib/types/props/serializable.rb +57 -3
- data/lib/types/props/type_validation.rb +10 -1
- data/lib/types/types/t_enum.rb +6 -1
- data/lib/types/types/type_parameter.rb +1 -1
- data/lib/types/types/typed_enumerable.rb +12 -2
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac9a48252c9529d0ca91f96108b141c85388c88180281e5c8a682b16c11a9d5a
|
|
4
|
+
data.tar.gz: 31dc95eabc00327b1672a8fc142da7dc47f0ee0699fd4112358042248628675b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21b1ef4ffdd76365effafe0c6a3eea4b15c1f9c36f2f402a1c8ba382d2f03d89f2202c1b8ac2aa82c315c7471f0989f7501d1888a49f67d401d33d26ac8e4d3a
|
|
7
|
+
data.tar.gz: 0f4c110725910ae452ea72fe6852538aa1c31cbb576dc242fe3631bd318e3b2bfda7b0fe3b05362584bd53885c9788a4f3eb2d5920d285c1c39ce727fb0ebd14
|
data/lib/sorbet-runtime.rb
CHANGED
|
@@ -80,6 +80,7 @@ require_relative 'types/private/abstract/hooks'
|
|
|
80
80
|
require_relative 'types/private/casts'
|
|
81
81
|
require_relative 'types/private/methods/decl_builder'
|
|
82
82
|
require_relative 'types/private/methods/signature'
|
|
83
|
+
require_relative 'types/private/retry'
|
|
83
84
|
require_relative 'types/utils'
|
|
84
85
|
require_relative 'types/boolean'
|
|
85
86
|
|
|
@@ -110,5 +111,6 @@ require_relative 'types/props/private/parser'
|
|
|
110
111
|
require_relative 'types/props/generated_code_validation'
|
|
111
112
|
|
|
112
113
|
require_relative 'types/struct'
|
|
114
|
+
require_relative 'types/non_forcing_constants'
|
|
113
115
|
|
|
114
116
|
require_relative 'types/compatibility_patches'
|
data/lib/types/_types.rb
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
require_relative 'private/methods/_methods'
|
|
5
5
|
|
|
6
6
|
# Work around an interaction bug with sorbet-runtime and rspec-mocks,
|
|
7
|
-
# which occurs when using *_any_instance_of
|
|
7
|
+
# which occurs when using message expectations (*_any_instance_of,
|
|
8
|
+
# expect, allow) and and_call_original.
|
|
8
9
|
#
|
|
9
10
|
# When a sig is defined, sorbet-runtime will replace the sigged method
|
|
10
11
|
# with a wrapper that, upon first invocation, re-wraps the method with a faster
|
|
@@ -22,17 +23,73 @@ require_relative 'private/methods/_methods'
|
|
|
22
23
|
#
|
|
23
24
|
# We work around this by forcing re-wrapping before rspec stores a reference
|
|
24
25
|
# to the method.
|
|
25
|
-
if defined? ::RSpec::Mocks
|
|
26
|
+
if defined? ::RSpec::Mocks
|
|
26
27
|
module T
|
|
27
28
|
module CompatibilityPatches
|
|
28
|
-
module
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
module RSpecCompatibility
|
|
30
|
+
module RecorderExtensions
|
|
31
|
+
def observe!(method_name)
|
|
32
|
+
method = @klass.instance_method(method_name.to_sym)
|
|
33
|
+
T::Private::Methods.maybe_run_sig_block_for_method(method)
|
|
34
|
+
super(method_name)
|
|
35
|
+
end
|
|
33
36
|
end
|
|
37
|
+
::RSpec::Mocks::AnyInstance::Recorder.prepend(RecorderExtensions) if defined?(::RSpec::Mocks::AnyInstance::Recorder)
|
|
38
|
+
|
|
39
|
+
module MethodDoubleExtensions
|
|
40
|
+
def initialize(object, method_name, proxy)
|
|
41
|
+
if ::Kernel.instance_method(:respond_to?).bind(object).call(method_name, true)
|
|
42
|
+
method = ::RSpec::Support.method_handle_for(object, method_name)
|
|
43
|
+
T::Private::Methods.maybe_run_sig_block_for_method(method)
|
|
44
|
+
end
|
|
45
|
+
super(object, method_name, proxy)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
::RSpec::Mocks::MethodDouble.prepend(MethodDoubleExtensions) if defined?(::RSpec::Mocks::MethodDouble)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Work around for sorbet-runtime wrapped methods.
|
|
55
|
+
#
|
|
56
|
+
# When a sig is defined, sorbet-runtime will replace the sigged method
|
|
57
|
+
# with a wrapper. Those wrapper methods look like `foo(*args, &blk)`
|
|
58
|
+
# so that wrappers can handle and pass on all the arguments supplied.
|
|
59
|
+
#
|
|
60
|
+
# However, that creates a problem with runtime reflection on the methods,
|
|
61
|
+
# since when a sigged method is introspected, it will always return its
|
|
62
|
+
# `arity` as `-1`, its `parameters` as `[[:rest, :args], [:block, :blk]]`,
|
|
63
|
+
# and its `source_location` as `[<some_file_in_sorbet>, <some_line_number>]`.
|
|
64
|
+
#
|
|
65
|
+
# This might be a problem for some applications that rely on getting the
|
|
66
|
+
# correct information from these methods.
|
|
67
|
+
#
|
|
68
|
+
# This compatibility module, when prepended to the `Method` class, would fix
|
|
69
|
+
# the return values of `arity`, `parameters` and `source_location`.
|
|
70
|
+
#
|
|
71
|
+
# @example
|
|
72
|
+
# require 'sorbet-runtime'
|
|
73
|
+
# ::Method.prepend(T::CompatibilityPatches::MethodExtensions)
|
|
74
|
+
module T
|
|
75
|
+
module CompatibilityPatches
|
|
76
|
+
module MethodExtensions
|
|
77
|
+
def arity
|
|
78
|
+
arity = super
|
|
79
|
+
return arity if arity != -1 || self.is_a?(Proc)
|
|
80
|
+
sig = T::Private::Methods.signature_for_method(self)
|
|
81
|
+
sig ? sig.method.arity : arity
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def source_location
|
|
85
|
+
sig = T::Private::Methods.signature_for_method(self)
|
|
86
|
+
sig ? sig.method.source_location : super
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parameters
|
|
90
|
+
sig = T::Private::Methods.signature_for_method(self)
|
|
91
|
+
sig ? sig.method.parameters : super
|
|
34
92
|
end
|
|
35
|
-
::RSpec::Mocks::AnyInstance::Recorder.prepend(RecorderExtensions)
|
|
36
93
|
end
|
|
37
94
|
end
|
|
38
95
|
end
|
data/lib/types/configuration.rb
CHANGED
|
@@ -336,7 +336,7 @@ module T::Configuration
|
|
|
336
336
|
# T::Configuration.scalar_types = ["NilClass", "TrueClass", "FalseClass", ...]
|
|
337
337
|
def self.scalar_types=(values)
|
|
338
338
|
if values.nil?
|
|
339
|
-
@
|
|
339
|
+
@scalar_types = values
|
|
340
340
|
else
|
|
341
341
|
bad_values = values.select {|v| v.class != String}
|
|
342
342
|
unless bad_values.empty?
|
data/lib/types/enum.rb
CHANGED
|
@@ -97,17 +97,8 @@ class T::Enum
|
|
|
97
97
|
@mapping.include?(serialized_val)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
## T::Props::CustomType
|
|
102
|
-
|
|
103
|
-
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
104
|
-
sig {params(value: T.untyped).returns(T::Boolean).checked(:never)}
|
|
105
|
-
def self.instance?(value)
|
|
106
|
-
value.is_a?(self)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
100
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
110
|
-
sig {params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
|
|
101
|
+
sig {override.params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
|
|
111
102
|
def self.serialize(instance)
|
|
112
103
|
# This is needed otherwise if a Chalk::ODM::Document with a property of the shape
|
|
113
104
|
# T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
|
|
@@ -124,7 +115,7 @@ class T::Enum
|
|
|
124
115
|
end
|
|
125
116
|
|
|
126
117
|
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
|
127
|
-
sig {params(mongo_value: SerializedVal).returns(T.attached_class).checked(:never)}
|
|
118
|
+
sig {override.params(mongo_value: SerializedVal).returns(T.attached_class).checked(:never)}
|
|
128
119
|
def self.deserialize(mongo_value)
|
|
129
120
|
if self == T::Enum
|
|
130
121
|
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module T::NonForcingConstants
|
|
5
|
+
# NOTE: This method is documented on the RBI in Sorbet's payload, so that it
|
|
6
|
+
# shows up in the hover/completion documentation via LSP.
|
|
7
|
+
T::Sig::WithoutRuntime.sig {params(val: BasicObject, klass: String).returns(T::Boolean)}
|
|
8
|
+
def self.non_forcing_is_a?(val, klass)
|
|
9
|
+
method_name = "T::NonForcingConstants.non_forcing_is_a?"
|
|
10
|
+
if klass.empty?
|
|
11
|
+
raise ArgumentError.new("The string given to `#{method_name}` must not be empty")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
current_klass = T.let(nil, T.nilable(Module))
|
|
15
|
+
current_prefix = T.let(nil, T.nilable(String))
|
|
16
|
+
|
|
17
|
+
parts = klass.split('::')
|
|
18
|
+
parts.each do |part|
|
|
19
|
+
if current_klass.nil?
|
|
20
|
+
# First iteration
|
|
21
|
+
if part != ""
|
|
22
|
+
raise ArgumentError.new("The string given to `#{method_name}` must be an absolute constant reference that starts with `::`")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
current_klass = Object
|
|
26
|
+
current_prefix = ''
|
|
27
|
+
else
|
|
28
|
+
if current_klass.autoload?(part)
|
|
29
|
+
# There's an autoload registered for that constant, which means it's not
|
|
30
|
+
# yet loaded. `value` can't be an instance of something not yet loaded.
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Sorbet guarantees that the string is an absolutely resolved name.
|
|
35
|
+
search_inheritance_chain = false
|
|
36
|
+
if !current_klass.const_defined?(part, search_inheritance_chain)
|
|
37
|
+
return false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
current_klass = current_klass.const_get(part)
|
|
41
|
+
current_prefix = "#{current_prefix}::#{part}"
|
|
42
|
+
|
|
43
|
+
if !Module.===(current_klass)
|
|
44
|
+
raise ArgumentError.new("#{current_prefix} is not a class or module")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return current_klass.===(val)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -98,6 +98,9 @@ module T::Private::ClassUtils
|
|
|
98
98
|
T::Configuration.without_ruby_warnings do
|
|
99
99
|
T::Private::DeclState.current.without_on_method_added do
|
|
100
100
|
mod.send(:define_method, name, &blk) # rubocop:disable PrisonGuard/UsePublicSend
|
|
101
|
+
if blk.arity < 0 && mod.respond_to?(:ruby2_keywords, true)
|
|
102
|
+
mod.send(:ruby2_keywords, name)
|
|
103
|
+
end
|
|
101
104
|
end
|
|
102
105
|
end
|
|
103
106
|
mod.send(original_visibility, name) # rubocop:disable PrisonGuard/UsePublicSend
|
|
@@ -185,6 +185,9 @@ module T::Private::Methods::CallValidation
|
|
|
185
185
|
mod.send(:define_method, method_sig.method_name) do |*args, &blk|
|
|
186
186
|
CallValidation.validate_call(self, original_method, method_sig, args, blk)
|
|
187
187
|
end
|
|
188
|
+
if mod.respond_to?(:ruby2_keywords, true)
|
|
189
|
+
mod.send(:ruby2_keywords, method_sig.method_name)
|
|
190
|
+
end
|
|
188
191
|
end
|
|
189
192
|
|
|
190
193
|
def self.create_validator_method_fast(mod, original_method, method_sig)
|
data/lib/types/props/_props.rb
CHANGED
|
@@ -39,7 +39,7 @@ module T::Props
|
|
|
39
39
|
# a document class.
|
|
40
40
|
#
|
|
41
41
|
# @param name [Symbol] The name of this property
|
|
42
|
-
# @param cls [Class,T::
|
|
42
|
+
# @param cls [Class,T::Types::Base] The type of this
|
|
43
43
|
# property. If the type is itself a {Document} subclass, this
|
|
44
44
|
# property will be recursively serialized/deserialized.
|
|
45
45
|
# @param rules [Hash] Options to control this property's behavior.
|
|
@@ -90,10 +90,6 @@ module T::Props
|
|
|
90
90
|
# none is provided.
|
|
91
91
|
# @option rules [T::Boolean] :immutable If true, this prop cannot be
|
|
92
92
|
# modified after an instance is created or loaded from a hash.
|
|
93
|
-
# @option rules [Class,T::Props::CustomType] :array If set, specifies the
|
|
94
|
-
# type of individual members of a prop with `type` `Array`. This
|
|
95
|
-
# allows for typechecking of arrays, and also for recursive
|
|
96
|
-
# serialization of arrays of sub-{Document}s.
|
|
97
93
|
# @option rules [T::Boolean] :override It is an error to redeclare a
|
|
98
94
|
# `prop` that has already been declared (including on a
|
|
99
95
|
# superclass), unless `:override` is set to `true`.
|
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# typed:
|
|
2
|
+
# typed: strict
|
|
3
3
|
|
|
4
4
|
module T::Props
|
|
5
5
|
module CustomType
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend T::Helpers
|
|
8
|
+
|
|
9
|
+
abstract!
|
|
10
|
+
|
|
6
11
|
include Kernel # for `is_a?`
|
|
7
12
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Alias for backwards compatibility
|
|
14
|
+
sig(:final) do
|
|
15
|
+
params(
|
|
16
|
+
value: BasicObject,
|
|
17
|
+
)
|
|
18
|
+
.returns(T::Boolean)
|
|
19
|
+
.checked(:never)
|
|
20
|
+
end
|
|
21
|
+
def instance?(value)
|
|
22
|
+
self.===(value)
|
|
15
23
|
end
|
|
16
24
|
|
|
17
|
-
# Alias for
|
|
25
|
+
# Alias for backwards compatibility
|
|
26
|
+
sig(:final) do
|
|
27
|
+
params(
|
|
28
|
+
value: BasicObject,
|
|
29
|
+
)
|
|
30
|
+
.returns(T::Boolean)
|
|
31
|
+
.checked(:never)
|
|
32
|
+
end
|
|
18
33
|
def valid?(value)
|
|
19
34
|
instance?(value)
|
|
20
35
|
end
|
|
@@ -24,30 +39,30 @@ module T::Props
|
|
|
24
39
|
#
|
|
25
40
|
# @param [Object] _instance
|
|
26
41
|
# @return An instance of one of T::Configuration.scalar_types
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
42
|
+
sig {abstract.params(instance: T.untyped).returns(T.untyped).checked(:never)}
|
|
43
|
+
def serialize(instance); end
|
|
30
44
|
|
|
31
45
|
# Given the serialized form of your type, this returns an instance
|
|
32
46
|
# of that custom type representing that value.
|
|
33
47
|
#
|
|
34
|
-
# @param
|
|
48
|
+
# @param scalar One of T::Configuration.scalar_types
|
|
35
49
|
# @return Object
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
50
|
+
sig {abstract.params(scalar: T.untyped).returns(T.untyped).checked(:never)}
|
|
51
|
+
def deserialize(scalar); end
|
|
39
52
|
|
|
53
|
+
sig {override.params(_base: Module).void}
|
|
40
54
|
def self.included(_base)
|
|
41
55
|
super
|
|
42
56
|
|
|
43
57
|
raise 'Please use "extend", not "include" to attach this module'
|
|
44
58
|
end
|
|
45
59
|
|
|
60
|
+
sig(:final) {params(val: Object).returns(T::Boolean).checked(:never)}
|
|
46
61
|
def self.scalar_type?(val)
|
|
47
62
|
# We don't need to check for val's included modules in
|
|
48
63
|
# T::Configuration.scalar_types, because T::Configuration.scalar_types
|
|
49
64
|
# are all classes.
|
|
50
|
-
klass = val.class
|
|
65
|
+
klass = T.let(val.class, T.nilable(Class))
|
|
51
66
|
until klass.nil?
|
|
52
67
|
return true if T::Configuration.scalar_types.include?(klass.to_s)
|
|
53
68
|
klass = klass.superclass
|
|
@@ -59,16 +74,8 @@ module T::Props
|
|
|
59
74
|
# implement set-like fields that store a unique-array, but forbid
|
|
60
75
|
# hashes; Custom hash types should be implemented via an emebdded
|
|
61
76
|
# T::Struct (or a subclass like Chalk::ODM::Document) or via T.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# Special case we allow for backwards compatibility with props formerly
|
|
65
|
-
# typed as "Object" or "Hash", which contain arbitrarily-nested BSON
|
|
66
|
-
# data (e.g. parsed API request bodies). In general, we aren't pushing
|
|
67
|
-
# to convert these to Chalk::ODM::BsonTypes - we'd rather delurk them -
|
|
68
|
-
# but this lets us convert events with these types to Proto.
|
|
69
|
-
return true
|
|
70
|
-
end
|
|
71
|
-
|
|
77
|
+
sig(:final) {params(val: Object).returns(T::Boolean).checked(:never)}
|
|
78
|
+
def self.valid_serialization?(val)
|
|
72
79
|
case val
|
|
73
80
|
when Array
|
|
74
81
|
val.each do |v|
|
|
@@ -81,14 +88,19 @@ module T::Props
|
|
|
81
88
|
end
|
|
82
89
|
end
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
sig(:final) do
|
|
92
|
+
params(instance: Object)
|
|
93
|
+
.returns(T.untyped)
|
|
94
|
+
.checked(:never)
|
|
95
|
+
end
|
|
96
|
+
def self.checked_serialize(instance)
|
|
97
|
+
val = T.cast(instance.class, T::Props::CustomType).serialize(instance)
|
|
98
|
+
unless valid_serialization?(val)
|
|
99
|
+
msg = "#{instance.class} did not serialize to a valid scalar type. It became a: #{val.class}"
|
|
88
100
|
if val.is_a?(Hash)
|
|
89
101
|
msg += "\nIf you want to store a structured Hash, consider using a T::Struct as your type."
|
|
90
102
|
end
|
|
91
|
-
raise
|
|
103
|
+
raise TypeError.new(msg)
|
|
92
104
|
end
|
|
93
105
|
val
|
|
94
106
|
end
|
|
@@ -12,7 +12,7 @@ class T::Props::Decorator
|
|
|
12
12
|
|
|
13
13
|
Rules = T.type_alias {T::Hash[Symbol, T.untyped]}
|
|
14
14
|
DecoratedInstance = T.type_alias {Object} # Would be T::Props, but that produces circular reference errors in some circumstances
|
|
15
|
-
PropType = T.type_alias {T
|
|
15
|
+
PropType = T.type_alias {T::Types::Base}
|
|
16
16
|
PropTypeOrClass = T.type_alias {T.any(PropType, Module)}
|
|
17
17
|
|
|
18
18
|
class NoRulesError < StandardError; end
|
|
@@ -66,7 +66,6 @@ class T::Props::Decorator
|
|
|
66
66
|
without_accessors
|
|
67
67
|
clobber_existing_method!
|
|
68
68
|
extra
|
|
69
|
-
optional
|
|
70
69
|
setter_validate
|
|
71
70
|
_tnilable
|
|
72
71
|
}.map {|k| [k, true]}.to_h.freeze, T::Hash[Symbol, T::Boolean])
|
|
@@ -119,6 +118,21 @@ class T::Props::Decorator
|
|
|
119
118
|
end
|
|
120
119
|
alias_method :set, :prop_set
|
|
121
120
|
|
|
121
|
+
# Only Models have any custom get logic but we need to call this on
|
|
122
|
+
# non-Models since we don't know at code gen time what we have.
|
|
123
|
+
sig do
|
|
124
|
+
params(
|
|
125
|
+
instance: DecoratedInstance,
|
|
126
|
+
prop: Symbol,
|
|
127
|
+
value: T.untyped
|
|
128
|
+
)
|
|
129
|
+
.returns(T.untyped)
|
|
130
|
+
.checked(:never)
|
|
131
|
+
end
|
|
132
|
+
def prop_get_logic(instance, prop, value)
|
|
133
|
+
value
|
|
134
|
+
end
|
|
135
|
+
|
|
122
136
|
# For performance, don't use named params here.
|
|
123
137
|
# Passing in rules here is purely a performance optimization.
|
|
124
138
|
#
|
|
@@ -174,7 +188,7 @@ class T::Props::Decorator
|
|
|
174
188
|
.returns(T.untyped)
|
|
175
189
|
.checked(:never)
|
|
176
190
|
end
|
|
177
|
-
def foreign_prop_get(instance, prop, foreign_class, rules=
|
|
191
|
+
def foreign_prop_get(instance, prop, foreign_class, rules=prop_rules(prop), opts={})
|
|
178
192
|
return if !(value = prop_get(instance, prop, rules))
|
|
179
193
|
T.unsafe(foreign_class).load(value, {}, opts)
|
|
180
194
|
end
|
|
@@ -205,12 +219,6 @@ class T::Props::Decorator
|
|
|
205
219
|
raise ArgumentError.new("At least one invalid prop arg supplied in #{self}: #{rules.keys.inspect}")
|
|
206
220
|
end
|
|
207
221
|
|
|
208
|
-
if (array = rules[:array])
|
|
209
|
-
unless array.is_a?(Module)
|
|
210
|
-
raise ArgumentError.new("Bad class as subtype in prop #{@class.name}.#{name}: #{array.inspect}")
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
222
|
if !(rules[:clobber_existing_method!]) && !(rules[:without_accessors])
|
|
215
223
|
if BANNED_METHOD_NAMES.include?(name.to_sym)
|
|
216
224
|
raise ArgumentError.new(
|
|
@@ -280,47 +288,6 @@ class T::Props::Decorator
|
|
|
280
288
|
end
|
|
281
289
|
def prop_defined(name, cls, rules={})
|
|
282
290
|
cls = T::Utils.resolve_alias(cls)
|
|
283
|
-
if rules[:optional] == true
|
|
284
|
-
T::Configuration.hard_assert_handler(
|
|
285
|
-
'Use of `optional: true` is deprecated, please use `T.nilable(...)` instead.',
|
|
286
|
-
storytime: {
|
|
287
|
-
name: name,
|
|
288
|
-
cls_or_args: cls.to_s,
|
|
289
|
-
args: rules,
|
|
290
|
-
klass: decorated_class.name,
|
|
291
|
-
},
|
|
292
|
-
)
|
|
293
|
-
elsif rules[:optional] == false
|
|
294
|
-
T::Configuration.hard_assert_handler(
|
|
295
|
-
'Use of `optional: :false` is deprecated as it\'s the default value.',
|
|
296
|
-
storytime: {
|
|
297
|
-
name: name,
|
|
298
|
-
cls_or_args: cls.to_s,
|
|
299
|
-
args: rules,
|
|
300
|
-
klass: decorated_class.name,
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
elsif rules[:optional] == :on_load
|
|
304
|
-
T::Configuration.hard_assert_handler(
|
|
305
|
-
'Use of `optional: :on_load` is deprecated. You probably want `T.nilable(...)` with :raise_on_nil_write instead.',
|
|
306
|
-
storytime: {
|
|
307
|
-
name: name,
|
|
308
|
-
cls_or_args: cls.to_s,
|
|
309
|
-
args: rules,
|
|
310
|
-
klass: decorated_class.name,
|
|
311
|
-
},
|
|
312
|
-
)
|
|
313
|
-
elsif rules[:optional] == :existing
|
|
314
|
-
T::Configuration.hard_assert_handler(
|
|
315
|
-
'Use of `optional: :existing` is not allowed: you should use use T.nilable (http://go/optional)',
|
|
316
|
-
storytime: {
|
|
317
|
-
name: name,
|
|
318
|
-
cls_or_args: cls.to_s,
|
|
319
|
-
args: rules,
|
|
320
|
-
klass: decorated_class.name,
|
|
321
|
-
},
|
|
322
|
-
)
|
|
323
|
-
end
|
|
324
291
|
|
|
325
292
|
if T::Utils::Nilable.is_union_with_nilclass(cls)
|
|
326
293
|
# :_tnilable is introduced internally for performance purpose so that clients do not need to call
|
|
@@ -335,21 +302,13 @@ class T::Props::Decorator
|
|
|
335
302
|
if !cls.is_a?(Module)
|
|
336
303
|
cls = convert_type_to_class(cls)
|
|
337
304
|
end
|
|
338
|
-
type_object = type
|
|
339
|
-
if !(type_object.singleton_class < T::Props::CustomType)
|
|
340
|
-
type_object = smart_coerce(type_object, array: rules[:array], enum: rules[:enum])
|
|
341
|
-
end
|
|
305
|
+
type_object = smart_coerce(type, enum: rules[:enum])
|
|
342
306
|
|
|
343
307
|
prop_validate_definition!(name, cls, rules, type_object)
|
|
344
308
|
|
|
345
309
|
# Retrive the possible underlying object with T.nilable.
|
|
346
|
-
underlying_type_object = T::Utils::Nilable.get_underlying_type_object(type_object)
|
|
347
310
|
type = T::Utils::Nilable.get_underlying_type(type)
|
|
348
311
|
|
|
349
|
-
array_subdoc_type = array_subdoc_type(underlying_type_object)
|
|
350
|
-
hash_value_subdoc_type = hash_value_subdoc_type(underlying_type_object)
|
|
351
|
-
hash_key_custom_type = hash_key_custom_type(underlying_type_object)
|
|
352
|
-
|
|
353
312
|
sensitivity_and_pii = {sensitivity: rules[:sensitivity]}
|
|
354
313
|
if defined?(Opus) && defined?(Opus::Sensitivity) && defined?(Opus::Sensitivity::Utils)
|
|
355
314
|
sensitivity_and_pii = Opus::Sensitivity::Utils.normalize_sensitivity_and_pii_annotation(sensitivity_and_pii)
|
|
@@ -367,27 +326,11 @@ class T::Props::Decorator
|
|
|
367
326
|
end
|
|
368
327
|
end
|
|
369
328
|
|
|
370
|
-
needs_clone =
|
|
371
|
-
if cls <= Array || cls <= Hash || cls <= Set
|
|
372
|
-
shallow_clone_ok(underlying_type_object) ? :shallow : true
|
|
373
|
-
else
|
|
374
|
-
false
|
|
375
|
-
end
|
|
376
|
-
|
|
377
329
|
rules = rules.merge(
|
|
378
330
|
# TODO: The type of this element is confusing. We should refactor so that
|
|
379
331
|
# it can be always `type_object` (a PropType) or always `cls` (a Module)
|
|
380
332
|
type: type,
|
|
381
|
-
# These are precomputed for performance
|
|
382
|
-
# TODO: A lot of these are only needed by T::Props::Serializable or T::Struct
|
|
383
|
-
# and can/should be moved accordingly.
|
|
384
|
-
type_is_custom_type: cls.singleton_class < T::Props::CustomType,
|
|
385
|
-
type_is_serializable: cls < T::Props::Serializable,
|
|
386
|
-
type_is_array_of_serializable: !array_subdoc_type.nil?,
|
|
387
|
-
type_is_hash_of_serializable_values: !hash_value_subdoc_type.nil?,
|
|
388
|
-
type_is_hash_of_custom_type_keys: !hash_key_custom_type.nil?,
|
|
389
333
|
type_object: type_object,
|
|
390
|
-
type_needs_clone: needs_clone,
|
|
391
334
|
accessor_key: "@#{name}".to_sym,
|
|
392
335
|
sensitivity: sensitivity_and_pii[:sensitivity],
|
|
393
336
|
pii: sensitivity_and_pii[:pii],
|
|
@@ -397,26 +340,13 @@ class T::Props::Decorator
|
|
|
397
340
|
|
|
398
341
|
validate_not_missing_sensitivity(name, rules)
|
|
399
342
|
|
|
400
|
-
# for backcompat
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if rules[:type_is_serializable]
|
|
408
|
-
rules[:serializable_subtype] = cls
|
|
409
|
-
elsif array_subdoc_type
|
|
410
|
-
rules[:serializable_subtype] = array_subdoc_type
|
|
411
|
-
elsif hash_value_subdoc_type && hash_key_custom_type
|
|
412
|
-
rules[:serializable_subtype] = {
|
|
413
|
-
keys: hash_key_custom_type,
|
|
414
|
-
values: hash_value_subdoc_type,
|
|
415
|
-
}
|
|
416
|
-
elsif hash_value_subdoc_type
|
|
417
|
-
rules[:serializable_subtype] = hash_value_subdoc_type
|
|
418
|
-
elsif hash_key_custom_type
|
|
419
|
-
rules[:serializable_subtype] = hash_key_custom_type
|
|
343
|
+
# for backcompat (the `:array` key is deprecated but because the name is
|
|
344
|
+
# so generic it's really hard to be sure it's not being relied on anymore)
|
|
345
|
+
if type.is_a?(T::Types::TypedArray)
|
|
346
|
+
inner = T::Utils::Nilable.get_underlying_type(type.type)
|
|
347
|
+
if inner.is_a?(Module)
|
|
348
|
+
rules[:array] = inner
|
|
349
|
+
end
|
|
420
350
|
end
|
|
421
351
|
|
|
422
352
|
rules[:setter_proc] = T::Props::Private::SetterFactory.build_setter_proc(@class, name, rules).freeze
|
|
@@ -462,110 +392,22 @@ class T::Props::Decorator
|
|
|
462
392
|
end
|
|
463
393
|
end
|
|
464
394
|
|
|
465
|
-
# returns the subdoc of the array type, or nil if it's not a Document type
|
|
466
|
-
#
|
|
467
|
-
# checked(:never) - Typechecks internally
|
|
468
|
-
sig do
|
|
469
|
-
params(type: PropType)
|
|
470
|
-
.returns(T.nilable(Module))
|
|
471
|
-
.checked(:never)
|
|
472
|
-
end
|
|
473
|
-
private def array_subdoc_type(type)
|
|
474
|
-
if type.is_a?(T::Types::TypedArray)
|
|
475
|
-
el_type = T::Utils.unwrap_nilable(type.type) || type.type
|
|
476
|
-
|
|
477
|
-
if el_type.is_a?(T::Types::Simple) &&
|
|
478
|
-
(el_type.raw_type < T::Props::Serializable || el_type.raw_type.is_a?(T::Props::CustomType))
|
|
479
|
-
return el_type.raw_type
|
|
480
|
-
end
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
nil
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
# returns the subdoc of the hash value type, or nil if it's not a Document type
|
|
487
|
-
#
|
|
488
|
-
# checked(:never) - Typechecks internally
|
|
489
395
|
sig do
|
|
490
|
-
params(type:
|
|
491
|
-
.returns(T.nilable(Module))
|
|
492
|
-
.checked(:never)
|
|
493
|
-
end
|
|
494
|
-
private def hash_value_subdoc_type(type)
|
|
495
|
-
if type.is_a?(T::Types::TypedHash)
|
|
496
|
-
values_type = T::Utils.unwrap_nilable(type.values) || type.values
|
|
497
|
-
|
|
498
|
-
if values_type.is_a?(T::Types::Simple) &&
|
|
499
|
-
(values_type.raw_type < T::Props::Serializable || values_type.raw_type.is_a?(T::Props::CustomType))
|
|
500
|
-
return values_type.raw_type
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
nil
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
# returns the type of the hash key, or nil. Any CustomType could be a key, but we only expect T::Enum right now.
|
|
508
|
-
#
|
|
509
|
-
# checked(:never) - Typechecks internally
|
|
510
|
-
sig do
|
|
511
|
-
params(type: PropType)
|
|
512
|
-
.returns(T.nilable(Module))
|
|
513
|
-
.checked(:never)
|
|
514
|
-
end
|
|
515
|
-
private def hash_key_custom_type(type)
|
|
516
|
-
if type.is_a?(T::Types::TypedHash)
|
|
517
|
-
keys_type = T::Utils.unwrap_nilable(type.keys) || type.keys
|
|
518
|
-
|
|
519
|
-
if keys_type.is_a?(T::Types::Simple) && keys_type.raw_type.is_a?(T::Props::CustomType)
|
|
520
|
-
return keys_type.raw_type
|
|
521
|
-
end
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
nil
|
|
525
|
-
end
|
|
526
|
-
|
|
527
|
-
# From T::Props::Utils.deep_clone_object, plus String
|
|
528
|
-
TYPES_NOT_NEEDING_CLONE = T.let([TrueClass, FalseClass, NilClass, Symbol, String, Numeric], T::Array[Module])
|
|
529
|
-
|
|
530
|
-
# checked(:never) - Typechecks internally
|
|
531
|
-
sig {params(type: PropType).returns(T.nilable(T::Boolean)).checked(:never)}
|
|
532
|
-
private def shallow_clone_ok(type)
|
|
533
|
-
inner_type =
|
|
534
|
-
if type.is_a?(T::Types::TypedArray)
|
|
535
|
-
type.type
|
|
536
|
-
elsif type.is_a?(T::Types::TypedSet)
|
|
537
|
-
type.type
|
|
538
|
-
elsif type.is_a?(T::Types::TypedHash)
|
|
539
|
-
type.values
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
inner_type.is_a?(T::Types::Simple) && TYPES_NOT_NEEDING_CLONE.any? do |cls|
|
|
543
|
-
inner_type.raw_type <= cls
|
|
544
|
-
end
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
sig do
|
|
548
|
-
params(type: PropTypeOrClass, array: T.untyped, enum: T.untyped)
|
|
396
|
+
params(type: PropTypeOrClass, enum: T.untyped)
|
|
549
397
|
.returns(T::Types::Base)
|
|
550
398
|
end
|
|
551
|
-
private def smart_coerce(type,
|
|
399
|
+
private def smart_coerce(type, enum:)
|
|
552
400
|
# Backwards compatibility for pre-T::Types style
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
T
|
|
560
|
-
end
|
|
561
|
-
elsif !enum.nil?
|
|
562
|
-
if T::Utils.unwrap_nilable(type)
|
|
563
|
-
T.nilable(T.enum(enum))
|
|
401
|
+
type = T::Utils.coerce(type)
|
|
402
|
+
if enum.nil?
|
|
403
|
+
type
|
|
404
|
+
else
|
|
405
|
+
nonnil_type = T::Utils.unwrap_nilable(type)
|
|
406
|
+
if nonnil_type
|
|
407
|
+
T.nilable(T.all(nonnil_type, T.enum(enum)))
|
|
564
408
|
else
|
|
565
|
-
T.enum(enum)
|
|
409
|
+
T.all(type, T.enum(enum))
|
|
566
410
|
end
|
|
567
|
-
else
|
|
568
|
-
T::Utils.coerce(type)
|
|
569
411
|
end
|
|
570
412
|
end
|
|
571
413
|
|
|
@@ -602,7 +444,7 @@ class T::Props::Decorator
|
|
|
602
444
|
sig do
|
|
603
445
|
params(
|
|
604
446
|
prop_name: Symbol,
|
|
605
|
-
redaction:
|
|
447
|
+
redaction: T.untyped,
|
|
606
448
|
)
|
|
607
449
|
.void
|
|
608
450
|
end
|
|
@@ -724,14 +566,6 @@ class T::Props::Decorator
|
|
|
724
566
|
end
|
|
725
567
|
loaded_foreign
|
|
726
568
|
end
|
|
727
|
-
|
|
728
|
-
@class.send(:define_method, "#{prop_name}_record") do |allow_direct_mutation: nil|
|
|
729
|
-
T::Configuration.soft_assert_handler(
|
|
730
|
-
"Using deprecated 'model.#{prop_name}_record' foreign key syntax. You should replace this with 'model.#{prop_name}_'",
|
|
731
|
-
notify: 'vasi'
|
|
732
|
-
)
|
|
733
|
-
send(fk_method, allow_direct_mutation: allow_direct_mutation)
|
|
734
|
-
end
|
|
735
569
|
end
|
|
736
570
|
|
|
737
571
|
# checked(:never) - Rules hash is expensive to check
|
|
@@ -148,13 +148,30 @@ module T::Props
|
|
|
148
148
|
assert_equal(s(:op_asgn, s(:lvasgn, :found), :-, s(:int, 1)), found_else_body)
|
|
149
149
|
|
|
150
150
|
validate_deserialize_handle_nil(handle_nil)
|
|
151
|
-
|
|
151
|
+
|
|
152
|
+
if else_body.type == :kwbegin
|
|
153
|
+
rescue_expression, = else_body.children
|
|
154
|
+
assert_equal(:rescue, rescue_expression.type)
|
|
155
|
+
|
|
156
|
+
try, rescue_body = rescue_expression.children
|
|
157
|
+
validate_lack_of_side_effects(try, whitelisted_methods_for_deserialize)
|
|
158
|
+
|
|
159
|
+
assert_equal(:resbody, rescue_body.type)
|
|
160
|
+
exceptions, assignment, handler = rescue_body.children
|
|
161
|
+
assert_equal(:array, exceptions.type)
|
|
162
|
+
exceptions.children.each {|c| assert_equal(:const, c.type)}
|
|
163
|
+
assert_equal(:lvasgn, assignment.type)
|
|
164
|
+
assert_equal([:e], assignment.children)
|
|
165
|
+
validate_lack_of_side_effects(handler, whitelisted_methods_for_deserialize)
|
|
166
|
+
else
|
|
167
|
+
validate_lack_of_side_effects(else_body, whitelisted_methods_for_deserialize)
|
|
168
|
+
end
|
|
152
169
|
end
|
|
153
170
|
|
|
154
171
|
private_class_method def self.validate_deserialize_handle_nil(node)
|
|
155
172
|
case node.type
|
|
156
|
-
when :hash, :array, :str, :sym, :int, :float, :true, :false, :nil
|
|
157
|
-
# Primitives are safe
|
|
173
|
+
when :hash, :array, :str, :sym, :int, :float, :true, :false, :nil, :const
|
|
174
|
+
# Primitives and constants are safe
|
|
158
175
|
when :send
|
|
159
176
|
receiver, method, arg = node.children
|
|
160
177
|
if receiver.nil?
|
|
@@ -243,8 +260,8 @@ module T::Props
|
|
|
243
260
|
# Method calls generated by SerdeTransform
|
|
244
261
|
private_class_method def self.whitelisted_methods_for_deserialize
|
|
245
262
|
@whitelisted_methods_for_deserialize ||= {
|
|
246
|
-
:lvar => %i{dup map transform_values transform_keys each_with_object nil? []=},
|
|
247
|
-
:const => %i{deserialize from_hash deep_clone_object},
|
|
263
|
+
:lvar => %i{dup map transform_values transform_keys each_with_object nil? []= to_f},
|
|
264
|
+
:const => %i{deserialize from_hash deep_clone_object soft_assert_handler},
|
|
248
265
|
}
|
|
249
266
|
end
|
|
250
267
|
end
|
|
@@ -66,10 +66,13 @@ module T::Props
|
|
|
66
66
|
lazily_defined_methods[name] = blk
|
|
67
67
|
|
|
68
68
|
cls = decorated_class
|
|
69
|
-
cls.define_method
|
|
69
|
+
cls.send(:define_method, name) do |*args|
|
|
70
70
|
self.class.decorator.send(:eval_lazily_defined_method!, name)
|
|
71
71
|
send(name, *args)
|
|
72
72
|
end
|
|
73
|
+
if cls.respond_to?(:ruby2_keywords, true)
|
|
74
|
+
cls.send(:ruby2_keywords, name)
|
|
75
|
+
end
|
|
73
76
|
cls.send(:private, name)
|
|
74
77
|
end
|
|
75
78
|
|
data/lib/types/props/optional.rb
CHANGED
|
@@ -14,19 +14,9 @@ end
|
|
|
14
14
|
module T::Props::Optional::DecoratorMethods
|
|
15
15
|
extend T::Sig
|
|
16
16
|
|
|
17
|
-
# TODO: clean this up. This set of options is confusing, and some of them are not universally
|
|
18
|
-
# applicable (e.g., :on_load only applies when using T::Serializable).
|
|
19
|
-
VALID_OPTIONAL_RULES = Set[
|
|
20
|
-
:existing, # deprecated
|
|
21
|
-
:on_load,
|
|
22
|
-
false,
|
|
23
|
-
true,
|
|
24
|
-
].freeze
|
|
25
|
-
|
|
26
17
|
VALID_RULE_KEYS = {
|
|
27
18
|
default: true,
|
|
28
19
|
factory: true,
|
|
29
|
-
optional: true,
|
|
30
20
|
}.freeze
|
|
31
21
|
private_constant :VALID_RULE_KEYS
|
|
32
22
|
|
|
@@ -74,12 +64,6 @@ module T::Props::Optional::DecoratorMethods
|
|
|
74
64
|
def prop_validate_definition!(name, cls, rules, type)
|
|
75
65
|
result = super
|
|
76
66
|
|
|
77
|
-
if (rules_optional = rules[:optional])
|
|
78
|
-
if !VALID_OPTIONAL_RULES.include?(rules_optional)
|
|
79
|
-
raise ArgumentError.new(":optional must be one of #{VALID_OPTIONAL_RULES.inspect}")
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
67
|
if rules.key?(:default) && rules.key?(:factory)
|
|
84
68
|
raise ArgumentError.new("Setting both :default and :factory is invalid. See: go/chalk-docs")
|
|
85
69
|
end
|
|
@@ -43,11 +43,36 @@ module T::Props
|
|
|
43
43
|
ivar_name = rules.fetch(:accessor_key).to_s
|
|
44
44
|
raise unless ivar_name.start_with?('@') && T::Props::Decorator::SAFE_NAME.match?(ivar_name[1..-1])
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
transformation = SerdeTransform.generate(
|
|
47
47
|
T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object)),
|
|
48
48
|
SerdeTransform::Mode::DESERIALIZE,
|
|
49
49
|
'val'
|
|
50
|
-
)
|
|
50
|
+
)
|
|
51
|
+
if transformation
|
|
52
|
+
# Rescuing exactly NoMethodError is intended as a temporary hack
|
|
53
|
+
# to preserve the semantics from before codegen. More generally
|
|
54
|
+
# we are inconsistent about typechecking on deser and need to decide
|
|
55
|
+
# our strategy here.
|
|
56
|
+
transformed_val = <<~RUBY
|
|
57
|
+
begin
|
|
58
|
+
#{transformation}
|
|
59
|
+
rescue NoMethodError => e
|
|
60
|
+
T::Configuration.soft_assert_handler(
|
|
61
|
+
'Deserialization error (probably unexpected stored type)',
|
|
62
|
+
storytime: {
|
|
63
|
+
klass: self.class,
|
|
64
|
+
prop: #{prop.inspect},
|
|
65
|
+
value: val,
|
|
66
|
+
error: e.message,
|
|
67
|
+
notify: 'djudd'
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
val
|
|
71
|
+
end
|
|
72
|
+
RUBY
|
|
73
|
+
else
|
|
74
|
+
transformed_val = 'val'
|
|
75
|
+
end
|
|
51
76
|
|
|
52
77
|
nil_handler = generate_nil_handler(
|
|
53
78
|
prop: prop,
|
|
@@ -19,14 +19,14 @@ module T::Props
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
NO_TRANSFORM_TYPES = T.let(
|
|
22
|
-
[TrueClass, FalseClass, NilClass, Symbol, String
|
|
22
|
+
[TrueClass, FalseClass, NilClass, Symbol, String].freeze,
|
|
23
23
|
T::Array[Module],
|
|
24
24
|
)
|
|
25
25
|
private_constant :NO_TRANSFORM_TYPES
|
|
26
26
|
|
|
27
27
|
sig do
|
|
28
28
|
params(
|
|
29
|
-
type: T
|
|
29
|
+
type: T::Types::Base,
|
|
30
30
|
mode: ModeType,
|
|
31
31
|
varname: String,
|
|
32
32
|
)
|
|
@@ -65,10 +65,28 @@ module T::Props
|
|
|
65
65
|
raw = type.raw_type
|
|
66
66
|
if NO_TRANSFORM_TYPES.any? {|cls| raw <= cls}
|
|
67
67
|
nil
|
|
68
|
+
elsif raw <= Float
|
|
69
|
+
case mode
|
|
70
|
+
when Deserialize then "#{varname}.to_f"
|
|
71
|
+
when Serialize then nil
|
|
72
|
+
else T.absurd(mode)
|
|
73
|
+
end
|
|
74
|
+
elsif raw <= Numeric
|
|
75
|
+
nil
|
|
68
76
|
elsif raw < T::Props::Serializable
|
|
69
77
|
handle_serializable_subtype(varname, raw, mode)
|
|
70
78
|
elsif raw.singleton_class < T::Props::CustomType
|
|
71
79
|
handle_custom_type(varname, T.unsafe(raw), mode)
|
|
80
|
+
elsif T::Configuration.scalar_types.include?(raw.name)
|
|
81
|
+
# It's a bit of a hack that this is separate from NO_TRANSFORM_TYPES
|
|
82
|
+
# and doesn't check inheritance (like `T::Props::CustomType.scalar_type?`
|
|
83
|
+
# does), but it covers the main use case (pay-server's custom `Boolean`
|
|
84
|
+
# module) without either requiring `T::Configuration.scalar_types` to
|
|
85
|
+
# accept modules instead of strings (which produces load-order issues
|
|
86
|
+
# and subtle behavior changes) or eating the performance cost of doing
|
|
87
|
+
# an inheritance check by manually crawling a class hierarchy and doing
|
|
88
|
+
# string comparisons.
|
|
89
|
+
nil
|
|
72
90
|
else
|
|
73
91
|
"T::Props::Utils.deep_clone_object(#{varname})"
|
|
74
92
|
end
|
|
@@ -82,17 +100,56 @@ module T::Props
|
|
|
82
100
|
"#{varname}.nil? ? nil : #{inner}"
|
|
83
101
|
end
|
|
84
102
|
else
|
|
85
|
-
|
|
103
|
+
# Handle, e.g., T::Boolean
|
|
104
|
+
if type.types.all? {|t| generate(t, mode, varname).nil?}
|
|
105
|
+
nil
|
|
106
|
+
else
|
|
107
|
+
# We currently deep_clone_object if the type was T.any(Integer, Float).
|
|
108
|
+
# When we get better support for union types (maybe this specific
|
|
109
|
+
# union type, because it would be a replacement for
|
|
110
|
+
# Chalk::ODM::DeprecatedNumemric), we could opt to special case
|
|
111
|
+
# this union to have no specific serde transform (the only reason
|
|
112
|
+
# why Float has a special case is because round tripping through
|
|
113
|
+
# JSON might normalize Floats to Integers)
|
|
114
|
+
"T::Props::Utils.deep_clone_object(#{varname})"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
when T::Types::Intersection
|
|
118
|
+
dynamic_fallback = "T::Props::Utils.deep_clone_object(#{varname})"
|
|
119
|
+
|
|
120
|
+
# Transformations for any members of the intersection type where we
|
|
121
|
+
# know what we need to do and did not have to fall back to the
|
|
122
|
+
# dynamic deep clone method.
|
|
123
|
+
#
|
|
124
|
+
# NB: This deliberately does include `nil`, which means we know we
|
|
125
|
+
# don't need to do any transforming.
|
|
126
|
+
inner_known = type.types
|
|
127
|
+
.map {|t| generate(t, mode, varname)}
|
|
128
|
+
.reject {|t| t == dynamic_fallback}
|
|
129
|
+
.uniq
|
|
130
|
+
|
|
131
|
+
if inner_known.size != 1
|
|
132
|
+
# If there were no cases where we could tell what we need to do,
|
|
133
|
+
# e.g. if this is `T.all(SomethingWeird, WhoKnows)`, just use the
|
|
134
|
+
# dynamic fallback.
|
|
135
|
+
#
|
|
136
|
+
# If there were multiple cases and they weren't consistent, e.g.
|
|
137
|
+
# if this is `T.all(String, T::Array[Integer])`, the type is probably
|
|
138
|
+
# bogus/uninhabited, but use the dynamic fallback because we still
|
|
139
|
+
# don't have a better option, and this isn't the place to raise that
|
|
140
|
+
# error.
|
|
141
|
+
dynamic_fallback
|
|
142
|
+
else
|
|
143
|
+
# This is probably something like `T.all(String, SomeMarker)` or
|
|
144
|
+
# `T.all(SomeEnum, T.enum(SomeEnum::FOO))` and we should treat it
|
|
145
|
+
# like String or SomeEnum even if we don't know what to do with
|
|
146
|
+
# the rest of the type.
|
|
147
|
+
inner_known.first
|
|
86
148
|
end
|
|
87
149
|
when T::Types::Enum
|
|
88
150
|
generate(T::Utils.lift_enum(type), mode, varname)
|
|
89
151
|
else
|
|
90
|
-
|
|
91
|
-
# Sometimes this comes wrapped in a T::Types::Simple and sometimes not
|
|
92
|
-
handle_custom_type(varname, T.unsafe(type), mode)
|
|
93
|
-
else
|
|
94
|
-
"T::Props::Utils.deep_clone_object(#{varname})"
|
|
95
|
-
end
|
|
152
|
+
"T::Props::Utils.deep_clone_object(#{varname})"
|
|
96
153
|
end
|
|
97
154
|
end
|
|
98
155
|
|
|
@@ -113,8 +170,7 @@ module T::Props
|
|
|
113
170
|
private_class_method def self.handle_custom_type(varname, type, mode)
|
|
114
171
|
case mode
|
|
115
172
|
when Serialize
|
|
116
|
-
|
|
117
|
-
"T::Props::CustomType.checked_serialize(#{type_name}, #{varname})"
|
|
173
|
+
"T::Props::CustomType.checked_serialize(#{varname})"
|
|
118
174
|
when Deserialize
|
|
119
175
|
type_name = T.must(module_name(type))
|
|
120
176
|
"#{type_name}.deserialize(#{varname})"
|
|
@@ -20,17 +20,10 @@ module T::Props
|
|
|
20
20
|
end
|
|
21
21
|
def self.build_setter_proc(klass, prop, rules)
|
|
22
22
|
# Our nil check works differently than a simple T.nilable for various
|
|
23
|
-
# reasons (including the `raise_on_nil_write` setting
|
|
24
|
-
# of defaults & factories,
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
# do a check manually. (Note this hack does not fix custom types as
|
|
28
|
-
# collection elements.)
|
|
29
|
-
non_nil_type = if rules[:type_is_custom_type]
|
|
30
|
-
rules.fetch(:type)
|
|
31
|
-
else
|
|
32
|
-
T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object))
|
|
33
|
-
end
|
|
23
|
+
# reasons (including the `raise_on_nil_write` setting and the existence
|
|
24
|
+
# of defaults & factories), so unwrap any T.nilable and do a check
|
|
25
|
+
# manually.
|
|
26
|
+
non_nil_type = T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object))
|
|
34
27
|
accessor_key = rules.fetch(:accessor_key)
|
|
35
28
|
validate = rules[:setter_validate]
|
|
36
29
|
|
|
@@ -51,7 +44,7 @@ module T::Props
|
|
|
51
44
|
params(
|
|
52
45
|
prop: Symbol,
|
|
53
46
|
accessor_key: Symbol,
|
|
54
|
-
non_nil_type: T
|
|
47
|
+
non_nil_type: T::Types::Base,
|
|
55
48
|
klass: T.all(Module, T::Props::ClassMethods),
|
|
56
49
|
validate: T.nilable(ValidateProc)
|
|
57
50
|
)
|
|
@@ -71,6 +64,7 @@ module T::Props
|
|
|
71
64
|
non_nil_type,
|
|
72
65
|
val,
|
|
73
66
|
)
|
|
67
|
+
instance_variable_set(accessor_key, val)
|
|
74
68
|
end
|
|
75
69
|
end
|
|
76
70
|
end
|
|
@@ -79,7 +73,7 @@ module T::Props
|
|
|
79
73
|
params(
|
|
80
74
|
prop: Symbol,
|
|
81
75
|
accessor_key: Symbol,
|
|
82
|
-
non_nil_type: T
|
|
76
|
+
non_nil_type: T::Types::Base,
|
|
83
77
|
klass: T.all(Module, T::Props::ClassMethods),
|
|
84
78
|
validate: T.nilable(ValidateProc),
|
|
85
79
|
)
|
|
@@ -101,6 +95,7 @@ module T::Props
|
|
|
101
95
|
non_nil_type,
|
|
102
96
|
val,
|
|
103
97
|
)
|
|
98
|
+
instance_variable_set(accessor_key, val)
|
|
104
99
|
end
|
|
105
100
|
end
|
|
106
101
|
end
|
|
@@ -16,7 +16,25 @@ module T::Props::Serializable
|
|
|
16
16
|
# values.
|
|
17
17
|
# @return [Hash] A serialization of this object.
|
|
18
18
|
def serialize(strict=true)
|
|
19
|
-
|
|
19
|
+
begin
|
|
20
|
+
h = __t_props_generated_serialize(strict)
|
|
21
|
+
rescue => e
|
|
22
|
+
msg = self.class.decorator.message_with_generated_source_context(
|
|
23
|
+
e,
|
|
24
|
+
:__t_props_generated_serialize,
|
|
25
|
+
:generate_serialize_source
|
|
26
|
+
)
|
|
27
|
+
if msg
|
|
28
|
+
begin
|
|
29
|
+
raise e.class.new(msg)
|
|
30
|
+
rescue ArgumentError
|
|
31
|
+
raise TypeError.new(msg)
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
20
38
|
h.merge!(@_extra_props) if @_extra_props
|
|
21
39
|
h
|
|
22
40
|
end
|
|
@@ -39,7 +57,25 @@ module T::Props::Serializable
|
|
|
39
57
|
# props on this instance.
|
|
40
58
|
# @return [void]
|
|
41
59
|
def deserialize(hash, strict=false)
|
|
42
|
-
|
|
60
|
+
begin
|
|
61
|
+
hash_keys_matching_props = __t_props_generated_deserialize(hash)
|
|
62
|
+
rescue => e
|
|
63
|
+
msg = self.class.decorator.message_with_generated_source_context(
|
|
64
|
+
e,
|
|
65
|
+
:__t_props_generated_deserialize,
|
|
66
|
+
:generate_deserialize_source
|
|
67
|
+
)
|
|
68
|
+
if msg
|
|
69
|
+
begin
|
|
70
|
+
raise e.class.new(msg)
|
|
71
|
+
rescue ArgumentError
|
|
72
|
+
raise TypeError.new(msg)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
raise
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
43
79
|
if hash.size > hash_keys_matching_props
|
|
44
80
|
serialized_forms = self.class.decorator.prop_by_serialized_forms
|
|
45
81
|
extra = hash.reject {|k, _| serialized_forms.key?(k)}
|
|
@@ -110,7 +146,7 @@ module T::Props::Serializable
|
|
|
110
146
|
prop: prop, class: self.class.name, id: self.class.decorator.get_id(self)
|
|
111
147
|
)
|
|
112
148
|
else
|
|
113
|
-
raise
|
|
149
|
+
raise TypeError.new("#{self.class.name}.#{prop} not set for non-optional prop")
|
|
114
150
|
end
|
|
115
151
|
end
|
|
116
152
|
|
|
@@ -181,6 +217,24 @@ module T::Props::Serializable::DecoratorMethods
|
|
|
181
217
|
)
|
|
182
218
|
end
|
|
183
219
|
|
|
220
|
+
def message_with_generated_source_context(error, generated_method, generate_source_method)
|
|
221
|
+
line_label = error.backtrace.find {|l| l.end_with?("in `#{generated_method}'")}
|
|
222
|
+
return unless line_label
|
|
223
|
+
|
|
224
|
+
line_num = line_label.split(':')[1]&.to_i
|
|
225
|
+
return unless line_num
|
|
226
|
+
|
|
227
|
+
source_lines = self.send(generate_source_method).split("\n")
|
|
228
|
+
previous_blank = source_lines[0...line_num].rindex(&:empty?) || 0
|
|
229
|
+
next_blank = line_num + (source_lines[line_num..-1]&.find_index(&:empty?) || 0)
|
|
230
|
+
context = " " + source_lines[(previous_blank + 1)...(next_blank)].join("\n ")
|
|
231
|
+
<<~MSG
|
|
232
|
+
Error in #{decorated_class.name}##{generated_method}: #{error.message}
|
|
233
|
+
at line #{line_num-previous_blank-1} in:
|
|
234
|
+
#{context}
|
|
235
|
+
MSG
|
|
236
|
+
end
|
|
237
|
+
|
|
184
238
|
def raise_nil_deserialize_error(hkey)
|
|
185
239
|
msg = "Tried to deserialize a required prop from a nil value. It's "\
|
|
186
240
|
"possible that a nil value exists in the database, so you should "\
|
|
@@ -28,7 +28,7 @@ module T::Props::TypeValidation
|
|
|
28
28
|
def prop_validate_definition!(name, _cls, rules, type)
|
|
29
29
|
super
|
|
30
30
|
|
|
31
|
-
if !rules[:DEPRECATED_underspecified_type]
|
|
31
|
+
if !rules[:DEPRECATED_underspecified_type]
|
|
32
32
|
validate_type(type, field_name: name)
|
|
33
33
|
elsif rules[:DEPRECATED_underspecified_type] && find_invalid_subtype(type).nil?
|
|
34
34
|
raise ArgumentError.new("DEPRECATED_underspecified_type set unnecessarily for #{@class.name}.#{name} - #{type} is a valid type")
|
|
@@ -63,7 +63,16 @@ module T::Props::TypeValidation
|
|
|
63
63
|
when T::Types::FixedHash
|
|
64
64
|
type.types.values.map {|subtype| find_invalid_subtype(subtype)}.compact.first
|
|
65
65
|
when T::Types::Union, T::Types::FixedArray
|
|
66
|
+
# `T.any` is valid if all of the members are valid
|
|
66
67
|
type.types.map {|subtype| find_invalid_subtype(subtype)}.compact.first
|
|
68
|
+
when T::Types::Intersection
|
|
69
|
+
# `T.all` is valid if at least one of the members is valid
|
|
70
|
+
invalid = type.types.map {|subtype| find_invalid_subtype(subtype)}.compact
|
|
71
|
+
if invalid.length == type.types.length
|
|
72
|
+
invalid.first
|
|
73
|
+
else
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
67
76
|
when T::Types::Enum, T::Types::ClassOf
|
|
68
77
|
nil
|
|
69
78
|
when T::Private::Types::TypeAlias
|
data/lib/types/types/t_enum.rb
CHANGED
|
@@ -12,7 +12,12 @@ module T::Types
|
|
|
12
12
|
|
|
13
13
|
# @override Base
|
|
14
14
|
def name
|
|
15
|
-
|
|
15
|
+
# Strips the #<...> off, just leaving the ...
|
|
16
|
+
# Reasoning: the user will have written something like
|
|
17
|
+
# T.any(MyEnum::A, MyEnum::B)
|
|
18
|
+
# in the type, so we should print what they wrote in errors, not:
|
|
19
|
+
# T.any(#<MyEnum::A>, #<MyEnum::B>)
|
|
20
|
+
@val.inspect[2..-2]
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
# @override Base
|
|
@@ -50,7 +50,11 @@ module T::Types
|
|
|
50
50
|
# Enumerators can be unbounded: see `[:foo, :bar].cycle`
|
|
51
51
|
return true
|
|
52
52
|
when Range
|
|
53
|
-
|
|
53
|
+
# A nil beginning or a nil end does not provide any type information. That is, nil in a range represents
|
|
54
|
+
# boundlessness, it does not express a type. For example `(nil...nil)` is not a T::Range[NilClass], its a range
|
|
55
|
+
# of unknown types (T::Range[T.untyped]).
|
|
56
|
+
# Similarly, `(nil...1)` is not a `T::Range[T.nilable(Integer)]`, it's a boundless range of Integer.
|
|
57
|
+
(obj.begin.nil? || @type.valid?(obj.begin)) && (obj.end.nil? || @type.valid?(obj.end))
|
|
54
58
|
when Set
|
|
55
59
|
obj.each do |item|
|
|
56
60
|
return false unless @type.valid?(item)
|
|
@@ -124,7 +128,13 @@ module T::Types
|
|
|
124
128
|
inferred_val = type_from_instances(obj.values)
|
|
125
129
|
T::Hash[inferred_key, inferred_val]
|
|
126
130
|
when Range
|
|
127
|
-
|
|
131
|
+
# We can't get any information from `NilClass` in ranges (since nil is used to represent boundlessness).
|
|
132
|
+
typeable_objects = [obj.begin, obj.end].compact
|
|
133
|
+
if typeable_objects.empty?
|
|
134
|
+
T::Range[T.untyped]
|
|
135
|
+
else
|
|
136
|
+
T::Range[type_from_instances(typeable_objects)]
|
|
137
|
+
end
|
|
128
138
|
when Enumerator
|
|
129
139
|
T::Enumerator[type_from_instances(obj)]
|
|
130
140
|
when Set
|
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.5.
|
|
4
|
+
version: 0.5.5794
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stripe
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-07-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -124,6 +124,7 @@ files:
|
|
|
124
124
|
- lib/types/generic.rb
|
|
125
125
|
- lib/types/helpers.rb
|
|
126
126
|
- lib/types/interface_wrapper.rb
|
|
127
|
+
- lib/types/non_forcing_constants.rb
|
|
127
128
|
- lib/types/private/abstract/data.rb
|
|
128
129
|
- lib/types/private/abstract/declare.rb
|
|
129
130
|
- lib/types/private/abstract/hooks.rb
|
|
@@ -139,6 +140,7 @@ files:
|
|
|
139
140
|
- lib/types/private/methods/signature.rb
|
|
140
141
|
- lib/types/private/methods/signature_validation.rb
|
|
141
142
|
- lib/types/private/mixins/mixins.rb
|
|
143
|
+
- lib/types/private/retry.rb
|
|
142
144
|
- lib/types/private/runtime_levels.rb
|
|
143
145
|
- lib/types/private/sealed.rb
|
|
144
146
|
- lib/types/private/types/not_typed.rb
|