Module: Docscribe::Infer::Returns
- Defined in:
- lib/docscribe/infer/returns.rb
Overview
Return type inference and rescue-conditional return extraction.
Class Method Summary collapse
-
.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, fallback_type:, nil_as_optional:, core_rbs_provider: nil, param_types: nil) ⇒ String?
Infer the type of the last expression in a node.
-
.resolve_rbs_return_type(container_type, method_name, core_rbs_provider) ⇒ String
Resolve a return type from core RBS for a method call.
-
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) ⇒ Hash
Return a structured return-type spec for a method node.
-
.unify_types(a, b, fallback_type:, nil_as_optional:) ⇒ String?
Unify two inferred types into a single type string.
Class Method Details
.infer_return_type(method_source) ⇒ String
module_function: when included, also defines #infer_return_type (instance 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.
18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/docscribe/infer/returns.rb', line 18 def infer_return_type(method_source) return FALLBACK_TYPE if method_source.nil? || method_source.strip.empty? buffer = Parser::Source::Buffer.new('(method)') buffer.source = method_source root = Docscribe::Parsing.parse_buffer(buffer) return FALLBACK_TYPE unless root && %i[def defs].include?(root.type) body = root.children.last last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE rescue Parser::SyntaxError FALLBACK_TYPE end |
.infer_return_type_from_node(node) ⇒ String
module_function: when included, also defines #infer_return_type_from_node (instance visibility: private)
Infer a method’s normal return type from an already parsed def/defs node.
37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/docscribe/infer/returns.rb', line 37 def infer_return_type_from_node(node) body = case node.type when :def then node.children[2] when :defs then node.children[3] end return FALLBACK_TYPE unless body last_expr_type(body, fallback_type: FALLBACK_TYPE, nil_as_optional: true) || FALLBACK_TYPE end |
.last_expr_type(node, fallback_type:, nil_as_optional:, core_rbs_provider: nil, param_types: nil) ⇒ String?
module_function: when included, also defines #last_expr_type (instance visibility: private)
Infer the type of the last expression in a node.
Supports:
-
‘begin` groups
-
‘if` branches
-
‘case` expressions
-
explicit ‘return`
-
literal-like expressions via Literals.type_from_literal
-
method calls with RBS core type lookup
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/docscribe/infer/returns.rb', line 116 def last_expr_type(node, fallback_type:, nil_as_optional:, core_rbs_provider: nil, param_types: nil) return nil unless node case node.type when :begin last_expr_type(node.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) when :if t = last_expr_type(node.children[1], fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) e = last_expr_type(node.children[2], fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) unify_types(t, e, fallback_type: fallback_type, nil_as_optional: nil_as_optional) when :case branches = node.children[1..].compact.flat_map do |child| if child.type == :when last_expr_type(child.children.last, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) else last_expr_type(child, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) end end.compact if branches.empty? fallback_type else branches.reduce do |a, b| unify_types(a, b, fallback_type: fallback_type, nil_as_optional: nil_as_optional) end end when :return Literals.type_from_literal(node.children.first, fallback_type: fallback_type) when :send recv = node.children[0] meth = node.children[1] # Try to resolve return type from RBS core for method calls if core_rbs_provider && recv&.type == :send # Chained call: arg.to_i.positive? inner_type = last_expr_type(recv, fallback_type: nil, nil_as_optional: false, core_rbs_provider: core_rbs_provider, param_types: param_types) if inner_type rbs_type = resolve_rbs_return_type(inner_type, meth, core_rbs_provider) return rbs_type unless rbs_type == FALLBACK_TYPE end elsif core_rbs_provider && recv&.type == :lvar # Direct call: arg.positive? lvar_name = recv.children.first if lvar_name && param_types recv_type = param_types[lvar_name.to_s] if recv_type rbs_type = resolve_rbs_return_type(recv_type, meth, core_rbs_provider) return rbs_type unless rbs_type == FALLBACK_TYPE end end end Literals.type_from_literal(node, fallback_type: fallback_type) else Literals.type_from_literal(node, fallback_type: fallback_type) end end |
.resolve_rbs_return_type(container_type, method_name, core_rbs_provider) ⇒ String
module_function: when included, also defines #resolve_rbs_return_type (instance visibility: private)
Resolve a return type from core RBS for a method call.
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/docscribe/infer/returns.rb', line 193 def resolve_rbs_return_type(container_type, method_name, core_rbs_provider) return FALLBACK_TYPE unless core_rbs_provider sig = core_rbs_provider.signature_for( container: container_type, scope: :instance, name: method_name ) sig&.return_type || FALLBACK_TYPE end |
.returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) ⇒ Hash
module_function: when included, also defines #returns_spec_from_node (instance 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
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/docscribe/infer/returns.rb', line 62 def returns_spec_from_node(node, fallback_type: FALLBACK_TYPE, nil_as_optional: true, core_rbs_provider: nil, param_types: nil) body = case node.type when :def then node.children[2] when :defs then node.children[3] end spec = { normal: FALLBACK_TYPE, rescues: [] } return spec unless body if body.type == :rescue main_body = body.children[0] spec[:normal] = last_expr_type(main_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) || FALLBACK_TYPE 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 = last_expr_type(rescue_body, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) || fallback_type spec[:rescues] << [exc_names, rtype] end else spec[:normal] = last_expr_type(body, fallback_type: fallback_type, nil_as_optional: nil_as_optional, core_rbs_provider: core_rbs_provider, param_types: param_types) || FALLBACK_TYPE end spec end |
.unify_types(a, b, fallback_type:, nil_as_optional:) ⇒ String?
module_function: when included, also defines #unify_types (instance visibility: private)
Unify two inferred types into a single type string.
Rules:
-
identical types remain unchanged
-
‘nil` unions may become optional types if enabled
-
otherwise falls back conservatively to ‘fallback_type`
218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/docscribe/infer/returns.rb', line 218 def unify_types(a, b, fallback_type:, nil_as_optional:) a ||= fallback_type b ||= fallback_type return a if a == b if a == 'nil' || b == 'nil' non_nil = (a == 'nil' ? b : a) return nil_as_optional ? "#{non_nil}?" : "#{non_nil}, nil" end fallback_type end |