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.
- checksums.yaml +5 -5
- data/lib/sorbet-runtime.rb +100 -0
- data/lib/types/_types.rb +245 -0
- data/lib/types/abstract_utils.rb +50 -0
- data/lib/types/boolean.rb +8 -0
- data/lib/types/compatibility_patches.rb +37 -0
- data/lib/types/configuration.rb +368 -0
- data/lib/types/generic.rb +23 -0
- data/lib/types/helpers.rb +31 -0
- data/lib/types/interface_wrapper.rb +158 -0
- data/lib/types/private/abstract/data.rb +36 -0
- data/lib/types/private/abstract/declare.rb +39 -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 +102 -0
- data/lib/types/private/decl_state.rb +18 -0
- data/lib/types/private/error_handler.rb +37 -0
- data/lib/types/private/methods/_methods.rb +344 -0
- data/lib/types/private/methods/call_validation.rb +1177 -0
- data/lib/types/private/methods/decl_builder.rb +275 -0
- data/lib/types/private/methods/modes.rb +18 -0
- data/lib/types/private/methods/signature.rb +196 -0
- data/lib/types/private/methods/signature_validation.rb +232 -0
- data/lib/types/private/mixins/mixins.rb +27 -0
- data/lib/types/private/runtime_levels.rb +41 -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/void.rb +33 -0
- data/lib/types/profile.rb +27 -0
- data/lib/types/props/_props.rb +165 -0
- data/lib/types/props/constructor.rb +20 -0
- data/lib/types/props/custom_type.rb +84 -0
- data/lib/types/props/decorator.rb +826 -0
- data/lib/types/props/errors.rb +8 -0
- data/lib/types/props/optional.rb +73 -0
- data/lib/types/props/plugin.rb +15 -0
- data/lib/types/props/pretty_printable.rb +106 -0
- data/lib/types/props/serializable.rb +376 -0
- data/lib/types/props/type_validation.rb +98 -0
- data/lib/types/props/utils.rb +49 -0
- data/lib/types/props/weak_constructor.rb +30 -0
- data/lib/types/runtime_profiled.rb +36 -0
- data/lib/types/sig.rb +28 -0
- data/lib/types/struct.rb +8 -0
- data/lib/types/types/base.rb +141 -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 +36 -0
- data/lib/types/types/noreturn.rb +25 -0
- data/lib/types/types/proc.rb +51 -0
- data/lib/types/types/self_type.rb +31 -0
- data/lib/types/types/simple.rb +33 -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 +20 -0
- data/lib/types/types/typed_enumerable.rb +141 -0
- data/lib/types/types/typed_enumerator.rb +22 -0
- data/lib/types/types/typed_hash.rb +29 -0
- data/lib/types/types/typed_range.rb +22 -0
- data/lib/types/types/typed_set.rb +22 -0
- data/lib/types/types/union.rb +59 -0
- data/lib/types/types/untyped.rb +25 -0
- data/lib/types/utils.rb +223 -0
- 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
|