decode 0.24.2 → 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 +5 -2
- 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 +6 -2
- data/lib/decode/comment/rbs.rb +8 -8
- data/lib/decode/comment/tag.rb +19 -0
- 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 -65
- 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 +1189 -0
- data.tar.gz.sig +0 -0
- metadata +5 -15
- metadata.gz.sig +0 -0
data/lib/decode/rbs/method.rb
CHANGED
@@ -5,19 +5,21 @@
|
|
5
5
|
|
6
6
|
require "rbs"
|
7
7
|
require "console"
|
8
|
-
require "types"
|
9
8
|
require_relative "wrapper"
|
9
|
+
require_relative "type"
|
10
10
|
|
11
11
|
module Decode
|
12
12
|
module RBS
|
13
13
|
# Represents a Ruby method definition wrapper for RBS generation.
|
14
14
|
class Method < Wrapper
|
15
|
-
|
16
15
|
# Initialize a new method wrapper.
|
17
16
|
# @parameter definition [Decode::Definition] The method definition to wrap.
|
18
17
|
def initialize(definition)
|
19
18
|
super
|
20
19
|
@signatures = nil
|
20
|
+
@keyword_arguments = nil
|
21
|
+
@return_type = nil
|
22
|
+
@parameters = nil
|
21
23
|
end
|
22
24
|
|
23
25
|
# Extract method signatures from the method definition.
|
@@ -26,10 +28,28 @@ module Decode
|
|
26
28
|
@signatures ||= extract_signatures
|
27
29
|
end
|
28
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
|
+
|
29
49
|
# Convert the method definition to RBS AST
|
30
50
|
def to_rbs_ast(index = nil)
|
31
51
|
method_name = @definition.name
|
32
|
-
comment =
|
52
|
+
comment = self.comment
|
33
53
|
|
34
54
|
overloads = []
|
35
55
|
if signatures.any?
|
@@ -41,25 +61,38 @@ module Decode
|
|
41
61
|
)
|
42
62
|
end
|
43
63
|
else
|
44
|
-
return_type =
|
45
|
-
parameters = extract_parameters(@definition, index)
|
46
|
-
block_type = extract_block_type(@definition, index)
|
64
|
+
return_type = self.return_type
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
63
96
|
|
64
97
|
overloads << ::RBS::AST::Members::MethodDefinition::Overload.new(
|
65
98
|
method_type: method_type,
|
@@ -70,14 +103,118 @@ module Decode
|
|
70
103
|
kind = @definition.receiver ? :singleton : :instance
|
71
104
|
|
72
105
|
::RBS::AST::Members::MethodDefinition.new(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
81
218
|
)
|
82
219
|
end
|
83
220
|
|
@@ -87,37 +224,54 @@ module Decode
|
|
87
224
|
extract_tags.select(&:method_signature?).map(&:method_signature)
|
88
225
|
end
|
89
226
|
|
90
|
-
# Extract return type from method documentation
|
227
|
+
# Extract return type from method documentation.
|
91
228
|
def extract_return_type(definition, index)
|
92
|
-
# Look for @returns tags in the method's documentation
|
229
|
+
# Look for @returns tags in the method's documentation:
|
93
230
|
documentation = definition.documentation
|
94
231
|
|
95
|
-
# Find @returns
|
96
|
-
|
232
|
+
# Find all @returns tags:
|
233
|
+
returns_tags = documentation&.filter(Decode::Comment::Returns)&.to_a
|
97
234
|
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
102
249
|
else
|
103
|
-
# Infer return type based on method name patterns
|
250
|
+
# Infer return type based on method name patterns:
|
104
251
|
infer_return_type(definition)
|
105
252
|
end
|
106
253
|
end
|
107
254
|
|
108
|
-
# Extract parameter types from method documentation
|
255
|
+
# Extract parameter types from method documentation.
|
109
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:
|
110
263
|
documentation = definition.documentation
|
111
264
|
return [] unless documentation
|
112
265
|
|
113
|
-
# Find @parameter tags
|
266
|
+
# Find @parameter tags (but not @option tags, which are handled separately):
|
114
267
|
param_tags = documentation.filter(Decode::Comment::Parameter).to_a
|
268
|
+
param_tags = param_tags.reject {|tag| tag.is_a?(Decode::Comment::Option)}
|
115
269
|
return [] if param_tags.empty?
|
116
270
|
|
117
271
|
param_tags.map do |tag|
|
118
272
|
name = tag.name
|
119
273
|
type_string = tag.type.strip
|
120
|
-
type =
|
274
|
+
type = Type.parse(type_string)
|
121
275
|
|
122
276
|
::RBS::Types::Function::Param.new(
|
123
277
|
type: type,
|
@@ -126,20 +280,205 @@ module Decode
|
|
126
280
|
end
|
127
281
|
end
|
128
282
|
|
129
|
-
# 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.
|
130
469
|
def extract_block_type(definition, index)
|
131
470
|
documentation = definition.documentation
|
132
471
|
return nil unless documentation
|
133
472
|
|
134
|
-
# Find
|
473
|
+
# Find `@yields` tags:
|
135
474
|
yields_tag = documentation.filter(Decode::Comment::Yields).first
|
136
475
|
return nil unless yields_tag
|
137
476
|
|
138
|
-
# Extract block parameters from nested
|
477
|
+
# Extract block parameters from nested `@parameter` tags:
|
139
478
|
block_params = yields_tag.filter(Decode::Comment::Parameter).map do |param_tag|
|
140
479
|
name = param_tag.name
|
141
480
|
type_string = param_tag.type.strip
|
142
|
-
type =
|
481
|
+
type = Type.parse(type_string)
|
143
482
|
|
144
483
|
::RBS::Types::Function::Param.new(
|
145
484
|
type: type,
|
@@ -147,16 +486,16 @@ module Decode
|
|
147
486
|
)
|
148
487
|
end
|
149
488
|
|
150
|
-
# Parse the block signature to determine if it's required
|
151
|
-
# 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:
|
152
491
|
block_signature = yields_tag.block
|
153
492
|
directive_name = yields_tag.directive
|
154
493
|
required = !directive_name.include?("?") && !block_signature.include?("?") && !block_signature.include?("optional")
|
155
494
|
|
156
|
-
# Determine block return type (default to void if not specified)
|
495
|
+
# Determine block return type (default to `void` if not specified):
|
157
496
|
block_return_type = ::RBS::Parser.parse_type("void")
|
158
497
|
|
159
|
-
# Create the block function type
|
498
|
+
# Create the block function type:
|
160
499
|
block_function = ::RBS::Types::Function.new(
|
161
500
|
required_positionals: block_params,
|
162
501
|
optional_positionals: [],
|
@@ -168,7 +507,7 @@ module Decode
|
|
168
507
|
return_type: block_return_type
|
169
508
|
)
|
170
509
|
|
171
|
-
# Create and return the block type
|
510
|
+
# Create and return the block type:
|
172
511
|
::RBS::Types::Block.new(
|
173
512
|
type: block_function,
|
174
513
|
required: required,
|
@@ -176,39 +515,29 @@ module Decode
|
|
176
515
|
)
|
177
516
|
end
|
178
517
|
|
179
|
-
# Infer return type based on method patterns and heuristics
|
518
|
+
# Infer return type based on method patterns and heuristics.
|
180
519
|
def infer_return_type(definition)
|
181
520
|
method_name = definition.name
|
182
521
|
method_name_str = method_name.to_s
|
183
522
|
|
184
|
-
# Methods ending with
|
523
|
+
# Methods ending with `?` are typically boolean:
|
185
524
|
if method_name_str.end_with?("?")
|
186
525
|
return ::RBS::Parser.parse_type("bool")
|
187
526
|
end
|
188
527
|
|
189
|
-
# Methods named initialize return void
|
528
|
+
# Methods named `initialize` return `void`:
|
190
529
|
if method_name == :initialize
|
191
530
|
return ::RBS::Parser.parse_type("void")
|
192
531
|
end
|
193
532
|
|
194
|
-
# Methods with names that suggest they return self
|
533
|
+
# Methods with names that suggest they return `self`:
|
195
534
|
if method_name_str.match?(/^(add|append|prepend|push|<<|concat|merge!|sort!|reverse!|clear|delete|remove)/)
|
196
535
|
return ::RBS::Parser.parse_type("self")
|
197
536
|
end
|
198
537
|
|
199
|
-
# Default to untyped
|
538
|
+
# Default to `untyped`:
|
200
539
|
::RBS::Parser.parse_type("untyped")
|
201
540
|
end
|
202
|
-
|
203
|
-
# Parse a type string and convert it to RBS type
|
204
|
-
def parse_type_string(type_string)
|
205
|
-
type = Types.parse(type_string)
|
206
|
-
return ::RBS::Parser.parse_type(type.to_rbs)
|
207
|
-
rescue => error
|
208
|
-
Console.warn(self, "Failed to parse type string: #{type_string}", error)
|
209
|
-
return ::RBS::Parser.parse_type("untyped")
|
210
|
-
end
|
211
|
-
|
212
541
|
end
|
213
542
|
end
|
214
543
|
end
|
data/lib/decode/rbs/module.rb
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
require "rbs"
|
7
7
|
require_relative "wrapper"
|
8
|
+
require_relative "method"
|
9
|
+
require_relative "type"
|
8
10
|
|
9
11
|
module Decode
|
10
12
|
module RBS
|
@@ -18,18 +20,29 @@ module Decode
|
|
18
20
|
end
|
19
21
|
|
20
22
|
# Convert the module definition to RBS AST
|
21
|
-
|
23
|
+
# @parameter method_definitions [Array(Method)] The method definitions to convert.
|
24
|
+
# @parameter constant_definitions [Array(Constant)] The constant definitions to convert.
|
25
|
+
# @parameter attribute_definitions [Array(Attribute)] The attribute definitions to convert.
|
26
|
+
# @parameter index [Index?] The index for resolving references.
|
27
|
+
# @returns [RBS::AST::Declarations::Module] The RBS AST for the module.
|
28
|
+
def to_rbs_ast(method_definitions = [], constant_definitions = [], attribute_definitions = [], index = nil)
|
22
29
|
name = simple_name_to_rbs(@definition.name)
|
23
|
-
comment =
|
30
|
+
comment = self.comment
|
24
31
|
|
25
32
|
# Build method definitions
|
26
33
|
methods = method_definitions.map{|method_def| Method.new(method_def).to_rbs_ast(index)}.compact
|
27
34
|
|
35
|
+
# Build constant definitions:
|
36
|
+
constants = constant_definitions.map{|const_def| build_constant_rbs(const_def)}.compact
|
37
|
+
|
38
|
+
# Build attribute definitions and infer instance variable types:
|
39
|
+
attributes, instance_variables = build_attributes_rbs(attribute_definitions)
|
40
|
+
|
28
41
|
::RBS::AST::Declarations::Module.new(
|
29
42
|
name: name,
|
30
43
|
type_params: [],
|
31
44
|
self_types: [],
|
32
|
-
members: methods,
|
45
|
+
members: constants + attributes + instance_variables + methods,
|
33
46
|
annotations: [],
|
34
47
|
location: nil,
|
35
48
|
comment: comment
|
@@ -38,11 +51,74 @@ module Decode
|
|
38
51
|
|
39
52
|
private
|
40
53
|
|
41
|
-
#
|
54
|
+
# Build a constant RBS declaration.
|
55
|
+
def build_constant_rbs(constant_definition)
|
56
|
+
# Look for @constant tags in the constant's documentation:
|
57
|
+
documentation = constant_definition.documentation
|
58
|
+
constant_tags = documentation&.filter(Decode::Comment::Constant)&.to_a
|
59
|
+
|
60
|
+
if constant_tags&.any?
|
61
|
+
type_string = constant_tags.first.type.strip
|
62
|
+
type = ::Decode::RBS::Type.parse(type_string)
|
63
|
+
|
64
|
+
::RBS::AST::Declarations::Constant.new(
|
65
|
+
name: constant_definition.name.to_sym,
|
66
|
+
type: type,
|
67
|
+
location: nil,
|
68
|
+
comment: nil
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convert a simple name to RBS TypeName (not qualified).
|
42
74
|
def simple_name_to_rbs(name)
|
43
75
|
::RBS::TypeName.new(name: name.to_sym, namespace: ::RBS::Namespace.empty)
|
44
76
|
end
|
45
77
|
|
78
|
+
# Build attribute RBS declarations and infer instance variable types.
|
79
|
+
# @parameter attribute_definitions [Array] Array of Attribute definition objects
|
80
|
+
# @returns [Array] A tuple of [attribute_declarations, instance_variable_declarations]
|
81
|
+
def build_attributes_rbs(attribute_definitions)
|
82
|
+
attributes = []
|
83
|
+
instance_variables = []
|
84
|
+
|
85
|
+
# Create a mapping from attribute names to their types:
|
86
|
+
attribute_types = {}
|
87
|
+
|
88
|
+
attribute_definitions.each do |attribute_definition|
|
89
|
+
# Extract @attribute type annotation from documentation:
|
90
|
+
documentation = attribute_definition.documentation
|
91
|
+
attribute_tags = documentation&.filter(Decode::Comment::Attribute)&.to_a
|
92
|
+
|
93
|
+
if attribute_tags&.any?
|
94
|
+
type_string = attribute_tags.first.type.strip
|
95
|
+
type = ::Decode::RBS::Type.parse(type_string)
|
96
|
+
|
97
|
+
attribute_types[attribute_definition.name] = type
|
98
|
+
|
99
|
+
# Generate attr_reader RBS declaration:
|
100
|
+
attributes << ::RBS::AST::Members::AttrReader.new(
|
101
|
+
name: attribute_definition.name.to_sym,
|
102
|
+
type: type,
|
103
|
+
ivar_name: :"@#{attribute_definition.name}",
|
104
|
+
kind: :instance,
|
105
|
+
annotations: [],
|
106
|
+
location: nil,
|
107
|
+
comment: nil
|
108
|
+
)
|
109
|
+
|
110
|
+
# Generate instance variable declaration:
|
111
|
+
instance_variables << ::RBS::AST::Members::InstanceVariable.new(
|
112
|
+
name: :"@#{attribute_definition.name}",
|
113
|
+
type: type,
|
114
|
+
location: nil,
|
115
|
+
comment: nil
|
116
|
+
)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
[attributes, instance_variables]
|
121
|
+
end
|
46
122
|
end
|
47
123
|
end
|
48
|
-
end
|
124
|
+
end
|