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
:rescuenode 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. -
.container_rbs_return_type(meth, **opts) ⇒ String?
Resolve return type from the current method's container via RBS.
-
.extract_def_body(node) ⇒ Parser::AST::Node?
Extract the body child node from a
:defor:defsAST node. -
.handle_and_node(node, **opts) ⇒ String?
Handle
:andnode (a && b) for last_expr_type. -
.handle_begin_node(node, **opts) ⇒ String?
Handle
:beginnode for last_expr_type. -
.handle_block_node(node, **opts) ⇒ String?
Handle
:blocknode for last_expr_type. -
.handle_case_match_node(node, **opts) ⇒ String?
Handle
:case_matchnode (case x; in pat; expr; end) for last_expr_type. -
.handle_case_node(node, **opts) ⇒ String?
Handle
:casenode for last_expr_type. -
.handle_cvar_node(node, **opts) ⇒ String?
Handle
:cvarnode for last_expr_type — look up class variable in local_var_types. -
.handle_cvasgn_node(node, **opts) ⇒ String?
Handle
:cvasgnnode 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
:ensurenode (begin; expr; ensure; cleanup; end) for last_expr_type. -
.handle_gvar_node(node, **opts) ⇒ String?
Handle
:gvarnode for last_expr_type — look up global variable in local_var_types. -
.handle_gvasgn_node(node, **opts) ⇒ String?
Handle
:gvasgnnode for last_expr_type — look up global var assignment in local_var_types. -
.handle_if_node(node, **opts) ⇒ String?
Handle
:ifnode for last_expr_type. -
.handle_in_pattern_node(node, **opts) ⇒ String?
Handle
:in_patternnode (pattern insidecase...in) for last_expr_type. -
.handle_ivar_node(node, **opts) ⇒ String?
Handle
:ivarnode for last_expr_type — look up instance variable in local_var_types. -
.handle_ivasgn_node(node, **opts) ⇒ String?
Handle
:ivasgnnode for last_expr_type — look up ivar assignment in local_var_types. -
.handle_kwbegin_node(node, **opts) ⇒ String?
Handle
:kwbeginnode (begin; expr; end) for last_expr_type. -
.handle_lvar_node(node, **opts) ⇒ String?
Handle
:lvarnode for last_expr_type — look up the variable in local_var_types. -
.handle_lvasgn_node(node, **opts) ⇒ String?
Handle
:lvasgnnode for last_expr_type — look up local var assignment in local_var_types. -
.handle_op_asgn_node(node, **opts) ⇒ String?
Handle
:op_asgnnode (compound assignment:x += 1,@var -= 2, etc.). -
.handle_or_node(node, **opts) ⇒ String?
Handle
:ornode (a || b) for last_expr_type. -
.handle_rescue_node(node, **opts) ⇒ String?
Handle
:rescuenode for last_expr_type. -
.handle_return_node(node, **opts) ⇒ String?
Extract the return type from an explicit
:returnnode. -
.handle_send_node(node, **opts) ⇒ String?
Handle
:sendnode for last_expr_type. -
.handle_super_node(_node, **opts) ⇒ String?
Handle
:supernode (super(args)) for last_expr_type. -
.handle_yield_node(_node, **opts) ⇒ String?
Handle
:yieldnode (yield/yield(args)) for last_expr_type. -
.handle_zsuper_node(_node, **opts) ⇒ String?
Handle
:zsupernode (superwith no arguments) for last_expr_type. -
.infer_from_compound_assign(node, **opts) ⇒ String?
Infer return type from a compound-assignment-like
:sendby reading the first literal argument's type — only fires whencore_rbs_provideris 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
:sendreceiver. -
.resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for an
:lvarreceiver. -
.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_for_send_with_signature_provider(recv, meth, **opts) ⇒ String?
Resolve RBS return type via signature_provider fallback.
-
.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, container: nil, signature_provider: nil) ⇒ Object
Return a structured return-type spec for a method node.
-
.run_last_expr_type(node, **opts) ⇒ String?
Dispatch
last_expr_typebased on node type. -
.send_rbs_type(recv, meth, **opts) ⇒ String?
Resolve RBS return type for a send node, trying explicit receiver first, then falling back to the method's container for implicit self calls.
-
.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.
202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/docscribe/infer/returns.rb', line 202 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.
165 166 167 168 169 170 171 |
# File 'lib/docscribe/infer/returns.rb', line 165 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.
184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/docscribe/infer/returns.rb', line 184 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.
467 468 469 470 471 472 473 474 475 476 477 478 |
# File 'lib/docscribe/infer/returns.rb', line 467 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.
229 230 231 |
# File 'lib/docscribe/infer/returns.rb', line 229 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.
220 221 222 |
# File 'lib/docscribe/infer/returns.rb', line 220 def constant_name_and_value(node) [node.children[0].to_s, node.children[2]] end |
.container_rbs_return_type(meth, **opts) ⇒ String?
module_function: defines #container_rbs_return_type (visibility: private)
Resolve return type from the current method's container via RBS.
Handles implicit self calls (recv is nil) by looking up the method on the container class.
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 |
# File 'lib/docscribe/infer/returns.rb', line 711 def container_rbs_return_type(meth, **opts) return unless opts[:container] if opts[:core_rbs_provider] rbs = resolve_rbs_return_type(opts[:container], meth, opts[:core_rbs_provider]) return rbs unless rbs == FALLBACK_TYPE end if opts[:signature_provider] sig = opts[:signature_provider].signature_for(container: opts[:container], scope: :instance, name: meth) return sig.return_type if sig end nil 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.
91 92 93 94 95 96 |
# File 'lib/docscribe/infer/returns.rb', line 91 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.
421 422 423 424 425 426 |
# File 'lib/docscribe/infer/returns.rb', line 421 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.
357 358 359 |
# File 'lib/docscribe/infer/returns.rb', line 357 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.
611 612 613 614 615 616 617 618 619 |
# File 'lib/docscribe/infer/returns.rb', line 611 def handle_block_node(node, **opts) send_node = node.children[0] if send_node&.type == :send type = send_rbs_type(send_node.children[0], send_node.children[1], **opts) return type if 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.
553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/docscribe/infer/returns.rb', line 553 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.
384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/docscribe/infer/returns.rb', line 384 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.
272 273 274 275 |
# File 'lib/docscribe/infer/returns.rb', line 272 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.
322 323 324 325 326 327 |
# File 'lib/docscribe/infer/returns.rb', line 322 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?.
501 502 503 504 |
# File 'lib/docscribe/infer/returns.rb', line 501 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.
488 489 490 |
# File 'lib/docscribe/infer/returns.rb', line 488 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.
261 262 263 264 |
# File 'lib/docscribe/infer/returns.rb', line 261 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.
309 310 311 312 313 314 |
# File 'lib/docscribe/infer/returns.rb', line 309 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.
367 368 369 370 371 372 373 374 375 376 |
# File 'lib/docscribe/infer/returns.rb', line 367 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.
573 574 575 |
# File 'lib/docscribe/infer/returns.rb', line 573 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.
250 251 252 253 |
# File 'lib/docscribe/infer/returns.rb', line 250 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.
296 297 298 299 300 301 |
# File 'lib/docscribe/infer/returns.rb', line 296 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.
437 438 439 |
# File 'lib/docscribe/infer/returns.rb', line 437 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.
239 240 241 242 |
# File 'lib/docscribe/infer/returns.rb', line 239 def handle_lvar_node(node, **opts) name = node.children[0].to_s lookup_lvar_type(name, opts[:local_var_types], opts[:param_types]) || 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.
283 284 285 286 287 288 |
# File 'lib/docscribe/infer/returns.rb', line 283 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).
338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/docscribe/infer/returns.rb', line 338 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.
405 406 407 408 409 410 |
# File 'lib/docscribe/infer/returns.rb', line 405 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).
450 451 452 453 454 455 456 |
# File 'lib/docscribe/infer/returns.rb', line 450 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.
896 897 898 |
# File 'lib/docscribe/infer/returns.rb', line 896 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 |
# File 'lib/docscribe/infer/returns.rb', line 627 def handle_send_node(node, **opts) recv = node.children[0] meth = node.children[1] rbs_type = send_rbs_type(recv, meth, **opts) if opts[:core_rbs_provider] return rbs_type if rbs_type 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.
528 529 530 |
# File 'lib/docscribe/infer/returns.rb', line 528 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.
541 542 543 |
# File 'lib/docscribe/infer/returns.rb', line 541 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.
515 516 517 |
# File 'lib/docscribe/infer/returns.rb', line 515 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.
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 |
# File 'lib/docscribe/infer/returns.rb', line 774 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.
120 121 122 |
# File 'lib/docscribe/infer/returns.rb', line 120 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:
begingroupsifbranchescaseexpressions- explicit
return - literal-like expressions via Literals.type_from_literal
- method calls with RBS core type lookup
868 869 870 |
# File 'lib/docscribe/infer/returns.rb', line 868 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.
828 829 830 831 832 833 |
# File 'lib/docscribe/infer/returns.rb', line 828 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.
106 107 108 109 110 111 112 |
# File 'lib/docscribe/infer/returns.rb', line 106 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.
595 596 597 598 599 600 601 602 603 |
# File 'lib/docscribe/infer/returns.rb', line 595 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.
583 584 585 586 587 |
# File 'lib/docscribe/infer/returns.rb', line 583 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.
131 132 133 134 135 136 137 138 139 |
# File 'lib/docscribe/infer/returns.rb', line 131 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.
148 149 150 151 152 153 154 155 156 157 |
# File 'lib/docscribe/infer/returns.rb', line 148 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.
751 752 753 754 755 756 757 758 759 760 761 762 |
# File 'lib/docscribe/infer/returns.rb', line 751 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.
844 845 846 847 848 849 850 851 852 |
# File 'lib/docscribe/infer/returns.rb', line 844 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.
812 813 814 815 816 817 818 819 |
# File 'lib/docscribe/infer/returns.rb', line 812 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.
671 672 673 674 675 676 677 678 679 680 681 |
# File 'lib/docscribe/infer/returns.rb', line 671 def resolve_rbs_for_send(recv, meth, core_rbs_provider, local_var_types, param_types) recv_type = receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) return nil unless recv_type if core_rbs_provider rbs = resolve_rbs_return_type(recv_type, meth, core_rbs_provider) return rbs unless rbs == FALLBACK_TYPE end nil end |
.resolve_rbs_for_send_with_signature_provider(recv, meth, **opts) ⇒ String?
module_function: defines #resolve_rbs_for_send_with_signature_provider (visibility: private)
Resolve RBS return type via signature_provider fallback.
Tries project-level RBS when core_rbs_provider fails.
692 693 694 695 696 697 698 699 700 |
# File 'lib/docscribe/infer/returns.rb', line 692 def resolve_rbs_for_send_with_signature_provider(recv, meth, **opts) return nil unless opts[:signature_provider] recv_type = receiver_rbs_type_name(recv, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) return nil unless recv_type opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)&.return_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.
907 908 909 910 911 912 913 914 915 916 917 |
# File 'lib/docscribe/infer/returns.rb', line 907 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, container: nil, signature_provider: 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
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/docscribe/infer/returns.rb', line 73 def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, # rubocop:disable Metrics/ParameterLists param_types: nil, container: nil, signature_provider: 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, container: container, signature_provider: signature_provider) 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.
878 879 880 881 882 883 884 885 886 887 888 |
# File 'lib/docscribe/infer/returns.rb', line 878 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 |
.send_rbs_type(recv, meth, **opts) ⇒ String?
module_function: defines #send_rbs_type (visibility: private)
Resolve RBS return type for a send node, trying explicit receiver first, then falling back to the method's container for implicit self calls.
648 649 650 651 652 653 654 655 656 657 |
# File 'lib/docscribe/infer/returns.rb', line 648 def send_rbs_type(recv, meth, **opts) 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 rbs_type = resolve_rbs_for_send_with_signature_provider(recv, meth, **opts) return rbs_type if rbs_type container_rbs_return_type(meth, **opts) if recv.nil? 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.
796 797 798 799 800 801 |
# File 'lib/docscribe/infer/returns.rb', line 796 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.
947 948 949 950 951 952 953 954 |
# File 'lib/docscribe/infer/returns.rb', line 947 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
nilunions may become optional types if enabled- otherwise falls back conservatively to
fallback_type
932 933 934 935 936 937 938 |
# File 'lib/docscribe/infer/returns.rb', line 932 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 |