sorbet-runtime 0.5.6336 → 0.5.6351

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: 0b81aa820085c3f82ab984100ffa4eca33fc67ed314655818113d3af1968f7af
4
- data.tar.gz: c8fc6e0885c3a8851e0a68f55dccda195006b1745c9d55dbb872723fc07c39b5
3
+ metadata.gz: d598bc133cb90dc719b9072e14eb5bc578c679694887ee98e657571d85a46d2a
4
+ data.tar.gz: 7c718728dfb154109f628a36566bf7fa85ca3d8df7f379a391344863e520fa63
5
5
  SHA512:
6
- metadata.gz: acd340a1290ddba91e093fe315f25bf0e17f4648faf0fe0bfee7c09f3e1793ca7ff8832eb9fa5d880215fba349c2c4bb8512d3825dc5b4741bdc79cd92dc5928
7
- data.tar.gz: d8297b731b4d5dfab1566092c53ae78e9db20d9a0fc9a7e51bf9d59f36b4361095df0d19ccd74ba6153e9145545e2cd5535515440e88a7529978a5700e05c739
6
+ metadata.gz: 10c9194b02d89b13d546bcf3a9e2ca6e4e1d3d0b7225e1f4b3d17d7fb8f599a3e5f55815fd36997d86ad4834c531674befe7e224dcf8697b3f856c173812f78c
7
+ data.tar.gz: 184e4212a39731ae43f29a4120505c9d0f4b974d1a8ec9dba015b804f57c1ec17fceebefafd260aaca32fd08600cdf7f8a9b723248d5535e8397639ff520783d
@@ -433,6 +433,22 @@ module T::Configuration
433
433
  @sensitivity_and_pii_handler
434
434
  end
435
435
 
436
+ @redaction_handler = nil
437
+ # Set to a redaction handling function. This will be called when the
438
+ # `_redacted` version of a prop reader is used. By default this is set to
439
+ # `nil` and will raise an exception when the redacted version of a prop is
440
+ # accessed.
441
+ #
442
+ # @param [Lambda, Proc, nil] value Proc that converts a value into its
443
+ # redacted version according to the spec passed as the second argument.
444
+ def self.redaction_handler=(handler)
445
+ @redaction_handler = handler
446
+ end
447
+
448
+ def self.redaction_handler
449
+ @redaction_handler
450
+ end
451
+
436
452
  # Set to a function which can get the 'owner' of a class. This is
437
453
  # used in reporting deserialization errors
438
454
  #
@@ -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,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.
@@ -99,80 +98,86 @@ module T::Private::Methods
99
98
  # when target includes a module with instance methods source_method_names, ensure there is zero intersection between
100
99
  # the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
101
100
  # is already a method defined on one of target_ancestors with the same name that is final.
102
- def self._check_final_ancestors(target, target_ancestors, source_method_names)
103
- if !module_with_final?(target)
104
- return
105
- end
106
- source_method_names.select! do |method_name|
107
- was_ever_final?(method_name)
108
- end
109
- if source_method_names.empty?
110
- return
111
- end
112
- # use reverse_each to check farther-up ancestors first, for better error messages. we could avoid this if we were on
113
- # the version of ruby that adds the optional argument to method_defined? that allows you to exclude ancestors.
101
+ #
102
+ # we assume that source_method_names has already been filtered to only include method
103
+ # names that were declared final at one point.
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.
114
107
  target_ancestors.reverse_each do |ancestor|
115
- 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
116
114
  source_method_names.each do |method_name|
117
- # the usage of method_owner_and_name_to_key(ancestor, method_name) instead of
118
- # method_to_key(ancestor.instance_method(method_name)) is not (just) an optimization, but also required for
119
- # correctness, since ancestor.method_defined?(method_name) may return true even if method_name is not defined
120
- # directly on ancestor but instead an ancestor of ancestor.
121
- if final_method?(method_owner_and_name_to_key(ancestor, method_name))
122
- definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
123
- is_redefined = target == ancestor
124
- caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
125
- extra_info = "\n"
126
- if caller_loc
127
- 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
128
128
  end
129
-
130
- error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
131
- (is_redefined ? "redefined" : "overridden in #{target}")
132
- pretty_message = "#{error_message}\n" \
133
- "Made final here: #{definition_file}:#{definition_line}\n" \
134
- "#{extra_info}"
135
-
136
- begin
137
- raise pretty_message
138
- rescue => e
139
- # sig_validation_error_handler raises by default; on the off chance that
140
- # it doesn't raise, we need to ensure that the rest of signature building
141
- # sees a consistent state. This sig failed to validate, so we should get
142
- # rid of it. If we don't do this, errors of the form "You called sig
143
- # twice without declaring a method in between" will non-deterministically
144
- # crop up in tests.
145
- T::Private::DeclState.current.reset!
146
- 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)
147
133
  end
134
+ next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor
148
135
  end
149
- end
150
- end
151
- end
152
-
153
- private_class_method def self.add_final_method(method_key)
154
- @final_methods.add(method_key)
155
- end
156
136
 
157
- private_class_method def self.final_method?(method_key)
158
- @final_methods.include?(method_key)
159
- end
160
-
161
- private_class_method def self.add_was_ever_final(method_name)
162
- @was_ever_final_names.add(method_name)
163
- 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
164
144
 
165
- private_class_method def self.was_ever_final?(method_name)
166
- @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
167
165
  end
168
166
 
169
- def self.add_module_with_final(mod)
170
- @modules_with_final.add(mod)
171
- @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)
172
175
  end
173
176
 
174
- private_class_method def self.module_with_final?(mod)
175
- @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]
176
181
  end
177
182
 
178
183
  # Only public because it needs to get called below inside the replace_method blocks below.
@@ -187,11 +192,14 @@ module T::Private::Methods
187
192
  if T::Private::Final.final_module?(mod) && (current_declaration.nil? || !current_declaration.final)
188
193
  raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
189
194
  end
190
- _check_final_ancestors(mod, mod.ancestors, [method_name])
195
+ # Don't compute mod.ancestors if we don't need to bother checking final-ness.
196
+ if @was_ever_final_names.include?(method_name) && @modules_with_final.include?(mod)
197
+ _check_final_ancestors(mod, mod.ancestors, [method_name], nil)
198
+ # We need to fetch the active declaration again, as _check_final_ancestors
199
+ # may have reset it (see the comment in that method for details).
200
+ current_declaration = T::Private::DeclState.current.active_declaration
201
+ end
191
202
 
192
- # We need to fetch the active declaration again, as _check_final_ancestors
193
- # may have reset it (see the comment in that method for details).
194
- current_declaration = T::Private::DeclState.current.active_declaration
195
203
  if current_declaration.nil?
196
204
  return
197
205
  end
@@ -213,9 +221,9 @@ module T::Private::Methods
213
221
  # which is called only on the *first* invocation.
214
222
  # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
215
223
  # (or unwrap back to the original method).
216
- new_method = nil
224
+ key = method_owner_and_name_to_key(mod, method_name)
217
225
  T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
218
- method_sig = T::Private::Methods.maybe_run_sig_block_for_method(new_method)
226
+ method_sig = T::Private::Methods.maybe_run_sig_block_for_key(key)
219
227
  method_sig ||= T::Private::Methods._handle_missing_method_signature(
220
228
  self,
221
229
  original_method,
@@ -242,15 +250,13 @@ module T::Private::Methods
242
250
  end
243
251
  end
244
252
 
245
- new_method = mod.instance_method(method_name)
246
- key = method_to_key(new_method)
247
253
  @sig_wrappers[key] = sig_block
248
254
  if current_declaration.final
249
- add_final_method(key)
250
- add_was_ever_final(method_name)
255
+ @was_ever_final_names.add(method_name)
251
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
252
257
  # final. change this to mod to see some final_method tests fail.
253
- 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)
254
260
  end
255
261
  end
256
262
 
@@ -382,7 +388,8 @@ module T::Private::Methods
382
388
  maybe_run_sig_block_for_key(method_to_key(method))
383
389
  end
384
390
 
385
- private_class_method def self.maybe_run_sig_block_for_key(key)
391
+ # Only public so that it can be accessed in the closure for _on_method_added
392
+ def self.maybe_run_sig_block_for_key(key)
386
393
  run_sig_block_for_key(key) if has_sig_block_for_key(key)
387
394
  end
388
395
 
@@ -426,15 +433,28 @@ module T::Private::Methods
426
433
 
427
434
  # the module target is adding the methods from the module source to itself. we need to check that for all instance
428
435
  # methods M on source, M is not defined on any of target's ancestors.
429
- def self._hook_impl(target, target_ancestors, source)
430
- if !module_with_final?(target) && !module_with_final?(source)
431
- return
432
- end
436
+ def self._hook_impl(target, singleton_class, source)
433
437
  # we do not need to call add_was_ever_final here, because we have already marked
434
438
  # any such methods when source was originally defined.
435
- add_module_with_final(target)
436
- install_hooks(target)
437
- _check_final_ancestors(target, target_ancestors - source.ancestors, source.instance_methods)
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)
445
+ return
446
+ end
447
+
448
+ methods = source.instance_methods
449
+ methods.select! do |method_name|
450
+ @was_ever_final_names.include?(method_name)
451
+ end
452
+ if methods.empty?
453
+ return
454
+ end
455
+
456
+ target_ancestors = singleton_class ? target.singleton_class.ancestors : target.ancestors
457
+ _check_final_ancestors(target, target_ancestors, methods, source)
438
458
  end
439
459
 
440
460
  def self.set_final_checks_on_hooks(enable)
@@ -448,15 +468,15 @@ module T::Private::Methods
448
468
  else
449
469
  old_included = T::Private::ClassUtils.replace_method(Module, :included) do |arg|
450
470
  old_included.bind(self).call(arg)
451
- ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
471
+ ::T::Private::Methods._hook_impl(arg, false, self)
452
472
  end
453
473
  old_extended = T::Private::ClassUtils.replace_method(Module, :extended) do |arg|
454
474
  old_extended.bind(self).call(arg)
455
- ::T::Private::Methods._hook_impl(arg, arg.singleton_class.ancestors, self)
475
+ ::T::Private::Methods._hook_impl(arg, true, self)
456
476
  end
457
477
  old_inherited = T::Private::ClassUtils.replace_method(Class, :inherited) do |arg|
458
478
  old_inherited.bind(self).call(arg)
459
- ::T::Private::Methods._hook_impl(arg, arg.ancestors, self)
479
+ ::T::Private::Methods._hook_impl(arg, false, self)
460
480
  end
461
481
  @old_hooks = [old_included, old_extended, old_inherited]
462
482
  end
@@ -449,8 +449,11 @@ class T::Props::Decorator
449
449
 
450
450
  @class.send(:define_method, redacted_method) do
451
451
  value = self.public_send(prop_name)
452
- Chalk::Tools::RedactionUtils.redact_with_directive(
453
- value, redaction)
452
+ handler = T::Configuration.redaction_handler
453
+ if !handler
454
+ raise "Using `redaction:` on a prop requires specifying `T::Configuration.redaction_handler`"
455
+ end
456
+ handler.call(value, redaction)
454
457
  end
455
458
  end
456
459
 
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.6336
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 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