sorbet-runtime 0.5.5841

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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sorbet-runtime.rb +116 -0
  3. data/lib/types/_types.rb +285 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +95 -0
  7. data/lib/types/configuration.rb +428 -0
  8. data/lib/types/enum.rb +349 -0
  9. data/lib/types/generic.rb +23 -0
  10. data/lib/types/helpers.rb +39 -0
  11. data/lib/types/interface_wrapper.rb +158 -0
  12. data/lib/types/non_forcing_constants.rb +51 -0
  13. data/lib/types/private/abstract/data.rb +36 -0
  14. data/lib/types/private/abstract/declare.rb +48 -0
  15. data/lib/types/private/abstract/hooks.rb +43 -0
  16. data/lib/types/private/abstract/validate.rb +128 -0
  17. data/lib/types/private/casts.rb +22 -0
  18. data/lib/types/private/class_utils.rb +111 -0
  19. data/lib/types/private/decl_state.rb +30 -0
  20. data/lib/types/private/final.rb +51 -0
  21. data/lib/types/private/methods/_methods.rb +460 -0
  22. data/lib/types/private/methods/call_validation.rb +1149 -0
  23. data/lib/types/private/methods/decl_builder.rb +228 -0
  24. data/lib/types/private/methods/modes.rb +16 -0
  25. data/lib/types/private/methods/signature.rb +196 -0
  26. data/lib/types/private/methods/signature_validation.rb +229 -0
  27. data/lib/types/private/mixins/mixins.rb +27 -0
  28. data/lib/types/private/retry.rb +10 -0
  29. data/lib/types/private/runtime_levels.rb +56 -0
  30. data/lib/types/private/sealed.rb +65 -0
  31. data/lib/types/private/types/not_typed.rb +23 -0
  32. data/lib/types/private/types/string_holder.rb +26 -0
  33. data/lib/types/private/types/type_alias.rb +26 -0
  34. data/lib/types/private/types/void.rb +34 -0
  35. data/lib/types/profile.rb +31 -0
  36. data/lib/types/props/_props.rb +161 -0
  37. data/lib/types/props/constructor.rb +40 -0
  38. data/lib/types/props/custom_type.rb +108 -0
  39. data/lib/types/props/decorator.rb +672 -0
  40. data/lib/types/props/errors.rb +8 -0
  41. data/lib/types/props/generated_code_validation.rb +268 -0
  42. data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
  43. data/lib/types/props/optional.rb +81 -0
  44. data/lib/types/props/plugin.rb +37 -0
  45. data/lib/types/props/pretty_printable.rb +107 -0
  46. data/lib/types/props/private/apply_default.rb +170 -0
  47. data/lib/types/props/private/deserializer_generator.rb +165 -0
  48. data/lib/types/props/private/parser.rb +32 -0
  49. data/lib/types/props/private/serde_transform.rb +192 -0
  50. data/lib/types/props/private/serializer_generator.rb +77 -0
  51. data/lib/types/props/private/setter_factory.rb +134 -0
  52. data/lib/types/props/serializable.rb +330 -0
  53. data/lib/types/props/type_validation.rb +111 -0
  54. data/lib/types/props/utils.rb +59 -0
  55. data/lib/types/props/weak_constructor.rb +67 -0
  56. data/lib/types/runtime_profiled.rb +24 -0
  57. data/lib/types/sig.rb +30 -0
  58. data/lib/types/struct.rb +18 -0
  59. data/lib/types/types/attached_class.rb +37 -0
  60. data/lib/types/types/base.rb +151 -0
  61. data/lib/types/types/class_of.rb +38 -0
  62. data/lib/types/types/enum.rb +42 -0
  63. data/lib/types/types/fixed_array.rb +60 -0
  64. data/lib/types/types/fixed_hash.rb +59 -0
  65. data/lib/types/types/intersection.rb +37 -0
  66. data/lib/types/types/noreturn.rb +29 -0
  67. data/lib/types/types/proc.rb +51 -0
  68. data/lib/types/types/self_type.rb +35 -0
  69. data/lib/types/types/simple.rb +33 -0
  70. data/lib/types/types/t_enum.rb +38 -0
  71. data/lib/types/types/type_member.rb +7 -0
  72. data/lib/types/types/type_parameter.rb +23 -0
  73. data/lib/types/types/type_template.rb +7 -0
  74. data/lib/types/types/type_variable.rb +31 -0
  75. data/lib/types/types/typed_array.rb +34 -0
  76. data/lib/types/types/typed_enumerable.rb +161 -0
  77. data/lib/types/types/typed_enumerator.rb +36 -0
  78. data/lib/types/types/typed_hash.rb +43 -0
  79. data/lib/types/types/typed_range.rb +26 -0
  80. data/lib/types/types/typed_set.rb +36 -0
  81. data/lib/types/types/union.rb +56 -0
  82. data/lib/types/types/untyped.rb +29 -0
  83. data/lib/types/utils.rb +217 -0
  84. metadata +223 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Private::Final
5
+ module NoInherit
6
+ def inherited(arg)
7
+ super(arg)
8
+ raise "#{self} was declared as final and cannot be inherited"
9
+ end
10
+ end
11
+
12
+ module NoIncludeExtend
13
+ def included(arg)
14
+ super(arg)
15
+ raise "#{self} was declared as final and cannot be included"
16
+ end
17
+
18
+ def extended(arg)
19
+ super(arg)
20
+ raise "#{self} was declared as final and cannot be extended"
21
+ end
22
+ end
23
+
24
+ def self.declare(mod)
25
+ if !mod.is_a?(Module)
26
+ raise "#{mod} is not a class or module and cannot be declared as final with `final!`"
27
+ end
28
+ if final_module?(mod)
29
+ raise "#{mod} was already declared as final and cannot be re-declared as final"
30
+ end
31
+ if T::AbstractUtils.abstract_module?(mod)
32
+ raise "#{mod} was already declared as abstract and cannot be declared as final"
33
+ end
34
+ if T::Private::Sealed.sealed_module?(mod)
35
+ raise "#{mod} was already declared as sealed and cannot be declared as final"
36
+ end
37
+ mod.extend(mod.is_a?(Class) ? NoInherit : NoIncludeExtend)
38
+ mark_as_final_module(mod)
39
+ mark_as_final_module(mod.singleton_class)
40
+ T::Private::Methods.add_module_with_final(mod)
41
+ T::Private::Methods.install_hooks(mod)
42
+ end
43
+
44
+ def self.final_module?(mod)
45
+ mod.instance_variable_defined?(:@sorbet_final_module)
46
+ end
47
+
48
+ private_class_method def self.mark_as_final_module(mod)
49
+ mod.instance_variable_set(:@sorbet_final_module, true)
50
+ end
51
+ end
@@ -0,0 +1,460 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Private::Methods
5
+ @installed_hooks = Set.new
6
+ @signatures_by_method = {}
7
+ @sig_wrappers = {}
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
22
+ # this stores the old [included, extended] hooks for Module and inherited hook for Class that we override when
23
+ # enabling final checks for when those hooks are called. the 'hooks' here don't have anything to do with the 'hooks'
24
+ # in installed_hooks.
25
+ @old_hooks = nil
26
+
27
+ ARG_NOT_PROVIDED = Object.new
28
+ PROC_TYPE = Object.new
29
+
30
+ DeclarationBlock = Struct.new(:mod, :loc, :blk, :final)
31
+
32
+ def self.declare_sig(mod, arg, &blk)
33
+ install_hooks(mod)
34
+
35
+ if T::Private::DeclState.current.active_declaration
36
+ T::Private::DeclState.current.reset!
37
+ raise "You called sig twice without declaring a method inbetween"
38
+ end
39
+
40
+ if !arg.nil? && arg != :final
41
+ raise "Invalid argument to `sig`: #{arg}"
42
+ end
43
+
44
+ loc = caller_locations(2, 1).first
45
+
46
+ T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk, arg == :final)
47
+
48
+ nil
49
+ end
50
+
51
+ def self.start_proc
52
+ DeclBuilder.new(PROC_TYPE)
53
+ end
54
+
55
+ def self.finalize_proc(decl)
56
+ decl.finalized = true
57
+
58
+ if decl.mode != Modes.standard
59
+ raise "Procs cannot have override/abstract modifiers"
60
+ end
61
+ if decl.mod != PROC_TYPE
62
+ raise "You are passing a DeclBuilder as a type. Did you accidentally use `self` inside a `sig` block?"
63
+ end
64
+ if decl.returns == ARG_NOT_PROVIDED
65
+ raise "Procs must specify a return type"
66
+ end
67
+ if decl.on_failure != ARG_NOT_PROVIDED
68
+ raise "Procs cannot use .on_failure"
69
+ end
70
+
71
+ if decl.params == ARG_NOT_PROVIDED
72
+ decl.params = {}
73
+ end
74
+
75
+ T::Types::Proc.new(decl.params, decl.returns) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
76
+ end
77
+
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
+ # Returns the signature for a method whose definition was preceded by `sig`.
113
+ #
114
+ # @param method [UnboundMethod]
115
+ # @return [T::Private::Methods::Signature]
116
+ def self.signature_for_method(method)
117
+ signature_for_key(method_to_key(method))
118
+ end
119
+
120
+ private_class_method def self.signature_for_key(key)
121
+ maybe_run_sig_block_for_key(key)
122
+
123
+ # If a subclass Sub inherits a method `foo` from Base, then
124
+ # Sub.instance_method(:foo) != Base.instance_method(:foo) even though they resolve to the
125
+ # same method. Similarly, Foo.method(:bar) != Foo.singleton_class.instance_method(:bar).
126
+ # So, we always do the look up by the method on the owner (Base in this example).
127
+ @signatures_by_method[key]
128
+ end
129
+
130
+ # when target includes a module with instance methods source_method_names, ensure there is zero intersection between
131
+ # the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
132
+ # 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.
139
+ target_ancestors.reverse_each do |ancestor|
140
+ 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) && final_method?(method_owner_and_name_to_key(ancestor, method_name))
146
+ raise(
147
+ "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
148
+ (target == ancestor ? "redefined" : "overridden in #{target}")
149
+ )
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ private_class_method def self.add_final_method(method_key)
156
+ @final_methods.add(method_key)
157
+ end
158
+
159
+ private_class_method def self.final_method?(method_key)
160
+ @final_methods.include?(method_key)
161
+ end
162
+
163
+ def self.add_module_with_final(mod)
164
+ @modules_with_final.add(mod)
165
+ @modules_with_final.add(mod.singleton_class)
166
+ end
167
+
168
+ private_class_method def self.module_with_final?(mod)
169
+ @modules_with_final.include?(mod)
170
+ end
171
+
172
+ # Only public because it needs to get called below inside the replace_method blocks below.
173
+ def self._on_method_added(hook_mod, method_name, is_singleton_method: false)
174
+ if T::Private::DeclState.current.skip_on_method_added
175
+ return
176
+ end
177
+
178
+ current_declaration = T::Private::DeclState.current.active_declaration
179
+ mod = is_singleton_method ? hook_mod.singleton_class : hook_mod
180
+
181
+ if T::Private::Final.final_module?(mod) && (current_declaration.nil? || !current_declaration.final)
182
+ raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
183
+ end
184
+ _check_final_ancestors(mod, mod.ancestors, [method_name])
185
+
186
+ if current_declaration.nil?
187
+ return
188
+ end
189
+ T::Private::DeclState.current.reset!
190
+
191
+ if method_name == :method_added || method_name == :singleton_method_added
192
+ raise(
193
+ "Putting a `sig` on `#{method_name}` is not supported" +
194
+ " (sorbet-runtime uses this method internally to perform `sig` validation logic)"
195
+ )
196
+ end
197
+
198
+ original_method = mod.instance_method(method_name)
199
+ sig_block = lambda do
200
+ T::Private::Methods.run_sig(hook_mod, method_name, original_method, current_declaration)
201
+ end
202
+
203
+ # Always replace the original method with this wrapper,
204
+ # which is called only on the *first* invocation.
205
+ # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
206
+ # (or unwrap back to the original method).
207
+ new_method = nil
208
+ T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
209
+ if !T::Private::Methods.has_sig_block_for_method(new_method)
210
+ # This should only happen if the user used alias_method to grab a handle
211
+ # to the original pre-unwound `sig` method. I guess we'll just proxy the
212
+ # call forever since we don't know who is holding onto this handle to
213
+ # replace it.
214
+ new_new_method = mod.instance_method(method_name)
215
+ if new_method == new_new_method
216
+ raise "`sig` not present for method `#{method_name}` but you're trying to run it anyways. " \
217
+ "This should only be executed if you used `alias_method` to grab a handle to a method after `sig`ing it, but that clearly isn't what you are doing. " \
218
+ "Maybe look to see if an exception was thrown in your `sig` lambda or somehow else your `sig` wasn't actually applied to the method. " \
219
+ "Contact #dev-productivity if you're really stuck."
220
+ end
221
+ return new_new_method.bind(self).call(*args, &blk)
222
+ end
223
+
224
+ method_sig = T::Private::Methods.run_sig_block_for_method(new_method)
225
+
226
+ # Should be the same logic as CallValidation.wrap_method_if_needed but we
227
+ # don't want that extra layer of indirection in the callstack
228
+ if method_sig.mode == T::Private::Methods::Modes.abstract
229
+ # We're in an interface method, keep going up the chain
230
+ if defined?(super)
231
+ super(*args, &blk)
232
+ else
233
+ raise NotImplementedError.new("The method `#{method_sig.method_name}` on #{mod} is declared as `abstract`. It does not have an implementation.")
234
+ end
235
+ # Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
236
+ # make sure to keep changes in sync.
237
+ elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
238
+ CallValidation.validate_call(self, original_method, method_sig, args, blk)
239
+ else
240
+ original_method.bind(self).call(*args, &blk)
241
+ end
242
+ end
243
+
244
+ new_method = mod.instance_method(method_name)
245
+ key = method_to_key(new_method)
246
+ @sig_wrappers[key] = sig_block
247
+ if current_declaration.final
248
+ add_final_method(key)
249
+ # use hook_mod, not mod, because for example, we want class C to be marked as having final if we def C.foo as
250
+ # final. change this to mod to see some final_method tests fail.
251
+ add_module_with_final(hook_mod)
252
+ end
253
+ end
254
+
255
+ # Executes the `sig` block, and converts the resulting Declaration
256
+ # to a Signature.
257
+ def self.run_sig(hook_mod, method_name, original_method, declaration_block)
258
+ current_declaration =
259
+ begin
260
+ run_builder(declaration_block)
261
+ rescue DeclBuilder::BuilderError => e
262
+ T::Configuration.sig_builder_error_handler(e, declaration_block.loc)
263
+ nil
264
+ end
265
+
266
+ signature =
267
+ if current_declaration
268
+ build_sig(hook_mod, method_name, original_method, current_declaration, declaration_block.loc)
269
+ else
270
+ Signature.new_untyped(method: original_method)
271
+ end
272
+
273
+ unwrap_method(hook_mod, signature, original_method)
274
+ signature
275
+ end
276
+
277
+ def self.run_builder(declaration_block)
278
+ builder = DeclBuilder.new(declaration_block.mod)
279
+ builder
280
+ .instance_exec(&declaration_block.blk)
281
+ .finalize!
282
+ .decl
283
+ end
284
+
285
+ def self.build_sig(hook_mod, method_name, original_method, current_declaration, loc)
286
+ begin
287
+ # We allow `sig` in the current module's context (normal case) and inside `class << self`
288
+ if hook_mod != current_declaration.mod && hook_mod.singleton_class != current_declaration.mod
289
+ raise "A method (#{method_name}) is being added on a different class/module (#{hook_mod}) than the " \
290
+ "last call to `sig` (#{current_declaration.mod}). Make sure each call " \
291
+ "to `sig` is immediately followed by a method definition on the same " \
292
+ "class/module."
293
+ end
294
+
295
+ signature = Signature.new(
296
+ method: original_method,
297
+ method_name: method_name,
298
+ raw_arg_types: current_declaration.params,
299
+ raw_return_type: current_declaration.returns,
300
+ bind: current_declaration.bind,
301
+ mode: current_declaration.mode,
302
+ check_level: current_declaration.checked,
303
+ on_failure: current_declaration.on_failure,
304
+ override_allow_incompatible: current_declaration.override_allow_incompatible,
305
+ )
306
+
307
+ SignatureValidation.validate(signature)
308
+ signature
309
+ rescue => e
310
+ super_method = original_method&.super_method
311
+ super_signature = signature_for_method(super_method) if super_method
312
+
313
+ T::Configuration.sig_validation_error_handler(
314
+ e,
315
+ method: original_method,
316
+ declaration: current_declaration,
317
+ signature: signature,
318
+ super_signature: super_signature
319
+ )
320
+
321
+ Signature.new_untyped(method: original_method)
322
+ end
323
+ end
324
+
325
+ def self.unwrap_method(hook_mod, signature, original_method)
326
+ maybe_wrapped_method = CallValidation.wrap_method_if_needed(signature.method.owner, signature, original_method)
327
+ @signatures_by_method[method_to_key(maybe_wrapped_method)] = signature
328
+ end
329
+
330
+ def self.has_sig_block_for_method(method)
331
+ has_sig_block_for_key(method_to_key(method))
332
+ end
333
+
334
+ private_class_method def self.has_sig_block_for_key(key)
335
+ @sig_wrappers.key?(key)
336
+ end
337
+
338
+ def self.maybe_run_sig_block_for_method(method)
339
+ maybe_run_sig_block_for_key(method_to_key(method))
340
+ end
341
+
342
+ private_class_method def self.maybe_run_sig_block_for_key(key)
343
+ run_sig_block_for_key(key) if has_sig_block_for_key(key)
344
+ end
345
+
346
+ def self.run_sig_block_for_method(method)
347
+ run_sig_block_for_key(method_to_key(method))
348
+ end
349
+
350
+ private_class_method def self.run_sig_block_for_key(key)
351
+ blk = @sig_wrappers[key]
352
+ if !blk
353
+ sig = @signatures_by_method[key]
354
+ if sig
355
+ # We already ran the sig block, perhaps in another thread.
356
+ return sig
357
+ else
358
+ raise "No `sig` wrapper for #{key_to_method(key)}"
359
+ end
360
+ end
361
+
362
+ begin
363
+ sig = blk.call
364
+ rescue
365
+ @sigs_that_raised[key] = true
366
+ raise
367
+ end
368
+ if @sigs_that_raised[key]
369
+ raise "A previous invocation of #{key_to_method(key)} raised, and the current one succeeded. Please don't do that."
370
+ end
371
+
372
+ @sig_wrappers.delete(key)
373
+ sig
374
+ end
375
+
376
+ def self.run_all_sig_blocks
377
+ loop do
378
+ break if @sig_wrappers.empty?
379
+ key, _ = @sig_wrappers.first
380
+ run_sig_block_for_key(key)
381
+ end
382
+ end
383
+
384
+ # the module target is adding the methods from the module source to itself. we need to check that for all instance
385
+ # methods M on source, M is not defined on any of target's ancestors.
386
+ def self._hook_impl(target, target_ancestors, source)
387
+ if !module_with_final?(target) && !module_with_final?(source)
388
+ return
389
+ end
390
+ add_module_with_final(target)
391
+ install_hooks(target)
392
+ _check_final_ancestors(target, target_ancestors - source.ancestors, source.instance_methods)
393
+ end
394
+
395
+ def self.set_final_checks_on_hooks(enable)
396
+ is_enabled = @old_hooks != nil
397
+ if enable == is_enabled
398
+ return
399
+ end
400
+ if is_enabled
401
+ @old_hooks.each(&:restore)
402
+ @old_hooks = nil
403
+ else
404
+ old_included = T::Private::ClassUtils.replace_method(Module, :included) do |arg|
405
+ old_included.bind(self).call(arg)
406
+ ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
407
+ end
408
+ old_extended = T::Private::ClassUtils.replace_method(Module, :extended) do |arg|
409
+ old_extended.bind(self).call(arg)
410
+ ::T::Private::Methods._hook_impl(arg, arg.singleton_class.ancestors, self)
411
+ end
412
+ old_inherited = T::Private::ClassUtils.replace_method(Class, :inherited) do |arg|
413
+ old_inherited.bind(self).call(arg)
414
+ ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
415
+ end
416
+ @old_hooks = [old_included, old_extended, old_inherited]
417
+ end
418
+ end
419
+
420
+ module MethodHooks
421
+ def method_added(name)
422
+ super(name)
423
+ ::T::Private::Methods._on_method_added(self, name, is_singleton_method: false)
424
+ end
425
+ end
426
+
427
+ module SingletonMethodHooks
428
+ def singleton_method_added(name)
429
+ super(name)
430
+ ::T::Private::Methods._on_method_added(self, name, is_singleton_method: true)
431
+ end
432
+ end
433
+
434
+ def self.install_hooks(mod)
435
+ return if @installed_hooks.include?(mod)
436
+ @installed_hooks << mod
437
+
438
+ if mod.singleton_class?
439
+ mod.include(SingletonMethodHooks)
440
+ else
441
+ mod.extend(MethodHooks)
442
+ end
443
+ mod.extend(SingletonMethodHooks)
444
+ end
445
+
446
+ # use this directly if you don't want/need to box up the method into an object to pass to method_to_key.
447
+ private_class_method def self.method_owner_and_name_to_key(owner, name)
448
+ "#{owner.object_id}##{name}"
449
+ end
450
+
451
+ private_class_method def self.method_to_key(method)
452
+ method_owner_and_name_to_key(method.owner, method.name)
453
+ end
454
+
455
+ private_class_method def self.key_to_method(key)
456
+ id, name = key.split("#")
457
+ obj = ObjectSpace._id2ref(id.to_i) # rubocop:disable PrisonGuard/NoDynamicConstAccess
458
+ obj.instance_method(name)
459
+ end
460
+ end