docscribe 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +76 -193
- data/exe/docscribe-client +26 -7
- data/lib/docscribe/cli/config_builder.rb +37 -2
- data/lib/docscribe/cli/coverage.rb +5 -5
- data/lib/docscribe/cli/formatters/json.rb +74 -29
- data/lib/docscribe/cli/formatters/sarif.rb +20 -3
- data/lib/docscribe/cli/options.rb +17 -2
- data/lib/docscribe/cli/rbs_gen.rb +4 -4
- data/lib/docscribe/cli/run.rb +107 -24
- data/lib/docscribe/cli/update_types.rb +61 -17
- data/lib/docscribe/cli.rb +19 -13
- data/lib/docscribe/config/defaults.rb +1 -0
- data/lib/docscribe/config/rbs.rb +22 -1
- data/lib/docscribe/config/template.rb +3 -0
- data/lib/docscribe/config/validation.rb +19 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/behavior.rb +13 -13
- data/lib/docscribe/infer/params.rb +2 -2
- data/lib/docscribe/infer/raises.rb +5 -6
- data/lib/docscribe/infer/returns.rb +1612 -151
- data/lib/docscribe/infer.rb +7 -7
- data/lib/docscribe/inline_rewriter/doc_builder.rb +485 -102
- data/lib/docscribe/inline_rewriter.rb +263 -97
- data/lib/docscribe/plugin/registry.rb +1 -0
- data/lib/docscribe/server/base.rb +255 -0
- data/lib/docscribe/server/client.rb +95 -0
- data/lib/docscribe/server/daemon.rb +678 -0
- data/lib/docscribe/server/protocol.rb +50 -0
- data/lib/docscribe/server.rb +4 -835
- data/lib/docscribe/types/primitive.rb +160 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +33 -1
- data/lib/docscribe/types/yard/formatter.rb +35 -6
- data/lib/docscribe/types/yard/parser.rb +25 -20
- data/lib/docscribe/types/yard/validator.rb +131 -0
- data/lib/docscribe/validator/generic_compatibility.rb +698 -0
- data/lib/docscribe/validator/type_mismatch_validator.rb +287 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +12 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../types/primitive'
|
|
4
|
+
|
|
3
5
|
module Docscribe
|
|
4
6
|
module Infer
|
|
5
7
|
# Return type inference and rescue-conditional return extraction.
|
|
@@ -15,7 +17,7 @@ module Docscribe
|
|
|
15
17
|
# @param [String?] method_source full method definition source
|
|
16
18
|
# @raise [Parser::SyntaxError]
|
|
17
19
|
# @return [String]
|
|
18
|
-
# @return [
|
|
20
|
+
# @return [String] if Parser::SyntaxError
|
|
19
21
|
def infer_return_type(method_source)
|
|
20
22
|
return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty?
|
|
21
23
|
|
|
@@ -65,46 +67,58 @@ module Docscribe
|
|
|
65
67
|
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
66
68
|
# @param [String] fallback_type type used when inference is uncertain
|
|
67
69
|
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
68
|
-
# @param [
|
|
69
|
-
# @param [Hash
|
|
70
|
-
# @
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, # rubocop:disable Metrics/ParameterLists
|
|
74
|
-
param_types: nil, container: nil, signature_provider: nil)
|
|
70
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider core RBS type lookup provider
|
|
71
|
+
# @param [Hash] opts
|
|
72
|
+
# @return [Hash<Symbol, Object>]
|
|
73
|
+
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
|
|
74
|
+
core_rbs_provider: nil, **opts)
|
|
75
75
|
body = extract_def_body(node)
|
|
76
76
|
spec = { normal: FALLBACK_TYPE, rescues: [] } #: Hash[Symbol, untyped]
|
|
77
77
|
return spec unless body
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
82
|
-
container: container, signature_provider: signature_provider)
|
|
79
|
+
populate_spec_with_types(spec, body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
80
|
+
core_rbs_provider: core_rbs_provider, **opts)
|
|
83
81
|
spec
|
|
84
82
|
end
|
|
85
83
|
|
|
84
|
+
# @note module_function: defines #populate_spec_with_types (visibility: private)
|
|
85
|
+
# @param [Hash<Symbol, Object>] spec
|
|
86
|
+
# @param [Parser::AST::Node] body
|
|
87
|
+
# @param [String?] fallback_type
|
|
88
|
+
# @param [Boolean] nil_as_optional
|
|
89
|
+
# @param [Hash] opts
|
|
90
|
+
# @return [void]
|
|
91
|
+
def populate_spec_with_types(spec, body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, **opts)
|
|
92
|
+
core_rbs_provider = opts[:core_rbs_provider]
|
|
93
|
+
types = build_local_variable_types(body, core_rbs_provider: core_rbs_provider,
|
|
94
|
+
param_types: opts[:param_types])
|
|
95
|
+
populate_returns_spec(spec, body, types, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
96
|
+
core_rbs_provider: core_rbs_provider, param_types: opts[:param_types],
|
|
97
|
+
container: opts[:container], signature_provider: opts[:signature_provider])
|
|
98
|
+
end
|
|
99
|
+
|
|
86
100
|
# Extract the body child node from a `:def` or `:defs` AST node.
|
|
87
101
|
#
|
|
88
102
|
# @note module_function: defines #extract_def_body (visibility: private)
|
|
89
103
|
# @param [Parser::AST::Node] node a `:def` or `:defs` AST node
|
|
90
104
|
# @return [Parser::AST::Node, nil]
|
|
91
105
|
def extract_def_body(node)
|
|
92
|
-
case node
|
|
93
|
-
when :def then node
|
|
94
|
-
when :defs then node
|
|
106
|
+
case node&.type
|
|
107
|
+
when :def then node&.children&.[](2)
|
|
108
|
+
when :defs then node&.children&.[](3)
|
|
95
109
|
end
|
|
96
110
|
end
|
|
97
111
|
|
|
98
112
|
# Populate the spec hash with normal and/or rescue return types from the body.
|
|
99
113
|
#
|
|
100
114
|
# @note module_function: defines #populate_returns_spec (visibility: private)
|
|
101
|
-
# @param [Object] spec the return spec hash to populate
|
|
115
|
+
# @param [Hash<Symbol, Object>] spec the return spec hash to populate
|
|
102
116
|
# @param [Parser::AST::Node] body the method body AST node
|
|
103
|
-
# @param [Hash<
|
|
104
|
-
# @param [
|
|
105
|
-
# @return [
|
|
117
|
+
# @param [Hash<String, String>?] local_var_types inferred local variable type map
|
|
118
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
119
|
+
# @return [void]
|
|
106
120
|
def populate_returns_spec(spec, body, local_var_types, **opts)
|
|
107
|
-
if body
|
|
121
|
+
if body&.type == :rescue
|
|
108
122
|
process_rescue_body(spec, body, **opts)
|
|
109
123
|
else
|
|
110
124
|
spec[:normal] = infer_normal_return_type(body, **opts, local_var_types: local_var_types)
|
|
@@ -115,7 +129,7 @@ module Docscribe
|
|
|
115
129
|
#
|
|
116
130
|
# @note module_function: defines #infer_normal_return_type (visibility: private)
|
|
117
131
|
# @param [Parser::AST::Node] body the method body AST node
|
|
118
|
-
# @param [
|
|
132
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
119
133
|
# @return [String]
|
|
120
134
|
def infer_normal_return_type(body, **opts)
|
|
121
135
|
run_last_expr_type(body, **opts) || FALLBACK_TYPE
|
|
@@ -124,10 +138,10 @@ module Docscribe
|
|
|
124
138
|
# Process a :rescue body node and populate spec with normal + rescue return types.
|
|
125
139
|
#
|
|
126
140
|
# @note module_function: defines #process_rescue_body (visibility: private)
|
|
127
|
-
# @param [Object] spec the return spec hash to populate
|
|
141
|
+
# @param [Hash<Symbol, Object>] spec the return spec hash to populate
|
|
128
142
|
# @param [Parser::AST::Node] body the :rescue AST node
|
|
129
|
-
# @param [
|
|
130
|
-
# @return [
|
|
143
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
144
|
+
# @return [void]
|
|
131
145
|
def process_rescue_body(spec, body, **opts)
|
|
132
146
|
main_body = body.children[0]
|
|
133
147
|
local_var_types = build_local_variable_types(body,
|
|
@@ -141,26 +155,46 @@ module Docscribe
|
|
|
141
155
|
# Extract return types from each :resbody child and append to spec[:rescues].
|
|
142
156
|
#
|
|
143
157
|
# @note module_function: defines #process_rescue_branches (visibility: private)
|
|
144
|
-
# @param [Object] spec the return spec hash to populate
|
|
158
|
+
# @param [Hash<Symbol, Object>] spec the return spec hash to populate
|
|
145
159
|
# @param [Parser::AST::Node] body the :rescue AST node
|
|
146
|
-
# @param [
|
|
147
|
-
# @return [
|
|
160
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
161
|
+
# @return [void]
|
|
148
162
|
def process_rescue_branches(spec, body, **opts)
|
|
149
163
|
body.children.each do |ch|
|
|
150
164
|
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
151
165
|
|
|
152
166
|
exc_list, _asgn, rescue_body = *ch
|
|
153
167
|
exc_names = Raises.exception_names_from_rescue_list(exc_list)
|
|
154
|
-
rtype =
|
|
168
|
+
rtype = rescue_branch_type(rescue_body, **opts) || opts[:fallback_type]
|
|
155
169
|
spec[:rescues] << [exc_names, rtype]
|
|
156
170
|
end
|
|
157
171
|
end
|
|
158
172
|
|
|
173
|
+
# Infer a rescue branch type, resolving data constants precisely.
|
|
174
|
+
#
|
|
175
|
+
# Rescue branches form the documented contract (`@return [X] if Error`),
|
|
176
|
+
# so a bare data constant (e.g., FALLBACK_TYPE, whose runtime value is
|
|
177
|
+
# the String 'Object') infers as its value type instead of the bare
|
|
178
|
+
# fallback. Other nodes use the standard path unchanged.
|
|
179
|
+
#
|
|
180
|
+
# @note module_function: defines #rescue_branch_type (visibility: private)
|
|
181
|
+
# @param [Parser::AST::Node, nil] rescue_body rescue branch body node
|
|
182
|
+
# @param [Hash] opts additional keyword options forwarded to inference
|
|
183
|
+
# @return [String, nil] inferred branch type or nil
|
|
184
|
+
def rescue_branch_type(rescue_body, **opts)
|
|
185
|
+
if rescue_body.is_a?(Parser::AST::Node) && rescue_body.type == :const
|
|
186
|
+
resolve_const_value_type(rescue_body, opts[:container]) ||
|
|
187
|
+
run_last_expr_type(rescue_body, **opts)
|
|
188
|
+
else
|
|
189
|
+
run_last_expr_type(rescue_body, **opts)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
159
193
|
# Build a map of local/global/ivar/constant assignments to inferred types.
|
|
160
194
|
#
|
|
161
195
|
# @note module_function: defines #build_local_variable_types (visibility: private)
|
|
162
196
|
# @param [Parser::AST::Node] node AST node to walk
|
|
163
|
-
# @param [
|
|
197
|
+
# @param [Hash] opts additional keyword options forwarded to inference
|
|
164
198
|
# @return [Hash<String, String>, nil]
|
|
165
199
|
def build_local_variable_types(node, **opts)
|
|
166
200
|
types = {} #: Hash[String, String]
|
|
@@ -179,36 +213,56 @@ module Docscribe
|
|
|
179
213
|
# @note module_function: defines #collect_assignment_type (visibility: private)
|
|
180
214
|
# @param [Parser::AST::Node] node an assignment AST node
|
|
181
215
|
# @param [Hash<String, String>] types the accumulated local variable type map
|
|
182
|
-
# @param [
|
|
216
|
+
# @param [Hash] opts additional keyword options forwarded to inference
|
|
183
217
|
# @return [void]
|
|
184
218
|
def collect_assignment_type(node, types, **opts)
|
|
185
219
|
name, value = assignment_name_and_value(node)
|
|
186
220
|
return unless name && value
|
|
187
221
|
|
|
188
|
-
inferred = if
|
|
189
|
-
|
|
190
|
-
nil_as_optional: false, local_var_types: types)
|
|
222
|
+
inferred = if %i[op_asgn or_asgn].include?(node.type)
|
|
223
|
+
assignment_op_asgn_type(node, types, **opts)
|
|
191
224
|
else
|
|
192
|
-
|
|
225
|
+
assignment_inferred_type(value, types, **opts)
|
|
193
226
|
end
|
|
194
227
|
types[name] = inferred if inferred && inferred != FALLBACK_TYPE
|
|
195
228
|
end
|
|
196
229
|
|
|
230
|
+
# @note module_function: defines #assignment_inferred_type (visibility: private)
|
|
231
|
+
# @param [Parser::AST::Node] value
|
|
232
|
+
# @param [Hash<String, String>] types
|
|
233
|
+
# @param [Hash] opts
|
|
234
|
+
# @return [String, nil]
|
|
235
|
+
def assignment_inferred_type(value, types, **opts)
|
|
236
|
+
run_last_expr_type(value, fallback_type: FALLBACK_TYPE, nil_as_optional: false, local_var_types: types,
|
|
237
|
+
core_rbs_provider: opts[:core_rbs_provider], param_types: opts[:param_types],
|
|
238
|
+
signature_provider: opts[:signature_provider], container: opts[:container])
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# @note module_function: defines #assignment_op_asgn_type (visibility: private)
|
|
242
|
+
# @param [Parser::AST::Node] node
|
|
243
|
+
# @param [Hash<String, String>] types
|
|
244
|
+
# @param [Hash] opts
|
|
245
|
+
# @return [String, nil]
|
|
246
|
+
def assignment_op_asgn_type(node, types, **opts)
|
|
247
|
+
run_last_expr_type(node, fallback_type: FALLBACK_TYPE, nil_as_optional: false, local_var_types: types,
|
|
248
|
+
core_rbs_provider: opts[:core_rbs_provider], param_types: opts[:param_types],
|
|
249
|
+
signature_provider: opts[:signature_provider], container: opts[:container])
|
|
250
|
+
end
|
|
251
|
+
|
|
197
252
|
# Extract the variable name and value expression from an assignment node.
|
|
198
253
|
#
|
|
199
254
|
# @note module_function: defines #assignment_name_and_value (visibility: private)
|
|
200
|
-
# @param [Parser::AST::Node] node an assignment AST node (:lvasgn, :gvasgn, :ivasgn, :casgn, :op_asgn)
|
|
255
|
+
# @param [Parser::AST::Node] node an assignment AST node (:lvasgn, :gvasgn, :ivasgn, :casgn, :op_asgn, :or_asgn)
|
|
201
256
|
# @return [(String, nil, Parser::AST::Node, nil)]
|
|
202
257
|
def assignment_name_and_value(node)
|
|
258
|
+
return [nil, nil] unless node.is_a?(Parser::AST::Node)
|
|
259
|
+
|
|
203
260
|
case node.type
|
|
204
|
-
when :lvasgn, :gvasgn, :ivasgn, :cvasgn
|
|
205
|
-
|
|
206
|
-
when :
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
compound_name_and_value(node)
|
|
210
|
-
else
|
|
211
|
-
[nil, nil]
|
|
261
|
+
when :lvasgn, :gvasgn, :ivasgn, :cvasgn then [node.children[0].to_s, node.children[1]]
|
|
262
|
+
when :casgn then constant_name_and_value(node)
|
|
263
|
+
when :op_asgn then compound_name_and_value(node)
|
|
264
|
+
when :or_asgn then or_asgn_name_and_value(node)
|
|
265
|
+
else [nil, nil]
|
|
212
266
|
end
|
|
213
267
|
end
|
|
214
268
|
|
|
@@ -230,11 +284,26 @@ module Docscribe
|
|
|
230
284
|
[node.children[0].children.first.to_s, node.children[2]]
|
|
231
285
|
end
|
|
232
286
|
|
|
287
|
+
# Extract the name and value from an `:or_asgn` (`||=`) node.
|
|
288
|
+
#
|
|
289
|
+
# Unlike `:op_asgn` (three children: target, operator, value), `:or_asgn`
|
|
290
|
+
# carries only target and value.
|
|
291
|
+
#
|
|
292
|
+
# @note module_function: defines #or_asgn_name_and_value (visibility: private)
|
|
293
|
+
# @param [Parser::AST::Node] node the `:or_asgn` AST node
|
|
294
|
+
# @return [(String, nil, Parser::AST::Node, nil)]
|
|
295
|
+
def or_asgn_name_and_value(node)
|
|
296
|
+
target = node.children[0]
|
|
297
|
+
return [nil, nil] unless target.is_a?(Parser::AST::Node)
|
|
298
|
+
|
|
299
|
+
[target.children.first.to_s, node.children[1]]
|
|
300
|
+
end
|
|
301
|
+
|
|
233
302
|
# Handle `:lvar` node for last_expr_type — look up the variable in local_var_types.
|
|
234
303
|
#
|
|
235
304
|
# @note module_function: defines #handle_lvar_node (visibility: private)
|
|
236
305
|
# @param [Parser::AST::Node] node the `:lvar` AST node
|
|
237
|
-
# @param [
|
|
306
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
238
307
|
# @return [String, nil]
|
|
239
308
|
def handle_lvar_node(node, **opts)
|
|
240
309
|
name = node.children[0].to_s
|
|
@@ -245,7 +314,7 @@ module Docscribe
|
|
|
245
314
|
#
|
|
246
315
|
# @note module_function: defines #handle_ivar_node (visibility: private)
|
|
247
316
|
# @param [Parser::AST::Node] node the `:ivar` AST node
|
|
248
|
-
# @param [
|
|
317
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
249
318
|
# @return [String, nil]
|
|
250
319
|
def handle_ivar_node(node, **opts)
|
|
251
320
|
name = node.children[0].to_s
|
|
@@ -256,7 +325,7 @@ module Docscribe
|
|
|
256
325
|
#
|
|
257
326
|
# @note module_function: defines #handle_gvar_node (visibility: private)
|
|
258
327
|
# @param [Parser::AST::Node] node the `:gvar` AST node
|
|
259
|
-
# @param [
|
|
328
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
260
329
|
# @return [String, nil]
|
|
261
330
|
def handle_gvar_node(node, **opts)
|
|
262
331
|
name = node.children[0].to_s
|
|
@@ -267,7 +336,7 @@ module Docscribe
|
|
|
267
336
|
#
|
|
268
337
|
# @note module_function: defines #handle_cvar_node (visibility: private)
|
|
269
338
|
# @param [Parser::AST::Node] node the `:cvar` AST node
|
|
270
|
-
# @param [
|
|
339
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
271
340
|
# @return [String, nil]
|
|
272
341
|
def handle_cvar_node(node, **opts)
|
|
273
342
|
name = node.children[0].to_s
|
|
@@ -278,7 +347,7 @@ module Docscribe
|
|
|
278
347
|
#
|
|
279
348
|
# @note module_function: defines #handle_lvasgn_node (visibility: private)
|
|
280
349
|
# @param [Parser::AST::Node] node the `:lvasgn` AST node
|
|
281
|
-
# @param [
|
|
350
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
282
351
|
# @return [String, nil]
|
|
283
352
|
def handle_lvasgn_node(node, **opts)
|
|
284
353
|
name = node.children[0].to_s
|
|
@@ -291,7 +360,7 @@ module Docscribe
|
|
|
291
360
|
#
|
|
292
361
|
# @note module_function: defines #handle_ivasgn_node (visibility: private)
|
|
293
362
|
# @param [Parser::AST::Node] node the `:ivasgn` AST node
|
|
294
|
-
# @param [
|
|
363
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
295
364
|
# @return [String, nil]
|
|
296
365
|
def handle_ivasgn_node(node, **opts)
|
|
297
366
|
name = node.children[0].to_s
|
|
@@ -304,7 +373,7 @@ module Docscribe
|
|
|
304
373
|
#
|
|
305
374
|
# @note module_function: defines #handle_gvasgn_node (visibility: private)
|
|
306
375
|
# @param [Parser::AST::Node] node the `:gvasgn` AST node
|
|
307
|
-
# @param [
|
|
376
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
308
377
|
# @return [String, nil]
|
|
309
378
|
def handle_gvasgn_node(node, **opts)
|
|
310
379
|
name = node.children[0].to_s
|
|
@@ -317,7 +386,7 @@ module Docscribe
|
|
|
317
386
|
#
|
|
318
387
|
# @note module_function: defines #handle_cvasgn_node (visibility: private)
|
|
319
388
|
# @param [Parser::AST::Node] node the `:cvasgn` AST node
|
|
320
|
-
# @param [
|
|
389
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
321
390
|
# @return [String, nil]
|
|
322
391
|
def handle_cvasgn_node(node, **opts)
|
|
323
392
|
name = node.children[0].to_s
|
|
@@ -328,31 +397,142 @@ module Docscribe
|
|
|
328
397
|
|
|
329
398
|
# Handle `:op_asgn` node (compound assignment: `x += 1`, `@var -= 2`, etc.).
|
|
330
399
|
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
400
|
+
# RBS -> Infer: try RBS for meth on receiver type, else unify left/right
|
|
401
|
+
# keeping String? via nil_as_optional:true. No hardcoded operator list.
|
|
333
402
|
#
|
|
334
403
|
# @note module_function: defines #handle_op_asgn_node (visibility: private)
|
|
335
404
|
# @param [Parser::AST::Node] node the `:op_asgn` AST node
|
|
336
|
-
# @param [
|
|
405
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
337
406
|
# @return [String, nil]
|
|
338
407
|
def handle_op_asgn_node(node, **opts)
|
|
339
408
|
meth = node.children[1]
|
|
340
|
-
|
|
341
|
-
|
|
409
|
+
lhs = node.children[0]
|
|
410
|
+
rhs = node.children[2]
|
|
411
|
+
left = op_asgn_left_type(lhs, **opts)
|
|
412
|
+
right = op_asgn_right_type(rhs, **opts)
|
|
413
|
+
rbs = op_asgn_rbs_type(lhs, left, meth, **opts)
|
|
414
|
+
return rbs if rbs
|
|
415
|
+
|
|
416
|
+
op_asgn_fallback_type(left, right, meth, **opts)
|
|
417
|
+
end
|
|
342
418
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
419
|
+
# @note module_function: defines #op_asgn_left_type (visibility: private)
|
|
420
|
+
# @param [Parser::AST::Node, nil] lhs the lhs target node
|
|
421
|
+
# @param [Hash] opts
|
|
422
|
+
# @return [String, nil]
|
|
423
|
+
def op_asgn_left_type(lhs, **opts)
|
|
424
|
+
name = op_asgn_var_name(lhs)
|
|
425
|
+
if name
|
|
426
|
+
found = op_asgn_lookup_type(lhs, name, **opts)
|
|
427
|
+
return found if found
|
|
428
|
+
end
|
|
429
|
+
run_last_expr_type(lhs, **op_asgn_expr_opts(**opts))
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# @note module_function: defines #op_asgn_var_name (visibility: private)
|
|
433
|
+
# @param [Parser::AST::Node, nil] lhs
|
|
434
|
+
# @return [String, nil]
|
|
435
|
+
def op_asgn_var_name(lhs)
|
|
436
|
+
return nil unless lhs.is_a?(Parser::AST::Node)
|
|
437
|
+
|
|
438
|
+
case lhs&.type
|
|
439
|
+
when :lvasgn, :ivasgn, :gvasgn, :cvasgn then lhs&.children&.[](0).to_s
|
|
440
|
+
when :casgn then lhs&.children&.[](1).to_s
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# @note module_function: defines #op_asgn_lookup_type (visibility: private)
|
|
445
|
+
# @param [Parser::AST::Node] lhs
|
|
446
|
+
# @param [String] name
|
|
447
|
+
# @param [Hash] opts
|
|
448
|
+
# @return [String, nil]
|
|
449
|
+
def op_asgn_lookup_type(lhs, name, **opts)
|
|
450
|
+
return nil unless lhs.is_a?(Parser::AST::Node)
|
|
451
|
+
|
|
452
|
+
case lhs&.type
|
|
453
|
+
when :lvasgn
|
|
454
|
+
lookup_lvar_type(name, opts[:local_var_types], opts[:param_types])
|
|
455
|
+
when :ivasgn, :gvasgn, :cvasgn, :casgn
|
|
456
|
+
opts[:local_var_types]&.fetch(name, nil)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# @note module_function: defines #op_asgn_right_type (visibility: private)
|
|
461
|
+
# @param [Parser::AST::Node, nil] rhs
|
|
462
|
+
# @param [Hash] opts
|
|
463
|
+
# @return [String, nil]
|
|
464
|
+
def op_asgn_right_type(rhs, **opts)
|
|
465
|
+
return nil unless rhs
|
|
466
|
+
|
|
467
|
+
run_last_expr_type(rhs, **op_asgn_expr_opts(**opts))
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# @note module_function: defines #op_asgn_expr_opts (visibility: private)
|
|
471
|
+
# @param [Hash] opts
|
|
472
|
+
# @return [Hash<Symbol, Object>]
|
|
473
|
+
def op_asgn_expr_opts(**opts)
|
|
474
|
+
{
|
|
475
|
+
fallback_type: opts[:fallback_type] || FALLBACK_TYPE,
|
|
476
|
+
nil_as_optional: true,
|
|
477
|
+
local_var_types: opts[:local_var_types],
|
|
478
|
+
param_types: opts[:param_types],
|
|
479
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
480
|
+
signature_provider: opts[:signature_provider],
|
|
481
|
+
container: opts[:container]
|
|
482
|
+
}
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# @note module_function: defines #op_asgn_rbs_type (visibility: private)
|
|
486
|
+
# @param [Parser::AST::Node, nil] lhs
|
|
487
|
+
# @param [String, nil] left
|
|
488
|
+
# @param [Symbol] meth
|
|
489
|
+
# @param [Hash] opts
|
|
490
|
+
# @return [String, nil]
|
|
491
|
+
def op_asgn_rbs_type(lhs, left, meth, **opts)
|
|
492
|
+
recv = cleaned_recv_type(left) ||
|
|
493
|
+
receiver_rbs_type_name(lhs, opts[:core_rbs_provider],
|
|
494
|
+
opts[:local_var_types], opts[:param_types])
|
|
495
|
+
return nil unless recv && meth
|
|
496
|
+
|
|
497
|
+
resolve_op_asgn_rbs(recv, meth, **opts)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# @note module_function: defines #resolve_op_asgn_rbs (visibility: private)
|
|
501
|
+
# @param [String] recv_type
|
|
502
|
+
# @param [Symbol] meth
|
|
503
|
+
# @param [Hash] opts
|
|
504
|
+
# @return [String, nil]
|
|
505
|
+
def resolve_op_asgn_rbs(recv_type, meth, **opts)
|
|
506
|
+
if opts[:core_rbs_provider]
|
|
507
|
+
rbs = resolve_rbs_return_type(recv_type, meth, opts[:core_rbs_provider])
|
|
508
|
+
return substitute_rbs_type(rbs, recv_type) unless rbs == FALLBACK_TYPE
|
|
509
|
+
end
|
|
510
|
+
if opts[:signature_provider]
|
|
511
|
+
sig = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)
|
|
512
|
+
return substitute_rbs_type(sig.return_type, recv_type) if sig
|
|
513
|
+
end
|
|
514
|
+
nil
|
|
515
|
+
end
|
|
346
516
|
|
|
347
|
-
|
|
348
|
-
|
|
517
|
+
# @note module_function: defines #op_asgn_fallback_type (visibility: private)
|
|
518
|
+
# @param [String, nil] left
|
|
519
|
+
# @param [String, nil] right
|
|
520
|
+
# @param [Symbol] meth
|
|
521
|
+
# @param [Hash] opts
|
|
522
|
+
# @return [String, nil]
|
|
523
|
+
def op_asgn_fallback_type(left, right, meth, **opts)
|
|
524
|
+
fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s
|
|
525
|
+
return synthesize_shovel_type(left, right, fallback: fallback) if shovel_method?(left, meth, opts[:core_rbs_provider])
|
|
526
|
+
|
|
527
|
+
fallback_concrete_type(left, right, fallback) ||
|
|
528
|
+
unify_types(left, right, fallback_type: fallback, nil_as_optional: true)
|
|
349
529
|
end
|
|
350
530
|
|
|
351
531
|
# Handle `:begin` node for last_expr_type.
|
|
352
532
|
#
|
|
353
533
|
# @note module_function: defines #handle_begin_node (visibility: private)
|
|
354
534
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
355
|
-
# @param [
|
|
535
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
356
536
|
# @return [String, nil]
|
|
357
537
|
def handle_begin_node(node, **opts)
|
|
358
538
|
run_last_expr_type(node.children.last, **opts)
|
|
@@ -362,7 +542,7 @@ module Docscribe
|
|
|
362
542
|
#
|
|
363
543
|
# @note module_function: defines #handle_if_node (visibility: private)
|
|
364
544
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
365
|
-
# @param [
|
|
545
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
366
546
|
# @return [String, nil]
|
|
367
547
|
def handle_if_node(node, **opts)
|
|
368
548
|
t = run_last_expr_type(node.children[1], **opts)
|
|
@@ -379,7 +559,7 @@ module Docscribe
|
|
|
379
559
|
#
|
|
380
560
|
# @note module_function: defines #handle_case_node (visibility: private)
|
|
381
561
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
382
|
-
# @param [
|
|
562
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
383
563
|
# @return [String, nil]
|
|
384
564
|
def handle_case_node(node, **opts)
|
|
385
565
|
branches = process_case_branches(node, **opts)
|
|
@@ -400,15 +580,57 @@ module Docscribe
|
|
|
400
580
|
#
|
|
401
581
|
# @note module_function: defines #handle_or_node (visibility: private)
|
|
402
582
|
# @param [Parser::AST::Node] node the `:or` AST node
|
|
403
|
-
# @param [
|
|
583
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
404
584
|
# @return [String, nil]
|
|
405
585
|
def handle_or_node(node, **opts)
|
|
406
586
|
t = run_last_expr_type(node.children[0], **opts)
|
|
407
587
|
e = run_last_expr_type(node.children[1], **opts)
|
|
408
|
-
|
|
588
|
+
fallback = opts[:fallback_type] || 'untyped'
|
|
589
|
+
# If one side is the fallback alias (FALLBACK_TYPE / fallback_type) and the other is concrete, prefer the concrete
|
|
590
|
+
# This prevents `sig&.return_type || FALLBACK_TYPE` from becoming `String, Object` when String is known
|
|
591
|
+
preferred = or_prefer_concrete(t, e, fallback)
|
|
592
|
+
return preferred if preferred
|
|
593
|
+
|
|
594
|
+
unify_types(t, e, fallback_type: fallback,
|
|
595
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# Handle `:or_asgn` node (`x ||= y`) for last_expr_type.
|
|
599
|
+
#
|
|
600
|
+
# Same type semantics as `||`: the assignment target counts as the left
|
|
601
|
+
# side, so an unknown receiver with a concrete literal right-hand side
|
|
602
|
+
# (e.g. `@h ||= Hash.new`) infers the literal type.
|
|
603
|
+
#
|
|
604
|
+
# @note module_function: defines #handle_or_asgn_node (visibility: private)
|
|
605
|
+
# @param [Parser::AST::Node] node the `:or_asgn` AST node
|
|
606
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
607
|
+
# @return [String, nil]
|
|
608
|
+
def handle_or_asgn_node(node, **opts)
|
|
609
|
+
t = run_last_expr_type(node.children[0], **opts)
|
|
610
|
+
e = run_last_expr_type(node.children[1], **opts)
|
|
611
|
+
fallback = opts[:fallback_type] || 'untyped'
|
|
612
|
+
preferred = or_prefer_concrete(t, e, fallback)
|
|
613
|
+
return preferred if preferred
|
|
614
|
+
|
|
615
|
+
unify_types(t, e, fallback_type: fallback,
|
|
409
616
|
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
410
617
|
end
|
|
411
618
|
|
|
619
|
+
# Prefer the concrete side when the other is a fallback alias.
|
|
620
|
+
#
|
|
621
|
+
# @note module_function: defines #or_prefer_concrete (visibility: private)
|
|
622
|
+
# @param [String, nil] left_type left side inferred type
|
|
623
|
+
# @param [String, nil] right_type right side inferred type
|
|
624
|
+
# @param [String] fallback fallback type name
|
|
625
|
+
# @return [String, nil] preferred side or nil when neither applies
|
|
626
|
+
def or_prefer_concrete(left_type, right_type, fallback)
|
|
627
|
+
if fallback_alias?(left_type, fallback) && !fallback_alias?(right_type, fallback)
|
|
628
|
+
right_type
|
|
629
|
+
elsif fallback_alias?(right_type, fallback) && !fallback_alias?(left_type, fallback)
|
|
630
|
+
left_type
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
412
634
|
# Handle `:and` node (`a && b`) for last_expr_type.
|
|
413
635
|
#
|
|
414
636
|
# The result type is the union of both sides, since either may be returned
|
|
@@ -416,7 +638,7 @@ module Docscribe
|
|
|
416
638
|
#
|
|
417
639
|
# @note module_function: defines #handle_and_node (visibility: private)
|
|
418
640
|
# @param [Parser::AST::Node] node the `:and` AST node
|
|
419
|
-
# @param [
|
|
641
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
420
642
|
# @return [String, nil]
|
|
421
643
|
def handle_and_node(node, **opts)
|
|
422
644
|
t = run_last_expr_type(node.children[0], **opts)
|
|
@@ -432,7 +654,7 @@ module Docscribe
|
|
|
432
654
|
#
|
|
433
655
|
# @note module_function: defines #handle_kwbegin_node (visibility: private)
|
|
434
656
|
# @param [Parser::AST::Node] node the `:kwbegin` AST node
|
|
435
|
-
# @param [
|
|
657
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
436
658
|
# @return [String, nil]
|
|
437
659
|
def handle_kwbegin_node(node, **opts)
|
|
438
660
|
run_last_expr_type(node.children.first, **opts)
|
|
@@ -445,7 +667,7 @@ module Docscribe
|
|
|
445
667
|
#
|
|
446
668
|
# @note module_function: defines #handle_rescue_node (visibility: private)
|
|
447
669
|
# @param [Parser::AST::Node] node the `:rescue` AST node
|
|
448
|
-
# @param [
|
|
670
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
449
671
|
# @return [String, nil]
|
|
450
672
|
def handle_rescue_node(node, **opts)
|
|
451
673
|
branches = collect_rescue_branches(node, **opts)
|
|
@@ -462,7 +684,7 @@ module Docscribe
|
|
|
462
684
|
#
|
|
463
685
|
# @note module_function: defines #collect_rescue_branches (visibility: private)
|
|
464
686
|
# @param [Parser::AST::Node] node the `:rescue` AST node
|
|
465
|
-
# @param [
|
|
687
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
466
688
|
# @return [Array<String, nil>]
|
|
467
689
|
def collect_rescue_branches(node, **opts)
|
|
468
690
|
branches = [run_last_expr_type(node.children[0], **opts)]
|
|
@@ -483,7 +705,7 @@ module Docscribe
|
|
|
483
705
|
#
|
|
484
706
|
# @note module_function: defines #handle_ensure_node (visibility: private)
|
|
485
707
|
# @param [Parser::AST::Node] node the `:ensure` AST node
|
|
486
|
-
# @param [
|
|
708
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
487
709
|
# @return [String, nil]
|
|
488
710
|
def handle_ensure_node(node, **opts)
|
|
489
711
|
run_last_expr_type(node.children[0], **opts)
|
|
@@ -496,7 +718,7 @@ module Docscribe
|
|
|
496
718
|
#
|
|
497
719
|
# @note module_function: defines #handle_defined_node (visibility: private)
|
|
498
720
|
# @param [Parser::AST::Node] _node the `:defined?` AST node
|
|
499
|
-
# @param [
|
|
721
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
500
722
|
# @return [String, nil]
|
|
501
723
|
def handle_defined_node(_node, **opts)
|
|
502
724
|
nil_as_optional = opts.fetch(:nil_as_optional, true)
|
|
@@ -510,7 +732,7 @@ module Docscribe
|
|
|
510
732
|
#
|
|
511
733
|
# @note module_function: defines #handle_zsuper_node (visibility: private)
|
|
512
734
|
# @param [Parser::AST::Node] _node the `:zsuper` AST node
|
|
513
|
-
# @param [
|
|
735
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
514
736
|
# @return [String, nil]
|
|
515
737
|
def handle_zsuper_node(_node, **opts)
|
|
516
738
|
opts[:fallback_type]
|
|
@@ -523,7 +745,7 @@ module Docscribe
|
|
|
523
745
|
#
|
|
524
746
|
# @note module_function: defines #handle_super_node (visibility: private)
|
|
525
747
|
# @param [Parser::AST::Node] _node the `:super` AST node
|
|
526
|
-
# @param [
|
|
748
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
527
749
|
# @return [String, nil]
|
|
528
750
|
def handle_super_node(_node, **opts)
|
|
529
751
|
opts[:fallback_type]
|
|
@@ -536,7 +758,7 @@ module Docscribe
|
|
|
536
758
|
#
|
|
537
759
|
# @note module_function: defines #handle_yield_node (visibility: private)
|
|
538
760
|
# @param [Parser::AST::Node] _node the `:yield` AST node
|
|
539
|
-
# @param [
|
|
761
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
540
762
|
# @return [String, nil]
|
|
541
763
|
def handle_yield_node(_node, **opts)
|
|
542
764
|
opts[:fallback_type]
|
|
@@ -548,7 +770,7 @@ module Docscribe
|
|
|
548
770
|
#
|
|
549
771
|
# @note module_function: defines #handle_case_match_node (visibility: private)
|
|
550
772
|
# @param [Parser::AST::Node] node the `:case_match` AST node
|
|
551
|
-
# @param [
|
|
773
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
552
774
|
# @return [String, nil]
|
|
553
775
|
def handle_case_match_node(node, **opts)
|
|
554
776
|
branches = process_pattern_branches(node, **opts)
|
|
@@ -568,7 +790,7 @@ module Docscribe
|
|
|
568
790
|
#
|
|
569
791
|
# @note module_function: defines #handle_in_pattern_node (visibility: private)
|
|
570
792
|
# @param [Parser::AST::Node] node the `:in_pattern` AST node
|
|
571
|
-
# @param [
|
|
793
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
572
794
|
# @return [String, nil]
|
|
573
795
|
def handle_in_pattern_node(node, **opts)
|
|
574
796
|
run_last_expr_type(node.children[2], **opts)
|
|
@@ -578,7 +800,7 @@ module Docscribe
|
|
|
578
800
|
#
|
|
579
801
|
# @note module_function: defines #process_pattern_branches (visibility: private)
|
|
580
802
|
# @param [Parser::AST::Node] node the :case_match AST node
|
|
581
|
-
# @param [
|
|
803
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
582
804
|
# @return [Array<String>] list of inferred types from each branch
|
|
583
805
|
def process_pattern_branches(node, **opts)
|
|
584
806
|
(node.children[1..] || []).compact.filter_map do |child|
|
|
@@ -590,51 +812,524 @@ module Docscribe
|
|
|
590
812
|
#
|
|
591
813
|
# @note module_function: defines #process_case_branches (visibility: private)
|
|
592
814
|
# @param [Parser::AST::Node] node the :case AST node
|
|
593
|
-
# @param [
|
|
815
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
594
816
|
# @return [Array<String>] list of inferred types from each branch
|
|
595
817
|
def process_case_branches(node, **opts)
|
|
596
|
-
(node.children[1..] || []).compact
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
else
|
|
600
|
-
run_last_expr_type(child, **opts)
|
|
601
|
-
end
|
|
818
|
+
children = (node.children[1..] || []).compact
|
|
819
|
+
branches = children.flat_map do |child|
|
|
820
|
+
child.type == :when ? run_last_expr_type(child.children.last, **opts) : run_last_expr_type(child, **opts)
|
|
602
821
|
end.compact
|
|
822
|
+
branches << 'nil' unless children.last && children.last.type != :when
|
|
823
|
+
branches
|
|
603
824
|
end
|
|
604
825
|
|
|
605
826
|
# Handle `:block` node for last_expr_type.
|
|
606
827
|
#
|
|
607
828
|
# @note module_function: defines #handle_block_node (visibility: private)
|
|
608
829
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
609
|
-
# @param [
|
|
830
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
610
831
|
# @return [String, nil]
|
|
611
832
|
def handle_block_node(node, **opts)
|
|
612
833
|
send_node = node.children[0]
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
834
|
+
return run_last_expr_type(node.children[2], **opts) unless send_node&.type == :send
|
|
835
|
+
|
|
836
|
+
meth = send_node.children[1]
|
|
837
|
+
handle_then_block(node, meth, **opts) ||
|
|
838
|
+
handle_to_h_block(node, meth, **opts) ||
|
|
839
|
+
block_send_rbs_type(node, send_node, **opts) ||
|
|
840
|
+
handle_map_block(node, meth, **opts) ||
|
|
841
|
+
run_last_expr_type(node.children[2], **opts)
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
# @note module_function: defines #handle_then_block (visibility: private)
|
|
845
|
+
# @param [Parser::AST::Node] node
|
|
846
|
+
# @param [Symbol] meth
|
|
847
|
+
# @param [Hash] opts
|
|
848
|
+
# @return [String, nil]
|
|
849
|
+
def handle_then_block(node, meth, **opts)
|
|
850
|
+
return nil unless %i[then yield_self].include?(meth)
|
|
617
851
|
|
|
618
852
|
run_last_expr_type(node.children[2], **opts)
|
|
619
853
|
end
|
|
620
854
|
|
|
855
|
+
# @note module_function: defines #handle_to_h_block (visibility: private)
|
|
856
|
+
# @param [Parser::AST::Node] node
|
|
857
|
+
# @param [Symbol] meth
|
|
858
|
+
# @param [Hash] opts
|
|
859
|
+
# @return [String, nil]
|
|
860
|
+
def handle_to_h_block(node, meth, **opts)
|
|
861
|
+
return nil unless meth == :to_h
|
|
862
|
+
|
|
863
|
+
recv = to_h_each_recv(node)
|
|
864
|
+
return nil unless recv
|
|
865
|
+
|
|
866
|
+
key, value = to_h_key_value(node, recv, **opts)
|
|
867
|
+
"Hash<#{key}, #{value}>"
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
# Receiver of `to_h` when it chains off `each_with_index`.
|
|
871
|
+
#
|
|
872
|
+
# @note module_function: defines #to_h_each_recv (visibility: private)
|
|
873
|
+
# @param [Parser::AST::Node] node block node
|
|
874
|
+
# @return [Parser::AST::Node, nil]
|
|
875
|
+
def to_h_each_recv(node)
|
|
876
|
+
send_node = node.children[0]
|
|
877
|
+
recv = send_node.children[0]
|
|
878
|
+
recv if recv&.type == :send && recv.children[1] == :each_with_index
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# Resolve key/value types for a `to_h` block.
|
|
882
|
+
#
|
|
883
|
+
# @note module_function: defines #to_h_key_value (visibility: private)
|
|
884
|
+
# @param [Parser::AST::Node] node block node
|
|
885
|
+
# @param [Parser::AST::Node] recv `each_with_index` send node
|
|
886
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
887
|
+
# @return [(String, String)]
|
|
888
|
+
def to_h_key_value(node, recv, **opts)
|
|
889
|
+
body = node.children[2]
|
|
890
|
+
key, value = to_h_block_pair_types(body, block_arg_names(node), **opts)
|
|
891
|
+
key ||= hash_elem_from_enumerator(recv.children[0], **opts)
|
|
892
|
+
key = 'Object' if unknown_type?(key)
|
|
893
|
+
value ||= pair_literal?(body) ? 'Object' : 'Integer'
|
|
894
|
+
[key, value]
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Names of the block parameters, if any.
|
|
898
|
+
#
|
|
899
|
+
# @note module_function: defines #block_arg_names (visibility: private)
|
|
900
|
+
# @param [Parser::AST::Node] node block node
|
|
901
|
+
# @return [Array<String>]
|
|
902
|
+
def block_arg_names(node)
|
|
903
|
+
args = node.children[1]
|
|
904
|
+
return [] unless args&.type == :args
|
|
905
|
+
|
|
906
|
+
args.children.filter_map { |a| a.children[0]&.to_s }
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# Infer key/value types from a `[k, v]` pair literal block body.
|
|
910
|
+
#
|
|
911
|
+
# A bare `lvar` matching an `each_with_index` block parameter resolves
|
|
912
|
+
# structurally: first parameter is the element (resolved from the receiver
|
|
913
|
+
# by the caller), second parameter is always the Integer index.
|
|
914
|
+
#
|
|
915
|
+
# @note module_function: defines #to_h_block_pair_types (visibility: private)
|
|
916
|
+
# @param [Parser::AST::Node?] body block body node
|
|
917
|
+
# @param [Array<String>] arg_names block parameter names
|
|
918
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
919
|
+
# @return [(String, nil, String, nil)] inferred key and value types, nil when unavailable
|
|
920
|
+
def to_h_block_pair_types(body, arg_names, **opts)
|
|
921
|
+
return [nil, nil] unless pair_literal?(body)
|
|
922
|
+
|
|
923
|
+
pair = body.type == :begin ? body.children.last : body
|
|
924
|
+
[pair_elem_type(pair.children[0], arg_names, 0, **opts),
|
|
925
|
+
pair_elem_type(pair.children[1], arg_names, 1, **opts)]
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
# Whether the block body is a `[k, v]` pair literal.
|
|
929
|
+
#
|
|
930
|
+
# @note module_function: defines #pair_literal? (visibility: private)
|
|
931
|
+
# @param [Parser::AST::Node?] body block body node
|
|
932
|
+
# @return [Boolean]
|
|
933
|
+
def pair_literal?(body)
|
|
934
|
+
body = body.children.last if body&.type == :begin
|
|
935
|
+
body&.type == :array && body.children.size == 2
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# Infer one pair element, honoring block parameter positions.
|
|
939
|
+
#
|
|
940
|
+
# @note module_function: defines #pair_elem_type (visibility: private)
|
|
941
|
+
# @param [Parser::AST::Node?] node element node
|
|
942
|
+
# @param [Array<String>] arg_names block parameter names
|
|
943
|
+
# @param [Integer] position 0 for key, 1 for value
|
|
944
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
945
|
+
# @return [String, nil]
|
|
946
|
+
def pair_elem_type(node, arg_names, position, **opts)
|
|
947
|
+
if node&.type == :lvar && node.children[0].to_s == arg_names[position]
|
|
948
|
+
return position == 1 ? 'Integer' : nil
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
infer_pair_elem(node, **opts)
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
# Infer one pair element type, normalizing unknown to nil.
|
|
955
|
+
#
|
|
956
|
+
# @note module_function: defines #infer_pair_elem (visibility: private)
|
|
957
|
+
# @param [Parser::AST::Node?] node element node
|
|
958
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
959
|
+
# @return [String, nil]
|
|
960
|
+
def infer_pair_elem(node, **opts)
|
|
961
|
+
type = run_last_expr_type(node, **opts)
|
|
962
|
+
type unless unknown_type?(type)
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# Whether a type string means "could not determine".
|
|
966
|
+
#
|
|
967
|
+
# @note module_function: defines #unknown_type? (visibility: private)
|
|
968
|
+
# @param [String, nil] type inferred type string
|
|
969
|
+
# @return [Boolean]
|
|
970
|
+
def unknown_type?(type)
|
|
971
|
+
type.nil? || %w[Object untyped nil].include?(type)
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
# @note module_function: defines #handle_map_block (visibility: private)
|
|
975
|
+
# @param [Parser::AST::Node] node
|
|
976
|
+
# @param [Symbol] meth
|
|
977
|
+
# @param [Hash] opts
|
|
978
|
+
# @return [String, nil]
|
|
979
|
+
def handle_map_block(node, meth, **opts)
|
|
980
|
+
return nil unless %i[map collect].include?(meth)
|
|
981
|
+
|
|
982
|
+
inner = run_last_expr_type(node.children[2], **opts)
|
|
983
|
+
return nil unless inner && inner != 'Object' && inner != 'untyped'
|
|
984
|
+
|
|
985
|
+
"Array<#{inner}>"
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
# @note module_function: defines #block_send_rbs_type (visibility: private)
|
|
989
|
+
# @param [Parser::AST::Node] node
|
|
990
|
+
# @param [Parser::AST::Node] send_node
|
|
991
|
+
# @param [Hash] opts
|
|
992
|
+
# @return [String, nil]
|
|
993
|
+
def block_send_rbs_type(node, send_node, **opts)
|
|
994
|
+
rbs_type = send_rbs_type(send_node.children[0], send_node.children[1], **opts)
|
|
995
|
+
return nil unless rbs_type
|
|
996
|
+
|
|
997
|
+
block_rbs_with_inner(rbs_type, node.children[2], **opts) || rbs_type
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
# @note module_function: defines #block_rbs_with_inner (visibility: private)
|
|
1001
|
+
# @param [String, nil] rbs_type
|
|
1002
|
+
# @param [Parser::AST::Node] block_body
|
|
1003
|
+
# @param [Hash] opts
|
|
1004
|
+
# @return [String, nil]
|
|
1005
|
+
def block_rbs_with_inner(rbs_type, block_body, **opts)
|
|
1006
|
+
inner = run_last_expr_type(block_body, **opts)
|
|
1007
|
+
return nil unless inner
|
|
1008
|
+
|
|
1009
|
+
substituted = block_generic_substitution(rbs_type, inner)
|
|
1010
|
+
return substituted if substituted
|
|
1011
|
+
|
|
1012
|
+
bare_container_type(rbs_type, inner)
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
# @note module_function: defines #block_generic_substitution (visibility: private)
|
|
1016
|
+
# @param [String] rbs_type
|
|
1017
|
+
# @param [String, nil] inner
|
|
1018
|
+
# @return [String, nil]
|
|
1019
|
+
def block_generic_substitution(rbs_type, inner)
|
|
1020
|
+
return nil unless generic_placeholder?(rbs_type)
|
|
1021
|
+
|
|
1022
|
+
inner_generic = extract_generic_inner(rbs_type)
|
|
1023
|
+
return nil unless inner_generic
|
|
1024
|
+
|
|
1025
|
+
placeholders = placeholder_tokens(inner_generic)
|
|
1026
|
+
result = substitute_placeholders(rbs_type, placeholders, inner)
|
|
1027
|
+
return result unless result == rbs_type
|
|
1028
|
+
|
|
1029
|
+
fallback_generic_substitution(rbs_type, inner)
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# @note module_function: defines #placeholder_tokens (visibility: private)
|
|
1033
|
+
# @param [String] inner_generic
|
|
1034
|
+
# @return [Array<String>]
|
|
1035
|
+
def placeholder_tokens(inner_generic)
|
|
1036
|
+
split_generic_args(inner_generic).select do |arg|
|
|
1037
|
+
placeholder_token?(arg.strip.delete_suffix('?').strip)
|
|
1038
|
+
end
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
# @note module_function: defines #substitute_placeholders (visibility: private)
|
|
1042
|
+
# @param [String] rbs_type
|
|
1043
|
+
# @param [Array<String>] placeholders
|
|
1044
|
+
# @param [String] inner
|
|
1045
|
+
# @return [String]
|
|
1046
|
+
def substitute_placeholders(rbs_type, placeholders, inner)
|
|
1047
|
+
result = rbs_type.dup
|
|
1048
|
+
placeholders.each do |ph|
|
|
1049
|
+
token = ph.strip.delete_suffix('?').strip
|
|
1050
|
+
result = result.gsub(token, inner)
|
|
1051
|
+
end
|
|
1052
|
+
result
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
# @note module_function: defines #fallback_generic_substitution (visibility: private)
|
|
1056
|
+
# @param [String] rbs_type
|
|
1057
|
+
# @param [String] inner
|
|
1058
|
+
# @return [String]
|
|
1059
|
+
def fallback_generic_substitution(rbs_type, inner)
|
|
1060
|
+
rbs_type.gsub(/\bU\b/, inner).gsub(/\bElem\b/, inner).gsub(/\buntyped\b/, inner)
|
|
1061
|
+
.gsub(/\bV\b/, inner).gsub(/\bT\b/, inner).gsub(/\bE\b/, inner).gsub(/\bK\b/, inner)
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
# @note module_function: defines #bare_container_type (visibility: private)
|
|
1065
|
+
# @param [String, nil] rbs_type
|
|
1066
|
+
# @param [String] inner
|
|
1067
|
+
# @return [String, nil]
|
|
1068
|
+
def bare_container_type(rbs_type, inner)
|
|
1069
|
+
return nil unless rbs_type.is_a?(String)
|
|
1070
|
+
|
|
1071
|
+
base = rbs_type&.split(/[<\[ ]/)&.first
|
|
1072
|
+
return nil unless %w[Array Set Enumerable Enumerator].include?(base)
|
|
1073
|
+
return nil if rbs_type.include?('<') || rbs_type.include?('[')
|
|
1074
|
+
|
|
1075
|
+
"#{base}<#{inner}>"
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
# @note module_function: defines #generic_placeholder? (visibility: private)
|
|
1079
|
+
# @param [String, nil] rbs_type
|
|
1080
|
+
# @return [Boolean]
|
|
1081
|
+
def generic_placeholder?(rbs_type)
|
|
1082
|
+
return false unless rbs_type =~ /[<\[]/
|
|
1083
|
+
|
|
1084
|
+
inner = extract_generic_inner(rbs_type)
|
|
1085
|
+
return false unless inner
|
|
1086
|
+
|
|
1087
|
+
split_generic_args(inner).any? do |arg|
|
|
1088
|
+
placeholder_token?(arg.strip.delete_suffix('?').strip)
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
# @note module_function: defines #placeholder_token? (visibility: private)
|
|
1093
|
+
# @param [String] token
|
|
1094
|
+
# @return [Boolean]
|
|
1095
|
+
def placeholder_token?(token)
|
|
1096
|
+
!!(token == 'untyped' || token.include?('::') || token =~ /\A[a-z]/ ||
|
|
1097
|
+
(token =~ /\A[A-Z][A-Za-z0-9_]*\z/ && !Docscribe::Types::Primitive.primitive?(token)))
|
|
1098
|
+
end
|
|
1099
|
+
|
|
621
1100
|
# Handle `:send` node for last_expr_type.
|
|
622
1101
|
#
|
|
623
1102
|
# @note module_function: defines #handle_send_node (visibility: private)
|
|
624
1103
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
625
|
-
# @param [
|
|
1104
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
626
1105
|
# @return [String, nil]
|
|
627
1106
|
def handle_send_node(node, **opts)
|
|
628
1107
|
recv = node.children[0]
|
|
629
1108
|
meth = node.children[1]
|
|
1109
|
+
try_synthetic(node, meth, recv, **opts) ||
|
|
1110
|
+
try_rbs(meth, recv, **opts) ||
|
|
1111
|
+
try_compound(node, **opts) ||
|
|
1112
|
+
string_send_type(meth, recv) ||
|
|
1113
|
+
rbs_fallback(meth, recv, **opts) ||
|
|
1114
|
+
Literals.type_from_literal(node, fallback_type: opts[:fallback_type])
|
|
1115
|
+
end
|
|
630
1116
|
|
|
631
|
-
|
|
632
|
-
|
|
1117
|
+
# @note module_function: defines #try_synthetic (visibility: private)
|
|
1118
|
+
# @param [Parser::AST::Node] node
|
|
1119
|
+
# @param [Symbol] meth
|
|
1120
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1121
|
+
# @param [Hash] opts
|
|
1122
|
+
# @return [String, nil]
|
|
1123
|
+
def try_synthetic(node, meth, recv, **opts)
|
|
1124
|
+
synthetic_enumerator_type(node, meth, recv, **opts) ||
|
|
1125
|
+
synthetic_hash_type(node, meth, recv, **opts)
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
# @note module_function: defines #try_rbs (visibility: private)
|
|
1129
|
+
# @param [Symbol] meth
|
|
1130
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1131
|
+
# @param [Hash] opts
|
|
1132
|
+
# @return [String, nil]
|
|
1133
|
+
def try_rbs(meth, recv, **opts)
|
|
1134
|
+
return nil unless opts[:core_rbs_provider]
|
|
1135
|
+
|
|
1136
|
+
rbs = send_rbs_type(recv, meth, **opts)
|
|
1137
|
+
return rbs if rbs && rbs != FALLBACK_TYPE && !rbs.include?('Object')
|
|
1138
|
+
|
|
1139
|
+
nil
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
# @note module_function: defines #try_compound (visibility: private)
|
|
1143
|
+
# @param [Parser::AST::Node] node
|
|
1144
|
+
# @param [Hash] opts
|
|
1145
|
+
# @return [String, nil]
|
|
1146
|
+
def try_compound(node, **opts)
|
|
1147
|
+
infer_from_compound_assign(node, **opts)
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
# @note module_function: defines #rbs_fallback (visibility: private)
|
|
1151
|
+
# @param [Symbol] meth
|
|
1152
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1153
|
+
# @param [Hash] opts
|
|
1154
|
+
# @return [String, nil]
|
|
1155
|
+
def rbs_fallback(meth, recv, **opts)
|
|
1156
|
+
return nil unless opts[:core_rbs_provider]
|
|
1157
|
+
|
|
1158
|
+
send_rbs_type(recv, meth, **opts)
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
# @note module_function: defines #synthetic_enumerator_type (visibility: private)
|
|
1162
|
+
# @param [Parser::AST::Node] node
|
|
1163
|
+
# @param [Symbol] meth
|
|
1164
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1165
|
+
# @param [Hash] opts
|
|
1166
|
+
# @return [String, nil]
|
|
1167
|
+
def synthetic_enumerator_type(node, meth, recv, **opts)
|
|
1168
|
+
return unless meth == :each_with_index && node.children.size == 2
|
|
1169
|
+
|
|
1170
|
+
elem = enumerator_elem_from_recv(recv, **opts)
|
|
1171
|
+
return "Enumerator<#{elem}, Integer>" if elem
|
|
1172
|
+
|
|
1173
|
+
'Enumerator<Object, Integer>'
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# @note module_function: defines #enumerator_elem_from_recv (visibility: private)
|
|
1177
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1178
|
+
# @param [Hash] opts
|
|
1179
|
+
# @return [String, nil]
|
|
1180
|
+
def enumerator_elem_from_recv(recv, **opts)
|
|
1181
|
+
recv_type = enumerator_recv_type(recv, **opts)
|
|
1182
|
+
return nil unless recv_type
|
|
1183
|
+
|
|
1184
|
+
extract_array_elem_strict(recv_type) || enumerator_object_elem(recv_type)
|
|
1185
|
+
end
|
|
1186
|
+
|
|
1187
|
+
# @note module_function: defines #enumerator_recv_type (visibility: private)
|
|
1188
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1189
|
+
# @param [Hash] opts
|
|
1190
|
+
# @return [String, nil]
|
|
1191
|
+
def enumerator_recv_type(recv, **opts)
|
|
1192
|
+
receiver_rbs_type_name(recv, opts[:core_rbs_provider], opts[:local_var_types],
|
|
1193
|
+
opts[:param_types]) ||
|
|
1194
|
+
run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
|
|
1195
|
+
local_var_types: opts[:local_var_types],
|
|
1196
|
+
param_types: opts[:param_types],
|
|
1197
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
1198
|
+
signature_provider: opts[:signature_provider],
|
|
1199
|
+
container: opts[:container])
|
|
1200
|
+
end
|
|
633
1201
|
|
|
634
|
-
|
|
635
|
-
|
|
1202
|
+
# @note module_function: defines #extract_array_elem_strict (visibility: private)
|
|
1203
|
+
# @param [String] type_str
|
|
1204
|
+
# @return [String, nil]
|
|
1205
|
+
def extract_array_elem_strict(type_str)
|
|
1206
|
+
return nil unless type_str =~ /\AArray<(.+)>\z/
|
|
636
1207
|
|
|
637
|
-
|
|
1208
|
+
Regexp.last_match(1).strip
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
# @note module_function: defines #enumerator_object_elem (visibility: private)
|
|
1212
|
+
# @param [String] recv_type
|
|
1213
|
+
# @return [String, nil]
|
|
1214
|
+
def enumerator_object_elem(recv_type)
|
|
1215
|
+
return 'Object' if recv_type == 'Array'
|
|
1216
|
+
|
|
1217
|
+
nil
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
# @note module_function: defines #synthetic_hash_type (visibility: private)
|
|
1221
|
+
# @param [Parser::AST::Node] _node
|
|
1222
|
+
# @param [Symbol] meth
|
|
1223
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1224
|
+
# @param [Hash] opts
|
|
1225
|
+
# @return [String, nil]
|
|
1226
|
+
def synthetic_hash_type(_node, meth, recv, **opts)
|
|
1227
|
+
return unless meth == :to_h && recv && recv.type == :send && recv.children[1] == :each_with_index
|
|
1228
|
+
|
|
1229
|
+
inner_recv = recv.children[0]
|
|
1230
|
+
elem = hash_elem_from_enumerator(inner_recv, **opts) || 'Object'
|
|
1231
|
+
"Hash<#{elem}, Integer>"
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
# @note module_function: defines #hash_elem_from_enumerator (visibility: private)
|
|
1235
|
+
# @param [Parser::AST::Node, nil] inner_recv
|
|
1236
|
+
# @param [Hash] opts
|
|
1237
|
+
# @return [String, nil]
|
|
1238
|
+
def hash_elem_from_enumerator(inner_recv, **opts)
|
|
1239
|
+
inner_type = hash_inner_type(inner_recv, **opts)
|
|
1240
|
+
return nil unless inner_type
|
|
1241
|
+
|
|
1242
|
+
extract_array_elem(inner_type) || extract_enumerator_elem(inner_type)
|
|
1243
|
+
end
|
|
1244
|
+
|
|
1245
|
+
# @note module_function: defines #hash_inner_type (visibility: private)
|
|
1246
|
+
# @param [Parser::AST::Node, nil] inner_recv
|
|
1247
|
+
# @param [Hash] opts
|
|
1248
|
+
# @return [String, nil]
|
|
1249
|
+
def hash_inner_type(inner_recv, **opts)
|
|
1250
|
+
receiver_rbs_type_name(inner_recv, opts[:core_rbs_provider], opts[:local_var_types],
|
|
1251
|
+
opts[:param_types]) ||
|
|
1252
|
+
run_last_expr_type(inner_recv, fallback_type: nil, nil_as_optional: false,
|
|
1253
|
+
local_var_types: opts[:local_var_types],
|
|
1254
|
+
param_types: opts[:param_types],
|
|
1255
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
1256
|
+
signature_provider: opts[:signature_provider],
|
|
1257
|
+
container: opts[:container])
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
# @note module_function: defines #extract_array_elem (visibility: private)
|
|
1261
|
+
# @param [String] type_str
|
|
1262
|
+
# @return [String, nil]
|
|
1263
|
+
def extract_array_elem(type_str)
|
|
1264
|
+
return nil unless type_str =~ /\AArray<(.+)>\z/
|
|
1265
|
+
|
|
1266
|
+
cand = Regexp.last_match(1).strip
|
|
1267
|
+
return nil if cand.empty? || %w[Object untyped].include?(cand)
|
|
1268
|
+
|
|
1269
|
+
cand
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
# @note module_function: defines #extract_enumerator_elem (visibility: private)
|
|
1273
|
+
# @param [String] type_str
|
|
1274
|
+
# @return [String, nil]
|
|
1275
|
+
def extract_enumerator_elem(type_str)
|
|
1276
|
+
return nil unless type_str =~ /\AEnumerator<(.+),\s*Integer>\z/
|
|
1277
|
+
|
|
1278
|
+
cand = Regexp.last_match(1).strip
|
|
1279
|
+
return nil if cand.empty?
|
|
1280
|
+
|
|
1281
|
+
cand
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
# @note module_function: defines #string_send_type (visibility: private)
|
|
1285
|
+
# @param [Symbol] meth
|
|
1286
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1287
|
+
# @return [String, nil]
|
|
1288
|
+
def string_send_type(meth, recv)
|
|
1289
|
+
return 'String' if string_like_method?(meth)
|
|
1290
|
+
return 'String' if file_join_method?(meth, recv)
|
|
1291
|
+
return 'String' if sub_string_method?(meth, recv)
|
|
1292
|
+
|
|
1293
|
+
nil
|
|
1294
|
+
end
|
|
1295
|
+
|
|
1296
|
+
# @note module_function: defines #string_like_method? (visibility: private)
|
|
1297
|
+
# @param [Symbol] meth
|
|
1298
|
+
# @return [Boolean]
|
|
1299
|
+
def string_like_method?(meth)
|
|
1300
|
+
%i[to_s to_str inspect].include?(meth)
|
|
1301
|
+
end
|
|
1302
|
+
|
|
1303
|
+
# @note module_function: defines #file_join_method? (visibility: private)
|
|
1304
|
+
# @param [Symbol] meth
|
|
1305
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1306
|
+
# @return [Boolean]
|
|
1307
|
+
def file_join_method?(meth, recv)
|
|
1308
|
+
meth == :join && recv&.type == :const && recv&.children&.[](1) == :File
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
# @note module_function: defines #sub_string_method? (visibility: private)
|
|
1312
|
+
# @param [Symbol] meth
|
|
1313
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1314
|
+
# @return [Boolean]
|
|
1315
|
+
def sub_string_method?(meth, recv)
|
|
1316
|
+
meth == :sub && recv && recv&.type != :const
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
# @note module_function: defines #handle_csend_node (visibility: private)
|
|
1320
|
+
# @param [Parser::AST::Node] node the `:csend` AST node (safe navigation)
|
|
1321
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
1322
|
+
# @return [String, nil]
|
|
1323
|
+
def handle_csend_node(node, **opts)
|
|
1324
|
+
recv = node.children[0]
|
|
1325
|
+
meth = node.children[1]
|
|
1326
|
+
rbs_type = send_rbs_type(recv, meth, **opts) if opts[:core_rbs_provider] || opts[:signature_provider]
|
|
1327
|
+
if rbs_type
|
|
1328
|
+
unify_types(rbs_type, 'nil', fallback_type: opts[:fallback_type] || FALLBACK_TYPE,
|
|
1329
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
1330
|
+
else
|
|
1331
|
+
opts[:fallback_type] || FALLBACK_TYPE
|
|
1332
|
+
end
|
|
638
1333
|
end
|
|
639
1334
|
|
|
640
1335
|
# Resolve RBS return type for a send node, trying explicit receiver first,
|
|
@@ -643,7 +1338,7 @@ module Docscribe
|
|
|
643
1338
|
# @note module_function: defines #send_rbs_type (visibility: private)
|
|
644
1339
|
# @param [Parser::AST::Node, nil] recv the receiver node
|
|
645
1340
|
# @param [Symbol] meth the method name
|
|
646
|
-
# @param [
|
|
1341
|
+
# @param [Hash] opts additional keyword options
|
|
647
1342
|
# @return [String, nil]
|
|
648
1343
|
def send_rbs_type(recv, meth, **opts)
|
|
649
1344
|
rbs_type = resolve_rbs_for_send(recv, meth, opts[:core_rbs_provider], opts[:local_var_types],
|
|
@@ -664,9 +1359,9 @@ module Docscribe
|
|
|
664
1359
|
# @note module_function: defines #resolve_rbs_for_send (visibility: private)
|
|
665
1360
|
# @param [Parser::AST::Node, nil] recv the receiver node of the send
|
|
666
1361
|
# @param [Symbol] meth the method name being called
|
|
667
|
-
# @param [
|
|
668
|
-
# @param [Hash<
|
|
669
|
-
# @param [Hash<String, String
|
|
1362
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider optional RBS provider for core type lookup
|
|
1363
|
+
# @param [Hash<String, String>?] local_var_types inferred local variable type map
|
|
1364
|
+
# @param [Hash<String, String>?] param_types parameter name to type map
|
|
670
1365
|
# @return [String, nil] resolved type or nil if unresolvable
|
|
671
1366
|
def resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
672
1367
|
recv_type = receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types)
|
|
@@ -674,7 +1369,7 @@ module Docscribe
|
|
|
674
1369
|
|
|
675
1370
|
if core_rbs_provider
|
|
676
1371
|
rbs = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
|
|
677
|
-
return rbs unless rbs == FALLBACK_TYPE
|
|
1372
|
+
return substitute_rbs_type(rbs, recv_type) unless rbs == FALLBACK_TYPE
|
|
678
1373
|
end
|
|
679
1374
|
|
|
680
1375
|
nil
|
|
@@ -687,7 +1382,7 @@ module Docscribe
|
|
|
687
1382
|
# @note module_function: defines #resolve_rbs_for_send_with_signature_provider (visibility: private)
|
|
688
1383
|
# @param [Parser::AST::Node, nil] recv the receiver node
|
|
689
1384
|
# @param [Symbol] meth the method name
|
|
690
|
-
# @param [
|
|
1385
|
+
# @param [Hash] opts additional keyword options
|
|
691
1386
|
# @return [String, nil]
|
|
692
1387
|
def resolve_rbs_for_send_with_signature_provider(recv, meth, **opts)
|
|
693
1388
|
return nil unless opts[:signature_provider]
|
|
@@ -696,7 +1391,8 @@ module Docscribe
|
|
|
696
1391
|
opts[:param_types])
|
|
697
1392
|
return nil unless recv_type
|
|
698
1393
|
|
|
699
|
-
opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)&.return_type
|
|
1394
|
+
rbs = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)&.return_type
|
|
1395
|
+
rbs ? substitute_rbs_type(rbs, recv_type) : nil
|
|
700
1396
|
end
|
|
701
1397
|
|
|
702
1398
|
# Resolve return type from the current method's container via RBS.
|
|
@@ -706,19 +1402,19 @@ module Docscribe
|
|
|
706
1402
|
#
|
|
707
1403
|
# @note module_function: defines #container_rbs_return_type (visibility: private)
|
|
708
1404
|
# @param [Symbol] meth the method name being called
|
|
709
|
-
# @param [
|
|
1405
|
+
# @param [Hash] opts additional keyword options (must include :container and :core_rbs_provider)
|
|
710
1406
|
# @return [String, nil] resolved type or nil if unresolvable
|
|
711
1407
|
def container_rbs_return_type(meth, **opts)
|
|
712
1408
|
return unless opts[:container]
|
|
713
1409
|
|
|
714
1410
|
if opts[:core_rbs_provider]
|
|
715
1411
|
rbs = resolve_rbs_return_type(opts[:container], meth, opts[:core_rbs_provider])
|
|
716
|
-
return rbs unless rbs == FALLBACK_TYPE
|
|
1412
|
+
return substitute_rbs_type(rbs, opts[:container]) unless rbs == FALLBACK_TYPE
|
|
717
1413
|
end
|
|
718
1414
|
|
|
719
1415
|
if opts[:signature_provider]
|
|
720
1416
|
sig = opts[:signature_provider].signature_for(container: opts[:container], scope: :instance, name: meth)
|
|
721
|
-
return sig.return_type if sig
|
|
1417
|
+
return substitute_rbs_type(sig.return_type, opts[:container]) if sig
|
|
722
1418
|
end
|
|
723
1419
|
|
|
724
1420
|
nil
|
|
@@ -740,51 +1436,454 @@ module Docscribe
|
|
|
740
1436
|
nil: 'NilClass'
|
|
741
1437
|
}.freeze
|
|
742
1438
|
|
|
1439
|
+
# Infer return type from a compound-assignment-like `:send`.
|
|
1440
|
+
#
|
|
1441
|
+
# RBS -> Infer: no hardcoded operator list, always try left/right via Infer,
|
|
1442
|
+
# then RBS for meth on receiver, else unify with String? handling.
|
|
1443
|
+
#
|
|
1444
|
+
# @note module_function: defines #infer_from_compound_assign (visibility: private)
|
|
1445
|
+
# @param [Parser::AST::Node] node the `:send` AST node
|
|
1446
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
1447
|
+
# @return [String, nil]
|
|
1448
|
+
def infer_from_compound_assign(node, **opts)
|
|
1449
|
+
meth = node.children[1]
|
|
1450
|
+
recv = node.children[0]
|
|
1451
|
+
arg = node.children[2]
|
|
1452
|
+
left = compound_left_type(recv, **opts)
|
|
1453
|
+
right = compound_right_type(arg, **opts)
|
|
1454
|
+
rbs = compound_rbs_type(recv, left, meth, **opts)
|
|
1455
|
+
return rbs if rbs
|
|
1456
|
+
|
|
1457
|
+
compound_fallback_type(left, right, meth, **opts)
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
# @note module_function: defines #compound_left_type (visibility: private)
|
|
1461
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1462
|
+
# @param [Hash] opts
|
|
1463
|
+
# @return [String, nil]
|
|
1464
|
+
def compound_left_type(recv, **opts)
|
|
1465
|
+
return nil unless recv
|
|
1466
|
+
|
|
1467
|
+
found = compound_var_lookup(recv, **opts)
|
|
1468
|
+
return found if found
|
|
1469
|
+
|
|
1470
|
+
run_last_expr_type(recv, **compound_expr_opts(**opts))
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# @note module_function: defines #compound_var_lookup (visibility: private)
|
|
1474
|
+
# @param [Parser::AST::Node] recv
|
|
1475
|
+
# @param [Hash] opts
|
|
1476
|
+
# @return [String, nil]
|
|
1477
|
+
def compound_var_lookup(recv, **opts)
|
|
1478
|
+
return nil unless recv.is_a?(Parser::AST::Node)
|
|
1479
|
+
return nil unless %i[lvar ivar gvar cvar].include?(recv&.type)
|
|
1480
|
+
|
|
1481
|
+
name = recv&.children&.[](0).to_s
|
|
1482
|
+
if recv&.type == :lvar
|
|
1483
|
+
lookup_lvar_type(name, opts[:local_var_types], opts[:param_types])
|
|
1484
|
+
else
|
|
1485
|
+
opts[:local_var_types]&.fetch(name, nil)
|
|
1486
|
+
end
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
# @note module_function: defines #compound_right_type (visibility: private)
|
|
1490
|
+
# @param [Parser::AST::Node, nil] arg
|
|
1491
|
+
# @param [Hash] opts
|
|
1492
|
+
# @return [String, nil]
|
|
1493
|
+
def compound_right_type(arg, **opts)
|
|
1494
|
+
return nil unless arg
|
|
1495
|
+
|
|
1496
|
+
run_last_expr_type(arg, **compound_expr_opts(**opts))
|
|
1497
|
+
end
|
|
1498
|
+
|
|
1499
|
+
# @note module_function: defines #compound_expr_opts (visibility: private)
|
|
1500
|
+
# @param [Hash] opts
|
|
1501
|
+
# @return [Hash<Symbol, Object>]
|
|
1502
|
+
def compound_expr_opts(**opts)
|
|
1503
|
+
{
|
|
1504
|
+
fallback_type: opts[:fallback_type] || FALLBACK_TYPE,
|
|
1505
|
+
nil_as_optional: true,
|
|
1506
|
+
local_var_types: opts[:local_var_types],
|
|
1507
|
+
param_types: opts[:param_types],
|
|
1508
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
1509
|
+
signature_provider: opts[:signature_provider],
|
|
1510
|
+
container: opts[:container]
|
|
1511
|
+
}
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
# @note module_function: defines #compound_rbs_type (visibility: private)
|
|
1515
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1516
|
+
# @param [String, nil] left
|
|
1517
|
+
# @param [Symbol] meth
|
|
1518
|
+
# @param [Hash] opts
|
|
1519
|
+
# @return [String, nil]
|
|
1520
|
+
def compound_rbs_type(recv, left, meth, **opts)
|
|
1521
|
+
recv_type = cleaned_recv_type(left) ||
|
|
1522
|
+
receiver_rbs_type_name(recv, opts[:core_rbs_provider],
|
|
1523
|
+
opts[:local_var_types], opts[:param_types])
|
|
1524
|
+
return nil unless recv_type && meth
|
|
1525
|
+
|
|
1526
|
+
resolve_compound_rbs(recv_type, meth, **opts)
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
# @note module_function: defines #resolve_compound_rbs (visibility: private)
|
|
1530
|
+
# @param [String] recv_type
|
|
1531
|
+
# @param [Symbol] meth
|
|
1532
|
+
# @param [Hash] opts
|
|
1533
|
+
# @return [String, nil]
|
|
1534
|
+
def resolve_compound_rbs(recv_type, meth, **opts)
|
|
1535
|
+
if opts[:core_rbs_provider]
|
|
1536
|
+
rbs = resolve_rbs_return_type(recv_type, meth, opts[:core_rbs_provider])
|
|
1537
|
+
return substitute_rbs_type(rbs, recv_type) unless rbs == FALLBACK_TYPE
|
|
1538
|
+
end
|
|
1539
|
+
if opts[:signature_provider]
|
|
1540
|
+
sig = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)
|
|
1541
|
+
return substitute_rbs_type(sig.return_type, recv_type) if sig
|
|
1542
|
+
end
|
|
1543
|
+
nil
|
|
1544
|
+
end
|
|
1545
|
+
|
|
1546
|
+
# @note module_function: defines #compound_fallback_type (visibility: private)
|
|
1547
|
+
# @param [String, nil] left
|
|
1548
|
+
# @param [String, nil] right
|
|
1549
|
+
# @param [Symbol] meth
|
|
1550
|
+
# @param [Hash] opts
|
|
1551
|
+
# @return [String, nil]
|
|
1552
|
+
def compound_fallback_type(left, right, meth, **opts)
|
|
1553
|
+
fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s
|
|
1554
|
+
return synthesize_shovel_type(left, right, fallback: fallback) if shovel_method?(left, meth, opts[:core_rbs_provider])
|
|
1555
|
+
return nil unless %i[+ - * / % ** | & ^].include?(meth)
|
|
1556
|
+
|
|
1557
|
+
fallback_concrete_type(left, right, fallback) ||
|
|
1558
|
+
unify_types(left, right, fallback_type: fallback, nil_as_optional: true)
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1561
|
+
# @note module_function: defines #fallback_concrete_type (visibility: private)
|
|
1562
|
+
# @param [String, nil] left
|
|
1563
|
+
# @param [String, nil] right
|
|
1564
|
+
# @param [String] fallback
|
|
1565
|
+
# @return [String]
|
|
1566
|
+
def fallback_concrete_type(left, right, fallback)
|
|
1567
|
+
left_is_fallback = fallback_type?(left, fallback)
|
|
1568
|
+
right_is_fallback = fallback_type?(right, fallback)
|
|
1569
|
+
preferred = fallback_preferred_side(left, right, left_is_fallback, right_is_fallback)
|
|
1570
|
+
return preferred if preferred
|
|
1571
|
+
return fallback if left_is_fallback && right_is_fallback
|
|
1572
|
+
|
|
1573
|
+
nil
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
# @note module_function: defines #fallback_preferred_side (visibility: private)
|
|
1577
|
+
# @param [String, nil] left
|
|
1578
|
+
# @param [String, nil] right
|
|
1579
|
+
# @param [Boolean] left_is_fallback
|
|
1580
|
+
# @param [Boolean] right_is_fallback
|
|
1581
|
+
# @return [String, nil]
|
|
1582
|
+
def fallback_preferred_side(left, right, left_is_fallback, right_is_fallback)
|
|
1583
|
+
return right.to_s if left_is_fallback && !right_is_fallback && right
|
|
1584
|
+
return left.to_s if right_is_fallback && !left_is_fallback && left
|
|
1585
|
+
|
|
1586
|
+
nil
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
# @note module_function: defines #fallback_type? (visibility: private)
|
|
1590
|
+
# @param [String, nil] type
|
|
1591
|
+
# @param [String] fallback
|
|
1592
|
+
# @return [Boolean]
|
|
1593
|
+
def fallback_type?(type, fallback)
|
|
1594
|
+
type.nil? || fallback_alias?(type, fallback)
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
# @note module_function: defines #cleaned_recv_type (visibility: private)
|
|
1598
|
+
# @param [String, nil] raw
|
|
1599
|
+
# @return [String, nil]
|
|
1600
|
+
def cleaned_recv_type(raw) # rubocop:disable SortedMethodsByCall/Waterfall
|
|
1601
|
+
return nil unless raw && raw != FALLBACK_TYPE
|
|
1602
|
+
|
|
1603
|
+
str = raw.to_s.strip
|
|
1604
|
+
return nil if str.empty?
|
|
1605
|
+
|
|
1606
|
+
str = stripped_union_type(str) || str if str.include?(',')
|
|
1607
|
+
cleaned = str.delete_suffix('?').strip
|
|
1608
|
+
cleaned.empty? ? nil : cleaned
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
# @note module_function: defines #synthesize_shovel_type (visibility: private)
|
|
1612
|
+
# @param [String, nil] left
|
|
1613
|
+
# @param [String, nil] right
|
|
1614
|
+
# @param [String] fallback
|
|
1615
|
+
# @return [String]
|
|
1616
|
+
def synthesize_shovel_type(left, right, fallback:)
|
|
1617
|
+
l = left || fallback
|
|
1618
|
+
r = right || fallback
|
|
1619
|
+
base = l.split(/[<\[ ]/).first.to_s.strip.delete_suffix('?')
|
|
1620
|
+
return shovel_array_type(l, r, base, fallback) if %w[Array Set Enumerable Enumerator].include?(base)
|
|
1621
|
+
|
|
1622
|
+
l
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
# Whether left#meth is shovel (returns self) via RBS dynamically, not hardcoding :<<.
|
|
1626
|
+
#
|
|
1627
|
+
# @note module_function: defines #shovel_method? (visibility: private)
|
|
1628
|
+
# @param [String, nil] left left type
|
|
1629
|
+
# @param [Symbol] meth method name
|
|
1630
|
+
# @param [Docscribe::Types::RBS::Provider?] provider optional RBS provider
|
|
1631
|
+
# @return [Boolean] true if shovel
|
|
1632
|
+
def shovel_method?(left, meth, provider)
|
|
1633
|
+
return false unless left && meth
|
|
1634
|
+
|
|
1635
|
+
base = left.split(/[<\[ ]/).first.to_s.strip.delete_suffix('?')
|
|
1636
|
+
return false if base.empty?
|
|
1637
|
+
return true if resolve_self_via_rbs?(base, meth, provider)
|
|
1638
|
+
|
|
1639
|
+
resolve_self_via_rbs?(base, meth, core_rbs_provider)
|
|
1640
|
+
end
|
|
1641
|
+
|
|
1642
|
+
# @note module_function: defines #resolve_self_via_rbs? (visibility: private)
|
|
1643
|
+
# @param [String] base
|
|
1644
|
+
# @param [Symbol] meth
|
|
1645
|
+
# @param [Docscribe::Types::RBS::Provider?] provider
|
|
1646
|
+
# @return [Boolean]
|
|
1647
|
+
def resolve_self_via_rbs?(base, meth, provider)
|
|
1648
|
+
return false unless provider
|
|
1649
|
+
|
|
1650
|
+
resolve_rbs_return_type(base, meth, provider) == 'self'
|
|
1651
|
+
end
|
|
1652
|
+
|
|
1653
|
+
# Core RBS provider singleton for shovel/primitive checks (dynamic, not hardcoding).
|
|
1654
|
+
#
|
|
1655
|
+
# @note module_function: defines #core_rbs_provider (visibility: private)
|
|
1656
|
+
# @raise [LoadError]
|
|
1657
|
+
# @raise [StandardError]
|
|
1658
|
+
# @return [Docscribe::Types::RBS::Provider, nil]
|
|
1659
|
+
def core_rbs_provider
|
|
1660
|
+
@core_rbs_provider ||= begin
|
|
1661
|
+
require_relative '../types/rbs/provider'
|
|
1662
|
+
Docscribe::Types::RBS::Provider.new(sig_dirs: ['sig'], collection_dirs: [])
|
|
1663
|
+
rescue LoadError, StandardError
|
|
1664
|
+
nil
|
|
1665
|
+
end
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
# @note module_function: defines #shovel_array_type (visibility: private)
|
|
1669
|
+
# @param [String] left_str
|
|
1670
|
+
# @param [String] right_str
|
|
1671
|
+
# @param [String] base
|
|
1672
|
+
# @param [String] fallback
|
|
1673
|
+
# @return [String]
|
|
1674
|
+
def shovel_array_type(left_str, right_str, base, fallback)
|
|
1675
|
+
return left_str if shovel_left_generic?(left_str)
|
|
1676
|
+
return left_str if shovel_right_invalid?(right_str, fallback)
|
|
1677
|
+
|
|
1678
|
+
cleaned = right_str.to_s.strip.delete_suffix('?').strip
|
|
1679
|
+
return left_str if cleaned == 'nil' || cleaned.empty? || cleaned == FALLBACK_TYPE
|
|
1680
|
+
|
|
1681
|
+
"#{base}<#{cleaned}>"
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1684
|
+
# @note module_function: defines #shovel_left_generic? (visibility: private)
|
|
1685
|
+
# @param [String] str
|
|
1686
|
+
# @return [Boolean]
|
|
1687
|
+
def shovel_left_generic?(str)
|
|
1688
|
+
str.include?('<') || str.include?('[')
|
|
1689
|
+
end
|
|
1690
|
+
|
|
1691
|
+
# @note module_function: defines #shovel_right_invalid? (visibility: private)
|
|
1692
|
+
# @param [String] str
|
|
1693
|
+
# @param [String] fallback
|
|
1694
|
+
# @return [Boolean]
|
|
1695
|
+
def shovel_right_invalid?(str, fallback)
|
|
1696
|
+
str == fallback || %w[Object untyped].include?(str)
|
|
1697
|
+
end
|
|
1698
|
+
|
|
743
1699
|
# Map receiver AST node to RBS type name.
|
|
744
1700
|
#
|
|
745
1701
|
# @note module_function: defines #receiver_rbs_type_name (visibility: private)
|
|
746
1702
|
# @param [Parser::AST::Node, nil] recv the receiver AST node
|
|
747
|
-
# @param [
|
|
748
|
-
# @param [Hash<
|
|
749
|
-
# @param [Hash<String, String
|
|
1703
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider core RBS type provider
|
|
1704
|
+
# @param [Hash<String, String>?] local_var_types inferred local variable types
|
|
1705
|
+
# @param [Hash<String, String>?] param_types parameter name-to-type map
|
|
750
1706
|
# @return [String, nil]
|
|
751
1707
|
def receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types)
|
|
752
1708
|
return unless recv
|
|
753
|
-
return LITERAL_RBS_TYPES[recv.type] if LITERAL_RBS_TYPES.key?(recv.type)
|
|
754
|
-
return lookup_lvar_type(recv.children.first, local_var_types, param_types) if %i[lvar ivar gvar
|
|
755
|
-
cvar].include?(recv.type)
|
|
756
|
-
return unless recv.type == :send
|
|
757
1709
|
|
|
1710
|
+
literal = receiver_literal_type(recv)
|
|
1711
|
+
return literal if literal
|
|
1712
|
+
return receiver_var_type(recv, local_var_types, param_types) if var_receiver?(recv)
|
|
1713
|
+
|
|
1714
|
+
receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1717
|
+
# @note module_function: defines #receiver_dispatch_type (visibility: private)
|
|
1718
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1719
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider
|
|
1720
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1721
|
+
# @param [Hash<String, String>?] param_types
|
|
1722
|
+
# @return [String, nil]
|
|
1723
|
+
def receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1724
|
+
return nil unless recv.is_a?(Parser::AST::Node)
|
|
1725
|
+
|
|
1726
|
+
case recv.type
|
|
1727
|
+
when :send, :csend then receiver_send_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1728
|
+
when :block then block_receiver_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1729
|
+
when :or, :and then receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1730
|
+
when :begin then receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1731
|
+
end
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
# @note module_function: defines #block_receiver_type (visibility: private)
|
|
1735
|
+
# @param [Parser::AST::Node] recv
|
|
1736
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider
|
|
1737
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1738
|
+
# @param [Hash<String, String>?] param_types
|
|
1739
|
+
# @return [String, nil]
|
|
1740
|
+
def block_receiver_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
758
1741
|
run_last_expr_type(recv, fallback_type: FALLBACK_TYPE, nil_as_optional: false,
|
|
759
|
-
core_rbs_provider: core_rbs_provider,
|
|
760
|
-
param_types: param_types
|
|
761
|
-
|
|
1742
|
+
core_rbs_provider: core_rbs_provider, local_var_types: local_var_types,
|
|
1743
|
+
param_types: param_types) ||
|
|
1744
|
+
receiver_send_type(recv.children[0], core_rbs_provider, local_var_types, param_types)
|
|
762
1745
|
end
|
|
763
1746
|
|
|
764
|
-
#
|
|
765
|
-
#
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
#
|
|
769
|
-
#
|
|
770
|
-
# @note module_function: defines #infer_from_compound_assign (visibility: private)
|
|
771
|
-
# @param [Parser::AST::Node] node the `:send` AST node
|
|
772
|
-
# @param [Object] opts additional keyword options forwarded to type inference
|
|
1747
|
+
# @note module_function: defines #receiver_begin_type (visibility: private)
|
|
1748
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1749
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider
|
|
1750
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1751
|
+
# @param [Hash<String, String>?] param_types
|
|
773
1752
|
# @return [String, nil]
|
|
774
|
-
def
|
|
775
|
-
return nil unless
|
|
1753
|
+
def receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1754
|
+
return nil unless recv.is_a?(Parser::AST::Node)
|
|
776
1755
|
|
|
777
|
-
|
|
778
|
-
return
|
|
1756
|
+
inner = recv&.children&.[](0)
|
|
1757
|
+
return unless inner && recv&.children&.size == 1
|
|
1758
|
+
|
|
1759
|
+
receiver_rbs_type_name(inner, core_rbs_provider, local_var_types, param_types)
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
# @note module_function: defines #receiver_or_and_type (visibility: private)
|
|
1763
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1764
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider
|
|
1765
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1766
|
+
# @param [Hash<String, String>?] param_types
|
|
1767
|
+
# @return [String, nil]
|
|
1768
|
+
def receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1769
|
+
return nil unless recv.is_a?(Parser::AST::Node)
|
|
1770
|
+
|
|
1771
|
+
left = receiver_rbs_type_name(recv&.children&.[](0), core_rbs_provider, local_var_types, param_types)
|
|
1772
|
+
right = receiver_rbs_type_name(recv&.children&.[](1), core_rbs_provider, local_var_types, param_types)
|
|
1773
|
+
left_clean = resolve_cleaned_type(left)
|
|
1774
|
+
right_clean = resolve_cleaned_type(right)
|
|
1775
|
+
receiver_or_and_preference(left_clean, right_clean, left, right)
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
# @note module_function: defines #resolve_cleaned_type (visibility: private)
|
|
1779
|
+
# @param [String, nil] type
|
|
1780
|
+
# @return [String, nil]
|
|
1781
|
+
def resolve_cleaned_type(type)
|
|
1782
|
+
return nil unless type
|
|
779
1783
|
|
|
780
|
-
|
|
781
|
-
|
|
1784
|
+
cleaned_recv_type(type) || type
|
|
1785
|
+
end
|
|
782
1786
|
|
|
783
|
-
|
|
784
|
-
|
|
1787
|
+
# @note module_function: defines #receiver_or_and_preference (visibility: private)
|
|
1788
|
+
# @param [String, nil] left_clean
|
|
1789
|
+
# @param [String, nil] right_clean
|
|
1790
|
+
# @param [String, nil] left
|
|
1791
|
+
# @param [String, nil] right
|
|
1792
|
+
# @return [String, nil]
|
|
1793
|
+
def receiver_or_and_preference(left_clean, right_clean, left, right)
|
|
1794
|
+
preferred = single_clean_preference(left_clean, right_clean)
|
|
1795
|
+
return preferred if preferred
|
|
1796
|
+
return left_clean if both_clean_equal?(left_clean, right_clean)
|
|
785
1797
|
|
|
786
|
-
|
|
787
|
-
|
|
1798
|
+
left_clean || right_clean || left || right
|
|
1799
|
+
end
|
|
1800
|
+
|
|
1801
|
+
# @note module_function: defines #single_clean_preference (visibility: private)
|
|
1802
|
+
# @param [String, nil] left_clean
|
|
1803
|
+
# @param [String, nil] right_clean
|
|
1804
|
+
# @return [String, nil]
|
|
1805
|
+
def single_clean_preference(left_clean, right_clean)
|
|
1806
|
+
return left_clean if left_clean && !right_clean
|
|
1807
|
+
return right_clean if right_clean && !left_clean
|
|
1808
|
+
|
|
1809
|
+
nil
|
|
1810
|
+
end
|
|
1811
|
+
|
|
1812
|
+
# @note module_function: defines #both_clean_equal? (visibility: private)
|
|
1813
|
+
# @param [String, nil] left_clean
|
|
1814
|
+
# @param [String, nil] right_clean
|
|
1815
|
+
# @return [Boolean]
|
|
1816
|
+
def both_clean_equal?(left_clean, right_clean)
|
|
1817
|
+
left_clean && right_clean && left_clean == right_clean
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1820
|
+
# @note module_function: defines #receiver_literal_type (visibility: private)
|
|
1821
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1822
|
+
# @return [String, nil]
|
|
1823
|
+
def receiver_literal_type(recv)
|
|
1824
|
+
return nil unless recv.is_a?(Parser::AST::Node)
|
|
1825
|
+
|
|
1826
|
+
LITERAL_RBS_TYPES[recv&.type] if LITERAL_RBS_TYPES.key?(recv&.type)
|
|
1827
|
+
end
|
|
1828
|
+
|
|
1829
|
+
# @note module_function: defines #var_receiver? (visibility: private)
|
|
1830
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1831
|
+
# @return [Boolean]
|
|
1832
|
+
def var_receiver?(recv)
|
|
1833
|
+
return false unless recv.is_a?(Parser::AST::Node)
|
|
1834
|
+
|
|
1835
|
+
%i[lvar ivar gvar cvar].include?(recv&.type)
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
# @note module_function: defines #receiver_var_type (visibility: private)
|
|
1839
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1840
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1841
|
+
# @param [Hash<String, String>?] param_types
|
|
1842
|
+
# @return [String, nil]
|
|
1843
|
+
def receiver_var_type(recv, local_var_types, param_types)
|
|
1844
|
+
raw = lookup_lvar_type(recv.children.first, local_var_types, param_types)
|
|
1845
|
+
return nil unless raw
|
|
1846
|
+
|
|
1847
|
+
cleaned = raw.include?(',') ? stripped_union_type(raw) : raw
|
|
1848
|
+
cleaned = cleaned.to_s.strip.delete_suffix('?').strip
|
|
1849
|
+
return nil if cleaned.empty? || cleaned == 'FALLBACK_TYPE'
|
|
1850
|
+
|
|
1851
|
+
cleaned
|
|
1852
|
+
end
|
|
1853
|
+
|
|
1854
|
+
# @note module_function: defines #stripped_union_type (visibility: private)
|
|
1855
|
+
# @param [String, nil] raw
|
|
1856
|
+
# @return [String, nil]
|
|
1857
|
+
def stripped_union_type(raw)
|
|
1858
|
+
parts = split_top_level_commas(raw).map { |p| p.strip.delete_suffix('?').strip }
|
|
1859
|
+
non_nil = parts.reject { |p| %w[nil FALLBACK_TYPE].include?(p) }
|
|
1860
|
+
(non_nil.first || parts.first).to_s.strip.delete_suffix('?').strip
|
|
1861
|
+
end
|
|
1862
|
+
|
|
1863
|
+
# Split a type string by top-level commas (outside any < > [ ] ( ) nesting).
|
|
1864
|
+
#
|
|
1865
|
+
# Used to distinguish union types (`String, nil`) from generic commas (`Hash<Integer, String>`).
|
|
1866
|
+
#
|
|
1867
|
+
# @note module_function: defines #split_top_level_commas (visibility: private)
|
|
1868
|
+
# @param [String] str the type string to split
|
|
1869
|
+
# @return [Array<String>]
|
|
1870
|
+
def split_top_level_commas(str)
|
|
1871
|
+
state = { parts: [], cur: +'', da: 0, db: 0, dp: 0 } #: Hash[Symbol, untyped]
|
|
1872
|
+
str.each_char { |chr| split_process_char(chr, state, strip: false) }
|
|
1873
|
+
state[:parts] << state[:cur] unless state[:cur].empty?
|
|
1874
|
+
state[:parts]
|
|
1875
|
+
end
|
|
1876
|
+
|
|
1877
|
+
# @note module_function: defines #receiver_send_type (visibility: private)
|
|
1878
|
+
# @param [Parser::AST::Node, nil] recv
|
|
1879
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider
|
|
1880
|
+
# @param [Hash<String, String>?] local_var_types
|
|
1881
|
+
# @param [Hash<String, String>?] param_types
|
|
1882
|
+
# @return [String, nil]
|
|
1883
|
+
def receiver_send_type(recv, core_rbs_provider, local_var_types, param_types)
|
|
1884
|
+
run_last_expr_type(recv, fallback_type: FALLBACK_TYPE, nil_as_optional: false,
|
|
1885
|
+
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
1886
|
+
local_var_types: local_var_types)
|
|
788
1887
|
end
|
|
789
1888
|
|
|
790
1889
|
# Safely get a type string from a literal node, returning nil if the node
|
|
@@ -805,9 +1904,9 @@ module Docscribe
|
|
|
805
1904
|
# @note module_function: defines #resolve_lvar_rbs (visibility: private)
|
|
806
1905
|
# @param [Parser::AST::Node?] recv the receiver node of the send
|
|
807
1906
|
# @param [Symbol] meth the method name being called
|
|
808
|
-
# @param [
|
|
809
|
-
# @param [Hash<
|
|
810
|
-
# @param [Hash<String, String
|
|
1907
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider core RBS type lookup provider
|
|
1908
|
+
# @param [Hash<String, String>?] local_var_types pre-built local variable types map
|
|
1909
|
+
# @param [Hash<String, String>?] param_types parameter name -> type map for lvar resolution
|
|
811
1910
|
# @return [String, nil]
|
|
812
1911
|
def resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
813
1912
|
lvar_name = recv&.children&.first
|
|
@@ -821,12 +1920,17 @@ module Docscribe
|
|
|
821
1920
|
# Look up a local variable's inferred type from local or parameter type maps.
|
|
822
1921
|
#
|
|
823
1922
|
# @note module_function: defines #lookup_lvar_type (visibility: private)
|
|
824
|
-
# @param [
|
|
825
|
-
# @param [Hash<
|
|
826
|
-
# @param [Hash<String, String
|
|
1923
|
+
# @param [String, Symbol, nil] lvar_name the local variable name
|
|
1924
|
+
# @param [Hash<String, String>?] local_var_types inferred local variable type map
|
|
1925
|
+
# @param [Hash<String, String>?] param_types parameter name to type map
|
|
827
1926
|
# @return [String, nil]
|
|
828
1927
|
def lookup_lvar_type(lvar_name, local_var_types, param_types)
|
|
829
|
-
|
|
1928
|
+
if local_var_types&.key?(lvar_name.to_s)
|
|
1929
|
+
val = local_var_types[lvar_name.to_s]
|
|
1930
|
+
return nil if val == FALLBACK_TYPE
|
|
1931
|
+
|
|
1932
|
+
return val
|
|
1933
|
+
end
|
|
830
1934
|
return param_types[lvar_name.to_s] if param_types&.key?(lvar_name.to_s)
|
|
831
1935
|
|
|
832
1936
|
nil
|
|
@@ -837,9 +1941,9 @@ module Docscribe
|
|
|
837
1941
|
# @note module_function: defines #resolve_chained_send_rbs (visibility: private)
|
|
838
1942
|
# @param [Parser::AST::Node?] recv the receiver node of the send
|
|
839
1943
|
# @param [Symbol] meth the method name being called
|
|
840
|
-
# @param [
|
|
841
|
-
# @param [Hash<
|
|
842
|
-
# @param [Hash<String, String
|
|
1944
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider core RBS type lookup provider
|
|
1945
|
+
# @param [Hash<String, String>?] local_var_types pre-built local variable types map
|
|
1946
|
+
# @param [Hash<String, String>?] param_types parameter name -> type map for lvar resolution
|
|
843
1947
|
# @return [String, nil]
|
|
844
1948
|
def resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
845
1949
|
inner_type = run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
|
|
@@ -863,7 +1967,7 @@ module Docscribe
|
|
|
863
1967
|
#
|
|
864
1968
|
# @note module_function: defines #last_expr_type (visibility: private)
|
|
865
1969
|
# @param [Parser::AST::Node, nil] node expression node
|
|
866
|
-
# @param [
|
|
1970
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
867
1971
|
# @return [String, nil]
|
|
868
1972
|
def last_expr_type(node, **opts)
|
|
869
1973
|
run_last_expr_type(node, **opts)
|
|
@@ -873,7 +1977,7 @@ module Docscribe
|
|
|
873
1977
|
#
|
|
874
1978
|
# @note module_function: defines #run_last_expr_type (visibility: private)
|
|
875
1979
|
# @param [Parser::AST::Node, nil] node the `:return` AST node
|
|
876
|
-
# @param [
|
|
1980
|
+
# @param [Hash] opts options passed through as keyword args
|
|
877
1981
|
# @return [String, nil]
|
|
878
1982
|
def run_last_expr_type(node, **opts)
|
|
879
1983
|
return unless node
|
|
@@ -891,18 +1995,181 @@ module Docscribe
|
|
|
891
1995
|
#
|
|
892
1996
|
# @note module_function: defines #handle_return_node (visibility: private)
|
|
893
1997
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
894
|
-
# @param [
|
|
1998
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
895
1999
|
# @return [String, nil]
|
|
896
2000
|
def handle_return_node(node, **opts)
|
|
897
2001
|
Literals.type_from_literal(node.children.first, fallback_type: opts[:fallback_type])
|
|
898
2002
|
end
|
|
899
2003
|
|
|
2004
|
+
# @note module_function: defines #handle_const_node (visibility: private)
|
|
2005
|
+
# @param [Parser::AST::Node] node the `:const` AST node
|
|
2006
|
+
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
2007
|
+
# @return [String, nil]
|
|
2008
|
+
def handle_const_node(node, **opts)
|
|
2009
|
+
fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s #: String
|
|
2010
|
+
const_name = node.children.last.to_s #: String
|
|
2011
|
+
resolved = Literals.type_from_literal(node, fallback_type: fallback)
|
|
2012
|
+
return fallback if fallback_alias?(resolved, fallback)
|
|
2013
|
+
return fallback if fallback_alias?(const_name, fallback)
|
|
2014
|
+
|
|
2015
|
+
resolved
|
|
2016
|
+
end
|
|
2017
|
+
|
|
2018
|
+
# Resolve a const node to the YARD type of its runtime value.
|
|
2019
|
+
#
|
|
2020
|
+
# Looks the constant up in this process through the analyzed lexical
|
|
2021
|
+
# scope (innermost container outward, then top level) and maps its
|
|
2022
|
+
# value's class to a type name. Only constants actually present here
|
|
2023
|
+
# resolve — user code is never loaded, so unknown names safely fall
|
|
2024
|
+
# through to the fallback path. Classes and modules are skipped so
|
|
2025
|
+
# references like `String` keep name-based inference.
|
|
2026
|
+
#
|
|
2027
|
+
# @note module_function: defines #resolve_const_value_type (visibility: private)
|
|
2028
|
+
# @param [Parser::AST::Node] node the `:const` node
|
|
2029
|
+
# @param [String, nil] container lexical container (e.g. "Foo::Bar")
|
|
2030
|
+
# @return [String, nil] YARD type of the value, or nil when unresolvable
|
|
2031
|
+
def resolve_const_value_type(node, container)
|
|
2032
|
+
segments, absolute = const_path_segments(node)
|
|
2033
|
+
return nil if segments.empty?
|
|
2034
|
+
|
|
2035
|
+
*scope_parts, const_name = segments
|
|
2036
|
+
const_owner_paths(container, scope_parts, absolute).each do |owner_path|
|
|
2037
|
+
found, value = runtime_const_lookup(owner_path, const_name)
|
|
2038
|
+
next unless found
|
|
2039
|
+
|
|
2040
|
+
type = yard_type_for_const_value(value)
|
|
2041
|
+
return type if type
|
|
2042
|
+
end
|
|
2043
|
+
nil
|
|
2044
|
+
end
|
|
2045
|
+
|
|
2046
|
+
# Split a const node into static path segments and absoluteness.
|
|
2047
|
+
#
|
|
2048
|
+
# @note module_function: defines #const_path_segments (visibility: private)
|
|
2049
|
+
# @param [Parser::AST::Node] node the `:const` node
|
|
2050
|
+
# @return [(Array<String>, Boolean), nil] segments with last element
|
|
2051
|
+
# being the constant name plus absolute flag, or nil when dynamic
|
|
2052
|
+
def const_path_segments(node)
|
|
2053
|
+
parts = [] #: Array[String]
|
|
2054
|
+
current = node
|
|
2055
|
+
while current.is_a?(Parser::AST::Node) && current.type == :const
|
|
2056
|
+
parts.unshift(current.children[1].to_s)
|
|
2057
|
+
current = current.children[0]
|
|
2058
|
+
end
|
|
2059
|
+
return nil if current && !(current.is_a?(Parser::AST::Node) && current.type == :cbase)
|
|
2060
|
+
|
|
2061
|
+
absolute = !current.nil?
|
|
2062
|
+
[parts, absolute]
|
|
2063
|
+
end
|
|
2064
|
+
|
|
2065
|
+
# Candidate owner paths for a constant lookup, innermost first.
|
|
2066
|
+
#
|
|
2067
|
+
# Flat strings keep generic inference (and RubyMine) happy — nested
|
|
2068
|
+
# arrays lose a level (`first(n)` degrades to Elem).
|
|
2069
|
+
#
|
|
2070
|
+
# @note module_function: defines #const_owner_paths (visibility: private)
|
|
2071
|
+
# @param [String, nil] container lexical container (e.g. "Foo::Bar")
|
|
2072
|
+
# @param [Array<String>] scope_parts static scope segments (may be empty)
|
|
2073
|
+
# @param [Boolean] absolute whether the reference starts with `::`
|
|
2074
|
+
# @return [Array<String>] owner paths, "" means top level
|
|
2075
|
+
def const_owner_paths(container, scope_parts, absolute)
|
|
2076
|
+
return [scope_parts.join('::')] if absolute
|
|
2077
|
+
|
|
2078
|
+
segments = container.to_s.split('::').grep(/\A[A-Z]\w*\z/)
|
|
2079
|
+
# NOTE: `take` (not `first`) keeps RBS generic inference precise —
|
|
2080
|
+
# `first(n)` loses one nesting level.
|
|
2081
|
+
paths = segments.length.downto(1).map { |n| (segments.take(n) + scope_parts).join('::') }
|
|
2082
|
+
paths << scope_parts.join('::')
|
|
2083
|
+
paths
|
|
2084
|
+
end
|
|
2085
|
+
|
|
2086
|
+
# Look up a constant in this process without side effects.
|
|
2087
|
+
#
|
|
2088
|
+
# @note module_function: defines #runtime_const_lookup (visibility: private)
|
|
2089
|
+
# @param [String] owner_path owner namespace path ("" is top level)
|
|
2090
|
+
# @param [String] const_name constant name to look up
|
|
2091
|
+
# @raise [StandardError]
|
|
2092
|
+
# @return [(Boolean, Object)] found flag with value (nil value is valid)
|
|
2093
|
+
# @return [Array] if StandardError
|
|
2094
|
+
def runtime_const_lookup(owner_path, const_name)
|
|
2095
|
+
owner = owner_path.empty? ? Object : safe_const_path(owner_path.split('::'))
|
|
2096
|
+
return [false, nil] unless owner.is_a?(Module)
|
|
2097
|
+
return [false, nil] unless owner.const_defined?(const_name, false)
|
|
2098
|
+
return [false, nil] if owner.autoload?(const_name)
|
|
2099
|
+
|
|
2100
|
+
[true, owner.const_get(const_name, false)]
|
|
2101
|
+
rescue StandardError
|
|
2102
|
+
[false, nil]
|
|
2103
|
+
end
|
|
2104
|
+
|
|
2105
|
+
# Resolve a namespace path in this process without side effects.
|
|
2106
|
+
#
|
|
2107
|
+
# @note module_function: defines #safe_const_path (visibility: private)
|
|
2108
|
+
# @param [Array<String>] parts namespace segments
|
|
2109
|
+
# @return [Module, nil] the namespace or nil when unresolvable
|
|
2110
|
+
def safe_const_path(parts)
|
|
2111
|
+
mod = Object #: Module
|
|
2112
|
+
parts.each do |name|
|
|
2113
|
+
fetched = safe_const_step(mod, name)
|
|
2114
|
+
return nil unless fetched
|
|
2115
|
+
|
|
2116
|
+
mod = fetched
|
|
2117
|
+
end
|
|
2118
|
+
mod
|
|
2119
|
+
end
|
|
2120
|
+
|
|
2121
|
+
# Resolve one namespace step without side effects.
|
|
2122
|
+
#
|
|
2123
|
+
# @note module_function: defines #safe_const_step (visibility: private)
|
|
2124
|
+
# @param [Module] mod current namespace
|
|
2125
|
+
# @param [String] name nested constant name
|
|
2126
|
+
# @raise [StandardError]
|
|
2127
|
+
# @return [Module, nil] nested namespace or nil when unresolvable
|
|
2128
|
+
# @return [nil] if StandardError
|
|
2129
|
+
def safe_const_step(mod, name)
|
|
2130
|
+
return nil unless mod.const_defined?(name, false) && !mod.autoload?(name)
|
|
2131
|
+
|
|
2132
|
+
fetched = mod.const_get(name, false)
|
|
2133
|
+
fetched if fetched.is_a?(Module)
|
|
2134
|
+
rescue StandardError
|
|
2135
|
+
nil
|
|
2136
|
+
end
|
|
2137
|
+
|
|
2138
|
+
# Map a constant's runtime value to a YARD type name.
|
|
2139
|
+
#
|
|
2140
|
+
# @note module_function: defines #yard_type_for_const_value (visibility: private)
|
|
2141
|
+
# @param [Object] value the constant's runtime value
|
|
2142
|
+
# @return [String, nil] YARD type or nil when not mappable
|
|
2143
|
+
def yard_type_for_const_value(value)
|
|
2144
|
+
return 'nil' if value.nil?
|
|
2145
|
+
return 'Boolean' if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
2146
|
+
return nil if value.is_a?(Module)
|
|
2147
|
+
|
|
2148
|
+
name = value.class.name
|
|
2149
|
+
return nil unless name.is_a?(String) && name.match?(/\A[A-Z][A-Za-z0-9_:]*\z/)
|
|
2150
|
+
|
|
2151
|
+
name
|
|
2152
|
+
end
|
|
2153
|
+
|
|
2154
|
+
# Whether a type string is the fallback alias (FALLBACK_TYPE or the configured fallback type).
|
|
2155
|
+
#
|
|
2156
|
+
# @note module_function: defines #fallback_alias? (visibility: private)
|
|
2157
|
+
# @param [String, nil] type_str the type string to check
|
|
2158
|
+
# @param [String] fallback_type the configured fallback type
|
|
2159
|
+
# @return [Boolean]
|
|
2160
|
+
def fallback_alias?(type_str, fallback_type)
|
|
2161
|
+
return false if type_str.nil?
|
|
2162
|
+
|
|
2163
|
+
s = type_str.to_s.strip.delete_suffix('?').strip
|
|
2164
|
+
s == fallback_type || s == 'FALLBACK_TYPE' || (fallback_type == 'Object' && s == 'untyped')
|
|
2165
|
+
end
|
|
2166
|
+
|
|
900
2167
|
# Resolve an RBS return type for a method call.
|
|
901
2168
|
#
|
|
902
2169
|
# @note module_function: defines #resolve_rbs_return_type (visibility: private)
|
|
903
2170
|
# @param [String] container_type class or module name
|
|
904
2171
|
# @param [String, Symbol] method_name method name
|
|
905
|
-
# @param [
|
|
2172
|
+
# @param [Docscribe::Types::RBS::Provider?] core_rbs_provider core RBS type lookup provider
|
|
906
2173
|
# @return [String] inferred return type
|
|
907
2174
|
def resolve_rbs_return_type(container_type, method_name, core_rbs_provider)
|
|
908
2175
|
return FALLBACK_TYPE unless core_rbs_provider
|
|
@@ -916,6 +2183,187 @@ module Docscribe
|
|
|
916
2183
|
sig&.return_type || FALLBACK_TYPE
|
|
917
2184
|
end
|
|
918
2185
|
|
|
2186
|
+
# Substitute `self` and generic type variables in an RBS return type with the concrete receiver type.
|
|
2187
|
+
#
|
|
2188
|
+
# Handles `Array#<<` (`self` -> `Array<Elem>`) and `Hash#[]` (`V` -> value type).
|
|
2189
|
+
# For `self` returns the concrete receiver type; for `K`/`V`/`Elem` substitutes from generic args.
|
|
2190
|
+
#
|
|
2191
|
+
# @note module_function: defines #substitute_rbs_type (visibility: private)
|
|
2192
|
+
# @param [String] rbs the raw RBS return type string
|
|
2193
|
+
# @param [String] recv_type the concrete receiver type string
|
|
2194
|
+
# @return [String]
|
|
2195
|
+
def substitute_rbs_type(rbs, recv_type)
|
|
2196
|
+
self_sub = substitute_self_type(rbs, recv_type)
|
|
2197
|
+
return self_sub if self_sub
|
|
2198
|
+
|
|
2199
|
+
inner = extract_generic_inner(recv_type)
|
|
2200
|
+
return rbs unless inner
|
|
2201
|
+
|
|
2202
|
+
args = split_generic_args(inner)
|
|
2203
|
+
return rbs if args.empty?
|
|
2204
|
+
|
|
2205
|
+
substitute_with_mapping(rbs, recv_type, args)
|
|
2206
|
+
end
|
|
2207
|
+
|
|
2208
|
+
# @note module_function: defines #substitute_self_type (visibility: private)
|
|
2209
|
+
# @param [String] rbs
|
|
2210
|
+
# @param [String] recv_type
|
|
2211
|
+
# @return [String, nil]
|
|
2212
|
+
def substitute_self_type(rbs, recv_type)
|
|
2213
|
+
return recv_type if rbs == 'self'
|
|
2214
|
+
return "#{recv_type}?" if rbs == 'self?'
|
|
2215
|
+
|
|
2216
|
+
nil
|
|
2217
|
+
end
|
|
2218
|
+
|
|
2219
|
+
# @note module_function: defines #substitute_with_mapping (visibility: private)
|
|
2220
|
+
# @param [String] rbs
|
|
2221
|
+
# @param [String] recv_type
|
|
2222
|
+
# @param [Array<String>] args
|
|
2223
|
+
# @return [String]
|
|
2224
|
+
def substitute_with_mapping(rbs, recv_type, args)
|
|
2225
|
+
mapping = build_generic_mapping(recv_type, args)
|
|
2226
|
+
return rbs if mapping.empty?
|
|
2227
|
+
|
|
2228
|
+
apply_generic_mapping(rbs, mapping, recv_type)
|
|
2229
|
+
end
|
|
2230
|
+
|
|
2231
|
+
# @note module_function: defines #build_generic_mapping (visibility: private)
|
|
2232
|
+
# @param [String] recv_type
|
|
2233
|
+
# @param [Array<String>] args
|
|
2234
|
+
# @return [Hash<String, String>]
|
|
2235
|
+
def build_generic_mapping(recv_type, args)
|
|
2236
|
+
base = recv_type.split(/[<\[ ]/).first.to_s.strip
|
|
2237
|
+
mapping = {} #: Hash[String, String]
|
|
2238
|
+
fill_mapping_for_base(mapping, base, args)
|
|
2239
|
+
mapping
|
|
2240
|
+
end
|
|
2241
|
+
|
|
2242
|
+
# @note module_function: defines #fill_mapping_for_base (visibility: private)
|
|
2243
|
+
# @param [Hash<String, String>] mapping
|
|
2244
|
+
# @param [String] base
|
|
2245
|
+
# @param [Array<String>] args
|
|
2246
|
+
# @return [void]
|
|
2247
|
+
def fill_mapping_for_base(mapping, base, args)
|
|
2248
|
+
case base
|
|
2249
|
+
when 'Hash' then fill_hash_mapping(mapping, args)
|
|
2250
|
+
when 'Array', 'Set', 'Enumerable', 'Enumerator' then fill_array_mapping(mapping, args)
|
|
2251
|
+
else fill_other_mapping(mapping, args)
|
|
2252
|
+
end
|
|
2253
|
+
end
|
|
2254
|
+
|
|
2255
|
+
# @note module_function: defines #fill_hash_mapping (visibility: private)
|
|
2256
|
+
# @param [Hash<String, String>] mapping
|
|
2257
|
+
# @param [Array<String>] args
|
|
2258
|
+
# @return [void]
|
|
2259
|
+
def fill_hash_mapping(mapping, args)
|
|
2260
|
+
mapping['K'] = args[0] if args[0]
|
|
2261
|
+
mapping['V'] = args[1] if args[1]
|
|
2262
|
+
end
|
|
2263
|
+
|
|
2264
|
+
# @note module_function: defines #fill_array_mapping (visibility: private)
|
|
2265
|
+
# @param [Hash<String, String>] mapping
|
|
2266
|
+
# @param [Array<String>] args
|
|
2267
|
+
# @return [void]
|
|
2268
|
+
def fill_array_mapping(mapping, args)
|
|
2269
|
+
%w[Elem T U E].each { |key| mapping[key] = args[0] if args[0] }
|
|
2270
|
+
end
|
|
2271
|
+
|
|
2272
|
+
# @note module_function: defines #fill_other_mapping (visibility: private)
|
|
2273
|
+
# @param [Hash<String, String>] mapping
|
|
2274
|
+
# @param [Array<String>] args
|
|
2275
|
+
# @return [void]
|
|
2276
|
+
def fill_other_mapping(mapping, args)
|
|
2277
|
+
%w[Elem T U].each { |key| mapping[key] = args[0] if args[0] }
|
|
2278
|
+
end
|
|
2279
|
+
|
|
2280
|
+
# @note module_function: defines #apply_generic_mapping (visibility: private)
|
|
2281
|
+
# @param [String] rbs
|
|
2282
|
+
# @param [Hash<String, String>] mapping
|
|
2283
|
+
# @param [String] recv_type
|
|
2284
|
+
# @return [String]
|
|
2285
|
+
def apply_generic_mapping(rbs, mapping, recv_type)
|
|
2286
|
+
stripped = rbs.delete_suffix('?').strip
|
|
2287
|
+
optional = rbs.end_with?('?')
|
|
2288
|
+
return optional ? "#{mapping[stripped]}?" : mapping[stripped] if mapping.key?(stripped)
|
|
2289
|
+
|
|
2290
|
+
new_rbs = rbs.dup
|
|
2291
|
+
mapping.each { |var, val| new_rbs = new_rbs.gsub(/\b#{Regexp.escape(var)}\b/, val) }
|
|
2292
|
+
new_rbs.include?('self') ? new_rbs.gsub(/\bself\b/, recv_type) : new_rbs
|
|
2293
|
+
end
|
|
2294
|
+
|
|
2295
|
+
# Extract the inner generic args string from a receiver type like `Array<String>` or `Hash<Integer, String>`.
|
|
2296
|
+
#
|
|
2297
|
+
# @note module_function: defines #extract_generic_inner (visibility: private)
|
|
2298
|
+
# @param [String] type the concrete type string
|
|
2299
|
+
# @return [String, nil]
|
|
2300
|
+
def extract_generic_inner(type)
|
|
2301
|
+
return unless type =~ /\A(?:Array|Hash|Set|Enumerable)[<\[](.*)[>\]]\z/m || type =~ /\A[^<\[\]]+[<\[](.*)[>\]]\z/m
|
|
2302
|
+
|
|
2303
|
+
Regexp.last_match(1)
|
|
2304
|
+
end
|
|
2305
|
+
|
|
2306
|
+
# Split a generic inner string like `Integer, Array<(Integer, String)>` by top-level commas.
|
|
2307
|
+
#
|
|
2308
|
+
# Respects nesting of `< > [ ] ( )` so tuples are not split.
|
|
2309
|
+
#
|
|
2310
|
+
# @note module_function: defines #split_generic_args (visibility: private)
|
|
2311
|
+
# @param [String] inner the raw inner string
|
|
2312
|
+
# @return [Array<String>]
|
|
2313
|
+
def split_generic_args(inner)
|
|
2314
|
+
state = { parts: [], cur: +'', da: 0, db: 0, dp: 0 } #: Hash[Symbol, untyped]
|
|
2315
|
+
inner.each_char { |chr| split_process_char(chr, state, strip: true) }
|
|
2316
|
+
last = state[:cur].strip
|
|
2317
|
+
state[:parts] << last unless last.empty?
|
|
2318
|
+
state[:parts]
|
|
2319
|
+
end
|
|
2320
|
+
|
|
2321
|
+
# @note module_function: defines #split_process_char (visibility: private)
|
|
2322
|
+
# @param [String] chr single character
|
|
2323
|
+
# @param [Hash<Symbol, Object>] state mutable split state
|
|
2324
|
+
# @param [Boolean] strip whether to strip parts on comma
|
|
2325
|
+
# @return [void]
|
|
2326
|
+
def split_process_char(chr, state, strip:)
|
|
2327
|
+
case chr
|
|
2328
|
+
when '<', '>', '[', ']', '(', ')'
|
|
2329
|
+
split_handle_bracket(chr, state)
|
|
2330
|
+
when ','
|
|
2331
|
+
split_handle_comma(state, strip: strip)
|
|
2332
|
+
else
|
|
2333
|
+
state[:cur] << chr
|
|
2334
|
+
end
|
|
2335
|
+
end
|
|
2336
|
+
|
|
2337
|
+
# @note module_function: defines #split_handle_bracket (visibility: private)
|
|
2338
|
+
# @param [String] chr bracket character
|
|
2339
|
+
# @param [Hash<Symbol, Object>] state mutable split state
|
|
2340
|
+
# @return [void]
|
|
2341
|
+
def split_handle_bracket(chr, state)
|
|
2342
|
+
case chr
|
|
2343
|
+
when '<' then state[:da] += 1
|
|
2344
|
+
when '>' then state[:da] -= 1
|
|
2345
|
+
when '[' then state[:db] += 1
|
|
2346
|
+
when ']' then state[:db] -= 1
|
|
2347
|
+
when '(' then state[:dp] += 1
|
|
2348
|
+
when ')' then state[:dp] -= 1
|
|
2349
|
+
end
|
|
2350
|
+
state[:cur] << chr
|
|
2351
|
+
end
|
|
2352
|
+
|
|
2353
|
+
# @note module_function: defines #split_handle_comma (visibility: private)
|
|
2354
|
+
# @param [Hash<Symbol, Object>] state mutable split state
|
|
2355
|
+
# @param [Boolean] strip whether to strip
|
|
2356
|
+
# @return [void]
|
|
2357
|
+
def split_handle_comma(state, strip:)
|
|
2358
|
+
if state[:da].zero? && state[:db].zero? && state[:dp].zero?
|
|
2359
|
+
part = strip ? state[:cur].strip : state[:cur]
|
|
2360
|
+
state[:parts] << part
|
|
2361
|
+
state[:cur] = +''
|
|
2362
|
+
else
|
|
2363
|
+
state[:cur] << ','
|
|
2364
|
+
end
|
|
2365
|
+
end
|
|
2366
|
+
|
|
919
2367
|
# Unify two inferred types into a single type string.
|
|
920
2368
|
#
|
|
921
2369
|
# Rules:
|
|
@@ -930,13 +2378,24 @@ module Docscribe
|
|
|
930
2378
|
# @param [Boolean] nil_as_optional whether to render nil unions as optional types
|
|
931
2379
|
# @return [String]
|
|
932
2380
|
def unify_types(type_a, type_b, fallback_type:, nil_as_optional:)
|
|
933
|
-
type_a
|
|
934
|
-
type_b
|
|
2381
|
+
type_a = coalesce_type(type_a, fallback_type)
|
|
2382
|
+
type_b = coalesce_type(type_b, fallback_type)
|
|
935
2383
|
return type_a if type_a == type_b
|
|
936
2384
|
|
|
937
2385
|
unify_nil_types(type_a, type_b, nil_as_optional: nil_as_optional)
|
|
938
2386
|
end
|
|
939
2387
|
|
|
2388
|
+
# @note module_function: defines #coalesce_type (visibility: private)
|
|
2389
|
+
# @param [String, nil] type
|
|
2390
|
+
# @param [String] fallback_type
|
|
2391
|
+
# @return [String]
|
|
2392
|
+
def coalesce_type(type, fallback_type)
|
|
2393
|
+
normalized = type || fallback_type
|
|
2394
|
+
normalized = fallback_type if normalized == 'FALLBACK_TYPE'
|
|
2395
|
+
normalized = 'Object' if normalized == 'untyped' && fallback_type == 'Object'
|
|
2396
|
+
normalized
|
|
2397
|
+
end
|
|
2398
|
+
|
|
940
2399
|
# Unify two types where one may be `nil`, producing optional or union type.
|
|
941
2400
|
#
|
|
942
2401
|
# @note module_function: defines #unify_nil_types (visibility: private)
|
|
@@ -947,6 +2406,8 @@ module Docscribe
|
|
|
947
2406
|
def unify_nil_types(type_a, type_b, nil_as_optional:)
|
|
948
2407
|
if type_a == 'nil' || type_b == 'nil'
|
|
949
2408
|
non_nil = (type_a == 'nil' ? type_b : type_a)
|
|
2409
|
+
return non_nil if non_nil.end_with?('?')
|
|
2410
|
+
|
|
950
2411
|
return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil"
|
|
951
2412
|
end
|
|
952
2413
|
|