sorbet-runtime 0.5.6349 → 0.5.6351
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/types/private/methods/_methods.rb +91 -82
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d598bc133cb90dc719b9072e14eb5bc578c679694887ee98e657571d85a46d2a
|
4
|
+
data.tar.gz: 7c718728dfb154109f628a36566bf7fa85ca3d8df7f379a391344863e520fa63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10c9194b02d89b13d546bcf3a9e2ca6e4e1d3d0b7225e1f4b3d17d7fb8f599a3e5f55815fd36997d86ad4834c531674befe7e224dcf8697b3f856c173812f78c
|
7
|
+
data.tar.gz: 184e4212a39731ae43f29a4120505c9d0f4b974d1a8ec9dba015b804f57c1ec17fceebefafd260aaca32fd08600cdf7f8a9b723248d5535e8397639ff520783d
|
@@ -6,22 +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
9
|
# stores method names that were declared final without regard for where.
|
15
10
|
# enables early rejection of names that we know can't induce final method violations.
|
16
11
|
@was_ever_final_names = Set.new
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
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 }
|
25
24
|
# this stores the old [included, extended] hooks for Module and inherited hook for Class that we override when
|
26
25
|
# enabling final checks for when those hooks are called. the 'hooks' here don't have anything to do with the 'hooks'
|
27
26
|
# in installed_hooks.
|
@@ -102,71 +101,83 @@ module T::Private::Methods
|
|
102
101
|
#
|
103
102
|
# we assume that source_method_names has already been filtered to only include method
|
104
103
|
# names that were declared final at one point.
|
105
|
-
def self._check_final_ancestors(target, target_ancestors, source_method_names)
|
106
|
-
|
107
|
-
#
|
104
|
+
def self._check_final_ancestors(target, target_ancestors, source_method_names, source)
|
105
|
+
source_ancestors = nil
|
106
|
+
# use reverse_each to check farther-up ancestors first, for better error messages.
|
108
107
|
target_ancestors.reverse_each do |ancestor|
|
109
|
-
|
108
|
+
final_methods = @modules_with_final.fetch(ancestor, nil)
|
109
|
+
# In this case, either ancestor didn't have any final methods anywhere in its
|
110
|
+
# ancestor chain, or ancestor did have final methods somewhere in its ancestor
|
111
|
+
# chain, but no final methods defined in ancestor itself. Either way, there
|
112
|
+
# are no final methods to check here, so we can move on to the next ancestor.
|
113
|
+
next unless final_methods
|
110
114
|
source_method_names.each do |method_name|
|
111
|
-
|
112
|
-
|
113
|
-
#
|
114
|
-
#
|
115
|
-
if
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
115
|
+
next unless final_methods.include?(method_name)
|
116
|
+
|
117
|
+
# If we get here, we are defining a method that some ancestor declared as
|
118
|
+
# final. however, we permit a final method to be defined multiple
|
119
|
+
# times if it is the same final method being defined each time.
|
120
|
+
if source
|
121
|
+
if !source_ancestors
|
122
|
+
source_ancestors = source.ancestors
|
123
|
+
# filter out things without actual final methods just to make sure that
|
124
|
+
# the below checks (which should be uncommon) go as quickly as possible.
|
125
|
+
source_ancestors.select! do |a|
|
126
|
+
@modules_with_final.fetch(a, nil)
|
127
|
+
end
|
122
128
|
end
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
"Made final here: #{definition_file}:#{definition_line}\n" \
|
128
|
-
"#{extra_info}"
|
129
|
-
|
130
|
-
begin
|
131
|
-
raise pretty_message
|
132
|
-
rescue => e
|
133
|
-
# sig_validation_error_handler raises by default; on the off chance that
|
134
|
-
# it doesn't raise, we need to ensure that the rest of signature building
|
135
|
-
# sees a consistent state. This sig failed to validate, so we should get
|
136
|
-
# rid of it. If we don't do this, errors of the form "You called sig
|
137
|
-
# twice without declaring a method in between" will non-deterministically
|
138
|
-
# crop up in tests.
|
139
|
-
T::Private::DeclState.current.reset!
|
140
|
-
T::Configuration.sig_validation_error_handler(e, {})
|
129
|
+
# final-ness means that there should be no more than one index for which
|
130
|
+
# the below block returns true.
|
131
|
+
defining_ancestor_idx = source_ancestors.index do |a|
|
132
|
+
@modules_with_final.fetch(a).include?(method_name)
|
141
133
|
end
|
134
|
+
next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor
|
142
135
|
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
136
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
private_class_method def self.add_was_ever_final(method_name)
|
156
|
-
@was_ever_final_names.add(method_name)
|
157
|
-
end
|
137
|
+
definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
|
138
|
+
is_redefined = target == ancestor
|
139
|
+
caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
|
140
|
+
extra_info = "\n"
|
141
|
+
if caller_loc
|
142
|
+
extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
|
143
|
+
end
|
158
144
|
|
159
|
-
|
160
|
-
|
145
|
+
error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
|
146
|
+
(is_redefined ? "redefined" : "overridden in #{target}")
|
147
|
+
pretty_message = "#{error_message}\n" \
|
148
|
+
"Made final here: #{definition_file}:#{definition_line}\n" \
|
149
|
+
"#{extra_info}"
|
150
|
+
|
151
|
+
begin
|
152
|
+
raise pretty_message
|
153
|
+
rescue => e
|
154
|
+
# sig_validation_error_handler raises by default; on the off chance that
|
155
|
+
# it doesn't raise, we need to ensure that the rest of signature building
|
156
|
+
# sees a consistent state. This sig failed to validate, so we should get
|
157
|
+
# rid of it. If we don't do this, errors of the form "You called sig
|
158
|
+
# twice without declaring a method in between" will non-deterministically
|
159
|
+
# crop up in tests.
|
160
|
+
T::Private::DeclState.current.reset!
|
161
|
+
T::Configuration.sig_validation_error_handler(e, {})
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
161
165
|
end
|
162
166
|
|
163
|
-
def self.
|
164
|
-
|
165
|
-
@modules_with_final
|
167
|
+
def self.add_module_with_final_method(mod, method_name, is_singleton_method)
|
168
|
+
m = is_singleton_method ? mod.singleton_class : mod
|
169
|
+
methods = @modules_with_final[m]
|
170
|
+
if methods.nil?
|
171
|
+
methods = Set.new
|
172
|
+
@modules_with_final[m] = methods
|
173
|
+
end
|
174
|
+
methods.add(method_name)
|
166
175
|
end
|
167
176
|
|
168
|
-
|
169
|
-
|
177
|
+
def self.note_module_deals_with_final(mod)
|
178
|
+
# Side-effectfully initialize the value if it's not already there
|
179
|
+
@modules_with_final[mod]
|
180
|
+
@modules_with_final[mod.singleton_class]
|
170
181
|
end
|
171
182
|
|
172
183
|
# Only public because it needs to get called below inside the replace_method blocks below.
|
@@ -182,8 +193,8 @@ module T::Private::Methods
|
|
182
193
|
raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
|
183
194
|
end
|
184
195
|
# Don't compute mod.ancestors if we don't need to bother checking final-ness.
|
185
|
-
if
|
186
|
-
_check_final_ancestors(mod, mod.ancestors, [method_name])
|
196
|
+
if @was_ever_final_names.include?(method_name) && @modules_with_final.include?(mod)
|
197
|
+
_check_final_ancestors(mod, mod.ancestors, [method_name], nil)
|
187
198
|
# We need to fetch the active declaration again, as _check_final_ancestors
|
188
199
|
# may have reset it (see the comment in that method for details).
|
189
200
|
current_declaration = T::Private::DeclState.current.active_declaration
|
@@ -241,11 +252,11 @@ module T::Private::Methods
|
|
241
252
|
|
242
253
|
@sig_wrappers[key] = sig_block
|
243
254
|
if current_declaration.final
|
244
|
-
|
245
|
-
add_was_ever_final(method_name)
|
255
|
+
@was_ever_final_names.add(method_name)
|
246
256
|
# use hook_mod, not mod, because for example, we want class C to be marked as having final if we def C.foo as
|
247
257
|
# final. change this to mod to see some final_method tests fail.
|
248
|
-
|
258
|
+
note_module_deals_with_final(hook_mod)
|
259
|
+
add_module_with_final_method(hook_mod, method_name, is_singleton_method)
|
249
260
|
end
|
250
261
|
end
|
251
262
|
|
@@ -423,29 +434,27 @@ module T::Private::Methods
|
|
423
434
|
# the module target is adding the methods from the module source to itself. we need to check that for all instance
|
424
435
|
# methods M on source, M is not defined on any of target's ancestors.
|
425
436
|
def self._hook_impl(target, singleton_class, source)
|
426
|
-
target_was_final = module_with_final?(target)
|
427
|
-
if !target_was_final && !module_with_final?(source)
|
428
|
-
return
|
429
|
-
end
|
430
437
|
# we do not need to call add_was_ever_final here, because we have already marked
|
431
438
|
# any such methods when source was originally defined.
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
439
|
+
if !@modules_with_final.include?(target)
|
440
|
+
if !@modules_with_final.include?(source)
|
441
|
+
return
|
442
|
+
end
|
443
|
+
note_module_deals_with_final(target)
|
444
|
+
install_hooks(target)
|
436
445
|
return
|
437
446
|
end
|
438
447
|
|
439
448
|
methods = source.instance_methods
|
440
449
|
methods.select! do |method_name|
|
441
|
-
|
450
|
+
@was_ever_final_names.include?(method_name)
|
442
451
|
end
|
443
452
|
if methods.empty?
|
444
453
|
return
|
445
454
|
end
|
446
455
|
|
447
456
|
target_ancestors = singleton_class ? target.singleton_class.ancestors : target.ancestors
|
448
|
-
_check_final_ancestors(target, target_ancestors
|
457
|
+
_check_final_ancestors(target, target_ancestors, methods, source)
|
449
458
|
end
|
450
459
|
|
451
460
|
def self.set_final_checks_on_hooks(enable)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorbet-runtime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6351
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stripe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|