decode 0.24.3 → 0.24.4
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
- checksums.yaml.gz.sig +0 -0
- data/bake/decode/rbs.rb +1 -1
- data/context/coverage.md +1 -1
- data/context/getting-started.md +1 -1
- data/context/ruby-documentation.md +3 -3
- data/context/types.md +127 -0
- data/lib/decode/comment/attribute.rb +4 -1
- data/lib/decode/comment/constant.rb +47 -0
- data/lib/decode/comment/node.rb +32 -12
- data/lib/decode/comment/option.rb +1 -1
- data/lib/decode/comment/parameter.rb +5 -1
- data/lib/decode/comment/rbs.rb +8 -8
- data/lib/decode/comment/tag.rb +13 -1
- data/lib/decode/comment/tags.rb +16 -5
- data/lib/decode/comment/text.rb +1 -0
- data/lib/decode/comment/yields.rb +5 -1
- data/lib/decode/definition.rb +33 -31
- data/lib/decode/documentation.rb +10 -5
- data/lib/decode/index.rb +12 -7
- data/lib/decode/language/generic.rb +10 -1
- data/lib/decode/language/reference.rb +7 -4
- data/lib/decode/language/ruby/class.rb +2 -2
- data/lib/decode/language/ruby/code.rb +21 -3
- data/lib/decode/language/ruby/definition.rb +15 -3
- data/lib/decode/language/ruby/generic.rb +2 -1
- data/lib/decode/language/ruby/parser.rb +132 -91
- data/lib/decode/language/ruby/reference.rb +4 -1
- data/lib/decode/language/ruby/segment.rb +2 -2
- data/lib/decode/languages.rb +29 -8
- data/lib/decode/location.rb +12 -1
- data/lib/decode/rbs/class.rb +91 -14
- data/lib/decode/rbs/generator.rb +67 -11
- data/lib/decode/rbs/method.rb +394 -68
- data/lib/decode/rbs/module.rb +81 -5
- data/lib/decode/rbs/type.rb +51 -0
- data/lib/decode/rbs/wrapper.rb +10 -3
- data/lib/decode/scope.rb +2 -2
- data/lib/decode/segment.rb +3 -2
- data/lib/decode/source.rb +5 -14
- data/lib/decode/syntax/rewriter.rb +4 -1
- data/lib/decode/trie.rb +29 -21
- data/lib/decode/version.rb +2 -1
- data/readme.md +6 -0
- data/releases.md +6 -0
- data/sig/decode.rbs +501 -113
- data.tar.gz.sig +0 -0
- metadata +4 -1
- metadata.gz.sig +0 -0
data/lib/decode/rbs/method.rb
CHANGED
@@ -6,17 +6,20 @@
|
|
6
6
|
require "rbs"
|
7
7
|
require "console"
|
8
8
|
require_relative "wrapper"
|
9
|
+
require_relative "type"
|
9
10
|
|
10
11
|
module Decode
|
11
12
|
module RBS
|
12
13
|
# Represents a Ruby method definition wrapper for RBS generation.
|
13
14
|
class Method < Wrapper
|
14
|
-
|
15
15
|
# Initialize a new method wrapper.
|
16
16
|
# @parameter definition [Decode::Definition] The method definition to wrap.
|
17
17
|
def initialize(definition)
|
18
18
|
super
|
19
19
|
@signatures = nil
|
20
|
+
@keyword_arguments = nil
|
21
|
+
@return_type = nil
|
22
|
+
@parameters = nil
|
20
23
|
end
|
21
24
|
|
22
25
|
# Extract method signatures from the method definition.
|
@@ -25,10 +28,28 @@ module Decode
|
|
25
28
|
@signatures ||= extract_signatures
|
26
29
|
end
|
27
30
|
|
31
|
+
# Extract keyword arguments from the method definition.
|
32
|
+
# @returns [Hash] Hash with :required and :optional keys.
|
33
|
+
def keyword_arguments
|
34
|
+
@keyword_arguments ||= extract_keyword_arguments(@definition, nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extract return type from the method definition.
|
38
|
+
# @returns [::RBS::Types::t] The RBS return type.
|
39
|
+
def return_type
|
40
|
+
@return_type ||= extract_return_type(@definition, nil) || ::RBS::Parser.parse_type("untyped")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Extract parameters from the method definition.
|
44
|
+
# @returns [Array] Array of RBS parameter objects.
|
45
|
+
def parameters
|
46
|
+
@parameters ||= extract_parameters(@definition, nil)
|
47
|
+
end
|
48
|
+
|
28
49
|
# Convert the method definition to RBS AST
|
29
50
|
def to_rbs_ast(index = nil)
|
30
51
|
method_name = @definition.name
|
31
|
-
comment =
|
52
|
+
comment = self.comment
|
32
53
|
|
33
54
|
overloads = []
|
34
55
|
if signatures.any?
|
@@ -40,25 +61,38 @@ module Decode
|
|
40
61
|
)
|
41
62
|
end
|
42
63
|
else
|
43
|
-
return_type =
|
44
|
-
parameters = extract_parameters(@definition, index)
|
45
|
-
block_type = extract_block_type(@definition, index)
|
64
|
+
return_type = self.return_type
|
46
65
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
# Get parameters using AST-based detection
|
67
|
+
if ast_function = build_function_type_from_ast(@definition, index)
|
68
|
+
method_type = ::RBS::MethodType.new(
|
69
|
+
type_params: [],
|
70
|
+
type: ast_function,
|
71
|
+
block: extract_block_type(@definition, index),
|
72
|
+
location: nil
|
73
|
+
)
|
74
|
+
else
|
75
|
+
# Fall back to documentation-based approach
|
76
|
+
parameters = self.parameters
|
77
|
+
keywords = self.keyword_arguments
|
78
|
+
block_type = extract_block_type(@definition, index)
|
79
|
+
|
80
|
+
method_type = ::RBS::MethodType.new(
|
81
|
+
type_params: [],
|
82
|
+
type: ::RBS::Types::Function.new(
|
83
|
+
required_positionals: parameters,
|
84
|
+
optional_positionals: [],
|
85
|
+
rest_positionals: nil,
|
86
|
+
trailing_positionals: [],
|
87
|
+
required_keywords: keywords[:required],
|
88
|
+
optional_keywords: keywords[:optional],
|
89
|
+
rest_keywords: nil,
|
90
|
+
return_type: return_type
|
91
|
+
),
|
92
|
+
block: block_type,
|
93
|
+
location: nil
|
94
|
+
)
|
95
|
+
end
|
62
96
|
|
63
97
|
overloads << ::RBS::AST::Members::MethodDefinition::Overload.new(
|
64
98
|
method_type: method_type,
|
@@ -69,14 +103,118 @@ module Decode
|
|
69
103
|
kind = @definition.receiver ? :singleton : :instance
|
70
104
|
|
71
105
|
::RBS::AST::Members::MethodDefinition.new(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
106
|
+
name: method_name.to_sym,
|
107
|
+
kind: kind,
|
108
|
+
overloads: overloads,
|
109
|
+
annotations: [],
|
110
|
+
location: nil,
|
111
|
+
comment: comment,
|
112
|
+
overloading: false,
|
113
|
+
visibility: @definition.visibility || :public
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Build a complete RBS function type from AST information.
|
118
|
+
# @parameter definition [Definition] The method definition.
|
119
|
+
# @parameter index [Index] The index for context.
|
120
|
+
# @returns [RBS::Types::Function] The complete function type, or nil if no AST.
|
121
|
+
def build_function_type_from_ast(definition, index)
|
122
|
+
node = definition.node
|
123
|
+
# Only return nil if we don't have an AST node at all
|
124
|
+
return nil unless node&.respond_to?(:parameters)
|
125
|
+
|
126
|
+
doc_types = extract_documented_parameter_types(definition)
|
127
|
+
|
128
|
+
required_positionals = []
|
129
|
+
optional_positionals = []
|
130
|
+
rest_positionals = nil
|
131
|
+
required_keywords = {}
|
132
|
+
optional_keywords = {}
|
133
|
+
keyword_rest = nil
|
134
|
+
|
135
|
+
# Only process parameters if the node actually has them:
|
136
|
+
if node.parameters
|
137
|
+
# Handle required positional parameters:
|
138
|
+
if node.parameters.respond_to?(:requireds) && node.parameters.requireds
|
139
|
+
node.parameters.requireds.each do |param|
|
140
|
+
name = param.name
|
141
|
+
type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
142
|
+
|
143
|
+
required_positionals << ::RBS::Types::Function::Param.new(
|
144
|
+
type: type,
|
145
|
+
name: name.to_sym
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Handle optional positional parameters (with defaults):
|
151
|
+
if node.parameters.respond_to?(:optionals) && node.parameters.optionals
|
152
|
+
node.parameters.optionals.each do |param|
|
153
|
+
name = param.name
|
154
|
+
# For optional parameters, use the documented type as-is (don't make it nullable):
|
155
|
+
type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
156
|
+
|
157
|
+
optional_positionals << ::RBS::Types::Function::Param.new(
|
158
|
+
type: type,
|
159
|
+
name: name.to_sym
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Handle rest parameter (*args):
|
165
|
+
if node.parameters.respond_to?(:rest) && node.parameters.rest
|
166
|
+
rest_param = node.parameters.rest
|
167
|
+
name = rest_param.name || :args
|
168
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
169
|
+
|
170
|
+
rest_positionals = ::RBS::Types::Function::Param.new(
|
171
|
+
type: base_type,
|
172
|
+
name: name.to_sym
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Handle keyword parameters:
|
177
|
+
if node.parameters.respond_to?(:keywords) && node.parameters.keywords
|
178
|
+
node.parameters.keywords.each do |param|
|
179
|
+
name = param.name
|
180
|
+
type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
181
|
+
|
182
|
+
if param.respond_to?(:value) && param.value
|
183
|
+
# Has default value - optional keyword:
|
184
|
+
optional_keywords[name.to_sym] = type
|
185
|
+
else
|
186
|
+
# No default value - required keyword:
|
187
|
+
required_keywords[name.to_sym] = type
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Handle keyword rest parameter (**kwargs):
|
193
|
+
if node.parameters.respond_to?(:keyword_rest) && node.parameters.keyword_rest
|
194
|
+
rest_param = node.parameters.keyword_rest
|
195
|
+
if rest_param.name
|
196
|
+
name = rest_param.name
|
197
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
198
|
+
|
199
|
+
keyword_rest = ::RBS::Types::Function::Param.new(
|
200
|
+
type: base_type,
|
201
|
+
name: name.to_sym
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
return_type = extract_return_type(@definition, index) || ::RBS::Parser.parse_type("untyped")
|
208
|
+
|
209
|
+
::RBS::Types::Function.new(
|
210
|
+
required_positionals: required_positionals,
|
211
|
+
optional_positionals: optional_positionals,
|
212
|
+
rest_positionals: rest_positionals,
|
213
|
+
trailing_positionals: [],
|
214
|
+
required_keywords: required_keywords,
|
215
|
+
optional_keywords: optional_keywords,
|
216
|
+
rest_keywords: keyword_rest,
|
217
|
+
return_type: return_type
|
80
218
|
)
|
81
219
|
end
|
82
220
|
|
@@ -86,37 +224,54 @@ module Decode
|
|
86
224
|
extract_tags.select(&:method_signature?).map(&:method_signature)
|
87
225
|
end
|
88
226
|
|
89
|
-
# Extract return type from method documentation
|
227
|
+
# Extract return type from method documentation.
|
90
228
|
def extract_return_type(definition, index)
|
91
|
-
# Look for @returns tags in the method's documentation
|
229
|
+
# Look for @returns tags in the method's documentation:
|
92
230
|
documentation = definition.documentation
|
93
231
|
|
94
|
-
# Find @returns
|
95
|
-
|
232
|
+
# Find all @returns tags:
|
233
|
+
returns_tags = documentation&.filter(Decode::Comment::Returns)&.to_a
|
96
234
|
|
97
|
-
if
|
98
|
-
|
99
|
-
|
100
|
-
|
235
|
+
if returns_tags&.any?
|
236
|
+
if returns_tags.length == 1
|
237
|
+
# Single return type:
|
238
|
+
type_string = returns_tags.first.type.strip
|
239
|
+
Type.parse(type_string)
|
240
|
+
else
|
241
|
+
# Multiple return types - create union:
|
242
|
+
types = returns_tags.map do |tag|
|
243
|
+
type_string = tag.type.strip
|
244
|
+
Type.parse(type_string)
|
245
|
+
end
|
246
|
+
|
247
|
+
::RBS::Types::Union.new(types: types, location: nil)
|
248
|
+
end
|
101
249
|
else
|
102
|
-
# Infer return type based on method name patterns
|
250
|
+
# Infer return type based on method name patterns:
|
103
251
|
infer_return_type(definition)
|
104
252
|
end
|
105
253
|
end
|
106
254
|
|
107
|
-
# Extract parameter types from method documentation
|
255
|
+
# Extract parameter types from method documentation.
|
108
256
|
def extract_parameters(definition, index)
|
257
|
+
# Try AST-based extraction first:
|
258
|
+
if ast_params = extract_parameters_from_ast(definition)
|
259
|
+
return ast_params unless ast_params.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
# Fall back to documentation-based extraction:
|
109
263
|
documentation = definition.documentation
|
110
264
|
return [] unless documentation
|
111
265
|
|
112
|
-
# Find @parameter tags
|
266
|
+
# Find @parameter tags (but not @option tags, which are handled separately):
|
113
267
|
param_tags = documentation.filter(Decode::Comment::Parameter).to_a
|
268
|
+
param_tags = param_tags.reject {|tag| tag.is_a?(Decode::Comment::Option)}
|
114
269
|
return [] if param_tags.empty?
|
115
270
|
|
116
271
|
param_tags.map do |tag|
|
117
272
|
name = tag.name
|
118
273
|
type_string = tag.type.strip
|
119
|
-
type =
|
274
|
+
type = Type.parse(type_string)
|
120
275
|
|
121
276
|
::RBS::Types::Function::Param.new(
|
122
277
|
type: type,
|
@@ -125,20 +280,205 @@ module Decode
|
|
125
280
|
end
|
126
281
|
end
|
127
282
|
|
128
|
-
# Extract
|
283
|
+
# Extract parameter information from the Prism AST node.
|
284
|
+
# @parameter definition [Definition] The method definition with AST node.
|
285
|
+
# @returns [Array] Array of RBS parameter objects, or nil if no AST available.
|
286
|
+
def extract_parameters_from_ast(definition)
|
287
|
+
node = definition.node
|
288
|
+
return nil unless node&.respond_to?(:parameters) && node.parameters
|
289
|
+
|
290
|
+
params = []
|
291
|
+
doc_types = extract_documented_parameter_types(definition)
|
292
|
+
|
293
|
+
# Handle required positional parameters:
|
294
|
+
if node.parameters.respond_to?(:requireds) && node.parameters.requireds
|
295
|
+
node.parameters.requireds.each do |param|
|
296
|
+
name = param.name
|
297
|
+
type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
298
|
+
|
299
|
+
params << ::RBS::Types::Function::Param.new(
|
300
|
+
type: type,
|
301
|
+
name: name.to_sym
|
302
|
+
)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Handle optional positional parameters (with defaults):
|
307
|
+
if node.parameters.respond_to?(:optionals) && node.parameters.optionals
|
308
|
+
node.parameters.optionals.each do |param|
|
309
|
+
name = param.name
|
310
|
+
# For optional parameters, make the documented type optional if not already:
|
311
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
312
|
+
type = make_type_optional_if_needed(base_type)
|
313
|
+
|
314
|
+
params << ::RBS::Types::Function::Param.new(
|
315
|
+
type: type,
|
316
|
+
name: name.to_sym
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Handle rest parameter (*args):
|
322
|
+
if node.parameters.respond_to?(:rest) && node.parameters.rest
|
323
|
+
rest_param = node.parameters.rest
|
324
|
+
name = rest_param.name || :args
|
325
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
326
|
+
# Rest parameters should be `Array[T]`:
|
327
|
+
array_type = ::RBS::Types::ClassInstance.new(
|
328
|
+
name: ::RBS::TypeName.new(name: :Array, namespace: ::RBS::Namespace.empty),
|
329
|
+
args: [base_type],
|
330
|
+
location: nil
|
331
|
+
)
|
332
|
+
|
333
|
+
params << ::RBS::Types::Function::Param.new(
|
334
|
+
type: array_type,
|
335
|
+
name: name.to_sym
|
336
|
+
)
|
337
|
+
end
|
338
|
+
|
339
|
+
params
|
340
|
+
end
|
341
|
+
|
342
|
+
# Extract keyword arguments from @option tags and AST.
|
343
|
+
def extract_keyword_arguments(definition, index)
|
344
|
+
# Try AST-based extraction first:
|
345
|
+
if ast_keywords = extract_keyword_arguments_from_ast(definition)
|
346
|
+
return ast_keywords
|
347
|
+
end
|
348
|
+
|
349
|
+
# Fall back to documentation-based extraction:
|
350
|
+
documentation = definition.documentation
|
351
|
+
return { required: {}, optional: {} } unless documentation
|
352
|
+
|
353
|
+
# Find @option tags:
|
354
|
+
option_tags = documentation.filter(Decode::Comment::Option).to_a
|
355
|
+
return { required: {}, optional: {} } if option_tags.empty?
|
356
|
+
|
357
|
+
keywords = { required: {}, optional: {} }
|
358
|
+
|
359
|
+
option_tags.each do |tag|
|
360
|
+
name = tag.name.to_s
|
361
|
+
# Remove leading colon if present (e.g., ":cached" -> "cached"):
|
362
|
+
name = name.sub(/\A:/, "")
|
363
|
+
|
364
|
+
type_string = tag.type.strip
|
365
|
+
type = Type.parse(type_string)
|
366
|
+
|
367
|
+
# Determine if the keyword is optional based on the type annotation.
|
368
|
+
# If the type is nullable (contains nil or ends with ?), make it optional:
|
369
|
+
if Type.nullable?(type)
|
370
|
+
keywords[:optional][name.to_sym] = type
|
371
|
+
else
|
372
|
+
keywords[:required][name.to_sym] = type
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
keywords
|
377
|
+
end
|
378
|
+
|
379
|
+
# Extract keyword arguments from the Prism AST node.
|
380
|
+
# @parameter definition [Definition] The method definition with AST node.
|
381
|
+
# @returns [Hash] Hash with :required and :optional keyword arguments, or nil if no AST.
|
382
|
+
def extract_keyword_arguments_from_ast(definition)
|
383
|
+
node = definition.node
|
384
|
+
return nil unless node&.respond_to?(:parameters) && node.parameters
|
385
|
+
|
386
|
+
required = {}
|
387
|
+
optional = {}
|
388
|
+
doc_types = extract_documented_parameter_types(definition)
|
389
|
+
|
390
|
+
# Handle keyword parameters:
|
391
|
+
if node.parameters.respond_to?(:keywords) && node.parameters.keywords
|
392
|
+
node.parameters.keywords.each do |param|
|
393
|
+
name = param.name
|
394
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
395
|
+
|
396
|
+
if param.respond_to?(:value) && param.value
|
397
|
+
# Has default value - optional keyword:
|
398
|
+
type = make_type_optional_if_needed(base_type)
|
399
|
+
optional[name.to_sym] = type
|
400
|
+
else
|
401
|
+
# No default value - required keyword:
|
402
|
+
required[name.to_sym] = base_type
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Handle keyword rest parameter (**kwargs):
|
408
|
+
if node.parameters.respond_to?(:keyword_rest) && node.parameters.keyword_rest
|
409
|
+
rest_param = node.parameters.keyword_rest
|
410
|
+
if rest_param.name
|
411
|
+
name = rest_param.name
|
412
|
+
base_type = doc_types[name.to_s] || ::RBS::Parser.parse_type("untyped")
|
413
|
+
# Keyword rest should be `Hash[Symbol, T]`:
|
414
|
+
hash_type = ::RBS::Types::ClassInstance.new(
|
415
|
+
name: ::RBS::TypeName.new(name: :Hash, namespace: ::RBS::Namespace.empty),
|
416
|
+
args: [
|
417
|
+
::RBS::Types::ClassInstance.new(name: ::RBS::TypeName.new(name: :Symbol, namespace: ::RBS::Namespace.empty), args: [], location: nil),
|
418
|
+
base_type
|
419
|
+
],
|
420
|
+
location: nil
|
421
|
+
)
|
422
|
+
optional[name.to_sym] = hash_type
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
{ required: required, optional: optional }
|
427
|
+
end
|
428
|
+
|
429
|
+
# Extract documented parameter types into a hash for lookup.
|
430
|
+
# @parameter definition [Definition] The method definition.
|
431
|
+
# @returns [Hash] Map of parameter name to RBS type.
|
432
|
+
def extract_documented_parameter_types(definition)
|
433
|
+
doc_types = {}
|
434
|
+
documentation = definition.documentation
|
435
|
+
return doc_types unless documentation
|
436
|
+
|
437
|
+
# Extract types from @parameter tags:
|
438
|
+
param_tags = documentation.filter(Decode::Comment::Parameter).to_a
|
439
|
+
param_tags.each do |tag|
|
440
|
+
doc_types[tag.name] = Type.parse(tag.type.strip)
|
441
|
+
end
|
442
|
+
|
443
|
+
# Extract types from @option tags
|
444
|
+
option_tags = documentation.filter(Decode::Comment::Option).to_a
|
445
|
+
option_tags.each do |tag|
|
446
|
+
# Remove leading colon:
|
447
|
+
name = tag.name.sub(/\A:/, "")
|
448
|
+
doc_types[name] = Type.parse(tag.type.strip)
|
449
|
+
end
|
450
|
+
|
451
|
+
doc_types
|
452
|
+
end
|
453
|
+
|
454
|
+
# Make a type optional if it's not already nullable.
|
455
|
+
# @parameter type [RBS::Types::t] The base type.
|
456
|
+
# @returns [RBS::Types::t] The optionally-nullable type.
|
457
|
+
def make_type_optional_if_needed(type)
|
458
|
+
return type if Type.nullable?(type)
|
459
|
+
|
460
|
+
# Create a union with nil to make it optional:
|
461
|
+
::RBS::Types::Union.new(
|
462
|
+
types: [type, ::RBS::Types::Bases::Nil.new(location: nil)],
|
463
|
+
location: nil
|
464
|
+
)
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
# Extract block type from method documentation.
|
129
469
|
def extract_block_type(definition, index)
|
130
470
|
documentation = definition.documentation
|
131
471
|
return nil unless documentation
|
132
472
|
|
133
|
-
# Find
|
473
|
+
# Find `@yields` tags:
|
134
474
|
yields_tag = documentation.filter(Decode::Comment::Yields).first
|
135
475
|
return nil unless yields_tag
|
136
476
|
|
137
|
-
# Extract block parameters from nested
|
477
|
+
# Extract block parameters from nested `@parameter` tags:
|
138
478
|
block_params = yields_tag.filter(Decode::Comment::Parameter).map do |param_tag|
|
139
479
|
name = param_tag.name
|
140
480
|
type_string = param_tag.type.strip
|
141
|
-
type =
|
481
|
+
type = Type.parse(type_string)
|
142
482
|
|
143
483
|
::RBS::Types::Function::Param.new(
|
144
484
|
type: type,
|
@@ -146,16 +486,16 @@ module Decode
|
|
146
486
|
)
|
147
487
|
end
|
148
488
|
|
149
|
-
# Parse the block signature to determine if it's required
|
150
|
-
# Check both the directive name and the block signature
|
489
|
+
# Parse the block signature to determine if it's required.
|
490
|
+
# Check both the directive name and the block signature:
|
151
491
|
block_signature = yields_tag.block
|
152
492
|
directive_name = yields_tag.directive
|
153
493
|
required = !directive_name.include?("?") && !block_signature.include?("?") && !block_signature.include?("optional")
|
154
494
|
|
155
|
-
# Determine block return type (default to void if not specified)
|
495
|
+
# Determine block return type (default to `void` if not specified):
|
156
496
|
block_return_type = ::RBS::Parser.parse_type("void")
|
157
497
|
|
158
|
-
# Create the block function type
|
498
|
+
# Create the block function type:
|
159
499
|
block_function = ::RBS::Types::Function.new(
|
160
500
|
required_positionals: block_params,
|
161
501
|
optional_positionals: [],
|
@@ -167,7 +507,7 @@ module Decode
|
|
167
507
|
return_type: block_return_type
|
168
508
|
)
|
169
509
|
|
170
|
-
# Create and return the block type
|
510
|
+
# Create and return the block type:
|
171
511
|
::RBS::Types::Block.new(
|
172
512
|
type: block_function,
|
173
513
|
required: required,
|
@@ -175,43 +515,29 @@ module Decode
|
|
175
515
|
)
|
176
516
|
end
|
177
517
|
|
178
|
-
# Infer return type based on method patterns and heuristics
|
518
|
+
# Infer return type based on method patterns and heuristics.
|
179
519
|
def infer_return_type(definition)
|
180
520
|
method_name = definition.name
|
181
521
|
method_name_str = method_name.to_s
|
182
522
|
|
183
|
-
# Methods ending with
|
523
|
+
# Methods ending with `?` are typically boolean:
|
184
524
|
if method_name_str.end_with?("?")
|
185
525
|
return ::RBS::Parser.parse_type("bool")
|
186
526
|
end
|
187
527
|
|
188
|
-
# Methods named initialize return void
|
528
|
+
# Methods named `initialize` return `void`:
|
189
529
|
if method_name == :initialize
|
190
530
|
return ::RBS::Parser.parse_type("void")
|
191
531
|
end
|
192
532
|
|
193
|
-
# Methods with names that suggest they return self
|
533
|
+
# Methods with names that suggest they return `self`:
|
194
534
|
if method_name_str.match?(/^(add|append|prepend|push|<<|concat|merge!|sort!|reverse!|clear|delete|remove)/)
|
195
535
|
return ::RBS::Parser.parse_type("self")
|
196
536
|
end
|
197
537
|
|
198
|
-
# Default to untyped
|
538
|
+
# Default to `untyped`:
|
199
539
|
::RBS::Parser.parse_type("untyped")
|
200
540
|
end
|
201
|
-
|
202
|
-
# Parse a type string and convert it to RBS type
|
203
|
-
def parse_type_string(type_string)
|
204
|
-
# This is for backwards compatibility with the old syntax, eventually we will emit warnings for these:
|
205
|
-
type_string = type_string.tr("()", "[]")
|
206
|
-
type_string.gsub!("| Nil", "| nil")
|
207
|
-
type_string.gsub!("Boolean", "bool")
|
208
|
-
|
209
|
-
return ::RBS::Parser.parse_type(type_string)
|
210
|
-
rescue => error
|
211
|
-
Console.warn(self, "Failed to parse type string: #{type_string}", error)
|
212
|
-
return ::RBS::Parser.parse_type("untyped")
|
213
|
-
end
|
214
|
-
|
215
541
|
end
|
216
542
|
end
|
217
543
|
end
|