sorbet-runtime 0.5.6198 → 0.5.6539
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 +4 -4
- data/lib/sorbet-runtime.rb +0 -1
- data/lib/types/_types.rb +19 -0
- data/lib/types/configuration.rb +82 -1
- data/lib/types/generic.rb +0 -1
- data/lib/types/helpers.rb +19 -2
- data/lib/types/non_forcing_constants.rb +11 -3
- data/lib/types/private/final.rb +0 -1
- data/lib/types/private/methods/_methods.rb +185 -123
- data/lib/types/private/methods/call_validation.rb +54 -982
- data/lib/types/private/methods/call_validation_2_6.rb +1203 -0
- data/lib/types/private/methods/call_validation_2_7.rb +1203 -0
- data/lib/types/private/methods/decl_builder.rb +3 -2
- data/lib/types/private/methods/signature.rb +8 -2
- data/lib/types/private/methods/signature_validation.rb +3 -3
- data/lib/types/private/mixins/mixins.rb +5 -5
- data/lib/types/props/_props.rb +2 -1
- data/lib/types/props/decorator.rb +8 -4
- data/lib/types/props/generated_code_validation.rb +11 -2
- data/lib/types/props/has_lazily_specialized_methods.rb +43 -0
- data/lib/types/props/private/deserializer_generator.rb +4 -9
- data/lib/types/props/private/setter_factory.rb +60 -2
- data/lib/types/props/serializable.rb +34 -4
- data/lib/types/sig.rb +1 -1
- data/lib/types/types/fixed_hash.rb +10 -2
- data/lib/types/types/simple.rb +18 -3
- data/lib/types/types/typed_enumerable.rb +5 -1
- data/lib/types/types/union.rb +2 -2
- data/lib/types/utils.rb +9 -25
- metadata +5 -4
- data/lib/types/profile.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3594dc0c726d2a7189c7544226a6ced4240f4a6df2c18bdae635cc30bb223e7b
|
|
4
|
+
data.tar.gz: dae60008d7f685ecdf231edefdf0b01b595c597c6ae646e7ba7070eada079fa2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 519bf1b1c4f04792d88c817a9bb3a213b01e783a96597e1e328a8a5c9736e78ff7a0a93ef31d56787fd9468d09980e4670edffcd71f59ff299cc7a09a80a714b
|
|
7
|
+
data.tar.gz: 0150d316fb26d07fd0693d3b948dfaa6fe1979053dc12f9811c5c9c6d29b711affaa3e6be16a34a08fd6abf5a1db816b4872b7b655895353cdf65094f7e0ad5c
|
data/lib/sorbet-runtime.rb
CHANGED
|
@@ -19,7 +19,6 @@ require 'set'
|
|
|
19
19
|
|
|
20
20
|
# These are pre-reqs for almost everything in here.
|
|
21
21
|
require_relative 'types/configuration'
|
|
22
|
-
require_relative 'types/profile'
|
|
23
22
|
require_relative 'types/_types'
|
|
24
23
|
require_relative 'types/private/decl_state'
|
|
25
24
|
require_relative 'types/private/class_utils'
|
data/lib/types/_types.rb
CHANGED
|
@@ -147,6 +147,25 @@ module T
|
|
|
147
147
|
Private::Casts.cast(value, type, cast_method: "T.let")
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
+
# Tells the type checker to treat `self` in the current block as `type`.
|
|
151
|
+
# Useful for blocks that are captured and executed later with instance_exec.
|
|
152
|
+
# Use like:
|
|
153
|
+
#
|
|
154
|
+
# seconds = lambda do
|
|
155
|
+
# T.bind(self, NewBinding)
|
|
156
|
+
# ...
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# `T.bind` behaves like `T.cast` in that it is assumed to be true statically.
|
|
160
|
+
#
|
|
161
|
+
# If `checked` is true, raises an exception at runtime if the value
|
|
162
|
+
# doesn't match the type (this is the default).
|
|
163
|
+
def self.bind(value, type, checked: true)
|
|
164
|
+
return value unless checked
|
|
165
|
+
|
|
166
|
+
Private::Casts.cast(value, type, cast_method: "T.bind")
|
|
167
|
+
end
|
|
168
|
+
|
|
150
169
|
# Tells the typechecker to ensure that `value` is of type `type` (if not, the typechecker will
|
|
151
170
|
# fail). Use this for debugging typechecking errors, or to ensure that type information is
|
|
152
171
|
# statically known and being checked appropriately. If `checked` is true, raises an exception at
|
data/lib/types/configuration.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module T::Configuration
|
|
5
|
+
# Cache this comparisonn to avoid two allocations all over the place.
|
|
6
|
+
AT_LEAST_RUBY_2_7 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
|
|
7
|
+
|
|
5
8
|
# Announces to Sorbet that we are currently in a test environment, so it
|
|
6
9
|
# should treat any sigs which are marked `.checked(:tests)` as if they were
|
|
7
10
|
# just a normal sig.
|
|
@@ -70,6 +73,37 @@ module T::Configuration
|
|
|
70
73
|
@include_value_in_type_errors = true
|
|
71
74
|
end
|
|
72
75
|
|
|
76
|
+
# Whether VM-defined prop serialization/deserialization routines can be enabled.
|
|
77
|
+
#
|
|
78
|
+
# @return [T::Boolean]
|
|
79
|
+
def self.can_enable_vm_prop_serde?
|
|
80
|
+
T::Props::Private::DeserializerGenerator.respond_to?(:generate2)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Whether to use VM-defined prop serialization/deserialization routines.
|
|
84
|
+
#
|
|
85
|
+
# The default is to use runtime codegen inside sorbet-runtime itself.
|
|
86
|
+
#
|
|
87
|
+
# @return [T::Boolean]
|
|
88
|
+
def self.use_vm_prop_serde?
|
|
89
|
+
@use_vm_prop_serde || false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Enable using VM-defined prop serialization/deserialization routines.
|
|
93
|
+
#
|
|
94
|
+
# This method is likely to break things outside of Stripe's systems.
|
|
95
|
+
def self.enable_vm_prop_serde
|
|
96
|
+
if !can_enable_vm_prop_serde?
|
|
97
|
+
hard_assert_handler('Ruby VM is not setup to use VM-defined prop serde')
|
|
98
|
+
end
|
|
99
|
+
@use_vm_prop_serde = true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Disable using VM-defined prop serialization/deserialization routines.
|
|
103
|
+
def self.disable_vm_prop_serde
|
|
104
|
+
@use_vm_prop_serde = false
|
|
105
|
+
end
|
|
106
|
+
|
|
73
107
|
# Configure the default checked level for a sig with no explicit `.checked`
|
|
74
108
|
# builder. When unset, the default checked level is `:always`.
|
|
75
109
|
#
|
|
@@ -395,7 +429,12 @@ module T::Configuration
|
|
|
395
429
|
MODULE_NAME = Module.instance_method(:name)
|
|
396
430
|
private_constant :MODULE_NAME
|
|
397
431
|
|
|
398
|
-
|
|
432
|
+
if T::Configuration::AT_LEAST_RUBY_2_7
|
|
433
|
+
@default_module_name_mangler = ->(type) {MODULE_NAME.bind_call(type)}
|
|
434
|
+
else
|
|
435
|
+
@default_module_name_mangler = ->(type) {MODULE_NAME.bind(type).call}
|
|
436
|
+
end
|
|
437
|
+
|
|
399
438
|
@module_name_mangler = nil
|
|
400
439
|
|
|
401
440
|
def self.module_name_mangler
|
|
@@ -412,6 +451,48 @@ module T::Configuration
|
|
|
412
451
|
@module_name_mangler = handler
|
|
413
452
|
end
|
|
414
453
|
|
|
454
|
+
# Set to a PII handler function. This will be called with the `sensitivity:`
|
|
455
|
+
# annotations on things that use `T::Props` and can modify them ahead-of-time.
|
|
456
|
+
#
|
|
457
|
+
# @param [Lambda, Proc, nil] value Proc that takes a hash mapping symbols to the
|
|
458
|
+
# prop values. Pass nil to avoid changing `sensitivity:` annotations.
|
|
459
|
+
def self.normalize_sensitivity_and_pii_handler=(handler)
|
|
460
|
+
@sensitivity_and_pii_handler = handler
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def self.normalize_sensitivity_and_pii_handler
|
|
464
|
+
@sensitivity_and_pii_handler
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
@redaction_handler = nil
|
|
468
|
+
# Set to a redaction handling function. This will be called when the
|
|
469
|
+
# `_redacted` version of a prop reader is used. By default this is set to
|
|
470
|
+
# `nil` and will raise an exception when the redacted version of a prop is
|
|
471
|
+
# accessed.
|
|
472
|
+
#
|
|
473
|
+
# @param [Lambda, Proc, nil] value Proc that converts a value into its
|
|
474
|
+
# redacted version according to the spec passed as the second argument.
|
|
475
|
+
def self.redaction_handler=(handler)
|
|
476
|
+
@redaction_handler = handler
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def self.redaction_handler
|
|
480
|
+
@redaction_handler
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Set to a function which can get the 'owner' of a class. This is
|
|
484
|
+
# used in reporting deserialization errors
|
|
485
|
+
#
|
|
486
|
+
# @param [Lambda, Proc, nil] value Proc that takes a class and
|
|
487
|
+
# produces its owner, or `nil` if it does not have one.
|
|
488
|
+
def self.class_owner_finder=(handler)
|
|
489
|
+
@class_owner_finder = handler
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def self.class_owner_finder
|
|
493
|
+
@class_owner_finder
|
|
494
|
+
end
|
|
495
|
+
|
|
415
496
|
# Temporarily disable ruby warnings while executing the given block. This is
|
|
416
497
|
# useful when doing something that would normally cause a warning to be
|
|
417
498
|
# emitted in Ruby verbose mode ($VERBOSE = true).
|
data/lib/types/generic.rb
CHANGED
data/lib/types/helpers.rb
CHANGED
|
@@ -33,7 +33,24 @@ module T::Helpers
|
|
|
33
33
|
# end
|
|
34
34
|
#
|
|
35
35
|
# Except that it is statically analyzed by sorbet.
|
|
36
|
-
def mixes_in_class_methods(mod)
|
|
37
|
-
Private::Mixins.declare_mixes_in_class_methods(self, mod)
|
|
36
|
+
def mixes_in_class_methods(mod, *mods)
|
|
37
|
+
Private::Mixins.declare_mixes_in_class_methods(self, [mod].concat(mods))
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
# Specify an inclusion or inheritance requirement for `self`.
|
|
41
|
+
#
|
|
42
|
+
# Example:
|
|
43
|
+
#
|
|
44
|
+
# module MyHelper
|
|
45
|
+
# extend T::Helpers
|
|
46
|
+
#
|
|
47
|
+
# requires_ancestor Kernel
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# class MyClass < BasicObject # error: `MyClass` must include `Kernel` (required by `MyHelper`)
|
|
51
|
+
# include MyHelper
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# TODO: implement the checks in sorbet-runtime.
|
|
55
|
+
def requires_ancestor(mod, *mods); end
|
|
39
56
|
end
|
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
# typed: strict
|
|
3
3
|
|
|
4
4
|
module T::NonForcingConstants
|
|
5
|
+
T::Sig::WithoutRuntime.sig {returns(T::Boolean)}
|
|
6
|
+
private_class_method def self.stripe_packages_enabled?
|
|
7
|
+
const_defined?('StripePackages') && const_get('StripePackages').enabled?
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
# NOTE: This method is documented on the RBI in Sorbet's payload, so that it
|
|
6
11
|
# shows up in the hover/completion documentation via LSP.
|
|
7
12
|
T::Sig::WithoutRuntime.sig {params(val: BasicObject, klass: String, package: T.nilable(String)).returns(T::Boolean)}
|
|
8
13
|
def self.non_forcing_is_a?(val, klass, package: nil)
|
|
9
|
-
# TODO(gdritter): once we have a runtime implementation of
|
|
10
|
-
# packages, we'll need to actually handle the `package` argument
|
|
11
|
-
# here.
|
|
12
14
|
method_name = "T::NonForcingConstants.non_forcing_is_a?"
|
|
13
15
|
if klass.empty?
|
|
14
16
|
raise ArgumentError.new("The string given to `#{method_name}` must not be empty")
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# TODO(gdritter): this might not be want we want in a multipackage
|
|
20
|
+
# world; revisit this implementation we move on from the monopackage
|
|
21
|
+
if stripe_packages_enabled?
|
|
22
|
+
klass = "PkgRegistry::#{package}::#{klass}"
|
|
23
|
+
end
|
|
24
|
+
|
|
17
25
|
current_klass = T.let(nil, T.nilable(Module))
|
|
18
26
|
current_prefix = T.let(nil, T.nilable(String))
|
|
19
27
|
|
data/lib/types/private/final.rb
CHANGED
|
@@ -37,7 +37,6 @@ module T::Private::Final
|
|
|
37
37
|
mod.extend(mod.is_a?(Class) ? NoInherit : NoIncludeExtend)
|
|
38
38
|
mark_as_final_module(mod)
|
|
39
39
|
mark_as_final_module(mod.singleton_class)
|
|
40
|
-
T::Private::Methods.add_module_with_final(mod)
|
|
41
40
|
T::Private::Methods.install_hooks(mod)
|
|
42
41
|
end
|
|
43
42
|
|
|
@@ -6,19 +6,21 @@ module T::Private::Methods
|
|
|
6
6
|
@signatures_by_method = {}
|
|
7
7
|
@sig_wrappers = {}
|
|
8
8
|
@sigs_that_raised = {}
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# a
|
|
20
|
-
#
|
|
21
|
-
|
|
9
|
+
# stores method names that were declared final without regard for where.
|
|
10
|
+
# enables early rejection of names that we know can't induce final method violations.
|
|
11
|
+
@was_ever_final_names = Set.new
|
|
12
|
+
# maps from modules to the set of final methods declared in that module.
|
|
13
|
+
# we also overload entries slightly: if the value is nil, that means that the
|
|
14
|
+
# module has final methods somewhere along its ancestor chain, but does not itself
|
|
15
|
+
# have any final methods.
|
|
16
|
+
#
|
|
17
|
+
# we need the latter information to know whether we need to check along the ancestor
|
|
18
|
+
# chain for final method violations. we need the former information because we
|
|
19
|
+
# care about exactly where a final method is defined (e.g. including the same module
|
|
20
|
+
# twice is permitted). we could do this with two tables, but it seems slightly
|
|
21
|
+
# cleaner with a single table.
|
|
22
|
+
# Effectively T::Hash[Module, T.nilable(Set))]
|
|
23
|
+
@modules_with_final = Hash.new { |hash, key| hash[key] = nil }
|
|
22
24
|
# this stores the old [included, extended] hooks for Module and inherited hook for Class that we override when
|
|
23
25
|
# enabling final checks for when those hooks are called. the 'hooks' here don't have anything to do with the 'hooks'
|
|
24
26
|
# in installed_hooks.
|
|
@@ -27,29 +29,51 @@ module T::Private::Methods
|
|
|
27
29
|
ARG_NOT_PROVIDED = Object.new
|
|
28
30
|
PROC_TYPE = Object.new
|
|
29
31
|
|
|
30
|
-
DeclarationBlock = Struct.new(:mod, :loc, :blk, :final)
|
|
32
|
+
DeclarationBlock = Struct.new(:mod, :loc, :blk, :final, :raw)
|
|
33
|
+
|
|
34
|
+
def self.declare_sig(mod, loc, arg, &blk)
|
|
35
|
+
T::Private::DeclState.current.active_declaration = _declare_sig_internal(mod, loc, arg, &blk)
|
|
36
|
+
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# See tests for how to use this. But you shouldn't be using this.
|
|
41
|
+
def self._declare_sig(mod, arg=nil, &blk)
|
|
42
|
+
_declare_sig_internal(mod, caller_locations(1, 1).first, arg, raw: true, &blk)
|
|
43
|
+
end
|
|
31
44
|
|
|
32
|
-
def self.
|
|
45
|
+
private_class_method def self._declare_sig_internal(mod, loc, arg, raw: false, &blk)
|
|
33
46
|
install_hooks(mod)
|
|
34
47
|
|
|
35
48
|
if T::Private::DeclState.current.active_declaration
|
|
36
49
|
T::Private::DeclState.current.reset!
|
|
37
|
-
raise "You called sig twice without declaring a method
|
|
50
|
+
raise "You called sig twice without declaring a method in between"
|
|
38
51
|
end
|
|
39
52
|
|
|
40
53
|
if !arg.nil? && arg != :final
|
|
41
54
|
raise "Invalid argument to `sig`: #{arg}"
|
|
42
55
|
end
|
|
43
56
|
|
|
44
|
-
loc
|
|
45
|
-
|
|
46
|
-
T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk, arg == :final)
|
|
57
|
+
DeclarationBlock.new(mod, loc, blk, arg == :final, raw)
|
|
58
|
+
end
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
def self._with_declared_signature(mod, declblock, &blk)
|
|
61
|
+
# If declblock is provided, this code is equivalent to the check in
|
|
62
|
+
# _declare_sig_internal, above.
|
|
63
|
+
# If declblock is not provided and we have an active declaration, we are
|
|
64
|
+
# obviously doing something wrong.
|
|
65
|
+
if T::Private::DeclState.current.active_declaration
|
|
66
|
+
T::Private::DeclState.current.reset!
|
|
67
|
+
raise "You called sig twice without declaring a method in between"
|
|
68
|
+
end
|
|
69
|
+
if declblock
|
|
70
|
+
T::Private::DeclState.current.active_declaration = declblock
|
|
71
|
+
end
|
|
72
|
+
mod.module_exec(&blk)
|
|
49
73
|
end
|
|
50
74
|
|
|
51
75
|
def self.start_proc
|
|
52
|
-
DeclBuilder.new(PROC_TYPE)
|
|
76
|
+
DeclBuilder.new(PROC_TYPE, false)
|
|
53
77
|
end
|
|
54
78
|
|
|
55
79
|
def self.finalize_proc(decl)
|
|
@@ -75,40 +99,6 @@ module T::Private::Methods
|
|
|
75
99
|
T::Types::Proc.new(decl.params, decl.returns)
|
|
76
100
|
end
|
|
77
101
|
|
|
78
|
-
# See docs at T::Utils.register_forwarder.
|
|
79
|
-
def self.register_forwarder(from_method, to_method, mode: Modes.overridable, remove_first_param: false)
|
|
80
|
-
# Normalize the method (see comment in signature_for_key).
|
|
81
|
-
from_method = from_method.owner.instance_method(from_method.name)
|
|
82
|
-
|
|
83
|
-
from_key = method_to_key(from_method)
|
|
84
|
-
maybe_run_sig_block_for_key(from_key)
|
|
85
|
-
if @signatures_by_method.key?(from_key)
|
|
86
|
-
raise "#{from_method} already has a method signature"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
from_params = from_method.parameters
|
|
90
|
-
if from_params.length != 2 || from_params[0][0] != :rest || from_params[1][0] != :block
|
|
91
|
-
raise "forwarder methods should take a single splat param and a block param. `#{from_method}` " \
|
|
92
|
-
"takes these params: #{from_params}. For help, ask #dev-productivity."
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# If there's already a signature for to_method, we get `parameters` from there, to enable
|
|
96
|
-
# chained forwarding. NB: we don't use `signature_for_key` here, because the normalization it
|
|
97
|
-
# does is broken when `to_method` has been clobbered by another method.
|
|
98
|
-
to_key = method_to_key(to_method)
|
|
99
|
-
maybe_run_sig_block_for_key(to_key)
|
|
100
|
-
to_params = @signatures_by_method[to_key]&.parameters || to_method.parameters
|
|
101
|
-
|
|
102
|
-
if remove_first_param
|
|
103
|
-
to_params = to_params[1..-1]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# We don't bother trying to preserve any types from to_signature because this won't be
|
|
107
|
-
# statically analyzed, and the types will just get checked when the forwarding happens.
|
|
108
|
-
from_signature = Signature.new_untyped(method: from_method, mode: mode, parameters: to_params)
|
|
109
|
-
@signatures_by_method[from_key] = from_signature
|
|
110
|
-
end
|
|
111
|
-
|
|
112
102
|
# Returns the signature for a method whose definition was preceded by `sig`.
|
|
113
103
|
#
|
|
114
104
|
# @param method [UnboundMethod]
|
|
@@ -130,46 +120,86 @@ module T::Private::Methods
|
|
|
130
120
|
# when target includes a module with instance methods source_method_names, ensure there is zero intersection between
|
|
131
121
|
# the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
|
|
132
122
|
# is already a method defined on one of target_ancestors with the same name that is final.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
123
|
+
#
|
|
124
|
+
# we assume that source_method_names has already been filtered to only include method
|
|
125
|
+
# names that were declared final at one point.
|
|
126
|
+
def self._check_final_ancestors(target, target_ancestors, source_method_names, source)
|
|
127
|
+
source_ancestors = nil
|
|
128
|
+
# use reverse_each to check farther-up ancestors first, for better error messages.
|
|
139
129
|
target_ancestors.reverse_each do |ancestor|
|
|
130
|
+
final_methods = @modules_with_final.fetch(ancestor, nil)
|
|
131
|
+
# In this case, either ancestor didn't have any final methods anywhere in its
|
|
132
|
+
# ancestor chain, or ancestor did have final methods somewhere in its ancestor
|
|
133
|
+
# chain, but no final methods defined in ancestor itself. Either way, there
|
|
134
|
+
# are no final methods to check here, so we can move on to the next ancestor.
|
|
135
|
+
next unless final_methods
|
|
140
136
|
source_method_names.each do |method_name|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
next unless final_methods.include?(method_name)
|
|
138
|
+
|
|
139
|
+
# If we get here, we are defining a method that some ancestor declared as
|
|
140
|
+
# final. however, we permit a final method to be defined multiple
|
|
141
|
+
# times if it is the same final method being defined each time.
|
|
142
|
+
if source
|
|
143
|
+
if !source_ancestors
|
|
144
|
+
source_ancestors = source.ancestors
|
|
145
|
+
# filter out things without actual final methods just to make sure that
|
|
146
|
+
# the below checks (which should be uncommon) go as quickly as possible.
|
|
147
|
+
source_ancestors.select! do |a|
|
|
148
|
+
@modules_with_final.fetch(a, nil)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
# final-ness means that there should be no more than one index for which
|
|
152
|
+
# the below block returns true.
|
|
153
|
+
defining_ancestor_idx = source_ancestors.index do |a|
|
|
154
|
+
@modules_with_final.fetch(a).include?(method_name)
|
|
155
|
+
end
|
|
156
|
+
next if defining_ancestor_idx && source_ancestors[defining_ancestor_idx] == ancestor
|
|
153
157
|
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
definition_file, definition_line = T::Private::Methods.signature_for_method(ancestor.instance_method(method_name)).method.source_location
|
|
160
|
+
is_redefined = target == ancestor
|
|
161
|
+
caller_loc = caller_locations&.find {|l| !l.to_s.match?(%r{sorbet-runtime[^/]*/lib/}) }
|
|
162
|
+
extra_info = "\n"
|
|
163
|
+
if caller_loc
|
|
164
|
+
extra_info = (is_redefined ? "Redefined" : "Overridden") + " here: #{caller_loc.path}:#{caller_loc.lineno}\n"
|
|
165
|
+
end
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
error_message = "The method `#{method_name}` on #{ancestor} was declared as final and cannot be " +
|
|
168
|
+
(is_redefined ? "redefined" : "overridden in #{target}")
|
|
169
|
+
pretty_message = "#{error_message}\n" \
|
|
170
|
+
"Made final here: #{definition_file}:#{definition_line}\n" \
|
|
171
|
+
"#{extra_info}"
|
|
172
|
+
|
|
173
|
+
begin
|
|
174
|
+
raise pretty_message
|
|
175
|
+
rescue => e
|
|
176
|
+
# sig_validation_error_handler raises by default; on the off chance that
|
|
177
|
+
# it doesn't raise, we need to ensure that the rest of signature building
|
|
178
|
+
# sees a consistent state. This sig failed to validate, so we should get
|
|
179
|
+
# rid of it. If we don't do this, errors of the form "You called sig
|
|
180
|
+
# twice without declaring a method in between" will non-deterministically
|
|
181
|
+
# crop up in tests.
|
|
182
|
+
T::Private::DeclState.current.reset!
|
|
183
|
+
T::Configuration.sig_validation_error_handler(e, {})
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
164
187
|
end
|
|
165
188
|
|
|
166
|
-
def self.
|
|
167
|
-
|
|
168
|
-
@modules_with_final
|
|
189
|
+
def self.add_module_with_final_method(mod, method_name, is_singleton_method)
|
|
190
|
+
m = is_singleton_method ? mod.singleton_class : mod
|
|
191
|
+
methods = @modules_with_final[m]
|
|
192
|
+
if methods.nil?
|
|
193
|
+
methods = Set.new
|
|
194
|
+
@modules_with_final[m] = methods
|
|
195
|
+
end
|
|
196
|
+
methods.add(method_name)
|
|
169
197
|
end
|
|
170
198
|
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
def self.note_module_deals_with_final(mod)
|
|
200
|
+
# Side-effectfully initialize the value if it's not already there
|
|
201
|
+
@modules_with_final[mod]
|
|
202
|
+
@modules_with_final[mod.singleton_class]
|
|
173
203
|
end
|
|
174
204
|
|
|
175
205
|
# Only public because it needs to get called below inside the replace_method blocks below.
|
|
@@ -184,7 +214,13 @@ module T::Private::Methods
|
|
|
184
214
|
if T::Private::Final.final_module?(mod) && (current_declaration.nil? || !current_declaration.final)
|
|
185
215
|
raise "#{mod} was declared as final but its method `#{method_name}` was not declared as final"
|
|
186
216
|
end
|
|
187
|
-
|
|
217
|
+
# Don't compute mod.ancestors if we don't need to bother checking final-ness.
|
|
218
|
+
if @was_ever_final_names.include?(method_name) && @modules_with_final.include?(mod)
|
|
219
|
+
_check_final_ancestors(mod, mod.ancestors, [method_name], nil)
|
|
220
|
+
# We need to fetch the active declaration again, as _check_final_ancestors
|
|
221
|
+
# may have reset it (see the comment in that method for details).
|
|
222
|
+
current_declaration = T::Private::DeclState.current.active_declaration
|
|
223
|
+
end
|
|
188
224
|
|
|
189
225
|
if current_declaration.nil?
|
|
190
226
|
return
|
|
@@ -207,41 +243,44 @@ module T::Private::Methods
|
|
|
207
243
|
# which is called only on the *first* invocation.
|
|
208
244
|
# This wrapper is very slow, so it will subsequently re-wrap with a much faster wrapper
|
|
209
245
|
# (or unwrap back to the original method).
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
super
|
|
246
|
+
key = method_owner_and_name_to_key(mod, method_name)
|
|
247
|
+
unless current_declaration.raw
|
|
248
|
+
T::Private::ClassUtils.replace_method(mod, method_name) do |*args, &blk|
|
|
249
|
+
method_sig = T::Private::Methods.maybe_run_sig_block_for_key(key)
|
|
250
|
+
method_sig ||= T::Private::Methods._handle_missing_method_signature(
|
|
251
|
+
self,
|
|
252
|
+
original_method,
|
|
253
|
+
__callee__,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Should be the same logic as CallValidation.wrap_method_if_needed but we
|
|
257
|
+
# don't want that extra layer of indirection in the callstack
|
|
258
|
+
if method_sig.mode == T::Private::Methods::Modes.abstract
|
|
259
|
+
# We're in an interface method, keep going up the chain
|
|
260
|
+
if defined?(super)
|
|
261
|
+
super(*args, &blk)
|
|
262
|
+
else
|
|
263
|
+
raise NotImplementedError.new("The method `#{method_sig.method_name}` on #{mod} is declared as `abstract`. It does not have an implementation.")
|
|
264
|
+
end
|
|
265
|
+
# Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
|
|
266
|
+
# make sure to keep changes in sync.
|
|
267
|
+
elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
|
|
268
|
+
CallValidation.validate_call(self, original_method, method_sig, args, blk)
|
|
269
|
+
elsif T::Configuration::AT_LEAST_RUBY_2_7
|
|
270
|
+
original_method.bind_call(self, *args, &blk)
|
|
225
271
|
else
|
|
226
|
-
|
|
272
|
+
original_method.bind(self).call(*args, &blk)
|
|
227
273
|
end
|
|
228
|
-
# Note, this logic is duplicated (intentionally, for micro-perf) at `CallValidation.wrap_method_if_needed`,
|
|
229
|
-
# make sure to keep changes in sync.
|
|
230
|
-
elsif method_sig.check_level == :always || (method_sig.check_level == :tests && T::Private::RuntimeLevels.check_tests?)
|
|
231
|
-
CallValidation.validate_call(self, original_method, method_sig, args, blk)
|
|
232
|
-
else
|
|
233
|
-
original_method.bind(self).call(*args, &blk)
|
|
234
274
|
end
|
|
235
275
|
end
|
|
236
276
|
|
|
237
|
-
new_method = mod.instance_method(method_name)
|
|
238
|
-
key = method_to_key(new_method)
|
|
239
277
|
@sig_wrappers[key] = sig_block
|
|
240
278
|
if current_declaration.final
|
|
241
|
-
|
|
279
|
+
@was_ever_final_names.add(method_name)
|
|
242
280
|
# use hook_mod, not mod, because for example, we want class C to be marked as having final if we def C.foo as
|
|
243
281
|
# final. change this to mod to see some final_method tests fail.
|
|
244
|
-
|
|
282
|
+
note_module_deals_with_final(hook_mod)
|
|
283
|
+
add_module_with_final_method(hook_mod, method_name, is_singleton_method)
|
|
245
284
|
end
|
|
246
285
|
end
|
|
247
286
|
|
|
@@ -305,7 +344,7 @@ module T::Private::Methods
|
|
|
305
344
|
end
|
|
306
345
|
|
|
307
346
|
def self.run_builder(declaration_block)
|
|
308
|
-
builder = DeclBuilder.new(declaration_block.mod)
|
|
347
|
+
builder = DeclBuilder.new(declaration_block.mod, declaration_block.raw)
|
|
309
348
|
builder
|
|
310
349
|
.instance_exec(&declaration_block.blk)
|
|
311
350
|
.finalize!
|
|
@@ -336,6 +375,7 @@ module T::Private::Methods
|
|
|
336
375
|
check_level: current_declaration.checked,
|
|
337
376
|
on_failure: current_declaration.on_failure,
|
|
338
377
|
override_allow_incompatible: current_declaration.override_allow_incompatible,
|
|
378
|
+
defined_raw: current_declaration.raw,
|
|
339
379
|
)
|
|
340
380
|
|
|
341
381
|
SignatureValidation.validate(signature)
|
|
@@ -373,7 +413,8 @@ module T::Private::Methods
|
|
|
373
413
|
maybe_run_sig_block_for_key(method_to_key(method))
|
|
374
414
|
end
|
|
375
415
|
|
|
376
|
-
|
|
416
|
+
# Only public so that it can be accessed in the closure for _on_method_added
|
|
417
|
+
def self.maybe_run_sig_block_for_key(key)
|
|
377
418
|
run_sig_block_for_key(key) if has_sig_block_for_key(key)
|
|
378
419
|
end
|
|
379
420
|
|
|
@@ -417,13 +458,28 @@ module T::Private::Methods
|
|
|
417
458
|
|
|
418
459
|
# the module target is adding the methods from the module source to itself. we need to check that for all instance
|
|
419
460
|
# methods M on source, M is not defined on any of target's ancestors.
|
|
420
|
-
def self._hook_impl(target,
|
|
421
|
-
|
|
461
|
+
def self._hook_impl(target, singleton_class, source)
|
|
462
|
+
# we do not need to call add_was_ever_final here, because we have already marked
|
|
463
|
+
# any such methods when source was originally defined.
|
|
464
|
+
if !@modules_with_final.include?(target)
|
|
465
|
+
if !@modules_with_final.include?(source)
|
|
466
|
+
return
|
|
467
|
+
end
|
|
468
|
+
note_module_deals_with_final(target)
|
|
469
|
+
install_hooks(target)
|
|
470
|
+
return
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
methods = source.instance_methods
|
|
474
|
+
methods.select! do |method_name|
|
|
475
|
+
@was_ever_final_names.include?(method_name)
|
|
476
|
+
end
|
|
477
|
+
if methods.empty?
|
|
422
478
|
return
|
|
423
479
|
end
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
_check_final_ancestors(target, target_ancestors
|
|
480
|
+
|
|
481
|
+
target_ancestors = singleton_class ? target.singleton_class.ancestors : target.ancestors
|
|
482
|
+
_check_final_ancestors(target, target_ancestors, methods, source)
|
|
427
483
|
end
|
|
428
484
|
|
|
429
485
|
def self.set_final_checks_on_hooks(enable)
|
|
@@ -437,15 +493,15 @@ module T::Private::Methods
|
|
|
437
493
|
else
|
|
438
494
|
old_included = T::Private::ClassUtils.replace_method(Module, :included) do |arg|
|
|
439
495
|
old_included.bind(self).call(arg)
|
|
440
|
-
::T::Private::Methods._hook_impl(arg,
|
|
496
|
+
::T::Private::Methods._hook_impl(arg, false, self)
|
|
441
497
|
end
|
|
442
498
|
old_extended = T::Private::ClassUtils.replace_method(Module, :extended) do |arg|
|
|
443
499
|
old_extended.bind(self).call(arg)
|
|
444
|
-
::T::Private::Methods._hook_impl(arg,
|
|
500
|
+
::T::Private::Methods._hook_impl(arg, true, self)
|
|
445
501
|
end
|
|
446
502
|
old_inherited = T::Private::ClassUtils.replace_method(Class, :inherited) do |arg|
|
|
447
503
|
old_inherited.bind(self).call(arg)
|
|
448
|
-
::T::Private::Methods._hook_impl(arg,
|
|
504
|
+
::T::Private::Methods._hook_impl(arg, false, self)
|
|
449
505
|
end
|
|
450
506
|
@old_hooks = [old_included, old_extended, old_inherited]
|
|
451
507
|
end
|
|
@@ -483,6 +539,12 @@ module T::Private::Methods
|
|
|
483
539
|
return
|
|
484
540
|
end
|
|
485
541
|
|
|
542
|
+
# See https://github.com/sorbet/sorbet/pull/3964 for an explanation of why this
|
|
543
|
+
# check (which theoretically should not be needed) is actually needed.
|
|
544
|
+
if !mod.is_a?(Module)
|
|
545
|
+
return
|
|
546
|
+
end
|
|
547
|
+
|
|
486
548
|
if mod.singleton_class?
|
|
487
549
|
mod.include(SingletonMethodHooks)
|
|
488
550
|
else
|