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