type_toolkit 0.0.5 → 0.0.7
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/.github/workflows/ci.yml +0 -4
- data/README.md +61 -0
- data/Rakefile +0 -7
- 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 +258 -0
- data/docs/design/self_dot_methods.md +79 -0
- data/lib/rubocop/cop/type_toolkit/plugin.rb +1 -1
- data/lib/rubocop/cop/type_toolkit/prefer_not_nil.rb +150 -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 +66 -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/sorbet/rbi/shims/rubocop_minitest.rbi +5 -2
- data/spec/interface_spec.rb +419 -0
- data/spec/misc/how_method_added_hooks_work_spec.rb +63 -0
- data/spec/rubocop/cop/type_toolkit/prefer_not_nil_spec.rb +464 -0
- data/spec/spec_helper.rb +14 -0
- metadata +24 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12374ec2c61c2218bb47b3e2f79d1d7c9bf59a7f4d75d2c429ebeecb02d4d51d
|
|
4
|
+
data.tar.gz: 13ef655859f7305f5ce532d6f9a3ac53513f2fcc8935d1b1c1f85b07c5f9e5d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ef3a56d5b8838ffcd97adf208f2ce67598dfa6157414ba084a91e146d43669ab1b06810659c9b3226ad5cd80f2ac875f937301238f45ba68d918ddabd177fd4
|
|
7
|
+
data.tar.gz: df81fdc2a72a0146a244a9a1028c227d789db201d3e1f66300a1cb4ccc9caf5345b27474dac8fe858a6f5753ae20bae184380a4837afd1c45da8a9ec4de1f6cf
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -19,12 +19,8 @@ jobs:
|
|
|
19
19
|
rubygems: 4.0.4
|
|
20
20
|
- name: Run type check
|
|
21
21
|
run: bundle exec rake typecheck
|
|
22
|
-
- name: Run type check with Prism parser
|
|
23
|
-
run: bundle exec rake typecheck_prism
|
|
24
|
-
continue-on-error: true
|
|
25
22
|
- name: Run tests
|
|
26
23
|
run: bundle exec rake test
|
|
27
|
-
continue-on-error: true
|
|
28
24
|
- name: Lint Ruby files
|
|
29
25
|
run: bundle exec rake rubocop
|
|
30
26
|
- name: Verify gem RBIs are up-to-date
|
data/README.md
CHANGED
|
@@ -70,6 +70,67 @@ last_delivery = user.not_nil!
|
|
|
70
70
|
.deliveries.last.not_nil!
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
The `TypeToolkit/PreferNotNil` RuboCop cop autocorrects `T.must` and `T.must_because` to `.not_nil!`.
|
|
74
|
+
For `T.must_because`, the reason block's source is preserved as comments inside a parenthesized expression,
|
|
75
|
+
so the correction also works in chained calls and nested expressions:
|
|
76
|
+
|
|
77
|
+
```rb
|
|
78
|
+
# Before
|
|
79
|
+
value = T.must_because(foo) { "Proven non-nil by validation." }
|
|
80
|
+
|
|
81
|
+
# After
|
|
82
|
+
value = (
|
|
83
|
+
# Proven non-nil by validation.
|
|
84
|
+
foo.not_nil!
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The reason is documentation only after correction: it is no longer evaluated or included in the exception message.
|
|
89
|
+
Calls with heredocs in the value or reason block are reported but not autocorrected, because heredoc bodies can extend beyond the block's source range.
|
|
90
|
+
|
|
91
|
+
### Interfaces
|
|
92
|
+
|
|
93
|
+
Interfaces are modules with abstract methods which a conforming class must implement. They help make duck-typing easier to use in Ruby, by validating that your conforming classes do actually provide the correct methods needed of them.
|
|
94
|
+
|
|
95
|
+
The Type Toolkit provides runtime support for interfaces (marked with `interface!`) and abstract methods (marked with `abstract` before the `def` keyword).
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
module Notifier
|
|
101
|
+
interface!
|
|
102
|
+
|
|
103
|
+
#: (String) -> void
|
|
104
|
+
abstract def send_notification(message); end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class SlackNotifier
|
|
108
|
+
include Notifier
|
|
109
|
+
|
|
110
|
+
# @override
|
|
111
|
+
#: (String) -> void
|
|
112
|
+
def send_notification(message)
|
|
113
|
+
puts "Posting to Slack API: #{message.inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
SlackNotifier.new.send_notification("Hello, world!") # ✅
|
|
118
|
+
# => Posting to Slack API: "Hello, world!"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Unimplemented abstract methods cannot be called, and the Type Toolkit runtime will raise an error if you try to do so:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
class EmailNotifier
|
|
125
|
+
include Notifier
|
|
126
|
+
|
|
127
|
+
# Oops, forgot to implement `#send_notification`!
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
EmailNotifier.new.send_notification("Hello, world!") # ❌ TypeToolkit::AbstractMethodNotImplementedError
|
|
131
|
+
# => Abstract method #send_notification was never implemented.
|
|
132
|
+
```
|
|
133
|
+
|
|
73
134
|
## Guiding Principles
|
|
74
135
|
|
|
75
136
|
### Blazingly fast™
|
data/Rakefile
CHANGED
|
@@ -14,13 +14,6 @@ desc "alias for typecheck"; task tc: :typecheck
|
|
|
14
14
|
desc "alias for typecheck"; task srb: :typecheck
|
|
15
15
|
desc "alias for typecheck"; task sorbet: :typecheck
|
|
16
16
|
|
|
17
|
-
desc "Type-check the code base with Sorbet using Prism"
|
|
18
|
-
task :typecheck_prism do
|
|
19
|
-
sh "bundle exec srb tc --parser=prism" do |ok, _res|
|
|
20
|
-
abort unless ok
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
17
|
require "minitest/test_task"
|
|
25
18
|
|
|
26
19
|
Minitest::TestTask.create do |t|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# typed: ignore
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Benchmark the performance overhead of calling:
|
|
5
|
+
# - A concrete implementation of an abstract method
|
|
6
|
+
# - An inherited concrete implementation of an abstract method
|
|
7
|
+
# - The error case of calling an unimplemented abstract method
|
|
8
|
+
|
|
9
|
+
############################################# Results #############################################
|
|
10
|
+
#
|
|
11
|
+
# ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin23]
|
|
12
|
+
#
|
|
13
|
+
# ## Interpreter
|
|
14
|
+
#
|
|
15
|
+
# | Call to... | Regular impl | Inherited impl | Missing impl |
|
|
16
|
+
# |-------------------|--------------------:|------------------------:|--------------------------:|
|
|
17
|
+
# | sorbet-runtime | (same-ish) 23.02 ns | (2.70x slower) 57.30 ns | (1.13x slower) 472.86 ns |
|
|
18
|
+
# | manual delegation | (same-ish) 22.18 ns | (2.07x slower) 44.90 ns | *415.36 ns* |
|
|
19
|
+
# | type_toolkit | (same-ish) 22.56 ns | *22.03 ns* | (2.11x slower) 890.38 ns |
|
|
20
|
+
#
|
|
21
|
+
# ## YJIT#
|
|
22
|
+
# | Call to... | Regular impl | Inherited impl | Missing impl |
|
|
23
|
+
# |-------------------|--------------------:|-------------------------:|--------------------------:|
|
|
24
|
+
# | sorbet-runtime | (same-ish) 1.63 ns | (21.41x slower) 34.91 ns | (1.10x slower) 447.59 ns |
|
|
25
|
+
# | manual delegation | (same-ish) 1.63 ns | (7.15x slower) 11.66 ns | *405.84 ns* |
|
|
26
|
+
# | type_toolkit | (same-ish) 1.67 ns | *1.63 ns* | (1.91x slower) 774.91 ns |
|
|
27
|
+
#
|
|
28
|
+
####################################################################################################
|
|
29
|
+
|
|
30
|
+
require "bundler"
|
|
31
|
+
Bundler.require(:default, :benchmark)
|
|
32
|
+
|
|
33
|
+
require "type_toolkit"
|
|
34
|
+
|
|
35
|
+
module TypeKitDemo
|
|
36
|
+
# Provides the concrete implementation of `m`
|
|
37
|
+
class Parent
|
|
38
|
+
def m1 = "Parent#m1"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module I
|
|
42
|
+
interface!
|
|
43
|
+
|
|
44
|
+
abstract def m1; end
|
|
45
|
+
abstract def m2; end
|
|
46
|
+
abstract def not_implemented; end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Inherits the concrete implementation of `m` from DemoParentClass.
|
|
50
|
+
class Child < Parent
|
|
51
|
+
include I
|
|
52
|
+
|
|
53
|
+
def m2 = "Child#m2"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module SorbetRuntimeDemo
|
|
58
|
+
# Provides the concrete implementation of `m`
|
|
59
|
+
class Parent
|
|
60
|
+
def m1 = "Parent#m1"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
module I
|
|
64
|
+
extend T::Sig
|
|
65
|
+
extend T::Helpers
|
|
66
|
+
|
|
67
|
+
interface!
|
|
68
|
+
|
|
69
|
+
sig { abstract.returns(String) }
|
|
70
|
+
def m1; end
|
|
71
|
+
|
|
72
|
+
sig { abstract.returns(String) }
|
|
73
|
+
def m2; end
|
|
74
|
+
|
|
75
|
+
sig { abstract.returns(String) }
|
|
76
|
+
def not_implemented; end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Inherits the concrete implementation of `m` from DemoParentClass.
|
|
80
|
+
class Child < Parent
|
|
81
|
+
include I
|
|
82
|
+
|
|
83
|
+
def m2 = "Child#m2"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module ManualDelegationDemo
|
|
88
|
+
class Parent
|
|
89
|
+
def m1 = "Parent#m1"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module I
|
|
93
|
+
def m1 = defined?(super) ? super : raise
|
|
94
|
+
def m2 = defined?(super) ? super : raise
|
|
95
|
+
def not_implemented = defined?(super) ? super : raise
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Inherits the concrete implementation of `m` from DemoParentClass.
|
|
99
|
+
class Child < Parent
|
|
100
|
+
include I
|
|
101
|
+
|
|
102
|
+
def m2 = "Child#m2"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
type_toolkit_object = TypeKitDemo::Child.new
|
|
107
|
+
manual_delegation_object = ManualDelegationDemo::Child.new
|
|
108
|
+
sorbet_runtime_object = SorbetRuntimeDemo::Child.new
|
|
109
|
+
|
|
110
|
+
[:interpreter, :yjit].each do |mode|
|
|
111
|
+
if mode == :yjit
|
|
112
|
+
puts <<~MSG
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
================================================================================
|
|
116
|
+
Enabling YJIT...
|
|
117
|
+
================================================================================
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
MSG
|
|
121
|
+
RubyVM::YJIT.enable
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
warmup = 5
|
|
125
|
+
time = 10
|
|
126
|
+
|
|
127
|
+
width = ["type_toolkit", "sorbet-runtime", "manual delegation"].max_by(&:length).length
|
|
128
|
+
|
|
129
|
+
puts "Benchmark the performance of calling the concrete implementation directly..."
|
|
130
|
+
Benchmark.ips do |x|
|
|
131
|
+
x.config(warmup:, time:)
|
|
132
|
+
|
|
133
|
+
x.report("type_toolkit".rjust(width)) do |times|
|
|
134
|
+
i = 0
|
|
135
|
+
while (i += 1) < times
|
|
136
|
+
type_toolkit_object.m2
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
x.report("sorbet-runtime".rjust(width)) do |times|
|
|
141
|
+
i = 0
|
|
142
|
+
while (i += 1) < times
|
|
143
|
+
sorbet_runtime_object.m2
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
x.report("manual delegation".rjust(width)) do |times|
|
|
148
|
+
i = 0
|
|
149
|
+
while (i += 1) < times
|
|
150
|
+
manual_delegation_object.m2
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
x.compare!
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
puts "\n\nBenchmark the performance of calling the inherited concrete implementation..."
|
|
158
|
+
Benchmark.ips do |x|
|
|
159
|
+
x.config(warmup:, time:)
|
|
160
|
+
|
|
161
|
+
x.report("type_toolkit".rjust(width)) do |times|
|
|
162
|
+
i = 0
|
|
163
|
+
while (i += 1) < times
|
|
164
|
+
type_toolkit_object.m1
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
x.report("sorbet-runtime".rjust(width)) do |times|
|
|
169
|
+
i = 0
|
|
170
|
+
while (i += 1) < times
|
|
171
|
+
sorbet_runtime_object.m1
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
x.report("manual delegation".rjust(width)) do |times|
|
|
176
|
+
i = 0
|
|
177
|
+
while (i += 1) < times
|
|
178
|
+
manual_delegation_object.m1
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
x.compare!
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
puts "\n\nTest the performance of calling an unimplemented abstract method..."
|
|
186
|
+
Benchmark.ips do |x|
|
|
187
|
+
x.config(warmup:, time:)
|
|
188
|
+
|
|
189
|
+
x.report("type_toolkit".rjust(width)) do |times|
|
|
190
|
+
i = 0
|
|
191
|
+
while (i += 1) < times
|
|
192
|
+
begin
|
|
193
|
+
type_toolkit_object.not_implemented
|
|
194
|
+
rescue AbstractMethodNotImplementedError # rubocop:disable Lint/SuppressedException
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
x.report("sorbet-runtime".rjust(width)) do |times|
|
|
200
|
+
i = 0
|
|
201
|
+
while (i += 1) < times
|
|
202
|
+
begin
|
|
203
|
+
sorbet_runtime_object.not_implemented
|
|
204
|
+
rescue NotImplementedError # rubocop:disable Lint/SuppressedException
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
x.report("manual delegation".rjust(width)) do |times|
|
|
210
|
+
i = 0
|
|
211
|
+
while (i += 1) < times
|
|
212
|
+
begin
|
|
213
|
+
manual_delegation_object.not_implemented
|
|
214
|
+
rescue StandardError # rubocop:disable Lint/SuppressedException
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
x.compare!
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# typed: ignore
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Benchmark the startup performance of declaring modules/interfaces in 3 different styles:
|
|
5
|
+
# - TypeToolkit (abstract gem)
|
|
6
|
+
# - Sorbet runtime
|
|
7
|
+
# - Manual delegation (defined?(super) pattern)
|
|
8
|
+
|
|
9
|
+
############################################# Results #############################################
|
|
10
|
+
#
|
|
11
|
+
# ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin23]
|
|
12
|
+
#
|
|
13
|
+
# | | Interpreter | YJIT |
|
|
14
|
+
# |-------------------|-------------------------:|---------------------------:|
|
|
15
|
+
# | sorbet-runtime | (21.34x slower) 50.79 μs | (152.34x slower) 377.27 μs |
|
|
16
|
+
# | type_toolkit | (4.18x slower) 9.95 μs | (4.18x slower) 10.35 μs |
|
|
17
|
+
# | manual delegation | 2.38 μs | 2.48 μs |
|
|
18
|
+
#
|
|
19
|
+
####################################################################################################
|
|
20
|
+
|
|
21
|
+
require "bundler"
|
|
22
|
+
Bundler.require(:default, :benchmark)
|
|
23
|
+
|
|
24
|
+
require "type_toolkit"
|
|
25
|
+
|
|
26
|
+
warmup = 5
|
|
27
|
+
time = 10
|
|
28
|
+
|
|
29
|
+
width = ["type_toolkit", "sorbet-runtime", "manual delegation"].max_by(&:length).length
|
|
30
|
+
|
|
31
|
+
puts "Benchmark the time to declare an interface module with abstract methods..."
|
|
32
|
+
|
|
33
|
+
[:interpreter, :yjit].each do |mode|
|
|
34
|
+
if mode == :yjit
|
|
35
|
+
puts <<~MSG
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
================================================================================
|
|
39
|
+
Enabling YJIT...
|
|
40
|
+
================================================================================
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
MSG
|
|
44
|
+
RubyVM::YJIT.enable
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Benchmark.ips do |x|
|
|
48
|
+
x.config(warmup:, time:)
|
|
49
|
+
|
|
50
|
+
x.report("type_toolkit".rjust(width)) do |times|
|
|
51
|
+
i = 0
|
|
52
|
+
while (i += 1) < times
|
|
53
|
+
interface = Module.new do
|
|
54
|
+
interface!
|
|
55
|
+
|
|
56
|
+
abstract def m1; end
|
|
57
|
+
abstract def m2; end
|
|
58
|
+
abstract def m3; end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Class.new do
|
|
62
|
+
include interface
|
|
63
|
+
|
|
64
|
+
def m1 = "m1"
|
|
65
|
+
def m2 = "m2"
|
|
66
|
+
def m3 = "m3"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
x.report("sorbet-runtime".rjust(width)) do |times|
|
|
72
|
+
i = 0
|
|
73
|
+
while (i += 1) < times
|
|
74
|
+
interface = Module.new do
|
|
75
|
+
extend T::Sig
|
|
76
|
+
extend T::Helpers
|
|
77
|
+
|
|
78
|
+
interface!
|
|
79
|
+
|
|
80
|
+
sig { abstract.returns(String) }
|
|
81
|
+
def m1; end
|
|
82
|
+
|
|
83
|
+
sig { abstract.returns(String) }
|
|
84
|
+
def m2; end
|
|
85
|
+
|
|
86
|
+
sig { abstract.returns(String) }
|
|
87
|
+
def m3; end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Class.new do
|
|
91
|
+
extend T::Sig
|
|
92
|
+
|
|
93
|
+
include interface
|
|
94
|
+
|
|
95
|
+
sig { override.returns(String) }
|
|
96
|
+
def m1 = "m1"
|
|
97
|
+
|
|
98
|
+
sig { override.returns(String) }
|
|
99
|
+
def m2 = "m2"
|
|
100
|
+
|
|
101
|
+
sig { override.returns(String) }
|
|
102
|
+
def m3 = "m3"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
x.report("manual delegation".rjust(width)) do |times|
|
|
108
|
+
i = 0
|
|
109
|
+
while (i += 1) < times
|
|
110
|
+
interface = Module.new do
|
|
111
|
+
def m1 = defined?(super) ? super : raise
|
|
112
|
+
def m2 = defined?(super) ? super : raise
|
|
113
|
+
def m3 = defined?(super) ? super : raise
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
Class.new do
|
|
117
|
+
include interface
|
|
118
|
+
|
|
119
|
+
def m1 = "m1"
|
|
120
|
+
def m2 = "m2"
|
|
121
|
+
def m3 = "m3"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
x.compare!
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Benchmark if it's worth checking `Module.include?` before calling`Module.include`
|
|
4
|
+
# ... spoiler: meh, not really.
|
|
5
|
+
|
|
6
|
+
############################################# Results #############################################
|
|
7
|
+
#
|
|
8
|
+
# ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [arm64-darwin23]
|
|
9
|
+
#
|
|
10
|
+
# | | Interpreter | YJIT |
|
|
11
|
+
# |-------------------:|------------------------:|------------------------:|
|
|
12
|
+
# | just include | (2.39x slower) 54.35 ns | (8.53x slower) 35.29 ns |
|
|
13
|
+
# | check then include | 22.72 ns | 4.14 ns |
|
|
14
|
+
#
|
|
15
|
+
####################################################################################################
|
|
16
|
+
|
|
17
|
+
require "bundler"
|
|
18
|
+
Bundler.require(:default, :benchmark)
|
|
19
|
+
|
|
20
|
+
module M1; end
|
|
21
|
+
module M2; end
|
|
22
|
+
class C1; end
|
|
23
|
+
class C2; end
|
|
24
|
+
|
|
25
|
+
[:interpreter, :yjit].each do |mode|
|
|
26
|
+
if mode == :yjit
|
|
27
|
+
puts <<~MSG
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
================================================================================
|
|
31
|
+
Enabling YJIT...
|
|
32
|
+
================================================================================
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
MSG
|
|
36
|
+
RubyVM::YJIT.enable
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
warmup = 5
|
|
40
|
+
time = 10
|
|
41
|
+
|
|
42
|
+
puts "Benchmark the performance of calling the concrete implementation directly..."
|
|
43
|
+
Benchmark.ips do |x|
|
|
44
|
+
x.config(warmup:, time:)
|
|
45
|
+
|
|
46
|
+
x.report("check then include") do |times|
|
|
47
|
+
i = 0
|
|
48
|
+
while (i += 1) < times
|
|
49
|
+
C1.include(M1) unless C1.include?(M1)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
x.report("just include") do |times|
|
|
54
|
+
i = 0
|
|
55
|
+
while (i += 1) < times
|
|
56
|
+
C2.include(M2)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
x.compare!
|
|
61
|
+
end
|
|
62
|
+
end
|
data/config/default.yml
CHANGED
|
@@ -2,3 +2,8 @@ TypeToolkit/DontExpectUnexpectedNil:
|
|
|
2
2
|
Description: "Detects misuse of UnexpectedNilError (rescuing, raising, or asserting it)."
|
|
3
3
|
Enabled: true
|
|
4
4
|
VersionAdded: "0.1.0"
|
|
5
|
+
|
|
6
|
+
TypeToolkit/PreferNotNil:
|
|
7
|
+
Description: "Replaces T.must and T.must_because assertions with Type Toolkit's not_nil! assertion."
|
|
8
|
+
Enabled: true
|
|
9
|
+
VersionAdded: "0.1.0"
|