sorbet-runtime 0.6.13412 → 0.6.13414

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: 12f5cde61fc274c67b1e6230f7662c4d97079fb76dd37248b77601652b301123
4
- data.tar.gz: a557477677d13ba37a1ec5e2ca923d844fadaf5414761d6fffcf49a5afebd1eb
3
+ metadata.gz: c785f4639764f63f21035a28e89190a76a91b8c03bed613c2a1173df0affb145
4
+ data.tar.gz: 17d9de2b1f8cff74c7d4793b64bcc5f50f37f6647ba1bdd3dc5566c087cc4a4b
5
5
  SHA512:
6
- metadata.gz: ede94d922ffbdc539855d627bf67c4947148179f7b1e426f150806a91e7d2b78eece10f8a22851a18f0cf5bd9c9c8df8b685452fdb38af09a65324ff020a26c5
7
- data.tar.gz: 6df95518c869b5c12a0f7ac873be2b38359f249a03dbc68020d20199f104d55055df9dc9c1bb51e83165dd3bf241f504362b46fdddd6b100632c2bea0e4a9a96
6
+ metadata.gz: 7dabc7ec609291736c71f2fe2864b71f46cbc602266138cb9f9726175eb5a521d1e0ba4cf552e27ff138b4e1023ac1e47586f5340cbbf3c943f9eeeecf08bdad
7
+ data.tar.gz: 1150cd3d617f5c87aeb20d258320e52481857b4f00d14d1fdebd0b9cc2c88f0d01e48615be25a09cc92576ed3ce25c26960d00e773e5f96054a399a4b8f74308
@@ -13,6 +13,7 @@ class T::Private::DeclState
13
13
  attr_accessor :active_declaration
14
14
  attr_accessor :skip_on_method_added
15
15
  attr_accessor :previous_declaration
16
+ attr_accessor :last_method_key
16
17
 
17
18
  def reset!
18
19
  self.active_declaration = nil
@@ -42,13 +42,16 @@ module T::Private::Methods
42
42
  end
43
43
  # rubocop:enable Naming/ClassAndModuleCamelCase
44
44
 
45
- # blk_or_decl:
45
+ # sig_state:
46
46
  # - It's a `Proc` if we haven't forced the thunk yet.
47
47
  # - It's a `Declaration` if we have, but we haven't finished building the sig
48
48
  # (This can matter for circular load-time behavior, where a method is
49
49
  # called while its Signature is being built)
50
- # - It's `nil` if we've finished building the sig
51
- DeclarationBlock = Struct.new(:mod, :method_name, :loc, :blk_or_decl, :final, :abstract, :override, :overridable)
50
+ # - It's a `Signature` if we've finished building the sig. This allows
51
+ # the first-call wrapper to find its own signature even if it was
52
+ # evaluated externally (e.g., by run_all_sig_blocks) and
53
+ # @signatures_by_method[key] was overwritten by a redefinition.
54
+ DeclarationBlock = Struct.new(:mod, :method_name, :loc, :sig_state, :final, :abstract, :override, :overridable)
52
55
 
53
56
  def self.declare_sig(mod, loc, arg, &blk)
54
57
  if T::Private::DeclState.current.active_declaration
@@ -76,7 +79,7 @@ module T::Private::Methods
76
79
  raise DeclBuilder::BuilderError.new("You must declare a `sig` before using `#{dsl_name}` on the method `#{method_name}`")
77
80
  end
78
81
 
79
- if !previous_declaration.blk_or_decl.is_a?(Proc)
82
+ if !previous_declaration.sig_state.is_a?(Proc)
80
83
  raise DeclBuilder::BuilderError.new("Cannot call `#{dsl_name} #{method_name.inspect}`, because the sig block has already run")
81
84
  end
82
85
 
@@ -320,6 +323,43 @@ module T::Private::Methods
320
323
  end
321
324
 
322
325
  if current_declaration.nil?
326
+ key = method_owner_and_name_to_key(mod, method_name)
327
+
328
+ # `last_method_key` guards against multiple calls to `_on_method_added`,
329
+ # from having multiple copies of the method in the ancestor chain (this
330
+ # can happen commonly if `Module` is monkey patched, because various
331
+ # files in sorbet-runtime itself run `extend T::Sig` before a monkey
332
+ # patch has a chance to fire).
333
+ #
334
+ # This is a performance optimization, to guarantee that methods with
335
+ # `.checked(:never)` sigs are unwrapped on first call rather than
336
+ # permanently trapped behind the first-call wrapper (which allocates via
337
+ # `|*args, &blk|` on every call). Without this guard, the duplicate
338
+ # `_on_method_added` deletes the key from `@sig_wrappers`, so the wrapper
339
+ # can never find its sig block and never unwraps.
340
+ if decl_state.last_method_key == key
341
+ return
342
+ end
343
+
344
+ # Drop any existing sig, because the `sig_block` closes over the
345
+ # `original_method` at the time that the sig wrapper was registered, and
346
+ # forcing the sig would thus redefine the the redefined method back to
347
+ # the original method.
348
+ old_sig = @sig_wrappers.delete(key)
349
+
350
+ # Ruby only reports method redefinitions if `$VERBOSE` is truthy. Let's
351
+ # do the same for sigs.
352
+ if old_sig && $VERBOSE
353
+ # We can probably get away with not printing any location information
354
+ # (like the Ruby VM would for method redefinition warnings) because the
355
+ # Ruby VM itself will have printed the location information in its
356
+ # method redefinition warning (and it does that faster, using functions
357
+ # that constult the CFP directly).
358
+ Warning.warn(
359
+ "sorbet-runtime: warning: Dropping unevaluated signature for #{mod}##{method_name} because it was redefined\n"
360
+ )
361
+ end
362
+
323
363
  return
324
364
  end
325
365
 
@@ -348,7 +388,7 @@ module T::Private::Methods
348
388
 
349
389
  original_method = mod.instance_method(method_name)
350
390
  sig_block = lambda do
351
- T::Private::Methods.run_sig(method_name, original_method, current_declaration)
391
+ T::Private::Methods.run_sig(method_name, original_method, current_declaration, unwrap: true)
352
392
  end
353
393
 
354
394
  # Always replace the original method with this wrapper,
@@ -356,23 +396,56 @@ module T::Private::Methods
356
396
  # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
357
397
  # (or unwrap back to the original method).
358
398
  key = method_owner_and_name_to_key(mod, method_name)
399
+ built_sig = nil
400
+ if T::Private::IS_TYPECHECKING
401
+ built_sig = T.let(nil, T.nilable(Signature))
402
+ end
359
403
  T::Private::ClassUtils.replace_method(original_method, mod, method_name) do |*args, &blk|
360
- method_sig = T::Private::Methods.maybe_run_sig_block_for_key(key)
404
+ callee = Kernel.__callee__ || raise("Unknown __callee__ for method without a signature")
405
+ method_sig = built_sig
361
406
  if !method_sig
362
- callee = __callee__ || raise("Unknown __callee__ for method without a signature")
363
- method_sig = T::Private::Methods.signature_for_method(original_method)
407
+ # Check if this wrapper's sig_block is still the active one for this key
408
+ #
409
+ # If not, the method was redefined and this wrapper is being called via
410
+ # a captured method object from reflection (e.g., `instance_method` or
411
+ # `method` from before the redefinition).
412
+ #
413
+ # We can only use maybe_run_sig_block_for_key if there was no
414
+ # redefinition, because it would otherwise call unwrap_method,
415
+ # overwriting the redefinition.
416
+ method_sig =
417
+ if T::Private::Methods._sig_block_is_current?(key, sig_block)
418
+ T::Private::Methods.maybe_run_sig_block_for_key(key)
419
+ elsif !(method_sig = current_declaration.sig_state).is_a?(Signature)
420
+ # This is essentially a permanent slow path: since the method was
421
+ # redefined before the sig block had a chance to run, we can't unwrap
422
+ # the method, as that would overwrite the redefinition, effectively
423
+ # putting the method back to its original definition. (Note that
424
+ # `run_sig` is what we call in the `sig_block`, but this one just
425
+ # doesn't unwrap.)
426
+ T::Private::Methods.run_sig(method_name, original_method, current_declaration, unwrap: false)
427
+ else
428
+ # The declaration was already consumed (e.g., module_function copies
429
+ # the first-call wrapper to the singleton class, sharing the same
430
+ # DeclarationBlock; if the instance method's sig is evaluated first,
431
+ # sig_state is a Signature when the singleton copy runs).
432
+ method_sig
433
+ end
364
434
  if !method_sig
365
- raise "`sig` not present for method `#{callee}` on #{self.inspect} but you're trying to run it anyways. " \
366
- "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. " \
367
- "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."
435
+ method_sig = T::Private::Methods.signature_for_method(original_method)
436
+ if !method_sig
437
+ raise "`sig` not present for method `#{callee}` on #{self.inspect} but you're trying to run it anyways. " \
438
+ "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. " \
439
+ "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."
440
+ end
368
441
  end
442
+ built_sig = method_sig
443
+ end
369
444
 
370
- method_sig = T::Private::Methods._unwrap_alias(
371
- method_sig,
372
- self,
373
- original_method,
374
- callee,
375
- )
445
+ # If this wrapper is being called via an alias, unwrap the alias so
446
+ # subsequent calls bypass the wrapper entirely.
447
+ if callee != method_name
448
+ T::Private::Methods._unwrap_alias(method_sig, self, original_method, callee)
376
449
  end
377
450
 
378
451
  # Should be the same logic as CallValidation.wrap_method_if_needed but we
@@ -397,6 +470,8 @@ module T::Private::Methods
397
470
  if current_declaration.final
398
471
  add_module_with_final_method(mod, method_name)
399
472
  end
473
+
474
+ key
400
475
  end
401
476
 
402
477
  # Only public so that it can be accessed in the closure for _on_method_added
@@ -432,7 +507,7 @@ module T::Private::Methods
432
507
 
433
508
  # Executes the `sig` block, and converts the resulting Declaration
434
509
  # to a Signature.
435
- def self.run_sig(method_name, original_method, declaration_block)
510
+ def self.run_sig(method_name, original_method, declaration_block, unwrap:)
436
511
  current_declaration =
437
512
  begin
438
513
  run_builder(declaration_block)
@@ -451,21 +526,29 @@ module T::Private::Methods
451
526
  Signature.new_untyped(method: original_method)
452
527
  end
453
528
 
454
- unwrap_method(signature.method.owner, signature, original_method)
529
+ if unwrap
530
+ unwrap_method(signature.method.owner, signature, original_method)
531
+ end
455
532
 
456
- # Drop this declaration. Only drop it after we've actually wrapped the
457
- # method and recorded the signature, because that might raise an exception,
458
- # leaving the declaration in a weird state if the program rescues that
459
- # exception and continues.
460
- declaration_block.blk_or_decl = nil
533
+ # Store the finished signature (dropping the Declaration in the process).
534
+ # Only do this after we've actually wrapped the method and recorded the
535
+ # signature, because that might raise an exception, leaving the
536
+ # declaration in a weird state if the program rescues that exception and
537
+ # continues.
538
+ #
539
+ # Storing the signature here allows the first-call wrapper to find its
540
+ # own signature even if it was evaluated externally (e.g., by
541
+ # run_all_sig_blocks) and @signatures_by_method[key] was later overwritten
542
+ # by a redefinition.
543
+ declaration_block.sig_state = signature
461
544
 
462
545
  signature
463
546
  end
464
547
 
465
548
  def self.run_builder(declaration_block)
466
- blk_or_decl = declaration_block.blk_or_decl
467
- return blk_or_decl if blk_or_decl.is_a?(Declaration)
468
- if blk_or_decl.nil?
549
+ sig_state = declaration_block.sig_state
550
+ return sig_state if sig_state.is_a?(Declaration)
551
+ if !sig_state.is_a?(Proc)
469
552
  raise "DeclarationBlock for #{declaration_block.mod} at #{declaration_block.loc} should have already been unwrapped"
470
553
  end
471
554
 
@@ -475,9 +558,9 @@ module T::Private::Methods
475
558
  declaration_block.override,
476
559
  declaration_block.overridable
477
560
  )
478
- decl = builder.instance_exec(&blk_or_decl).finalize!.decl
561
+ decl = builder.instance_exec(&sig_state).finalize!.decl
479
562
  # Record that we've already run `blk` once and constructed a `Declaration`
480
- declaration_block.blk_or_decl = decl
563
+ declaration_block.sig_state = decl
481
564
  decl
482
565
  end
483
566
 
@@ -536,6 +619,11 @@ module T::Private::Methods
536
619
  has_sig_block_for_key(method_to_key(method))
537
620
  end
538
621
 
622
+ # Only public because it needs to get called inside the first-call wrapper closure.
623
+ def self._sig_block_is_current?(key, sig_block)
624
+ @sig_wrappers[key].equal?(sig_block)
625
+ end
626
+
539
627
  private_class_method def self.has_sig_block_for_key(key)
540
628
  @sig_wrappers.key?(key)
541
629
  end
@@ -685,15 +773,19 @@ module T::Private::Methods
685
773
 
686
774
  module MethodHooks
687
775
  def method_added(name)
688
- ::T::Private::Methods._on_method_added(T.unsafe(self), T.unsafe(self), name)
776
+ key = ::T::Private::Methods._on_method_added(T.unsafe(self), T.unsafe(self), name)
777
+ T::Private::DeclState.current.last_method_key = key if key
689
778
  super(name)
779
+ T::Private::DeclState.current.last_method_key = nil if key
690
780
  end
691
781
  end
692
782
 
693
783
  module SingletonMethodHooks
694
784
  def singleton_method_added(name)
695
- ::T::Private::Methods._on_method_added(T.unsafe(self), singleton_class, name)
785
+ key = ::T::Private::Methods._on_method_added(T.unsafe(self), singleton_class, name)
786
+ T::Private::DeclState.current.last_method_key = key if key
696
787
  super(name)
788
+ T::Private::DeclState.current.last_method_key = nil if key
697
789
  end
698
790
  end
699
791
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.13412
4
+ version: 0.6.13414
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe