sorbet-runtime 0.5.10782 → 0.5.10993

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,17 +8,20 @@ class T::Private::Methods::Signature
8
8
  :check_level, :parameters, :on_failure, :override_allow_incompatible,
9
9
  :defined_raw
10
10
 
11
+ UNNAMED_REQUIRED_PARAMETERS = [[:req]].freeze
12
+
11
13
  def self.new_untyped(method:, mode: T::Private::Methods::Modes.untyped, parameters: method.parameters)
12
- # Using `Untyped` ensures we'll get an error if we ever try validation on these.
13
- not_typed = T::Private::Types::NotTyped.new
14
+ # Using `NotTyped` ensures we'll get an error if we ever try validation on these.
15
+ not_typed = T::Private::Types::NotTyped::INSTANCE
14
16
  raw_return_type = not_typed
15
17
  # Map missing parameter names to "argN" positionally
16
18
  parameters = parameters.each_with_index.map do |(param_kind, param_name), index|
17
19
  [param_kind, param_name || "arg#{index}"]
18
20
  end
19
- raw_arg_types = parameters.map do |_param_kind, param_name|
20
- [param_name, not_typed]
21
- end.to_h
21
+ raw_arg_types = {}
22
+ parameters.each do |_, param_name|
23
+ raw_arg_types[param_name] = not_typed
24
+ end
22
25
 
23
26
  self.new(
24
27
  method: method,
@@ -36,8 +39,6 @@ class T::Private::Methods::Signature
36
39
  def initialize(method:, method_name:, raw_arg_types:, raw_return_type:, bind:, mode:, check_level:, on_failure:, parameters: method.parameters, override_allow_incompatible: false, defined_raw: false)
37
40
  @method = method
38
41
  @method_name = method_name
39
- @arg_types = []
40
- @kwarg_types = {}
41
42
  @block_type = nil
42
43
  @block_name = nil
43
44
  @rest_type = nil
@@ -48,8 +49,6 @@ class T::Private::Methods::Signature
48
49
  @bind = bind ? T::Utils.coerce(bind) : bind
49
50
  @mode = mode
50
51
  @check_level = check_level
51
- @req_arg_count = 0
52
- @req_kwarg_names = []
53
52
  @has_rest = false
54
53
  @has_keyrest = false
55
54
  @parameters = parameters
@@ -57,28 +56,40 @@ class T::Private::Methods::Signature
57
56
  @override_allow_incompatible = override_allow_incompatible
58
57
  @defined_raw = defined_raw
59
58
 
60
- declared_param_names = raw_arg_types.keys
59
+ # Use T.untyped in lieu of T.nilable to try to avoid unnecessary allocations.
60
+ arg_types = T.let(nil, T.untyped)
61
+ kwarg_types = T.let(nil, T.untyped)
62
+ req_arg_count = 0
63
+ req_kwarg_names = T.let(nil, T.untyped)
64
+
61
65
  # If sig params are declared but there is a single parameter with a missing name
62
66
  # **and** the method ends with a "=", assume it is a writer method generated
63
67
  # by attr_writer or attr_accessor
64
- writer_method = declared_param_names != [nil] && parameters == [[:req]] && method_name[-1] == "="
68
+ writer_method = !(raw_arg_types.size == 1 && raw_arg_types.key?(nil)) && parameters == UNNAMED_REQUIRED_PARAMETERS && method_name[-1] == "="
65
69
  # For writer methods, map the single parameter to the method name without the "=" at the end
66
70
  parameters = [[:req, method_name[0...-1].to_sym]] if writer_method
67
- param_names = parameters.map {|_, name| name}
68
- missing_names = param_names - declared_param_names
69
- extra_names = declared_param_names - param_names
70
- if !missing_names.empty?
71
+ is_name_missing = parameters.any? {|_, name| !raw_arg_types.key?(name)}
72
+ if is_name_missing
73
+ param_names = parameters.map {|_, name| name}
74
+ missing_names = param_names - raw_arg_types.keys
71
75
  raise "The declaration for `#{method.name}` is missing parameter(s): #{missing_names.join(', ')}"
72
- end
73
- if !extra_names.empty?
74
- raise "The declaration for `#{method.name}` has extra parameter(s): #{extra_names.join(', ')}"
76
+ elsif parameters.length == raw_arg_types.size
77
+ else
78
+ param_names = parameters.map {|_, name| name}
79
+ has_extra_names = parameters.count {|_, name| raw_arg_types.key?(name)} < raw_arg_types.size
80
+ if has_extra_names
81
+ extra_names = raw_arg_types.keys - param_names
82
+ raise "The declaration for `#{method.name}` has extra parameter(s): #{extra_names.join(', ')}"
83
+ end
75
84
  end
76
85
 
77
86
  if parameters.size != raw_arg_types.size
78
87
  raise "The declaration for `#{method.name}` has arguments with duplicate names"
79
88
  end
89
+ i = 0
90
+ raw_arg_types.each do |type_name, raw_type|
91
+ param_kind, param_name = parameters[i]
80
92
 
81
- parameters.zip(raw_arg_types) do |(param_kind, param_name), (type_name, raw_type)|
82
93
  if type_name != param_name
83
94
  hint = ""
84
95
  # Ruby reorders params so that required keyword arguments
@@ -92,15 +103,15 @@ class T::Private::Methods::Signature
92
103
  end
93
104
 
94
105
  raise "Parameter `#{type_name}` is declared out of order (declared as arg number " \
95
- "#{declared_param_names.index(type_name) + 1}, defined in the method as arg number " \
96
- "#{param_names.index(type_name) + 1}).#{hint}\nMethod: #{method_desc}"
106
+ "#{i + 1}, defined in the method as arg number " \
107
+ "#{parameters.index {|_, name| name == type_name} + 1}).#{hint}\nMethod: #{method_desc}"
97
108
  end
98
109
 
99
110
  type = T::Utils.coerce(raw_type)
100
111
 
101
112
  case param_kind
102
113
  when :req
103
- if @arg_types.length > @req_arg_count
114
+ if (arg_types ? arg_types.length : 0) > req_arg_count
104
115
  # Note that this is actually is supported by Ruby, but it would add complexity to
105
116
  # support it here, and I'm happy to discourage its use anyway.
106
117
  #
@@ -111,14 +122,14 @@ class T::Private::Methods::Signature
111
122
  # see this error. The simplest resolution is to rename your method.
112
123
  raise "Required params after optional params are not supported in method declarations. Method: #{method_desc}"
113
124
  end
114
- @arg_types << [param_name, type]
115
- @req_arg_count += 1
125
+ (arg_types ||= []) << [param_name, type]
126
+ req_arg_count += 1
116
127
  when :opt
117
- @arg_types << [param_name, type]
128
+ (arg_types ||= []) << [param_name, type]
118
129
  when :key, :keyreq
119
- @kwarg_types[param_name] = type
130
+ (kwarg_types ||= {})[param_name] = type
120
131
  if param_kind == :keyreq
121
- @req_kwarg_names << param_name
132
+ (req_kwarg_names ||= []) << param_name
122
133
  end
123
134
  when :block
124
135
  @block_name = param_name
@@ -134,7 +145,14 @@ class T::Private::Methods::Signature
134
145
  else
135
146
  raise "Unexpected param_kind: `#{param_kind}`. Method: #{method_desc}"
136
147
  end
148
+
149
+ i += 1
137
150
  end
151
+
152
+ @arg_types = arg_types || EMPTY_LIST
153
+ @kwarg_types = kwarg_types || EMPTY_HASH
154
+ @req_arg_count = req_arg_count
155
+ @req_kwarg_names = req_kwarg_names || EMPTY_LIST
138
156
  end
139
157
 
140
158
  attr_writer :method_name
@@ -179,15 +197,7 @@ class T::Private::Methods::Signature
179
197
  kwargs = EMPTY_HASH
180
198
  end
181
199
 
182
- arg_types = @arg_types
183
-
184
- if @has_rest
185
- rest_count = args_length - @arg_types.length
186
- rest_count = 0 if rest_count.negative?
187
-
188
- arg_types += [[@rest_name, @rest_type]] * rest_count
189
-
190
- elsif (args_length < @req_arg_count) || (args_length > @arg_types.length)
200
+ if !@has_rest && ((args_length < @req_arg_count) || (args_length > @arg_types.length))
191
201
  expected_str = @req_arg_count.to_s
192
202
  if @arg_types.length != @req_arg_count
193
203
  expected_str += "..#{@arg_types.length}"
@@ -197,10 +207,23 @@ class T::Private::Methods::Signature
197
207
 
198
208
  begin
199
209
  it = 0
200
- while it < args_length
201
- yield arg_types[it][0], args[it], arg_types[it][1]
210
+
211
+ # Process given pre-rest args. When there are no rest args,
212
+ # this is just the given number of args.
213
+ while it < args_length && it < @arg_types.length
214
+ yield @arg_types[it][0], args[it], @arg_types[it][1]
202
215
  it += 1
203
216
  end
217
+
218
+ if @has_rest
219
+ rest_count = args_length - @arg_types.length
220
+ rest_count = 0 if rest_count.negative?
221
+
222
+ rest_count.times do
223
+ yield @rest_name, args[it], @rest_type
224
+ it += 1
225
+ end
226
+ end
204
227
  end
205
228
 
206
229
  kwargs.each do |name, val|
@@ -208,6 +231,7 @@ class T::Private::Methods::Signature
208
231
  if !type && @has_keyrest
209
232
  type = @keyrest_type
210
233
  end
234
+
211
235
  yield name, val, type if type
212
236
  end
213
237
  end
@@ -221,5 +245,6 @@ class T::Private::Methods::Signature
221
245
  "#{@method} at #{loc}"
222
246
  end
223
247
 
248
+ EMPTY_LIST = [].freeze
224
249
  EMPTY_HASH = {}.freeze
225
250
  end
@@ -6,14 +6,63 @@ module T::Private::Methods::SignatureValidation
6
6
  Modes = Methods::Modes
7
7
 
8
8
  def self.validate(signature)
9
+ # Constructors in any language are always a bit weird: they're called in a
10
+ # static context, but their bodies are implemented by instance methods. So
11
+ # a mix of the rules that apply to instance methods and class methods
12
+ # apply.
13
+ #
14
+ # In languages like Java and Scala, static methods/companion object methods
15
+ # are never inherited. (In Java it almost looks like you can inherit them,
16
+ # because `Child.static_parent_method` works, but this method is simply
17
+ # resolved statically to `Parent.static_parent_method`). Even though most
18
+ # instance methods overrides have variance checking done, constructors are
19
+ # not treated like this, because static methods are never
20
+ # inherited/overridden, and the constructor can only ever be called
21
+ # indirectly by way of the static method. (Note: this is only a mental
22
+ # model--there's not actually a static method for the constructor in Java,
23
+ # there's an `invokespecial` JVM instruction that handles this).
24
+ #
25
+ # But Ruby is not like Java: singleton class methods in Ruby *are*
26
+ # inherited, unlike static methods in Java. In fact, this is similar to how
27
+ # JavaScript works. TypeScript simply then sidesteps the issue with
28
+ # structural typing: `typeof Parent` is not compatible with `typeof Child`
29
+ # if their constructors are different. (In a nominal type system, simply
30
+ # having Child descend from Parent should be the only factor in determining
31
+ # whether those types are compatible).
32
+ #
33
+ # Flow has nominal subtyping for classes. When overriding (static and
34
+ # instance) methods in a child class, the overrides must satisfy variance
35
+ # constraints. But it still carves out an exception for constructors,
36
+ # because then literally every class would have to have the same
37
+ # constructor. This is simply unsound. Hack does a similar thing--static
38
+ # method overrides are checked, but not constructors. Though what Hack
39
+ # *does* have is a way to opt into override checking for constructors with
40
+ # a special annotation.
41
+ #
42
+ # It turns out, Sorbet already has this special annotation: either
43
+ # `abstract` or `overridable`. At time of writing, *no* static override
44
+ # checking happens unless marked with these keywords (though at runtime, it
45
+ # always happens). Getting the static system to parity with the runtime by
46
+ # always checking overrides would be a great place to get to one day, but
47
+ # for now we can take advantage of it by only doing override checks for
48
+ # constructors if they've opted in.
49
+ #
50
+ # (When we get around to more widely checking overrides statically, we will
51
+ # need to build a matching special case for constructors statically.)
52
+ #
53
+ # Note that this breaks with tradition: normally, constructors are not
54
+ # allowed to be abstract. But that's kind of a side-effect of everything
55
+ # above: in Java/Scala, singleton class methods are never abstract because
56
+ # they're not inherited, and this extends to constructors. TypeScript
57
+ # simply rejects `new klass()` entirely if `klass` is
58
+ # `typeof AbstractClass`, requiring instead that you write
59
+ # `{ new(): AbstractClass }`. We may want to consider building some
60
+ # analogue to `T.class_of` in the future that works like this `{new():
61
+ # ...}` type.
9
62
  if signature.method_name == :initialize && signature.method.owner.is_a?(Class)
10
- # Constructors are special. They look like overrides in terms of a super_method existing,
11
- # but in practice, you never call them polymorphically. Conceptually, they're standard
12
- # methods (this is consistent with how they're treated in other languages, e.g. Java)
13
- if signature.mode != Modes.standard
14
- raise "`initialize` should not use `.abstract` or `.implementation` or any other inheritance modifiers."
63
+ if signature.mode == Modes.standard
64
+ return
15
65
  end
16
- return
17
66
  end
18
67
 
19
68
  super_method = signature.method.super_method
@@ -20,4 +20,6 @@ class T::Private::Types::NotTyped < T::Types::Base
20
20
  private def subtype_of_single?(other)
21
21
  raise ERROR_MESSAGE
22
22
  end
23
+
24
+ INSTANCE = ::T::Private::Types::NotTyped.new.freeze
23
25
  end
@@ -17,7 +17,7 @@ class T::Props::Decorator
17
17
 
18
18
  class NoRulesError < StandardError; end
19
19
 
20
- EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules])
20
+ EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules], checked: false)
21
21
  private_constant :EMPTY_PROPS
22
22
 
23
23
  sig {params(klass: T.untyped).void.checked(:never)}
@@ -26,7 +26,7 @@ class T::Props::Decorator
26
26
  @class.plugins.each do |mod|
27
27
  T::Props::Plugin::Private.apply_decorator_methods(mod, self)
28
28
  end
29
- @props = T.let(EMPTY_PROPS, T::Hash[Symbol, Rules])
29
+ @props = T.let(EMPTY_PROPS, T::Hash[Symbol, Rules], checked: false)
30
30
  end
31
31
 
32
32
  # checked(:never) - O(prop accesses)
@@ -50,9 +50,9 @@ class T::Props::Decorator
50
50
  override = rules.delete(:override)
51
51
 
52
52
  if props.include?(prop) && !override
53
- raise ArgumentError.new("Attempted to redefine prop #{prop.inspect} that's already defined without specifying :override => true: #{prop_rules(prop)}")
53
+ raise ArgumentError.new("Attempted to redefine prop #{prop.inspect} on class #{@class} that's already defined without specifying :override => true: #{prop_rules(prop)}")
54
54
  elsif !props.include?(prop) && override
55
- raise ArgumentError.new("Attempted to override a prop #{prop.inspect} that doesn't already exist")
55
+ raise ArgumentError.new("Attempted to override a prop #{prop.inspect} on class #{@class} that doesn't already exist")
56
56
  end
57
57
 
58
58
  @props = @props.merge(prop => rules.freeze).freeze
@@ -79,7 +79,7 @@ class T::Props::Decorator
79
79
  extra
80
80
  setter_validate
81
81
  _tnilable
82
- ].map {|k| [k, true]}.to_h.freeze, T::Hash[Symbol, T::Boolean])
82
+ ].to_h {|k| [k, true]}.freeze, T::Hash[Symbol, T::Boolean], checked: false)
83
83
  private_constant :VALID_RULE_KEYS
84
84
 
85
85
  sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
@@ -205,7 +205,7 @@ class T::Props::Decorator
205
205
  end
206
206
 
207
207
  # TODO: we should really be checking all the methods on `cls`, not just Object
208
- BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) {|x, acc| acc[x] = true}.freeze, T::Hash[Symbol, TrueClass])
208
+ BANNED_METHOD_NAMES = T.let(Object.instance_methods.each_with_object({}) {|x, acc| acc[x] = true}.freeze, T::Hash[Symbol, TrueClass], checked: false)
209
209
 
210
210
  # checked(:never) - Rules hash is expensive to check
211
211
  sig do
@@ -247,10 +247,10 @@ class T::Props::Decorator
247
247
  nil
248
248
  end
249
249
 
250
- SAFE_NAME = T.let(/\A[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp)
250
+ SAFE_NAME = T.let(/\A[A-Za-z_][A-Za-z0-9_-]*\z/.freeze, Regexp, checked: false)
251
251
 
252
252
  # Used to validate both prop names and serialized forms
253
- sig {params(name: T.any(Symbol, String)).void}
253
+ sig {params(name: T.any(Symbol, String)).void.checked(:never)}
254
254
  private def validate_prop_name(name)
255
255
  if !name.match?(SAFE_NAME)
256
256
  raise ArgumentError.new("Invalid prop name in #{@class.name}: #{name}")
@@ -258,7 +258,7 @@ class T::Props::Decorator
258
258
  end
259
259
 
260
260
  # This converts the type from a T::Type to a regular old ruby class.
261
- sig {params(type: T::Types::Base).returns(Module)}
261
+ sig {params(type: T::Types::Base).returns(Module).checked(:never)}
262
262
  private def convert_type_to_class(type)
263
263
  case type
264
264
  when T::Types::TypedArray, T::Types::FixedArray
@@ -338,34 +338,34 @@ class T::Props::Decorator
338
338
  # Retrive the possible underlying object with T.nilable.
339
339
  type = T::Utils::Nilable.get_underlying_type(type)
340
340
 
341
- sensitivity_and_pii = {sensitivity: rules[:sensitivity]}
342
- normalize = T::Configuration.normalize_sensitivity_and_pii_handler
343
- if normalize
344
- sensitivity_and_pii = normalize.call(sensitivity_and_pii)
345
-
346
- # We check for Class so this is only applied on concrete
347
- # documents/models; We allow mixins containing props to not
348
- # specify their PII nature, as long as every class into which they
349
- # are ultimately included does.
350
- #
351
- if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !T.unsafe(@class).contains_pii?
352
- raise ArgumentError.new(
353
- 'Cannot include a pii prop in a class that declares `contains_no_pii`'
354
- )
341
+ rules_sensitivity = rules[:sensitivity]
342
+ sensitivity_and_pii = {sensitivity: rules_sensitivity}
343
+ if !rules_sensitivity.nil?
344
+ normalize = T::Configuration.normalize_sensitivity_and_pii_handler
345
+ if normalize
346
+ sensitivity_and_pii = normalize.call(sensitivity_and_pii)
347
+
348
+ # We check for Class so this is only applied on concrete
349
+ # documents/models; We allow mixins containing props to not
350
+ # specify their PII nature, as long as every class into which they
351
+ # are ultimately included does.
352
+ #
353
+ if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !T.unsafe(@class).contains_pii?
354
+ raise ArgumentError.new(
355
+ 'Cannot include a pii prop in a class that declares `contains_no_pii`'
356
+ )
357
+ end
355
358
  end
356
359
  end
357
360
 
358
- rules = rules.merge(
359
- # TODO: The type of this element is confusing. We should refactor so that
360
- # it can be always `type_object` (a PropType) or always `cls` (a Module)
361
- type: type,
362
- type_object: type_object,
363
- accessor_key: "@#{name}".to_sym,
364
- sensitivity: sensitivity_and_pii[:sensitivity],
365
- pii: sensitivity_and_pii[:pii],
366
- # extra arbitrary metadata attached by the code defining this property
367
- extra: rules[:extra]&.freeze,
368
- )
361
+ rules[:type] = type
362
+ rules[:type_object] = type_object
363
+ rules[:accessor_key] = "@#{name}".to_sym
364
+ rules[:sensitivity] = sensitivity_and_pii[:sensitivity]
365
+ rules[:pii] = sensitivity_and_pii[:pii]
366
+ rules[:extra] = rules[:extra]&.freeze
367
+
368
+ # extra arbitrary metadata attached by the code defining this property
369
369
 
370
370
  validate_not_missing_sensitivity(name, rules)
371
371
 
@@ -419,6 +419,7 @@ class T::Props::Decorator
419
419
  sig do
420
420
  params(type: PropTypeOrClass, enum: T.untyped)
421
421
  .returns(T::Types::Base)
422
+ .checked(:never)
422
423
  end
423
424
  private def smart_coerce(type, enum:)
424
425
  # Backwards compatibility for pre-T::Types style
@@ -471,6 +472,7 @@ class T::Props::Decorator
471
472
  redaction: T.untyped,
472
473
  )
473
474
  .void
475
+ .checked(:never)
474
476
  end
475
477
  private def handle_redaction_option(prop_name, redaction)
476
478
  redacted_method = "#{prop_name}_redacted"
@@ -492,6 +494,7 @@ class T::Props::Decorator
492
494
  valid_type_msg: String,
493
495
  )
494
496
  .void
497
+ .checked(:never)
495
498
  end
496
499
  private def validate_foreign_option(option_sym, foreign, valid_type_msg:)
497
500
  if foreign.is_a?(Symbol) || foreign.is_a?(String)
@@ -523,8 +526,8 @@ class T::Props::Decorator
523
526
  # here, but we're baking in `allow_direct_mutation` since we
524
527
  # *haven't* allowed additional options in the past and want to
525
528
  # default to keeping this interface narrow.
529
+ foreign = T.let(foreign, T.untyped, checked: false)
526
530
  @class.send(:define_method, fk_method) do |allow_direct_mutation: nil|
527
- foreign = T.let(foreign, T.untyped)
528
531
  if foreign.is_a?(Proc)
529
532
  resolved_foreign = foreign.call
530
533
  if !resolved_foreign.respond_to?(:load)
@@ -117,7 +117,7 @@ module T::Props
117
117
  def eagerly_define_lazy_methods!
118
118
  return if lazily_defined_methods.empty?
119
119
 
120
- source = lazily_defined_methods.values.map(&:call).map(&:to_s).join("\n\n")
120
+ source = "# frozen_string_literal: true\n" + lazily_defined_methods.values.map(&:call).map(&:to_s).join("\n\n")
121
121
 
122
122
  cls = decorated_class
123
123
  cls.class_eval(source)
@@ -176,7 +176,7 @@ module T::Props
176
176
  base_message = "Can't set #{klass.name}.#{prop} to #{val.inspect} (instance of #{val.class}) - need a #{type}"
177
177
 
178
178
  pretty_message = "Parameter '#{prop}': #{base_message}\n"
179
- caller_loc = caller_locations&.find {|l| !l.to_s.include?('sorbet-runtime/lib/types/props')}
179
+ caller_loc = caller_locations.find {|l| !l.to_s.include?('sorbet-runtime/lib/types/props')}
180
180
  if caller_loc
181
181
  pretty_message += "Caller: #{caller_loc.path}:#{caller_loc.lineno}\n"
182
182
  end
@@ -16,6 +16,7 @@ module T::Props::TypeValidation
16
16
  super || key == :DEPRECATED_underspecified_type
17
17
  end
18
18
 
19
+ # checked(:never) - Rules hash is expensive to check
19
20
  sig do
20
21
  params(
21
22
  name: T.any(Symbol, String),
@@ -24,12 +25,13 @@ module T::Props::TypeValidation
24
25
  type: T.any(T::Types::Base, Module)
25
26
  )
26
27
  .void
28
+ .checked(:never)
27
29
  end
28
30
  def prop_validate_definition!(name, _cls, rules, type)
29
31
  super
30
32
 
31
33
  if !rules[:DEPRECATED_underspecified_type]
32
- validate_type(type, field_name: name)
34
+ validate_type(type, name)
33
35
  elsif rules[:DEPRECATED_underspecified_type] && find_invalid_subtype(type).nil?
34
36
  raise ArgumentError.new("DEPRECATED_underspecified_type set unnecessarily for #{@class.name}.#{name} - #{type} is a valid type")
35
37
  end
@@ -41,8 +43,9 @@ module T::Props::TypeValidation
41
43
  field_name: T.any(Symbol, String),
42
44
  )
43
45
  .void
46
+ .checked(:never)
44
47
  end
45
- private def validate_type(type, field_name:)
48
+ private def validate_type(type, field_name)
46
49
  if (invalid_subtype = find_invalid_subtype(type))
47
50
  raise UnderspecifiedType.new(type_error_message(invalid_subtype, field_name, type))
48
51
  end
data/lib/types/struct.rb CHANGED
@@ -10,7 +10,7 @@ end
10
10
  class T::Struct < T::InexactStruct
11
11
  def self.inherited(subclass)
12
12
  super(subclass)
13
- T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited) do |s|
13
+ T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited, true) do |s|
14
14
  super(s)
15
15
  raise "#{self.name} is a subclass of T::Struct and cannot be subclassed"
16
16
  end
@@ -23,7 +23,7 @@ class T::ImmutableStruct < T::InexactStruct
23
23
  def self.inherited(subclass)
24
24
  super(subclass)
25
25
 
26
- T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited) do |s|
26
+ T::Private::ClassUtils.replace_method(subclass.singleton_class, :inherited, true) do |s|
27
27
  super(s)
28
28
  raise "#{self.name} is a subclass of T::ImmutableStruct and cannot be subclassed"
29
29
  end
@@ -27,6 +27,8 @@ module T::Types
27
27
  @type <= other.type
28
28
  when Simple
29
29
  @type.is_a?(other.raw_type)
30
+ when TypedClass
31
+ true
30
32
  else
31
33
  false
32
34
  end
@@ -36,5 +38,14 @@ module T::Types
36
38
  def describe_obj(obj)
37
39
  obj.inspect
38
40
  end
41
+
42
+ # So that `T.class_of(...)[...]` syntax is valid.
43
+ # Mirrors the definition of T::Generic#[] (generics are erased).
44
+ #
45
+ # We avoid simply writing `include T::Generic` because we don't want any of
46
+ # the other methods to appear (`T.class_of(A).type_member` doesn't make sense)
47
+ def [](*types)
48
+ self
49
+ end
39
50
  end
40
51
  end
@@ -4,6 +4,9 @@
4
4
  module T::Types
5
5
  # Validates that an object belongs to the specified class.
6
6
  class Simple < Base
7
+ NAME_METHOD = Module.instance_method(:name)
8
+ private_constant(:NAME_METHOD)
9
+
7
10
  attr_reader :raw_type
8
11
 
9
12
  def initialize(raw_type)
@@ -16,7 +19,7 @@ module T::Types
16
19
  #
17
20
  # `name` isn't normally a hot path for types, but it is used in initializing a T::Types::Union,
18
21
  # and so in `T.nilable`, and so in runtime constructions like `x = T.let(nil, T.nilable(Integer))`.
19
- @name ||= @raw_type.name.freeze
22
+ @name ||= (NAME_METHOD.bind(@raw_type).call || @raw_type.name).freeze
20
23
  end
21
24
 
22
25
  # overrides Base
@@ -29,6 +32,11 @@ module T::Types
29
32
  case other
30
33
  when Simple
31
34
  @raw_type <= other.raw_type
35
+ when TypedClass
36
+ # This case is a bit odd--we would have liked to solve this like we do
37
+ # for `T::Array` et al., but don't for backwards compatibility.
38
+ # See `type_for_module` below.
39
+ @raw_type <= other.underlying_class
32
40
  else
33
41
  false
34
42
  end
@@ -86,6 +94,9 @@ module T::Types
86
94
  elsif !Object.autoload?(:Set) && Object.const_defined?(:Set) && mod == ::Set
87
95
  T::Set[T.untyped]
88
96
  else
97
+ # ideally we would have a case mapping from ::Class -> T::Class here
98
+ # but for backwards compatibility we don't have that, and instead
99
+ # have a special case in subtype_of_single?
89
100
  Simple.new(mod)
90
101
  end
91
102
 
@@ -3,11 +3,30 @@
3
3
 
4
4
  module T::Types
5
5
  class TypeParameter < Base
6
+ module Private
7
+ @pool = {}
8
+
9
+ def self.cached_entry(name)
10
+ @pool[name]
11
+ end
12
+
13
+ def self.set_entry_for(name, type)
14
+ @pool[name] = type
15
+ end
16
+ end
17
+
6
18
  def initialize(name)
7
19
  raise ArgumentError.new("not a symbol: #{name}") unless name.is_a?(Symbol)
8
20
  @name = name
9
21
  end
10
22
 
23
+ def self.make(name)
24
+ cached = Private.cached_entry(name)
25
+ return cached if cached
26
+
27
+ Private.set_entry_for(name, new(name))
28
+ end
29
+
11
30
  def valid?(obj)
12
31
  true
13
32
  end