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.
- checksums.yaml +7 -0
- data/lib/sorbet-runtime.rb +116 -0
- data/lib/types/_types.rb +285 -0
- data/lib/types/abstract_utils.rb +50 -0
- data/lib/types/boolean.rb +8 -0
- data/lib/types/compatibility_patches.rb +95 -0
- data/lib/types/configuration.rb +428 -0
- data/lib/types/enum.rb +349 -0
- data/lib/types/generic.rb +23 -0
- data/lib/types/helpers.rb +39 -0
- data/lib/types/interface_wrapper.rb +158 -0
- data/lib/types/non_forcing_constants.rb +51 -0
- data/lib/types/private/abstract/data.rb +36 -0
- data/lib/types/private/abstract/declare.rb +48 -0
- data/lib/types/private/abstract/hooks.rb +43 -0
- data/lib/types/private/abstract/validate.rb +128 -0
- data/lib/types/private/casts.rb +22 -0
- data/lib/types/private/class_utils.rb +111 -0
- data/lib/types/private/decl_state.rb +30 -0
- data/lib/types/private/final.rb +51 -0
- data/lib/types/private/methods/_methods.rb +460 -0
- data/lib/types/private/methods/call_validation.rb +1149 -0
- data/lib/types/private/methods/decl_builder.rb +228 -0
- data/lib/types/private/methods/modes.rb +16 -0
- data/lib/types/private/methods/signature.rb +196 -0
- data/lib/types/private/methods/signature_validation.rb +229 -0
- data/lib/types/private/mixins/mixins.rb +27 -0
- data/lib/types/private/retry.rb +10 -0
- data/lib/types/private/runtime_levels.rb +56 -0
- data/lib/types/private/sealed.rb +65 -0
- data/lib/types/private/types/not_typed.rb +23 -0
- data/lib/types/private/types/string_holder.rb +26 -0
- data/lib/types/private/types/type_alias.rb +26 -0
- data/lib/types/private/types/void.rb +34 -0
- data/lib/types/profile.rb +31 -0
- data/lib/types/props/_props.rb +161 -0
- data/lib/types/props/constructor.rb +40 -0
- data/lib/types/props/custom_type.rb +108 -0
- data/lib/types/props/decorator.rb +672 -0
- data/lib/types/props/errors.rb +8 -0
- data/lib/types/props/generated_code_validation.rb +268 -0
- data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
- data/lib/types/props/optional.rb +81 -0
- data/lib/types/props/plugin.rb +37 -0
- data/lib/types/props/pretty_printable.rb +107 -0
- data/lib/types/props/private/apply_default.rb +170 -0
- data/lib/types/props/private/deserializer_generator.rb +165 -0
- data/lib/types/props/private/parser.rb +32 -0
- data/lib/types/props/private/serde_transform.rb +192 -0
- data/lib/types/props/private/serializer_generator.rb +77 -0
- data/lib/types/props/private/setter_factory.rb +134 -0
- data/lib/types/props/serializable.rb +330 -0
- data/lib/types/props/type_validation.rb +111 -0
- data/lib/types/props/utils.rb +59 -0
- data/lib/types/props/weak_constructor.rb +67 -0
- data/lib/types/runtime_profiled.rb +24 -0
- data/lib/types/sig.rb +30 -0
- data/lib/types/struct.rb +18 -0
- data/lib/types/types/attached_class.rb +37 -0
- data/lib/types/types/base.rb +151 -0
- data/lib/types/types/class_of.rb +38 -0
- data/lib/types/types/enum.rb +42 -0
- data/lib/types/types/fixed_array.rb +60 -0
- data/lib/types/types/fixed_hash.rb +59 -0
- data/lib/types/types/intersection.rb +37 -0
- data/lib/types/types/noreturn.rb +29 -0
- data/lib/types/types/proc.rb +51 -0
- data/lib/types/types/self_type.rb +35 -0
- data/lib/types/types/simple.rb +33 -0
- data/lib/types/types/t_enum.rb +38 -0
- data/lib/types/types/type_member.rb +7 -0
- data/lib/types/types/type_parameter.rb +23 -0
- data/lib/types/types/type_template.rb +7 -0
- data/lib/types/types/type_variable.rb +31 -0
- data/lib/types/types/typed_array.rb +34 -0
- data/lib/types/types/typed_enumerable.rb +161 -0
- data/lib/types/types/typed_enumerator.rb +36 -0
- data/lib/types/types/typed_hash.rb +43 -0
- data/lib/types/types/typed_range.rb +26 -0
- data/lib/types/types/typed_set.rb +36 -0
- data/lib/types/types/union.rb +56 -0
- data/lib/types/types/untyped.rb +29 -0
- data/lib/types/utils.rb +217 -0
- 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
|