type_toolkit 0.0.4 → 0.0.6
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/README.md +45 -1
- data/benchmark/.rubocop.yml +7 -0
- data/benchmark/abstract_methods_benchmark.rb +221 -0
- data/benchmark/interface_startup_performance.rb +128 -0
- data/benchmark/module_benchmark.rb +62 -0
- data/config/default.yml +5 -0
- data/docs/design/inherited_implementation_problem.md +119 -0
- data/docs/design/self_dot_methods.md +79 -0
- data/lib/rubocop/cop/type_toolkit/plugin.rb +2 -1
- data/lib/rubocop/cop/type_toolkit/prefer_not_nil.rb +96 -0
- data/lib/rubocop-type_toolkit.rb +1 -0
- data/lib/type_toolkit/abstract_method_receiver.rb +39 -0
- data/lib/type_toolkit/dsl.rb +64 -0
- data/lib/type_toolkit/ext/method.rb +11 -0
- data/lib/type_toolkit/ext/module.rb +7 -0
- data/lib/type_toolkit/ext/nil_assertions.rb +3 -1
- data/lib/type_toolkit/has_abstract_methods.rb +108 -0
- data/lib/type_toolkit/interface.rb +38 -0
- data/lib/type_toolkit/method_def_recorder.rb +53 -0
- data/lib/type_toolkit/method_patch.rb +16 -0
- data/lib/type_toolkit/version.rb +1 -1
- data/lib/type_toolkit.rb +3 -0
- data/sorbet/config +3 -1
- data/sorbet/rbi/gems/benchmark-ips@2.14.0.rbi +981 -0
- data/sorbet/rbi/gems/{erb@6.0.1.rbi → erb@6.0.1.1.rbi} +2 -2
- data/sorbet/rbi/gems/{json@2.18.1.rbi → json@2.19.9.rbi} +115 -161
- data/sorbet/rbi/gems/minitest@5.27.0.rbi +707 -0
- data/sorbet/rbi/gems/rexml@3.4.4.rbi +0 -167
- data/sorbet/rbi/gems/rubocop-ast@1.49.0.rbi +6 -0
- data/sorbet/rbi/gems/rubocop@1.84.2.rbi +7 -0
- data/sorbet/rbi/gems/tapioca@0.17.10.rbi +1 -0
- data/sorbet/rbi/gems/{yard@0.9.38.rbi → yard@0.9.44.rbi} +1206 -241
- data/sorbet/rbi/shims/core.rbi +19 -0
- data/sorbet/rbi/shims/minitest.rbi +5 -2
- data/spec/interface_spec.rb +405 -0
- data/spec/rubocop/cop/type_toolkit/prefer_not_nil_spec.rb +229 -0
- data/spec/spec_helper.rb +14 -0
- metadata +23 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "lint_roller"
|
|
5
|
+
require "type_toolkit/version"
|
|
5
6
|
|
|
6
7
|
module RuboCop
|
|
7
8
|
module Cop
|
|
@@ -12,7 +13,7 @@ module RuboCop
|
|
|
12
13
|
name: "rubocop-type_toolkit",
|
|
13
14
|
version: ::TypeToolkit::VERSION,
|
|
14
15
|
homepage: "https://github.com/Shopify/type_toolkit",
|
|
15
|
-
description: "
|
|
16
|
+
description: "RuboCop rules for Type Toolkit.",
|
|
16
17
|
)
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RuboCop
|
|
5
|
+
module Cop
|
|
6
|
+
module TypeToolkit
|
|
7
|
+
# Replaces Sorbet's `T.must(value)` assertion with Type Toolkit's `value.not_nil!` assertion.
|
|
8
|
+
class PreferNotNil < Base
|
|
9
|
+
extend AutoCorrector
|
|
10
|
+
|
|
11
|
+
MSG = "Use `.not_nil!` instead of `T.must()`."
|
|
12
|
+
RESTRICT_ON_SEND = [:must].freeze
|
|
13
|
+
|
|
14
|
+
COMMA_BYTE = ",".ord
|
|
15
|
+
private_constant :COMMA_BYTE
|
|
16
|
+
|
|
17
|
+
KEYWORD_EXPRESSION_TYPES = [:defined?, :super, :yield, :zsuper].freeze
|
|
18
|
+
private_constant :KEYWORD_EXPRESSION_TYPES
|
|
19
|
+
|
|
20
|
+
#: (RuboCop::AST::SendNode) -> void
|
|
21
|
+
def on_send(node)
|
|
22
|
+
return unless (argument = extract_t_must_argument(node))
|
|
23
|
+
|
|
24
|
+
if nested_t_must?(node)
|
|
25
|
+
add_offense(node, message: MSG)
|
|
26
|
+
else
|
|
27
|
+
replacement = replacement_for(argument)
|
|
28
|
+
correction = correction_for(node, argument, replacement)
|
|
29
|
+
|
|
30
|
+
add_offense(node, message: MSG) do |corrector|
|
|
31
|
+
corrector.replace(node, correction)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
#: (RuboCop::AST::SendNode) -> RuboCop::AST::Node?
|
|
39
|
+
def extract_t_must_argument(node)
|
|
40
|
+
receiver = node.receiver
|
|
41
|
+
return unless receiver.is_a?(RuboCop::AST::ConstNode)
|
|
42
|
+
return unless receiver.short_name == :T && node.method?(:must) && node.arguments.one?
|
|
43
|
+
|
|
44
|
+
namespace = receiver.namespace
|
|
45
|
+
return unless namespace.nil? || namespace.cbase_type?
|
|
46
|
+
|
|
47
|
+
argument = node.first_argument
|
|
48
|
+
return unless argument
|
|
49
|
+
return if argument.splat_type? || argument.kwsplat_type?
|
|
50
|
+
|
|
51
|
+
argument
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#: (RuboCop::AST::Node) -> String
|
|
55
|
+
def replacement_for(argument)
|
|
56
|
+
source = argument.source
|
|
57
|
+
source = "(#{source})" if requires_parentheses?(argument)
|
|
58
|
+
"#{source}.not_nil!"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#: (RuboCop::AST::SendNode, RuboCop::AST::Node, String) -> String
|
|
62
|
+
def correction_for(node, argument, replacement)
|
|
63
|
+
return replacement unless node.multiline?
|
|
64
|
+
|
|
65
|
+
grouped_range = node.source_range.with(begin_pos: node.loc.begin.begin_pos, end_pos: node.loc.end.end_pos)
|
|
66
|
+
grouped_source = grouped_range.source
|
|
67
|
+
comma_offset = argument.source_range.end_pos - grouped_range.begin_pos
|
|
68
|
+
grouped_source.slice!(comma_offset) if grouped_source.getbyte(comma_offset) == COMMA_BYTE
|
|
69
|
+
"#{grouped_source}.not_nil!"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#: (RuboCop::AST::SendNode) -> bool
|
|
73
|
+
def nested_t_must?(node)
|
|
74
|
+
node.each_ancestor(:send).any? do |ancestor|
|
|
75
|
+
ancestor.is_a?(RuboCop::AST::SendNode) && extract_t_must_argument(ancestor)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#: (RuboCop::AST::Node) -> bool
|
|
80
|
+
def requires_parentheses?(argument)
|
|
81
|
+
return false if argument.begin_type?
|
|
82
|
+
|
|
83
|
+
if argument.is_a?(RuboCop::AST::SendNode)
|
|
84
|
+
return true if argument.operator_method?
|
|
85
|
+
return true if argument.arguments? && !argument.parenthesized_call?
|
|
86
|
+
end
|
|
87
|
+
return true if argument.range_type? || argument.operator_keyword?
|
|
88
|
+
return true if argument.if_type? || argument.assignment?
|
|
89
|
+
return true if argument.any_block_type?
|
|
90
|
+
|
|
91
|
+
KEYWORD_EXPRESSION_TYPES.include?(argument.type)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/rubocop-type_toolkit.rb
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypeToolkit
|
|
5
|
+
# Raised when a call is made to an abstract method that never had a real implementation.
|
|
6
|
+
class AbstractMethodNotImplementedError < Exception # rubocop:disable Lint/InheritException
|
|
7
|
+
def initialize(method_name:)
|
|
8
|
+
# Do not rely on this message content! Its content is subject to change.
|
|
9
|
+
super("Abstract method `##{method_name}` was never implemented.")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# This module is included on a class whose instances can be receivers of calls to abstract methods.
|
|
14
|
+
#
|
|
15
|
+
# Since abstract methods are removed at runtime (see `TypeToolkit::DSL#abstract`), attempting to call
|
|
16
|
+
# an unimplemented abstract method would usually raise a `NoMethodError`.
|
|
17
|
+
# This module uses `method_missing` to raise `AbstractMethodNotImplementedError` instead.
|
|
18
|
+
# @requires_ancestor: Kernel
|
|
19
|
+
module AbstractInstanceMethodReceiver
|
|
20
|
+
# This `#method_missing` is hit when calling a potentially abstract method on an instance
|
|
21
|
+
# E.g. TheClass.new.maybe_abstract_method
|
|
22
|
+
#
|
|
23
|
+
# (Symbol, ...) -> untyped
|
|
24
|
+
def method_missing(method_name, ...)
|
|
25
|
+
c = self.class #: as Class[top] & HasAbstractMethods
|
|
26
|
+
|
|
27
|
+
if c.abstract_method_declared?(method_name)
|
|
28
|
+
raise AbstractMethodNotImplementedError.new(method_name:)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#: (Symbol, ?bool) -> bool
|
|
35
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
36
|
+
self.class.abstract_method_declared?(method_name) || super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypeToolkit
|
|
5
|
+
# @requires_ancestor: MethodDefRecorder
|
|
6
|
+
module DSL
|
|
7
|
+
# Mark `method_name` as abstract.
|
|
8
|
+
#
|
|
9
|
+
# A real implementation of the method must be provided somewhere in the ancestor chain.
|
|
10
|
+
# Calls to an unimplemented abstract method will raise `AbstractMethodNotImplementedError`.
|
|
11
|
+
#
|
|
12
|
+
#: (Symbol) -> Symbol
|
|
13
|
+
def abstract(method_name)
|
|
14
|
+
#: self as (Module[top] & HasAbstractMethods & MethodDefRecorder)
|
|
15
|
+
|
|
16
|
+
recorded_method_name, is_singleton_method = __last_method_def
|
|
17
|
+
|
|
18
|
+
if recorded_method_name != method_name
|
|
19
|
+
prefix = is_singleton_method ? "." : "#"
|
|
20
|
+
|
|
21
|
+
# Do not rely on this message content! Its content is subject to change.
|
|
22
|
+
raise <<~MSG.chomp
|
|
23
|
+
`abstract` expected to see `#{prefix}#{method_name}`, but the last recorded method was called `#{recorded_method_name}`.
|
|
24
|
+
This can happen when `abstract` is combined with other metaprogramming.
|
|
25
|
+
If you think this is a bug, please open an issue: https://github.com/Shopify/type_toolkit/issues
|
|
26
|
+
MSG
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The `method_owner` is the class whose method table stores the abstract method.
|
|
30
|
+
#
|
|
31
|
+
# Example:
|
|
32
|
+
#
|
|
33
|
+
# class Foo
|
|
34
|
+
# # is_singleton_method = false, owner is the `Foo` class
|
|
35
|
+
# abstract def foo; end
|
|
36
|
+
#
|
|
37
|
+
# # is_singleton_method = true, owner is `Foo.singleton_class`
|
|
38
|
+
# abstract def self.foo; end
|
|
39
|
+
# end
|
|
40
|
+
method_owner = is_singleton_method ? singleton_class : self #: as Module[top] & HasAbstractMethods
|
|
41
|
+
|
|
42
|
+
# Register the fact that this method is meant to be abstract,
|
|
43
|
+
# used by APIs like `abstract_method_declared?` and `Method#abstract?`
|
|
44
|
+
method_owner.__register_abstract_method(method_name)
|
|
45
|
+
|
|
46
|
+
# We never want the empty "stub" method to be called, so we remove it. This has one of 3 effects:
|
|
47
|
+
#
|
|
48
|
+
# 1. If the abstract method is implemented by a subclass, then there's no effect.
|
|
49
|
+
# The subclass' implementation will always be invoked, so this removal does nothing.
|
|
50
|
+
#
|
|
51
|
+
# 2. If the abstract method was already implemented by a superclass,
|
|
52
|
+
# Then this removal ensures that calls to the method will resolve to
|
|
53
|
+
# the superclass' implementation, and never the empty stub.
|
|
54
|
+
#
|
|
55
|
+
# 3. If the abstract method was not implemented anywhere in the ancestor chain,
|
|
56
|
+
# then this removal ensures we hit `method_missing`, which will then raise
|
|
57
|
+
# the `AbstractMethodNotImplementedError`.
|
|
58
|
+
method_owner.remove_method(method_name)
|
|
59
|
+
|
|
60
|
+
# Return the method name, so `abstract` can be chained, e.g. `private abstract def foo; end`
|
|
61
|
+
method_name
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -27,7 +27,9 @@ module TypeToolkit
|
|
|
27
27
|
#
|
|
28
28
|
# `UnexpectedNilError` should never occur in well-formed code, so it should never be rescued.
|
|
29
29
|
# This is why it inherits from `Exception` instead of `StandardError`,
|
|
30
|
-
# so that bare
|
|
30
|
+
# so that bare rescue clauses (like `rescue => e`) don't accidentally swallow it.
|
|
31
|
+
#
|
|
32
|
+
# Note: `rescue Exception` can still catch it, but that's intentionally harder to write accidentally.
|
|
31
33
|
class UnexpectedNilError < Exception # rubocop:disable Lint/InheritException
|
|
32
34
|
def initialize(message = "Called `not_nil!` on nil.")
|
|
33
35
|
super
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypeToolkit
|
|
5
|
+
# Given a class Foo, abstract methods can be defined at two levels:
|
|
6
|
+
# 1. Instance methods available on the instance of Foo
|
|
7
|
+
# 2. "Class methods" available on the Foo class itself
|
|
8
|
+
#
|
|
9
|
+
# In case 1, the storage belongs one level up, in the class Foo.
|
|
10
|
+
# In case 2, the storage belongs two levels up, in the singleton class of Foo (`Foo::<Foo>`)
|
|
11
|
+
module HasAbstractMethods
|
|
12
|
+
# Private API, do not use directly. Only meant to be called from the `abstract` macro.
|
|
13
|
+
#: (Symbol) -> void
|
|
14
|
+
def __register_abstract_method(method_name) # :nodoc:
|
|
15
|
+
(
|
|
16
|
+
@__abstract_methods ||= Set.new #: Set[Symbol]?
|
|
17
|
+
) << method_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns all methods that were marked abstract (even those which are implemented).
|
|
21
|
+
#: (?bool) -> Array[Symbol]
|
|
22
|
+
def declared_abstract_instance_methods(include_super = true)
|
|
23
|
+
#: self as HasAbstractMethods & Module[top]
|
|
24
|
+
|
|
25
|
+
result = @__abstract_methods
|
|
26
|
+
|
|
27
|
+
return result.to_a unless include_super
|
|
28
|
+
|
|
29
|
+
if defined?(super) && (super_abstract_methods = super)
|
|
30
|
+
if result
|
|
31
|
+
result.merge(super_abstract_methods)
|
|
32
|
+
else
|
|
33
|
+
result = super_abstract_methods
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
abstract_methods_in_interfaces = included_modules.flat_map do |m|
|
|
38
|
+
m.is_a?(HasAbstractMethods) ? m.declared_abstract_instance_methods : []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if abstract_methods_in_interfaces.any?
|
|
42
|
+
if result&.any?
|
|
43
|
+
result.merge(abstract_methods_in_interfaces)
|
|
44
|
+
else
|
|
45
|
+
result = abstract_methods_in_interfaces
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result.to_a
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns all methods that are abstract and have not been implemented.
|
|
53
|
+
#: (?bool) -> Array[Symbol]
|
|
54
|
+
def abstract_instance_methods(include_super = true)
|
|
55
|
+
#: self as HasAbstractMethods & Module[top]
|
|
56
|
+
|
|
57
|
+
declared_abstract_instance_methods(include_super).reject do |m|
|
|
58
|
+
method_defined?(m) || private_method_defined?(m)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns true if the given method name was ever marked abstract, even if it has a concrete implementation.
|
|
63
|
+
#
|
|
64
|
+
# Similar to `public_method_defined?` and friends, this method is called on a class to check if the method
|
|
65
|
+
# is defined for _instances_ of that class. For example:
|
|
66
|
+
#
|
|
67
|
+
# if Foo.abstract_method_declared?(:instance_method)
|
|
68
|
+
# Foo.new.instance_method # Might raise AbstractMethodNotImplementedError
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# if Foo.singleton_class.abstract_method_declared?(:class_method)
|
|
72
|
+
# Foo.class_method # Might raise AbstractMethodNotImplementedError
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
#: (Symbol) -> bool
|
|
76
|
+
def abstract_method_declared?(method_name)
|
|
77
|
+
#: self as Module[top]
|
|
78
|
+
|
|
79
|
+
@__abstract_methods&.include?(method_name) ||
|
|
80
|
+
included_modules.any? { |m| m.is_a?(HasAbstractMethods) && m.abstract_method_declared?(method_name) } ||
|
|
81
|
+
(defined?(super) && super)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns true if the given method is abstract, and has not been implemented.
|
|
85
|
+
# Calling it *will* raise an `AbstractMethodNotImplementedError`.
|
|
86
|
+
#
|
|
87
|
+
# Similar to `public_method_defined?` and friends, this method is called on a class to check if the method
|
|
88
|
+
# is defined for _instances_ of that class. For example:
|
|
89
|
+
#
|
|
90
|
+
# if Foo.abstract_method?(:instance_method)
|
|
91
|
+
# Foo.new.instance_method # Will raise AbstractMethodNotImplementedError
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# if Foo.singleton_class.abstract_method?(:class_method)
|
|
95
|
+
# Foo.class_method # Will raise AbstractMethodNotImplementedError
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
#: (Symbol) -> bool
|
|
99
|
+
def abstract_method?(method_name)
|
|
100
|
+
#: self as (HasAbstractMethods & Module[top])
|
|
101
|
+
|
|
102
|
+
# If the method is defined, it has a concrete implementation, so it's not abstract.
|
|
103
|
+
return false if method_defined?(method_name) || private_method_defined?(method_name)
|
|
104
|
+
|
|
105
|
+
abstract_method_declared?(method_name)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "dsl"
|
|
5
|
+
require_relative "method_def_recorder"
|
|
6
|
+
require_relative "has_abstract_methods"
|
|
7
|
+
require_relative "abstract_method_receiver"
|
|
8
|
+
|
|
9
|
+
module TypeToolkit
|
|
10
|
+
class << self
|
|
11
|
+
#: (Module[top]) -> void
|
|
12
|
+
def make_interface!(mod)
|
|
13
|
+
if Class === mod
|
|
14
|
+
raise TypeError, "Classes can't be interfaces. Did you mean to make it `abstract` instead?"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
mod.extend(TypeToolkit::Interface)
|
|
18
|
+
mod.extend(TypeToolkit::DSL)
|
|
19
|
+
mod.extend(TypeToolkit::MethodDefRecorder)
|
|
20
|
+
mod.extend(TypeToolkit::HasAbstractMethods)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# This module is extended onto any module that represents an interface.
|
|
25
|
+
# All of its members should be public and abstract.
|
|
26
|
+
module Interface
|
|
27
|
+
#: (Module[top]) -> void
|
|
28
|
+
def included(target_module)
|
|
29
|
+
# Including/extending a module is idempotent, so we don't have to worry these were already included/extended.
|
|
30
|
+
|
|
31
|
+
# Potentially abstract methods will be called on instances of `self`, so we need the `method_missing` hooks.
|
|
32
|
+
target_module.include(TypeToolkit::AbstractInstanceMethodReceiver)
|
|
33
|
+
|
|
34
|
+
# The `method_missing` hooks need to be able to look up the abstract methods (from the interface).
|
|
35
|
+
target_module.extend(TypeToolkit::HasAbstractMethods)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypeToolkit
|
|
5
|
+
# This module tracks new methods being defined, and whether they were
|
|
6
|
+
# instance methods (`def foo; end`) or singleton methods (`def self.foo; end`).
|
|
7
|
+
module MethodDefRecorder
|
|
8
|
+
#: [Symbol, bool]?
|
|
9
|
+
attr_reader :__last_method_def
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
#: (Module[top]) -> void
|
|
13
|
+
def extended(target_module)
|
|
14
|
+
# Also extend the singleton class so methods are available there
|
|
15
|
+
target_module.singleton_class.extend(ClassMethods)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# These actually go on the YourClass.singleton_class.singleton_class
|
|
20
|
+
# @requires_ancestor: MethodDefRecorder
|
|
21
|
+
module ClassMethods
|
|
22
|
+
#: () -> [Symbol, bool]?
|
|
23
|
+
def __last_method_def
|
|
24
|
+
#: self as Class[MethodDefRecorder]
|
|
25
|
+
cls = attached_object #: as MethodDefRecorder
|
|
26
|
+
cls.__last_method_def
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Need `@without_runtime` because of https://github.com/Shopify/tapioca/issues/2513
|
|
31
|
+
# @without_runtime
|
|
32
|
+
#: (Symbol) -> void
|
|
33
|
+
def method_added(m)
|
|
34
|
+
is_singleton_method = false
|
|
35
|
+
@__last_method_def = [m, is_singleton_method] #: [Symbol, bool]?
|
|
36
|
+
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Need `@without_runtime` because of https://github.com/Shopify/tapioca/issues/2513
|
|
41
|
+
# @without_runtime
|
|
42
|
+
# @override
|
|
43
|
+
#: (Symbol) -> void
|
|
44
|
+
def singleton_method_added(m)
|
|
45
|
+
return super if m == :singleton_method_added
|
|
46
|
+
|
|
47
|
+
is_singleton_method = true
|
|
48
|
+
@__last_method_def = [m, is_singleton_method] #: [Symbol, bool]?
|
|
49
|
+
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypeToolkit
|
|
5
|
+
# @requires_ancestor: MethodOrUnboundMethod
|
|
6
|
+
module MethodPatch
|
|
7
|
+
# Returns true if this method is an abstract method that hasn't been implemented.
|
|
8
|
+
# Calling it will raise an `AbstractMethodNotImplementedError`.
|
|
9
|
+
#: -> bool
|
|
10
|
+
def abstract?
|
|
11
|
+
return false unless TypeToolkit::HasAbstractMethods === (owner = self.owner)
|
|
12
|
+
|
|
13
|
+
owner.abstract_method?(name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/type_toolkit/version.rb
CHANGED
data/lib/type_toolkit.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require_relative "type_toolkit/version"
|
|
5
|
+
require_relative "type_toolkit/interface"
|
|
6
|
+
require_relative "type_toolkit/ext/method"
|
|
7
|
+
require_relative "type_toolkit/ext/module"
|
|
5
8
|
require_relative "type_toolkit/ext/nil_assertions"
|
|
6
9
|
|
|
7
10
|
module TypeToolkit
|
data/sorbet/config
CHANGED