parlour 3.0.0 → 5.0.0.beta.3
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/ISSUE_TEMPLATE/bug-report.md +0 -0
- data/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- data/.gitignore +1 -1
- data/.parlour +5 -0
- data/.rspec +0 -0
- data/.travis.yml +0 -0
- data/CHANGELOG.md +57 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +233 -19
- data/Rakefile +0 -0
- data/exe/parlour +109 -4
- data/lib/parlour.rb +27 -1
- data/lib/parlour/conflict_resolver.rb +31 -9
- data/lib/parlour/conversion/converter.rb +34 -0
- data/lib/parlour/conversion/rbi_to_rbs.rb +223 -0
- data/lib/parlour/debugging.rb +0 -0
- data/lib/parlour/detached_rbi_generator.rb +1 -6
- data/lib/parlour/detached_rbs_generator.rb +25 -0
- data/lib/parlour/generator.rb +34 -0
- data/lib/parlour/kernel_hack.rb +0 -0
- data/lib/parlour/options.rb +71 -0
- data/lib/parlour/parse_error.rb +0 -0
- data/lib/parlour/plugin.rb +1 -1
- data/lib/parlour/rbi_generator.rb +24 -37
- data/lib/parlour/rbi_generator/arbitrary.rb +5 -2
- data/lib/parlour/rbi_generator/attribute.rb +14 -5
- data/lib/parlour/rbi_generator/class_namespace.rb +8 -3
- data/lib/parlour/rbi_generator/constant.rb +28 -8
- data/lib/parlour/rbi_generator/enum_class_namespace.rb +8 -3
- data/lib/parlour/rbi_generator/extend.rb +5 -2
- data/lib/parlour/rbi_generator/include.rb +5 -2
- data/lib/parlour/rbi_generator/method.rb +15 -10
- data/lib/parlour/rbi_generator/module_namespace.rb +7 -2
- data/lib/parlour/rbi_generator/namespace.rb +68 -18
- data/lib/parlour/rbi_generator/parameter.rb +13 -7
- data/lib/parlour/rbi_generator/rbi_object.rb +19 -78
- data/lib/parlour/rbi_generator/struct_class_namespace.rb +9 -2
- data/lib/parlour/rbi_generator/struct_prop.rb +12 -9
- data/lib/parlour/rbi_generator/type_alias.rb +101 -0
- data/lib/parlour/rbs_generator.rb +24 -0
- data/lib/parlour/rbs_generator/arbitrary.rb +92 -0
- data/lib/parlour/rbs_generator/attribute.rb +82 -0
- data/lib/parlour/rbs_generator/block.rb +49 -0
- data/lib/parlour/rbs_generator/class_namespace.rb +106 -0
- data/lib/parlour/rbs_generator/constant.rb +95 -0
- data/lib/parlour/rbs_generator/extend.rb +92 -0
- data/lib/parlour/rbs_generator/include.rb +92 -0
- data/lib/parlour/rbs_generator/interface_namespace.rb +34 -0
- data/lib/parlour/rbs_generator/method.rb +146 -0
- data/lib/parlour/rbs_generator/method_signature.rb +104 -0
- data/lib/parlour/rbs_generator/module_namespace.rb +35 -0
- data/lib/parlour/rbs_generator/namespace.rb +627 -0
- data/lib/parlour/rbs_generator/parameter.rb +146 -0
- data/lib/parlour/rbs_generator/rbs_object.rb +78 -0
- data/lib/parlour/rbs_generator/type_alias.rb +96 -0
- data/lib/parlour/type_loader.rb +25 -12
- data/lib/parlour/type_parser.rb +174 -17
- data/lib/parlour/typed_object.rb +87 -0
- data/lib/parlour/types.rb +539 -0
- data/lib/parlour/version.rb +1 -1
- data/parlour.gemspec +1 -1
- data/plugin_examples/foobar_plugin.rb +0 -0
- data/rbi/parlour.rbi +1856 -0
- metadata +35 -10
- data/lib/parlour/rbi_generator/options.rb +0 -74
data/lib/parlour.rb
CHANGED
@@ -9,12 +9,17 @@ require 'parlour/kernel_hack'
|
|
9
9
|
|
10
10
|
require 'parlour/plugin'
|
11
11
|
|
12
|
+
require 'parlour/types'
|
13
|
+
|
14
|
+
require 'parlour/options'
|
15
|
+
require 'parlour/typed_object'
|
16
|
+
require 'parlour/generator'
|
12
17
|
require 'parlour/rbi_generator/parameter'
|
13
18
|
require 'parlour/rbi_generator/rbi_object'
|
19
|
+
require 'parlour/rbi_generator/type_alias'
|
14
20
|
require 'parlour/rbi_generator/method'
|
15
21
|
require 'parlour/rbi_generator/attribute'
|
16
22
|
require 'parlour/rbi_generator/arbitrary'
|
17
|
-
require 'parlour/rbi_generator/options'
|
18
23
|
require 'parlour/rbi_generator/include'
|
19
24
|
require 'parlour/rbi_generator/extend'
|
20
25
|
require 'parlour/rbi_generator/constant'
|
@@ -27,6 +32,27 @@ require 'parlour/rbi_generator/struct_class_namespace'
|
|
27
32
|
require 'parlour/rbi_generator'
|
28
33
|
require 'parlour/detached_rbi_generator'
|
29
34
|
|
35
|
+
require 'parlour/rbs_generator/rbs_object'
|
36
|
+
require 'parlour/rbs_generator/type_alias'
|
37
|
+
require 'parlour/rbs_generator/namespace'
|
38
|
+
require 'parlour/rbs_generator/method'
|
39
|
+
require 'parlour/rbs_generator/arbitrary'
|
40
|
+
require 'parlour/rbs_generator/attribute'
|
41
|
+
require 'parlour/rbs_generator/block'
|
42
|
+
require 'parlour/rbs_generator/class_namespace'
|
43
|
+
require 'parlour/rbs_generator/constant'
|
44
|
+
require 'parlour/rbs_generator/extend'
|
45
|
+
require 'parlour/rbs_generator/include'
|
46
|
+
require 'parlour/rbs_generator/method_signature'
|
47
|
+
require 'parlour/rbs_generator/module_namespace'
|
48
|
+
require 'parlour/rbs_generator/interface_namespace'
|
49
|
+
require 'parlour/rbs_generator/parameter'
|
50
|
+
require 'parlour/rbs_generator'
|
51
|
+
require 'parlour/detached_rbs_generator'
|
52
|
+
|
53
|
+
require 'parlour/conversion/converter'
|
54
|
+
require 'parlour/conversion/rbi_to_rbs'
|
55
|
+
|
30
56
|
require 'parlour/conflict_resolver'
|
31
57
|
|
32
58
|
require 'parlour/parse_error'
|
@@ -13,7 +13,7 @@ module Parlour
|
|
13
13
|
resolver: T.proc.params(
|
14
14
|
desc: String,
|
15
15
|
choices: T::Array[RbiGenerator::RbiObject]
|
16
|
-
).returns(RbiGenerator::RbiObject)
|
16
|
+
).returns(T.nilable(RbiGenerator::RbiObject))
|
17
17
|
).void
|
18
18
|
end
|
19
19
|
# Given a namespace, attempts to automatically resolve conflicts in the
|
@@ -23,13 +23,13 @@ module Parlour
|
|
23
23
|
# All children of the given namespace which are also namespaces are
|
24
24
|
# processed recursively, so passing {RbiGenerator#root} will eliminate all
|
25
25
|
# conflicts in the entire object tree.
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# If automatic resolution is not possible, the block passed to this method
|
28
28
|
# is invoked and passed two arguments: a message on what the conflict is,
|
29
29
|
# and an array of candidate objects. The block should return one of these
|
30
30
|
# candidate objects, which will be kept, and all other definitions are
|
31
31
|
# deleted. Alternatively, the block may return nil, which will delete all
|
32
|
-
# definitions. The block may be invoked many times from one call to
|
32
|
+
# definitions. The block may be invoked many times from one call to
|
33
33
|
# {resolve_conflicts}, one for each unresolvable conflict.
|
34
34
|
#
|
35
35
|
# @param namespace [RbiGenerator::Namespace] The starting namespace to
|
@@ -52,14 +52,14 @@ module Parlour
|
|
52
52
|
child.name
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
grouped_by_name_children.each do |name, children|
|
57
57
|
Debugging.debug_puts(self, Debugging::Tree.begin("Checking children named #{name}..."))
|
58
58
|
|
59
59
|
if children.length > 1
|
60
60
|
Debugging.debug_puts(self, Debugging::Tree.here("Possible conflict between #{children.length} objects"))
|
61
61
|
|
62
|
-
# Special case: do we have two methods, one of which is a class method
|
62
|
+
# Special case: do we have two methods, one of which is a class method
|
63
63
|
# and the other isn't? If so, do nothing - this is fine
|
64
64
|
if children.length == 2 &&
|
65
65
|
children.all? { |c| c.is_a?(RbiGenerator::Method) } &&
|
@@ -78,12 +78,13 @@ module Parlour
|
|
78
78
|
c.is_a?(RbiGenerator::Include) || c.is_a?(RbiGenerator::Extend)
|
79
79
|
end
|
80
80
|
end
|
81
|
-
|
81
|
+
deduplicate_mixins_of_name(namespace, name)
|
82
|
+
|
82
83
|
Debugging.debug_puts(self, Debugging::Tree.end("Includes/extends do not conflict with namespaces; no resolution required"))
|
83
84
|
next
|
84
85
|
end
|
85
86
|
|
86
|
-
# Special case: do we have two attributes, one of which is a class
|
87
|
+
# Special case: do we have two attributes, one of which is a class
|
87
88
|
# attribute and the other isn't? If so, do nothing - this is fine
|
88
89
|
if children.length == 2 &&
|
89
90
|
children.all? { |c| c.is_a?(RbiGenerator::Attribute) } &&
|
@@ -93,13 +94,13 @@ module Parlour
|
|
93
94
|
next
|
94
95
|
end
|
95
96
|
|
96
|
-
# Special case: are they all clearly equal? If so, remove all but one
|
97
|
+
# Optimization for Special case: are they all clearly equal? If so, remove all but one
|
97
98
|
if all_eql?(children)
|
98
99
|
Debugging.debug_puts(self, Debugging::Tree.end("All children are identical"))
|
99
100
|
|
100
101
|
# All of the children are the same, so this deletes all of them
|
101
102
|
namespace.children.delete(T.must(children.first))
|
102
|
-
|
103
|
+
|
103
104
|
# Re-add one child
|
104
105
|
namespace.children << T.must(children.first)
|
105
106
|
next
|
@@ -239,5 +240,26 @@ module Parlour
|
|
239
240
|
def all_eql?(arr)
|
240
241
|
arr.each_cons(2).all? { |x, y| x == y }
|
241
242
|
end
|
243
|
+
|
244
|
+
sig { params(namespace: RbiGenerator::Namespace, name: T.nilable(String)).void }
|
245
|
+
# Given a namespace and a child name, removes all duplicate children that are mixins
|
246
|
+
# and that have the given name, except the first found instance.
|
247
|
+
#
|
248
|
+
# @param namespace [RbiGenerator::Namespace] The namespace to deduplicate mixins in.
|
249
|
+
# @param name [String] The name of the mixin modules to deduplicate.
|
250
|
+
# @return [void]
|
251
|
+
def deduplicate_mixins_of_name(namespace, name)
|
252
|
+
found_map = {}
|
253
|
+
namespace.children.delete_if do |x|
|
254
|
+
# ignore children whose names don't match
|
255
|
+
next unless x.name == name
|
256
|
+
# ignore children that are not mixins
|
257
|
+
next unless x.is_a?(RbiGenerator::Include) || x.is_a?(RbiGenerator::Extend)
|
258
|
+
|
259
|
+
delete = found_map.key?(x.class)
|
260
|
+
found_map[x.class] = true
|
261
|
+
delete
|
262
|
+
end
|
263
|
+
end
|
242
264
|
end
|
243
265
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: true
|
2
|
+
require 'rainbow'
|
3
|
+
|
4
|
+
module Parlour
|
5
|
+
module Conversion
|
6
|
+
# An abstract class which converts between the node trees of two type
|
7
|
+
# systems.
|
8
|
+
class Converter
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
abstract!
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@warnings = []
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { returns(T::Array[[String, TypedObject]]) }
|
18
|
+
attr_reader :warnings
|
19
|
+
|
20
|
+
sig { params(msg: String, node: RbiGenerator::RbiObject).void }
|
21
|
+
def add_warning(msg, node)
|
22
|
+
warnings << [msg, node]
|
23
|
+
|
24
|
+
return if $VERBOSE.nil?
|
25
|
+
class_name = T.must(self.class.name).split('::').last
|
26
|
+
print Rainbow("Parlour warning: ").yellow.dark.bold
|
27
|
+
print Rainbow("#{class_name}: ").magenta.bright.bold
|
28
|
+
puts msg
|
29
|
+
print Rainbow(" └ at object: ").blue.bright.bold
|
30
|
+
puts node.describe
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
module Conversion
|
4
|
+
# Converts RBI types to RBS types.
|
5
|
+
class RbiToRbs < Converter
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(rbs_gen: RbsGenerator).void }
|
9
|
+
def initialize(rbs_gen)
|
10
|
+
super()
|
11
|
+
@rbs_gen = rbs_gen
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { returns(RbsGenerator) }
|
15
|
+
attr_reader :rbs_gen
|
16
|
+
|
17
|
+
sig { params(from: RbiGenerator::Namespace, to: RbsGenerator::Namespace).void }
|
18
|
+
def convert_all(from, to)
|
19
|
+
from.children.each do |child|
|
20
|
+
convert_object(child, to)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
node: RbiGenerator::RbiObject,
|
27
|
+
new_parent: RbsGenerator::Namespace,
|
28
|
+
).void
|
29
|
+
end
|
30
|
+
def convert_object(node, new_parent)
|
31
|
+
case node
|
32
|
+
when RbiGenerator::StructClassNamespace
|
33
|
+
add_warning 'performing a one-way conversion of an RBI struct to RBS', node
|
34
|
+
|
35
|
+
klass = new_parent.create_class(node.name)
|
36
|
+
klass.add_comments(node.comments)
|
37
|
+
|
38
|
+
# Create a constructor
|
39
|
+
klass.create_method('initialize', [
|
40
|
+
RbsGenerator::MethodSignature.new(
|
41
|
+
node.props.map do |prop|
|
42
|
+
RbsGenerator::Parameter.new(
|
43
|
+
"#{prop.name}:",
|
44
|
+
type: prop.type,
|
45
|
+
required: !prop.optional,
|
46
|
+
)
|
47
|
+
end,
|
48
|
+
nil,
|
49
|
+
)
|
50
|
+
])
|
51
|
+
|
52
|
+
# Make each prop a getter (and setter, if not immutable) attribute
|
53
|
+
node.props.each do |prop|
|
54
|
+
klass.create_attribute(
|
55
|
+
prop.name,
|
56
|
+
kind: prop.immutable ? :reader : :accessor,
|
57
|
+
type: prop.type,
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
klass
|
62
|
+
|
63
|
+
when RbiGenerator::EnumClassNamespace
|
64
|
+
add_warning 'performing a one-way conversion of an RBI enum to RBS', node
|
65
|
+
|
66
|
+
klass = new_parent.create_class(node.name)
|
67
|
+
klass.add_comments(node.comments)
|
68
|
+
|
69
|
+
# Define .values
|
70
|
+
klass.create_method('values', [
|
71
|
+
RbsGenerator::MethodSignature.new([], Types::Array.new(node.name))
|
72
|
+
], class_method: true)
|
73
|
+
|
74
|
+
# Define each enum variant
|
75
|
+
node.enums.each do |variant|
|
76
|
+
# We don't care about any extra value
|
77
|
+
variant = variant[0] if Array === variant
|
78
|
+
|
79
|
+
klass.create_constant(variant, type: node.name)
|
80
|
+
end
|
81
|
+
|
82
|
+
klass
|
83
|
+
|
84
|
+
when RbiGenerator::Arbitrary
|
85
|
+
add_warning 'converting type of Arbitrary is likely to cause syntax errors; doing it anyway', node
|
86
|
+
new_parent.create_arbitrary(
|
87
|
+
code: node.code,
|
88
|
+
).add_comments(node.comments)
|
89
|
+
|
90
|
+
when RbiGenerator::Attribute
|
91
|
+
if node.class_attribute
|
92
|
+
add_warning 'RBS does not support class attributes; dropping', node
|
93
|
+
return
|
94
|
+
end
|
95
|
+
new_parent.create_attribute(
|
96
|
+
node.name,
|
97
|
+
kind: node.kind,
|
98
|
+
type: node.type,
|
99
|
+
).add_comments(node.comments)
|
100
|
+
|
101
|
+
when RbiGenerator::ClassNamespace
|
102
|
+
if node.abstract
|
103
|
+
add_warning 'RBS does not support abstract classes', node
|
104
|
+
end
|
105
|
+
klass = new_parent.create_class(
|
106
|
+
node.name,
|
107
|
+
superclass: node.superclass
|
108
|
+
)
|
109
|
+
klass.add_comments(node.comments)
|
110
|
+
node.children.each do |child|
|
111
|
+
convert_object(child, klass)
|
112
|
+
end
|
113
|
+
|
114
|
+
when RbiGenerator::Constant
|
115
|
+
if node.eigen_constant
|
116
|
+
add_warning 'RBS does not support constants on eigenclasses; dropping', node
|
117
|
+
return
|
118
|
+
end
|
119
|
+
new_parent.create_constant(
|
120
|
+
node.name,
|
121
|
+
type: node.value,
|
122
|
+
).add_comments(node.comments)
|
123
|
+
|
124
|
+
when RbiGenerator::Extend
|
125
|
+
new_parent.create_extend(node.name).add_comments(node.comments)
|
126
|
+
|
127
|
+
when RbiGenerator::Include
|
128
|
+
new_parent.create_include(node.name).add_comments(node.comments)
|
129
|
+
|
130
|
+
when RbiGenerator::Method
|
131
|
+
# Convert parameters
|
132
|
+
parameters = node.parameters
|
133
|
+
.reject { |param| param.kind == :block }
|
134
|
+
.map do |param|
|
135
|
+
RbsGenerator::Parameter.new(
|
136
|
+
param.name,
|
137
|
+
type: param.type,
|
138
|
+
required: param.default.nil?
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Find block if there is one
|
143
|
+
block_param = node.parameters.find { |param| param.kind == :block }
|
144
|
+
if block_param
|
145
|
+
if String === block_param.type
|
146
|
+
add_warning "block must have a Types::Type for conversion; dropping block", node
|
147
|
+
block = nil
|
148
|
+
else
|
149
|
+
# A nilable proc is an optional block
|
150
|
+
block_param_type = block_param.type
|
151
|
+
if Types::Nilable === block_param_type && Types::Proc === block_param_type.type
|
152
|
+
t = T.cast(block_param_type.type, Types::Proc)
|
153
|
+
required = false
|
154
|
+
block = RbsGenerator::Block.new(t, required)
|
155
|
+
elsif Types::Proc === block_param_type
|
156
|
+
t = block_param_type
|
157
|
+
required = true
|
158
|
+
block = RbsGenerator::Block.new(t, required)
|
159
|
+
elsif Types::Untyped === block_param_type
|
160
|
+
# Consider there to be a block of unknown types
|
161
|
+
block = RbsGenerator::Block.new(
|
162
|
+
Types::Proc.new(
|
163
|
+
[
|
164
|
+
Types::Proc::Parameter.new('*args', Types::Untyped.new),
|
165
|
+
Types::Proc::Parameter.new('**kwargs', Types::Untyped.new),
|
166
|
+
],
|
167
|
+
Types::Untyped.new,
|
168
|
+
),
|
169
|
+
false,
|
170
|
+
)
|
171
|
+
else
|
172
|
+
add_warning 'block type must be a Types::Proc (or nilable one); dropping block', node
|
173
|
+
end
|
174
|
+
end
|
175
|
+
else
|
176
|
+
block = nil
|
177
|
+
end
|
178
|
+
|
179
|
+
new_parent.create_method(
|
180
|
+
node.name,
|
181
|
+
[
|
182
|
+
RbsGenerator::MethodSignature.new(
|
183
|
+
parameters,
|
184
|
+
node.return_type,
|
185
|
+
block: block,
|
186
|
+
type_parameters: node.type_parameters,
|
187
|
+
)
|
188
|
+
],
|
189
|
+
class_method: node.class_method,
|
190
|
+
).add_comments(node.comments)
|
191
|
+
|
192
|
+
when RbiGenerator::ModuleNamespace
|
193
|
+
if node.interface
|
194
|
+
rbs_node = new_parent.create_interface(
|
195
|
+
node.name,
|
196
|
+
)
|
197
|
+
else
|
198
|
+
rbs_node = new_parent.create_module(
|
199
|
+
node.name,
|
200
|
+
)
|
201
|
+
end
|
202
|
+
rbs_node.add_comments(node.comments)
|
203
|
+
node.children.each do |child|
|
204
|
+
convert_object(child, rbs_node)
|
205
|
+
end
|
206
|
+
|
207
|
+
when RbiGenerator::Namespace
|
208
|
+
add_warning 'unspecialized namespaces are not supposed to be in the tree; you may run into issues', node
|
209
|
+
namespace = RbsGenerator::Namespace.new(rbs_gen)
|
210
|
+
namespace.add_comments(node.comments)
|
211
|
+
node.children.each do |child|
|
212
|
+
convert_object(child, namespace)
|
213
|
+
end
|
214
|
+
new_parent.children << namespace
|
215
|
+
|
216
|
+
else
|
217
|
+
raise "missing conversion for #{node.describe}"
|
218
|
+
# TODO: stick a T.absurd here
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
data/lib/parlour/debugging.rb
CHANGED
File without changes
|
@@ -6,17 +6,12 @@ module Parlour
|
|
6
6
|
def detached!
|
7
7
|
raise "cannot call methods on a detached RBI generator"
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
sig { override.returns(Options) }
|
11
11
|
def options
|
12
12
|
detached!
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.returns(Namespace) }
|
16
|
-
def root
|
17
|
-
detached!
|
18
|
-
end
|
19
|
-
|
20
15
|
sig { override.returns(T.nilable(Plugin)) }
|
21
16
|
def current_plugin
|
22
17
|
nil
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module Parlour
|
4
|
+
class DetachedRbsGenerator < RbsGenerator
|
5
|
+
sig { returns(T.untyped) }
|
6
|
+
def detached!
|
7
|
+
raise "cannot call methods on a detached RBS generator"
|
8
|
+
end
|
9
|
+
|
10
|
+
sig { override.returns(Options) }
|
11
|
+
def options
|
12
|
+
detached!
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.returns(T.nilable(Plugin)) }
|
16
|
+
def current_plugin
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { override.returns(String) }
|
21
|
+
def rbs
|
22
|
+
detached!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|