Module: Docscribe::Infer::Returns
- Defined in:
- lib/docscribe/infer/returns.rb
Overview
Return type inference and rescue-conditional return extraction.
Constant Summary collapse
- LITERAL_RBS_TYPES =
Note:
module_function: when included, also defines #receiver_rbs_type_name (instance visibility: private)
Map a receiver AST node to its RBS type name string.
Supports local variables, method calls, literals, and instance/global/class variables.
{ int: 'Integer', str: 'String', sym: 'Symbol', true: 'Boolean', false: 'Boolean', float: 'Float', array: 'Array', hash: 'Hash', nil: 'NilClass' }.freeze
Class Method Summary collapse
-
.assignment_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the variable name and value expression from an assignment node.
-
.build_local_variable_types(node, **opts) ⇒ Hash<String, String>?
Build a map of local/global/ivar/constant assignments to inferred types.
-
.collect_assignment_type(node, types, **opts) ⇒ void
Infer the type of a single assignment node and store it in the types hash.
-
.collect_rescue_branches(node, **opts) ⇒ Array<String, nil>
Handle ‘:rescue` node for last_expr_type.
-
.compound_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the name and value from an ‘:op_asgn` (compound assignment) node.
-
.constant_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the name and value from a ‘:casgn` (constant assignment) node.
-
.extract_def_body(node) ⇒ Parser::AST::Node?
Extract the body child node from a ‘:def` or `:defs` AST node.
-
.handle_and_node(node, **opts) ⇒ String?
Handle ‘:and` node (`a && b`) for last_expr_type.
-
.handle_begin_node(node, **opts) ⇒ String?
Handle ‘:begin` node for last_expr_type.
-
.handle_block_node(node, **opts) ⇒ String?
Handle ‘:block` node for last_expr_type.
-
.handle_case_match_node(node, **opts) ⇒ String?
Handle ‘:case_match` node (`case x; in pat; expr; end`) for last_expr_type.
-
.handle_case_node(node, **opts) ⇒ String?
Handle ‘:case` node for last_expr_type.
-
.handle_cvar_node(node, **opts) ⇒ String?
Handle ‘:cvar` node for last_expr_type — look up class variable in local_var_types.
-
.handle_cvasgn_node(node, **opts) ⇒ String?
Handle ‘:cvasgn` node for last_expr_type — look up class var assignment in local_var_types.
-
.handle_defined_node(_node, **opts) ⇒ String?
Handle ‘:defined?` node (`defined?(expr)`) for last_expr_type.
-
.handle_ensure_node(node, **opts) ⇒ String?
Handle ‘:ensure` node (`begin; expr; ensure; cleanup; end`) for last_expr_type.
-
.handle_gvar_node(node, **opts) ⇒ String?
Handle ‘:gvar` node for last_expr_type — look up global variable in local_var_types.
-
.handle_gvasgn_node(node, **opts) ⇒ String?
Handle ‘:gvasgn` node for last_expr_type — look up global var assignment in local_var_types.
-
.handle_if_node(node, **opts) ⇒ String?
Handle ‘:if` node for last_expr_type.
-
.handle_in_pattern_node(node, **opts) ⇒ String?
Handle ‘:in_pattern` node (pattern inside `case…in`) for last_expr_type.
-
.handle_ivar_node(node, **opts) ⇒ String?
Handle ‘:ivar` node for last_expr_type — look up instance variable in local_var_types.
-
.handle_ivasgn_node(node, **opts) ⇒ String?
Handle ‘:ivasgn` node for last_expr_type — look up ivar assignment in local_var_types.
-
.handle_kwbegin_node(node, **opts) ⇒ String?
Handle ‘:kwbegin` node (`begin; expr; end`) for last_expr_type.
-
.handle_lvar_node(node, **opts) ⇒ String?
Handle ‘:lvar` node for last_expr_type — look up the variable in local_var_types.
-
.handle_lvasgn_node(node, **opts) ⇒ String?
Handle ‘:lvasgn` node for last_expr_type — look up local var assignment in local_var_types.
-
.handle_op_asgn_node(node, **opts) ⇒ String?
Handle ‘:op_asgn` node (compound assignment: `x += 1`, `@var -= 2`, etc.).
-
.handle_or_node(node, **opts) ⇒ String?
Handle ‘:or` node (`a || b`) for last_expr_type.
-
.handle_rescue_node(node, **opts) ⇒ String?
Handle ‘:rescue` node for last_expr_type.
-
.handle_return_node(node, **opts) ⇒ String?
Extract the return type from an explicit ‘:return` node.
-
.handle_send_node(node, **opts) ⇒ String?
Handle ‘:send` node for last_expr_type.
-
.handle_super_node(_node, **opts) ⇒ String?
Handle ‘:super` node (`super(args)`) for last_expr_type.
-
.handle_yield_node(_node, **opts) ⇒ String?
Handle ‘:yield` node (`yield` / `yield(args)`) for last_expr_type.
-
.handle_zsuper_node(_node, **opts) ⇒ String?
Handle ‘:zsuper` node (`super` with no arguments) for last_expr_type.
-
.infer_from_compound_assign(node, **opts) ⇒ String?
Infer return type from a compound-assignment-like ‘:send` by reading the first literal argument’s type — only fires when ‘core_rbs_provider` is present and the argument’s RBS return type can be resolved.
-
.infer_normal_return_type(body, **opts) ⇒ String
Infer the normal (non-rescue) return type from a method body node.
-
.infer_return_type(method_source) ⇒ String, FALLBACK_TYPE
Infer a return type from a full method definition source string.
-
.infer_return_type_from_node(node) ⇒ String
Infer a method’s normal return type from an already parsed def/defs node.
-
.last_expr_type(node, **opts) ⇒ String?
Infer the type of the last expression in a node.
-
.lookup_lvar_type(lvar_name, local_var_types, param_types) ⇒ String?
Look up a local variable’s inferred type from local or parameter type maps.
-
.parse_method_source(method_source) ⇒ Parser::AST::Node?
Parse a Ruby source string into an AST using the Parser gem.
-
.populate_returns_spec(spec, body, local_var_types, **opts) ⇒ Object
Populate the spec hash with normal and/or rescue return types from the body.
-
.process_case_branches(node, **opts) ⇒ Array<String>
Extract inferred return types from all branches of a :case expression.
-
.process_pattern_branches(node, **opts) ⇒ Array<String>
Extract inferred return types from all in_pattern branches of a :case_match expression.
-
.process_rescue_body(spec, body, **opts) ⇒ Object
Process a :rescue body node and populate spec with normal + rescue return types.
-
.process_rescue_branches(spec, body, **opts) ⇒ Array<Object>
Extract return types from each :resbody child and append to spec.
-
.receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
Map receiver AST node to RBS type name.
-
.resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for a chained ‘:send` receiver.
-
.resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for an ‘:lvar` receiver.
-
.resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for a send node’s receiver, if possible.
-
.resolve_rbs_return_type(container_type, method_name, core_rbs_provider) ⇒ String
Resolve an RBS return type for a method call.
-
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) ⇒ Object
Return a structured return-type spec for a method node.
-
.run_last_expr_type(node, **opts) ⇒ String?
Dispatch ‘last_expr_type` based on node type.
-
.type_from_literal_safe(node) ⇒ String?
Safely get a type string from a literal node, returning nil if the node is not a literal or yields no type.
-
.unify_nil_types(type_a, type_b, nil_as_optional:) ⇒ String
Unify two types where one may be ‘nil`, producing optional or union type.
-
.unify_types(type_a, type_b, fallback_type:, nil_as_optional:) ⇒ String
Unify two inferred types into a single type string.
Class Method Details
.assignment_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
module_function: defines #assignment_name_and_value (visibility: private)
Extract the variable name and value expression from an assignment node.
199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/docscribe/infer/returns.rb', line 199 def assignment_name_and_value(node) case node.type when :lvasgn, :gvasgn, :ivasgn, :cvasgn [node.children[0].to_s, node.children[1]] when :casgn constant_name_and_value(node) when :op_asgn compound_name_and_value(node) else [nil, nil] end end |
.build_local_variable_types(node, **opts) ⇒ Hash<String, String>?
module_function: defines #build_local_variable_types (visibility: private)
Build a map of local/global/ivar/constant assignments to inferred types.
162 163 164 165 166 167 168 |
# File 'lib/docscribe/infer/returns.rb', line 162 def build_local_variable_types(node, **opts) types = {} #: Hash[String, String] ASTWalk.walk(node) do |n| collect_assignment_type(n, types, **opts) end types.empty? ? nil : types end |
.collect_assignment_type(node, types, **opts) ⇒ void
module_function: defines #collect_assignment_type (visibility: private)
This method returns an undefined value.
Infer the type of a single assignment node and store it in the types hash.
Uses ‘run_last_expr_type` when `core_rbs_provider` is available to resolve send expressions (e.g., `x = 123 + 1` -> `Integer`). Falls back to `Literals.type_from_literal` for plain literals.
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/docscribe/infer/returns.rb', line 181 def collect_assignment_type(node, types, **opts) name, value = assignment_name_and_value(node) return unless name && value inferred = if opts[:core_rbs_provider] run_last_expr_type(value, **opts, fallback_type: FALLBACK_TYPE, nil_as_optional: false, local_var_types: types) else Literals.type_from_literal(value, fallback_type: FALLBACK_TYPE) end types[name] = inferred if inferred && inferred != FALLBACK_TYPE end |
.collect_rescue_branches(node, **opts) ⇒ Array<String, nil>
module_function: defines #collect_rescue_branches (visibility: private)
Handle ‘:rescue` node for last_expr_type.
Unifies the body type with all rescue handler types and the optional else clause. Collect all rescue branch return types from a ‘:rescue` AST node.
464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/docscribe/infer/returns.rb', line 464 def collect_rescue_branches(node, **opts) branches = [run_last_expr_type(node.children[0], **opts)] (node.children[1..] || []).each do |child| if child.is_a?(Parser::AST::Node) && child.type == :resbody handler = child.children[2] branches << run_last_expr_type(handler, **opts) if handler else branches << run_last_expr_type(child, **opts) end end branches end |
.compound_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
module_function: defines #compound_name_and_value (visibility: private)
Extract the name and value from an ‘:op_asgn` (compound assignment) node.
226 227 228 |
# File 'lib/docscribe/infer/returns.rb', line 226 def compound_name_and_value(node) [node.children[0].children.first.to_s, node.children[2]] end |
.constant_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
module_function: defines #constant_name_and_value (visibility: private)
Extract the name and value from a ‘:casgn` (constant assignment) node.
217 218 219 |
# File 'lib/docscribe/infer/returns.rb', line 217 def constant_name_and_value(node) [node.children[0].to_s, node.children[2]] end |
.extract_def_body(node) ⇒ Parser::AST::Node?
module_function: defines #extract_def_body (visibility: private)
Extract the body child node from a ‘:def` or `:defs` AST node.
88 89 90 91 92 93 |
# File 'lib/docscribe/infer/returns.rb', line 88 def extract_def_body(node) case node.type when :def then node.children[2] when :defs then node.children[3] end end |
.handle_and_node(node, **opts) ⇒ String?
module_function: defines #handle_and_node (visibility: private)
Handle ‘:and` node (`a && b`) for last_expr_type.
The result type is the union of both sides, since either may be returned depending on the truthiness of the left operand.
418 419 420 421 422 423 |
# File 'lib/docscribe/infer/returns.rb', line 418 def handle_and_node(node, **opts) t = run_last_expr_type(node.children[0], **opts) e = run_last_expr_type(node.children[1], **opts) unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end |
.handle_begin_node(node, **opts) ⇒ String?
module_function: defines #handle_begin_node (visibility: private)
Handle ‘:begin` node for last_expr_type.
354 355 356 |
# File 'lib/docscribe/infer/returns.rb', line 354 def handle_begin_node(node, **opts) run_last_expr_type(node.children.last, **opts) end |
.handle_block_node(node, **opts) ⇒ String?
module_function: defines #handle_block_node (visibility: private)
Handle ‘:block` node for last_expr_type.
608 609 610 611 612 613 614 615 616 617 618 619 |
# File 'lib/docscribe/infer/returns.rb', line 608 def handle_block_node(node, **opts) send_node = node.children[0] if send_node&.type == :send recv = send_node.children[0] meth = send_node.children[1] rbs_type = resolve_rbs_for_send(recv, meth, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) return rbs_type if rbs_type end run_last_expr_type(node.children[2], **opts) end |
.handle_case_match_node(node, **opts) ⇒ String?
module_function: defines #handle_case_match_node (visibility: private)
Handle ‘:case_match` node (`case x; in pat; expr; end`) for last_expr_type.
Similar to ‘:case` — unifies all `in_pattern` branch types and the optional else clause.
550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/docscribe/infer/returns.rb', line 550 def handle_case_match_node(node, **opts) branches = process_pattern_branches(node, **opts) if branches.empty? opts[:fallback_type] else branches.reduce do |a, b| unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end end end |
.handle_case_node(node, **opts) ⇒ String?
module_function: defines #handle_case_node (visibility: private)
Handle ‘:case` node for last_expr_type.
381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/docscribe/infer/returns.rb', line 381 def handle_case_node(node, **opts) branches = process_case_branches(node, **opts) if branches.empty? opts[:fallback_type] else branches.reduce do |a, b| unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end end end |
.handle_cvar_node(node, **opts) ⇒ String?
module_function: defines #handle_cvar_node (visibility: private)
Handle ‘:cvar` node for last_expr_type — look up class variable in local_var_types.
269 270 271 272 |
# File 'lib/docscribe/infer/returns.rb', line 269 def handle_cvar_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type] end |
.handle_cvasgn_node(node, **opts) ⇒ String?
module_function: defines #handle_cvasgn_node (visibility: private)
Handle ‘:cvasgn` node for last_expr_type — look up class var assignment in local_var_types.
319 320 321 322 323 324 |
# File 'lib/docscribe/infer/returns.rb', line 319 def handle_cvasgn_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || run_last_expr_type(node.children[1], **opts) || opts[:fallback_type] end |
.handle_defined_node(_node, **opts) ⇒ String?
module_function: defines #handle_defined_node (visibility: private)
Handle ‘:defined?` node (`defined?(expr)`) for last_expr_type.
Returns ‘nil` if the expression is not defined, or a String description if it is defined. The union type is `String?`.
498 499 500 501 |
# File 'lib/docscribe/infer/returns.rb', line 498 def handle_defined_node(_node, **opts) nil_as_optional = opts.fetch(:nil_as_optional, true) nil_as_optional ? 'String?' : 'String, nil' end |
.handle_ensure_node(node, **opts) ⇒ String?
module_function: defines #handle_ensure_node (visibility: private)
Handle ‘:ensure` node (`begin; expr; ensure; cleanup; end`) for last_expr_type.
The ensure clause’s result is discarded by Ruby; only the body type is returned.
485 486 487 |
# File 'lib/docscribe/infer/returns.rb', line 485 def handle_ensure_node(node, **opts) run_last_expr_type(node.children[0], **opts) end |
.handle_gvar_node(node, **opts) ⇒ String?
module_function: defines #handle_gvar_node (visibility: private)
Handle ‘:gvar` node for last_expr_type — look up global variable in local_var_types.
258 259 260 261 |
# File 'lib/docscribe/infer/returns.rb', line 258 def handle_gvar_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type] end |
.handle_gvasgn_node(node, **opts) ⇒ String?
module_function: defines #handle_gvasgn_node (visibility: private)
Handle ‘:gvasgn` node for last_expr_type — look up global var assignment in local_var_types.
306 307 308 309 310 311 |
# File 'lib/docscribe/infer/returns.rb', line 306 def handle_gvasgn_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || run_last_expr_type(node.children[1], **opts) || opts[:fallback_type] end |
.handle_if_node(node, **opts) ⇒ String?
module_function: defines #handle_if_node (visibility: private)
Handle ‘:if` node for last_expr_type.
364 365 366 367 368 369 370 371 372 373 |
# File 'lib/docscribe/infer/returns.rb', line 364 def handle_if_node(node, **opts) t = run_last_expr_type(node.children[1], **opts) e = if node.children[2] run_last_expr_type(node.children[2], **opts) else 'nil' end unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end |
.handle_in_pattern_node(node, **opts) ⇒ String?
module_function: defines #handle_in_pattern_node (visibility: private)
Handle ‘:in_pattern` node (pattern inside `case…in`) for last_expr_type.
Extracts the body expression from the pattern and recurses.
570 571 572 |
# File 'lib/docscribe/infer/returns.rb', line 570 def handle_in_pattern_node(node, **opts) run_last_expr_type(node.children[2], **opts) end |
.handle_ivar_node(node, **opts) ⇒ String?
module_function: defines #handle_ivar_node (visibility: private)
Handle ‘:ivar` node for last_expr_type — look up instance variable in local_var_types.
247 248 249 250 |
# File 'lib/docscribe/infer/returns.rb', line 247 def handle_ivar_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type] end |
.handle_ivasgn_node(node, **opts) ⇒ String?
module_function: defines #handle_ivasgn_node (visibility: private)
Handle ‘:ivasgn` node for last_expr_type — look up ivar assignment in local_var_types.
293 294 295 296 297 298 |
# File 'lib/docscribe/infer/returns.rb', line 293 def handle_ivasgn_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || run_last_expr_type(node.children[1], **opts) || opts[:fallback_type] end |
.handle_kwbegin_node(node, **opts) ⇒ String?
module_function: defines #handle_kwbegin_node (visibility: private)
Handle ‘:kwbegin` node (`begin; expr; end`) for last_expr_type.
Unwraps the explicit begin node and delegates to the inner expression, which may be a ‘:rescue` or `:ensure` node.
434 435 436 |
# File 'lib/docscribe/infer/returns.rb', line 434 def handle_kwbegin_node(node, **opts) run_last_expr_type(node.children.first, **opts) end |
.handle_lvar_node(node, **opts) ⇒ String?
module_function: defines #handle_lvar_node (visibility: private)
Handle ‘:lvar` node for last_expr_type — look up the variable in local_var_types.
236 237 238 239 |
# File 'lib/docscribe/infer/returns.rb', line 236 def handle_lvar_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || opts[:fallback_type] end |
.handle_lvasgn_node(node, **opts) ⇒ String?
module_function: defines #handle_lvasgn_node (visibility: private)
Handle ‘:lvasgn` node for last_expr_type — look up local var assignment in local_var_types.
280 281 282 283 284 285 |
# File 'lib/docscribe/infer/returns.rb', line 280 def handle_lvasgn_node(node, **opts) name = node.children[0].to_s opts[:local_var_types]&.fetch(name, nil) || run_last_expr_type(node.children[1], **opts) || opts[:fallback_type] end |
.handle_op_asgn_node(node, **opts) ⇒ String?
module_function: defines #handle_op_asgn_node (visibility: private)
Handle ‘:op_asgn` node (compound assignment: `x += 1`, `@var -= 2`, etc.).
Infers the result type from the operator and the right operand’s type. Uses RBS to resolve when available (e.g., ‘Integer#+` -> `Integer`).
335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/docscribe/infer/returns.rb', line 335 def handle_op_asgn_node(node, **opts) meth = node.children[1] return nil unless %i[+ - * / % ** << | & ^].include?(meth) return nil unless opts[:core_rbs_provider] arg = node.children[2] arg_type = type_from_literal_safe(arg) return nil unless arg_type rbs = resolve_rbs_return_type(arg_type, meth, opts[:core_rbs_provider]) rbs unless rbs == FALLBACK_TYPE end |
.handle_or_node(node, **opts) ⇒ String?
module_function: defines #handle_or_node (visibility: private)
Handle ‘:or` node (`a || b`) for last_expr_type.
The result type is the union of both sides, since either may be returned depending on the truthiness of the left operand.
402 403 404 405 406 407 |
# File 'lib/docscribe/infer/returns.rb', line 402 def handle_or_node(node, **opts) t = run_last_expr_type(node.children[0], **opts) e = run_last_expr_type(node.children[1], **opts) unify_types(t, e, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end |
.handle_rescue_node(node, **opts) ⇒ String?
module_function: defines #handle_rescue_node (visibility: private)
Handle ‘:rescue` node for last_expr_type.
Supports both inline rescue (‘expr rescue default`) and block rescue (`begin; expr; rescue; e; end`).
447 448 449 450 451 452 453 |
# File 'lib/docscribe/infer/returns.rb', line 447 def handle_rescue_node(node, **opts) branches = collect_rescue_branches(node, **opts) branches.reduce do |a, b| unify_types(a, b, fallback_type: opts[:fallback_type] || 'untyped', nil_as_optional: opts.fetch(:nil_as_optional, true)) end end |
.handle_return_node(node, **opts) ⇒ String?
module_function: defines #handle_return_node (visibility: private)
Extract the return type from an explicit ‘:return` node.
834 835 836 |
# File 'lib/docscribe/infer/returns.rb', line 834 def handle_return_node(node, **opts) Literals.type_from_literal(node.children.first, fallback_type: opts[:fallback_type]) end |
.handle_send_node(node, **opts) ⇒ String?
module_function: defines #handle_send_node (visibility: private)
Handle ‘:send` node for last_expr_type.
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 |
# File 'lib/docscribe/infer/returns.rb', line 627 def handle_send_node(node, **opts) recv = node.children[0] meth = node.children[1] if opts[:core_rbs_provider] rbs_type = resolve_rbs_for_send(recv, meth, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) return rbs_type if rbs_type end compound_type = infer_from_compound_assign(node, **opts) return compound_type if compound_type Literals.type_from_literal(node, fallback_type: opts[:fallback_type]) end |
.handle_super_node(_node, **opts) ⇒ String?
module_function: defines #handle_super_node (visibility: private)
Handle ‘:super` node (`super(args)`) for last_expr_type.
Returns the super method’s return type if resolvable via RBS, or the fallback type otherwise.
525 526 527 |
# File 'lib/docscribe/infer/returns.rb', line 525 def handle_super_node(_node, **opts) opts[:fallback_type] end |
.handle_yield_node(_node, **opts) ⇒ String?
module_function: defines #handle_yield_node (visibility: private)
Handle ‘:yield` node (`yield` / `yield(args)`) for last_expr_type.
Returns the block’s return type if resolvable via RBS (‘Proc#call`), or the fallback type otherwise.
538 539 540 |
# File 'lib/docscribe/infer/returns.rb', line 538 def handle_yield_node(_node, **opts) opts[:fallback_type] end |
.handle_zsuper_node(_node, **opts) ⇒ String?
module_function: defines #handle_zsuper_node (visibility: private)
Handle ‘:zsuper` node (`super` with no arguments) for last_expr_type.
Returns the super method’s return type if resolvable via RBS, or the fallback type otherwise.
512 513 514 |
# File 'lib/docscribe/infer/returns.rb', line 512 def handle_zsuper_node(_node, **opts) opts[:fallback_type] end |
.infer_from_compound_assign(node, **opts) ⇒ String?
module_function: defines #infer_from_compound_assign (visibility: private)
Infer return type from a compound-assignment-like ‘:send` by reading the first literal argument’s type — only fires when ‘core_rbs_provider` is present and the argument’s RBS return type can be resolved.
Enables ‘@var = 123` -> `Integer` (via `Integer#`) and similar patterns.
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 |
# File 'lib/docscribe/infer/returns.rb', line 712 def infer_from_compound_assign(node, **opts) return nil unless opts[:core_rbs_provider] meth = node.children[1] return nil unless %i[+ - * / % ** << | & ^].include?(meth) first_arg = node.children[2] return nil unless first_arg arg_type = type_from_literal_safe(first_arg) return nil unless arg_type rbs = resolve_rbs_return_type(arg_type, meth, opts[:core_rbs_provider]) rbs unless rbs == FALLBACK_TYPE end |
.infer_normal_return_type(body, **opts) ⇒ String
module_function: defines #infer_normal_return_type (visibility: private)
Infer the normal (non-rescue) return type from a method body node.
117 118 119 |
# File 'lib/docscribe/infer/returns.rb', line 117 def infer_normal_return_type(body, **opts) run_last_expr_type(body, **opts) || FALLBACK_TYPE end |
.infer_return_type(method_source) ⇒ String, FALLBACK_TYPE
module_function: defines #infer_return_type (visibility: private)
Infer a return type from a full method definition source string.
The source must parse to a ‘:def` or `:defs` node. If parsing fails or inference is uncertain, the fallback type is returned.
19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/docscribe/infer/returns.rb', line 19 def infer_return_type(method_source) return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty? root = parse_method_source(method_source) return FALLBACK_TYPE unless root && %i[def defs].include?(root.type) body = root.children.last local_var_types = build_local_variable_types(body) run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, local_var_types: local_var_types) || FALLBACK_TYPE rescue Parser::SyntaxError # steep:ignore FALLBACK_TYPE end |
.infer_return_type_from_node(node) ⇒ String
module_function: defines #infer_return_type_from_node (visibility: private)
Infer a method’s normal return type from an already parsed def/defs node.
49 50 51 52 53 54 55 56 |
# File 'lib/docscribe/infer/returns.rb', line 49 def infer_return_type_from_node(node) body = extract_def_body(node) return FALLBACK_TYPE unless body local_var_types = build_local_variable_types(body) run_last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, local_var_types: local_var_types) || FALLBACK_TYPE end |
.last_expr_type(node, **opts) ⇒ String?
module_function: defines #last_expr_type (visibility: private)
Infer the type of the last expression in a node.
Supports:
-
‘begin` groups
-
‘if` branches
-
‘case` expressions
-
explicit ‘return`
-
literal-like expressions via Literals.type_from_literal
-
method calls with RBS core type lookup
806 807 808 |
# File 'lib/docscribe/infer/returns.rb', line 806 def last_expr_type(node, **opts) run_last_expr_type(node, **opts) end |
.lookup_lvar_type(lvar_name, local_var_types, param_types) ⇒ String?
module_function: defines #lookup_lvar_type (visibility: private)
Look up a local variable’s inferred type from local or parameter type maps.
766 767 768 769 770 771 |
# File 'lib/docscribe/infer/returns.rb', line 766 def lookup_lvar_type(lvar_name, local_var_types, param_types) return local_var_types[lvar_name.to_s] if local_var_types&.key?(lvar_name.to_s) return param_types[lvar_name.to_s] if param_types&.key?(lvar_name.to_s) nil end |
.parse_method_source(method_source) ⇒ Parser::AST::Node?
module_function: defines #parse_method_source (visibility: private)
Parse a Ruby source string into an AST using the Parser gem.
38 39 40 41 42 |
# File 'lib/docscribe/infer/returns.rb', line 38 def parse_method_source(method_source) buffer = Parser::Source::Buffer.new('(method)') buffer.source = method_source Docscribe::Parsing.parse_buffer(buffer) end |
.populate_returns_spec(spec, body, local_var_types, **opts) ⇒ Object
module_function: defines #populate_returns_spec (visibility: private)
Populate the spec hash with normal and/or rescue return types from the body.
103 104 105 106 107 108 109 |
# File 'lib/docscribe/infer/returns.rb', line 103 def populate_returns_spec(spec, body, local_var_types, **opts) if body.type == :rescue process_rescue_body(spec, body, **opts) else spec[:normal] = infer_normal_return_type(body, **opts, local_var_types: local_var_types) end end |
.process_case_branches(node, **opts) ⇒ Array<String>
module_function: defines #process_case_branches (visibility: private)
Extract inferred return types from all branches of a :case expression.
592 593 594 595 596 597 598 599 600 |
# File 'lib/docscribe/infer/returns.rb', line 592 def process_case_branches(node, **opts) (node.children[1..] || []).compact.flat_map do |child| if child.type == :when run_last_expr_type(child.children.last, **opts) else run_last_expr_type(child, **opts) end end.compact end |
.process_pattern_branches(node, **opts) ⇒ Array<String>
module_function: defines #process_pattern_branches (visibility: private)
Extract inferred return types from all in_pattern branches of a :case_match expression.
580 581 582 583 584 |
# File 'lib/docscribe/infer/returns.rb', line 580 def process_pattern_branches(node, **opts) (node.children[1..] || []).compact.filter_map do |child| run_last_expr_type(child, **opts) if child.is_a?(Parser::AST::Node) end end |
.process_rescue_body(spec, body, **opts) ⇒ Object
module_function: defines #process_rescue_body (visibility: private)
Process a :rescue body node and populate spec with normal + rescue return types.
128 129 130 131 132 133 134 135 136 |
# File 'lib/docscribe/infer/returns.rb', line 128 def process_rescue_body(spec, body, **opts) main_body = body.children[0] local_var_types = build_local_variable_types(body, core_rbs_provider: opts[:core_rbs_provider], param_types: opts[:param_types]) rescue_opts = opts.merge(local_var_types: local_var_types) spec[:normal] = run_last_expr_type(main_body, **rescue_opts) || FALLBACK_TYPE process_rescue_branches(spec, body, **rescue_opts) end |
.process_rescue_branches(spec, body, **opts) ⇒ Array<Object>
module_function: defines #process_rescue_branches (visibility: private)
Extract return types from each :resbody child and append to spec.
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/docscribe/infer/returns.rb', line 145 def process_rescue_branches(spec, body, **opts) body.children.each do |ch| next unless ch.is_a?(Parser::AST::Node) && ch.type == :resbody exc_list, _asgn, rescue_body = *ch exc_names = Raises.exception_names_from_rescue_list(exc_list) rtype = run_last_expr_type(rescue_body, **opts) || opts[:fallback_type] spec[:rescues] << [exc_names, rtype] end end |
.receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_rbs_type_name (visibility: private)
Map receiver AST node to RBS type name.
689 690 691 692 693 694 695 696 697 698 699 700 |
# File 'lib/docscribe/infer/returns.rb', line 689 def receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) return unless recv return LITERAL_RBS_TYPES[recv.type] if LITERAL_RBS_TYPES.key?(recv.type) return lookup_lvar_type(recv.children.first, local_var_types, param_types) if %i[lvar ivar gvar cvar].include?(recv.type) return unless recv.type == :send run_last_expr_type(recv, fallback_type: FALLBACK_TYPE, nil_as_optional: false, core_rbs_provider: core_rbs_provider, param_types: param_types, local_var_types: local_var_types) end |
.resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #resolve_chained_send_rbs (visibility: private)
Resolve RBS return type for a chained ‘:send` receiver.
782 783 784 785 786 787 788 789 790 |
# File 'lib/docscribe/infer/returns.rb', line 782 def resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) inner_type = run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false, core_rbs_provider: core_rbs_provider, param_types: param_types, local_var_types: local_var_types) return nil unless inner_type rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider) rbs_type unless rbs_type == FALLBACK_TYPE end |
.resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #resolve_lvar_rbs (visibility: private)
Resolve RBS return type for an ‘:lvar` receiver.
750 751 752 753 754 755 756 757 |
# File 'lib/docscribe/infer/returns.rb', line 750 def resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) lvar_name = recv&.children&.first recv_type = lookup_lvar_type(lvar_name, local_var_types, param_types) return nil unless recv_type rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider) rbs_type unless rbs_type == FALLBACK_TYPE end |
.resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #resolve_rbs_for_send (visibility: private)
Resolve RBS return type for a send node’s receiver, if possible.
Handles ‘:lvar`, chained `:send`, literal (`:int`, `:str`, etc.), and variable (`:ivar`, `:gvar`, `:cvar`) receivers.
655 656 657 658 659 660 661 662 663 |
# File 'lib/docscribe/infer/returns.rb', line 655 def resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types) return nil unless core_rbs_provider recv_type = receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) return nil unless recv_type rbs = resolve_rbs_return_type(recv_type, meth, core_rbs_provider) rbs unless rbs == FALLBACK_TYPE end |
.resolve_rbs_return_type(container_type, method_name, core_rbs_provider) ⇒ String
module_function: defines #resolve_rbs_return_type (visibility: private)
Resolve an RBS return type for a method call.
845 846 847 848 849 850 851 852 853 854 855 |
# File 'lib/docscribe/infer/returns.rb', line 845 def resolve_rbs_return_type(container_type, method_name, core_rbs_provider) return FALLBACK_TYPE unless core_rbs_provider sig = core_rbs_provider.signature_for( container: container_type, scope: :instance, name: method_name ) sig&.return_type || FALLBACK_TYPE end |
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) ⇒ Object
module_function: defines #returns_spec_from_node (visibility: private)
Return a structured return-type spec for a method node.
The result includes:
-
‘:normal` => normal/happy-path return type
-
‘:rescues` => array of `[exception_names, return_type]` pairs for rescue branches
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/docscribe/infer/returns.rb', line 71 def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) body = extract_def_body(node) spec = { normal: FALLBACK_TYPE, rescues: [] } #: Hash[Symbol, untyped] return spec unless body types = build_local_variable_types(body, core_rbs_provider: core_rbs_provider, param_types: param_types) populate_returns_spec(spec, body, types, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) spec end |
.run_last_expr_type(node, **opts) ⇒ String?
module_function: defines #run_last_expr_type (visibility: private)
Dispatch ‘last_expr_type` based on node type.
816 817 818 819 820 821 822 823 824 825 826 |
# File 'lib/docscribe/infer/returns.rb', line 816 def run_last_expr_type(node, **opts) return unless node type = node.type == :defined? ? :defined : node.type method_name = :"handle_#{type}_node" if respond_to?(method_name, true) send(method_name, node, **opts) else Literals.type_from_literal(node, fallback_type: opts[:fallback_type]) end end |
.type_from_literal_safe(node) ⇒ String?
module_function: defines #type_from_literal_safe (visibility: private)
Safely get a type string from a literal node, returning nil if the node is not a literal or yields no type.
734 735 736 737 738 739 |
# File 'lib/docscribe/infer/returns.rb', line 734 def type_from_literal_safe(node) return nil unless node t = Literals.type_from_literal(node, fallback_type: FALLBACK_TYPE) t unless t == FALLBACK_TYPE end |
.unify_nil_types(type_a, type_b, nil_as_optional:) ⇒ String
module_function: defines #unify_nil_types (visibility: private)
Unify two types where one may be ‘nil`, producing optional or union type.
885 886 887 888 889 890 891 892 |
# File 'lib/docscribe/infer/returns.rb', line 885 def unify_nil_types(type_a, type_b, nil_as_optional:) if type_a == 'nil' || type_b == 'nil' non_nil = (type_a == 'nil' ? type_b : type_a) return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil" end "#{type_a}, #{type_b}" end |
.unify_types(type_a, type_b, fallback_type:, nil_as_optional:) ⇒ String
module_function: defines #unify_types (visibility: private)
Unify two inferred types into a single type string.
Rules:
-
identical types remain unchanged
-
‘nil` unions may become optional types if enabled
-
otherwise falls back conservatively to ‘fallback_type`
870 871 872 873 874 875 876 |
# File 'lib/docscribe/infer/returns.rb', line 870 def unify_types(type_a, type_b, fallback_type:, nil_as_optional:) type_a ||= fallback_type type_b ||= fallback_type return type_a if type_a == type_b unify_nil_types(type_a, type_b, nil_as_optional: nil_as_optional) end |