type-guessr 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +41 -0
- data/exe/type-guessr +30 -0
- data/lib/ruby_lsp/type_guessr/addon.rb +20 -45
- data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +352 -0
- data/lib/ruby_lsp/type_guessr/constants.rb +39 -0
- data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +27 -22
- data/lib/ruby_lsp/type_guessr/debug_server.rb +20 -17
- data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
- data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
- data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
- data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
- data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
- data/lib/ruby_lsp/type_guessr/hover.rb +129 -261
- data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +613 -277
- data/lib/ruby_lsp/type_guessr/type_inferrer.rb +8 -105
- data/lib/type-guessr.rb +3 -11
- data/lib/type_guessr/core/cache/gem_dependency_resolver.rb +113 -0
- data/lib/type_guessr/core/cache/gem_signature_cache.rb +98 -0
- data/lib/type_guessr/core/cache/gem_signature_extractor.rb +87 -0
- data/lib/type_guessr/core/cache.rb +5 -0
- data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +19 -34
- data/lib/type_guessr/core/converter/call_converter.rb +161 -0
- data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
- data/lib/type_guessr/core/converter/context.rb +144 -0
- data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
- data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
- data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
- data/lib/type_guessr/core/converter/prism_converter.rb +154 -1613
- data/lib/type_guessr/core/converter/rbs_converter.rb +35 -14
- data/lib/type_guessr/core/converter/registration.rb +100 -0
- data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
- data/lib/type_guessr/core/converter.rb +4 -0
- data/lib/type_guessr/core/index/location_index.rb +32 -0
- data/lib/type_guessr/core/index.rb +3 -0
- data/lib/type_guessr/core/inference/resolver.rb +516 -349
- data/lib/type_guessr/core/inference.rb +4 -0
- data/lib/type_guessr/core/ir/nodes.rb +362 -103
- data/lib/type_guessr/core/ir.rb +3 -0
- data/lib/type_guessr/core/logger.rb +6 -13
- data/lib/type_guessr/core/node_context_helper.rb +126 -0
- data/lib/type_guessr/core/node_key_generator.rb +31 -0
- data/lib/type_guessr/core/registry/class_variable_registry.rb +63 -0
- data/lib/type_guessr/core/registry/instance_variable_registry.rb +84 -0
- data/lib/type_guessr/core/registry/method_registry.rb +65 -38
- data/lib/type_guessr/core/registry/signature_registry.rb +543 -0
- data/lib/type_guessr/core/registry.rb +6 -0
- data/lib/type_guessr/core/signature_builder.rb +39 -0
- data/lib/type_guessr/core/type_serializer.rb +96 -0
- data/lib/type_guessr/core/type_simplifier.rb +15 -12
- data/lib/type_guessr/core/types.rb +250 -32
- data/lib/type_guessr/core.rb +29 -0
- data/lib/type_guessr/mcp/file_watcher.rb +87 -0
- data/lib/type_guessr/mcp/server.rb +463 -0
- data/lib/type_guessr/mcp/standalone_runtime.rb +213 -0
- data/lib/type_guessr/version.rb +1 -1
- metadata +57 -8
- data/lib/type_guessr/core/rbs_provider.rb +0 -304
- data/lib/type_guessr/core/registry/variable_registry.rb +0 -87
- data/lib/type_guessr/core/signature_provider.rb +0 -101
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../node_key_generator"
|
|
4
|
+
|
|
3
5
|
module TypeGuessr
|
|
4
6
|
module Core
|
|
7
|
+
# Intermediate Representation (IR) nodes for type inference.
|
|
8
|
+
# Each node represents a construct in the source code and forms a
|
|
9
|
+
# reverse dependency graph where nodes point to their dependencies.
|
|
10
|
+
#
|
|
11
|
+
# Performance note: All node types use plain Class with positional arguments
|
|
12
|
+
# and attr_reader/attr_accessor. This avoids the overhead of keyword argument
|
|
13
|
+
# processing in CRuby's VM (setup_parameters_complex path), yielding the
|
|
14
|
+
# fastest possible object allocation. Loc (formerly Data.define(:offset)) is
|
|
15
|
+
# inlined as a plain Integer to eliminate ~2M wrapper object allocations
|
|
16
|
+
# during indexing.
|
|
5
17
|
module IR
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
# Shortcut to NodeKeyGenerator for generating node hash keys
|
|
19
|
+
NodeKeyGenerator = Core::NodeKeyGenerator
|
|
20
|
+
|
|
21
|
+
# Extract the last segment of a class/module path without array allocation.
|
|
22
|
+
# Uses String#rindex instead of String#split to avoid creating intermediate arrays.
|
|
23
|
+
# @param path [String] Class path (e.g., "Admin::User", "TypeGuessr::Core::IR::LiteralNode")
|
|
24
|
+
# @return [String, nil] Last segment (e.g., "User", "LiteralNode"), the path itself if no "::", or nil if empty
|
|
25
|
+
def self.extract_last_name(path)
|
|
26
|
+
return nil if path.nil? || path.empty?
|
|
27
|
+
|
|
28
|
+
last_sep = path.rindex("::")
|
|
29
|
+
last_sep ? path[(last_sep + 2)..] : path
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Method call signature for duck typing inference
|
|
33
|
+
# @param name [Symbol] Method name
|
|
34
|
+
# @param positional_count [Integer, nil] Number of positional arguments (nil if splat used)
|
|
35
|
+
# @param keywords [Array<Symbol>] Keyword argument names
|
|
36
|
+
CalledMethod = Data.define(:name, :positional_count, :keywords) do
|
|
37
|
+
# String representation returns method name for logging/display
|
|
38
|
+
def to_s
|
|
39
|
+
name.to_s
|
|
40
|
+
end
|
|
41
|
+
end
|
|
10
42
|
|
|
11
43
|
# Pretty print helper for IR nodes (Prism-style tree output)
|
|
12
44
|
module TreeInspect
|
|
@@ -17,23 +49,21 @@ module TypeGuessr
|
|
|
17
49
|
|
|
18
50
|
def tree_inspect(indent: "", last: true, root: false)
|
|
19
51
|
lines = if root
|
|
20
|
-
["@ #{self.class.name
|
|
52
|
+
["@ #{IR.extract_last_name(self.class.name)} (location: #{format_loc})"]
|
|
21
53
|
else
|
|
22
54
|
prefix = last ? LAST_BRANCH : BRANCH
|
|
23
55
|
indent += (last ? SPACE : PIPE)
|
|
24
|
-
["#{indent.delete_suffix(last ? SPACE : PIPE)}#{prefix}@ #{self.class.name
|
|
56
|
+
["#{indent.delete_suffix(last ? SPACE : PIPE)}#{prefix}@ #{IR.extract_last_name(self.class.name)} (location: #{format_loc})"]
|
|
25
57
|
end
|
|
26
58
|
lines.concat(tree_inspect_fields(indent))
|
|
27
59
|
lines.join("\n")
|
|
28
60
|
end
|
|
29
61
|
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def format_loc
|
|
33
|
-
loc ? "(#{loc.line},#{loc.col_range.begin})-(#{loc.line},#{loc.col_range.end})" : "∅"
|
|
62
|
+
private def format_loc
|
|
63
|
+
loc ? "@#{loc}" : "∅"
|
|
34
64
|
end
|
|
35
65
|
|
|
36
|
-
def tree_field(name, value, indent, last: false)
|
|
66
|
+
private def tree_field(name, value, indent, last: false)
|
|
37
67
|
prefix = last ? LAST_BRANCH : BRANCH
|
|
38
68
|
case value
|
|
39
69
|
when nil
|
|
@@ -69,59 +99,37 @@ module TypeGuessr
|
|
|
69
99
|
end
|
|
70
100
|
end
|
|
71
101
|
|
|
72
|
-
def tree_inspect_fields(_indent)
|
|
102
|
+
private def tree_inspect_fields(_indent)
|
|
73
103
|
[]
|
|
74
104
|
end
|
|
75
105
|
end
|
|
76
106
|
|
|
77
|
-
# Base class for all IR nodes
|
|
78
|
-
# IR represents a reverse dependency graph where each node points to nodes it depends on
|
|
79
|
-
class Node
|
|
80
|
-
attr_reader :loc
|
|
81
|
-
|
|
82
|
-
def initialize(loc:)
|
|
83
|
-
@loc = loc
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Returns all nodes that this node directly depends on
|
|
87
|
-
# @return [Array<Node>]
|
|
88
|
-
def dependencies
|
|
89
|
-
[]
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Generate a unique hash for this node (type + identifier + line)
|
|
93
|
-
# @return [String]
|
|
94
|
-
def node_hash
|
|
95
|
-
raise NotImplementedError, "#{self.class} must implement node_hash"
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Generate a unique key for this node (scope_id + node_hash)
|
|
99
|
-
# @param scope_id [String] The scope identifier (e.g., "User#save")
|
|
100
|
-
# @return [String]
|
|
101
|
-
def node_key(scope_id)
|
|
102
|
-
"#{scope_id}:#{node_hash}"
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
107
|
# Literal value node
|
|
107
108
|
# @param type [TypeGuessr::Core::Types::Type] The type of the literal
|
|
108
109
|
# @param literal_value [Object, nil] The actual literal value (for Symbol, Integer, String)
|
|
109
110
|
# @param values [Array<Node>, nil] Internal value nodes for compound literals (Hash/Array)
|
|
110
|
-
# @param
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
# For compound literals, values contains the internal expression nodes
|
|
114
|
-
# For simple literals, literal_value stores the actual value (e.g., :a for symbols)
|
|
115
|
-
LiteralNode = Data.define(:type, :literal_value, :values, :loc) do
|
|
111
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
112
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
113
|
+
class LiteralNode
|
|
116
114
|
include TreeInspect
|
|
117
115
|
|
|
116
|
+
attr_reader :type, :literal_value, :values, :called_methods, :loc
|
|
117
|
+
|
|
118
|
+
def initialize(type, literal_value, values, called_methods, loc)
|
|
119
|
+
@type = type
|
|
120
|
+
@literal_value = literal_value
|
|
121
|
+
@values = values
|
|
122
|
+
@called_methods = called_methods
|
|
123
|
+
@loc = loc
|
|
124
|
+
end
|
|
125
|
+
|
|
118
126
|
def dependencies
|
|
119
127
|
values || []
|
|
120
128
|
end
|
|
121
129
|
|
|
122
130
|
def node_hash
|
|
123
|
-
type_name = type.is_a?(Class) ? type.name
|
|
124
|
-
|
|
131
|
+
type_name = type.is_a?(Class) ? IR.extract_last_name(type.name) : IR.extract_last_name(type.class.name)
|
|
132
|
+
NodeKeyGenerator.literal(type_name, loc)
|
|
125
133
|
end
|
|
126
134
|
|
|
127
135
|
def node_key(scope_id)
|
|
@@ -129,11 +137,12 @@ module TypeGuessr
|
|
|
129
137
|
end
|
|
130
138
|
|
|
131
139
|
def tree_inspect_fields(indent)
|
|
132
|
-
type_str = type.respond_to?(:name) ? type.name : type.class.name
|
|
140
|
+
type_str = type.respond_to?(:name) ? type.name : IR.extract_last_name(type.class.name)
|
|
133
141
|
[
|
|
134
142
|
tree_field(:type, type_str, indent),
|
|
135
143
|
tree_field(:literal_value, literal_value, indent),
|
|
136
|
-
tree_field(:values, values, indent
|
|
144
|
+
tree_field(:values, values, indent),
|
|
145
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
137
146
|
]
|
|
138
147
|
end
|
|
139
148
|
end
|
|
@@ -142,18 +151,27 @@ module TypeGuessr
|
|
|
142
151
|
# @param name [Symbol] Variable name
|
|
143
152
|
# @param value [Node] The node of the assigned value
|
|
144
153
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
145
|
-
# @param loc [
|
|
154
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
146
155
|
#
|
|
147
156
|
# Note: called_methods is a shared array object that can be mutated during parsing
|
|
148
|
-
LocalWriteNode
|
|
157
|
+
class LocalWriteNode
|
|
149
158
|
include TreeInspect
|
|
150
159
|
|
|
160
|
+
attr_reader :name, :value, :called_methods, :loc
|
|
161
|
+
|
|
162
|
+
def initialize(name, value, called_methods, loc)
|
|
163
|
+
@name = name
|
|
164
|
+
@value = value
|
|
165
|
+
@called_methods = called_methods
|
|
166
|
+
@loc = loc
|
|
167
|
+
end
|
|
168
|
+
|
|
151
169
|
def dependencies
|
|
152
170
|
value ? [value] : []
|
|
153
171
|
end
|
|
154
172
|
|
|
155
173
|
def node_hash
|
|
156
|
-
|
|
174
|
+
NodeKeyGenerator.local_write(name, loc)
|
|
157
175
|
end
|
|
158
176
|
|
|
159
177
|
def node_key(scope_id)
|
|
@@ -173,18 +191,27 @@ module TypeGuessr
|
|
|
173
191
|
# @param name [Symbol] Variable name
|
|
174
192
|
# @param write_node [LocalWriteNode, nil] The LocalWriteNode this read references
|
|
175
193
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
176
|
-
# @param loc [
|
|
194
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
177
195
|
#
|
|
178
196
|
# Note: called_methods is shared with LocalWriteNode for method-based inference propagation
|
|
179
|
-
LocalReadNode
|
|
197
|
+
class LocalReadNode
|
|
180
198
|
include TreeInspect
|
|
181
199
|
|
|
200
|
+
attr_reader :name, :write_node, :called_methods, :loc
|
|
201
|
+
|
|
202
|
+
def initialize(name, write_node, called_methods, loc)
|
|
203
|
+
@name = name
|
|
204
|
+
@write_node = write_node
|
|
205
|
+
@called_methods = called_methods
|
|
206
|
+
@loc = loc
|
|
207
|
+
end
|
|
208
|
+
|
|
182
209
|
def dependencies
|
|
183
210
|
write_node ? [write_node] : []
|
|
184
211
|
end
|
|
185
212
|
|
|
186
213
|
def node_hash
|
|
187
|
-
|
|
214
|
+
NodeKeyGenerator.local_read(name, loc)
|
|
188
215
|
end
|
|
189
216
|
|
|
190
217
|
def node_key(scope_id)
|
|
@@ -205,16 +232,26 @@ module TypeGuessr
|
|
|
205
232
|
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
206
233
|
# @param value [Node] The node of the assigned value
|
|
207
234
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
208
|
-
# @param loc [
|
|
209
|
-
InstanceVariableWriteNode
|
|
235
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
236
|
+
class InstanceVariableWriteNode
|
|
210
237
|
include TreeInspect
|
|
211
238
|
|
|
239
|
+
attr_reader :name, :class_name, :value, :called_methods, :loc
|
|
240
|
+
|
|
241
|
+
def initialize(name, class_name, value, called_methods, loc)
|
|
242
|
+
@name = name
|
|
243
|
+
@class_name = class_name
|
|
244
|
+
@value = value
|
|
245
|
+
@called_methods = called_methods
|
|
246
|
+
@loc = loc
|
|
247
|
+
end
|
|
248
|
+
|
|
212
249
|
def dependencies
|
|
213
250
|
value ? [value] : []
|
|
214
251
|
end
|
|
215
252
|
|
|
216
253
|
def node_hash
|
|
217
|
-
|
|
254
|
+
NodeKeyGenerator.ivar_write(name, loc)
|
|
218
255
|
end
|
|
219
256
|
|
|
220
257
|
def node_key(scope_id)
|
|
@@ -236,19 +273,29 @@ module TypeGuessr
|
|
|
236
273
|
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
237
274
|
# @param write_node [InstanceVariableWriteNode, nil] The write node this read references
|
|
238
275
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
239
|
-
# @param loc [
|
|
276
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
240
277
|
#
|
|
241
278
|
# Note: write_node may be nil at conversion time if assignment appears later.
|
|
242
279
|
# Resolver performs deferred lookup using class_name.
|
|
243
|
-
InstanceVariableReadNode
|
|
280
|
+
class InstanceVariableReadNode
|
|
244
281
|
include TreeInspect
|
|
245
282
|
|
|
283
|
+
attr_reader :name, :class_name, :write_node, :called_methods, :loc
|
|
284
|
+
|
|
285
|
+
def initialize(name, class_name, write_node, called_methods, loc)
|
|
286
|
+
@name = name
|
|
287
|
+
@class_name = class_name
|
|
288
|
+
@write_node = write_node
|
|
289
|
+
@called_methods = called_methods
|
|
290
|
+
@loc = loc
|
|
291
|
+
end
|
|
292
|
+
|
|
246
293
|
def dependencies
|
|
247
294
|
write_node ? [write_node] : []
|
|
248
295
|
end
|
|
249
296
|
|
|
250
297
|
def node_hash
|
|
251
|
-
|
|
298
|
+
NodeKeyGenerator.ivar_read(name, loc)
|
|
252
299
|
end
|
|
253
300
|
|
|
254
301
|
def node_key(scope_id)
|
|
@@ -270,16 +317,26 @@ module TypeGuessr
|
|
|
270
317
|
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
271
318
|
# @param value [Node] The node of the assigned value
|
|
272
319
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
273
|
-
# @param loc [
|
|
274
|
-
ClassVariableWriteNode
|
|
320
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
321
|
+
class ClassVariableWriteNode
|
|
275
322
|
include TreeInspect
|
|
276
323
|
|
|
324
|
+
attr_reader :name, :class_name, :value, :called_methods, :loc
|
|
325
|
+
|
|
326
|
+
def initialize(name, class_name, value, called_methods, loc)
|
|
327
|
+
@name = name
|
|
328
|
+
@class_name = class_name
|
|
329
|
+
@value = value
|
|
330
|
+
@called_methods = called_methods
|
|
331
|
+
@loc = loc
|
|
332
|
+
end
|
|
333
|
+
|
|
277
334
|
def dependencies
|
|
278
335
|
value ? [value] : []
|
|
279
336
|
end
|
|
280
337
|
|
|
281
338
|
def node_hash
|
|
282
|
-
|
|
339
|
+
NodeKeyGenerator.cvar_write(name, loc)
|
|
283
340
|
end
|
|
284
341
|
|
|
285
342
|
def node_key(scope_id)
|
|
@@ -301,16 +358,26 @@ module TypeGuessr
|
|
|
301
358
|
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
302
359
|
# @param write_node [ClassVariableWriteNode, nil] The write node this read references
|
|
303
360
|
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
304
|
-
# @param loc [
|
|
305
|
-
ClassVariableReadNode
|
|
361
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
362
|
+
class ClassVariableReadNode
|
|
306
363
|
include TreeInspect
|
|
307
364
|
|
|
365
|
+
attr_reader :name, :class_name, :write_node, :called_methods, :loc
|
|
366
|
+
|
|
367
|
+
def initialize(name, class_name, write_node, called_methods, loc)
|
|
368
|
+
@name = name
|
|
369
|
+
@class_name = class_name
|
|
370
|
+
@write_node = write_node
|
|
371
|
+
@called_methods = called_methods
|
|
372
|
+
@loc = loc
|
|
373
|
+
end
|
|
374
|
+
|
|
308
375
|
def dependencies
|
|
309
376
|
write_node ? [write_node] : []
|
|
310
377
|
end
|
|
311
378
|
|
|
312
379
|
def node_hash
|
|
313
|
-
|
|
380
|
+
NodeKeyGenerator.cvar_read(name, loc)
|
|
314
381
|
end
|
|
315
382
|
|
|
316
383
|
def node_key(scope_id)
|
|
@@ -333,18 +400,28 @@ module TypeGuessr
|
|
|
333
400
|
# :keyword_optional, :keyword_rest, :block, :forwarding)
|
|
334
401
|
# @param default_value [Node, nil] Default value node (nil if no default)
|
|
335
402
|
# @param called_methods [Array<Symbol>] Methods called on this parameter (for method-based inference)
|
|
336
|
-
# @param loc [
|
|
403
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
337
404
|
#
|
|
338
405
|
# Note: called_methods is a shared array object that can be mutated during parsing
|
|
339
|
-
ParamNode
|
|
406
|
+
class ParamNode
|
|
340
407
|
include TreeInspect
|
|
341
408
|
|
|
409
|
+
attr_reader :name, :kind, :default_value, :called_methods, :loc
|
|
410
|
+
|
|
411
|
+
def initialize(name, kind, default_value, called_methods, loc)
|
|
412
|
+
@name = name
|
|
413
|
+
@kind = kind
|
|
414
|
+
@default_value = default_value
|
|
415
|
+
@called_methods = called_methods
|
|
416
|
+
@loc = loc
|
|
417
|
+
end
|
|
418
|
+
|
|
342
419
|
def dependencies
|
|
343
420
|
default_value ? [default_value] : []
|
|
344
421
|
end
|
|
345
422
|
|
|
346
423
|
def node_hash
|
|
347
|
-
|
|
424
|
+
NodeKeyGenerator.param(name, loc)
|
|
348
425
|
end
|
|
349
426
|
|
|
350
427
|
def node_key(scope_id)
|
|
@@ -364,16 +441,26 @@ module TypeGuessr
|
|
|
364
441
|
# Constant reference node
|
|
365
442
|
# @param name [String] Constant name (e.g., "DEFAULT_NAME", "User::ADMIN")
|
|
366
443
|
# @param dependency [Node] The node where this constant is defined
|
|
367
|
-
# @param
|
|
368
|
-
|
|
444
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
445
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
446
|
+
class ConstantNode
|
|
369
447
|
include TreeInspect
|
|
370
448
|
|
|
449
|
+
attr_reader :name, :dependency, :called_methods, :loc
|
|
450
|
+
|
|
451
|
+
def initialize(name, dependency, called_methods, loc)
|
|
452
|
+
@name = name
|
|
453
|
+
@dependency = dependency
|
|
454
|
+
@called_methods = called_methods
|
|
455
|
+
@loc = loc
|
|
456
|
+
end
|
|
457
|
+
|
|
371
458
|
def dependencies
|
|
372
459
|
dependency ? [dependency] : []
|
|
373
460
|
end
|
|
374
461
|
|
|
375
462
|
def node_hash
|
|
376
|
-
|
|
463
|
+
NodeKeyGenerator.constant(name, loc)
|
|
377
464
|
end
|
|
378
465
|
|
|
379
466
|
def node_key(scope_id)
|
|
@@ -383,7 +470,8 @@ module TypeGuessr
|
|
|
383
470
|
def tree_inspect_fields(indent)
|
|
384
471
|
[
|
|
385
472
|
tree_field(:name, name, indent),
|
|
386
|
-
tree_field(:dependency, dependency, indent
|
|
473
|
+
tree_field(:dependency, dependency, indent),
|
|
474
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
387
475
|
]
|
|
388
476
|
end
|
|
389
477
|
end
|
|
@@ -395,10 +483,25 @@ module TypeGuessr
|
|
|
395
483
|
# @param block_params [Array<BlockParamSlot>] Block parameter slots
|
|
396
484
|
# @param block_body [Node, nil] Block body return node (for inferring block return type)
|
|
397
485
|
# @param has_block [Boolean] Whether a block was provided (even if empty)
|
|
398
|
-
# @param
|
|
399
|
-
|
|
486
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
487
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
488
|
+
class CallNode
|
|
400
489
|
include TreeInspect
|
|
401
490
|
|
|
491
|
+
attr_reader :method, :receiver, :args, :called_methods, :loc
|
|
492
|
+
attr_accessor :block_params, :block_body, :has_block
|
|
493
|
+
|
|
494
|
+
def initialize(method, receiver, args, block_params, block_body, has_block, called_methods, loc)
|
|
495
|
+
@method = method
|
|
496
|
+
@receiver = receiver
|
|
497
|
+
@args = args
|
|
498
|
+
@block_params = block_params
|
|
499
|
+
@block_body = block_body
|
|
500
|
+
@has_block = has_block
|
|
501
|
+
@called_methods = called_methods
|
|
502
|
+
@loc = loc
|
|
503
|
+
end
|
|
504
|
+
|
|
402
505
|
def dependencies
|
|
403
506
|
deps = []
|
|
404
507
|
deps << receiver if receiver
|
|
@@ -408,7 +511,7 @@ module TypeGuessr
|
|
|
408
511
|
end
|
|
409
512
|
|
|
410
513
|
def node_hash
|
|
411
|
-
|
|
514
|
+
NodeKeyGenerator.call(method, loc)
|
|
412
515
|
end
|
|
413
516
|
|
|
414
517
|
def node_key(scope_id)
|
|
@@ -422,7 +525,8 @@ module TypeGuessr
|
|
|
422
525
|
tree_field(:args, args, indent),
|
|
423
526
|
tree_field(:block_params, block_params, indent),
|
|
424
527
|
tree_field(:block_body, block_body, indent),
|
|
425
|
-
tree_field(:has_block, has_block, indent
|
|
528
|
+
tree_field(:has_block, has_block, indent),
|
|
529
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
426
530
|
]
|
|
427
531
|
end
|
|
428
532
|
end
|
|
@@ -431,16 +535,26 @@ module TypeGuessr
|
|
|
431
535
|
# Represents a parameter slot in a block (e.g., |user| in users.each { |user| ... })
|
|
432
536
|
# @param index [Integer] Parameter index (0-based)
|
|
433
537
|
# @param call_node [CallNode] The call node this slot belongs to
|
|
434
|
-
# @param
|
|
435
|
-
|
|
538
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
539
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
540
|
+
class BlockParamSlot
|
|
436
541
|
include TreeInspect
|
|
437
542
|
|
|
543
|
+
attr_reader :index, :call_node, :called_methods, :loc
|
|
544
|
+
|
|
545
|
+
def initialize(index, call_node, called_methods, loc)
|
|
546
|
+
@index = index
|
|
547
|
+
@call_node = call_node
|
|
548
|
+
@called_methods = called_methods
|
|
549
|
+
@loc = loc
|
|
550
|
+
end
|
|
551
|
+
|
|
438
552
|
def dependencies
|
|
439
553
|
[call_node]
|
|
440
554
|
end
|
|
441
555
|
|
|
442
556
|
def node_hash
|
|
443
|
-
|
|
557
|
+
NodeKeyGenerator.bparam(index, loc)
|
|
444
558
|
end
|
|
445
559
|
|
|
446
560
|
def node_key(scope_id)
|
|
@@ -450,7 +564,8 @@ module TypeGuessr
|
|
|
450
564
|
def tree_inspect_fields(indent)
|
|
451
565
|
[
|
|
452
566
|
tree_field(:index, index, indent),
|
|
453
|
-
tree_field(:call_node, "(CallNode ref)", indent
|
|
567
|
+
tree_field(:call_node, "(CallNode ref)", indent),
|
|
568
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
454
569
|
]
|
|
455
570
|
end
|
|
456
571
|
end
|
|
@@ -459,16 +574,25 @@ module TypeGuessr
|
|
|
459
574
|
# Represents the convergence point of multiple branches (if/else, case/when, etc.)
|
|
460
575
|
# The type is the union of all branch types
|
|
461
576
|
# @param branches [Array<Node>] Final nodes from each branch
|
|
462
|
-
# @param
|
|
463
|
-
|
|
577
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
578
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
579
|
+
class MergeNode
|
|
464
580
|
include TreeInspect
|
|
465
581
|
|
|
582
|
+
attr_reader :branches, :called_methods, :loc
|
|
583
|
+
|
|
584
|
+
def initialize(branches, called_methods, loc)
|
|
585
|
+
@branches = branches
|
|
586
|
+
@called_methods = called_methods
|
|
587
|
+
@loc = loc
|
|
588
|
+
end
|
|
589
|
+
|
|
466
590
|
def dependencies
|
|
467
591
|
branches
|
|
468
592
|
end
|
|
469
593
|
|
|
470
594
|
def node_hash
|
|
471
|
-
|
|
595
|
+
NodeKeyGenerator.merge(loc)
|
|
472
596
|
end
|
|
473
597
|
|
|
474
598
|
def node_key(scope_id)
|
|
@@ -476,7 +600,50 @@ module TypeGuessr
|
|
|
476
600
|
end
|
|
477
601
|
|
|
478
602
|
def tree_inspect_fields(indent)
|
|
479
|
-
[
|
|
603
|
+
[
|
|
604
|
+
tree_field(:branches, branches, indent),
|
|
605
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
606
|
+
]
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Short-circuit or node
|
|
611
|
+
# Represents || and ||= operations where LHS is evaluated first,
|
|
612
|
+
# and RHS is only evaluated if LHS is falsy (nil/false)
|
|
613
|
+
# @param lhs [Node] Left-hand side (evaluated first)
|
|
614
|
+
# @param rhs [Node] Right-hand side (evaluated only if LHS is falsy)
|
|
615
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node
|
|
616
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
617
|
+
class OrNode
|
|
618
|
+
include TreeInspect
|
|
619
|
+
|
|
620
|
+
attr_reader :lhs, :rhs, :called_methods, :loc
|
|
621
|
+
|
|
622
|
+
def initialize(lhs, rhs, called_methods, loc)
|
|
623
|
+
@lhs = lhs
|
|
624
|
+
@rhs = rhs
|
|
625
|
+
@called_methods = called_methods
|
|
626
|
+
@loc = loc
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def dependencies
|
|
630
|
+
[lhs, rhs]
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def node_hash
|
|
634
|
+
NodeKeyGenerator.or_node(loc)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def node_key(scope_id)
|
|
638
|
+
"#{scope_id}:#{node_hash}"
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def tree_inspect_fields(indent)
|
|
642
|
+
[
|
|
643
|
+
tree_field(:lhs, lhs, indent),
|
|
644
|
+
tree_field(:rhs, rhs, indent),
|
|
645
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
646
|
+
]
|
|
480
647
|
end
|
|
481
648
|
end
|
|
482
649
|
|
|
@@ -486,11 +653,28 @@ module TypeGuessr
|
|
|
486
653
|
# @param params [Array<ParamNode>] Parameter nodes
|
|
487
654
|
# @param return_node [Node] Node representing the return value
|
|
488
655
|
# @param body_nodes [Array<Node>] All nodes in the method body
|
|
489
|
-
# @param
|
|
656
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
657
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
490
658
|
# @param singleton [Boolean] true if this is a singleton method (def self.method_name)
|
|
491
|
-
DefNode
|
|
659
|
+
class DefNode
|
|
492
660
|
include TreeInspect
|
|
493
661
|
|
|
662
|
+
attr_reader :name, :class_name, :params, :return_node, :body_nodes, :called_methods, :loc, :singleton,
|
|
663
|
+
:module_function
|
|
664
|
+
|
|
665
|
+
def initialize(name, class_name, params, return_node, body_nodes, called_methods, loc, singleton,
|
|
666
|
+
module_function: false)
|
|
667
|
+
@name = name
|
|
668
|
+
@class_name = class_name
|
|
669
|
+
@params = params
|
|
670
|
+
@return_node = return_node
|
|
671
|
+
@body_nodes = body_nodes
|
|
672
|
+
@called_methods = called_methods
|
|
673
|
+
@loc = loc
|
|
674
|
+
@singleton = singleton
|
|
675
|
+
@module_function = module_function
|
|
676
|
+
end
|
|
677
|
+
|
|
494
678
|
def dependencies
|
|
495
679
|
deps = params.dup
|
|
496
680
|
deps << return_node if return_node
|
|
@@ -499,7 +683,7 @@ module TypeGuessr
|
|
|
499
683
|
end
|
|
500
684
|
|
|
501
685
|
def node_hash
|
|
502
|
-
|
|
686
|
+
NodeKeyGenerator.def_node(name, loc)
|
|
503
687
|
end
|
|
504
688
|
|
|
505
689
|
def node_key(scope_id)
|
|
@@ -513,6 +697,7 @@ module TypeGuessr
|
|
|
513
697
|
tree_field(:params, params, indent),
|
|
514
698
|
tree_field(:return_node, return_node, indent),
|
|
515
699
|
tree_field(:body_nodes, body_nodes, indent),
|
|
700
|
+
tree_field(:called_methods, called_methods, indent),
|
|
516
701
|
tree_field(:singleton, singleton, indent, last: true),
|
|
517
702
|
]
|
|
518
703
|
end
|
|
@@ -521,16 +706,26 @@ module TypeGuessr
|
|
|
521
706
|
# Class/Module node - container for methods and other definitions
|
|
522
707
|
# @param name [String] Class or module name
|
|
523
708
|
# @param methods [Array<DefNode>] Method definitions in this class/module
|
|
524
|
-
# @param
|
|
525
|
-
|
|
709
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
710
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
711
|
+
class ClassModuleNode
|
|
526
712
|
include TreeInspect
|
|
527
713
|
|
|
714
|
+
attr_reader :name, :methods, :called_methods, :loc
|
|
715
|
+
|
|
716
|
+
def initialize(name, methods, called_methods, loc)
|
|
717
|
+
@name = name
|
|
718
|
+
@methods = methods
|
|
719
|
+
@called_methods = called_methods
|
|
720
|
+
@loc = loc
|
|
721
|
+
end
|
|
722
|
+
|
|
528
723
|
def dependencies
|
|
529
724
|
methods
|
|
530
725
|
end
|
|
531
726
|
|
|
532
727
|
def node_hash
|
|
533
|
-
|
|
728
|
+
NodeKeyGenerator.class_module(name, loc)
|
|
534
729
|
end
|
|
535
730
|
|
|
536
731
|
def node_key(scope_id)
|
|
@@ -540,7 +735,8 @@ module TypeGuessr
|
|
|
540
735
|
def tree_inspect_fields(indent)
|
|
541
736
|
[
|
|
542
737
|
tree_field(:name, name, indent),
|
|
543
|
-
tree_field(:methods, methods, indent
|
|
738
|
+
tree_field(:methods, methods, indent),
|
|
739
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
544
740
|
]
|
|
545
741
|
end
|
|
546
742
|
end
|
|
@@ -548,16 +744,26 @@ module TypeGuessr
|
|
|
548
744
|
# Self reference node
|
|
549
745
|
# @param class_name [String] Name of the enclosing class/module
|
|
550
746
|
# @param singleton [Boolean] Whether this self is in a singleton method context
|
|
551
|
-
# @param
|
|
552
|
-
|
|
747
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
748
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
749
|
+
class SelfNode
|
|
553
750
|
include TreeInspect
|
|
554
751
|
|
|
752
|
+
attr_reader :class_name, :singleton, :called_methods, :loc
|
|
753
|
+
|
|
754
|
+
def initialize(class_name, singleton, called_methods, loc)
|
|
755
|
+
@class_name = class_name
|
|
756
|
+
@singleton = singleton
|
|
757
|
+
@called_methods = called_methods
|
|
758
|
+
@loc = loc
|
|
759
|
+
end
|
|
760
|
+
|
|
555
761
|
def dependencies
|
|
556
762
|
[]
|
|
557
763
|
end
|
|
558
764
|
|
|
559
765
|
def node_hash
|
|
560
|
-
|
|
766
|
+
NodeKeyGenerator.self_node(class_name, loc)
|
|
561
767
|
end
|
|
562
768
|
|
|
563
769
|
def node_key(scope_id)
|
|
@@ -567,23 +773,73 @@ module TypeGuessr
|
|
|
567
773
|
def tree_inspect_fields(indent)
|
|
568
774
|
[
|
|
569
775
|
tree_field(:class_name, class_name, indent),
|
|
570
|
-
tree_field(:singleton, singleton, indent
|
|
776
|
+
tree_field(:singleton, singleton, indent),
|
|
777
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
778
|
+
]
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# Type narrowing wrapper node
|
|
783
|
+
# Wraps a value node and signals that falsy types should be removed
|
|
784
|
+
# Used after guard clauses: `return unless x` → x is truthy after guard
|
|
785
|
+
# @param value [Node] The original node to narrow
|
|
786
|
+
# @param kind [Symbol] Narrowing kind (:truthy removes nil/false)
|
|
787
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node
|
|
788
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
789
|
+
class NarrowNode
|
|
790
|
+
include TreeInspect
|
|
791
|
+
|
|
792
|
+
attr_reader :value, :kind, :called_methods, :loc
|
|
793
|
+
|
|
794
|
+
def initialize(value, kind, called_methods, loc)
|
|
795
|
+
@value = value
|
|
796
|
+
@kind = kind
|
|
797
|
+
@called_methods = called_methods
|
|
798
|
+
@loc = loc
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def dependencies
|
|
802
|
+
value ? [value] : []
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def node_hash
|
|
806
|
+
NodeKeyGenerator.narrow(kind, loc)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
def node_key(scope_id)
|
|
810
|
+
"#{scope_id}:#{node_hash}"
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def tree_inspect_fields(indent)
|
|
814
|
+
[
|
|
815
|
+
tree_field(:value, value, indent),
|
|
816
|
+
tree_field(:kind, kind, indent),
|
|
817
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
571
818
|
]
|
|
572
819
|
end
|
|
573
820
|
end
|
|
574
821
|
|
|
575
822
|
# Explicit return statement node
|
|
576
823
|
# @param value [Node, nil] Return value node (nil for bare `return`)
|
|
577
|
-
# @param
|
|
578
|
-
|
|
824
|
+
# @param called_methods [Array<CalledMethod>] Methods called on this node (for method-based inference)
|
|
825
|
+
# @param loc [Integer, nil] Byte offset from start of file (0-indexed)
|
|
826
|
+
class ReturnNode
|
|
579
827
|
include TreeInspect
|
|
580
828
|
|
|
829
|
+
attr_reader :value, :called_methods, :loc
|
|
830
|
+
|
|
831
|
+
def initialize(value, called_methods, loc)
|
|
832
|
+
@value = value
|
|
833
|
+
@called_methods = called_methods
|
|
834
|
+
@loc = loc
|
|
835
|
+
end
|
|
836
|
+
|
|
581
837
|
def dependencies
|
|
582
838
|
value ? [value] : []
|
|
583
839
|
end
|
|
584
840
|
|
|
585
841
|
def node_hash
|
|
586
|
-
|
|
842
|
+
NodeKeyGenerator.return_node(loc)
|
|
587
843
|
end
|
|
588
844
|
|
|
589
845
|
def node_key(scope_id)
|
|
@@ -591,7 +847,10 @@ module TypeGuessr
|
|
|
591
847
|
end
|
|
592
848
|
|
|
593
849
|
def tree_inspect_fields(indent)
|
|
594
|
-
[
|
|
850
|
+
[
|
|
851
|
+
tree_field(:value, value, indent),
|
|
852
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
853
|
+
]
|
|
595
854
|
end
|
|
596
855
|
end
|
|
597
856
|
end
|