sorbet-runtime 0.5.10346 → 0.5.10597

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 074fc57d98df52ce1429d736e41df1ea7f3c2ba2fefa7386b07a4529741186c3
4
- data.tar.gz: e5fd218bc7085529da5fd85af7e91178998f37b1f9c609d8455802720987414e
3
+ metadata.gz: 5569102ba150ab771b791b9265fa4e5472793cb90b4d6066b66fbbf180a6b591
4
+ data.tar.gz: 64e6645c8a45b5623763b43f5141febbbe5623c8de3868d4842086874984aad0
5
5
  SHA512:
6
- metadata.gz: f9cde98126821f31e5d6ea0b066e5549e53f050f37105538c0817c5c85fd8e6b50e8c625adb712d9593c19bdfde2e3725aa20abadd08bf68d40c363425c1f23b
7
- data.tar.gz: a9e31374cfed23b17e4701a354ac91a84415e3860fe03e32c1c660eae59aa707beceebfc877a2c42ebffd94c5d45424709a87f73447b1d2f954a5b5b7ffbd7fe
6
+ metadata.gz: 3db0e5871eab9001aaaa336cda62ae6d2f951168beb393e8f60b1868ebe4d901753ae191cabb54753b3f85d50e6f2fb85d861352537a286e257b37870e89bd2e
7
+ data.tar.gz: 70ed5dba4f054c4a68362df8b65060d623762530f18522696eca9ed17ba076cc7ea92c474350ee99acb53db4f856a53088c904ff757ea2e687d1910e82c0cd34
@@ -55,6 +55,7 @@ require_relative 'types/private/types/not_typed'
55
55
  require_relative 'types/private/types/void'
56
56
  require_relative 'types/private/types/string_holder'
57
57
  require_relative 'types/private/types/type_alias'
58
+ require_relative 'types/private/types/simple_pair_union'
58
59
 
59
60
  require_relative 'types/types/type_variable'
60
61
  require_relative 'types/types/type_member'
data/lib/types/_types.rb CHANGED
@@ -130,7 +130,7 @@ module T
130
130
  def self.cast(value, type, checked: true)
131
131
  return value unless checked
132
132
 
133
- Private::Casts.cast(value, type, cast_method: "T.cast")
133
+ Private::Casts.cast(value, type, "T.cast")
134
134
  end
135
135
 
136
136
  # Tells the typechecker to declare a variable of type `type`. Use
@@ -145,7 +145,7 @@ module T
145
145
  def self.let(value, type, checked: true)
146
146
  return value unless checked
147
147
 
148
- Private::Casts.cast(value, type, cast_method: "T.let")
148
+ Private::Casts.cast(value, type, "T.let")
149
149
  end
150
150
 
151
151
  # Tells the type checker to treat `self` in the current block as `type`.
@@ -164,7 +164,7 @@ module T
164
164
  def self.bind(value, type, checked: true)
165
165
  return value unless checked
166
166
 
167
- Private::Casts.cast(value, type, cast_method: "T.bind")
167
+ Private::Casts.cast(value, type, "T.bind")
168
168
  end
169
169
 
170
170
  # Tells the typechecker to ensure that `value` is of type `type` (if not, the typechecker will
@@ -174,7 +174,7 @@ module T
174
174
  def self.assert_type!(value, type, checked: true)
175
175
  return value unless checked
176
176
 
177
- Private::Casts.cast(value, type, cast_method: "T.assert_type!")
177
+ Private::Casts.cast(value, type, "T.assert_type!")
178
178
  end
179
179
 
180
180
  # For the static type checker, strips all type information from a value
@@ -221,6 +221,34 @@ module T
221
221
  end
222
222
  end
223
223
 
224
+ # A convenience method to `raise` with a provided error reason when the argument
225
+ # is `nil` and return it otherwise.
226
+ #
227
+ # Intended to be used as:
228
+ #
229
+ # needs_foo(T.must_because(maybe_gives_foo) {"reason_foo_should_not_be_nil"})
230
+ #
231
+ # Equivalent to:
232
+ #
233
+ # foo = maybe_gives_foo
234
+ # raise "reason_foo_should_not_be_nil" if foo.nil?
235
+ # needs_foo(foo)
236
+ #
237
+ # Intended to be used to promise sorbet that a given nilable value happens
238
+ # to contain a non-nil value at this point.
239
+ #
240
+ # `sig {params(arg: T.nilable(A), reason_blk: T.proc.returns(String)).returns(A)}`
241
+ def self.must_because(arg)
242
+ return arg if arg
243
+ return arg if arg == false
244
+
245
+ begin
246
+ raise TypeError.new("Unexpected `nil` because #{yield}")
247
+ rescue TypeError => e # raise into rescue to ensure e.backtrace is populated
248
+ T::Configuration.inline_type_error_handler(e, {kind: 'T.must_because', value: arg, type: nil})
249
+ end
250
+ end
251
+
224
252
  # A way to ask Sorbet to show what type it thinks an expression has.
225
253
  # This can be useful for debugging and checking assumptions.
226
254
  # In the runtime, merely returns the value passed in.
@@ -71,6 +71,10 @@ class T::InterfaceWrapper
71
71
  target_obj.send(method_name, *args, &blk)
72
72
  end
73
73
 
74
+ if singleton_class.respond_to?(:ruby2_keywords, true)
75
+ singleton_class.send(:ruby2_keywords, method_name)
76
+ end
77
+
74
78
  if target_obj.singleton_class.public_method_defined?(method_name)
75
79
  # no-op, it's already public
76
80
  elsif target_obj.singleton_class.protected_method_defined?(method_name)
@@ -3,9 +3,12 @@
3
3
 
4
4
  module T::Private
5
5
  module Casts
6
- def self.cast(value, type, cast_method:)
6
+ def self.cast(value, type, cast_method)
7
7
  begin
8
- error = T::Utils.coerce(type).error_message_for_obj(value)
8
+ coerced_type = T::Utils::Private.coerce_and_check_module_types(type, value, true)
9
+ return value unless coerced_type
10
+
11
+ error = coerced_type.error_message_for_obj(value)
9
12
  return value unless error
10
13
 
11
14
  caller_loc = T.must(caller_locations(2..2)).first
@@ -22,7 +25,7 @@ module T::Private
22
25
  # there's a lot of shared logic with the above one, but factoring
23
26
  # it out like this makes it easier to hopefully one day delete
24
27
  # this one
25
- def self.cast_recursive(value, type, cast_method:)
28
+ def self.cast_recursive(value, type, cast_method)
26
29
  begin
27
30
  error = T::Utils.coerce(type).error_message_for_obj_recursive(value)
28
31
  return value unless error
@@ -59,8 +59,9 @@ module T::Private::Methods::CallValidation
59
59
 
60
60
  def self.create_validator_method(mod, original_method, method_sig, original_visibility)
61
61
  has_fixed_arity = method_sig.kwarg_types.empty? && !method_sig.has_rest && !method_sig.has_keyrest &&
62
- original_method.parameters.all? {|(kind, _name)| kind == :req}
63
- ok_for_fast_path = has_fixed_arity && !method_sig.bind && method_sig.arg_types.length < 5 && is_allowed_to_have_fast_path
62
+ original_method.parameters.all? {|(kind, _name)| kind == :req || kind == :block}
63
+ can_skip_block_type = method_sig.block_type.nil? || method_sig.block_type.valid?(nil)
64
+ ok_for_fast_path = has_fixed_arity && can_skip_block_type && !method_sig.bind && method_sig.arg_types.length < 5 && is_allowed_to_have_fast_path
64
65
 
65
66
  all_args_are_simple = ok_for_fast_path && method_sig.arg_types.all? {|_name, type| type.is_a?(T::Types::Simple)}
66
67
  simple_method = all_args_are_simple && method_sig.return_type.is_a?(T::Types::Simple)
@@ -76,6 +77,11 @@ module T::Private::Methods::CallValidation
76
77
  create_validator_procedure_medium(mod, original_method, method_sig, original_visibility)
77
78
  elsif ok_for_fast_path
78
79
  create_validator_method_medium(mod, original_method, method_sig, original_visibility)
80
+ elsif can_skip_block_type
81
+ # The Ruby VM already validates that any block passed to a method
82
+ # must be either `nil` or a `Proc` object, so there's no need to also
83
+ # have sorbet-runtime check that.
84
+ create_validator_slow_skip_block_type(mod, original_method, method_sig, original_visibility)
79
85
  else
80
86
  create_validator_slow(mod, original_method, method_sig, original_visibility)
81
87
  end
@@ -84,6 +90,88 @@ module T::Private::Methods::CallValidation
84
90
  end
85
91
  end
86
92
 
93
+ def self.create_validator_slow_skip_block_type(mod, original_method, method_sig, original_visibility)
94
+ T::Private::ClassUtils.def_with_visibility(mod, method_sig.method_name, original_visibility) do |*args, &blk|
95
+ CallValidation.validate_call_skip_block_type(self, original_method, method_sig, args, blk)
96
+ end
97
+ end
98
+
99
+ def self.validate_call_skip_block_type(instance, original_method, method_sig, args, blk)
100
+ # This method is called for every `sig`. It's critical to keep it fast and
101
+ # reduce number of allocations that happen here.
102
+
103
+ if method_sig.bind
104
+ message = method_sig.bind.error_message_for_obj(instance)
105
+ if message
106
+ CallValidation.report_error(
107
+ method_sig,
108
+ message,
109
+ 'Bind',
110
+ nil,
111
+ method_sig.bind,
112
+ instance
113
+ )
114
+ end
115
+ end
116
+
117
+ # NOTE: We don't bother validating for missing or extra kwargs;
118
+ # the method call itself will take care of that.
119
+ method_sig.each_args_value_type(args) do |name, arg, type|
120
+ message = type.error_message_for_obj(arg)
121
+ if message
122
+ CallValidation.report_error(
123
+ method_sig,
124
+ message,
125
+ 'Parameter',
126
+ name,
127
+ type,
128
+ arg,
129
+ caller_offset: 2
130
+ )
131
+ end
132
+ end
133
+
134
+ # The original method definition allows passing `nil` for the `&blk`
135
+ # argument, so we do not have to do any method_sig.block_type type checks
136
+ # of our own.
137
+
138
+ # The following line breaks are intentional to show nice pry message
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+ # PRY note:
150
+ # this code is sig validation code.
151
+ # Please issue `finish` to step out of it
152
+
153
+ return_value = T::Configuration::AT_LEAST_RUBY_2_7 ? original_method.bind_call(instance, *args, &blk) : original_method.bind(instance).call(*args, &blk)
154
+
155
+ # The only type that is allowed to change the return value is `.void`.
156
+ # It ignores what you returned and changes it to be a private singleton.
157
+ if method_sig.return_type.is_a?(T::Private::Types::Void)
158
+ T::Private::Types::Void::VOID
159
+ else
160
+ message = method_sig.return_type.error_message_for_obj(return_value)
161
+ if message
162
+ CallValidation.report_error(
163
+ method_sig,
164
+ message,
165
+ 'Return value',
166
+ nil,
167
+ method_sig.return_type,
168
+ return_value,
169
+ )
170
+ end
171
+ return_value
172
+ end
173
+ end
174
+
87
175
  def self.create_validator_slow(mod, original_method, method_sig, original_visibility)
88
176
  T::Private::ClassUtils.def_with_visibility(mod, method_sig.method_name, original_visibility) do |*args, &blk|
89
177
  CallValidation.validate_call(self, original_method, method_sig, args, blk)
@@ -125,8 +213,19 @@ module T::Private::Methods::CallValidation
125
213
  end
126
214
  end
127
215
 
128
- if method_sig.block_type
129
- message = method_sig.block_type.error_message_for_obj(blk)
216
+ # The Ruby VM already checks that `&blk` is either a `Proc` type or `nil`:
217
+ # https://github.com/ruby/ruby/blob/v2_7_6/vm_args.c#L1150-L1154
218
+ # And `T.proc` types don't (can't) do any runtime arg checking, so we can
219
+ # save work by simply checking that `blk` is non-nil (if the method allows
220
+ # `nil` for the block, it would not have used this validate_call path).
221
+ unless blk
222
+ # Have to use `&.` here, because it's technically a public API that
223
+ # people can _always_ call `validate_call` to validate any signature
224
+ # (i.e., the faster validators are merely optimizations).
225
+ # In practice, this only affects the first call to the method (before the
226
+ # optimized validators have a chance to replace the initial, slow
227
+ # wrapper).
228
+ message = method_sig.block_type&.error_message_for_obj(blk)
130
229
  if message
131
230
  CallValidation.report_error(
132
231
  method_sig,
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # Specialization of Union for the common case of the union of two simple types.
5
+ #
6
+ # This covers e.g. T.nilable(SomeModule), T.any(Integer, Float), and T::Boolean.
7
+ class T::Private::Types::SimplePairUnion < T::Types::Union
8
+ class DuplicateType < RuntimeError; end
9
+
10
+ # @param type_a [T::Types::Simple]
11
+ # @param type_b [T::Types::Simple]
12
+ def initialize(type_a, type_b)
13
+ if type_a == type_b
14
+ raise DuplicateType.new("#{type_a} == #{type_b}")
15
+ end
16
+
17
+ @raw_a = type_a.raw_type
18
+ @raw_b = type_b.raw_type
19
+ end
20
+
21
+ # @override Union
22
+ def recursively_valid?(obj)
23
+ obj.is_a?(@raw_a) || obj.is_a?(@raw_b)
24
+ end
25
+
26
+ # @override Union
27
+ def valid?(obj)
28
+ obj.is_a?(@raw_a) || obj.is_a?(@raw_b)
29
+ end
30
+
31
+ # @override Union
32
+ def types
33
+ # We reconstruct the simple types rather than just storing them because
34
+ # (1) this is normally not a hot path and (2) we want to keep the instance
35
+ # variable count <= 3 so that we can fit in a 40 byte heap entry along
36
+ # with object headers.
37
+ @types ||= [
38
+ T::Types::Simple::Private::Pool.type_for_module(@raw_a),
39
+ T::Types::Simple::Private::Pool.type_for_module(@raw_b),
40
+ ]
41
+ end
42
+ end
@@ -300,7 +300,9 @@ class T::Props::Decorator
300
300
  .checked(:never)
301
301
  end
302
302
  private def prop_nilable?(cls, rules)
303
- T::Utils::Nilable.is_union_with_nilclass(cls) || (cls == T.untyped && rules.key?(:default) && rules[:default].nil?)
303
+ # NB: `prop` and `const` do not `T::Utils::coerce the type of the prop if it is a `Module`,
304
+ # hence the bare `NilClass` check.
305
+ T::Utils::Nilable.is_union_with_nilclass(cls) || ((cls == T.untyped || cls == NilClass) && rules.key?(:default) && rules[:default].nil?)
304
306
  end
305
307
 
306
308
  # checked(:never) - Rules hash is expensive to check
@@ -83,7 +83,7 @@ module T::Props
83
83
  lazily_defined_methods[name] = blk
84
84
 
85
85
  cls = decorated_class
86
- if cls.method_defined?(name)
86
+ if cls.method_defined?(name) || cls.private_method_defined?(name)
87
87
  # Ruby does not emit "method redefined" warnings for aliased methods
88
88
  # (more robust than undef_method that would create a small window in which the method doesn't exist)
89
89
  cls.send(:alias_method, name, name)
@@ -1,18 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
  # typed: true
3
+ require 'pp'
3
4
 
4
5
  module T::Props::PrettyPrintable
5
6
  include T::Props::Plugin
6
7
 
7
- # Return a string representation of this object and all of its props
8
+ # Override the PP gem with something that's similar, but gives us a hook to do redaction and customization
9
+ def pretty_print(pp)
10
+ clazz = T.unsafe(T.cast(self, Object).class).decorator
11
+ multiline = pp.is_a?(PP)
12
+ pp.group(1, "<#{clazz.inspect_class_with_decoration(self)}", ">") do
13
+ clazz.all_props.sort.each do |prop|
14
+ pp.breakable
15
+ val = clazz.get(self, prop)
16
+ rules = clazz.prop_rules(prop)
17
+ pp.text("#{prop}=")
18
+ if (custom_inspect = rules[:inspect])
19
+ inspected = if T::Utils.arity(custom_inspect) == 1
20
+ custom_inspect.call(val)
21
+ else
22
+ custom_inspect.call(val, {multiline: multiline})
23
+ end
24
+ pp.text(inspected.nil? ? "nil" : inspected)
25
+ elsif rules[:sensitivity] && !rules[:sensitivity].empty? && !val.nil?
26
+ pp.text("<REDACTED #{rules[:sensitivity].join(', ')}>")
27
+ else
28
+ val.pretty_print(pp)
29
+ end
30
+ end
31
+ clazz.pretty_print_extra(self, pp)
32
+ end
33
+ end
34
+
35
+ # Return a string representation of this object and all of its props in a single line
8
36
  def inspect
9
- T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self)
37
+ string = +""
38
+ PP.singleline_pp(self, string)
39
+ string
10
40
  end
11
41
 
12
- # Override the PP gem with something that's similar, but gives us a hook
13
- # to do redaction
42
+ # Return a pretty string representation of this object and all of its props
14
43
  def pretty_inspect
15
- T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self, multiline: true)
44
+ string = +""
45
+ PP.pp(self, string)
46
+ string
16
47
  end
17
48
 
18
49
  module DecoratorMethods
@@ -23,85 +54,16 @@ module T::Props::PrettyPrintable
23
54
  super || key == :inspect
24
55
  end
25
56
 
26
- sig do
27
- params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
28
- .returns(String)
57
+ # Overridable method to specify how the first part of a `pretty_print`d object's class should look like
58
+ # NOTE: This is just to support Stripe's `PrettyPrintableModel` case, and not recommended to be overriden
59
+ sig {params(instance: T::Props::PrettyPrintable).returns(String)}
60
+ def inspect_class_with_decoration(instance)
61
+ T.unsafe(instance).class.to_s
29
62
  end
30
- def inspect_instance(instance, multiline: false, indent: ' ')
31
- components =
32
- inspect_instance_components(
33
- instance,
34
- multiline: multiline,
35
- indent: indent
36
- )
37
- .reject(&:empty?)
38
-
39
- # Not using #<> here as that makes pry highlight these objects
40
- # as if they were all comments, whereas this makes them look
41
- # like the structured thing they are.
42
- if multiline
43
- "#{components[0]}:\n" + T.must(components[1..-1]).join("\n")
44
- else
45
- "<#{components.join(' ')}>"
46
- end
47
- end
48
-
49
- sig do
50
- params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
51
- .returns(T::Array[String])
52
- end
53
- private def inspect_instance_components(instance, multiline:, indent:)
54
- pretty_props = T.unsafe(self).all_props.map do |prop|
55
- [prop, inspect_prop_value(instance, prop, multiline: multiline, indent: indent)]
56
- end
57
-
58
- joined_props = join_props_with_pretty_values(
59
- pretty_props,
60
- multiline: multiline,
61
- indent: indent
62
- )
63
63
 
64
- [
65
- T.unsafe(self).decorated_class.to_s,
66
- joined_props,
67
- ]
68
- end
69
-
70
- sig do
71
- params(instance: T::Props::PrettyPrintable, prop: Symbol, multiline: T::Boolean, indent: String)
72
- .returns(String)
73
- .checked(:never)
74
- end
75
- private def inspect_prop_value(instance, prop, multiline:, indent:)
76
- val = T.unsafe(self).get(instance, prop)
77
- rules = T.unsafe(self).prop_rules(prop)
78
- if (custom_inspect = rules[:inspect])
79
- if T::Utils.arity(custom_inspect) == 1
80
- custom_inspect.call(val)
81
- else
82
- custom_inspect.call(val, {multiline: multiline, indent: indent})
83
- end
84
- elsif rules[:sensitivity] && !rules[:sensitivity].empty? && !val.nil?
85
- "<REDACTED #{rules[:sensitivity].join(', ')}>"
86
- else
87
- val.inspect
88
- end
89
- end
90
-
91
- sig do
92
- params(pretty_kvs: T::Array[[Symbol, String]], multiline: T::Boolean, indent: String)
93
- .returns(String)
94
- end
95
- private def join_props_with_pretty_values(pretty_kvs, multiline:, indent: ' ')
96
- pairs = pretty_kvs
97
- .sort_by {|k, _v| k.to_s}
98
- .map {|k, v| "#{k}=#{v}"}
99
-
100
- if multiline
101
- indent + pairs.join("\n#{indent}")
102
- else
103
- pairs.join(', ')
104
- end
105
- end
64
+ # Overridable method to add anything that is not a prop
65
+ # NOTE: This is to support cases like Serializable's `@_extra_props`, and Stripe's `PrettyPrintableModel#@_deleted`
66
+ sig {params(instance: T::Props::PrettyPrintable, pp: T.any(PrettyPrint, PP::SingleLine)).void}
67
+ def pretty_print_extra(instance, pp); end
106
68
  end
107
69
  end
@@ -338,14 +338,21 @@ module T::Props::Serializable::DecoratorMethods
338
338
  end
339
339
  end
340
340
 
341
- # overrides T::Props::PrettyPrintable
342
- private def inspect_instance_components(instance, multiline:, indent:)
341
+ # adds to the default result of T::Props::PrettyPrintable
342
+ def pretty_print_extra(instance, pp)
343
+ # This is to maintain backwards compatibility with Stripe's codebase, where only the single line (through `inspect`)
344
+ # version is expected to add anything extra
345
+ return if !pp.is_a?(PP::SingleLine)
343
346
  if (extra_props = extra_props(instance)) && !extra_props.empty?
344
- pretty_kvs = extra_props.map {|k, v| [k.to_sym, v.inspect]}
345
- extra = join_props_with_pretty_values(pretty_kvs, multiline: false)
346
- super + ["@_extra_props=<#{extra}>"]
347
- else
348
- super
347
+ pp.breakable
348
+ pp.text("@_extra_props=")
349
+ pp.group(1, "<", ">") do
350
+ extra_props.each_with_index do |(prop, value), i|
351
+ pp.breakable unless i.zero?
352
+ pp.text("#{prop}=")
353
+ value.pretty_print(pp)
354
+ end
355
+ end
349
356
  end
350
357
  end
351
358
  end
@@ -163,8 +163,12 @@ module T::Types
163
163
  # Type equivalence, defined by serializing the type to a string (with
164
164
  # `#name`) and comparing the resulting strings for equality.
165
165
  def ==(other)
166
- (T::Utils.resolve_alias(other).class == T::Utils.resolve_alias(self).class) &&
166
+ case other
167
+ when T::Types::Base
167
168
  other.name == self.name
169
+ else
170
+ false
171
+ end
168
172
  end
169
173
 
170
174
  alias_method :eql?, :==
@@ -29,7 +29,7 @@ module T::Types
29
29
 
30
30
  # overrides Base
31
31
  def name
32
- "T.deprecated_enum([#{@values.map(&:inspect).join(', ')}])"
32
+ @name ||= "T.deprecated_enum([#{@values.map(&:inspect).join(', ')}])"
33
33
  end
34
34
 
35
35
  # overrides Base
@@ -20,7 +20,7 @@ module T::Types
20
20
 
21
21
  # overrides Base
22
22
  def name
23
- "T.all(#{@types.map(&:name).sort.join(', ')})"
23
+ "T.all(#{@types.map(&:name).compact.sort.join(', ')})"
24
24
  end
25
25
 
26
26
  # overrides Base
@@ -52,7 +52,10 @@ module T::Types
52
52
  end
53
53
 
54
54
  def to_nilable
55
- @nilable ||= T::Types::Union.new([self, T::Utils::Nilable::NIL_TYPE])
55
+ @nilable ||= T::Private::Types::SimplePairUnion.new(
56
+ self,
57
+ T::Utils::Nilable::NIL_TYPE,
58
+ )
56
59
  end
57
60
 
58
61
  module Private
@@ -6,6 +6,8 @@ module T::Types
6
6
  class Union < Base
7
7
  attr_reader :types
8
8
 
9
+ # Don't use Union.new directly, use `Private::Pool.union_of_types`
10
+ # inside sorbet-runtime and `T.any` elsewhere.
9
11
  def initialize(types)
10
12
  @types = types.flat_map do |type|
11
13
  type = T::Utils.coerce(type)
@@ -20,11 +22,16 @@ module T::Types
20
22
 
21
23
  # overrides Base
22
24
  def name
23
- type_shortcuts(@types)
25
+ # Use the attr_reader here so we can override it in SimplePairUnion
26
+ type_shortcuts(types)
24
27
  end
25
28
 
26
29
  private def type_shortcuts(types)
27
30
  if types.size == 1
31
+ # We shouldn't generally get here but it's possible if initializing the type
32
+ # evades Sorbet's static check and we end up on the slow path, or if someone
33
+ # is using the T:Types::Union constructor directly (the latter possibility
34
+ # is why we don't just move the `uniq` into `Private::Pool.union_of_types`).
28
35
  return types[0].name
29
36
  end
30
37
  nilable = T::Utils.coerce(NilClass)
@@ -62,27 +69,45 @@ module T::Types
62
69
  EMPTY_ARRAY = [].freeze
63
70
  private_constant :EMPTY_ARRAY
64
71
 
72
+ # Try to use `to_nilable` on a type to get memoization, or failing that
73
+ # try to at least use SimplePairUnion to get faster init and typechecking.
74
+ #
75
+ # We aren't guaranteed to detect a simple `T.nilable(<Module>)` type here
76
+ # in cases where there are duplicate types, nested unions, etc.
77
+ #
78
+ # That's ok, because returning is SimplePairUnion an optimization which
79
+ # isn't necessary for correctness.
80
+ #
65
81
  # @param type_a [T::Types::Base]
66
82
  # @param type_b [T::Types::Base]
67
83
  # @param types [Array] optional array of additional T::Types::Base instances
68
84
  def self.union_of_types(type_a, type_b, types=EMPTY_ARRAY)
69
- if types.empty?
70
- # We aren't guaranteed to detect a simple `T.nilable(<Module>)` type here
71
- # in cases where there are duplicate types, nested unions, etc.
72
- #
73
- # That's ok, because this is an optimization which isn't necessary for
74
- # correctness.
75
- if type_b == T::Utils::Nilable::NIL_TYPE && type_a.is_a?(T::Types::Simple)
85
+ if !types.empty?
86
+ # Slow path
87
+ return Union.new([type_a, type_b] + types)
88
+ elsif !type_a.is_a?(T::Types::Simple) || !type_b.is_a?(T::Types::Simple)
89
+ # Slow path
90
+ return Union.new([type_a, type_b])
91
+ end
92
+
93
+ begin
94
+ if type_b == T::Utils::Nilable::NIL_TYPE
76
95
  type_a.to_nilable
77
- elsif type_a == T::Utils::Nilable::NIL_TYPE && type_b.is_a?(T::Types::Simple)
96
+ elsif type_a == T::Utils::Nilable::NIL_TYPE
78
97
  type_b.to_nilable
79
98
  else
80
- Union.new([type_a, type_b])
99
+ T::Private::Types::SimplePairUnion.new(type_a, type_b)
81
100
  end
82
- else
83
- # This can't be a `T.nilable(<Module>)` case unless there are duplicates,
84
- # which is possible but unexpected.
85
- Union.new([type_a, type_b] + types)
101
+ rescue T::Private::Types::SimplePairUnion::DuplicateType
102
+ # Slow path
103
+ #
104
+ # This shouldn't normally be possible due to static checks,
105
+ # but we can get here if we're constructing a type dynamically.
106
+ #
107
+ # Relying on the duplicate check in the constructor has the
108
+ # advantage that we avoid it when we hit the memoized case
109
+ # of `to_nilable`.
110
+ type_a
86
111
  end
87
112
  end
88
113
  end
data/lib/types/utils.rb CHANGED
@@ -2,29 +2,41 @@
2
2
  # typed: true
3
3
 
4
4
  module T::Utils
5
+ module Private
6
+ def self.coerce_and_check_module_types(val, check_val, check_module_type)
7
+ if val.is_a?(T::Types::Base)
8
+ if val.is_a?(T::Private::Types::TypeAlias)
9
+ val.aliased_type
10
+ else
11
+ val
12
+ end
13
+ elsif val.is_a?(Module)
14
+ if check_module_type && check_val.is_a?(val)
15
+ nil
16
+ else
17
+ T::Types::Simple::Private::Pool.type_for_module(val)
18
+ end
19
+ elsif val.is_a?(::Array)
20
+ T::Types::FixedArray.new(val)
21
+ elsif val.is_a?(::Hash)
22
+ T::Types::FixedHash.new(val)
23
+ elsif val.is_a?(T::Private::Methods::DeclBuilder)
24
+ T::Private::Methods.finalize_proc(val.decl)
25
+ elsif val.is_a?(::T::Enum)
26
+ T::Types::TEnum.new(val)
27
+ elsif val.is_a?(::String)
28
+ raise "Invalid String literal for type constraint. Must be an #{T::Types::Base}, a " \
29
+ "class/module, or an array. Got a String with value `#{val}`."
30
+ else
31
+ raise "Invalid value for type constraint. Must be an #{T::Types::Base}, a " \
32
+ "class/module, or an array. Got a `#{val.class}`."
33
+ end
34
+ end
35
+ end
36
+
5
37
  # Used to convert from a type specification to a `T::Types::Base`.
6
38
  def self.coerce(val)
7
- if val.is_a?(T::Private::Types::TypeAlias)
8
- val.aliased_type
9
- elsif val.is_a?(T::Types::Base)
10
- val
11
- elsif val.is_a?(Module)
12
- T::Types::Simple::Private::Pool.type_for_module(val)
13
- elsif val.is_a?(::Array)
14
- T::Types::FixedArray.new(val)
15
- elsif val.is_a?(::Hash)
16
- T::Types::FixedHash.new(val)
17
- elsif val.is_a?(T::Private::Methods::DeclBuilder)
18
- T::Private::Methods.finalize_proc(val.decl)
19
- elsif val.is_a?(::T::Enum)
20
- T::Types::TEnum.new(val)
21
- elsif val.is_a?(::String)
22
- raise "Invalid String literal for type constraint. Must be an #{T::Types::Base}, a " \
23
- "class/module, or an array. Got a String with value `#{val}`."
24
- else
25
- raise "Invalid value for type constraint. Must be an #{T::Types::Base}, a " \
26
- "class/module, or an array. Got a `#{val.class}`."
27
- end
39
+ Private.coerce_and_check_module_types(val, nil, false)
28
40
  end
29
41
 
30
42
  # Dynamically confirm that `value` is recursively a valid value of
@@ -32,7 +44,7 @@ module T::Utils
32
44
  # in some cases this runtime check can be very expensive, especially
33
45
  # with large collections of objects.
34
46
  def self.check_type_recursive!(value, type)
35
- T::Private::Casts.cast_recursive(value, type, cast_method: "T.check_type_recursive!")
47
+ T::Private::Casts.cast_recursive(value, type, "T.check_type_recursive!")
36
48
  end
37
49
 
38
50
  # Returns the set of all methods (public, protected, private) defined on a module or its
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.10346
4
+ version: 0.5.10597
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-18 00:00:00.000000000 Z
11
+ date: 2022-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -151,7 +151,7 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: 1.5.3
153
153
  description: Sorbet's runtime type checking component
154
- email:
154
+ email:
155
155
  executables: []
156
156
  extensions: []
157
157
  extra_rdoc_files: []
@@ -189,6 +189,7 @@ files:
189
189
  - lib/types/private/runtime_levels.rb
190
190
  - lib/types/private/sealed.rb
191
191
  - lib/types/private/types/not_typed.rb
192
+ - lib/types/private/types/simple_pair_union.rb
192
193
  - lib/types/private/types/string_holder.rb
193
194
  - lib/types/private/types/type_alias.rb
194
195
  - lib/types/private/types/void.rb
@@ -245,7 +246,7 @@ licenses:
245
246
  - Apache-2.0
246
247
  metadata:
247
248
  source_code_uri: https://github.com/sorbet/sorbet
248
- post_install_message:
249
+ post_install_message:
249
250
  rdoc_options: []
250
251
  require_paths:
251
252
  - lib
@@ -253,15 +254,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
253
254
  requirements:
254
255
  - - ">="
255
256
  - !ruby/object:Gem::Version
256
- version: 2.3.0
257
+ version: 2.7.0
257
258
  required_rubygems_version: !ruby/object:Gem::Requirement
258
259
  requirements:
259
260
  - - ">="
260
261
  - !ruby/object:Gem::Version
261
262
  version: '0'
262
263
  requirements: []
263
- rubygems_version: 3.1.4
264
- signing_key:
264
+ rubygems_version: 3.3.7
265
+ signing_key:
265
266
  specification_version: 4
266
267
  summary: Sorbet runtime
267
268
  test_files: []