sorbet-runtime 0.5.5841
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 +7 -0
- data/lib/sorbet-runtime.rb +116 -0
- data/lib/types/_types.rb +285 -0
- data/lib/types/abstract_utils.rb +50 -0
- data/lib/types/boolean.rb +8 -0
- data/lib/types/compatibility_patches.rb +95 -0
- data/lib/types/configuration.rb +428 -0
- data/lib/types/enum.rb +349 -0
- data/lib/types/generic.rb +23 -0
- data/lib/types/helpers.rb +39 -0
- data/lib/types/interface_wrapper.rb +158 -0
- data/lib/types/non_forcing_constants.rb +51 -0
- data/lib/types/private/abstract/data.rb +36 -0
- data/lib/types/private/abstract/declare.rb +48 -0
- data/lib/types/private/abstract/hooks.rb +43 -0
- data/lib/types/private/abstract/validate.rb +128 -0
- data/lib/types/private/casts.rb +22 -0
- data/lib/types/private/class_utils.rb +111 -0
- data/lib/types/private/decl_state.rb +30 -0
- data/lib/types/private/final.rb +51 -0
- data/lib/types/private/methods/_methods.rb +460 -0
- data/lib/types/private/methods/call_validation.rb +1149 -0
- data/lib/types/private/methods/decl_builder.rb +228 -0
- data/lib/types/private/methods/modes.rb +16 -0
- data/lib/types/private/methods/signature.rb +196 -0
- data/lib/types/private/methods/signature_validation.rb +229 -0
- data/lib/types/private/mixins/mixins.rb +27 -0
- data/lib/types/private/retry.rb +10 -0
- data/lib/types/private/runtime_levels.rb +56 -0
- data/lib/types/private/sealed.rb +65 -0
- data/lib/types/private/types/not_typed.rb +23 -0
- data/lib/types/private/types/string_holder.rb +26 -0
- data/lib/types/private/types/type_alias.rb +26 -0
- data/lib/types/private/types/void.rb +34 -0
- data/lib/types/profile.rb +31 -0
- data/lib/types/props/_props.rb +161 -0
- data/lib/types/props/constructor.rb +40 -0
- data/lib/types/props/custom_type.rb +108 -0
- data/lib/types/props/decorator.rb +672 -0
- data/lib/types/props/errors.rb +8 -0
- data/lib/types/props/generated_code_validation.rb +268 -0
- data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
- data/lib/types/props/optional.rb +81 -0
- data/lib/types/props/plugin.rb +37 -0
- data/lib/types/props/pretty_printable.rb +107 -0
- data/lib/types/props/private/apply_default.rb +170 -0
- data/lib/types/props/private/deserializer_generator.rb +165 -0
- data/lib/types/props/private/parser.rb +32 -0
- data/lib/types/props/private/serde_transform.rb +192 -0
- data/lib/types/props/private/serializer_generator.rb +77 -0
- data/lib/types/props/private/setter_factory.rb +134 -0
- data/lib/types/props/serializable.rb +330 -0
- data/lib/types/props/type_validation.rb +111 -0
- data/lib/types/props/utils.rb +59 -0
- data/lib/types/props/weak_constructor.rb +67 -0
- data/lib/types/runtime_profiled.rb +24 -0
- data/lib/types/sig.rb +30 -0
- data/lib/types/struct.rb +18 -0
- data/lib/types/types/attached_class.rb +37 -0
- data/lib/types/types/base.rb +151 -0
- data/lib/types/types/class_of.rb +38 -0
- data/lib/types/types/enum.rb +42 -0
- data/lib/types/types/fixed_array.rb +60 -0
- data/lib/types/types/fixed_hash.rb +59 -0
- data/lib/types/types/intersection.rb +37 -0
- data/lib/types/types/noreturn.rb +29 -0
- data/lib/types/types/proc.rb +51 -0
- data/lib/types/types/self_type.rb +35 -0
- data/lib/types/types/simple.rb +33 -0
- data/lib/types/types/t_enum.rb +38 -0
- data/lib/types/types/type_member.rb +7 -0
- data/lib/types/types/type_parameter.rb +23 -0
- data/lib/types/types/type_template.rb +7 -0
- data/lib/types/types/type_variable.rb +31 -0
- data/lib/types/types/typed_array.rb +34 -0
- data/lib/types/types/typed_enumerable.rb +161 -0
- data/lib/types/types/typed_enumerator.rb +36 -0
- data/lib/types/types/typed_hash.rb +43 -0
- data/lib/types/types/typed_range.rb +26 -0
- data/lib/types/types/typed_set.rb +36 -0
- data/lib/types/types/union.rb +56 -0
- data/lib/types/types/untyped.rb +29 -0
- data/lib/types/utils.rb +217 -0
- metadata +223 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
module T::NonForcingConstants
|
5
|
+
# NOTE: This method is documented on the RBI in Sorbet's payload, so that it
|
6
|
+
# shows up in the hover/completion documentation via LSP.
|
7
|
+
T::Sig::WithoutRuntime.sig {params(val: BasicObject, klass: String).returns(T::Boolean)}
|
8
|
+
def self.non_forcing_is_a?(val, klass)
|
9
|
+
method_name = "T::NonForcingConstants.non_forcing_is_a?"
|
10
|
+
if klass.empty?
|
11
|
+
raise ArgumentError.new("The string given to `#{method_name}` must not be empty")
|
12
|
+
end
|
13
|
+
|
14
|
+
current_klass = T.let(nil, T.nilable(Module))
|
15
|
+
current_prefix = T.let(nil, T.nilable(String))
|
16
|
+
|
17
|
+
parts = klass.split('::')
|
18
|
+
parts.each do |part|
|
19
|
+
if current_klass.nil?
|
20
|
+
# First iteration
|
21
|
+
if part != ""
|
22
|
+
raise ArgumentError.new("The string given to `#{method_name}` must be an absolute constant reference that starts with `::`")
|
23
|
+
end
|
24
|
+
|
25
|
+
current_klass = Object
|
26
|
+
current_prefix = ''
|
27
|
+
else
|
28
|
+
if current_klass.autoload?(part)
|
29
|
+
# There's an autoload registered for that constant, which means it's not
|
30
|
+
# yet loaded. `value` can't be an instance of something not yet loaded.
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sorbet guarantees that the string is an absolutely resolved name.
|
35
|
+
search_inheritance_chain = false
|
36
|
+
if !current_klass.const_defined?(part, search_inheritance_chain)
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
current_klass = current_klass.const_get(part)
|
41
|
+
current_prefix = "#{current_prefix}::#{part}"
|
42
|
+
|
43
|
+
if !Module.===(current_klass)
|
44
|
+
raise ArgumentError.new("#{current_prefix} is not a class or module")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
return current_klass.===(val)
|
50
|
+
end
|
51
|
+
end
|
@@ -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,48 @@
|
|
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} is already declared as abstract"
|
11
|
+
end
|
12
|
+
if T::Private::Final.final_module?(mod)
|
13
|
+
raise "#{mod} was already declared as final and cannot be declared as abstract"
|
14
|
+
end
|
15
|
+
|
16
|
+
Abstract::Data.set(mod, :can_have_abstract_methods, true)
|
17
|
+
Abstract::Data.set(mod.singleton_class, :can_have_abstract_methods, true)
|
18
|
+
Abstract::Data.set(mod, :abstract_type, type)
|
19
|
+
|
20
|
+
mod.extend(Abstract::Hooks)
|
21
|
+
mod.extend(T::InterfaceWrapper::Helpers)
|
22
|
+
|
23
|
+
if mod.is_a?(Class)
|
24
|
+
if type == :interface
|
25
|
+
# Since `interface!` is just `abstract!` with some extra validation, we could technically
|
26
|
+
# allow this, but it's unclear there are good use cases, and it might be confusing.
|
27
|
+
raise "Classes can't be interfaces. Use `abstract!` instead of `interface!`."
|
28
|
+
end
|
29
|
+
|
30
|
+
if mod.instance_method(:initialize).owner == mod
|
31
|
+
raise "You must call `abstract!` *before* defining an initialize method"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Don't need to silence warnings via without_ruby_warnings when calling
|
35
|
+
# define_method because of the guard above
|
36
|
+
|
37
|
+
mod.send(:define_method, :initialize) do |*args, &blk|
|
38
|
+
if self.class == mod
|
39
|
+
raise "#{mod} is declared as abstract; it cannot be instantiated"
|
40
|
+
end
|
41
|
+
super(*args, &blk)
|
42
|
+
end
|
43
|
+
if mod.respond_to?(:ruby2_keywords, true)
|
44
|
+
mod.send(:ruby2_keywords, :initialize)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
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.override
|
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::Configuration.inline_type_error_handler(e)
|
18
|
+
value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,111 @@
|
|
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
|
+
T::Configuration.without_ruby_warnings do
|
36
|
+
@mod.send(:define_method, @old_method.name, @old_method) # rubocop:disable PrisonGuard/UsePublicSend
|
37
|
+
end
|
38
|
+
else
|
39
|
+
# The original method was in an ancestor. Restore it by removing the overriding method.
|
40
|
+
@mod.send(:remove_method, @old_method.name) # rubocop:disable PrisonGuard/UsePublicSend
|
41
|
+
end
|
42
|
+
|
43
|
+
# Restore the visibility. Note that we need to do this even when we call remove_method
|
44
|
+
# above, because the module may have set custom visibility for a method it inherited.
|
45
|
+
@mod.send(@visibility, @old_method.name) # rubocop:disable PrisonGuard/UsePublicSend
|
46
|
+
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def bind(obj)
|
51
|
+
@old_method.bind(obj)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
@old_method.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# `name` must be an instance method (for class methods, pass in mod.singleton_class)
|
60
|
+
private_class_method def self.visibility_method_name(mod, name)
|
61
|
+
if mod.public_method_defined?(name)
|
62
|
+
:public
|
63
|
+
elsif mod.protected_method_defined?(name)
|
64
|
+
:protected
|
65
|
+
elsif mod.private_method_defined?(name)
|
66
|
+
:private
|
67
|
+
else
|
68
|
+
raise NameError.new("undefined method `#{name}` for `#{mod}`")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Replaces a method, either by overwriting it (if it is defined directly on `mod`) or by
|
73
|
+
# overriding it (if it is defined by one of mod's ancestors). Returns a ReplacedMethod instance
|
74
|
+
# on which you can call `bind(...).call(...)` to call the original method, or `restore` to
|
75
|
+
# restore the original method (by overwriting or removing the override).
|
76
|
+
def self.replace_method(mod, name, &blk)
|
77
|
+
original_method = mod.instance_method(name)
|
78
|
+
original_visibility = visibility_method_name(mod, name)
|
79
|
+
original_owner = original_method.owner
|
80
|
+
|
81
|
+
mod.ancestors.each do |ancestor|
|
82
|
+
break if ancestor == mod
|
83
|
+
if ancestor == original_owner
|
84
|
+
# If we get here, that means the method we're trying to replace exists on a *prepended*
|
85
|
+
# mixin, which means in order to supersede it, we'd need to create a method on a new
|
86
|
+
# module that we'd prepend before `ancestor`. The problem with that approach is there'd
|
87
|
+
# be no way to remove that new module after prepending it, so we'd be left with these
|
88
|
+
# empty anonymous modules in the ancestor chain after calling `restore`.
|
89
|
+
#
|
90
|
+
# That's not necessarily a deal breaker, but for now, we're keeping it as unsupported.
|
91
|
+
raise "You're trying to replace `#{name}` on `#{mod}`, but that method exists in a " \
|
92
|
+
"prepended module (#{ancestor}), which we don't currently support. Talk to " \
|
93
|
+
"#dev-productivity for help."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
overwritten = original_owner == mod
|
98
|
+
T::Configuration.without_ruby_warnings do
|
99
|
+
T::Private::DeclState.current.without_on_method_added do
|
100
|
+
mod.send(:define_method, name, &blk) # rubocop:disable PrisonGuard/UsePublicSend
|
101
|
+
if blk.arity < 0 && mod.respond_to?(:ruby2_keywords, true)
|
102
|
+
mod.send(:ruby2_keywords, name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
mod.send(original_visibility, name) # rubocop:disable PrisonGuard/UsePublicSend
|
107
|
+
new_method = mod.instance_method(name)
|
108
|
+
|
109
|
+
ReplacedMethod.new(mod, original_method, new_method, overwritten, original_visibility)
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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
|
+
attr_accessor :skip_on_method_added
|
15
|
+
|
16
|
+
def reset!
|
17
|
+
self.active_declaration = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def without_on_method_added
|
21
|
+
begin
|
22
|
+
# explicit 'self' is needed here
|
23
|
+
old_value = self.skip_on_method_added
|
24
|
+
self.skip_on_method_added = true
|
25
|
+
yield
|
26
|
+
ensure
|
27
|
+
self.skip_on_method_added = old_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|