sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. metadata +122 -15
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # We need to associate data with abstract modules. We could add instance methods to them that
5
+ # access ivars, but those methods will unnecessarily pollute the module namespace, and they'd
6
+ # have access to other private state and methods that they don't actually need. We also need to
7
+ # associate data with arbitrary classes/modules that implement abstract mixins, where we don't
8
+ # control the interface at all. So, we access data via these `get` and `set` methods.
9
+ #
10
+ # Using instance_variable_get/set here is gross, but the alternative is to use a hash keyed on
11
+ # `mod`, and we can't trust that arbitrary modules can be added to those, because there are lurky
12
+ # modules that override the `hash` method with something completely broken.
13
+ module T::Private::Abstract::Data
14
+ def self.get(mod, key)
15
+ mod.instance_variable_get("@opus_abstract__#{key}") # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
16
+ end
17
+
18
+ def self.set(mod, key, value)
19
+ mod.instance_variable_set("@opus_abstract__#{key}", value) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
20
+ end
21
+
22
+ def self.key?(mod, key)
23
+ mod.instance_variable_defined?("@opus_abstract__#{key}")
24
+ end
25
+
26
+ # Works like `setdefault` in Python. If key has already been set, return its value. If not,
27
+ # insert `key` with a value of `default` and return `default`.
28
+ def self.set_default(mod, key, default)
29
+ if self.key?(mod, key)
30
+ self.get(mod, key)
31
+ else
32
+ self.set(mod, key, default)
33
+ default
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Private::Abstract::Declare
5
+ Abstract = T::Private::Abstract
6
+ AbstractUtils = T::AbstractUtils
7
+
8
+ def self.declare_abstract(mod, type:)
9
+ if AbstractUtils.abstract_module?(mod)
10
+ raise "#{mod.name} is already declared as abstract"
11
+ end
12
+
13
+ Abstract::Data.set(mod, :can_have_abstract_methods, true)
14
+ Abstract::Data.set(mod.singleton_class, :can_have_abstract_methods, true)
15
+ Abstract::Data.set(mod, :abstract_type, type)
16
+
17
+ mod.extend(Abstract::Hooks)
18
+ mod.extend(T::InterfaceWrapper::Helpers)
19
+
20
+ if mod.is_a?(Class)
21
+ if type == :interface
22
+ # Since `interface!` is just `abstract!` with some extra validation, we could technically
23
+ # allow this, but it's unclear there are good use cases, and it might be confusing.
24
+ raise "Classes can't be interfaces. Use `abstract!` instead of `interface!`."
25
+ end
26
+
27
+ if mod.instance_method(:initialize).owner == mod
28
+ raise "You must call `abstract!` *before* defining an initialize method"
29
+ end
30
+
31
+ mod.send(:define_method, :initialize) do |*args, &blk|
32
+ if self.class == mod
33
+ raise "#{mod} is declared as abstract; it cannot be instantiated"
34
+ end
35
+ super(*args, &blk)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Private::Abstract::Hooks
5
+ # This will become the self.extend_object method on a module that extends Abstract::Hooks.
6
+ # It gets called when *that* module gets extended in another class/module (similar to the
7
+ # `extended` hook, but this gets calls before the ancestors of `other` get modified, which
8
+ # is important for our validation).
9
+ private def extend_object(other)
10
+ T::Private::Abstract::Data.set(self, :last_used_by, other)
11
+ super
12
+ end
13
+
14
+ # This will become the self.append_features method on a module that extends Abstract::Hooks.
15
+ # It gets called when *that* module gets included in another class/module (similar to the
16
+ # `included` hook, but this gets calls before the ancestors of `other` get modified, which
17
+ # is important for our validation).
18
+ private def append_features(other)
19
+ T::Private::Abstract::Data.set(self, :last_used_by, other)
20
+ super
21
+ end
22
+
23
+ # This will become the self.inherited method on a class that extends Abstract::Hooks.
24
+ # It gets called when *that* class gets inherited by another class.
25
+ private def inherited(other)
26
+ super
27
+ # `self` may not actually be abstract -- it could be a concrete class that inherited from an
28
+ # abstract class. We only need to check this in `inherited` because, for modules being included
29
+ # or extended, the concrete ones won't have these hooks at all. This is just an optimization.
30
+ return if !T::AbstractUtils.abstract_module?(self)
31
+
32
+ T::Private::Abstract::Data.set(self, :last_used_by, other)
33
+ end
34
+
35
+ # This will become the self.prepended method on a module that extends Abstract::Hooks.
36
+ # It will get called when *that* module gets prepended in another class/module.
37
+ private def prepended(other)
38
+ # Prepending abstract methods is weird. You'd only be able to override them via other prepended
39
+ # modules, or in subclasses. Punt until we have a use case.
40
+ Kernel.raise "Prepending abstract mixins is not currently supported. Please explain your use case " \
41
+ "to #dev-productivity."
42
+ end
43
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Private::Abstract::Validate
5
+ Abstract = T::Private::Abstract
6
+ AbstractUtils = T::AbstractUtils
7
+ Methods = T::Private::Methods
8
+ SignatureValidation = T::Private::Methods::SignatureValidation
9
+
10
+ def self.validate_abstract_module(mod)
11
+ type = Abstract::Data.get(mod, :abstract_type)
12
+ validate_interface(mod) if type == :interface
13
+ end
14
+
15
+ # Validates a class/module with an abstract class/module as an ancestor. This must be called
16
+ # after all methods on `mod` have been defined.
17
+ def self.validate_subclass(mod)
18
+ can_have_abstract_methods = !T::Private::Abstract::Data.get(mod, :can_have_abstract_methods)
19
+ unimplemented_methods = []
20
+
21
+ T::AbstractUtils.declared_abstract_methods_for(mod).each do |abstract_method|
22
+ implementation_method = mod.instance_method(abstract_method.name)
23
+ if AbstractUtils.abstract_method?(implementation_method)
24
+ # Note that when we end up here, implementation_method might not be the same as
25
+ # abstract_method; the latter could've been overridden by another abstract method. In either
26
+ # case, if we have a concrete definition in an ancestor, that will end up as the effective
27
+ # implementation (see CallValidation.wrap_method_if_needed), so that's what we'll validate
28
+ # against.
29
+ implementation_method = T.unsafe(nil)
30
+ mod.ancestors.each do |ancestor|
31
+ if ancestor.instance_methods.include?(abstract_method.name)
32
+ method = ancestor.instance_method(abstract_method.name)
33
+ T::Private::Methods.maybe_run_sig_block_for_method(method)
34
+ if !T::AbstractUtils.abstract_method?(method)
35
+ implementation_method = method
36
+ break
37
+ end
38
+ end
39
+ end
40
+ if !implementation_method
41
+ # There's no implementation
42
+ if can_have_abstract_methods
43
+ unimplemented_methods << describe_method(abstract_method)
44
+ end
45
+ next # Nothing to validate
46
+ end
47
+ end
48
+
49
+ implementation_signature = Methods.signature_for_method(implementation_method)
50
+ # When a signature exists and the method is defined directly on `mod`, we skip the validation
51
+ # here, because it will have already been done when the method was defined (by
52
+ # T::Private::Methods._on_method_added).
53
+ next if implementation_signature&.owner == mod
54
+
55
+ # We validate the remaining cases here: (a) methods defined directly on `mod` without a
56
+ # signature and (b) methods from ancestors (note that these ancestors can come before or
57
+ # after the abstract module in the inheritance chain -- the former coming from
58
+ # walking `mod.ancestors` above).
59
+ abstract_signature = Methods.signature_for_method(abstract_method)
60
+ # We allow implementation methods to be defined without a signature.
61
+ # In that case, get its untyped signature.
62
+ implementation_signature ||= Methods::Signature.new_untyped(
63
+ method: implementation_method,
64
+ mode: Methods::Modes.implementation
65
+ )
66
+ SignatureValidation.validate_override_shape(implementation_signature, abstract_signature)
67
+ SignatureValidation.validate_override_types(implementation_signature, abstract_signature)
68
+ end
69
+
70
+ method_type = mod.singleton_class? ? "class" : "instance"
71
+ if !unimplemented_methods.empty?
72
+ raise "Missing implementation for abstract #{method_type} method(s) in #{mod}:\n" \
73
+ "#{unimplemented_methods.join("\n")}\n" \
74
+ "If #{mod} is meant to be an abstract class/module, you can call " \
75
+ "`abstract!` or `interface!`. Otherwise, you must implement the method(s)."
76
+ end
77
+ end
78
+
79
+ private_class_method def self.validate_interface_all_abstract(mod, method_names)
80
+ violations = method_names.map do |method_name|
81
+ method = mod.instance_method(method_name)
82
+ if !AbstractUtils.abstract_method?(method)
83
+ describe_method(method, show_owner: false)
84
+ end
85
+ end.compact
86
+
87
+ if !violations.empty?
88
+ raise "`#{mod}` is declared as an interface, but the following methods are not declared " \
89
+ "with `abstract`:\n#{violations.join("\n")}"
90
+ end
91
+ end
92
+
93
+ private_class_method def self.validate_interface(mod)
94
+ interface_methods = T::Utils.methods_excluding_object(mod)
95
+ validate_interface_all_abstract(mod, interface_methods)
96
+ validate_interface_all_public(mod, interface_methods)
97
+ end
98
+
99
+ private_class_method def self.validate_interface_all_public(mod, method_names)
100
+ violations = method_names.map do |method_name|
101
+ if !mod.public_method_defined?(method_name)
102
+ describe_method(mod.instance_method(method_name), show_owner: false)
103
+ end
104
+ end.compact
105
+
106
+ if !violations.empty?
107
+ raise "All methods on an interface must be public. If you intend to have non-public " \
108
+ "methods, declare your class/module using `abstract!` instead of `interface!`. " \
109
+ "The following methods on `#{mod}` are not public: \n#{violations.join("\n")}"
110
+ end
111
+ end
112
+
113
+ private_class_method def self.describe_method(method, show_owner: true)
114
+ if method.source_location
115
+ loc = method.source_location.join(':')
116
+ else
117
+ loc = "<unknown location>"
118
+ end
119
+
120
+ if show_owner
121
+ owner = " declared in #{method.owner}"
122
+ else
123
+ owner = ""
124
+ end
125
+
126
+ " * `#{method.name}`#{owner} at #{loc}"
127
+ end
128
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Private
5
+ module Casts
6
+ def self.cast(value, type, cast_method:)
7
+ begin
8
+ error = T::Utils.coerce(type).error_message_for_obj(value)
9
+ return value unless error
10
+
11
+ caller_loc = T.must(caller_locations(2..2)).first
12
+
13
+ suffix = "Caller: #{T.must(caller_loc).path}:#{T.must(caller_loc).lineno}"
14
+
15
+ raise TypeError.new("#{cast_method}: #{error}\n#{suffix}")
16
+ rescue TypeError => e # raise into rescue to ensure e.backtrace is populated
17
+ T::Private::ErrorHandler.handle_inline_type_error(e)
18
+ value
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ # Cut down version of Chalk::Tools::ClassUtils with only :replace_method functionality.
5
+ # Extracted to a separate namespace so the type system can be used standalone.
6
+ module T::Private::ClassUtils
7
+ class ReplacedMethod
8
+ def initialize(mod, old_method, new_method, overwritten, visibility)
9
+ if old_method.name != new_method.name
10
+ raise "Method names must match. old=#{old_method.name} new=#{new_method.name}"
11
+ end
12
+ @mod = mod
13
+ @old_method = old_method
14
+ @new_method = new_method
15
+ @overwritten = overwritten
16
+ @name = old_method.name
17
+ @visibility = visibility
18
+ @restored = false
19
+ end
20
+
21
+ def restore
22
+ # The check below would also catch this, but this makes the failure mode much clearer
23
+ if @restored
24
+ raise "Method '#{@name}' on '#{@mod}' was already restored"
25
+ end
26
+
27
+ if @mod.instance_method(@name) != @new_method
28
+ raise "Trying to restore #{@mod}##{@name} but the method has changed since the call to replace_method"
29
+ end
30
+
31
+ @restored = true
32
+
33
+ if @overwritten
34
+ # The original method was overwritten. Overwrite again to restore it.
35
+ @mod.send(:define_method, @old_method.name, @old_method) # rubocop:disable PrisonGuard/UsePublicSend
36
+ else
37
+ # The original method was in an ancestor. Restore it by removing the overriding method.
38
+ @mod.send(:remove_method, @old_method.name) # rubocop:disable PrisonGuard/UsePublicSend
39
+ end
40
+
41
+ # Restore the visibility. Note that we need to do this even when we call remove_method
42
+ # above, because the module may have set custom visibility for a method it inherited.
43
+ @mod.send(@visibility, @old_method.name) # rubocop:disable PrisonGuard/UsePublicSend
44
+
45
+ nil
46
+ end
47
+
48
+ def bind(obj)
49
+ @old_method.bind(obj)
50
+ end
51
+
52
+ def to_s
53
+ @old_method.to_s
54
+ end
55
+ end
56
+
57
+ # `name` must be an instance method (for class methods, pass in mod.singleton_class)
58
+ private_class_method def self.visibility_method_name(mod, name)
59
+ if mod.public_method_defined?(name)
60
+ :public
61
+ elsif mod.protected_method_defined?(name)
62
+ :protected
63
+ elsif mod.private_method_defined?(name)
64
+ :private
65
+ else
66
+ raise NameError.new("undefined method `#{name}` for `#{mod}`")
67
+ end
68
+ end
69
+
70
+ # Replaces a method, either by overwriting it (if it is defined directly on `mod`) or by
71
+ # overriding it (if it is defined by one of mod's ancestors). Returns a ReplacedMethod instance
72
+ # on which you can call `bind(...).call(...)` to call the original method, or `restore` to
73
+ # restore the original method (by overwriting or removing the override).
74
+ def self.replace_method(mod, name, &blk)
75
+ original_method = mod.instance_method(name)
76
+ original_visibility = visibility_method_name(mod, name)
77
+ original_owner = original_method.owner
78
+
79
+ mod.ancestors.each do |ancestor|
80
+ break if ancestor == mod
81
+ if ancestor == original_owner
82
+ # If we get here, that means the method we're trying to replace exists on a *prepended*
83
+ # mixin, which means in order to supersede it, we'd need to create a method on a new
84
+ # module that we'd prepend before `ancestor`. The problem with that approach is there'd
85
+ # be no way to remove that new module after prepending it, so we'd be left with these
86
+ # empty anonymous modules in the ancestor chain after calling `restore`.
87
+ #
88
+ # That's not necessarily a deal breaker, but for now, we're keeping it as unsupported.
89
+ raise "You're trying to replace `#{name}` on `#{mod}`, but that method exists in a " \
90
+ "prepended module (#{ancestor}), which we don't currently support. Talk to " \
91
+ "#dev-productivity for help."
92
+ end
93
+ end
94
+
95
+ overwritten = original_owner == mod
96
+ mod.send(:define_method, name, &blk) # rubocop:disable PrisonGuard/UsePublicSend
97
+ mod.send(original_visibility, name) # rubocop:disable PrisonGuard/UsePublicSend
98
+ new_method = mod.instance_method(name)
99
+
100
+ ReplacedMethod.new(mod, original_method, new_method, overwritten, original_visibility)
101
+ end
102
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ class T::Private::DeclState
5
+ def self.current
6
+ Thread.current[:opus_types__decl_state] ||= self.new
7
+ end
8
+
9
+ def self.current=(other)
10
+ Thread.current[:opus_types__decl_state] = other
11
+ end
12
+
13
+ attr_accessor :active_declaration
14
+
15
+ def reset!
16
+ self.active_declaration = nil
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module T::Private::ErrorHandler
5
+ # Handle a rescued TypeError. This will pass the TypeError to the
6
+ # T::Configuration.inline_type_error_handler so that the user can override
7
+ # error handling if they wish. If no inline_type_error_handler is set, this
8
+ # method will call handle_inline_type_error_default, which raises the error.
9
+ def self.handle_inline_type_error(type_error)
10
+ T::Configuration.inline_type_error_handler(type_error)
11
+ nil
12
+ end
13
+
14
+ # Handle a sig declaration failure. This allows users to override the behavior
15
+ # when a sig decl fails. If T::Configuration.sig_builder_error_handler
16
+ # is unset, this method will call handle_sig_builder_error_default.
17
+ def self.handle_sig_builder_error(error, location)
18
+ T::Configuration.sig_builder_error_handler(error, location)
19
+ nil
20
+ end
21
+
22
+ # Handle a sig build validation failure. This allows users to override the
23
+ # behavior when a sig build fails. If T::Configuration.sig_validation_error_handler
24
+ # is unset, this method will call handle_sig_validation_error_default.
25
+ def self.handle_sig_validation_error(error, opts={})
26
+ T::Configuration.sig_validation_error_handler(error, opts)
27
+ nil
28
+ end
29
+
30
+ # Handle a sig call validation failure. This allows users to override the
31
+ # behavior when a sig call fails. If T::Configuration.call_validation_error_handler
32
+ # is unset, this method will call handle_call_validation_error_default.
33
+ def self.handle_call_validation_error(signature, opts={})
34
+ T::Configuration.call_validation_error_handler(signature, opts)
35
+ nil
36
+ end
37
+ end
@@ -0,0 +1,344 @@
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
+
10
+ ARG_NOT_PROVIDED = Object.new
11
+ PROC_TYPE = Object.new
12
+
13
+ DeclarationBlock = Struct.new(:mod, :loc, :blk)
14
+
15
+ def self.declare_sig(mod, &blk)
16
+ install_hooks(mod)
17
+
18
+ if T::Private::DeclState.current.active_declaration
19
+ T::Private::DeclState.current.active_declaration = nil
20
+ raise "You called sig twice without declaring a method inbetween"
21
+ end
22
+
23
+ loc = caller_locations(2, 1).first
24
+
25
+ T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk)
26
+
27
+ nil
28
+ end
29
+
30
+ def self.start_proc
31
+ DeclBuilder.new(PROC_TYPE)
32
+ end
33
+
34
+ def self.finalize_proc(decl)
35
+ decl.finalized = true
36
+
37
+ if decl.mode != Modes.standard
38
+ raise "Procs cannot have override/abstract modifiers"
39
+ end
40
+ if decl.mod != PROC_TYPE
41
+ raise "You are passing a DeclBuilder as a type. Did you accidentally use `self` inside a `sig` block?"
42
+ end
43
+ if decl.returns == ARG_NOT_PROVIDED
44
+ raise "Procs must specify a return type"
45
+ end
46
+ if decl.soft_notify != ARG_NOT_PROVIDED
47
+ raise "Procs cannot use .soft"
48
+ end
49
+
50
+ if decl.params == ARG_NOT_PROVIDED
51
+ decl.params = {}
52
+ end
53
+
54
+ T::Types::Proc.new(decl.params, decl.returns) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
55
+ end
56
+
57
+ # See docs at T::Utils.register_forwarder.
58
+ def self.register_forwarder(from_method, to_method, mode: Modes.overridable, remove_first_param: false)
59
+ # Normalize the method (see comment in signature_for_key).
60
+ from_method = from_method.owner.instance_method(from_method.name)
61
+
62
+ from_key = method_to_key(from_method)
63
+ maybe_run_sig_block_for_key(from_key)
64
+ if @signatures_by_method.key?(from_key)
65
+ raise "#{from_method} already has a method signature"
66
+ end
67
+
68
+ from_params = from_method.parameters
69
+ if from_params.length != 2 || from_params[0][0] != :rest || from_params[1][0] != :block
70
+ raise "forwarder methods should take a single splat param and a block param. `#{from_method}` " \
71
+ "takes these params: #{from_params}. For help, ask #dev-productivity."
72
+ end
73
+
74
+ # If there's already a signature for to_method, we get `parameters` from there, to enable
75
+ # chained forwarding. NB: we don't use `signature_for_key` here, because the normalization it
76
+ # does is broken when `to_method` has been clobbered by another method.
77
+ to_key = method_to_key(to_method)
78
+ maybe_run_sig_block_for_key(to_key)
79
+ to_params = @signatures_by_method[to_key]&.parameters || to_method.parameters
80
+
81
+ if remove_first_param
82
+ to_params = to_params[1..-1]
83
+ end
84
+
85
+ # We don't bother trying to preserve any types from to_signature because this won't be
86
+ # statically analyzed, and the types will just get checked when the forwarding happens.
87
+ from_signature = Signature.new_untyped(method: from_method, mode: mode, parameters: to_params)
88
+ @signatures_by_method[from_key] = from_signature
89
+ end
90
+
91
+ # Returns the signature for a method whose definition was preceded by `sig`.
92
+ #
93
+ # @param method [UnboundMethod]
94
+ # @return [T::Private::Methods::Signature]
95
+ def self.signature_for_method(method)
96
+ signature_for_key(method_to_key(method))
97
+ end
98
+
99
+ private_class_method def self.signature_for_key(key)
100
+ maybe_run_sig_block_for_key(key)
101
+
102
+ # If a subclass Sub inherits a method `foo` from Base, then
103
+ # Sub.instance_method(:foo) != Base.instance_method(:foo) even though they resolve to the
104
+ # same method. Similarly, Foo.method(:bar) != Foo.singleton_class.instance_method(:bar).
105
+ # So, we always do the look up by the method on the owner (Base in this example).
106
+ @signatures_by_method[key]
107
+ end
108
+
109
+ # Only public because it needs to get called below inside the replace_method blocks below.
110
+ def self._on_method_added(hook_mod, method_name, is_singleton_method: false)
111
+ current_declaration = T::Private::DeclState.current.active_declaration
112
+ return if !current_declaration
113
+ T::Private::DeclState.current.reset!
114
+
115
+ mod = is_singleton_method ? hook_mod.singleton_class : hook_mod
116
+ original_method = mod.instance_method(method_name)
117
+
118
+ sig_block = lambda do
119
+ T::Private::Methods.run_sig(hook_mod, method_name, original_method, current_declaration)
120
+ end
121
+
122
+ # Always replace the original method with this wrapper,
123
+ # which is called only on the *first* invocation.
124
+ # This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
125
+ # (or unwrap back to the original method).
126
+ new_method = nil
127
+ T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
128
+ if !T::Private::Methods.has_sig_block_for_method(new_method)
129
+ # This should only happen if the user used alias_method to grab a handle
130
+ # to the original pre-unwound `sig` method. I guess we'll just proxy the
131
+ # call forever since we don't know who is holding onto this handle to
132
+ # replace it.
133
+ new_new_method = mod.instance_method(method_name)
134
+ if new_method == new_new_method
135
+ raise "`sig` not present for method `#{method_name}` but you're trying to run it anyways. " \
136
+ "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. " \
137
+ "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. " \
138
+ "Contact #dev-productivity if you're really stuck."
139
+ end
140
+ return new_new_method.bind(self).call(*args, &blk)
141
+ end
142
+
143
+ method_sig = T::Private::Methods.run_sig_block_for_method(new_method)
144
+
145
+ # Should be the same logic as CallValidation.wrap_method_if_needed but we
146
+ # don't want that extra layer of indirection in the callstack
147
+ if method_sig.mode == T::Private::Methods::Modes.abstract
148
+ # We're in an interface method, keep going up the chain
149
+ if defined?(super)
150
+ super(*args, &blk)
151
+ else
152
+ raise NotImplementedError.new("The method `#{method_sig.method_name}` on #{mod} is declared as `abstract`. It does not have an implementation.")
153
+ end
154
+ # Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
155
+ # make sure to keep changes in sync.
156
+ elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
157
+ CallValidation.validate_call(self, original_method, method_sig, args, blk)
158
+ else
159
+ original_method.bind(self).call(*args, &blk)
160
+ end
161
+ end
162
+
163
+ new_method = mod.instance_method(method_name)
164
+ @sig_wrappers[method_to_key(new_method)] = sig_block
165
+ end
166
+
167
+ def self.sig_error(loc, message)
168
+ raise(
169
+ ArgumentError.new(
170
+ "#{loc.path}:#{loc.lineno}: Error interpreting `sig`:\n #{message}\n\n"
171
+ )
172
+ )
173
+ end
174
+
175
+ # Executes the `sig` block, and converts the resulting Declaration
176
+ # to a Signature.
177
+ def self.run_sig(hook_mod, method_name, original_method, declaration_block)
178
+ current_declaration =
179
+ begin
180
+ run_builder(declaration_block)
181
+ rescue DeclBuilder::BuilderError => e
182
+ T::Private::ErrorHandler.handle_sig_builder_error(e, declaration_block.loc)
183
+ nil
184
+ end
185
+
186
+ signature =
187
+ if current_declaration
188
+ build_sig(hook_mod, method_name, original_method, current_declaration, declaration_block.loc)
189
+ else
190
+ Signature.new_untyped(method: original_method)
191
+ end
192
+
193
+ unwrap_method(hook_mod, signature, original_method)
194
+ signature
195
+ end
196
+
197
+ def self.run_builder(declaration_block)
198
+ builder = DeclBuilder.new(declaration_block.mod)
199
+ builder
200
+ .instance_exec(&declaration_block.blk)
201
+ .finalize!
202
+ .decl
203
+ end
204
+
205
+ def self.build_sig(hook_mod, method_name, original_method, current_declaration, loc)
206
+ begin
207
+ # We allow `sig` in the current module's context (normal case) and inside `class << self`
208
+ if hook_mod != current_declaration.mod && hook_mod.singleton_class != current_declaration.mod
209
+ raise "A method (#{method_name}) is being added on a different class/module (#{hook_mod}) than the " \
210
+ "last call to `sig` (#{current_declaration.mod}). Make sure each call " \
211
+ "to `sig` is immediately followed by a method definition on the same " \
212
+ "class/module."
213
+ end
214
+
215
+ if current_declaration.returns.equal?(ARG_NOT_PROVIDED)
216
+ sig_error(loc, "You must provide a return type; use the `.returns` or `.void` builder methods. Method: #{original_method}")
217
+ end
218
+
219
+ signature = Signature.new(
220
+ method: original_method,
221
+ method_name: method_name,
222
+ raw_arg_types: current_declaration.params,
223
+ raw_return_type: current_declaration.returns,
224
+ bind: current_declaration.bind,
225
+ mode: current_declaration.mode,
226
+ check_level: current_declaration.checked,
227
+ soft_notify: current_declaration.soft_notify,
228
+ override_allow_incompatible: current_declaration.override_allow_incompatible,
229
+ generated: current_declaration.generated,
230
+ )
231
+
232
+ SignatureValidation.validate(signature)
233
+ signature
234
+ rescue => e
235
+ super_method = original_method&.super_method
236
+ super_signature = signature_for_method(super_method) if super_method
237
+
238
+ T::Private::ErrorHandler.handle_sig_validation_error(
239
+ e,
240
+ method: original_method,
241
+ declaration: current_declaration,
242
+ signature: signature,
243
+ super_signature: super_signature
244
+ )
245
+
246
+ Signature.new_untyped(method: original_method)
247
+ end
248
+ end
249
+
250
+ def self.unwrap_method(hook_mod, signature, original_method)
251
+ maybe_wrapped_method = CallValidation.wrap_method_if_needed(signature.method.owner, signature, original_method)
252
+ @signatures_by_method[method_to_key(maybe_wrapped_method)] = signature
253
+ end
254
+
255
+ def self.has_sig_block_for_method(method)
256
+ has_sig_block_for_key(method_to_key(method))
257
+ end
258
+
259
+ private_class_method def self.has_sig_block_for_key(key)
260
+ @sig_wrappers.key?(key)
261
+ end
262
+
263
+ def self.maybe_run_sig_block_for_method(method)
264
+ maybe_run_sig_block_for_key(method_to_key(method))
265
+ end
266
+
267
+ private_class_method def self.maybe_run_sig_block_for_key(key)
268
+ run_sig_block_for_key(key) if has_sig_block_for_key(key)
269
+ end
270
+
271
+ def self.run_sig_block_for_method(method)
272
+ run_sig_block_for_key(method_to_key(method))
273
+ end
274
+
275
+ private_class_method def self.run_sig_block_for_key(key)
276
+ blk = @sig_wrappers[key]
277
+ if !blk
278
+ raise "No `sig` wrapper for #{key_to_method(key)}"
279
+ end
280
+
281
+ begin
282
+ sig = blk.call
283
+ rescue
284
+ @sigs_that_raised[key] = true
285
+ raise
286
+ end
287
+ if @sigs_that_raised[key]
288
+ raise "A previous invocation of #{key_to_method(key)} raised, and the current one succeeded. Please don't do that."
289
+ end
290
+
291
+ @sig_wrappers.delete(key)
292
+ sig
293
+ end
294
+
295
+ def self.run_all_sig_blocks
296
+ loop do
297
+ break if @sig_wrappers.empty?
298
+ key, _ = @sig_wrappers.first
299
+ run_sig_block_for_key(key)
300
+ end
301
+ end
302
+
303
+ def self.install_hooks(mod)
304
+ return if @installed_hooks.include?(mod)
305
+ @installed_hooks << mod
306
+
307
+ if mod.singleton_class?
308
+ install_singleton_method_added_hook(mod)
309
+ install_singleton_method_added_hook(mod.singleton_class)
310
+ else
311
+ original_method = T::Private::ClassUtils.replace_method(mod.singleton_class, :method_added) do |name|
312
+ T::Private::Methods._on_method_added(self, name)
313
+ original_method.bind(self).call(name)
314
+ end
315
+
316
+ install_singleton_method_added_hook(mod.singleton_class)
317
+ end
318
+ end
319
+
320
+ private_class_method def self.install_singleton_method_added_hook(singleton_klass)
321
+ attached = nil
322
+ original_singleton_method = T::Private::ClassUtils.replace_method(singleton_klass, :singleton_method_added) do |name|
323
+ attached = self
324
+ T::Private::Methods._on_method_added(self, name, is_singleton_method: true)
325
+ # This will be nil when this gets called for the addition of this method itself. We
326
+ # call it below to compensate.
327
+ if original_singleton_method
328
+ original_singleton_method.bind(self).call(name)
329
+ end
330
+ end
331
+ # See the comment above
332
+ original_singleton_method.bind(attached).call(:singleton_method_added)
333
+ end
334
+
335
+ private_class_method def self.method_to_key(method)
336
+ "#{method.owner.object_id}##{method.name}"
337
+ end
338
+
339
+ private_class_method def self.key_to_method(key)
340
+ id, name = key.split("#")
341
+ obj = ObjectSpace._id2ref(id.to_i) # rubocop:disable PrisonGuard/NoDynamicConstAccess
342
+ obj.instance_method(name)
343
+ end
344
+ end