sorbet-runtime 0.4.4442 → 0.4.4443

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 640c933effd2dc03efc78babf1ae00ddec93c6cf
4
- data.tar.gz: c336afa4260d72745c65e65793f9d00938ebbaf8
3
+ metadata.gz: f71f4ecf6f6c24f0dd4ba065d1f911e5ca4f3477
4
+ data.tar.gz: 2992a0f425abcba10f414389cfb23469af9eca1d
5
5
  SHA512:
6
- metadata.gz: 38cf3b500f073f98d5148e36294f83b33be9fa32d8876f2c277344493df76867f4a387bc424643cb562d9b22af791f6b4dcea0f94158f04330f93fbaac2f3107
7
- data.tar.gz: e73a0f9f99a5e17c2e1a6ff598c2b4bbae31155f219c195c4dc8a76795b1c19612b1c3d348ab1b916bab24f73b25b4ab0ecc7075e3bff4c18adf46cc0bb57c10
6
+ metadata.gz: 4bcdd49035d1e279c21225ad88becc369ad63a5ce046e05587ecba1b6c0f8c4424e28eb2060726248168d6a0c6daee042dd84388e9321682f72e7a003d83f41a
7
+ data.tar.gz: f5c7844214396806c60bcbee562e72c87b5cb73a352b5593e8758d7b8b8c4fcfb91787475518bab58b28e03ef5b4c19a393c408ffcbb693e730237ea7384900e
@@ -17,6 +17,31 @@ module T::Configuration
17
17
  T::Private::RuntimeLevels.enable_checking_in_tests
18
18
  end
19
19
 
20
+ # Announce to Sorbet that we would like the final checks to be enabled when
21
+ # including and extending modules. Iff this is not called, then the following
22
+ # example will not raise an error.
23
+ #
24
+ # ```ruby
25
+ # module M
26
+ # extend T::Sig
27
+ # sig(:final) {void}
28
+ # def foo; end
29
+ # end
30
+ # class C
31
+ # include M
32
+ # def foo; end
33
+ # end
34
+ # ```
35
+ def self.enable_final_checks_for_include_extend
36
+ T::Private::Methods.set_final_checks_for_include_extend(true)
37
+ end
38
+
39
+ # Undo the effects of a previous call to
40
+ # `enable_final_checks_for_include_extend`.
41
+ def self.reset_final_checks_for_include_extend
42
+ T::Private::Methods.set_final_checks_for_include_extend(false)
43
+ end
44
+
20
45
  # Configure the default checked level for a sig with no explicit `.checked`
21
46
  # builder. When unset, the default checked level is `:always`.
22
47
  #
@@ -11,6 +11,7 @@ class T::Private::DeclState
11
11
  end
12
12
 
13
13
  attr_accessor :active_declaration
14
+ attr_accessor :skip_next_on_method_added
14
15
 
15
16
  def reset!
16
17
  self.active_declaration = nil
@@ -6,23 +6,36 @@ module T::Private::Methods
6
6
  @signatures_by_method = {}
7
7
  @sig_wrappers = {}
8
8
  @sigs_that_raised = {}
9
+ # the info about whether a method is final is not stored in a DeclBuilder nor a Signature, but instead right here.
10
+ # this is because final checks are special:
11
+ # - they are done possibly before any sig block has run.
12
+ # - they are done even if the method being defined doesn't have a sig.
13
+ @final_methods = Set.new
14
+ # if a module directly defines a final method, or includes, extends, or inherits from a module which does so, then it
15
+ # should be in this set.
16
+ @modules_with_final = Set.new
17
+ @old_included_extended = nil
9
18
 
10
19
  ARG_NOT_PROVIDED = Object.new
11
20
  PROC_TYPE = Object.new
12
21
 
13
- DeclarationBlock = Struct.new(:mod, :loc, :blk)
22
+ DeclarationBlock = Struct.new(:mod, :loc, :blk, :final)
14
23
 
15
- def self.declare_sig(mod, &blk)
24
+ def self.declare_sig(mod, arg, &blk)
16
25
  install_hooks(mod)
17
26
 
18
27
  if T::Private::DeclState.current.active_declaration
19
- T::Private::DeclState.current.active_declaration = nil
28
+ T::Private::DeclState.current.reset!
20
29
  raise "You called sig twice without declaring a method inbetween"
21
30
  end
22
31
 
32
+ if !arg.nil? && arg != :final
33
+ raise "Invalid argument to `sig`: #{arg}"
34
+ end
35
+
23
36
  loc = caller_locations(2, 1).first
24
37
 
25
- T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk)
38
+ T::Private::DeclState.current.active_declaration = DeclarationBlock.new(mod, loc, blk, arg == :final)
26
39
 
27
40
  nil
28
41
  end
@@ -106,15 +119,60 @@ module T::Private::Methods
106
119
  @signatures_by_method[key]
107
120
  end
108
121
 
122
+ # when target includes a module with instance methods source_method_names, ensure there is zero intersection between
123
+ # the final instance methods of target and source_method_names. so, for every m in source_method_names, check if there
124
+ # is already a method defined on one of target_ancestors with the same name that is final.
125
+ def self._check_final_ancestors(target, target_ancestors, source_method_names)
126
+ # use reverse_each to check farther-up ancestors first, for better error messages. we could avoid this if we were on
127
+ # the version of ruby that adds the optional argument to method_defined? that allows you to exclude ancestors.
128
+ target_ancestors.reverse_each do |ancestor|
129
+ source_method_names.each do |method_name|
130
+ # the usage of method_owner_and_name_to_key(ancestor, method_name) instead of
131
+ # method_to_key(ancestor.instance_method(method_name)) is not (just) an optimization, but also required for
132
+ # correctness, since ancestor.method_defined?(method_name) may return true even if method_name is not defined
133
+ # directly on ancestor but instead an ancestor of ancestor.
134
+ if ancestor.method_defined?(method_name) && final_method?(method_owner_and_name_to_key(ancestor, method_name))
135
+ raise(
136
+ "`#{ancestor.name}##{method_name}` was declared as final and cannot be " +
137
+ (target == ancestor ? "redefined" : "overridden in `#{target.name}`")
138
+ )
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ private_class_method def self.add_final_method(method_key)
145
+ @final_methods.add(method_key)
146
+ end
147
+
148
+ private_class_method def self.final_method?(method_key)
149
+ @final_methods.include?(method_key)
150
+ end
151
+
152
+ def self.add_module_with_final(mod)
153
+ @modules_with_final.add(mod)
154
+ end
155
+
156
+ private_class_method def self.module_with_final?(mod)
157
+ @modules_with_final.include?(mod)
158
+ end
159
+
109
160
  # Only public because it needs to get called below inside the replace_method blocks below.
110
161
  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!
162
+ if T::Private::DeclState.current.skip_next_on_method_added
163
+ T::Private::DeclState.current.skip_next_on_method_added = false
164
+ return
165
+ end
114
166
 
167
+ current_declaration = T::Private::DeclState.current.active_declaration
115
168
  mod = is_singleton_method ? hook_mod.singleton_class : hook_mod
116
169
  original_method = mod.instance_method(method_name)
117
170
 
171
+ _check_final_ancestors(mod, mod.ancestors, [method_name])
172
+
173
+ return if current_declaration.nil?
174
+ T::Private::DeclState.current.reset!
175
+
118
176
  sig_block = lambda do
119
177
  T::Private::Methods.run_sig(hook_mod, method_name, original_method, current_declaration)
120
178
  end
@@ -161,15 +219,16 @@ module T::Private::Methods
161
219
  end
162
220
 
163
221
  new_method = mod.instance_method(method_name)
164
- @sig_wrappers[method_to_key(new_method)] = sig_block
222
+ key = method_to_key(new_method)
223
+ @sig_wrappers[key] = sig_block
224
+ if current_declaration.final
225
+ add_final_method(key)
226
+ add_module_with_final(mod)
227
+ end
165
228
  end
166
229
 
167
230
  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
- )
231
+ raise(ArgumentError.new("#{loc.path}:#{loc.lineno}: Error interpreting `sig`:\n #{message}\n\n"))
173
232
  end
174
233
 
175
234
  # Executes the `sig` block, and converts the resulting Declaration
@@ -300,6 +359,38 @@ module T::Private::Methods
300
359
  end
301
360
  end
302
361
 
362
+ # the module target is adding the methods from the module source to itself. we need to check that for all instance
363
+ # methods M on source, M is not defined on any of target's ancestors.
364
+ def self._included_extended_impl(target, target_ancestors, source)
365
+ if !module_with_final?(target) && !module_with_final?(source)
366
+ return
367
+ end
368
+ add_module_with_final(target)
369
+ install_hooks(target)
370
+ _check_final_ancestors(target, target_ancestors - source.ancestors, source.instance_methods)
371
+ end
372
+
373
+ def self.set_final_checks_for_include_extend(enable)
374
+ is_enabled = @old_included_extended != nil
375
+ if enable == is_enabled
376
+ return
377
+ end
378
+ if is_enabled
379
+ @old_included_extended.each(&:restore)
380
+ @old_included_extended = nil
381
+ else
382
+ old_included = T::Private::ClassUtils.replace_method(Module, :included) do |arg|
383
+ old_included.bind(self).call(arg)
384
+ ::T::Private::Methods._included_extended_impl(arg, arg.ancestors, self)
385
+ end
386
+ old_extended = T::Private::ClassUtils.replace_method(Module, :extended) do |arg|
387
+ old_extended.bind(self).call(arg)
388
+ ::T::Private::Methods._included_extended_impl(arg, arg.singleton_class.ancestors, self)
389
+ end
390
+ @old_included_extended = [old_included, old_extended]
391
+ end
392
+ end
393
+
303
394
  def self.install_hooks(mod)
304
395
  return if @installed_hooks.include?(mod)
305
396
  @installed_hooks << mod
@@ -330,8 +421,13 @@ module T::Private::Methods
330
421
  original_singleton_method.bind(attached).call(:singleton_method_added)
331
422
  end
332
423
 
424
+ # use this directly if you don't want/need to box up the method into an object to pass to method_to_key.
425
+ private_class_method def self.method_owner_and_name_to_key(owner, name)
426
+ "#{owner.object_id}##{name}"
427
+ end
428
+
333
429
  private_class_method def self.method_to_key(method)
334
- "#{method.owner.object_id}##{method.name}"
430
+ method_owner_and_name_to_key(method.owner, method.name)
335
431
  end
336
432
 
337
433
  private_class_method def self.key_to_method(key)
@@ -163,6 +163,7 @@ module T::Private::Methods::CallValidation
163
163
  end
164
164
 
165
165
  def self.create_validator_method(mod, original_method, method_sig, original_visibility)
166
+ T::Private::DeclState.current.skip_next_on_method_added = true
166
167
  has_fixed_arity = method_sig.kwarg_types.empty? && !method_sig.has_rest && !method_sig.has_keyrest &&
167
168
  original_method.parameters.all? {|(kind, _name)| kind == :req}
168
169
  all_args_are_simple = method_sig.arg_types.all? {|_name, type| type.is_a?(T::Types::Simple)}
data/lib/types/sig.rb CHANGED
@@ -7,15 +7,15 @@ module T::Sig
7
7
  module WithoutRuntime
8
8
  # At runtime, does nothing, but statically it is treated exactly the same
9
9
  # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
10
- def self.sig(&blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
10
+ def self.sig(arg=nil, &blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
11
11
 
12
12
  original_verbose = $VERBOSE
13
13
  $VERBOSE = false
14
14
 
15
15
  # At runtime, does nothing, but statically it is treated exactly the same
16
16
  # as T::Sig#sig. Only use it in cases where you can't use T::Sig#sig.
17
- T::Sig::WithoutRuntime.sig {params(blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
18
- def self.sig(&blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride, Lint/DuplicateMethods
17
+ T::Sig::WithoutRuntime.sig {params(arg: T.nilable(Symbol), blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
18
+ def self.sig(arg=nil, &blk); end # rubocop:disable PrisonGuard/BanBuiltinMethodOverride, Lint/DuplicateMethods
19
19
 
20
20
  $VERBOSE = original_verbose
21
21
  end
@@ -23,8 +23,8 @@ module T::Sig
23
23
  # Declares a method with type signatures and/or
24
24
  # abstract/override/... helpers. See the documentation URL on
25
25
  # {T::Helpers}
26
- T::Sig::WithoutRuntime.sig {params(blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
27
- def sig(&blk) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
28
- T::Private::Methods.declare_sig(self, &blk)
26
+ T::Sig::WithoutRuntime.sig {params(arg: T.nilable(Symbol), blk: T.proc.bind(T::Private::Methods::DeclBuilder).void).void}
27
+ def sig(arg=nil, &blk) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
28
+ T::Private::Methods.declare_sig(self, arg, &blk)
29
29
  end
30
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-runtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4442
4
+ version: 0.4.4443
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe