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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7f54d40d5dbaf968c3ab004187df66db620ff62a74860b03f6c80f4cb59d0b2
4
- data.tar.gz: 278a695d5bf4f93bfdd2a03f225850e0d7f54b6ee60fa9b838e07b16a55b0370
3
+ metadata.gz: ac9a48252c9529d0ca91f96108b141c85388c88180281e5c8a682b16c11a9d5a
4
+ data.tar.gz: 31dc95eabc00327b1672a8fc142da7dc47f0ee0699fd4112358042248628675b
5
5
  SHA512:
6
- metadata.gz: 99bc296b66501a1e58616bbeebef572e8b7154bbeab2405a2c92290a9331e57be59670b081f27003270e32998666a6363da2d38e2d1dfa4489a01c04f3c46a19
7
- data.tar.gz: e7b6c400aaa036ad2b9fa50d43061785cc4e6fdce0e2afefda2dc515b5875ab7b42c741249d5fd3d603c6f37d517b340b52cbbb13084396b3ef54e45553f07dc
6
+ metadata.gz: 21b1ef4ffdd76365effafe0c6a3eea4b15c1f9c36f2f402a1c8ba382d2f03d89f2202c1b8ac2aa82c315c7471f0989f7501d1888a49f67d401d33d26ac8e4d3a
7
+ data.tar.gz: 0f4c110725910ae452ea72fe6852538aa1c31cbb576dc242fe3631bd318e3b2bfda7b0fe3b05362584bd53885c9788a4f3eb2d5920d285c1c39ce727fb0ebd14
@@ -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
@@ -103,7 +103,7 @@ module T
103
103
  end
104
104
  end
105
105
 
106
- # References a type paramater which was previously defined with
106
+ # References a type parameter which was previously defined with
107
107
  # `type_parameters`.
108
108
  #
109
109
  # This is used for generic methods. Example usage:
@@ -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 and and_call_original.
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::AnyInstance
26
+ if defined? ::RSpec::Mocks
26
27
  module T
27
28
  module CompatibilityPatches
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)
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
@@ -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
- @scalar_tyeps = values
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
@@ -40,6 +40,9 @@ module T::Private::Abstract::Declare
40
40
  end
41
41
  super(*args, &blk)
42
42
  end
43
+ if mod.respond_to?(:ruby2_keywords, true)
44
+ mod.send(:ruby2_keywords, :initialize)
45
+ end
43
46
  end
44
47
  end
45
48
  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)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Private::Retry
5
+
6
+ # A special singleton used for static analysis of exceptions.
7
+ module RETRY
8
+ freeze
9
+ end
10
+ end
@@ -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::Props::CustomType] (String) The type of this
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: false
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
- # Returns true if the given Ruby value can be assigned to a T::Props field
9
- # of this type.
10
- #
11
- # @param [Object] _value
12
- # @return T::Boolean
13
- def instance?(_value)
14
- raise NotImplementedError.new('Must override in included class')
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 consistent interface with T::Types::Base
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
- def serialize(_instance)
28
- raise NotImplementedError.new('Must override in included class')
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 _mongo_scalar One of T::Configuration.scalar_types
48
+ # @param scalar One of T::Configuration.scalar_types
35
49
  # @return Object
36
- def deserialize(_mongo_scalar)
37
- raise NotImplementedError.new('Must override in included class')
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
- def self.valid_serialization?(val, type=nil)
63
- if type&.name == 'Chalk::ODM::BsonTypes::BsonObject'
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
- def self.checked_serialize(type, instance)
85
- val = type.serialize(instance)
86
- unless valid_serialization?(val, type)
87
- msg = "#{type} did not serialize to a valid scalar type. It became a: #{val.class}"
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 T::Props::InvalidValueError.new(msg)
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.any(T::Types::Base, T::Props::CustomType)}
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=props[prop.to_sym], opts={})
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
- if type.is_a?(T::Types::TypedArray) && type.type.is_a?(T::Types::Simple)
402
- rules[:array] = type.type.raw_type
403
- elsif array_subdoc_type
404
- rules[:array] = array_subdoc_type
405
- end
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: PropType)
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, array:, enum:)
399
+ private def smart_coerce(type, enum:)
552
400
  # Backwards compatibility for pre-T::Types style
553
- if !array.nil? && !enum.nil?
554
- raise ArgumentError.new("Cannot specify both :array and :enum options")
555
- elsif !array.nil?
556
- if type == Set
557
- T::Set[array]
558
- else
559
- T::Array[array]
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: Chalk::Tools::RedactionUtils::RedactionDirectiveSpec,
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
- validate_lack_of_side_effects(else_body, whitelisted_methods_for_deserialize)
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(name) do |*args|
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
 
@@ -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
- transformed_val = SerdeTransform.generate(
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
- ) || 'val'
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, Numeric].freeze,
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.any(T::Types::Base, Module),
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
- "T::Props::Utils.deep_clone_object(#{varname})"
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
- if type.singleton_class < T::Props::CustomType
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
- type_name = T.must(module_name(type))
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, the existence
24
- # of defaults & factories, and the fact that we allow `T.nilable(Foo)`
25
- # where Foo < T::Props::CustomType as a prop type even though calling
26
- # `valid?` on it won't work as expected), so unwrap any T.nilable and
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.any(T::Types::Base, T.all(T::Props::CustomType, Module)),
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.any(T::Types::Base, T.all(T::Props::CustomType, Module)),
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
- h = __t_props_generated_serialize(strict)
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
- hash_keys_matching_props = __t_props_generated_deserialize(hash)
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 T::Props::InvalidValueError.new("#{self.class.name}.#{prop} not set for non-optional prop")
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] && !(type.singleton_class <= T::Props::CustomType)
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
@@ -12,7 +12,12 @@ module T::Types
12
12
 
13
13
  # @override Base
14
14
  def name
15
- @val.inspect
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
@@ -17,7 +17,7 @@ module T::Types
17
17
  end
18
18
 
19
19
  def name
20
- "T.type_parameter(#{@name})"
20
+ "T.type_parameter(:#{@name})"
21
21
  end
22
22
  end
23
23
  end
@@ -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
- @type.valid?(obj.first) && @type.valid?(obj.last)
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
- T::Range[type_from_instances([obj.first, obj.last])]
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.5429
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-03-10 00:00:00.000000000 Z
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