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
- .apply_generic_mapping(rbs, mapping, recv_type) ⇒ String
- .assignment_inferred_type(value, types, **opts) ⇒ String?
-
.assignment_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the variable name and value expression from an assignment node.
- .assignment_op_asgn_type(node, types, **opts) ⇒ String?
- .bare_container_type(rbs_type, inner) ⇒ String?
-
.block_arg_names(node) ⇒ Array<String>
Names of the block parameters, if any.
- .block_generic_substitution(rbs_type, inner) ⇒ String?
- .block_rbs_with_inner(rbs_type, block_body, **opts) ⇒ String?
- .block_receiver_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
- .block_send_rbs_type(node, send_node, **opts) ⇒ String?
- .both_clean_equal?(left_clean, right_clean) ⇒ Boolean
- .build_generic_mapping(recv_type, args) ⇒ Hash<String, String>
-
.build_local_variable_types(node, **opts) ⇒ Hash<String, String>?
Build a map of local/global/ivar/constant assignments to inferred types.
- .cleaned_recv_type(raw) ⇒ String?
- .coalesce_type(type, fallback_type) ⇒ String
-
.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_expr_opts(**opts) ⇒ Hash<Symbol, Object>
- .compound_fallback_type(left, right, meth, **opts) ⇒ String?
- .compound_left_type(recv, **opts) ⇒ String?
-
.compound_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the name and value from an
:op_asgn(compound assignment) node. - .compound_rbs_type(recv, left, meth, **opts) ⇒ String?
- .compound_right_type(arg, **opts) ⇒ String?
- .compound_var_lookup(recv, **opts) ⇒ String?
-
.const_owner_paths(container, scope_parts, absolute) ⇒ Array<String>
Candidate owner paths for a constant lookup, innermost first.
-
.const_path_segments(node) ⇒ (Array<String>, Boolean)?
Split a const node into static path segments and absoluteness.
-
.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.
-
.core_rbs_provider ⇒ Docscribe::Types::RBS::Provider?
Core RBS provider singleton for shovel/primitive checks (dynamic, not hardcoding).
- .enumerator_elem_from_recv(recv, **opts) ⇒ String?
- .enumerator_object_elem(recv_type) ⇒ String?
- .enumerator_recv_type(recv, **opts) ⇒ String?
- .extract_array_elem(type_str) ⇒ String?
- .extract_array_elem_strict(type_str) ⇒ String?
-
.extract_def_body(node) ⇒ Parser::AST::Node?
Extract the body child node from a
:defor:defsAST node. - .extract_enumerator_elem(type_str) ⇒ String?
-
.extract_generic_inner(type) ⇒ String?
Extract the inner generic args string from a receiver type like
Array<String>orHash<Integer, String>. -
.fallback_alias?(type_str, fallback_type) ⇒ Boolean
Whether a type string is the fallback alias (FALLBACK_TYPE or the configured fallback type).
- .fallback_concrete_type(left, right, fallback) ⇒ String
- .fallback_generic_substitution(rbs_type, inner) ⇒ String
- .fallback_preferred_side(left, right, left_is_fallback, right_is_fallback) ⇒ String?
- .fallback_type?(type, fallback) ⇒ Boolean
- .file_join_method?(meth, recv) ⇒ Boolean
- .fill_array_mapping(mapping, args) ⇒ void
- .fill_hash_mapping(mapping, args) ⇒ void
- .fill_mapping_for_base(mapping, base, args) ⇒ void
- .fill_other_mapping(mapping, args) ⇒ void
- .generic_placeholder?(rbs_type) ⇒ Boolean
-
.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_const_node(node, **opts) ⇒ String?
- .handle_csend_node(node, **opts) ⇒ String?
-
.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_map_block(node, meth, **opts) ⇒ String?
-
.handle_op_asgn_node(node, **opts) ⇒ String?
Handle
:op_asgnnode (compound assignment:x += 1,@var -= 2, etc.). -
.handle_or_asgn_node(node, **opts) ⇒ String?
Handle
:or_asgnnode (x ||= y) for last_expr_type. -
.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_then_block(node, meth, **opts) ⇒ String?
- .handle_to_h_block(node, meth, **opts) ⇒ String?
-
.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. - .hash_elem_from_enumerator(inner_recv, **opts) ⇒ String?
- .hash_inner_type(inner_recv, **opts) ⇒ String?
-
.infer_from_compound_assign(node, **opts) ⇒ String?
Infer return type from a compound-assignment-like
:send. -
.infer_normal_return_type(body, **opts) ⇒ String
Infer the normal (non-rescue) return type from a method body node.
-
.infer_pair_elem(node, **opts) ⇒ String?
Infer one pair element type, normalizing unknown to nil.
-
.infer_return_type(method_source) ⇒ String
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.
- .op_asgn_expr_opts(**opts) ⇒ Hash<Symbol, Object>
- .op_asgn_fallback_type(left, right, meth, **opts) ⇒ String?
- .op_asgn_left_type(lhs, **opts) ⇒ String?
- .op_asgn_lookup_type(lhs, name, **opts) ⇒ String?
- .op_asgn_rbs_type(lhs, left, meth, **opts) ⇒ String?
- .op_asgn_right_type(rhs, **opts) ⇒ String?
- .op_asgn_var_name(lhs) ⇒ String?
-
.or_asgn_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
Extract the name and value from an
:or_asgn(||=) node. -
.or_prefer_concrete(left_type, right_type, fallback) ⇒ String?
Prefer the concrete side when the other is a fallback alias.
-
.pair_elem_type(node, arg_names, position, **opts) ⇒ String?
Infer one pair element, honoring block parameter positions.
-
.pair_literal?(body) ⇒ Boolean
Whether the block body is a
[k, v]pair literal. -
.parse_method_source(method_source) ⇒ Parser::AST::Node?
Parse a Ruby source string into an AST using the Parser gem.
- .placeholder_token?(token) ⇒ Boolean
- .placeholder_tokens(inner_generic) ⇒ Array<String>
-
.populate_returns_spec(spec, body, local_var_types, **opts) ⇒ void
Populate the spec hash with normal and/or rescue return types from the body.
- .populate_spec_with_types(spec, body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, **opts) ⇒ void
-
.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) ⇒ void
Process a :rescue body node and populate spec with normal + rescue return types.
-
.process_rescue_branches(spec, body, **opts) ⇒ void
Extract return types from each :resbody child and append to spec.
- .rbs_fallback(meth, recv, **opts) ⇒ String?
- .receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
- .receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
- .receiver_literal_type(recv) ⇒ String?
- .receiver_or_and_preference(left_clean, right_clean, left, right) ⇒ String?
- .receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
-
.receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
Map receiver AST node to RBS type name.
- .receiver_send_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
- .receiver_var_type(recv, local_var_types, param_types) ⇒ String?
-
.rescue_branch_type(rescue_body, **opts) ⇒ String?
Infer a rescue branch type, resolving data constants precisely.
-
.resolve_chained_send_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for a chained
:sendreceiver. - .resolve_cleaned_type(type) ⇒ String?
- .resolve_compound_rbs(recv_type, meth, **opts) ⇒ String?
-
.resolve_const_value_type(node, container) ⇒ String?
Resolve a const node to the YARD type of its runtime value.
-
.resolve_lvar_rbs(recv, meth, core_rbs_provider, local_var_types, param_types) ⇒ String?
Resolve RBS return type for an
:lvarreceiver. - .resolve_op_asgn_rbs(recv_type, meth, **opts) ⇒ String?
-
.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.
- .resolve_self_via_rbs?(base, meth, provider) ⇒ Boolean
-
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, **opts) ⇒ Hash<Symbol, 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. -
.runtime_const_lookup(owner_path, const_name) ⇒ (Boolean, Object), Array
Look up a constant in this process without side effects.
-
.safe_const_path(parts) ⇒ Module?
Resolve a namespace path in this process without side effects.
-
.safe_const_step(mod, name) ⇒ Module?
Resolve one namespace step without side effects.
-
.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.
- .shovel_array_type(left_str, right_str, base, fallback) ⇒ String
- .shovel_left_generic?(str) ⇒ Boolean
-
.shovel_method?(left, meth, provider) ⇒ Boolean
Whether left#meth is shovel (returns self) via RBS dynamically, not hardcoding :<<.
- .shovel_right_invalid?(str, fallback) ⇒ Boolean
- .single_clean_preference(left_clean, right_clean) ⇒ String?
-
.split_generic_args(inner) ⇒ Array<String>
Split a generic inner string like
Integer, Array<(Integer, String)>by top-level commas. - .split_handle_bracket(chr, state) ⇒ void
- .split_handle_comma(state, strip:) ⇒ void
- .split_process_char(chr, state, strip:) ⇒ void
-
.split_top_level_commas(str) ⇒ Array<String>
Split a type string by top-level commas (outside any < > [ ] ( ) nesting).
- .string_like_method?(meth) ⇒ Boolean
- .string_send_type(meth, recv) ⇒ String?
- .stripped_union_type(raw) ⇒ String?
- .sub_string_method?(meth, recv) ⇒ Boolean
- .substitute_placeholders(rbs_type, placeholders, inner) ⇒ String
-
.substitute_rbs_type(rbs, recv_type) ⇒ String
Substitute
selfand generic type variables in an RBS return type with the concrete receiver type. - .substitute_self_type(rbs, recv_type) ⇒ String?
- .substitute_with_mapping(rbs, recv_type, args) ⇒ String
- .synthesize_shovel_type(left, right, fallback:) ⇒ String
- .synthetic_enumerator_type(node, meth, recv, **opts) ⇒ String?
- .synthetic_hash_type(_node, meth, recv, **opts) ⇒ String?
-
.to_h_block_pair_types(body, arg_names, **opts) ⇒ (String, nil, String, nil)
Infer key/value types from a
[k, v]pair literal block body. -
.to_h_each_recv(node) ⇒ Parser::AST::Node?
Receiver of
to_hwhen it chains offeach_with_index. -
.to_h_key_value(node, recv, **opts) ⇒ (String, String)
Resolve key/value types for a
to_hblock. - .try_compound(node, **opts) ⇒ String?
- .try_rbs(meth, recv, **opts) ⇒ String?
- .try_synthetic(node, meth, recv, **opts) ⇒ String?
-
.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.
-
.unknown_type?(type) ⇒ Boolean
Whether a type string means "could not determine".
- .var_receiver?(recv) ⇒ Boolean
-
.yard_type_for_const_value(value) ⇒ String?
Map a constant's runtime value to a YARD type name.
Class Method Details
.apply_generic_mapping(rbs, mapping, recv_type) ⇒ String
module_function: defines #apply_generic_mapping (visibility: private)
2285 2286 2287 2288 2289 2290 2291 2292 2293 |
# File 'lib/docscribe/infer/returns.rb', line 2285 def apply_generic_mapping(rbs, mapping, recv_type) stripped = rbs.delete_suffix('?').strip optional = rbs.end_with?('?') return optional ? "#{mapping[stripped]}?" : mapping[stripped] if mapping.key?(stripped) new_rbs = rbs.dup mapping.each { |var, val| new_rbs = new_rbs.gsub(/\b#{Regexp.escape(var)}\b/, val) } new_rbs.include?('self') ? new_rbs.gsub(/\bself\b/, recv_type) : new_rbs end |
.assignment_inferred_type(value, types, **opts) ⇒ String?
module_function: defines #assignment_inferred_type (visibility: private)
235 236 237 238 239 |
# File 'lib/docscribe/infer/returns.rb', line 235 def assignment_inferred_type(value, types, **opts) run_last_expr_type(value, fallback_type: FALLBACK_TYPE, nil_as_optional: false, local_var_types: types, core_rbs_provider: opts[:core_rbs_provider], param_types: opts[:param_types], signature_provider: opts[:signature_provider], container: opts[:container]) end |
.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.
257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/docscribe/infer/returns.rb', line 257 def assignment_name_and_value(node) return [nil, nil] unless node.is_a?(Parser::AST::Node) case node.type when :lvasgn, :gvasgn, :ivasgn, :cvasgn then [node.children[0].to_s, node.children[1]] when :casgn then constant_name_and_value(node) when :op_asgn then compound_name_and_value(node) when :or_asgn then or_asgn_name_and_value(node) else [nil, nil] end end |
.assignment_op_asgn_type(node, types, **opts) ⇒ String?
module_function: defines #assignment_op_asgn_type (visibility: private)
246 247 248 249 250 |
# File 'lib/docscribe/infer/returns.rb', line 246 def assignment_op_asgn_type(node, types, **opts) run_last_expr_type(node, fallback_type: FALLBACK_TYPE, nil_as_optional: false, local_var_types: types, core_rbs_provider: opts[:core_rbs_provider], param_types: opts[:param_types], signature_provider: opts[:signature_provider], container: opts[:container]) end |
.bare_container_type(rbs_type, inner) ⇒ String?
module_function: defines #bare_container_type (visibility: private)
1068 1069 1070 1071 1072 1073 1074 1075 1076 |
# File 'lib/docscribe/infer/returns.rb', line 1068 def (rbs_type, inner) return nil unless rbs_type.is_a?(String) base = rbs_type&.split(/[<\[ ]/)&.first return nil unless %w[Array Set Enumerable Enumerator].include?(base) return nil if rbs_type.include?('<') || rbs_type.include?('[') "#{base}<#{inner}>" end |
.block_arg_names(node) ⇒ Array<String>
module_function: defines #block_arg_names (visibility: private)
Names of the block parameters, if any.
902 903 904 905 906 907 |
# File 'lib/docscribe/infer/returns.rb', line 902 def block_arg_names(node) args = node.children[1] return [] unless args&.type == :args args.children.filter_map { |a| a.children[0]&.to_s } end |
.block_generic_substitution(rbs_type, inner) ⇒ String?
module_function: defines #block_generic_substitution (visibility: private)
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 |
# File 'lib/docscribe/infer/returns.rb', line 1019 def block_generic_substitution(rbs_type, inner) return nil unless generic_placeholder?(rbs_type) inner_generic = extract_generic_inner(rbs_type) return nil unless inner_generic placeholders = placeholder_tokens(inner_generic) result = substitute_placeholders(rbs_type, placeholders, inner) return result unless result == rbs_type fallback_generic_substitution(rbs_type, inner) end |
.block_rbs_with_inner(rbs_type, block_body, **opts) ⇒ String?
module_function: defines #block_rbs_with_inner (visibility: private)
1005 1006 1007 1008 1009 1010 1011 1012 1013 |
# File 'lib/docscribe/infer/returns.rb', line 1005 def block_rbs_with_inner(rbs_type, block_body, **opts) inner = run_last_expr_type(block_body, **opts) return nil unless inner substituted = block_generic_substitution(rbs_type, inner) return substituted if substituted (rbs_type, inner) end |
.block_receiver_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #block_receiver_type (visibility: private)
1740 1741 1742 1743 1744 1745 |
# File 'lib/docscribe/infer/returns.rb', line 1740 def block_receiver_type(recv, core_rbs_provider, local_var_types, param_types) run_last_expr_type(recv, fallback_type: FALLBACK_TYPE, nil_as_optional: false, core_rbs_provider: core_rbs_provider, local_var_types: local_var_types, param_types: param_types) || receiver_send_type(recv.children[0], core_rbs_provider, local_var_types, param_types) end |
.block_send_rbs_type(node, send_node, **opts) ⇒ String?
module_function: defines #block_send_rbs_type (visibility: private)
993 994 995 996 997 998 |
# File 'lib/docscribe/infer/returns.rb', line 993 def block_send_rbs_type(node, send_node, **opts) rbs_type = send_rbs_type(send_node.children[0], send_node.children[1], **opts) return nil unless rbs_type block_rbs_with_inner(rbs_type, node.children[2], **opts) || rbs_type end |
.both_clean_equal?(left_clean, right_clean) ⇒ Boolean
module_function: defines #both_clean_equal? (visibility: private)
1816 1817 1818 |
# File 'lib/docscribe/infer/returns.rb', line 1816 def both_clean_equal?(left_clean, right_clean) left_clean && right_clean && left_clean == right_clean end |
.build_generic_mapping(recv_type, args) ⇒ Hash<String, String>
module_function: defines #build_generic_mapping (visibility: private)
2235 2236 2237 2238 2239 2240 |
# File 'lib/docscribe/infer/returns.rb', line 2235 def build_generic_mapping(recv_type, args) base = recv_type.split(/[<\[ ]/).first.to_s.strip mapping = {} #: Hash[String, String] fill_mapping_for_base(mapping, base, args) mapping 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.
199 200 201 202 203 204 205 |
# File 'lib/docscribe/infer/returns.rb', line 199 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 |
.cleaned_recv_type(raw) ⇒ String?
module_function: defines #cleaned_recv_type (visibility: private)
1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 |
# File 'lib/docscribe/infer/returns.rb', line 1600 def cleaned_recv_type(raw) # rubocop:disable SortedMethodsByCall/Waterfall return nil unless raw && raw != FALLBACK_TYPE str = raw.to_s.strip return nil if str.empty? str = stripped_union_type(str) || str if str.include?(',') cleaned = str.delete_suffix('?').strip cleaned.empty? ? nil : cleaned end |
.coalesce_type(type, fallback_type) ⇒ String
module_function: defines #coalesce_type (visibility: private)
2392 2393 2394 2395 2396 2397 |
# File 'lib/docscribe/infer/returns.rb', line 2392 def coalesce_type(type, fallback_type) normalized = type || fallback_type normalized = fallback_type if normalized == 'FALLBACK_TYPE' normalized = 'Object' if normalized == 'untyped' && fallback_type == 'Object' normalized 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.
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/docscribe/infer/returns.rb', line 218 def collect_assignment_type(node, types, **opts) name, value = assignment_name_and_value(node) return unless name && value inferred = if %i[op_asgn or_asgn].include?(node.type) assignment_op_asgn_type(node, types, **opts) else assignment_inferred_type(value, types, **opts) 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.
689 690 691 692 693 694 695 696 697 698 699 700 |
# File 'lib/docscribe/infer/returns.rb', line 689 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_expr_opts(**opts) ⇒ Hash<Symbol, Object>
module_function: defines #compound_expr_opts (visibility: private)
1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 |
# File 'lib/docscribe/infer/returns.rb', line 1502 def compound_expr_opts(**opts) { fallback_type: opts[:fallback_type] || FALLBACK_TYPE, nil_as_optional: true, local_var_types: opts[:local_var_types], param_types: opts[:param_types], core_rbs_provider: opts[:core_rbs_provider], signature_provider: opts[:signature_provider], container: opts[:container] } end |
.compound_fallback_type(left, right, meth, **opts) ⇒ String?
module_function: defines #compound_fallback_type (visibility: private)
1552 1553 1554 1555 1556 1557 1558 1559 |
# File 'lib/docscribe/infer/returns.rb', line 1552 def compound_fallback_type(left, right, meth, **opts) fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s return synthesize_shovel_type(left, right, fallback: fallback) if shovel_method?(left, meth, opts[:core_rbs_provider]) return nil unless %i[+ - * / % ** | & ^].include?(meth) fallback_concrete_type(left, right, fallback) || unify_types(left, right, fallback_type: fallback, nil_as_optional: true) end |
.compound_left_type(recv, **opts) ⇒ String?
module_function: defines #compound_left_type (visibility: private)
1464 1465 1466 1467 1468 1469 1470 1471 |
# File 'lib/docscribe/infer/returns.rb', line 1464 def compound_left_type(recv, **opts) return nil unless recv found = compound_var_lookup(recv, **opts) return found if found run_last_expr_type(recv, **compound_expr_opts(**opts)) 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.
283 284 285 |
# File 'lib/docscribe/infer/returns.rb', line 283 def compound_name_and_value(node) [node.children[0].children.first.to_s, node.children[2]] end |
.compound_rbs_type(recv, left, meth, **opts) ⇒ String?
module_function: defines #compound_rbs_type (visibility: private)
1520 1521 1522 1523 1524 1525 1526 1527 |
# File 'lib/docscribe/infer/returns.rb', line 1520 def compound_rbs_type(recv, left, meth, **opts) recv_type = cleaned_recv_type(left) || receiver_rbs_type_name(recv, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) return nil unless recv_type && meth resolve_compound_rbs(recv_type, meth, **opts) end |
.compound_right_type(arg, **opts) ⇒ String?
module_function: defines #compound_right_type (visibility: private)
1493 1494 1495 1496 1497 |
# File 'lib/docscribe/infer/returns.rb', line 1493 def compound_right_type(arg, **opts) return nil unless arg run_last_expr_type(arg, **compound_expr_opts(**opts)) end |
.compound_var_lookup(recv, **opts) ⇒ String?
module_function: defines #compound_var_lookup (visibility: private)
1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 |
# File 'lib/docscribe/infer/returns.rb', line 1477 def compound_var_lookup(recv, **opts) return nil unless recv.is_a?(Parser::AST::Node) return nil unless %i[lvar ivar gvar cvar].include?(recv&.type) name = recv&.children&.[](0).to_s if recv&.type == :lvar lookup_lvar_type(name, opts[:local_var_types], opts[:param_types]) else opts[:local_var_types]&.fetch(name, nil) end end |
.const_owner_paths(container, scope_parts, absolute) ⇒ Array<String>
module_function: defines #const_owner_paths (visibility: private)
Candidate owner paths for a constant lookup, innermost first.
Flat strings keep generic inference (and RubyMine) happy — nested
arrays lose a level (first(n) degrades to Elem).
2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 |
# File 'lib/docscribe/infer/returns.rb', line 2075 def const_owner_paths(container, scope_parts, absolute) return [scope_parts.join('::')] if absolute segments = container.to_s.split('::').grep(/\A[A-Z]\w*\z/) # NOTE: `take` (not `first`) keeps RBS generic inference precise — # `first(n)` loses one nesting level. paths = segments.length.downto(1).map { |n| (segments.take(n) + scope_parts).join('::') } paths << scope_parts.join('::') paths end |
.const_path_segments(node) ⇒ (Array<String>, Boolean)?
module_function: defines #const_path_segments (visibility: private)
Split a const node into static path segments and absoluteness.
2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 |
# File 'lib/docscribe/infer/returns.rb', line 2052 def const_path_segments(node) parts = [] #: Array[String] current = node while current.is_a?(Parser::AST::Node) && current.type == :const parts.unshift(current.children[1].to_s) current = current.children[0] end return nil if current && !(current.is_a?(Parser::AST::Node) && current.type == :cbase) absolute = !current.nil? [parts, absolute] 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.
274 275 276 |
# File 'lib/docscribe/infer/returns.rb', line 274 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.
1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 |
# File 'lib/docscribe/infer/returns.rb', line 1407 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 substitute_rbs_type(rbs, opts[:container]) unless rbs == FALLBACK_TYPE end if opts[:signature_provider] sig = opts[:signature_provider].signature_for(container: opts[:container], scope: :instance, name: meth) return substitute_rbs_type(sig.return_type, opts[:container]) if sig end nil end |
.core_rbs_provider ⇒ Docscribe::Types::RBS::Provider?
module_function: defines #core_rbs_provider (visibility: private)
Core RBS provider singleton for shovel/primitive checks (dynamic, not hardcoding).
1659 1660 1661 1662 1663 1664 1665 1666 |
# File 'lib/docscribe/infer/returns.rb', line 1659 def core_rbs_provider @core_rbs_provider ||= begin require_relative '../types/rbs/provider' Docscribe::Types::RBS::Provider.new(sig_dirs: ['sig'], collection_dirs: []) rescue LoadError, StandardError nil end end |
.enumerator_elem_from_recv(recv, **opts) ⇒ String?
module_function: defines #enumerator_elem_from_recv (visibility: private)
1180 1181 1182 1183 1184 1185 |
# File 'lib/docscribe/infer/returns.rb', line 1180 def enumerator_elem_from_recv(recv, **opts) recv_type = enumerator_recv_type(recv, **opts) return nil unless recv_type extract_array_elem_strict(recv_type) || enumerator_object_elem(recv_type) end |
.enumerator_object_elem(recv_type) ⇒ String?
module_function: defines #enumerator_object_elem (visibility: private)
1214 1215 1216 1217 1218 |
# File 'lib/docscribe/infer/returns.rb', line 1214 def enumerator_object_elem(recv_type) return 'Object' if recv_type == 'Array' nil end |
.enumerator_recv_type(recv, **opts) ⇒ String?
module_function: defines #enumerator_recv_type (visibility: private)
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 |
# File 'lib/docscribe/infer/returns.rb', line 1191 def enumerator_recv_type(recv, **opts) receiver_rbs_type_name(recv, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) || run_last_expr_type(recv, fallback_type: nil, nil_as_optional: false, local_var_types: opts[:local_var_types], param_types: opts[:param_types], core_rbs_provider: opts[:core_rbs_provider], signature_provider: opts[:signature_provider], container: opts[:container]) end |
.extract_array_elem(type_str) ⇒ String?
module_function: defines #extract_array_elem (visibility: private)
1263 1264 1265 1266 1267 1268 1269 1270 |
# File 'lib/docscribe/infer/returns.rb', line 1263 def extract_array_elem(type_str) return nil unless type_str =~ /\AArray<(.+)>\z/ cand = Regexp.last_match(1).strip return nil if cand.empty? || %w[Object untyped].include?(cand) cand end |
.extract_array_elem_strict(type_str) ⇒ String?
module_function: defines #extract_array_elem_strict (visibility: private)
1205 1206 1207 1208 1209 |
# File 'lib/docscribe/infer/returns.rb', line 1205 def extract_array_elem_strict(type_str) return nil unless type_str =~ /\AArray<(.+)>\z/ Regexp.last_match(1).strip 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.
105 106 107 108 109 110 |
# File 'lib/docscribe/infer/returns.rb', line 105 def extract_def_body(node) case node&.type when :def then node&.children&.[](2) when :defs then node&.children&.[](3) end end |
.extract_enumerator_elem(type_str) ⇒ String?
module_function: defines #extract_enumerator_elem (visibility: private)
1275 1276 1277 1278 1279 1280 1281 1282 |
# File 'lib/docscribe/infer/returns.rb', line 1275 def extract_enumerator_elem(type_str) return nil unless type_str =~ /\AEnumerator<(.+),\s*Integer>\z/ cand = Regexp.last_match(1).strip return nil if cand.empty? cand end |
.extract_generic_inner(type) ⇒ String?
module_function: defines #extract_generic_inner (visibility: private)
Extract the inner generic args string from a receiver type like Array<String> or Hash<Integer, String>.
2300 2301 2302 2303 2304 |
# File 'lib/docscribe/infer/returns.rb', line 2300 def extract_generic_inner(type) return unless type =~ /\A(?:Array|Hash|Set|Enumerable)[<\[](.*)[>\]]\z/m || type =~ /\A[^<\[\]]+[<\[](.*)[>\]]\z/m Regexp.last_match(1) end |
.fallback_alias?(type_str, fallback_type) ⇒ Boolean
module_function: defines #fallback_alias? (visibility: private)
Whether a type string is the fallback alias (FALLBACK_TYPE or the configured fallback type).
2160 2161 2162 2163 2164 2165 |
# File 'lib/docscribe/infer/returns.rb', line 2160 def fallback_alias?(type_str, fallback_type) return false if type_str.nil? s = type_str.to_s.strip.delete_suffix('?').strip s == fallback_type || s == 'FALLBACK_TYPE' || (fallback_type == 'Object' && s == 'untyped') end |
.fallback_concrete_type(left, right, fallback) ⇒ String
module_function: defines #fallback_concrete_type (visibility: private)
1566 1567 1568 1569 1570 1571 1572 1573 1574 |
# File 'lib/docscribe/infer/returns.rb', line 1566 def fallback_concrete_type(left, right, fallback) left_is_fallback = fallback_type?(left, fallback) right_is_fallback = fallback_type?(right, fallback) preferred = fallback_preferred_side(left, right, left_is_fallback, right_is_fallback) return preferred if preferred return fallback if left_is_fallback && right_is_fallback nil end |
.fallback_generic_substitution(rbs_type, inner) ⇒ String
module_function: defines #fallback_generic_substitution (visibility: private)
1059 1060 1061 1062 |
# File 'lib/docscribe/infer/returns.rb', line 1059 def fallback_generic_substitution(rbs_type, inner) rbs_type.gsub(/\bU\b/, inner).gsub(/\bElem\b/, inner).gsub(/\buntyped\b/, inner) .gsub(/\bV\b/, inner).gsub(/\bT\b/, inner).gsub(/\bE\b/, inner).gsub(/\bK\b/, inner) end |
.fallback_preferred_side(left, right, left_is_fallback, right_is_fallback) ⇒ String?
module_function: defines #fallback_preferred_side (visibility: private)
1582 1583 1584 1585 1586 1587 |
# File 'lib/docscribe/infer/returns.rb', line 1582 def fallback_preferred_side(left, right, left_is_fallback, right_is_fallback) return right.to_s if left_is_fallback && !right_is_fallback && right return left.to_s if right_is_fallback && !left_is_fallback && left nil end |
.fallback_type?(type, fallback) ⇒ Boolean
module_function: defines #fallback_type? (visibility: private)
1593 1594 1595 |
# File 'lib/docscribe/infer/returns.rb', line 1593 def fallback_type?(type, fallback) type.nil? || fallback_alias?(type, fallback) end |
.file_join_method?(meth, recv) ⇒ Boolean
module_function: defines #file_join_method? (visibility: private)
1307 1308 1309 |
# File 'lib/docscribe/infer/returns.rb', line 1307 def file_join_method?(meth, recv) meth == :join && recv&.type == :const && recv&.children&.[](1) == :File end |
.fill_array_mapping(mapping, args) ⇒ void
module_function: defines #fill_array_mapping (visibility: private)
This method returns an undefined value.
2268 2269 2270 |
# File 'lib/docscribe/infer/returns.rb', line 2268 def fill_array_mapping(mapping, args) %w[Elem T U E].each { |key| mapping[key] = args[0] if args[0] } end |
.fill_hash_mapping(mapping, args) ⇒ void
module_function: defines #fill_hash_mapping (visibility: private)
This method returns an undefined value.
2259 2260 2261 2262 |
# File 'lib/docscribe/infer/returns.rb', line 2259 def fill_hash_mapping(mapping, args) mapping['K'] = args[0] if args[0] mapping['V'] = args[1] if args[1] end |
.fill_mapping_for_base(mapping, base, args) ⇒ void
module_function: defines #fill_mapping_for_base (visibility: private)
This method returns an undefined value.
2247 2248 2249 2250 2251 2252 2253 |
# File 'lib/docscribe/infer/returns.rb', line 2247 def fill_mapping_for_base(mapping, base, args) case base when 'Hash' then fill_hash_mapping(mapping, args) when 'Array', 'Set', 'Enumerable', 'Enumerator' then fill_array_mapping(mapping, args) else fill_other_mapping(mapping, args) end end |
.fill_other_mapping(mapping, args) ⇒ void
module_function: defines #fill_other_mapping (visibility: private)
This method returns an undefined value.
2276 2277 2278 |
# File 'lib/docscribe/infer/returns.rb', line 2276 def fill_other_mapping(mapping, args) %w[Elem T U].each { |key| mapping[key] = args[0] if args[0] } end |
.generic_placeholder?(rbs_type) ⇒ Boolean
module_function: defines #generic_placeholder? (visibility: private)
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 |
# File 'lib/docscribe/infer/returns.rb', line 1081 def generic_placeholder?(rbs_type) return false unless rbs_type =~ /[<\[]/ inner = extract_generic_inner(rbs_type) return false unless inner split_generic_args(inner).any? do |arg| placeholder_token?(arg.strip.delete_suffix('?').strip) 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.
643 644 645 646 647 648 |
# File 'lib/docscribe/infer/returns.rb', line 643 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.
537 538 539 |
# File 'lib/docscribe/infer/returns.rb', line 537 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.
832 833 834 835 836 837 838 839 840 841 842 |
# File 'lib/docscribe/infer/returns.rb', line 832 def handle_block_node(node, **opts) send_node = node.children[0] return run_last_expr_type(node.children[2], **opts) unless send_node&.type == :send meth = send_node.children[1] handle_then_block(node, meth, **opts) || handle_to_h_block(node, meth, **opts) || block_send_rbs_type(node, send_node, **opts) || handle_map_block(node, meth, **opts) || 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.
775 776 777 778 779 780 781 782 783 784 785 |
# File 'lib/docscribe/infer/returns.rb', line 775 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.
564 565 566 567 568 569 570 571 572 573 574 |
# File 'lib/docscribe/infer/returns.rb', line 564 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_const_node(node, **opts) ⇒ String?
module_function: defines #handle_const_node (visibility: private)
2008 2009 2010 2011 2012 2013 2014 2015 2016 |
# File 'lib/docscribe/infer/returns.rb', line 2008 def handle_const_node(node, **opts) fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s #: String const_name = node.children.last.to_s #: String resolved = Literals.type_from_literal(node, fallback_type: fallback) return fallback if fallback_alias?(resolved, fallback) return fallback if fallback_alias?(const_name, fallback) resolved end |
.handle_csend_node(node, **opts) ⇒ String?
module_function: defines #handle_csend_node (visibility: private)
1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 |
# File 'lib/docscribe/infer/returns.rb', line 1323 def handle_csend_node(node, **opts) recv = node.children[0] meth = node.children[1] rbs_type = send_rbs_type(recv, meth, **opts) if opts[:core_rbs_provider] || opts[:signature_provider] if rbs_type unify_types(rbs_type, 'nil', fallback_type: opts[:fallback_type] || FALLBACK_TYPE, nil_as_optional: opts.fetch(:nil_as_optional, true)) else opts[:fallback_type] || FALLBACK_TYPE 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.
341 342 343 344 |
# File 'lib/docscribe/infer/returns.rb', line 341 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.
391 392 393 394 395 396 |
# File 'lib/docscribe/infer/returns.rb', line 391 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?.
723 724 725 726 |
# File 'lib/docscribe/infer/returns.rb', line 723 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.
710 711 712 |
# File 'lib/docscribe/infer/returns.rb', line 710 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.
330 331 332 333 |
# File 'lib/docscribe/infer/returns.rb', line 330 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.
378 379 380 381 382 383 |
# File 'lib/docscribe/infer/returns.rb', line 378 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.
547 548 549 550 551 552 553 554 555 556 |
# File 'lib/docscribe/infer/returns.rb', line 547 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.
795 796 797 |
# File 'lib/docscribe/infer/returns.rb', line 795 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.
319 320 321 322 |
# File 'lib/docscribe/infer/returns.rb', line 319 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.
365 366 367 368 369 370 |
# File 'lib/docscribe/infer/returns.rb', line 365 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.
659 660 661 |
# File 'lib/docscribe/infer/returns.rb', line 659 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.
308 309 310 311 |
# File 'lib/docscribe/infer/returns.rb', line 308 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.
352 353 354 355 356 357 |
# File 'lib/docscribe/infer/returns.rb', line 352 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_map_block(node, meth, **opts) ⇒ String?
module_function: defines #handle_map_block (visibility: private)
979 980 981 982 983 984 985 986 |
# File 'lib/docscribe/infer/returns.rb', line 979 def handle_map_block(node, meth, **opts) return nil unless %i[map collect].include?(meth) inner = run_last_expr_type(node.children[2], **opts) return nil unless inner && inner != 'Object' && inner != 'untyped' "Array<#{inner}>" 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.).
RBS -> Infer: try RBS for meth on receiver type, else unify left/right keeping String? via nil_as_optional:true. No hardcoded operator list.
407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/docscribe/infer/returns.rb', line 407 def handle_op_asgn_node(node, **opts) meth = node.children[1] lhs = node.children[0] rhs = node.children[2] left = op_asgn_left_type(lhs, **opts) right = op_asgn_right_type(rhs, **opts) rbs = op_asgn_rbs_type(lhs, left, meth, **opts) return rbs if rbs op_asgn_fallback_type(left, right, meth, **opts) end |
.handle_or_asgn_node(node, **opts) ⇒ String?
module_function: defines #handle_or_asgn_node (visibility: private)
Handle :or_asgn node (x ||= y) for last_expr_type.
Same type semantics as ||: the assignment target counts as the left
side, so an unknown receiver with a concrete literal right-hand side
(e.g. @h ||= Hash.new) infers the literal type.
608 609 610 611 612 613 614 615 616 617 |
# File 'lib/docscribe/infer/returns.rb', line 608 def handle_or_asgn_node(node, **opts) t = run_last_expr_type(node.children[0], **opts) e = run_last_expr_type(node.children[1], **opts) fallback = opts[:fallback_type] || 'untyped' preferred = or_prefer_concrete(t, e, fallback) return preferred if preferred unify_types(t, e, fallback_type: fallback, nil_as_optional: opts.fetch(:nil_as_optional, true)) 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.
585 586 587 588 589 590 591 592 593 594 595 596 |
# File 'lib/docscribe/infer/returns.rb', line 585 def handle_or_node(node, **opts) t = run_last_expr_type(node.children[0], **opts) e = run_last_expr_type(node.children[1], **opts) fallback = opts[:fallback_type] || 'untyped' # If one side is the fallback alias (FALLBACK_TYPE / fallback_type) and the other is concrete, prefer the concrete # This prevents `sig&.return_type || FALLBACK_TYPE` from becoming `String, Object` when String is known preferred = or_prefer_concrete(t, e, fallback) return preferred if preferred unify_types(t, e, fallback_type: fallback, 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).
672 673 674 675 676 677 678 |
# File 'lib/docscribe/infer/returns.rb', line 672 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.
2000 2001 2002 |
# File 'lib/docscribe/infer/returns.rb', line 2000 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.
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 |
# File 'lib/docscribe/infer/returns.rb', line 1106 def handle_send_node(node, **opts) recv = node.children[0] meth = node.children[1] try_synthetic(node, meth, recv, **opts) || try_rbs(meth, recv, **opts) || try_compound(node, **opts) || string_send_type(meth, recv) || rbs_fallback(meth, recv, **opts) || 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.
750 751 752 |
# File 'lib/docscribe/infer/returns.rb', line 750 def handle_super_node(_node, **opts) opts[:fallback_type] end |
.handle_then_block(node, meth, **opts) ⇒ String?
module_function: defines #handle_then_block (visibility: private)
849 850 851 852 853 |
# File 'lib/docscribe/infer/returns.rb', line 849 def handle_then_block(node, meth, **opts) return nil unless %i[then yield_self].include?(meth) run_last_expr_type(node.children[2], **opts) end |
.handle_to_h_block(node, meth, **opts) ⇒ String?
module_function: defines #handle_to_h_block (visibility: private)
860 861 862 863 864 865 866 867 868 |
# File 'lib/docscribe/infer/returns.rb', line 860 def handle_to_h_block(node, meth, **opts) return nil unless meth == :to_h recv = to_h_each_recv(node) return nil unless recv key, value = to_h_key_value(node, recv, **opts) "Hash<#{key}, #{value}>" 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.
763 764 765 |
# File 'lib/docscribe/infer/returns.rb', line 763 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.
737 738 739 |
# File 'lib/docscribe/infer/returns.rb', line 737 def handle_zsuper_node(_node, **opts) opts[:fallback_type] end |
.hash_elem_from_enumerator(inner_recv, **opts) ⇒ String?
module_function: defines #hash_elem_from_enumerator (visibility: private)
1238 1239 1240 1241 1242 1243 |
# File 'lib/docscribe/infer/returns.rb', line 1238 def hash_elem_from_enumerator(inner_recv, **opts) inner_type = hash_inner_type(inner_recv, **opts) return nil unless inner_type extract_array_elem(inner_type) || extract_enumerator_elem(inner_type) end |
.hash_inner_type(inner_recv, **opts) ⇒ String?
module_function: defines #hash_inner_type (visibility: private)
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 |
# File 'lib/docscribe/infer/returns.rb', line 1249 def hash_inner_type(inner_recv, **opts) receiver_rbs_type_name(inner_recv, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) || run_last_expr_type(inner_recv, fallback_type: nil, nil_as_optional: false, local_var_types: opts[:local_var_types], param_types: opts[:param_types], core_rbs_provider: opts[:core_rbs_provider], signature_provider: opts[:signature_provider], container: opts[:container]) 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.
RBS -> Infer: no hardcoded operator list, always try left/right via Infer, then RBS for meth on receiver, else unify with String? handling.
1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 |
# File 'lib/docscribe/infer/returns.rb', line 1448 def infer_from_compound_assign(node, **opts) meth = node.children[1] recv = node.children[0] arg = node.children[2] left = compound_left_type(recv, **opts) right = compound_right_type(arg, **opts) rbs = compound_rbs_type(recv, left, meth, **opts) return rbs if rbs compound_fallback_type(left, right, meth, **opts) 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.
134 135 136 |
# File 'lib/docscribe/infer/returns.rb', line 134 def infer_normal_return_type(body, **opts) run_last_expr_type(body, **opts) || FALLBACK_TYPE end |
.infer_pair_elem(node, **opts) ⇒ String?
module_function: defines #infer_pair_elem (visibility: private)
Infer one pair element type, normalizing unknown to nil.
960 961 962 963 |
# File 'lib/docscribe/infer/returns.rb', line 960 def infer_pair_elem(node, **opts) type = run_last_expr_type(node, **opts) type unless unknown_type?(type) end |
.infer_return_type(method_source) ⇒ String
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.
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/docscribe/infer/returns.rb', line 21 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 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.
51 52 53 54 55 56 57 58 |
# File 'lib/docscribe/infer/returns.rb', line 51 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
1972 1973 1974 |
# File 'lib/docscribe/infer/returns.rb', line 1972 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.
1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 |
# File 'lib/docscribe/infer/returns.rb', line 1927 def lookup_lvar_type(lvar_name, local_var_types, param_types) if local_var_types&.key?(lvar_name.to_s) val = local_var_types[lvar_name.to_s] return nil if val == FALLBACK_TYPE return val end return param_types[lvar_name.to_s] if param_types&.key?(lvar_name.to_s) nil end |
.op_asgn_expr_opts(**opts) ⇒ Hash<Symbol, Object>
module_function: defines #op_asgn_expr_opts (visibility: private)
473 474 475 476 477 478 479 480 481 482 483 |
# File 'lib/docscribe/infer/returns.rb', line 473 def op_asgn_expr_opts(**opts) { fallback_type: opts[:fallback_type] || FALLBACK_TYPE, nil_as_optional: true, local_var_types: opts[:local_var_types], param_types: opts[:param_types], core_rbs_provider: opts[:core_rbs_provider], signature_provider: opts[:signature_provider], container: opts[:container] } end |
.op_asgn_fallback_type(left, right, meth, **opts) ⇒ String?
module_function: defines #op_asgn_fallback_type (visibility: private)
523 524 525 526 527 528 529 |
# File 'lib/docscribe/infer/returns.rb', line 523 def op_asgn_fallback_type(left, right, meth, **opts) fallback = (opts[:fallback_type] || FALLBACK_TYPE).to_s return synthesize_shovel_type(left, right, fallback: fallback) if shovel_method?(left, meth, opts[:core_rbs_provider]) fallback_concrete_type(left, right, fallback) || unify_types(left, right, fallback_type: fallback, nil_as_optional: true) end |
.op_asgn_left_type(lhs, **opts) ⇒ String?
module_function: defines #op_asgn_left_type (visibility: private)
423 424 425 426 427 428 429 430 |
# File 'lib/docscribe/infer/returns.rb', line 423 def op_asgn_left_type(lhs, **opts) name = op_asgn_var_name(lhs) if name found = op_asgn_lookup_type(lhs, name, **opts) return found if found end run_last_expr_type(lhs, **op_asgn_expr_opts(**opts)) end |
.op_asgn_lookup_type(lhs, name, **opts) ⇒ String?
module_function: defines #op_asgn_lookup_type (visibility: private)
449 450 451 452 453 454 455 456 457 458 |
# File 'lib/docscribe/infer/returns.rb', line 449 def op_asgn_lookup_type(lhs, name, **opts) return nil unless lhs.is_a?(Parser::AST::Node) case lhs&.type when :lvasgn lookup_lvar_type(name, opts[:local_var_types], opts[:param_types]) when :ivasgn, :gvasgn, :cvasgn, :casgn opts[:local_var_types]&.fetch(name, nil) end end |
.op_asgn_rbs_type(lhs, left, meth, **opts) ⇒ String?
module_function: defines #op_asgn_rbs_type (visibility: private)
491 492 493 494 495 496 497 498 |
# File 'lib/docscribe/infer/returns.rb', line 491 def op_asgn_rbs_type(lhs, left, meth, **opts) recv = cleaned_recv_type(left) || receiver_rbs_type_name(lhs, opts[:core_rbs_provider], opts[:local_var_types], opts[:param_types]) return nil unless recv && meth resolve_op_asgn_rbs(recv, meth, **opts) end |
.op_asgn_right_type(rhs, **opts) ⇒ String?
module_function: defines #op_asgn_right_type (visibility: private)
464 465 466 467 468 |
# File 'lib/docscribe/infer/returns.rb', line 464 def op_asgn_right_type(rhs, **opts) return nil unless rhs run_last_expr_type(rhs, **op_asgn_expr_opts(**opts)) end |
.op_asgn_var_name(lhs) ⇒ String?
module_function: defines #op_asgn_var_name (visibility: private)
435 436 437 438 439 440 441 442 |
# File 'lib/docscribe/infer/returns.rb', line 435 def op_asgn_var_name(lhs) return nil unless lhs.is_a?(Parser::AST::Node) case lhs&.type when :lvasgn, :ivasgn, :gvasgn, :cvasgn then lhs&.children&.[](0).to_s when :casgn then lhs&.children&.[](1).to_s end end |
.or_asgn_name_and_value(node) ⇒ (String, nil, Parser::AST::Node, nil)
module_function: defines #or_asgn_name_and_value (visibility: private)
Extract the name and value from an :or_asgn (||=) node.
Unlike :op_asgn (three children: target, operator, value), :or_asgn
carries only target and value.
295 296 297 298 299 300 |
# File 'lib/docscribe/infer/returns.rb', line 295 def or_asgn_name_and_value(node) target = node.children[0] return [nil, nil] unless target.is_a?(Parser::AST::Node) [target.children.first.to_s, node.children[1]] end |
.or_prefer_concrete(left_type, right_type, fallback) ⇒ String?
module_function: defines #or_prefer_concrete (visibility: private)
Prefer the concrete side when the other is a fallback alias.
626 627 628 629 630 631 632 |
# File 'lib/docscribe/infer/returns.rb', line 626 def or_prefer_concrete(left_type, right_type, fallback) if fallback_alias?(left_type, fallback) && !fallback_alias?(right_type, fallback) right_type elsif fallback_alias?(right_type, fallback) && !fallback_alias?(left_type, fallback) left_type end end |
.pair_elem_type(node, arg_names, position, **opts) ⇒ String?
module_function: defines #pair_elem_type (visibility: private)
Infer one pair element, honoring block parameter positions.
946 947 948 949 950 951 952 |
# File 'lib/docscribe/infer/returns.rb', line 946 def pair_elem_type(node, arg_names, position, **opts) if node&.type == :lvar && node.children[0].to_s == arg_names[position] return position == 1 ? 'Integer' : nil end infer_pair_elem(node, **opts) end |
.pair_literal?(body) ⇒ Boolean
module_function: defines #pair_literal? (visibility: private)
Whether the block body is a [k, v] pair literal.
933 934 935 936 |
# File 'lib/docscribe/infer/returns.rb', line 933 def pair_literal?(body) body = body.children.last if body&.type == :begin body&.type == :array && body.children.size == 2 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.
40 41 42 43 44 |
# File 'lib/docscribe/infer/returns.rb', line 40 def parse_method_source(method_source) buffer = Parser::Source::Buffer.new('(method)') buffer.source = method_source Docscribe::Parsing.parse_buffer(buffer) end |
.placeholder_token?(token) ⇒ Boolean
module_function: defines #placeholder_token? (visibility: private)
1095 1096 1097 1098 |
# File 'lib/docscribe/infer/returns.rb', line 1095 def placeholder_token?(token) !!(token == 'untyped' || token.include?('::') || token =~ /\A[a-z]/ || (token =~ /\A[A-Z][A-Za-z0-9_]*\z/ && !Docscribe::Types::Primitive.primitive?(token))) end |
.placeholder_tokens(inner_generic) ⇒ Array<String>
module_function: defines #placeholder_tokens (visibility: private)
1035 1036 1037 1038 1039 |
# File 'lib/docscribe/infer/returns.rb', line 1035 def placeholder_tokens(inner_generic) split_generic_args(inner_generic).select do |arg| placeholder_token?(arg.strip.delete_suffix('?').strip) end end |
.populate_returns_spec(spec, body, local_var_types, **opts) ⇒ void
module_function: defines #populate_returns_spec (visibility: private)
This method returns an undefined value.
Populate the spec hash with normal and/or rescue return types from the body.
120 121 122 123 124 125 126 |
# File 'lib/docscribe/infer/returns.rb', line 120 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 |
.populate_spec_with_types(spec, body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, **opts) ⇒ void
module_function: defines #populate_spec_with_types (visibility: private)
This method returns an undefined value.
91 92 93 94 95 96 97 98 |
# File 'lib/docscribe/infer/returns.rb', line 91 def populate_spec_with_types(spec, body, fallback_type: FALLBACK_TYPE, nil_as_optional: true, **opts) core_rbs_provider = opts[:core_rbs_provider] types = build_local_variable_types(body, core_rbs_provider: core_rbs_provider, param_types: opts[: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: opts[:param_types], container: opts[:container], signature_provider: opts[:signature_provider]) 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.
817 818 819 820 821 822 823 824 |
# File 'lib/docscribe/infer/returns.rb', line 817 def process_case_branches(node, **opts) children = (node.children[1..] || []).compact branches = children.flat_map do |child| child.type == :when ? run_last_expr_type(child.children.last, **opts) : run_last_expr_type(child, **opts) end.compact branches << 'nil' unless children.last && children.last.type != :when branches 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.
805 806 807 808 809 |
# File 'lib/docscribe/infer/returns.rb', line 805 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) ⇒ void
module_function: defines #process_rescue_body (visibility: private)
This method returns an undefined value.
Process a :rescue body node and populate spec with normal + rescue return types.
145 146 147 148 149 150 151 152 153 |
# File 'lib/docscribe/infer/returns.rb', line 145 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) ⇒ void
module_function: defines #process_rescue_branches (visibility: private)
This method returns an undefined value.
Extract return types from each :resbody child and append to spec.
162 163 164 165 166 167 168 169 170 171 |
# File 'lib/docscribe/infer/returns.rb', line 162 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 = rescue_branch_type(rescue_body, **opts) || opts[:fallback_type] spec[:rescues] << [exc_names, rtype] end end |
.rbs_fallback(meth, recv, **opts) ⇒ String?
module_function: defines #rbs_fallback (visibility: private)
1155 1156 1157 1158 1159 |
# File 'lib/docscribe/infer/returns.rb', line 1155 def rbs_fallback(meth, recv, **opts) return nil unless opts[:core_rbs_provider] send_rbs_type(recv, meth, **opts) end |
.receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_begin_type (visibility: private)
1753 1754 1755 1756 1757 1758 1759 1760 |
# File 'lib/docscribe/infer/returns.rb', line 1753 def receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types) return nil unless recv.is_a?(Parser::AST::Node) inner = recv&.children&.[](0) return unless inner && recv&.children&.size == 1 receiver_rbs_type_name(inner, core_rbs_provider, local_var_types, param_types) end |
.receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_dispatch_type (visibility: private)
1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 |
# File 'lib/docscribe/infer/returns.rb', line 1723 def receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types) return nil unless recv.is_a?(Parser::AST::Node) case recv.type when :send, :csend then receiver_send_type(recv, core_rbs_provider, local_var_types, param_types) when :block then block_receiver_type(recv, core_rbs_provider, local_var_types, param_types) when :or, :and then receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types) when :begin then receiver_begin_type(recv, core_rbs_provider, local_var_types, param_types) end end |
.receiver_literal_type(recv) ⇒ String?
module_function: defines #receiver_literal_type (visibility: private)
1823 1824 1825 1826 1827 |
# File 'lib/docscribe/infer/returns.rb', line 1823 def receiver_literal_type(recv) return nil unless recv.is_a?(Parser::AST::Node) LITERAL_RBS_TYPES[recv&.type] if LITERAL_RBS_TYPES.key?(recv&.type) end |
.receiver_or_and_preference(left_clean, right_clean, left, right) ⇒ String?
module_function: defines #receiver_or_and_preference (visibility: private)
1793 1794 1795 1796 1797 1798 1799 |
# File 'lib/docscribe/infer/returns.rb', line 1793 def receiver_or_and_preference(left_clean, right_clean, left, right) preferred = single_clean_preference(left_clean, right_clean) return preferred if preferred return left_clean if both_clean_equal?(left_clean, right_clean) left_clean || right_clean || left || right end |
.receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_or_and_type (visibility: private)
1768 1769 1770 1771 1772 1773 1774 1775 1776 |
# File 'lib/docscribe/infer/returns.rb', line 1768 def receiver_or_and_type(recv, core_rbs_provider, local_var_types, param_types) return nil unless recv.is_a?(Parser::AST::Node) left = receiver_rbs_type_name(recv&.children&.[](0), core_rbs_provider, local_var_types, param_types) right = receiver_rbs_type_name(recv&.children&.[](1), core_rbs_provider, local_var_types, param_types) left_clean = resolve_cleaned_type(left) right_clean = resolve_cleaned_type(right) receiver_or_and_preference(left_clean, right_clean, left, right) 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.
1707 1708 1709 1710 1711 1712 1713 1714 1715 |
# File 'lib/docscribe/infer/returns.rb', line 1707 def receiver_rbs_type_name(recv, core_rbs_provider, local_var_types, param_types) return unless recv literal = receiver_literal_type(recv) return literal if literal return receiver_var_type(recv, local_var_types, param_types) if var_receiver?(recv) receiver_dispatch_type(recv, core_rbs_provider, local_var_types, param_types) end |
.receiver_send_type(recv, core_rbs_provider, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_send_type (visibility: private)
1883 1884 1885 1886 1887 |
# File 'lib/docscribe/infer/returns.rb', line 1883 def receiver_send_type(recv, core_rbs_provider, local_var_types, param_types) 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 |
.receiver_var_type(recv, local_var_types, param_types) ⇒ String?
module_function: defines #receiver_var_type (visibility: private)
1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 |
# File 'lib/docscribe/infer/returns.rb', line 1843 def receiver_var_type(recv, local_var_types, param_types) raw = lookup_lvar_type(recv.children.first, local_var_types, param_types) return nil unless raw cleaned = raw.include?(',') ? stripped_union_type(raw) : raw cleaned = cleaned.to_s.strip.delete_suffix('?').strip return nil if cleaned.empty? || cleaned == 'FALLBACK_TYPE' cleaned end |
.rescue_branch_type(rescue_body, **opts) ⇒ String?
module_function: defines #rescue_branch_type (visibility: private)
Infer a rescue branch type, resolving data constants precisely.
Rescue branches form the documented contract (@return [X] if Error),
so a bare data constant (e.g., FALLBACK_TYPE, whose runtime value is
the String 'Object') infers as its value type instead of the bare
fallback. Other nodes use the standard path unchanged.
184 185 186 187 188 189 190 191 |
# File 'lib/docscribe/infer/returns.rb', line 184 def rescue_branch_type(rescue_body, **opts) if rescue_body.is_a?(Parser::AST::Node) && rescue_body.type == :const resolve_const_value_type(rescue_body, opts[:container]) || run_last_expr_type(rescue_body, **opts) else run_last_expr_type(rescue_body, **opts) end 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.
1948 1949 1950 1951 1952 1953 1954 1955 1956 |
# File 'lib/docscribe/infer/returns.rb', line 1948 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_cleaned_type(type) ⇒ String?
module_function: defines #resolve_cleaned_type (visibility: private)
1781 1782 1783 1784 1785 |
# File 'lib/docscribe/infer/returns.rb', line 1781 def resolve_cleaned_type(type) return nil unless type cleaned_recv_type(type) || type end |
.resolve_compound_rbs(recv_type, meth, **opts) ⇒ String?
module_function: defines #resolve_compound_rbs (visibility: private)
1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 |
# File 'lib/docscribe/infer/returns.rb', line 1534 def resolve_compound_rbs(recv_type, meth, **opts) if opts[:core_rbs_provider] rbs = resolve_rbs_return_type(recv_type, meth, opts[:core_rbs_provider]) return substitute_rbs_type(rbs, recv_type) unless rbs == FALLBACK_TYPE end if opts[:signature_provider] sig = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth) return substitute_rbs_type(sig.return_type, recv_type) if sig end nil end |
.resolve_const_value_type(node, container) ⇒ String?
module_function: defines #resolve_const_value_type (visibility: private)
Resolve a const node to the YARD type of its runtime value.
Looks the constant up in this process through the analyzed lexical
scope (innermost container outward, then top level) and maps its
value's class to a type name. Only constants actually present here
resolve — user code is never loaded, so unknown names safely fall
through to the fallback path. Classes and modules are skipped so
references like String keep name-based inference.
2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 |
# File 'lib/docscribe/infer/returns.rb', line 2031 def resolve_const_value_type(node, container) segments, absolute = const_path_segments(node) return nil if segments.empty? *scope_parts, const_name = segments const_owner_paths(container, scope_parts, absolute).each do |owner_path| found, value = runtime_const_lookup(owner_path, const_name) next unless found type = yard_type_for_const_value(value) return type if type end nil 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.
1911 1912 1913 1914 1915 1916 1917 1918 |
# File 'lib/docscribe/infer/returns.rb', line 1911 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_op_asgn_rbs(recv_type, meth, **opts) ⇒ String?
module_function: defines #resolve_op_asgn_rbs (visibility: private)
505 506 507 508 509 510 511 512 513 514 515 |
# File 'lib/docscribe/infer/returns.rb', line 505 def resolve_op_asgn_rbs(recv_type, meth, **opts) if opts[:core_rbs_provider] rbs = resolve_rbs_return_type(recv_type, meth, opts[:core_rbs_provider]) return substitute_rbs_type(rbs, recv_type) unless rbs == FALLBACK_TYPE end if opts[:signature_provider] sig = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth) return substitute_rbs_type(sig.return_type, recv_type) if sig end nil 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.
1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 |
# File 'lib/docscribe/infer/returns.rb', line 1366 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 substitute_rbs_type(rbs, recv_type) 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.
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 |
# File 'lib/docscribe/infer/returns.rb', line 1387 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 rbs = opts[:signature_provider].signature_for(container: recv_type, scope: :instance, name: meth)&.return_type rbs ? substitute_rbs_type(rbs, recv_type) : nil 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.
2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 |
# File 'lib/docscribe/infer/returns.rb', line 2174 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 |
.resolve_self_via_rbs?(base, meth, provider) ⇒ Boolean
module_function: defines #resolve_self_via_rbs? (visibility: private)
1647 1648 1649 1650 1651 |
# File 'lib/docscribe/infer/returns.rb', line 1647 def resolve_self_via_rbs?(base, meth, provider) return false unless provider resolve_rbs_return_type(base, meth, provider) == 'self' end |
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, **opts) ⇒ Hash<Symbol, 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 |
# 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, **opts) body = extract_def_body(node) spec = { normal: FALLBACK_TYPE, rescues: [] } #: Hash[Symbol, untyped] return spec unless body populate_spec_with_types(spec, body, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, **opts) 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.
1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 |
# File 'lib/docscribe/infer/returns.rb', line 1982 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 |
.runtime_const_lookup(owner_path, const_name) ⇒ (Boolean, Object), Array
module_function: defines #runtime_const_lookup (visibility: private)
Look up a constant in this process without side effects.
2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 |
# File 'lib/docscribe/infer/returns.rb', line 2094 def runtime_const_lookup(owner_path, const_name) owner = owner_path.empty? ? Object : safe_const_path(owner_path.split('::')) return [false, nil] unless owner.is_a?(Module) return [false, nil] unless owner.const_defined?(const_name, false) return [false, nil] if owner.autoload?(const_name) [true, owner.const_get(const_name, false)] rescue StandardError [false, nil] end |
.safe_const_path(parts) ⇒ Module?
module_function: defines #safe_const_path (visibility: private)
Resolve a namespace path in this process without side effects.
2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 |
# File 'lib/docscribe/infer/returns.rb', line 2110 def safe_const_path(parts) mod = Object #: Module parts.each do |name| fetched = safe_const_step(mod, name) return nil unless fetched mod = fetched end mod end |
.safe_const_step(mod, name) ⇒ Module?
module_function: defines #safe_const_step (visibility: private)
Resolve one namespace step without side effects.
2129 2130 2131 2132 2133 2134 2135 2136 |
# File 'lib/docscribe/infer/returns.rb', line 2129 def safe_const_step(mod, name) return nil unless mod.const_defined?(name, false) && !mod.autoload?(name) fetched = mod.const_get(name, false) fetched if fetched.is_a?(Module) rescue StandardError nil 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.
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 |
# File 'lib/docscribe/infer/returns.rb', line 1343 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 |
.shovel_array_type(left_str, right_str, base, fallback) ⇒ String
module_function: defines #shovel_array_type (visibility: private)
1674 1675 1676 1677 1678 1679 1680 1681 1682 |
# File 'lib/docscribe/infer/returns.rb', line 1674 def shovel_array_type(left_str, right_str, base, fallback) return left_str if shovel_left_generic?(left_str) return left_str if shovel_right_invalid?(right_str, fallback) cleaned = right_str.to_s.strip.delete_suffix('?').strip return left_str if cleaned == 'nil' || cleaned.empty? || cleaned == FALLBACK_TYPE "#{base}<#{cleaned}>" end |
.shovel_left_generic?(str) ⇒ Boolean
module_function: defines #shovel_left_generic? (visibility: private)
1687 1688 1689 |
# File 'lib/docscribe/infer/returns.rb', line 1687 def shovel_left_generic?(str) str.include?('<') || str.include?('[') end |
.shovel_method?(left, meth, provider) ⇒ Boolean
module_function: defines #shovel_method? (visibility: private)
Whether left#meth is shovel (returns self) via RBS dynamically, not hardcoding :<<.
1632 1633 1634 1635 1636 1637 1638 1639 1640 |
# File 'lib/docscribe/infer/returns.rb', line 1632 def shovel_method?(left, meth, provider) return false unless left && meth base = left.split(/[<\[ ]/).first.to_s.strip.delete_suffix('?') return false if base.empty? return true if resolve_self_via_rbs?(base, meth, provider) resolve_self_via_rbs?(base, meth, core_rbs_provider) end |
.shovel_right_invalid?(str, fallback) ⇒ Boolean
module_function: defines #shovel_right_invalid? (visibility: private)
1695 1696 1697 |
# File 'lib/docscribe/infer/returns.rb', line 1695 def shovel_right_invalid?(str, fallback) str == fallback || %w[Object untyped].include?(str) end |
.single_clean_preference(left_clean, right_clean) ⇒ String?
module_function: defines #single_clean_preference (visibility: private)
1805 1806 1807 1808 1809 1810 |
# File 'lib/docscribe/infer/returns.rb', line 1805 def single_clean_preference(left_clean, right_clean) return left_clean if left_clean && !right_clean return right_clean if right_clean && !left_clean nil end |
.split_generic_args(inner) ⇒ Array<String>
module_function: defines #split_generic_args (visibility: private)
Split a generic inner string like Integer, Array<(Integer, String)> by top-level commas.
Respects nesting of < > [ ] ( ) so tuples are not split.
2313 2314 2315 2316 2317 2318 2319 |
# File 'lib/docscribe/infer/returns.rb', line 2313 def split_generic_args(inner) state = { parts: [], cur: +'', da: 0, db: 0, dp: 0 } #: Hash[Symbol, untyped] inner.each_char { |chr| split_process_char(chr, state, strip: true) } last = state[:cur].strip state[:parts] << last unless last.empty? state[:parts] end |
.split_handle_bracket(chr, state) ⇒ void
module_function: defines #split_handle_bracket (visibility: private)
This method returns an undefined value.
2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 |
# File 'lib/docscribe/infer/returns.rb', line 2341 def split_handle_bracket(chr, state) case chr when '<' then state[:da] += 1 when '>' then state[:da] -= 1 when '[' then state[:db] += 1 when ']' then state[:db] -= 1 when '(' then state[:dp] += 1 when ')' then state[:dp] -= 1 end state[:cur] << chr end |
.split_handle_comma(state, strip:) ⇒ void
module_function: defines #split_handle_comma (visibility: private)
This method returns an undefined value.
2357 2358 2359 2360 2361 2362 2363 2364 2365 |
# File 'lib/docscribe/infer/returns.rb', line 2357 def split_handle_comma(state, strip:) if state[:da].zero? && state[:db].zero? && state[:dp].zero? part = strip ? state[:cur].strip : state[:cur] state[:parts] << part state[:cur] = +'' else state[:cur] << ',' end end |
.split_process_char(chr, state, strip:) ⇒ void
module_function: defines #split_process_char (visibility: private)
This method returns an undefined value.
2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 |
# File 'lib/docscribe/infer/returns.rb', line 2326 def split_process_char(chr, state, strip:) case chr when '<', '>', '[', ']', '(', ')' split_handle_bracket(chr, state) when ',' split_handle_comma(state, strip: strip) else state[:cur] << chr end end |
.split_top_level_commas(str) ⇒ Array<String>
module_function: defines #split_top_level_commas (visibility: private)
Split a type string by top-level commas (outside any < > [ ] ( ) nesting).
Used to distinguish union types (String, nil) from generic commas (Hash<Integer, String>).
1870 1871 1872 1873 1874 1875 |
# File 'lib/docscribe/infer/returns.rb', line 1870 def split_top_level_commas(str) state = { parts: [], cur: +'', da: 0, db: 0, dp: 0 } #: Hash[Symbol, untyped] str.each_char { |chr| split_process_char(chr, state, strip: false) } state[:parts] << state[:cur] unless state[:cur].empty? state[:parts] end |
.string_like_method?(meth) ⇒ Boolean
module_function: defines #string_like_method? (visibility: private)
1299 1300 1301 |
# File 'lib/docscribe/infer/returns.rb', line 1299 def string_like_method?(meth) %i[to_s to_str inspect].include?(meth) end |
.string_send_type(meth, recv) ⇒ String?
module_function: defines #string_send_type (visibility: private)
1288 1289 1290 1291 1292 1293 1294 |
# File 'lib/docscribe/infer/returns.rb', line 1288 def string_send_type(meth, recv) return 'String' if string_like_method?(meth) return 'String' if file_join_method?(meth, recv) return 'String' if sub_string_method?(meth, recv) nil end |
.stripped_union_type(raw) ⇒ String?
module_function: defines #stripped_union_type (visibility: private)
1857 1858 1859 1860 1861 |
# File 'lib/docscribe/infer/returns.rb', line 1857 def stripped_union_type(raw) parts = split_top_level_commas(raw).map { |p| p.strip.delete_suffix('?').strip } non_nil = parts.reject { |p| %w[nil FALLBACK_TYPE].include?(p) } (non_nil.first || parts.first).to_s.strip.delete_suffix('?').strip end |
.sub_string_method?(meth, recv) ⇒ Boolean
module_function: defines #sub_string_method? (visibility: private)
1315 1316 1317 |
# File 'lib/docscribe/infer/returns.rb', line 1315 def sub_string_method?(meth, recv) meth == :sub && recv && recv&.type != :const end |
.substitute_placeholders(rbs_type, placeholders, inner) ⇒ String
module_function: defines #substitute_placeholders (visibility: private)
1046 1047 1048 1049 1050 1051 1052 1053 |
# File 'lib/docscribe/infer/returns.rb', line 1046 def substitute_placeholders(rbs_type, placeholders, inner) result = rbs_type.dup placeholders.each do |ph| token = ph.strip.delete_suffix('?').strip result = result.gsub(token, inner) end result end |
.substitute_rbs_type(rbs, recv_type) ⇒ String
module_function: defines #substitute_rbs_type (visibility: private)
Substitute self and generic type variables in an RBS return type with the concrete receiver type.
Handles Array#<< (self -> Array<Elem>) and Hash#[] (V -> value type).
For self returns the concrete receiver type; for K/V/Elem substitutes from generic args.
2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 |
# File 'lib/docscribe/infer/returns.rb', line 2195 def substitute_rbs_type(rbs, recv_type) self_sub = substitute_self_type(rbs, recv_type) return self_sub if self_sub inner = extract_generic_inner(recv_type) return rbs unless inner args = split_generic_args(inner) return rbs if args.empty? substitute_with_mapping(rbs, recv_type, args) end |
.substitute_self_type(rbs, recv_type) ⇒ String?
module_function: defines #substitute_self_type (visibility: private)
2212 2213 2214 2215 2216 2217 |
# File 'lib/docscribe/infer/returns.rb', line 2212 def substitute_self_type(rbs, recv_type) return recv_type if rbs == 'self' return "#{recv_type}?" if rbs == 'self?' nil end |
.substitute_with_mapping(rbs, recv_type, args) ⇒ String
module_function: defines #substitute_with_mapping (visibility: private)
2224 2225 2226 2227 2228 2229 |
# File 'lib/docscribe/infer/returns.rb', line 2224 def substitute_with_mapping(rbs, recv_type, args) mapping = build_generic_mapping(recv_type, args) return rbs if mapping.empty? apply_generic_mapping(rbs, mapping, recv_type) end |
.synthesize_shovel_type(left, right, fallback:) ⇒ String
module_function: defines #synthesize_shovel_type (visibility: private)
1616 1617 1618 1619 1620 1621 1622 1623 |
# File 'lib/docscribe/infer/returns.rb', line 1616 def synthesize_shovel_type(left, right, fallback:) l = left || fallback r = right || fallback base = l.split(/[<\[ ]/).first.to_s.strip.delete_suffix('?') return shovel_array_type(l, r, base, fallback) if %w[Array Set Enumerable Enumerator].include?(base) l end |
.synthetic_enumerator_type(node, meth, recv, **opts) ⇒ String?
module_function: defines #synthetic_enumerator_type (visibility: private)
1167 1168 1169 1170 1171 1172 1173 1174 |
# File 'lib/docscribe/infer/returns.rb', line 1167 def synthetic_enumerator_type(node, meth, recv, **opts) return unless meth == :each_with_index && node.children.size == 2 elem = enumerator_elem_from_recv(recv, **opts) return "Enumerator<#{elem}, Integer>" if elem 'Enumerator<Object, Integer>' end |
.synthetic_hash_type(_node, meth, recv, **opts) ⇒ String?
module_function: defines #synthetic_hash_type (visibility: private)
1226 1227 1228 1229 1230 1231 1232 |
# File 'lib/docscribe/infer/returns.rb', line 1226 def synthetic_hash_type(_node, meth, recv, **opts) return unless meth == :to_h && recv && recv.type == :send && recv.children[1] == :each_with_index inner_recv = recv.children[0] elem = hash_elem_from_enumerator(inner_recv, **opts) || 'Object' "Hash<#{elem}, Integer>" end |
.to_h_block_pair_types(body, arg_names, **opts) ⇒ (String, nil, String, nil)
module_function: defines #to_h_block_pair_types (visibility: private)
Infer key/value types from a [k, v] pair literal block body.
A bare lvar matching an each_with_index block parameter resolves
structurally: first parameter is the element (resolved from the receiver
by the caller), second parameter is always the Integer index.
920 921 922 923 924 925 926 |
# File 'lib/docscribe/infer/returns.rb', line 920 def to_h_block_pair_types(body, arg_names, **opts) return [nil, nil] unless pair_literal?(body) pair = body.type == :begin ? body.children.last : body [pair_elem_type(pair.children[0], arg_names, 0, **opts), pair_elem_type(pair.children[1], arg_names, 1, **opts)] end |
.to_h_each_recv(node) ⇒ Parser::AST::Node?
module_function: defines #to_h_each_recv (visibility: private)
Receiver of to_h when it chains off each_with_index.
875 876 877 878 879 |
# File 'lib/docscribe/infer/returns.rb', line 875 def to_h_each_recv(node) send_node = node.children[0] recv = send_node.children[0] recv if recv&.type == :send && recv.children[1] == :each_with_index end |
.to_h_key_value(node, recv, **opts) ⇒ (String, String)
module_function: defines #to_h_key_value (visibility: private)
Resolve key/value types for a to_h block.
888 889 890 891 892 893 894 895 |
# File 'lib/docscribe/infer/returns.rb', line 888 def to_h_key_value(node, recv, **opts) body = node.children[2] key, value = to_h_block_pair_types(body, block_arg_names(node), **opts) key ||= hash_elem_from_enumerator(recv.children[0], **opts) key = 'Object' if unknown_type?(key) value ||= pair_literal?(body) ? 'Object' : 'Integer' [key, value] end |
.try_compound(node, **opts) ⇒ String?
module_function: defines #try_compound (visibility: private)
1146 1147 1148 |
# File 'lib/docscribe/infer/returns.rb', line 1146 def try_compound(node, **opts) infer_from_compound_assign(node, **opts) end |
.try_rbs(meth, recv, **opts) ⇒ String?
module_function: defines #try_rbs (visibility: private)
1133 1134 1135 1136 1137 1138 1139 1140 |
# File 'lib/docscribe/infer/returns.rb', line 1133 def try_rbs(meth, recv, **opts) return nil unless opts[:core_rbs_provider] rbs = send_rbs_type(recv, meth, **opts) return rbs if rbs && rbs != FALLBACK_TYPE && !rbs.include?('Object') nil end |
.try_synthetic(node, meth, recv, **opts) ⇒ String?
module_function: defines #try_synthetic (visibility: private)
1123 1124 1125 1126 |
# File 'lib/docscribe/infer/returns.rb', line 1123 def try_synthetic(node, meth, recv, **opts) synthetic_enumerator_type(node, meth, recv, **opts) || synthetic_hash_type(node, meth, recv, **opts) 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.
1895 1896 1897 1898 1899 1900 |
# File 'lib/docscribe/infer/returns.rb', line 1895 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.
2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 |
# File 'lib/docscribe/infer/returns.rb', line 2406 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 non_nil if non_nil.end_with?('?') 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
2380 2381 2382 2383 2384 2385 2386 |
# File 'lib/docscribe/infer/returns.rb', line 2380 def unify_types(type_a, type_b, fallback_type:, nil_as_optional:) type_a = coalesce_type(type_a, fallback_type) type_b = coalesce_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 |
.unknown_type?(type) ⇒ Boolean
module_function: defines #unknown_type? (visibility: private)
Whether a type string means "could not determine".
970 971 972 |
# File 'lib/docscribe/infer/returns.rb', line 970 def unknown_type?(type) type.nil? || %w[Object untyped nil].include?(type) end |
.var_receiver?(recv) ⇒ Boolean
module_function: defines #var_receiver? (visibility: private)
1832 1833 1834 1835 1836 |
# File 'lib/docscribe/infer/returns.rb', line 1832 def var_receiver?(recv) return false unless recv.is_a?(Parser::AST::Node) %i[lvar ivar gvar cvar].include?(recv&.type) end |
.yard_type_for_const_value(value) ⇒ String?
module_function: defines #yard_type_for_const_value (visibility: private)
Map a constant's runtime value to a YARD type name.
2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 |
# File 'lib/docscribe/infer/returns.rb', line 2143 def yard_type_for_const_value(value) return 'nil' if value.nil? return 'Boolean' if value.is_a?(TrueClass) || value.is_a?(FalseClass) return nil if value.is_a?(Module) name = value.class.name return nil unless name.is_a?(String) && name.match?(/\A[A-Z][A-Za-z0-9_:]*\z/) name end |