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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37e5c18902b6bf05fda832dcfae31a46dd3ad7c454c7741f7003924c70e351fc
4
- data.tar.gz: fba7a016bdbc64626802d5ccfdc9cf18ac382936ac7c98dc3bb62b610754a8e6
3
+ metadata.gz: d598bc133cb90dc719b9072e14eb5bc578c679694887ee98e657571d85a46d2a
4
+ data.tar.gz: 7c718728dfb154109f628a36566bf7fa85ca3d8df7f379a391344863e520fa63
5
5
  SHA512:
6
- metadata.gz: 4754a24ac572c182cb80f76c0505e02e61af830e2585cf73b464eba30fe24fc51c9ffeaa2b6ef1ff31a5d3b97e1c70e2d52de6398e924262cbae94e9408334ab
7
- data.tar.gz: e7120e72312db83159fdde80db0ca357bf526e69fd7d97d5327623b5d3cc1f0c6d76753c25c1fd8ec52542b087a7e17db94e65a4de8c920efbbcff785cd4cdd2
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
- # a non-singleton is a module for which at least one of the following is true:
18
- # - is declared final
19
- # - defines a method that is declared final
20
- # - includes an non-singleton
21
- # - extends an non-singleton
22
- # a singleton is the singleton_class of a non-singleton.
23
- # modules_with_final is the set of singletons and non-singletons.
24
- @modules_with_final = 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 }
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
- # use reverse_each to check farther-up ancestors first, for better error messages. we could avoid this if we were on
107
- # the version of ruby that adds the optional argument to method_defined? that allows you to exclude ancestors.
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
- next if !module_with_final?(ancestor)
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
- # the usage of method_owner_and_name_to_key(ancestor, method_name) instead of
112
- # method_to_key(ancestor.instance_method(method_name)) is not (just) an optimization, but also required for
113
- # correctness, since ancestor.method_defined?(method_name) may return true even if method_name is not defined
114
- # directly on ancestor but instead an ancestor of ancestor.
115
- if final_method?(method_owner_and_name_to_key(ancestor, method_name))
116
- definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
117
- is_redefined = target == ancestor
118
- caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
119
- extra_info = "\n"
120
- if caller_loc
121
- extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
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
- error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
125
- (is_redefined ? "redefined" : "overridden in #{target}")
126
- pretty_message = "#{error_message}\n" \
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
- private_class_method def self.add_final_method(method_key)
148
- @final_methods.add(method_key)
149
- end
150
-
151
- private_class_method def self.final_method?(method_key)
152
- @final_methods.include?(method_key)
153
- end
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
- private_class_method def self.was_ever_final?(method_name)
160
- @was_ever_final_names.include?(method_name)
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.add_module_with_final(mod)
164
- @modules_with_final.add(mod)
165
- @modules_with_final.add(mod.singleton_class)
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
- private_class_method def self.module_with_final?(mod)
169
- @modules_with_final.include?(mod)
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 was_ever_final?(method_name) && module_with_final?(mod)
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
- add_final_method(key)
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
- add_module_with_final(hook_mod)
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
- add_module_with_final(target)
433
- install_hooks(target)
434
-
435
- if !target_was_final
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
- was_ever_final?(method_name)
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 - source.ancestors, methods)
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.6349
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-18 00:00:00.000000000 Z
11
+ date: 2021-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest