sorbet-runtime 0.5.6198 → 0.5.6539

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: b9ad14907c264d6ca3dae954f1bf9cdfdff0544411fad7a390fcb424465bf828
4
- data.tar.gz: f02410f71f442e62a635881ec386a23911f1cb1a4d4c5eae77693d1a10a0695d
3
+ metadata.gz: 3594dc0c726d2a7189c7544226a6ced4240f4a6df2c18bdae635cc30bb223e7b
4
+ data.tar.gz: dae60008d7f685ecdf231edefdf0b01b595c597c6ae646e7ba7070eada079fa2
5
5
  SHA512:
6
- metadata.gz: 1bb53ba112fb1793b4e138e1a65ed648e2e202af6e8ec79be17b444c11c02a63a7c385b0abb474e38dca78a03ad91f02149ff24b7c5f622f7d1943c4d4c846b1
7
- data.tar.gz: bf301214903ea532fb931314c823f68a3017a2de5868f32d074a642664c4cf266864201d9ae4a62573a9713bb888d132cb0aadd23c77aaf628b8d443163bae36
6
+ metadata.gz: 519bf1b1c4f04792d88c817a9bb3a213b01e783a96597e1e328a8a5c9736e78ff7a0a93ef31d56787fd9468d09980e4670edffcd71f59ff299cc7a09a80a714b
7
+ data.tar.gz: 0150d316fb26d07fd0693d3b948dfaa6fe1979053dc12f9811c5c9c6d29b711affaa3e6be16a34a08fd6abf5a1db816b4872b7b655895353cdf65094f7e0ad5c
@@ -19,7 +19,6 @@ require 'set'
19
19
 
20
20
  # These are pre-reqs for almost everything in here.
21
21
  require_relative 'types/configuration'
22
- require_relative 'types/profile'
23
22
  require_relative 'types/_types'
24
23
  require_relative 'types/private/decl_state'
25
24
  require_relative 'types/private/class_utils'
data/lib/types/_types.rb CHANGED
@@ -147,6 +147,25 @@ module T
147
147
  Private::Casts.cast(value, type, cast_method: "T.let")
148
148
  end
149
149
 
150
+ # Tells the type checker to treat `self` in the current block as `type`.
151
+ # Useful for blocks that are captured and executed later with instance_exec.
152
+ # Use like:
153
+ #
154
+ # seconds = lambda do
155
+ # T.bind(self, NewBinding)
156
+ # ...
157
+ # end
158
+ #
159
+ # `T.bind` behaves like `T.cast` in that it is assumed to be true statically.
160
+ #
161
+ # If `checked` is true, raises an exception at runtime if the value
162
+ # doesn't match the type (this is the default).
163
+ def self.bind(value, type, checked: true)
164
+ return value unless checked
165
+
166
+ Private::Casts.cast(value, type, cast_method: "T.bind")
167
+ end
168
+
150
169
  # Tells the typechecker to ensure that `value` is of type `type` (if not, the typechecker will
151
170
  # fail). Use this for debugging typechecking errors, or to ensure that type information is
152
171
  # statically known and being checked appropriately. If `checked` is true, raises an exception at
@@ -2,6 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module T::Configuration
5
+ # Cache this comparisonn to avoid two allocations all over the place.
6
+ AT_LEAST_RUBY_2_7 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
7
+
5
8
  # Announces to Sorbet that we are currently in a test environment, so it
6
9
  # should treat any sigs which are marked `.checked(:tests)` as if they were
7
10
  # just a normal sig.
@@ -70,6 +73,37 @@ module T::Configuration
70
73
  @include_value_in_type_errors = true
71
74
  end
72
75
 
76
+ # Whether VM-defined prop serialization/deserialization routines can be enabled.
77
+ #
78
+ # @return [T::Boolean]
79
+ def self.can_enable_vm_prop_serde?
80
+ T::Props::Private::DeserializerGenerator.respond_to?(:generate2)
81
+ end
82
+
83
+ # Whether to use VM-defined prop serialization/deserialization routines.
84
+ #
85
+ # The default is to use runtime codegen inside sorbet-runtime itself.
86
+ #
87
+ # @return [T::Boolean]
88
+ def self.use_vm_prop_serde?
89
+ @use_vm_prop_serde || false
90
+ end
91
+
92
+ # Enable using VM-defined prop serialization/deserialization routines.
93
+ #
94
+ # This method is likely to break things outside of Stripe's systems.
95
+ def self.enable_vm_prop_serde
96
+ if !can_enable_vm_prop_serde?
97
+ hard_assert_handler('Ruby VM is not setup to use VM-defined prop serde')
98
+ end
99
+ @use_vm_prop_serde = true
100
+ end
101
+
102
+ # Disable using VM-defined prop serialization/deserialization routines.
103
+ def self.disable_vm_prop_serde
104
+ @use_vm_prop_serde = false
105
+ end
106
+
73
107
  # Configure the default checked level for a sig with no explicit `.checked`
74
108
  # builder. When unset, the default checked level is `:always`.
75
109
  #
@@ -395,7 +429,12 @@ module T::Configuration
395
429
  MODULE_NAME = Module.instance_method(:name)
396
430
  private_constant :MODULE_NAME
397
431
 
398
- @default_module_name_mangler = ->(type) {MODULE_NAME.bind(type).call}
432
+ if T::Configuration::AT_LEAST_RUBY_2_7
433
+ @default_module_name_mangler = ->(type) {MODULE_NAME.bind_call(type)}
434
+ else
435
+ @default_module_name_mangler = ->(type) {MODULE_NAME.bind(type).call}
436
+ end
437
+
399
438
  @module_name_mangler = nil
400
439
 
401
440
  def self.module_name_mangler
@@ -412,6 +451,48 @@ module T::Configuration
412
451
  @module_name_mangler = handler
413
452
  end
414
453
 
454
+ # Set to a PII handler function. This will be called with the `sensitivity:`
455
+ # annotations on things that use `T::Props` and can modify them ahead-of-time.
456
+ #
457
+ # @param [Lambda, Proc, nil] value Proc that takes a hash mapping symbols to the
458
+ # prop values. Pass nil to avoid changing `sensitivity:` annotations.
459
+ def self.normalize_sensitivity_and_pii_handler=(handler)
460
+ @sensitivity_and_pii_handler = handler
461
+ end
462
+
463
+ def self.normalize_sensitivity_and_pii_handler
464
+ @sensitivity_and_pii_handler
465
+ end
466
+
467
+ @redaction_handler = nil
468
+ # Set to a redaction handling function. This will be called when the
469
+ # `_redacted` version of a prop reader is used. By default this is set to
470
+ # `nil` and will raise an exception when the redacted version of a prop is
471
+ # accessed.
472
+ #
473
+ # @param [Lambda, Proc, nil] value Proc that converts a value into its
474
+ # redacted version according to the spec passed as the second argument.
475
+ def self.redaction_handler=(handler)
476
+ @redaction_handler = handler
477
+ end
478
+
479
+ def self.redaction_handler
480
+ @redaction_handler
481
+ end
482
+
483
+ # Set to a function which can get the 'owner' of a class. This is
484
+ # used in reporting deserialization errors
485
+ #
486
+ # @param [Lambda, Proc, nil] value Proc that takes a class and
487
+ # produces its owner, or `nil` if it does not have one.
488
+ def self.class_owner_finder=(handler)
489
+ @class_owner_finder = handler
490
+ end
491
+
492
+ def self.class_owner_finder
493
+ @class_owner_finder
494
+ end
495
+
415
496
  # Temporarily disable ruby warnings while executing the given block. This is
416
497
  # useful when doing something that would normally cause a warning to be
417
498
  # emitted in Ruby verbose mode ($VERBOSE = true).
data/lib/types/generic.rb CHANGED
@@ -2,7 +2,6 @@
2
2
  # typed: true
3
3
 
4
4
  # Use as a mixin with extend (`extend T::Generic`).
5
- # Docs at https://hackpad.corp.stripe.com/Type-Validation-in-pay-server-1JaoTHir5Mo.
6
5
  module T::Generic
7
6
  include T::Helpers
8
7
  include Kernel
data/lib/types/helpers.rb CHANGED
@@ -33,7 +33,24 @@ module T::Helpers
33
33
  # end
34
34
  #
35
35
  # Except that it is statically analyzed by sorbet.
36
- def mixes_in_class_methods(mod)
37
- Private::Mixins.declare_mixes_in_class_methods(self, mod)
36
+ def mixes_in_class_methods(mod, *mods)
37
+ Private::Mixins.declare_mixes_in_class_methods(self, [mod].concat(mods))
38
38
  end
39
+
40
+ # Specify an inclusion or inheritance requirement for `self`.
41
+ #
42
+ # Example:
43
+ #
44
+ # module MyHelper
45
+ # extend T::Helpers
46
+ #
47
+ # requires_ancestor Kernel
48
+ # end
49
+ #
50
+ # class MyClass < BasicObject # error: `MyClass` must include `Kernel` (required by `MyHelper`)
51
+ # include MyHelper
52
+ # end
53
+ #
54
+ # TODO: implement the checks in sorbet-runtime.
55
+ def requires_ancestor(mod, *mods); end
39
56
  end
@@ -2,18 +2,26 @@
2
2
  # typed: strict
3
3
 
4
4
  module T::NonForcingConstants
5
+ T::Sig::WithoutRuntime.sig {returns(T::Boolean)}
6
+ private_class_method def self.stripe_packages_enabled?
7
+ const_defined?('StripePackages') && const_get('StripePackages').enabled?
8
+ end
9
+
5
10
  # NOTE: This method is documented on the RBI in Sorbet's payload, so that it
6
11
  # shows up in the hover/completion documentation via LSP.
7
12
  T::Sig::WithoutRuntime.sig {params(val: BasicObject, klass: String, package: T.nilable(String)).returns(T::Boolean)}
8
13
  def self.non_forcing_is_a?(val, klass, package: nil)
9
- # TODO(gdritter): once we have a runtime implementation of
10
- # packages, we'll need to actually handle the `package` argument
11
- # here.
12
14
  method_name = "T::NonForcingConstants.non_forcing_is_a?"
13
15
  if klass.empty?
14
16
  raise ArgumentError.new("The string given to `#{method_name}` must not be empty")
15
17
  end
16
18
 
19
+ # TODO(gdritter): this might not be want we want in a multipackage
20
+ # world; revisit this implementation we move on from the monopackage
21
+ if stripe_packages_enabled?
22
+ klass = "PkgRegistry::#{package}::#{klass}"
23
+ end
24
+
17
25
  current_klass = T.let(nil, T.nilable(Module))
18
26
  current_prefix = T.let(nil, T.nilable(String))
19
27
 
@@ -37,7 +37,6 @@ module T::Private::Final
37
37
  mod.extend(mod.is_a?(Class) ? NoInherit : NoIncludeExtend)
38
38
  mark_as_final_module(mod)
39
39
  mark_as_final_module(mod.singleton_class)
40
- T::Private::Methods.add_module_with_final(mod)
41
40
  T::Private::Methods.install_hooks(mod)
42
41
  end
43
42
 
@@ -6,19 +6,21 @@ module T::Private::Methods
6
6
  @signatures_by_method = {}
7
7
  @sig_wrappers = {}
8
8
  @sigs_that_raised = {}
9
- # the info about whether a method is final is not stored in a DeclBuilder nor a Signature, but instead right here.
10
- # this is because final checks are special:
11
- # - they are done possibly before any sig block has run.
12
- # - they are done even if the method being defined doesn't have a sig.
13
- @final_methods = Set.new
14
- # a non-singleton is a module for which at least one of the following is true:
15
- # - is declared final
16
- # - defines a method that is declared final
17
- # - includes an non-singleton
18
- # - extends an non-singleton
19
- # a singleton is the singleton_class of a non-singleton.
20
- # modules_with_final is the set of singletons and non-singletons.
21
- @modules_with_final = Set.new
9
+ # stores method names that were declared final without regard for where.
10
+ # enables early rejection of names that we know can't induce final method violations.
11
+ @was_ever_final_names = Set.new
12
+ # maps from modules to the set of final methods declared in that module.
13
+ # we also overload entries slightly: if the value is nil, that means that the
14
+ # module has final methods somewhere along its ancestor chain, but does not itself
15
+ # have any final methods.
16
+ #
17
+ # we need the latter information to know whether we need to check along the ancestor
18
+ # chain for final method violations. we need the former information because we
19
+ # care about exactly where a final method is defined (e.g. including the same module
20
+ # twice is permitted). we could do this with two tables, but it seems slightly
21
+ # cleaner with a single table.
22
+ # Effectively T::Hash[Module, T.nilable(Set))]
23
+ @modules_with_final = Hash.new { |hash, key| hash[key] = nil }
22
24
  # this stores the old [included, extended] hooks for Module and inherited hook for Class that we override when
23
25
  # enabling final checks for when those hooks are called. the 'hooks' here don't have anything to do with the 'hooks'
24
26
  # in installed_hooks.
@@ -27,29 +29,51 @@ module T::Private::Methods
27
29
  ARG_NOT_PROVIDED = Object.new
28
30
  PROC_TYPE = Object.new
29
31
 
30
- DeclarationBlock = Struct.new(:mod, :loc, :blk, :final)
32
+ DeclarationBlock = Struct.new(:mod, :loc, :blk, :final, :raw)
33
+
34
+ def self.declare_sig(mod, loc, arg, &blk)
35
+ T::Private::DeclState.current.active_declaration = _declare_sig_internal(mod, loc, arg, &blk)
36
+
37
+ nil
38
+ end
39
+
40
+ # See tests for how to use this. But you shouldn't be using this.
41
+ def self._declare_sig(mod, arg=nil, &blk)
42
+ _declare_sig_internal(mod, caller_locations(1, 1).first, arg, raw: true, &blk)
43
+ end
31
44
 
32
- def self.declare_sig(mod, arg, &blk)
45
+ private_class_method def self._declare_sig_internal(mod, loc, arg, raw: false, &blk)
33
46
  install_hooks(mod)
34
47
 
35
48
  if T::Private::DeclState.current.active_declaration
36
49
  T::Private::DeclState.current.reset!
37
- raise "You called sig twice without declaring a method inbetween"
50
+ raise "You called sig twice without declaring a method in between"
38
51
  end
39
52
 
40
53
  if !arg.nil? && arg != :final
41
54
  raise "Invalid argument to `sig`: #{arg}"
42
55
  end
43
56
 
44
- loc = caller_locations(2, 1).first
45
-
46
- T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk, arg == :final)
57
+ DeclarationBlock.new(mod, loc, blk, arg == :final, raw)
58
+ end
47
59
 
48
- nil
60
+ def self._with_declared_signature(mod, declblock, &blk)
61
+ # If declblock is provided, this code is equivalent to the check in
62
+ # _declare_sig_internal, above.
63
+ # If declblock is not provided and we have an active declaration, we are
64
+ # obviously doing something wrong.
65
+ if T::Private::DeclState.current.active_declaration
66
+ T::Private::DeclState.current.reset!
67
+ raise "You called sig twice without declaring a method in between"
68
+ end
69
+ if declblock
70
+ T::Private::DeclState.current.active_declaration = declblock
71
+ end
72
+ mod.module_exec(&blk)
49
73
  end
50
74
 
51
75
  def self.start_proc
52
- DeclBuilder.new(PROC_TYPE)
76
+ DeclBuilder.new(PROC_TYPE, false)
53
77
  end
54
78
 
55
79
  def self.finalize_proc(decl)
@@ -75,40 +99,6 @@ module T::Private::Methods
75
99
  T::Types::Proc.new(decl.params, decl.returns)
76
100
  end
77
101
 
78
- # See docs at T::Utils.register_forwarder.
79
- def self.register_forwarder(from_method, to_method, mode: Modes.overridable, remove_first_param: false)
80
- # Normalize the method (see comment in signature_for_key).
81
- from_method = from_method.owner.instance_method(from_method.name)
82
-
83
- from_key = method_to_key(from_method)
84
- maybe_run_sig_block_for_key(from_key)
85
- if @signatures_by_method.key?(from_key)
86
- raise "#{from_method} already has a method signature"
87
- end
88
-
89
- from_params = from_method.parameters
90
- if from_params.length != 2 || from_params[0][0] != :rest || from_params[1][0] != :block
91
- raise "forwarder methods should take a single splat param and a block param. `#{from_method}` " \
92
- "takes these params: #{from_params}. For help, ask #dev-productivity."
93
- end
94
-
95
- # If there's already a signature for to_method, we get `parameters` from there, to enable
96
- # chained forwarding. NB: we don't use `signature_for_key` here, because the normalization it
97
- # does is broken when `to_method` has been clobbered by another method.
98
- to_key = method_to_key(to_method)
99
- maybe_run_sig_block_for_key(to_key)
100
- to_params = @signatures_by_method[to_key]&.parameters || to_method.parameters
101
-
102
- if remove_first_param
103
- to_params = to_params[1..-1]
104
- end
105
-
106
- # We don't bother trying to preserve any types from to_signature because this won't be
107
- # statically analyzed, and the types will just get checked when the forwarding happens.
108
- from_signature = Signature.new_untyped(method: from_method, mode: mode, parameters: to_params)
109
- @signatures_by_method[from_key] = from_signature
110
- end
111
-
112
102
  # Returns the signature for a method whose definition was preceded by `sig`.
113
103
  #
114
104
  # @param method [UnboundMethod]
@@ -130,46 +120,86 @@ module T::Private::Methods
130
120
  # when target includes a module with instance methods source_method_names, ensure there is zero intersection between
131
121
  # the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
132
122
  # is already a method defined on one of target_ancestors with the same name that is final.
133
- def self._check_final_ancestors(target, target_ancestors, source_method_names)
134
- if !module_with_final?(target)
135
- return
136
- end
137
- # use reverse_each to check farther-up ancestors first, for better error messages. we could avoid this if we were on
138
- # the version of ruby that adds the optional argument to method_defined? that allows you to exclude ancestors.
123
+ #
124
+ # we assume that source_method_names has already been filtered to only include method
125
+ # names that were declared final at one point.
126
+ def self._check_final_ancestors(target, target_ancestors, source_method_names, source)
127
+ source_ancestors = nil
128
+ # use reverse_each to check farther-up ancestors first, for better error messages.
139
129
  target_ancestors.reverse_each do |ancestor|
130
+ final_methods = @modules_with_final.fetch(ancestor, nil)
131
+ # In this case, either ancestor didn't have any final methods anywhere in its
132
+ # ancestor chain, or ancestor did have final methods somewhere in its ancestor
133
+ # chain, but no final methods defined in ancestor itself. Either way, there
134
+ # are no final methods to check here, so we can move on to the next ancestor.
135
+ next unless final_methods
140
136
  source_method_names.each do |method_name|
141
- # the usage of method_owner_and_name_to_key(ancestor, method_name) instead of
142
- # method_to_key(ancestor.instance_method(method_name)) is not (just) an optimization, but also required for
143
- # correctness, since ancestor.method_defined?(method_name) may return true even if method_name is not defined
144
- # directly on ancestor but instead an ancestor of ancestor.
145
- if (ancestor.method_defined?(method_name) ||
146
- ancestor.private_method_defined?(method_name) ||
147
- ancestor.protected_method_defined?(method_name)) &&
148
- final_method?(method_owner_and_name_to_key(ancestor, method_name))
149
- raise(
150
- "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
151
- (target == ancestor ? "redefined" : "overridden in #{target}")
152
- )
137
+ next unless final_methods.include?(method_name)
138
+
139
+ # If we get here, we are defining a method that some ancestor declared as
140
+ # final. however, we permit a final method to be defined multiple
141
+ # times if it is the same final method being defined each time.
142
+ if source
143
+ if !source_ancestors
144
+ source_ancestors = source.ancestors
145
+ # filter out things without actual final methods just to make sure that
146
+ # the below checks (which should be uncommon) go as quickly as possible.
147
+ source_ancestors.select! do |a|
148
+ @modules_with_final.fetch(a, nil)
149
+ end
150
+ end
151
+ # final-ness means that there should be no more than one index for which
152
+ # the below block returns true.
153
+ defining_ancestor_idx = source_ancestors.index do |a|
154
+ @modules_with_final.fetch(a).include?(method_name)
155
+ end
156
+ next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor
153
157
  end
154
- end
155
- end
156
- end
157
158
 
158
- private_class_method def self.add_final_method(method_key)
159
- @final_methods.add(method_key)
160
- end
159
+ definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
160
+ is_redefined = target == ancestor
161
+ caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
162
+ extra_info = "\n"
163
+ if caller_loc
164
+ extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
165
+ end
161
166
 
162
- private_class_method def self.final_method?(method_key)
163
- @final_methods.include?(method_key)
167
+ error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
168
+ (is_redefined ? "redefined" : "overridden in #{target}")
169
+ pretty_message = "#{error_message}\n" \
170
+ "Made final here: #{definition_file}:#{definition_line}\n" \
171
+ "#{extra_info}"
172
+
173
+ begin
174
+ raise pretty_message
175
+ rescue => e
176
+ # sig_validation_error_handler raises by default; on the off chance that
177
+ # it doesn't raise, we need to ensure that the rest of signature building
178
+ # sees a consistent state. This sig failed to validate, so we should get
179
+ # rid of it. If we don't do this, errors of the form "You called sig
180
+ # twice without declaring a method in between" will non-deterministically
181
+ # crop up in tests.
182
+ T::Private::DeclState.current.reset!
183
+ T::Configuration.sig_validation_error_handler(e, {})
184
+ end
185
+ end
186
+ end
164
187
  end
165
188
 
166
- def self.add_module_with_final(mod)
167
- @modules_with_final.add(mod)
168
- @modules_with_final.add(mod.singleton_class)
189
+ def self.add_module_with_final_method(mod, method_name, is_singleton_method)
190
+ m = is_singleton_method ? mod.singleton_class : mod
191
+ methods = @modules_with_final[m]
192
+ if methods.nil?
193
+ methods = Set.new
194
+ @modules_with_final[m] = methods
195
+ end
196
+ methods.add(method_name)
169
197
  end
170
198
 
171
- private_class_method def self.module_with_final?(mod)
172
- @modules_with_final.include?(mod)
199
+ def self.note_module_deals_with_final(mod)
200
+ # Side-effectfully initialize the value if it's not already there
201
+ @modules_with_final[mod]
202
+ @modules_with_final[mod.singleton_class]
173
203
  end
174
204
 
175
205
  # Only public because it needs to get called below inside the replace_method blocks below.
@@ -184,7 +214,13 @@ module T::Private::Methods
184
214
  if T::Private::Final.final_module?(mod) && (current_declaration.nil? || !current_declaration.final)
185
215
  raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
186
216
  end
187
- _check_final_ancestors(mod, mod.ancestors, [method_name])
217
+ # Don't compute mod.ancestors if we don't need to bother checking final-ness.
218
+ if @was_ever_final_names.include?(method_name) && @modules_with_final.include?(mod)
219
+ _check_final_ancestors(mod, mod.ancestors, [method_name], nil)
220
+ # We need to fetch the active declaration again, as _check_final_ancestors
221
+ # may have reset it (see the comment in that method for details).
222
+ current_declaration = T::Private::DeclState.current.active_declaration
223
+ end
188
224
 
189
225
  if current_declaration.nil?
190
226
  return
@@ -207,41 +243,44 @@ module T::Private::Methods
207
243
  # which is called only on the *first* invocation.
208
244
  # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
209
245
  # (or unwrap back to the original method).
210
- new_method = nil
211
- T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
212
- method_sig = T::Private::Methods.maybe_run_sig_block_for_method(new_method)
213
- method_sig ||= T::Private::Methods._handle_missing_method_signature(
214
- self,
215
- original_method,
216
- __callee__,
217
- )
218
-
219
- # Should be the same logic as CallValidation.wrap_method_if_needed but we
220
- # don't want that extra layer of indirection in the callstack
221
- if method_sig.mode == T::Private::Methods::Modes.abstract
222
- # We're in an interface method, keep going up the chain
223
- if defined?(super)
224
- super(*args, &blk)
246
+ key = method_owner_and_name_to_key(mod, method_name)
247
+ unless current_declaration.raw
248
+ T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
249
+ method_sig = T::Private::Methods.maybe_run_sig_block_for_key(key)
250
+ method_sig ||= T::Private::Methods._handle_missing_method_signature(
251
+ self,
252
+ original_method,
253
+ __callee__,
254
+ )
255
+
256
+ # Should be the same logic as CallValidation.wrap_method_if_needed but we
257
+ # don't want that extra layer of indirection in the callstack
258
+ if method_sig.mode == T::Private::Methods::Modes.abstract
259
+ # We're in an interface method, keep going up the chain
260
+ if defined?(super)
261
+ super(*args, &blk)
262
+ else
263
+ raise NotImplementedError.new("The method `#{method_sig.method_name}` on #{mod} is declared as `abstract`. It does not have an implementation.")
264
+ end
265
+ # Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
266
+ # make sure to keep changes in sync.
267
+ elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
268
+ CallValidation.validate_call(self, original_method, method_sig, args, blk)
269
+ elsif T::Configuration::AT_LEAST_RUBY_2_7
270
+ original_method.bind_call(self, *args, &blk)
225
271
  else
226
- raise NotImplementedError.new("The method `#{method_sig.method_name}` on #{mod} is declared as `abstract`. It does not have an implementation.")
272
+ original_method.bind(self).call(*args, &blk)
227
273
  end
228
- # Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
229
- # make sure to keep changes in sync.
230
- elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
231
- CallValidation.validate_call(self, original_method, method_sig, args, blk)
232
- else
233
- original_method.bind(self).call(*args, &blk)
234
274
  end
235
275
  end
236
276
 
237
- new_method = mod.instance_method(method_name)
238
- key = method_to_key(new_method)
239
277
  @sig_wrappers[key] = sig_block
240
278
  if current_declaration.final
241
- add_final_method(key)
279
+ @was_ever_final_names.add(method_name)
242
280
  # use hook_mod, not mod, because for example, we want class C to be marked as having final if we def C.foo as
243
281
  # final. change this to mod to see some final_method tests fail.
244
- add_module_with_final(hook_mod)
282
+ note_module_deals_with_final(hook_mod)
283
+ add_module_with_final_method(hook_mod, method_name, is_singleton_method)
245
284
  end
246
285
  end
247
286
 
@@ -305,7 +344,7 @@ module T::Private::Methods
305
344
  end
306
345
 
307
346
  def self.run_builder(declaration_block)
308
- builder = DeclBuilder.new(declaration_block.mod)
347
+ builder = DeclBuilder.new(declaration_block.mod, declaration_block.raw)
309
348
  builder
310
349
  .instance_exec(&declaration_block.blk)
311
350
  .finalize!
@@ -336,6 +375,7 @@ module T::Private::Methods
336
375
  check_level: current_declaration.checked,
337
376
  on_failure: current_declaration.on_failure,
338
377
  override_allow_incompatible: current_declaration.override_allow_incompatible,
378
+ defined_raw: current_declaration.raw,
339
379
  )
340
380
 
341
381
  SignatureValidation.validate(signature)
@@ -373,7 +413,8 @@ module T::Private::Methods
373
413
  maybe_run_sig_block_for_key(method_to_key(method))
374
414
  end
375
415
 
376
- private_class_method def self.maybe_run_sig_block_for_key(key)
416
+ # Only public so that it can be accessed in the closure for _on_method_added
417
+ def self.maybe_run_sig_block_for_key(key)
377
418
  run_sig_block_for_key(key) if has_sig_block_for_key(key)
378
419
  end
379
420
 
@@ -417,13 +458,28 @@ module T::Private::Methods
417
458
 
418
459
  # the module target is adding the methods from the module source to itself. we need to check that for all instance
419
460
  # methods M on source, M is not defined on any of target's ancestors.
420
- def self._hook_impl(target, target_ancestors, source)
421
- if !module_with_final?(target) && !module_with_final?(source)
461
+ def self._hook_impl(target, singleton_class, source)
462
+ # we do not need to call add_was_ever_final here, because we have already marked
463
+ # any such methods when source was originally defined.
464
+ if !@modules_with_final.include?(target)
465
+ if !@modules_with_final.include?(source)
466
+ return
467
+ end
468
+ note_module_deals_with_final(target)
469
+ install_hooks(target)
470
+ return
471
+ end
472
+
473
+ methods = source.instance_methods
474
+ methods.select! do |method_name|
475
+ @was_ever_final_names.include?(method_name)
476
+ end
477
+ if methods.empty?
422
478
  return
423
479
  end
424
- add_module_with_final(target)
425
- install_hooks(target)
426
- _check_final_ancestors(target, target_ancestors - source.ancestors, source.instance_methods)
480
+
481
+ target_ancestors = singleton_class ? target.singleton_class.ancestors : target.ancestors
482
+ _check_final_ancestors(target, target_ancestors, methods, source)
427
483
  end
428
484
 
429
485
  def self.set_final_checks_on_hooks(enable)
@@ -437,15 +493,15 @@ module T::Private::Methods
437
493
  else
438
494
  old_included = T::Private::ClassUtils.replace_method(Module, :included) do |arg|
439
495
  old_included.bind(self).call(arg)
440
- ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
496
+ ::T::Private::Methods._hook_impl(arg, false, self)
441
497
  end
442
498
  old_extended = T::Private::ClassUtils.replace_method(Module, :extended) do |arg|
443
499
  old_extended.bind(self).call(arg)
444
- ::T::Private::Methods._hook_impl(arg, arg.singleton_class.ancestors, self)
500
+ ::T::Private::Methods._hook_impl(arg, true, self)
445
501
  end
446
502
  old_inherited = T::Private::ClassUtils.replace_method(Class, :inherited) do |arg|
447
503
  old_inherited.bind(self).call(arg)
448
- ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
504
+ ::T::Private::Methods._hook_impl(arg, false, self)
449
505
  end
450
506
  @old_hooks = [old_included, old_extended, old_inherited]
451
507
  end
@@ -483,6 +539,12 @@ module T::Private::Methods
483
539
  return
484
540
  end
485
541
 
542
+ # See https://github.com/sorbet/sorbet/pull/3964 for an explanation of why this
543
+ # check (which theoretically should not be needed) is actually needed.
544
+ if !mod.is_a?(Module)
545
+ return
546
+ end
547
+
486
548
  if mod.singleton_class?
487
549
  mod.include(SingletonMethodHooks)
488
550
  else