Module: T::Private::Methods::SignatureValidation

Defined in:
lib/types/private/methods/signature_validation.rb

Overview

typed: true

Constant Summary collapse

Methods =
T::Private::Methods
Modes =
Methods::Modes

Class Method Summary collapse

Class Method Details

.validate(signature) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/types/private/methods/signature_validation.rb', line 8

def self.validate(signature)
  # Constructors in any language are always a bit weird: they're called in a
  # static context, but their bodies are implemented by instance methods. So
  # a mix of the rules that apply to instance methods and class methods
  # apply.
  #
  # In languages like Java and Scala, static methods/companion object methods
  # are never inherited. (In Java it almost looks like you can inherit them,
  # because `Child.static_parent_method` works, but this method is simply
  # resolved statically to `Parent.static_parent_method`). Even though most
  # instance methods overrides have variance checking done, constructors are
  # not treated like this, because static methods are never
  # inherited/overridden, and the constructor can only ever be called
  # indirectly by way of the static method. (Note: this is only a mental
  # model--there's not actually a static method for the constructor in Java,
  # there's an `invokespecial` JVM instruction that handles this).
  #
  # But Ruby is not like Java: singleton class methods in Ruby *are*
  # inherited, unlike static methods in Java. In fact, this is similar to how
  # JavaScript works. TypeScript simply then sidesteps the issue with
  # structural typing: `typeof Parent` is not compatible with `typeof Child`
  # if their constructors are different. (In a nominal type system, simply
  # having Child descend from Parent should be the only factor in determining
  # whether those types are compatible).
  #
  # Flow has nominal subtyping for classes. When overriding (static and
  # instance) methods in a child class, the overrides must satisfy variance
  # constraints. But it still carves out an exception for constructors,
  # because then literally every class would have to have the same
  # constructor. This is simply unsound. Hack does a similar thing--static
  # method overrides are checked, but not constructors. Though what Hack
  # *does* have is a way to opt into override checking for constructors with
  # a special annotation.
  #
  # It turns out, Sorbet already has this special annotation: either
  # `abstract` or `overridable`. At time of writing, *no* static override
  # checking happens unless marked with these keywords (though at runtime, it
  # always happens). Getting the static system to parity with the runtime by
  # always checking overrides would be a great place to get to one day, but
  # for now we can take advantage of it by only doing override checks for
  # constructors if they've opted in.
  #
  # (When we get around to more widely checking overrides statically, we will
  # need to build a matching special case for constructors statically.)
  #
  # Note that this breaks with tradition: normally, constructors are not
  # allowed to be abstract. But that's kind of a side-effect of everything
  # above: in Java/Scala, singleton class methods are never abstract because
  # they're not inherited, and this extends to constructors. TypeScript
  # simply rejects `new klass()` entirely if `klass` is
  # `typeof AbstractClass`, requiring instead that you write
  # `{ new(): AbstractClass }`. We may want to consider building some
  # analogue to `T.class_of` in the future that works like this `{new():
  # ...}` type.
  if signature.method_name == :initialize && signature.method.owner.is_a?(Class) &&
      signature.mode == Modes.standard
    return
  end

  super_method = signature.method.super_method

  if super_method && super_method.owner != signature.method.owner
    # No need to run the sig block for super_method explicitly:
    # signature_for_method forces it internally (via signature_for_key ->
    # maybe_run_sig_block_for_key on the identical registry key).
    super_signature = Methods.signature_for_method(super_method)

    # If the super_method has any kwargs we can't build a
    # Signature for it, so we'll just skip validation in that case.
    if !super_signature && !super_method.parameters.select { |kind, _| kind == :rest || kind == :kwrest }.empty?
      nil
    else
      # super_signature can be nil when we're overriding a method (perhaps a builtin) that didn't use
      # one of the method signature helpers. Use an untyped signature so we can still validate
      # everything but types.
      #
      # We treat these signatures as overridable, that way people can use `.override` with
      # overrides of builtins. In the future we could try to distinguish when the method is a
      # builtin and treat non-builtins as non-overridable (so you'd be forced to declare them with
      # `.overridable`).
      #
      super_signature ||= Methods::Signature.new_untyped(method: super_method)

      validate_override_mode(signature, super_signature)
      validate_override_shape(signature, super_signature)
      validate_override_types(signature, super_signature)
      validate_override_visibility(signature, super_signature)
    end
  else
    validate_non_override_mode(signature.mode, signature.method_name, signature.method)
  end
end

.validate_non_override_mode(mode, method_name, method, source_loc = method.source_location) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/types/private/methods/signature_validation.rb', line 143

def self.validate_non_override_mode(mode, method_name, method, source_loc=method.source_location)
  case mode
  when Modes.override
    if method_name == :each && method.owner < Enumerable
      # Enumerable#each is the only method in Sorbet's RBI payload that defines an abstract method.
      # Enumerable#each does not actually exist at runtime, but it is required to be implemented by
      # any class which includes Enumerable. We want to declare Enumerable#each as abstract so that
      # people can call it anything which implements the Enumerable interface, and so that it's a
      # static error to forget to implement it.
      #
      # This is a one-off hack, and we should think carefully before adding more methods here.
      nil
    else
      raise "You marked `#{method_name}` as #{pretty_mode(mode)}, but that method doesn't already exist in this class/module to be overridden.\n" \
        "  Either check for typos and for missing includes or super classes to make the parent method shows up\n" \
        "  ... or remove #{pretty_mode(mode)} here: #{T::Private::Methods::Signature.method_desc(method, method_name, source_loc)}\n"
    end
  when Modes.standard, *Modes::NON_OVERRIDE_MODES
    # Peaceful
    nil
  else
    raise "Unexpected mode: #{mode}. Please report this bug at https://github.com/sorbet/sorbet/issues"
  end

  # Given a singleton class, we can check if it belongs to a
  # module by looking at its superclass; given `module M`,
  # `M.singleton_class.superclass == Module`, which is not true
  # for any class.
  owner = method.owner
  if (mode == Modes.abstract || Modes::OVERRIDABLE_MODES.include?(mode)) &&
      owner.singleton_class? && Class === owner && owner.superclass == Module
    raise "Defining an overridable class method (via #{pretty_mode(mode)}) " \
          "on a module is not allowed. Class methods on " \
          "modules do not get inherited and thus cannot be overridden."
  end
end

.validate_override_mode(signature, super_signature) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/types/private/methods/signature_validation.rb', line 109

def self.validate_override_mode(signature, super_signature)
  case signature.mode
  when *Modes::OVERRIDE_MODES
    # Peaceful
  when Modes.abstract
    # Either the parent method is abstract, or it's not.
    #
    # If it's abstract, we want to allow overriding abstract with abstract to
    # possibly narrow the type or provide more specific documentation.
    #
    # If it's not, then marking this method `abstract` will silently be a no-op.
    # That's bad and we probably want to report an error, but fixing that
    # will have to be a separate fix (that bad behavior predates this current
    # comment, introduced when we fixed the abstract/abstract case).
    #
    # Therefore:
    # Peaceful (mostly)
  when *Modes::NON_OVERRIDE_MODES
    if super_signature.mode == Modes.standard
      # Peaceful
    elsif super_signature.mode == Modes.abstract
      raise "You must use `.override` when overriding the abstract method `#{signature.method_name}`.\n" \
            "  Abstract definition: #{super_signature.method_desc}\n" \
            "  Implementation definition: #{signature.method_desc}\n"
    elsif super_signature.mode != Modes.untyped
      raise "You must use `.override` when overriding the existing method `#{signature.method_name}`.\n" \
            "  Parent definition: #{super_signature.method_desc}\n" \
            "  Child definition:  #{signature.method_desc}\n"
    end
  else
    raise "Unexpected mode: #{signature.mode}. Please report this bug at https://github.com/sorbet/sorbet/issues"
  end
end

.validate_override_shape(signature, super_signature) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/types/private/methods/signature_validation.rb', line 180

def self.validate_override_shape(signature, super_signature)
  return if signature.override_allow_incompatible == true
  return if super_signature.mode == Modes.untyped

  method_name = signature.method_name
  mode_verb = super_signature.mode == Modes.abstract ? 'implements' : 'overrides'

  if signature.rest_type.nil? && signature.arg_count < super_signature.arg_count
    raise "Your definition of `#{method_name}` must accept at least #{super_signature.arg_count} " \
          "positional arguments to be compatible with the method it #{mode_verb}: " \
          "#{base_override_loc_str(signature, super_signature)}"
  end

  if signature.rest_type.nil? && !super_signature.rest_type.nil?
    raise "Your definition of `#{method_name}` must have `*#{super_signature.rest_name}` " \
          "to be compatible with the method it #{mode_verb}: " \
          "#{base_override_loc_str(signature, super_signature)}"
  end

  if signature.req_arg_count > super_signature.req_arg_count
    raise "Your definition of `#{method_name}` must have no more than #{super_signature.req_arg_count} " \
          "required argument(s) to be compatible with the method it #{mode_verb}: " \
          "#{base_override_loc_str(signature, super_signature)}"
  end

  # The kwarg_types.empty? guard is an exact implication (an empty super
  # kwarg set can never yield missing kwargs) that skips two fresh
  # `.keys` arrays plus the Array#- for the common kwarg-free method.
  if signature.keyrest_type.nil? && !super_signature.kwarg_types.empty?
    # O(nm), but n and m are tiny here
    missing_kwargs = super_signature.kwarg_names - signature.kwarg_names
    if !missing_kwargs.empty?
      raise "Your definition of `#{method_name}` is missing these keyword arg(s): #{missing_kwargs} " \
            "which are defined in the method it #{mode_verb}: " \
            "#{base_override_loc_str(signature, super_signature)}"
    end
  end

  if signature.keyrest_type.nil? && !super_signature.keyrest_type.nil?
    raise "Your definition of `#{method_name}` must have `**#{super_signature.keyrest_name}` " \
          "to be compatible with the method it #{mode_verb}: " \
          "#{base_override_loc_str(signature, super_signature)}"
  end

  # Guard on the minuend: an empty req_kwarg_names can never yield extras.
  if !signature.req_kwarg_names.empty?
    # O(nm), but n and m are tiny here
    extra_req_kwargs = signature.req_kwarg_names - super_signature.req_kwarg_names
    if !extra_req_kwargs.empty?
      raise "Your definition of `#{method_name}` has extra required keyword arg(s) " \
            "#{extra_req_kwargs} relative to the method it #{mode_verb}, making it incompatible: " \
            "#{base_override_loc_str(signature, super_signature)}"
    end
  end

  if super_signature.block_name && !signature.block_name
    raise "Your definition of `#{method_name}` must accept a block parameter to be compatible " \
          "with the method it #{mode_verb}: " \
          "#{base_override_loc_str(signature, super_signature)}"
  end
end

.validate_override_types(signature, super_signature) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/types/private/methods/signature_validation.rb', line 249

def self.validate_override_types(signature, super_signature)
  return if signature.override_allow_incompatible == true
  return if super_signature.mode == Modes.untyped
  return unless check_level_active?(signature) && check_level_active?(super_signature)
  mode_noun = super_signature.mode == Modes.abstract ? 'implementation' : 'override'

  # arg types must be contravariant
  #
  # An index loop avoids allocating a pair array per positional arg.
  # Iterating to super's length is deliberate: extra override positionals
  # go unchecked, and when the override has a rest param and fewer named
  # args than the base, the missing positions are checked as nil name/type.
  super_arg_types = super_signature.arg_types
  arg_types = signature.arg_types
  index = 0
  while index < super_arg_types.length
    super_type = super_arg_types.fetch(index)[1]
    pair = arg_types[index]
    name = pair && pair[0]
    type = pair && pair[1]
    if !super_type.subtype_of?(type)
      raise "Incompatible type for arg ##{index + 1} (`#{name}`) in signature for #{mode_noun} of method " \
            "`#{signature.method_name}`:\n" \
            "* Base: `#{super_type}` (in #{super_signature.method_desc})\n" \
            "* #{mode_noun.capitalize}: `#{type}` (in #{signature.method_desc})\n" \
            "(The types must be contravariant.)"
    end
    index += 1
  end

  # kwarg types must be contravariant
  super_signature.kwarg_types.each do |name, super_type|
    type = signature.kwarg_types[name]
    if !super_type.subtype_of?(type)
      raise "Incompatible type for arg `#{name}` in signature for #{mode_noun} of method `#{signature.method_name}`:\n" \
            "* Base: `#{super_type}` (in #{super_signature.method_desc})\n" \
            "* #{mode_noun.capitalize}: `#{type}` (in #{signature.method_desc})\n" \
            "(The types must be contravariant.)"
    end
  end

  # return types must be covariant
  super_signature_return_type = super_signature.effective_return_type

  if super_signature_return_type == T::Private::Types::Void::Private::INSTANCE
    # Treat `.void` as `T.anything` (see corresponding comment in definition_valitor for more)
    super_signature_return_type = T::Types::Anything::Private::INSTANCE
  end

  if !signature.effective_return_type.subtype_of?(super_signature_return_type)
    raise "Incompatible return type in signature for #{mode_noun} of method `#{signature.method_name}`:\n" \
          "* Base: `#{super_signature.effective_return_type}` (in #{super_signature.method_desc})\n" \
          "* #{mode_noun.capitalize}: `#{signature.effective_return_type}` (in #{signature.method_desc})\n" \
          "(The types must be covariant.)"
  end
end

.validate_override_visibility(signature, super_signature) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/types/private/methods/signature_validation.rb', line 309

def self.validate_override_visibility(signature, super_signature)
  return if super_signature.mode == Modes.untyped
  # This departs from the behavior of other `validate_override_whatever` functions in that it
  # only comes into effect when the child signature explicitly says the word `override`. This was
  # done because the primary method for silencing these errors (`allow_incompatible: :visibility`)
  # requires an `override` node to attach to. Once we have static override checking for implicitly
  # overridden methods, we can remove this.
  return unless Modes::OVERRIDE_MODES.include?(signature.mode)
  return if ALLOW_INCOMPATIBLE_VISIBILITY.include?(signature.override_allow_incompatible)
  method = signature.method
  super_method = super_signature.method
  mode_noun = super_signature.mode == Modes.abstract ? 'implementation' : 'override'
  vis = method_visibility(method)
  super_vis = method_visibility(super_method)

  if visibility_strength(vis) > visibility_strength(super_vis)
    raise "Incompatible visibility for #{mode_noun} of method #{method.name}\n" \
          "* Base: #{super_vis} (in #{super_signature.method_desc})\n" \
          "* #{mode_noun.capitalize}: #{vis} (in #{signature.method_desc})\n" \
          "(The override must be at least as permissive as the supermethod)" \
  end
end