docscribe 1.4.1 → 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 +588 -104
- data/lib/docscribe/cli/check_for_comments.rb +183 -0
- data/lib/docscribe/cli/config_builder.rb +180 -36
- 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 +296 -125
- data/lib/docscribe/cli/init.rb +58 -14
- data/lib/docscribe/cli/options.rb +410 -133
- data/lib/docscribe/cli/rbs_gen.rb +529 -0
- data/lib/docscribe/cli/run.rb +503 -189
- data/lib/docscribe/cli/sigs.rb +366 -0
- data/lib/docscribe/cli/update_types.rb +103 -0
- data/lib/docscribe/cli.rb +35 -9
- data/lib/docscribe/config/defaults.rb +16 -12
- data/lib/docscribe/config/emit.rb +18 -0
- data/lib/docscribe/config/filtering.rb +37 -31
- data/lib/docscribe/config/loader.rb +20 -13
- data/lib/docscribe/config/plugin.rb +2 -1
- data/lib/docscribe/config/rbs.rb +68 -27
- data/lib/docscribe/config/sorbet.rb +40 -17
- data/lib/docscribe/config/sorting.rb +2 -1
- data/lib/docscribe/config/template.rb +10 -1
- data/lib/docscribe/config/utils.rb +12 -9
- data/lib/docscribe/config.rb +3 -4
- data/lib/docscribe/infer/ast_walk.rb +1 -1
- data/lib/docscribe/infer/constants.rb +15 -0
- data/lib/docscribe/infer/literals.rb +39 -26
- data/lib/docscribe/infer/names.rb +24 -16
- data/lib/docscribe/infer/params.rb +57 -13
- data/lib/docscribe/infer/raises.rb +23 -15
- data/lib/docscribe/infer/returns.rb +784 -199
- data/lib/docscribe/infer.rb +28 -28
- data/lib/docscribe/inline_rewriter/collector.rb +816 -430
- data/lib/docscribe/inline_rewriter/doc_block.rb +323 -150
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1837 -648
- data/lib/docscribe/inline_rewriter/source_helpers.rb +119 -71
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +165 -107
- data/lib/docscribe/inline_rewriter.rb +1144 -727
- data/lib/docscribe/parsing.rb +29 -10
- data/lib/docscribe/plugin/base/collector_plugin.rb +3 -3
- data/lib/docscribe/plugin/base/tag_plugin.rb +1 -2
- data/lib/docscribe/plugin/context.rb +28 -18
- data/lib/docscribe/plugin/registry.rb +49 -23
- data/lib/docscribe/plugin/tag.rb +9 -14
- data/lib/docscribe/plugin.rb +54 -22
- data/lib/docscribe/types/provider_chain.rb +4 -2
- data/lib/docscribe/types/rbs/collection_loader.rb +2 -3
- data/lib/docscribe/types/rbs/provider.rb +127 -62
- data/lib/docscribe/types/rbs/type_formatter.rb +286 -77
- data/lib/docscribe/types/signature.rb +22 -42
- data/lib/docscribe/types/sorbet/base_provider.rb +51 -27
- 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 +34 -2
|
@@ -11,43 +11,48 @@ module Docscribe
|
|
|
11
11
|
# The source must parse to a `:def` or `:defs` node. If parsing fails or inference
|
|
12
12
|
# is uncertain, the fallback type is returned.
|
|
13
13
|
#
|
|
14
|
-
# @note module_function:
|
|
15
|
-
# @param [String
|
|
14
|
+
# @note module_function: defines #infer_return_type (visibility: private)
|
|
15
|
+
# @param [String?] method_source full method definition source
|
|
16
16
|
# @raise [Parser::SyntaxError]
|
|
17
|
-
# @return [String]
|
|
17
|
+
# @return [String] if Parser::SyntaxError
|
|
18
|
+
# @return [FALLBACK_TYPE] if Parser::SyntaxError
|
|
18
19
|
def infer_return_type(method_source)
|
|
19
20
|
return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
buffer.source = method_source
|
|
23
|
-
root = Docscribe::Parsing.parse_buffer(buffer)
|
|
22
|
+
root = parse_method_source(method_source)
|
|
24
23
|
return FALLBACK_TYPE unless root && %i[def defs].include?(root.type)
|
|
25
24
|
|
|
26
25
|
body = root.children.last
|
|
27
26
|
local_var_types = build_local_variable_types(body)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
rescue Parser::SyntaxError
|
|
27
|
+
run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
|
|
28
|
+
local_var_types: local_var_types) || FALLBACK_TYPE
|
|
29
|
+
rescue Parser::SyntaxError # steep:ignore
|
|
31
30
|
FALLBACK_TYPE
|
|
32
31
|
end
|
|
33
32
|
|
|
33
|
+
# Parse a Ruby source string into an AST using the Parser gem.
|
|
34
|
+
#
|
|
35
|
+
# @note module_function: defines #parse_method_source (visibility: private)
|
|
36
|
+
# @param [String] method_source the method definition source string to parse
|
|
37
|
+
# @return [Parser::AST::Node, nil]
|
|
38
|
+
def parse_method_source(method_source)
|
|
39
|
+
buffer = Parser::Source::Buffer.new('(method)')
|
|
40
|
+
buffer.source = method_source
|
|
41
|
+
Docscribe::Parsing.parse_buffer(buffer)
|
|
42
|
+
end
|
|
43
|
+
|
|
34
44
|
# Infer a method's normal return type from an already parsed def/defs node.
|
|
35
45
|
#
|
|
36
|
-
# @note module_function:
|
|
46
|
+
# @note module_function: defines #infer_return_type_from_node (visibility: private)
|
|
37
47
|
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
38
48
|
# @return [String]
|
|
39
49
|
def infer_return_type_from_node(node)
|
|
40
|
-
body =
|
|
41
|
-
case node.type
|
|
42
|
-
when :def then node.children[2]
|
|
43
|
-
when :defs then node.children[3]
|
|
44
|
-
end
|
|
45
|
-
|
|
50
|
+
body = extract_def_body(node)
|
|
46
51
|
return FALLBACK_TYPE unless body
|
|
47
52
|
|
|
48
53
|
local_var_types = build_local_variable_types(body)
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true,
|
|
55
|
+
local_var_types: local_var_types) || FALLBACK_TYPE
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
# Return a structured return-type spec for a method node.
|
|
@@ -56,218 +61,787 @@ module Docscribe
|
|
|
56
61
|
# - `:normal` => normal/happy-path return type
|
|
57
62
|
# - `:rescues` => array of `[exception_names, return_type]` pairs for rescue branches
|
|
58
63
|
#
|
|
59
|
-
# @note module_function:
|
|
64
|
+
# @note module_function: defines #returns_spec_from_node (visibility: private)
|
|
60
65
|
# @param [Parser::AST::Node] node `:def` or `:defs` node
|
|
61
66
|
# @param [String] fallback_type type used when inference is uncertain
|
|
62
67
|
# @param [Boolean] nil_as_optional whether `nil` unions should be rendered as optional types
|
|
63
|
-
# @param [
|
|
64
|
-
# @param [
|
|
65
|
-
# @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]
|
|
66
71
|
def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil,
|
|
67
72
|
param_types: nil)
|
|
68
|
-
body =
|
|
69
|
-
|
|
70
|
-
when :def then node.children[2]
|
|
71
|
-
when :defs then node.children[3]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
spec = { normal: FALLBACK_TYPE, rescues: [] }
|
|
73
|
+
body = extract_def_body(node)
|
|
74
|
+
spec = { normal: FALLBACK_TYPE, rescues: [] } #: Hash[Symbol, untyped]
|
|
75
75
|
return spec unless body
|
|
76
76
|
|
|
77
|
-
|
|
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)
|
|
80
|
+
spec
|
|
81
|
+
end
|
|
78
82
|
|
|
83
|
+
# Extract the body child node from a `:def` or `:defs` AST node.
|
|
84
|
+
#
|
|
85
|
+
# @note module_function: defines #extract_def_body (visibility: private)
|
|
86
|
+
# @param [Parser::AST::Node] node a `:def` or `:defs` AST node
|
|
87
|
+
# @return [Parser::AST::Node, nil]
|
|
88
|
+
def extract_def_body(node)
|
|
89
|
+
case node.type
|
|
90
|
+
when :def then node.children[2]
|
|
91
|
+
when :defs then node.children[3]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Populate the spec hash with normal and/or rescue return types from the body.
|
|
96
|
+
#
|
|
97
|
+
# @note module_function: defines #populate_returns_spec (visibility: private)
|
|
98
|
+
# @param [Object] spec the return spec hash to populate
|
|
99
|
+
# @param [Parser::AST::Node] body the method body AST node
|
|
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]
|
|
103
|
+
def populate_returns_spec(spec, body, local_var_types, **opts)
|
|
79
104
|
if body.type == :rescue
|
|
80
|
-
|
|
81
|
-
rescue_local_var_types = build_local_variable_types(body)
|
|
82
|
-
all_local_var_types = rescue_local_var_types || local_var_types
|
|
83
|
-
spec[:normal] =
|
|
84
|
-
last_expr_type(main_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
85
|
-
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
86
|
-
local_var_types: all_local_var_types) || FALLBACK_TYPE
|
|
87
|
-
|
|
88
|
-
body.children.each do |ch|
|
|
89
|
-
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
90
|
-
|
|
91
|
-
exc_list, _asgn, rescue_body = *ch
|
|
92
|
-
exc_names = Raises.exception_names_from_rescue_list(exc_list)
|
|
93
|
-
rtype =
|
|
94
|
-
last_expr_type(rescue_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
95
|
-
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
96
|
-
local_var_types: all_local_var_types) ||
|
|
97
|
-
fallback_type
|
|
98
|
-
spec[:rescues] << [exc_names, rtype]
|
|
99
|
-
end
|
|
105
|
+
process_rescue_body(spec, body, **opts)
|
|
100
106
|
else
|
|
101
|
-
spec[:normal] =
|
|
102
|
-
last_expr_type(body, fallback_type: fallback_type, nil_as_optional: nil_as_optional,
|
|
103
|
-
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
104
|
-
local_var_types: local_var_types) || FALLBACK_TYPE
|
|
107
|
+
spec[:normal] = infer_normal_return_type(body, **opts, local_var_types: local_var_types)
|
|
105
108
|
end
|
|
109
|
+
end
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
# Infer the normal (non-rescue) return type from a method body node.
|
|
112
|
+
#
|
|
113
|
+
# @note module_function: defines #infer_normal_return_type (visibility: private)
|
|
114
|
+
# @param [Parser::AST::Node] body the method body AST node
|
|
115
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
116
|
+
# @return [String]
|
|
117
|
+
def infer_normal_return_type(body, **opts)
|
|
118
|
+
run_last_expr_type(body, **opts) || FALLBACK_TYPE
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Process a :rescue body node and populate spec with normal + rescue return types.
|
|
122
|
+
#
|
|
123
|
+
# @note module_function: defines #process_rescue_body (visibility: private)
|
|
124
|
+
# @param [Object] spec the return spec hash to populate
|
|
125
|
+
# @param [Parser::AST::Node] body the :rescue AST node
|
|
126
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
127
|
+
# @return [Object]
|
|
128
|
+
def process_rescue_body(spec, body, **opts)
|
|
129
|
+
main_body = body.children[0]
|
|
130
|
+
local_var_types = build_local_variable_types(body,
|
|
131
|
+
core_rbs_provider: opts[:core_rbs_provider],
|
|
132
|
+
param_types: opts[:param_types])
|
|
133
|
+
rescue_opts = opts.merge(local_var_types: local_var_types)
|
|
134
|
+
spec[:normal] = run_last_expr_type(main_body, **rescue_opts) || FALLBACK_TYPE
|
|
135
|
+
process_rescue_branches(spec, body, **rescue_opts)
|
|
108
136
|
end
|
|
109
137
|
|
|
110
|
-
#
|
|
138
|
+
# Extract return types from each :resbody child and append to spec[:rescues].
|
|
111
139
|
#
|
|
112
|
-
# @note module_function:
|
|
113
|
-
# @
|
|
114
|
-
# @param [
|
|
115
|
-
# @
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
# @note module_function: defines #process_rescue_branches (visibility: private)
|
|
141
|
+
# @param [Object] spec the return spec hash to populate
|
|
142
|
+
# @param [Parser::AST::Node] body the :rescue AST node
|
|
143
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
144
|
+
# @return [Array<Object>] the list of rescue type entries
|
|
145
|
+
def process_rescue_branches(spec, body, **opts)
|
|
146
|
+
body.children.each do |ch|
|
|
147
|
+
next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody
|
|
148
|
+
|
|
149
|
+
exc_list, _asgn, rescue_body = *ch
|
|
150
|
+
exc_names = Raises.exception_names_from_rescue_list(exc_list)
|
|
151
|
+
rtype = run_last_expr_type(rescue_body, **opts) || opts[:fallback_type]
|
|
152
|
+
spec[:rescues] << [exc_names, rtype]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Build a map of local/global/ivar/constant assignments to inferred types.
|
|
157
|
+
#
|
|
158
|
+
# @note module_function: defines #build_local_variable_types (visibility: private)
|
|
159
|
+
# @param [Parser::AST::Node] node AST node to walk
|
|
160
|
+
# @param [Object] opts additional keyword options forwarded to inference
|
|
161
|
+
# @return [Hash<String, String>, nil]
|
|
162
|
+
def build_local_variable_types(node, **opts)
|
|
163
|
+
types = {} #: Hash[String, String]
|
|
118
164
|
ASTWalk.walk(node) do |n|
|
|
119
|
-
|
|
120
|
-
when :lvasgn, :gvasgn, :ivasgn
|
|
121
|
-
name = n.children[0].to_s
|
|
122
|
-
value = n.children[1]
|
|
123
|
-
if value
|
|
124
|
-
inferred = Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE)
|
|
125
|
-
types[name] = inferred if inferred && inferred != FALLBACK_TYPE
|
|
126
|
-
end
|
|
127
|
-
when :casgn
|
|
128
|
-
name = n.children[0].to_s
|
|
129
|
-
value = n.children[2]
|
|
130
|
-
if value
|
|
131
|
-
inferred = Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE)
|
|
132
|
-
types[name] = inferred if inferred && inferred != FALLBACK_TYPE
|
|
133
|
-
end
|
|
134
|
-
end
|
|
165
|
+
collect_assignment_type(n, types, **opts)
|
|
135
166
|
end
|
|
136
167
|
types.empty? ? nil : types
|
|
137
168
|
end
|
|
138
169
|
|
|
139
|
-
# Infer the type of
|
|
170
|
+
# Infer the type of a single assignment node and store it in the types hash.
|
|
140
171
|
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# - `case` expressions
|
|
145
|
-
# - explicit `return`
|
|
146
|
-
# - literal-like expressions via {Literals.type_from_literal}
|
|
147
|
-
# - method calls with RBS core type lookup
|
|
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.
|
|
148
175
|
#
|
|
149
|
-
# @note module_function:
|
|
150
|
-
# @param [Parser::AST::Node
|
|
151
|
-
# @param [String]
|
|
152
|
-
# @param [
|
|
153
|
-
# @
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
# @note module_function: defines #collect_assignment_type (visibility: private)
|
|
177
|
+
# @param [Parser::AST::Node] node an assignment AST node
|
|
178
|
+
# @param [Hash<String, String>] types the accumulated local variable type map
|
|
179
|
+
# @param [Object] opts additional keyword options forwarded to inference
|
|
180
|
+
# @return [void]
|
|
181
|
+
def collect_assignment_type(node, types, **opts)
|
|
182
|
+
name, value = assignment_name_and_value(node)
|
|
183
|
+
return unless name && value
|
|
184
|
+
|
|
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
|
|
191
|
+
types[name] = inferred if inferred && inferred != FALLBACK_TYPE
|
|
192
|
+
end
|
|
160
193
|
|
|
194
|
+
# Extract the variable name and value expression from an assignment node.
|
|
195
|
+
#
|
|
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)]
|
|
199
|
+
def assignment_name_and_value(node)
|
|
161
200
|
case node.type
|
|
162
|
-
when :
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
when :lvasgn, :gvasgn, :ivasgn, :cvasgn
|
|
202
|
+
[node.children[0].to_s, node.children[1]]
|
|
203
|
+
when :casgn
|
|
204
|
+
constant_name_and_value(node)
|
|
205
|
+
when :op_asgn
|
|
206
|
+
compound_name_and_value(node)
|
|
207
|
+
else
|
|
208
|
+
[nil, nil]
|
|
209
|
+
end
|
|
210
|
+
end
|
|
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
|
+
|
|
348
|
+
# Handle `:begin` node for last_expr_type.
|
|
349
|
+
#
|
|
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]
|
|
354
|
+
def handle_begin_node(node, **opts)
|
|
355
|
+
run_last_expr_type(node.children.last, **opts)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Handle `:if` node for last_expr_type.
|
|
359
|
+
#
|
|
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]
|
|
364
|
+
def handle_if_node(node, **opts)
|
|
365
|
+
t = run_last_expr_type(node.children[1], **opts)
|
|
366
|
+
e = if node.children[2]
|
|
367
|
+
run_last_expr_type(node.children[2], **opts)
|
|
182
368
|
else
|
|
183
|
-
|
|
184
|
-
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
185
|
-
local_var_types: local_var_types)
|
|
369
|
+
'nil'
|
|
186
370
|
end
|
|
187
|
-
|
|
371
|
+
unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped',
|
|
372
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Handle `:case` node for last_expr_type.
|
|
376
|
+
#
|
|
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]
|
|
381
|
+
def handle_case_node(node, **opts)
|
|
382
|
+
branches = process_case_branches(node, **opts)
|
|
383
|
+
if branches.empty?
|
|
384
|
+
opts[:fallback_type]
|
|
385
|
+
else
|
|
386
|
+
branches.reduce do |a, b|
|
|
387
|
+
unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped',
|
|
388
|
+
nil_as_optional: opts.fetch(:nil_as_optional, true))
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
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
|
|
188
424
|
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
191
470
|
else
|
|
192
|
-
branches
|
|
193
|
-
unify_types(a, b, fallback_type: fallback_type, nil_as_optional: nil_as_optional)
|
|
194
|
-
end
|
|
471
|
+
branches << run_last_expr_type(child, **opts)
|
|
195
472
|
end
|
|
473
|
+
end
|
|
474
|
+
branches
|
|
475
|
+
end
|
|
196
476
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
|
|
586
|
+
# Extract inferred return types from all branches of a :case expression.
|
|
587
|
+
#
|
|
588
|
+
# @note module_function: defines #process_case_branches (visibility: private)
|
|
589
|
+
# @param [Parser::AST::Node] node the :case AST node
|
|
590
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
591
|
+
# @return [Array<String>] list of inferred types from each branch
|
|
592
|
+
def process_case_branches(node, **opts)
|
|
593
|
+
(node.children[1..] || []).compact.flat_map do |child|
|
|
594
|
+
if child.type == :when
|
|
595
|
+
run_last_expr_type(child.children.last, **opts)
|
|
596
|
+
else
|
|
597
|
+
run_last_expr_type(child, **opts)
|
|
224
598
|
end
|
|
599
|
+
end.compact
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Handle `:block` node for last_expr_type.
|
|
603
|
+
#
|
|
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]
|
|
608
|
+
def handle_block_node(node, **opts)
|
|
609
|
+
send_node = node.children[0]
|
|
610
|
+
if send_node&.type == :send
|
|
611
|
+
recv = send_node.children[0]
|
|
612
|
+
meth = send_node.children[1]
|
|
613
|
+
rbs_type = resolve_rbs_for_send(recv, meth, opts[:core_rbs_provider], opts[:local_var_types],
|
|
614
|
+
opts[:param_types])
|
|
615
|
+
return rbs_type if rbs_type
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
run_last_expr_type(node.children[2], **opts)
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
# Handle `:send` node for last_expr_type.
|
|
622
|
+
#
|
|
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]
|
|
627
|
+
def handle_send_node(node, **opts)
|
|
628
|
+
recv = node.children[0]
|
|
629
|
+
meth = node.children[1]
|
|
630
|
+
|
|
631
|
+
if opts[:core_rbs_provider]
|
|
632
|
+
rbs_type = resolve_rbs_for_send(recv, meth, opts[:core_rbs_provider], opts[:local_var_types],
|
|
633
|
+
opts[:param_types])
|
|
634
|
+
return rbs_type if rbs_type
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
compound_type = infer_from_compound_assign(node, **opts)
|
|
638
|
+
return compound_type if compound_type
|
|
639
|
+
|
|
640
|
+
Literals.type_from_literal(node, fallback_type: opts[:fallback_type])
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Resolve RBS return type for a send node's receiver, if possible.
|
|
644
|
+
#
|
|
645
|
+
# Handles `:lvar`, chained `:send`, literal (`:int`, `:str`, etc.),
|
|
646
|
+
# and variable (`:ivar`, `:gvar`, `:cvar`) receivers.
|
|
647
|
+
#
|
|
648
|
+
# @note module_function: defines #resolve_rbs_for_send (visibility: private)
|
|
649
|
+
# @param [Parser::AST::Node, nil] recv the receiver node of the send
|
|
650
|
+
# @param [Symbol] meth the method name being called
|
|
651
|
+
# @param [Object, nil] core_rbs_provider optional RBS provider for core type lookup
|
|
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
|
|
654
|
+
# @return [String, nil] resolved type or nil if unresolvable
|
|
655
|
+
def resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
656
|
+
return nil unless core_rbs_provider
|
|
657
|
+
|
|
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)
|
|
225
717
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
local_var_types: local_var_types)
|
|
718
|
+
first_arg = node.children[2]
|
|
719
|
+
return nil unless first_arg
|
|
229
720
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
meth = node.children[1]
|
|
721
|
+
arg_type = type_from_literal_safe(first_arg)
|
|
722
|
+
return nil unless arg_type
|
|
233
723
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Resolve RBS return type for an `:lvar` receiver.
|
|
742
|
+
#
|
|
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
|
|
749
|
+
# @return [String, nil]
|
|
750
|
+
def resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
751
|
+
lvar_name = recv&.children&.first
|
|
752
|
+
recv_type = lookup_lvar_type(lvar_name, local_var_types, param_types)
|
|
753
|
+
return nil unless recv_type
|
|
754
|
+
|
|
755
|
+
rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider)
|
|
756
|
+
rbs_type unless rbs_type == FALLBACK_TYPE
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
# Look up a local variable's inferred type from local or parameter type maps.
|
|
760
|
+
#
|
|
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
|
|
765
|
+
# @return [String, nil]
|
|
766
|
+
def lookup_lvar_type(lvar_name, local_var_types, param_types)
|
|
767
|
+
return local_var_types[lvar_name.to_s] if local_var_types&.key?(lvar_name.to_s)
|
|
768
|
+
return param_types[lvar_name.to_s] if param_types&.key?(lvar_name.to_s)
|
|
769
|
+
|
|
770
|
+
nil
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Resolve RBS return type for a chained `:send` receiver.
|
|
774
|
+
#
|
|
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
|
|
781
|
+
# @return [String, nil]
|
|
782
|
+
def resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types)
|
|
783
|
+
inner_type = run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false,
|
|
238
784
|
core_rbs_provider: core_rbs_provider, param_types: param_types,
|
|
239
785
|
local_var_types: local_var_types)
|
|
240
|
-
|
|
241
|
-
rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider)
|
|
242
|
-
return rbs_type unless rbs_type == FALLBACK_TYPE
|
|
243
|
-
end
|
|
786
|
+
return nil unless inner_type
|
|
244
787
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
788
|
+
rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider)
|
|
789
|
+
rbs_type unless rbs_type == FALLBACK_TYPE
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Infer the type of the last expression in a node.
|
|
793
|
+
#
|
|
794
|
+
# Supports:
|
|
795
|
+
# - `begin` groups
|
|
796
|
+
# - `if` branches
|
|
797
|
+
# - `case` expressions
|
|
798
|
+
# - explicit `return`
|
|
799
|
+
# - literal-like expressions via {Literals.type_from_literal}
|
|
800
|
+
# - method calls with RBS core type lookup
|
|
801
|
+
#
|
|
802
|
+
# @note module_function: defines #last_expr_type (visibility: private)
|
|
803
|
+
# @param [Parser::AST::Node, nil] node expression node
|
|
804
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
805
|
+
# @return [String, nil]
|
|
806
|
+
def last_expr_type(node, **opts)
|
|
807
|
+
run_last_expr_type(node, **opts)
|
|
808
|
+
end
|
|
256
809
|
|
|
257
|
-
|
|
810
|
+
# Dispatch `last_expr_type` based on node type.
|
|
811
|
+
#
|
|
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
|
|
815
|
+
# @return [String, nil]
|
|
816
|
+
def run_last_expr_type(node, **opts)
|
|
817
|
+
return unless node
|
|
258
818
|
|
|
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)
|
|
259
823
|
else
|
|
260
|
-
Literals.type_from_literal(node, fallback_type: fallback_type)
|
|
824
|
+
Literals.type_from_literal(node, fallback_type: opts[:fallback_type])
|
|
261
825
|
end
|
|
262
826
|
end
|
|
263
827
|
|
|
264
|
-
#
|
|
828
|
+
# Extract the return type from an explicit `:return` node.
|
|
265
829
|
#
|
|
266
|
-
# @note module_function:
|
|
267
|
-
# @param [
|
|
268
|
-
# @param [Object]
|
|
269
|
-
# @
|
|
270
|
-
|
|
830
|
+
# @note module_function: defines #handle_return_node (visibility: private)
|
|
831
|
+
# @param [Parser::AST::Node] node the `:return` AST node
|
|
832
|
+
# @param [Object] opts additional keyword options forwarded to type inference
|
|
833
|
+
# @return [String, nil]
|
|
834
|
+
def handle_return_node(node, **opts)
|
|
835
|
+
Literals.type_from_literal(node.children.first, fallback_type: opts[:fallback_type])
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# Resolve an RBS return type for a method call.
|
|
839
|
+
#
|
|
840
|
+
# @note module_function: defines #resolve_rbs_return_type (visibility: private)
|
|
841
|
+
# @param [String] container_type class or module name
|
|
842
|
+
# @param [String, Symbol] method_name method name
|
|
843
|
+
# @param [Object, nil] core_rbs_provider core RBS type lookup provider
|
|
844
|
+
# @return [String] inferred return type
|
|
271
845
|
def resolve_rbs_return_type(container_type, method_name, core_rbs_provider)
|
|
272
846
|
return FALLBACK_TYPE unless core_rbs_provider
|
|
273
847
|
|
|
@@ -287,23 +861,34 @@ module Docscribe
|
|
|
287
861
|
# - `nil` unions may become optional types if enabled
|
|
288
862
|
# - otherwise falls back conservatively to `fallback_type`
|
|
289
863
|
#
|
|
290
|
-
# @note module_function:
|
|
291
|
-
# @param [String, nil]
|
|
292
|
-
# @param [String, nil]
|
|
293
|
-
# @param [String] fallback_type
|
|
294
|
-
# @param [Boolean] nil_as_optional
|
|
295
|
-
# @return [String
|
|
296
|
-
def unify_types(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return
|
|
864
|
+
# @note module_function: defines #unify_types (visibility: private)
|
|
865
|
+
# @param [String, nil] type_a first type to unify
|
|
866
|
+
# @param [String, nil] type_b second type to unify
|
|
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]
|
|
870
|
+
def unify_types(type_a, type_b, fallback_type:, nil_as_optional:)
|
|
871
|
+
type_a ||= fallback_type
|
|
872
|
+
type_b ||= fallback_type
|
|
873
|
+
return type_a if type_a == type_b
|
|
300
874
|
|
|
301
|
-
|
|
302
|
-
|
|
875
|
+
unify_nil_types(type_a, type_b, nil_as_optional: nil_as_optional)
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
# Unify two types where one may be `nil`, producing optional or union type.
|
|
879
|
+
#
|
|
880
|
+
# @note module_function: defines #unify_nil_types (visibility: private)
|
|
881
|
+
# @param [String] type_a first type string
|
|
882
|
+
# @param [String] type_b second type string
|
|
883
|
+
# @param [Boolean] nil_as_optional whether to render nil unions as optional types
|
|
884
|
+
# @return [String]
|
|
885
|
+
def unify_nil_types(type_a, type_b, nil_as_optional:)
|
|
886
|
+
if type_a == 'nil' || type_b == 'nil'
|
|
887
|
+
non_nil = (type_a == 'nil' ? type_b : type_a)
|
|
303
888
|
return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil"
|
|
304
889
|
end
|
|
305
890
|
|
|
306
|
-
|
|
891
|
+
"#{type_a}, #{type_b}"
|
|
307
892
|
end
|
|
308
893
|
end
|
|
309
894
|
end
|