docscribe 1.4.2 → 1.5.0
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 +465 -130
- data/lib/docscribe/cli/check_for_comments.rb +183 -0
- data/lib/docscribe/cli/config_builder.rb +107 -53
- data/lib/docscribe/cli/formatters/json.rb +294 -0
- data/lib/docscribe/cli/formatters/sarif.rb +235 -0
- data/lib/docscribe/cli/formatters/text.rb +208 -0
- data/lib/docscribe/cli/formatters.rb +26 -0
- data/lib/docscribe/cli/generate.rb +45 -45
- data/lib/docscribe/cli/init.rb +14 -6
- data/lib/docscribe/cli/options.rb +190 -88
- data/lib/docscribe/cli/rbs_gen.rb +529 -0
- data/lib/docscribe/cli/run.rb +210 -152
- data/lib/docscribe/cli/sigs.rb +366 -0
- data/lib/docscribe/cli/update_types.rb +103 -0
- data/lib/docscribe/cli.rb +21 -13
- data/lib/docscribe/config/defaults.rb +5 -1
- data/lib/docscribe/config/emit.rb +17 -0
- data/lib/docscribe/config/filtering.rb +18 -25
- data/lib/docscribe/config/loader.rb +15 -11
- data/lib/docscribe/config/plugin.rb +1 -1
- data/lib/docscribe/config/rbs.rb +41 -9
- data/lib/docscribe/config/sorbet.rb +9 -12
- data/lib/docscribe/config/sorting.rb +1 -1
- data/lib/docscribe/config/template.rb +9 -1
- data/lib/docscribe/config/utils.rb +11 -9
- data/lib/docscribe/config.rb +2 -4
- data/lib/docscribe/infer/ast_walk.rb +1 -1
- data/lib/docscribe/infer/literals.rb +6 -11
- data/lib/docscribe/infer/names.rb +2 -3
- data/lib/docscribe/infer/params.rb +15 -17
- data/lib/docscribe/infer/raises.rb +3 -5
- data/lib/docscribe/infer/returns.rb +542 -140
- data/lib/docscribe/infer.rb +22 -23
- data/lib/docscribe/inline_rewriter/collector.rb +159 -164
- data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
- data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
- data/lib/docscribe/inline_rewriter.rb +495 -492
- data/lib/docscribe/parsing.rb +29 -10
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
- data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
- data/lib/docscribe/plugin/context.rb +28 -18
- data/lib/docscribe/plugin/registry.rb +26 -27
- data/lib/docscribe/plugin/tag.rb +9 -14
- data/lib/docscribe/plugin.rb +17 -16
- data/lib/docscribe/types/provider_chain.rb +4 -2
- data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
- data/lib/docscribe/types/rbs/provider.rb +60 -44
- data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
- data/lib/docscribe/types/signature.rb +22 -42
- data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
- data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
- data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
- data/lib/docscribe/types/yard/formatter.rb +100 -0
- data/lib/docscribe/types/yard/parser.rb +240 -0
- data/lib/docscribe/types/yard/types.rb +52 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +33 -1
|
@@ -6,24 +6,16 @@ module Docscribe
|
|
|
6
6
|
module Returns
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
LAST_EXPR_TYPE_HANDLERS = {
|
|
10
|
-
begin: :handle_begin_node,
|
|
11
|
-
if: :handle_if_node,
|
|
12
|
-
case: :handle_case_node,
|
|
13
|
-
return: :handle_return_node,
|
|
14
|
-
block: :handle_block_node,
|
|
15
|
-
send: :handle_send_node
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
9
|
# Infer a return type from a full method definition source string.
|
|
19
10
|
#
|
|
20
11
|
# The source must parse to a `:def` or `:defs` node. If parsing fails or inference
|
|
21
12
|
# is uncertain, the fallback type is returned.
|
|
22
13
|
#
|
|
23
|
-
# @note module_function:
|
|
24
|
-
# @param [String
|
|
14
|
+
# @note module_function: defines #infer_return_type (visibility: private)
|
|
15
|
+
# @param [String?] method_source full method definition source
|
|
25
16
|
# @raise [Parser::SyntaxError]
|
|
26
|
-
# @return [String]
|
|
17
|
+
# @return [String] if Parser::SyntaxError
|
|
18
|
+
# @return [FALLBACK_TYPE] if Parser::SyntaxError
|
|
27
19
|
def infer_return_type(method_source)
|
|
28
20
|
return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty?
|
|
29
21
|
|
|
@@ -34,13 +26,13 @@ module Docscribe
|
|
|
34
26
|
local_var_types = build_local_variable_types(body)
|
|
35
27
|
run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
|
|
36
28
|
local_var_types: local_var_types) || FALLBACK_TYPE
|
|
37
|
-
rescue Parser::SyntaxError
|
|
29
|
+
rescue Parser::SyntaxError # steep:ignore
|
|
38
30
|
FALLBACK_TYPE
|
|
39
31
|
end
|
|
40
32
|
|
|
41
33
|
# Parse a Ruby source string into an AST using the Parser gem.
|
|
42
34
|
#
|
|
43
|
-
# @note module_function:
|
|
35
|
+
# @note module_function: defines #parse_method_source (visibility: private)
|
|
44
36
|
# @param [String] method_source the method definition source string to parse
|
|
45
37
|
# @return [Parser::AST::Node, nil]
|
|
46
38
|
def parse_method_source(method_source)
|
|
@@ -51,7 +43,7 @@ module Docscribe
|
|
|
51
43
|
|
|
52
44
|
# Infer a method's normal return type from an already parsed def/defs node.
|
|
53
45
|
#
|
|
54
|
-
# @note module_function:
|
|
46
|
+
# @note module_function: defines #infer_return_type_from_node (visibility: private)
|
|
55
47
|
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
56
48
|
# @return [String]
|
|
57
49
|
def infer_return_type_from_node(node)
|
|
@@ -69,33 +61,28 @@ module Docscribe
|
|
|
69
61
|
# - `:normal` => normal/happy-path return type
|
|
70
62
|
# - `:rescues` => array of `[exception_names, return_type]` pairs for rescue branches
|
|
71
63
|
#
|
|
72
|
-
# @note module_function:
|
|
64
|
+
# @note module_function: defines #returns_spec_from_node (visibility: private)
|
|
73
65
|
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
74
66
|
# @param [String] fallback_type type used when inference is uncertain
|
|
75
67
|
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
76
|
-
# @param [
|
|
77
|
-
# @param [
|
|
78
|
-
# @return [
|
|
68
|
+
# @param [Object?] core_rbs_provider core RBS type lookup provider
|
|
69
|
+
# @param [Hash<String, String>?] param_types parameter name -> type map
|
|
70
|
+
# @return [Object]
|
|
79
71
|
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil,
|
|
80
72
|
param_types: nil)
|
|
81
73
|
body = extract_def_body(node)
|
|
82
74
|
spec = { normal: FALLBACK_TYPE, rescues: [] } #: Hash[Symbol, untyped]
|
|
83
75
|
return spec unless body
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
fallback_type: fallback_type,
|
|
89
|
-
nil_as_optional: nil_as_optional,
|
|
90
|
-
core_rbs_provider: core_rbs_provider,
|
|
91
|
-
param_types: param_types)
|
|
92
|
-
|
|
77
|
+
types = build_local_variable_types(body, core_rbs_provider: core_rbs_provider, param_types: param_types)
|
|
78
|
+
populate_returns_spec(spec, body, types, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
79
|
+
core_rbs_provider: core_rbs_provider, param_types: param_types)
|
|
93
80
|
spec
|
|
94
81
|
end
|
|
95
82
|
|
|
96
83
|
# Extract the body child node from a `:def` or `:defs` AST node.
|
|
97
84
|
#
|
|
98
|
-
# @note module_function:
|
|
85
|
+
# @note module_function: defines #extract_def_body (visibility: private)
|
|
99
86
|
# @param [Parser::AST::Node] node a `:def` or `:defs` AST node
|
|
100
87
|
# @return [Parser::AST::Node, nil]
|
|
101
88
|
def extract_def_body(node)
|
|
@@ -107,12 +94,12 @@ module Docscribe
|
|
|
107
94
|
|
|
108
95
|
# Populate the spec hash with normal and/or rescue return types from the body.
|
|
109
96
|
#
|
|
110
|
-
# @note module_function:
|
|
111
|
-
# @param [
|
|
97
|
+
# @note module_function: defines #populate_returns_spec (visibility: private)
|
|
98
|
+
# @param [Object] spec the return spec hash to populate
|
|
112
99
|
# @param [Parser::AST::Node] body the method body AST node
|
|
113
|
-
# @param [Hash, nil] local_var_types inferred local variable type map
|
|
114
|
-
# @param [
|
|
115
|
-
# @return [
|
|
100
|
+
# @param [Hash<Object, Object>, nil] local_var_types inferred local variable type map
|
|
101
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
102
|
+
# @return [Object]
|
|
116
103
|
def populate_returns_spec(spec, body, local_var_types, **opts)
|
|
117
104
|
if body.type == :rescue
|
|
118
105
|
process_rescue_body(spec, body, **opts)
|
|
@@ -123,9 +110,9 @@ module Docscribe
|
|
|
123
110
|
|
|
124
111
|
# Infer the normal (non-rescue) return type from a method body node.
|
|
125
112
|
#
|
|
126
|
-
# @note module_function:
|
|
113
|
+
# @note module_function: defines #infer_normal_return_type (visibility: private)
|
|
127
114
|
# @param [Parser::AST::Node] body the method body AST node
|
|
128
|
-
# @param [
|
|
115
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
129
116
|
# @return [String]
|
|
130
117
|
def infer_normal_return_type(body, **opts)
|
|
131
118
|
run_last_expr_type(body, **opts) || FALLBACK_TYPE
|
|
@@ -133,18 +120,16 @@ module Docscribe
|
|
|
133
120
|
|
|
134
121
|
# Process a :rescue body node and populate spec with normal + rescue return types.
|
|
135
122
|
#
|
|
136
|
-
# @note module_function:
|
|
137
|
-
# @param [
|
|
123
|
+
# @note module_function: defines #process_rescue_body (visibility: private)
|
|
124
|
+
# @param [Object] spec the return spec hash to populate
|
|
138
125
|
# @param [Parser::AST::Node] body the :rescue AST node
|
|
139
|
-
# @param [
|
|
140
|
-
# @
|
|
141
|
-
# @param [Object, nil] core_rbs_provider optional RBS provider for core type lookup
|
|
142
|
-
# @param [Hash, nil] param_types parameter name to type map
|
|
143
|
-
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
144
|
-
# @return [Hash]
|
|
126
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
127
|
+
# @return [Object]
|
|
145
128
|
def process_rescue_body(spec, body, **opts)
|
|
146
129
|
main_body = body.children[0]
|
|
147
|
-
local_var_types = build_local_variable_types(body
|
|
130
|
+
local_var_types = build_local_variable_types(body,
|
|
131
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
132
|
+
param_types: opts[:param_types])
|
|
148
133
|
rescue_opts = opts.merge(local_var_types: local_var_types)
|
|
149
134
|
spec[:normal] = run_last_expr_type(main_body, **rescue_opts) || FALLBACK_TYPE
|
|
150
135
|
process_rescue_branches(spec, body, **rescue_opts)
|
|
@@ -152,11 +137,11 @@ module Docscribe
|
|
|
152
137
|
|
|
153
138
|
# Extract return types from each :resbody child and append to spec[:rescues].
|
|
154
139
|
#
|
|
155
|
-
# @note module_function:
|
|
156
|
-
# @param [
|
|
140
|
+
# @note module_function: defines #process_rescue_branches (visibility: private)
|
|
141
|
+
# @param [Object] spec the return spec hash to populate
|
|
157
142
|
# @param [Parser::AST::Node] body the :rescue AST node
|
|
158
|
-
# @param [
|
|
159
|
-
# @return [Array] the list of rescue type entries
|
|
143
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
144
|
+
# @return [Array<Object>] the list of rescue type entries
|
|
160
145
|
def process_rescue_branches(spec, body, **opts)
|
|
161
146
|
body.children.each do |ch|
|
|
162
147
|
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
@@ -170,76 +155,229 @@ module Docscribe
|
|
|
170
155
|
|
|
171
156
|
# Build a map of local/global/ivar/constant assignments to inferred types.
|
|
172
157
|
#
|
|
173
|
-
# @note module_function:
|
|
158
|
+
# @note module_function: defines #build_local_variable_types (visibility: private)
|
|
174
159
|
# @param [Parser::AST::Node] node AST node to walk
|
|
175
|
-
# @
|
|
176
|
-
|
|
160
|
+
# @param [Object] opts additional keyword options forwarded to inference
|
|
161
|
+
# @return [Hash<String, String>, nil]
|
|
162
|
+
def build_local_variable_types(node, **opts)
|
|
177
163
|
types = {} #: Hash[String, String]
|
|
178
164
|
ASTWalk.walk(node) do |n|
|
|
179
|
-
collect_assignment_type(n, types)
|
|
165
|
+
collect_assignment_type(n, types, **opts)
|
|
180
166
|
end
|
|
181
167
|
types.empty? ? nil : types
|
|
182
168
|
end
|
|
183
169
|
|
|
184
170
|
# Infer the type of a single assignment node and store it in the types hash.
|
|
185
171
|
#
|
|
186
|
-
#
|
|
172
|
+
# Uses `run_last_expr_type` when `core_rbs_provider` is available to
|
|
173
|
+
# resolve send expressions (e.g., `x = 123 + 1` -> `Integer`).
|
|
174
|
+
# Falls back to `Literals.type_from_literal` for plain literals.
|
|
175
|
+
#
|
|
176
|
+
# @note module_function: defines #collect_assignment_type (visibility: private)
|
|
187
177
|
# @param [Parser::AST::Node] node an assignment AST node
|
|
188
|
-
# @param [Hash] types the accumulated local variable type map
|
|
178
|
+
# @param [Hash<String, String>] types the accumulated local variable type map
|
|
179
|
+
# @param [Object] opts additional keyword options forwarded to inference
|
|
189
180
|
# @return [void]
|
|
190
|
-
def collect_assignment_type(node, types)
|
|
181
|
+
def collect_assignment_type(node, types, **opts)
|
|
191
182
|
name, value = assignment_name_and_value(node)
|
|
192
183
|
return unless name && value
|
|
193
184
|
|
|
194
|
-
inferred =
|
|
185
|
+
inferred = if opts[:core_rbs_provider]
|
|
186
|
+
run_last_expr_type(value, **opts, fallback_type: FALLBACK_TYPE,
|
|
187
|
+
nil_as_optional: false, local_var_types: types)
|
|
188
|
+
else
|
|
189
|
+
Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE)
|
|
190
|
+
end
|
|
195
191
|
types[name] = inferred if inferred && inferred != FALLBACK_TYPE
|
|
196
192
|
end
|
|
197
193
|
|
|
198
194
|
# Extract the variable name and value expression from an assignment node.
|
|
199
195
|
#
|
|
200
|
-
# @note module_function:
|
|
201
|
-
# @param [Parser::AST::Node] node an assignment AST node (:lvasgn, :gvasgn, :ivasgn, :casgn)
|
|
202
|
-
# @return [
|
|
196
|
+
# @note module_function: defines #assignment_name_and_value (visibility: private)
|
|
197
|
+
# @param [Parser::AST::Node] node an assignment AST node (:lvasgn, :gvasgn, :ivasgn, :casgn, :op_asgn)
|
|
198
|
+
# @return [(String, nil, Parser::AST::Node, nil)]
|
|
203
199
|
def assignment_name_and_value(node)
|
|
204
200
|
case node.type
|
|
205
|
-
when :lvasgn, :gvasgn, :ivasgn
|
|
201
|
+
when :lvasgn, :gvasgn, :ivasgn, :cvasgn
|
|
206
202
|
[node.children[0].to_s, node.children[1]]
|
|
207
203
|
when :casgn
|
|
208
|
-
|
|
204
|
+
constant_name_and_value(node)
|
|
205
|
+
when :op_asgn
|
|
206
|
+
compound_name_and_value(node)
|
|
209
207
|
else
|
|
210
208
|
[nil, nil]
|
|
211
209
|
end
|
|
212
210
|
end
|
|
213
211
|
|
|
212
|
+
# Extract the name and value from a `:casgn` (constant assignment) node.
|
|
213
|
+
#
|
|
214
|
+
# @note module_function: defines #constant_name_and_value (visibility: private)
|
|
215
|
+
# @param [Parser::AST::Node] node the `:casgn` AST node
|
|
216
|
+
# @return [(String, nil, Parser::AST::Node, nil)]
|
|
217
|
+
def constant_name_and_value(node)
|
|
218
|
+
[node.children[0].to_s, node.children[2]]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Extract the name and value from an `:op_asgn` (compound assignment) node.
|
|
222
|
+
#
|
|
223
|
+
# @note module_function: defines #compound_name_and_value (visibility: private)
|
|
224
|
+
# @param [Parser::AST::Node] node the `:op_asgn` AST node
|
|
225
|
+
# @return [(String, nil, Parser::AST::Node, nil)]
|
|
226
|
+
def compound_name_and_value(node)
|
|
227
|
+
[node.children[0].children.first.to_s, node.children[2]]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Handle `:lvar` node for last_expr_type — look up the variable in local_var_types.
|
|
231
|
+
#
|
|
232
|
+
# @note module_function: defines #handle_lvar_node (visibility: private)
|
|
233
|
+
# @param [Parser::AST::Node] node the `:lvar` AST node
|
|
234
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
235
|
+
# @return [String, nil]
|
|
236
|
+
def handle_lvar_node(node, **opts)
|
|
237
|
+
name = node.children[0].to_s
|
|
238
|
+
opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Handle `:ivar` node for last_expr_type — look up instance variable in local_var_types.
|
|
242
|
+
#
|
|
243
|
+
# @note module_function: defines #handle_ivar_node (visibility: private)
|
|
244
|
+
# @param [Parser::AST::Node] node the `:ivar` AST node
|
|
245
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
246
|
+
# @return [String, nil]
|
|
247
|
+
def handle_ivar_node(node, **opts)
|
|
248
|
+
name = node.children[0].to_s
|
|
249
|
+
opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Handle `:gvar` node for last_expr_type — look up global variable in local_var_types.
|
|
253
|
+
#
|
|
254
|
+
# @note module_function: defines #handle_gvar_node (visibility: private)
|
|
255
|
+
# @param [Parser::AST::Node] node the `:gvar` AST node
|
|
256
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
257
|
+
# @return [String, nil]
|
|
258
|
+
def handle_gvar_node(node, **opts)
|
|
259
|
+
name = node.children[0].to_s
|
|
260
|
+
opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Handle `:cvar` node for last_expr_type — look up class variable in local_var_types.
|
|
264
|
+
#
|
|
265
|
+
# @note module_function: defines #handle_cvar_node (visibility: private)
|
|
266
|
+
# @param [Parser::AST::Node] node the `:cvar` AST node
|
|
267
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
268
|
+
# @return [String, nil]
|
|
269
|
+
def handle_cvar_node(node, **opts)
|
|
270
|
+
name = node.children[0].to_s
|
|
271
|
+
opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Handle `:lvasgn` node for last_expr_type — look up local var assignment in local_var_types.
|
|
275
|
+
#
|
|
276
|
+
# @note module_function: defines #handle_lvasgn_node (visibility: private)
|
|
277
|
+
# @param [Parser::AST::Node] node the `:lvasgn` AST node
|
|
278
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
279
|
+
# @return [String, nil]
|
|
280
|
+
def handle_lvasgn_node(node, **opts)
|
|
281
|
+
name = node.children[0].to_s
|
|
282
|
+
opts[:local_var_types]&.fetch(name, nil) ||
|
|
283
|
+
run_last_expr_type(node.children[1], **opts) ||
|
|
284
|
+
opts[:fallback_type]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Handle `:ivasgn` node for last_expr_type — look up ivar assignment in local_var_types.
|
|
288
|
+
#
|
|
289
|
+
# @note module_function: defines #handle_ivasgn_node (visibility: private)
|
|
290
|
+
# @param [Parser::AST::Node] node the `:ivasgn` AST node
|
|
291
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
292
|
+
# @return [String, nil]
|
|
293
|
+
def handle_ivasgn_node(node, **opts)
|
|
294
|
+
name = node.children[0].to_s
|
|
295
|
+
opts[:local_var_types]&.fetch(name, nil) ||
|
|
296
|
+
run_last_expr_type(node.children[1], **opts) ||
|
|
297
|
+
opts[:fallback_type]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Handle `:gvasgn` node for last_expr_type — look up global var assignment in local_var_types.
|
|
301
|
+
#
|
|
302
|
+
# @note module_function: defines #handle_gvasgn_node (visibility: private)
|
|
303
|
+
# @param [Parser::AST::Node] node the `:gvasgn` AST node
|
|
304
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
305
|
+
# @return [String, nil]
|
|
306
|
+
def handle_gvasgn_node(node, **opts)
|
|
307
|
+
name = node.children[0].to_s
|
|
308
|
+
opts[:local_var_types]&.fetch(name, nil) ||
|
|
309
|
+
run_last_expr_type(node.children[1], **opts) ||
|
|
310
|
+
opts[:fallback_type]
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Handle `:cvasgn` node for last_expr_type — look up class var assignment in local_var_types.
|
|
314
|
+
#
|
|
315
|
+
# @note module_function: defines #handle_cvasgn_node (visibility: private)
|
|
316
|
+
# @param [Parser::AST::Node] node the `:cvasgn` AST node
|
|
317
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
318
|
+
# @return [String, nil]
|
|
319
|
+
def handle_cvasgn_node(node, **opts)
|
|
320
|
+
name = node.children[0].to_s
|
|
321
|
+
opts[:local_var_types]&.fetch(name, nil) ||
|
|
322
|
+
run_last_expr_type(node.children[1], **opts) ||
|
|
323
|
+
opts[:fallback_type]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Handle `:op_asgn` node (compound assignment: `x += 1`, `@var -= 2`, etc.).
|
|
327
|
+
#
|
|
328
|
+
# Infers the result type from the operator and the right operand's type.
|
|
329
|
+
# Uses RBS to resolve when available (e.g., `Integer#+` -> `Integer`).
|
|
330
|
+
#
|
|
331
|
+
# @note module_function: defines #handle_op_asgn_node (visibility: private)
|
|
332
|
+
# @param [Parser::AST::Node] node the `:op_asgn` AST node
|
|
333
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
334
|
+
# @return [String, nil]
|
|
335
|
+
def handle_op_asgn_node(node, **opts)
|
|
336
|
+
meth = node.children[1]
|
|
337
|
+
return nil unless %i[+ - * / % ** << | & ^].include?(meth)
|
|
338
|
+
return nil unless opts[:core_rbs_provider]
|
|
339
|
+
|
|
340
|
+
arg = node.children[2]
|
|
341
|
+
arg_type = type_from_literal_safe(arg)
|
|
342
|
+
return nil unless arg_type
|
|
343
|
+
|
|
344
|
+
rbs = resolve_rbs_return_type(arg_type, meth, opts[:core_rbs_provider])
|
|
345
|
+
rbs unless rbs == FALLBACK_TYPE
|
|
346
|
+
end
|
|
347
|
+
|
|
214
348
|
# Handle `:begin` node for last_expr_type.
|
|
215
349
|
#
|
|
216
|
-
# @note module_function:
|
|
217
|
-
# @param [
|
|
218
|
-
# @param [
|
|
219
|
-
# @return [
|
|
350
|
+
# @note module_function: defines #handle_begin_node (visibility: private)
|
|
351
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
352
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
353
|
+
# @return [String, nil]
|
|
220
354
|
def handle_begin_node(node, **opts)
|
|
221
355
|
run_last_expr_type(node.children.last, **opts)
|
|
222
356
|
end
|
|
223
357
|
|
|
224
358
|
# Handle `:if` node for last_expr_type.
|
|
225
359
|
#
|
|
226
|
-
# @note module_function:
|
|
227
|
-
# @param [
|
|
228
|
-
# @param [
|
|
229
|
-
# @return [
|
|
360
|
+
# @note module_function: defines #handle_if_node (visibility: private)
|
|
361
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
362
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
363
|
+
# @return [String, nil]
|
|
230
364
|
def handle_if_node(node, **opts)
|
|
231
365
|
t = run_last_expr_type(node.children[1], **opts)
|
|
232
|
-
e =
|
|
366
|
+
e = if node.children[2]
|
|
367
|
+
run_last_expr_type(node.children[2], **opts)
|
|
368
|
+
else
|
|
369
|
+
'nil'
|
|
370
|
+
end
|
|
233
371
|
unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped',
|
|
234
372
|
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
235
373
|
end
|
|
236
374
|
|
|
237
375
|
# Handle `:case` node for last_expr_type.
|
|
238
376
|
#
|
|
239
|
-
# @note module_function:
|
|
240
|
-
# @param [
|
|
241
|
-
# @param [
|
|
242
|
-
# @return [
|
|
377
|
+
# @note module_function: defines #handle_case_node (visibility: private)
|
|
378
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
379
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
380
|
+
# @return [String, nil]
|
|
243
381
|
def handle_case_node(node, **opts)
|
|
244
382
|
branches = process_case_branches(node, **opts)
|
|
245
383
|
if branches.empty?
|
|
@@ -252,11 +390,204 @@ module Docscribe
|
|
|
252
390
|
end
|
|
253
391
|
end
|
|
254
392
|
|
|
393
|
+
# Handle `:or` node (`a || b`) for last_expr_type.
|
|
394
|
+
#
|
|
395
|
+
# The result type is the union of both sides, since either may be returned
|
|
396
|
+
# depending on the truthiness of the left operand.
|
|
397
|
+
#
|
|
398
|
+
# @note module_function: defines #handle_or_node (visibility: private)
|
|
399
|
+
# @param [Parser::AST::Node] node the `:or` AST node
|
|
400
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
401
|
+
# @return [String, nil]
|
|
402
|
+
def handle_or_node(node, **opts)
|
|
403
|
+
t = run_last_expr_type(node.children[0], **opts)
|
|
404
|
+
e = run_last_expr_type(node.children[1], **opts)
|
|
405
|
+
unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped',
|
|
406
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Handle `:and` node (`a && b`) for last_expr_type.
|
|
410
|
+
#
|
|
411
|
+
# The result type is the union of both sides, since either may be returned
|
|
412
|
+
# depending on the truthiness of the left operand.
|
|
413
|
+
#
|
|
414
|
+
# @note module_function: defines #handle_and_node (visibility: private)
|
|
415
|
+
# @param [Parser::AST::Node] node the `:and` AST node
|
|
416
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
417
|
+
# @return [String, nil]
|
|
418
|
+
def handle_and_node(node, **opts)
|
|
419
|
+
t = run_last_expr_type(node.children[0], **opts)
|
|
420
|
+
e = run_last_expr_type(node.children[1], **opts)
|
|
421
|
+
unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped',
|
|
422
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Handle `:kwbegin` node (`begin; expr; end`) for last_expr_type.
|
|
426
|
+
#
|
|
427
|
+
# Unwraps the explicit begin node and delegates to the inner expression,
|
|
428
|
+
# which may be a `:rescue` or `:ensure` node.
|
|
429
|
+
#
|
|
430
|
+
# @note module_function: defines #handle_kwbegin_node (visibility: private)
|
|
431
|
+
# @param [Parser::AST::Node] node the `:kwbegin` AST node
|
|
432
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
433
|
+
# @return [String, nil]
|
|
434
|
+
def handle_kwbegin_node(node, **opts)
|
|
435
|
+
run_last_expr_type(node.children.first, **opts)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Handle `:rescue` node for last_expr_type.
|
|
439
|
+
#
|
|
440
|
+
# Supports both inline rescue (`expr rescue default`) and block rescue
|
|
441
|
+
# (`begin; expr; rescue; e; end`).
|
|
442
|
+
#
|
|
443
|
+
# @note module_function: defines #handle_rescue_node (visibility: private)
|
|
444
|
+
# @param [Parser::AST::Node] node the `:rescue` AST node
|
|
445
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
446
|
+
# @return [String, nil]
|
|
447
|
+
def handle_rescue_node(node, **opts)
|
|
448
|
+
branches = collect_rescue_branches(node, **opts)
|
|
449
|
+
branches.reduce do |a, b|
|
|
450
|
+
unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped',
|
|
451
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Handle `:rescue` node for last_expr_type.
|
|
456
|
+
#
|
|
457
|
+
# Unifies the body type with all rescue handler types and the optional else clause.
|
|
458
|
+
# Collect all rescue branch return types from a `:rescue` AST node.
|
|
459
|
+
#
|
|
460
|
+
# @note module_function: defines #collect_rescue_branches (visibility: private)
|
|
461
|
+
# @param [Parser::AST::Node] node the `:rescue` AST node
|
|
462
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
463
|
+
# @return [Array<String, nil>]
|
|
464
|
+
def collect_rescue_branches(node, **opts)
|
|
465
|
+
branches = [run_last_expr_type(node.children[0], **opts)]
|
|
466
|
+
(node.children[1..] || []).each do |child|
|
|
467
|
+
if child.is_a?(Parser::AST::Node) && child.type == :resbody
|
|
468
|
+
handler = child.children[2]
|
|
469
|
+
branches << run_last_expr_type(handler, **opts) if handler
|
|
470
|
+
else
|
|
471
|
+
branches << run_last_expr_type(child, **opts)
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
branches
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Handle `:ensure` node (`begin; expr; ensure; cleanup; end`) for last_expr_type.
|
|
478
|
+
#
|
|
479
|
+
# The ensure clause's result is discarded by Ruby; only the body type is returned.
|
|
480
|
+
#
|
|
481
|
+
# @note module_function: defines #handle_ensure_node (visibility: private)
|
|
482
|
+
# @param [Parser::AST::Node] node the `:ensure` AST node
|
|
483
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
484
|
+
# @return [String, nil]
|
|
485
|
+
def handle_ensure_node(node, **opts)
|
|
486
|
+
run_last_expr_type(node.children[0], **opts)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Handle `:defined?` node (`defined?(expr)`) for last_expr_type.
|
|
490
|
+
#
|
|
491
|
+
# Returns `nil` if the expression is not defined, or a String description
|
|
492
|
+
# if it is defined. The union type is `String?`.
|
|
493
|
+
#
|
|
494
|
+
# @note module_function: defines #handle_defined_node (visibility: private)
|
|
495
|
+
# @param [Parser::AST::Node] _node the `:defined?` AST node
|
|
496
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
497
|
+
# @return [String, nil]
|
|
498
|
+
def handle_defined_node(_node, **opts)
|
|
499
|
+
nil_as_optional = opts.fetch(:nil_as_optional, true)
|
|
500
|
+
nil_as_optional ? 'String?' : 'String, nil'
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Handle `:zsuper` node (`super` with no arguments) for last_expr_type.
|
|
504
|
+
#
|
|
505
|
+
# Returns the super method's return type if resolvable via RBS, or the
|
|
506
|
+
# fallback type otherwise.
|
|
507
|
+
#
|
|
508
|
+
# @note module_function: defines #handle_zsuper_node (visibility: private)
|
|
509
|
+
# @param [Parser::AST::Node] _node the `:zsuper` AST node
|
|
510
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
511
|
+
# @return [String, nil]
|
|
512
|
+
def handle_zsuper_node(_node, **opts)
|
|
513
|
+
opts[:fallback_type]
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Handle `:super` node (`super(args)`) for last_expr_type.
|
|
517
|
+
#
|
|
518
|
+
# Returns the super method's return type if resolvable via RBS, or the
|
|
519
|
+
# fallback type otherwise.
|
|
520
|
+
#
|
|
521
|
+
# @note module_function: defines #handle_super_node (visibility: private)
|
|
522
|
+
# @param [Parser::AST::Node] _node the `:super` AST node
|
|
523
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
524
|
+
# @return [String, nil]
|
|
525
|
+
def handle_super_node(_node, **opts)
|
|
526
|
+
opts[:fallback_type]
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Handle `:yield` node (`yield` / `yield(args)`) for last_expr_type.
|
|
530
|
+
#
|
|
531
|
+
# Returns the block's return type if resolvable via RBS (`Proc#call`),
|
|
532
|
+
# or the fallback type otherwise.
|
|
533
|
+
#
|
|
534
|
+
# @note module_function: defines #handle_yield_node (visibility: private)
|
|
535
|
+
# @param [Parser::AST::Node] _node the `:yield` AST node
|
|
536
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
537
|
+
# @return [String, nil]
|
|
538
|
+
def handle_yield_node(_node, **opts)
|
|
539
|
+
opts[:fallback_type]
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Handle `:case_match` node (`case x; in pat; expr; end`) for last_expr_type.
|
|
543
|
+
#
|
|
544
|
+
# Similar to `:case` — unifies all `in_pattern` branch types and the optional else clause.
|
|
545
|
+
#
|
|
546
|
+
# @note module_function: defines #handle_case_match_node (visibility: private)
|
|
547
|
+
# @param [Parser::AST::Node] node the `:case_match` AST node
|
|
548
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
549
|
+
# @return [String, nil]
|
|
550
|
+
def handle_case_match_node(node, **opts)
|
|
551
|
+
branches = process_pattern_branches(node, **opts)
|
|
552
|
+
if branches.empty?
|
|
553
|
+
opts[:fallback_type]
|
|
554
|
+
else
|
|
555
|
+
branches.reduce do |a, b|
|
|
556
|
+
unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped',
|
|
557
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Handle `:in_pattern` node (pattern inside `case...in`) for last_expr_type.
|
|
563
|
+
#
|
|
564
|
+
# Extracts the body expression from the pattern and recurses.
|
|
565
|
+
#
|
|
566
|
+
# @note module_function: defines #handle_in_pattern_node (visibility: private)
|
|
567
|
+
# @param [Parser::AST::Node] node the `:in_pattern` AST node
|
|
568
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
569
|
+
# @return [String, nil]
|
|
570
|
+
def handle_in_pattern_node(node, **opts)
|
|
571
|
+
run_last_expr_type(node.children[2], **opts)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Extract inferred return types from all in_pattern branches of a :case_match expression.
|
|
575
|
+
#
|
|
576
|
+
# @note module_function: defines #process_pattern_branches (visibility: private)
|
|
577
|
+
# @param [Parser::AST::Node] node the :case_match AST node
|
|
578
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
579
|
+
# @return [Array<String>] list of inferred types from each branch
|
|
580
|
+
def process_pattern_branches(node, **opts)
|
|
581
|
+
(node.children[1..] || []).compact.filter_map do |child|
|
|
582
|
+
run_last_expr_type(child, **opts) if child.is_a?(Parser::AST::Node)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
255
586
|
# Extract inferred return types from all branches of a :case expression.
|
|
256
587
|
#
|
|
257
|
-
# @note module_function:
|
|
588
|
+
# @note module_function: defines #process_case_branches (visibility: private)
|
|
258
589
|
# @param [Parser::AST::Node] node the :case AST node
|
|
259
|
-
# @param [
|
|
590
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
260
591
|
# @return [Array<String>] list of inferred types from each branch
|
|
261
592
|
def process_case_branches(node, **opts)
|
|
262
593
|
(node.children[1..] || []).compact.flat_map do |child|
|
|
@@ -270,10 +601,10 @@ module Docscribe
|
|
|
270
601
|
|
|
271
602
|
# Handle `:block` node for last_expr_type.
|
|
272
603
|
#
|
|
273
|
-
# @note module_function:
|
|
274
|
-
# @param [
|
|
275
|
-
# @param [
|
|
276
|
-
# @return [
|
|
604
|
+
# @note module_function: defines #handle_block_node (visibility: private)
|
|
605
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
606
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
607
|
+
# @return [String, nil]
|
|
277
608
|
def handle_block_node(node, **opts)
|
|
278
609
|
send_node = node.children[0]
|
|
279
610
|
if send_node&.type == :send
|
|
@@ -289,10 +620,10 @@ module Docscribe
|
|
|
289
620
|
|
|
290
621
|
# Handle `:send` node for last_expr_type.
|
|
291
622
|
#
|
|
292
|
-
# @note module_function:
|
|
293
|
-
# @param [
|
|
294
|
-
# @param [
|
|
295
|
-
# @return [
|
|
623
|
+
# @note module_function: defines #handle_send_node (visibility: private)
|
|
624
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
625
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
626
|
+
# @return [String, nil]
|
|
296
627
|
def handle_send_node(node, **opts)
|
|
297
628
|
recv = node.children[0]
|
|
298
629
|
meth = node.children[1]
|
|
@@ -303,39 +634,118 @@ module Docscribe
|
|
|
303
634
|
return rbs_type if rbs_type
|
|
304
635
|
end
|
|
305
636
|
|
|
637
|
+
compound_type = infer_from_compound_assign(node, **opts)
|
|
638
|
+
return compound_type if compound_type
|
|
639
|
+
|
|
306
640
|
Literals.type_from_literal(node, fallback_type: opts[:fallback_type])
|
|
307
641
|
end
|
|
308
642
|
|
|
309
643
|
# Resolve RBS return type for a send node's receiver, if possible.
|
|
310
644
|
#
|
|
311
|
-
# Handles `:lvar
|
|
645
|
+
# Handles `:lvar`, chained `:send`, literal (`:int`, `:str`, etc.),
|
|
646
|
+
# and variable (`:ivar`, `:gvar`, `:cvar`) receivers.
|
|
312
647
|
#
|
|
313
|
-
# @note module_function:
|
|
648
|
+
# @note module_function: defines #resolve_rbs_for_send (visibility: private)
|
|
314
649
|
# @param [Parser::AST::Node, nil] recv the receiver node of the send
|
|
315
650
|
# @param [Symbol] meth the method name being called
|
|
316
651
|
# @param [Object, nil] core_rbs_provider optional RBS provider for core type lookup
|
|
317
|
-
# @param [Hash, nil] local_var_types inferred local variable type map
|
|
318
|
-
# @param [Hash, nil] param_types parameter name to type map
|
|
652
|
+
# @param [Hash<Object, Object>, nil] local_var_types inferred local variable type map
|
|
653
|
+
# @param [Hash<String, String>, nil] param_types parameter name to type map
|
|
319
654
|
# @return [String, nil] resolved type or nil if unresolvable
|
|
320
655
|
def resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
321
656
|
return nil unless core_rbs_provider
|
|
322
657
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
658
|
+
recv_type = receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types)
|
|
659
|
+
return nil unless recv_type
|
|
660
|
+
|
|
661
|
+
rbs = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
|
|
662
|
+
rbs unless rbs == FALLBACK_TYPE
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Map a receiver AST node to its RBS type name string.
|
|
666
|
+
#
|
|
667
|
+
# Supports local variables, method calls, literals, and instance/global/class variables.
|
|
668
|
+
#
|
|
669
|
+
# @note module_function: when included, also defines #receiver_rbs_type_name (instance visibility: private)
|
|
670
|
+
# @param [Parser::AST::Node, nil] recv the receiver node
|
|
671
|
+
# @param [Object, nil] core_rbs_provider core RBS provider
|
|
672
|
+
# @param [Hash<Object, Object>, nil] local_var_types inferred local variable types
|
|
673
|
+
# @param [Hash<String, String>, nil] param_types parameter name to type map
|
|
674
|
+
# @return [String, nil]
|
|
675
|
+
LITERAL_RBS_TYPES = {
|
|
676
|
+
int: 'Integer', str: 'String', sym: 'Symbol', true: 'Boolean',
|
|
677
|
+
false: 'Boolean', float: 'Float', array: 'Array', hash: 'Hash',
|
|
678
|
+
nil: 'NilClass'
|
|
679
|
+
}.freeze
|
|
680
|
+
|
|
681
|
+
# Map receiver AST node to RBS type name.
|
|
682
|
+
#
|
|
683
|
+
# @note module_function: defines #receiver_rbs_type_name (visibility: private)
|
|
684
|
+
# @param [Parser::AST::Node, nil] recv the receiver AST node
|
|
685
|
+
# @param [Object, nil] core_rbs_provider core RBS type provider
|
|
686
|
+
# @param [Hash<Object, Object>, nil] local_var_types inferred local variable types
|
|
687
|
+
# @param [Hash<String, String>, nil] param_types parameter name-to-type map
|
|
688
|
+
# @return [String, nil]
|
|
689
|
+
def receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types)
|
|
690
|
+
return unless recv
|
|
691
|
+
return LITERAL_RBS_TYPES[recv.type] if LITERAL_RBS_TYPES.key?(recv.type)
|
|
692
|
+
return lookup_lvar_type(recv.children.first, local_var_types, param_types) if %i[lvar ivar gvar
|
|
693
|
+
cvar].include?(recv.type)
|
|
694
|
+
return unless recv.type == :send
|
|
695
|
+
|
|
696
|
+
run_last_expr_type(recv, fallback_type: FALLBACK_TYPE, nil_as_optional: false,
|
|
697
|
+
core_rbs_provider: core_rbs_provider,
|
|
698
|
+
param_types: param_types,
|
|
699
|
+
local_var_types: local_var_types)
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Infer return type from a compound-assignment-like `:send` by reading the
|
|
703
|
+
# first literal argument's type — only fires when `core_rbs_provider` is
|
|
704
|
+
# present and the argument's RBS return type can be resolved.
|
|
705
|
+
#
|
|
706
|
+
# Enables `@var += 123` -> `Integer` (via `Integer#+`) and similar patterns.
|
|
707
|
+
#
|
|
708
|
+
# @note module_function: defines #infer_from_compound_assign (visibility: private)
|
|
709
|
+
# @param [Parser::AST::Node] node the `:send` AST node
|
|
710
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
711
|
+
# @return [String, nil]
|
|
712
|
+
def infer_from_compound_assign(node, **opts)
|
|
713
|
+
return nil unless opts[:core_rbs_provider]
|
|
714
|
+
|
|
715
|
+
meth = node.children[1]
|
|
716
|
+
return nil unless %i[+ - * / % ** << | & ^].include?(meth)
|
|
717
|
+
|
|
718
|
+
first_arg = node.children[2]
|
|
719
|
+
return nil unless first_arg
|
|
720
|
+
|
|
721
|
+
arg_type = type_from_literal_safe(first_arg)
|
|
722
|
+
return nil unless arg_type
|
|
723
|
+
|
|
724
|
+
rbs = resolve_rbs_return_type(arg_type, meth, opts[:core_rbs_provider])
|
|
725
|
+
rbs unless rbs == FALLBACK_TYPE
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Safely get a type string from a literal node, returning nil if the node
|
|
729
|
+
# is not a literal or yields no type.
|
|
730
|
+
#
|
|
731
|
+
# @note module_function: defines #type_from_literal_safe (visibility: private)
|
|
732
|
+
# @param [Parser::AST::Node, nil] node literal AST node
|
|
733
|
+
# @return [String, nil]
|
|
734
|
+
def type_from_literal_safe(node)
|
|
735
|
+
return nil unless node
|
|
736
|
+
|
|
737
|
+
t = Literals.type_from_literal(node, fallback_type: FALLBACK_TYPE)
|
|
738
|
+
t unless t == FALLBACK_TYPE
|
|
328
739
|
end
|
|
329
740
|
|
|
330
741
|
# Resolve RBS return type for an `:lvar` receiver.
|
|
331
742
|
#
|
|
332
|
-
# @note module_function:
|
|
333
|
-
# @
|
|
334
|
-
# @param [
|
|
335
|
-
# @param [Object]
|
|
336
|
-
# @param [Object]
|
|
337
|
-
# @param [
|
|
338
|
-
# @param [Object] param_types
|
|
743
|
+
# @note module_function: defines #resolve_lvar_rbs (visibility: private)
|
|
744
|
+
# @param [Parser::AST::Node?] recv the receiver node of the send
|
|
745
|
+
# @param [Symbol] meth the method name being called
|
|
746
|
+
# @param [Object, nil] core_rbs_provider core RBS type lookup provider
|
|
747
|
+
# @param [Hash<Object, Object>, nil] local_var_types pre-built local variable types map
|
|
748
|
+
# @param [Hash<String, String>, nil] param_types parameter name -> type map for lvar resolution
|
|
339
749
|
# @return [String, nil]
|
|
340
750
|
def resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
341
751
|
lvar_name = recv&.children&.first
|
|
@@ -348,10 +758,10 @@ module Docscribe
|
|
|
348
758
|
|
|
349
759
|
# Look up a local variable's inferred type from local or parameter type maps.
|
|
350
760
|
#
|
|
351
|
-
# @note module_function:
|
|
352
|
-
# @param [
|
|
353
|
-
# @param [Hash, nil] local_var_types inferred local variable type map
|
|
354
|
-
# @param [Hash, nil] param_types parameter name to type map
|
|
761
|
+
# @note module_function: defines #lookup_lvar_type (visibility: private)
|
|
762
|
+
# @param [Object] lvar_name the local variable name
|
|
763
|
+
# @param [Hash<Object, Object>, nil] local_var_types inferred local variable type map
|
|
764
|
+
# @param [Hash<String, String>, nil] param_types parameter name to type map
|
|
355
765
|
# @return [String, nil]
|
|
356
766
|
def lookup_lvar_type(lvar_name, local_var_types, param_types)
|
|
357
767
|
return local_var_types[lvar_name.to_s] if local_var_types&.key?(lvar_name.to_s)
|
|
@@ -362,13 +772,12 @@ module Docscribe
|
|
|
362
772
|
|
|
363
773
|
# Resolve RBS return type for a chained `:send` receiver.
|
|
364
774
|
#
|
|
365
|
-
# @note module_function:
|
|
366
|
-
# @
|
|
367
|
-
# @param [
|
|
368
|
-
# @param [Object]
|
|
369
|
-
# @param [Object]
|
|
370
|
-
# @param [
|
|
371
|
-
# @param [Object] param_types
|
|
775
|
+
# @note module_function: defines #resolve_chained_send_rbs (visibility: private)
|
|
776
|
+
# @param [Parser::AST::Node?] recv the receiver node of the send
|
|
777
|
+
# @param [Symbol] meth the method name being called
|
|
778
|
+
# @param [Object, nil] core_rbs_provider core RBS type lookup provider
|
|
779
|
+
# @param [Hash<Object, Object>, nil] local_var_types pre-built local variable types map
|
|
780
|
+
# @param [Hash<String, String>, nil] param_types parameter name -> type map for lvar resolution
|
|
372
781
|
# @return [String, nil]
|
|
373
782
|
def resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
374
783
|
inner_type = run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
|
|
@@ -390,14 +799,9 @@ module Docscribe
|
|
|
390
799
|
# - literal-like expressions via {Literals.type_from_literal}
|
|
391
800
|
# - method calls with RBS core type lookup
|
|
392
801
|
#
|
|
393
|
-
# @note module_function:
|
|
802
|
+
# @note module_function: defines #last_expr_type (visibility: private)
|
|
394
803
|
# @param [Parser::AST::Node, nil] node expression node
|
|
395
|
-
# @param [
|
|
396
|
-
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
397
|
-
# @param [Object, nil] core_rbs_provider optional RBS provider for core type lookup
|
|
398
|
-
# @param [Hash, nil] param_types parameter name -> type map for lvar resolution
|
|
399
|
-
# @param [nil] local_var_types pre-built local variable types map
|
|
400
|
-
# @param [Hash] opts additional keyword options forwarded to type inference
|
|
804
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
401
805
|
# @return [String, nil]
|
|
402
806
|
def last_expr_type(node, **opts)
|
|
403
807
|
run_last_expr_type(node, **opts)
|
|
@@ -405,16 +809,17 @@ module Docscribe
|
|
|
405
809
|
|
|
406
810
|
# Dispatch `last_expr_type` based on node type.
|
|
407
811
|
#
|
|
408
|
-
# @note module_function:
|
|
409
|
-
# @param [Parser::AST::Node, nil] node
|
|
410
|
-
# @param [
|
|
812
|
+
# @note module_function: defines #run_last_expr_type (visibility: private)
|
|
813
|
+
# @param [Parser::AST::Node, nil] node the `:return` AST node
|
|
814
|
+
# @param [Object] opts options passed through as keyword args
|
|
411
815
|
# @return [String, nil]
|
|
412
816
|
def run_last_expr_type(node, **opts)
|
|
413
817
|
return unless node
|
|
414
818
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
819
|
+
type = node.type == :defined? ? :defined : node.type
|
|
820
|
+
method_name = :"handle_#{type}_node"
|
|
821
|
+
if respond_to?(method_name, true)
|
|
822
|
+
send(method_name, node, **opts)
|
|
418
823
|
else
|
|
419
824
|
Literals.type_from_literal(node, fallback_type: opts[:fallback_type])
|
|
420
825
|
end
|
|
@@ -422,9 +827,9 @@ module Docscribe
|
|
|
422
827
|
|
|
423
828
|
# Extract the return type from an explicit `:return` node.
|
|
424
829
|
#
|
|
425
|
-
# @note module_function:
|
|
830
|
+
# @note module_function: defines #handle_return_node (visibility: private)
|
|
426
831
|
# @param [Parser::AST::Node] node the `:return` AST node
|
|
427
|
-
# @param [
|
|
832
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
428
833
|
# @return [String, nil]
|
|
429
834
|
def handle_return_node(node, **opts)
|
|
430
835
|
Literals.type_from_literal(node.children.first, fallback_type: opts[:fallback_type])
|
|
@@ -432,10 +837,10 @@ module Docscribe
|
|
|
432
837
|
|
|
433
838
|
# Resolve an RBS return type for a method call.
|
|
434
839
|
#
|
|
435
|
-
# @note module_function:
|
|
840
|
+
# @note module_function: defines #resolve_rbs_return_type (visibility: private)
|
|
436
841
|
# @param [String] container_type class or module name
|
|
437
|
-
# @param [String] method_name method name
|
|
438
|
-
# @param [Object] core_rbs_provider core RBS type lookup provider
|
|
842
|
+
# @param [String, Symbol] method_name method name
|
|
843
|
+
# @param [Object, nil] core_rbs_provider core RBS type lookup provider
|
|
439
844
|
# @return [String] inferred return type
|
|
440
845
|
def resolve_rbs_return_type(container_type, method_name, core_rbs_provider)
|
|
441
846
|
return FALLBACK_TYPE unless core_rbs_provider
|
|
@@ -456,37 +861,34 @@ module Docscribe
|
|
|
456
861
|
# - `nil` unions may become optional types if enabled
|
|
457
862
|
# - otherwise falls back conservatively to `fallback_type`
|
|
458
863
|
#
|
|
459
|
-
# @note module_function:
|
|
460
|
-
# @param [String, nil] a
|
|
461
|
-
# @param [String, nil] b
|
|
462
|
-
# @param [String] fallback_type
|
|
463
|
-
# @param [Boolean] nil_as_optional
|
|
864
|
+
# @note module_function: defines #unify_types (visibility: private)
|
|
464
865
|
# @param [String, nil] type_a first type to unify
|
|
465
866
|
# @param [String, nil] type_b second type to unify
|
|
466
|
-
# @
|
|
867
|
+
# @param [String] fallback_type type used when neither is nil
|
|
868
|
+
# @param [Boolean] nil_as_optional whether to render nil unions as optional types
|
|
869
|
+
# @return [String]
|
|
467
870
|
def unify_types(type_a, type_b, fallback_type:, nil_as_optional:)
|
|
468
871
|
type_a ||= fallback_type
|
|
469
872
|
type_b ||= fallback_type
|
|
470
873
|
return type_a if type_a == type_b
|
|
471
874
|
|
|
472
|
-
unify_nil_types(type_a, type_b,
|
|
875
|
+
unify_nil_types(type_a, type_b, nil_as_optional: nil_as_optional)
|
|
473
876
|
end
|
|
474
877
|
|
|
475
878
|
# Unify two types where one may be `nil`, producing optional or union type.
|
|
476
879
|
#
|
|
477
|
-
# @note module_function:
|
|
880
|
+
# @note module_function: defines #unify_nil_types (visibility: private)
|
|
478
881
|
# @param [String] type_a first type string
|
|
479
882
|
# @param [String] type_b second type string
|
|
480
|
-
# @param [String] fallback_type type used when neither is nil
|
|
481
883
|
# @param [Boolean] nil_as_optional whether to render nil unions as optional types
|
|
482
884
|
# @return [String]
|
|
483
|
-
def unify_nil_types(type_a, type_b,
|
|
885
|
+
def unify_nil_types(type_a, type_b, nil_as_optional:)
|
|
484
886
|
if type_a == 'nil' || type_b == 'nil'
|
|
485
887
|
non_nil = (type_a == 'nil' ? type_b : type_a)
|
|
486
888
|
return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil"
|
|
487
889
|
end
|
|
488
890
|
|
|
489
|
-
|
|
891
|
+
"#{type_a}, #{type_b}"
|
|
490
892
|
end
|
|
491
893
|
end
|
|
492
894
|
end
|