type-guessr 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/lib/ruby_lsp/type_guessr/addon.rb +138 -0
- data/lib/ruby_lsp/type_guessr/config.rb +90 -0
- data/lib/ruby_lsp/type_guessr/debug_server.rb +861 -0
- data/lib/ruby_lsp/type_guessr/graph_builder.rb +349 -0
- data/lib/ruby_lsp/type_guessr/hover.rb +565 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +506 -0
- data/lib/ruby_lsp/type_guessr/type_inferrer.rb +200 -0
- data/lib/type-guessr.rb +28 -0
- data/lib/type_guessr/core/converter/prism_converter.rb +1649 -0
- data/lib/type_guessr/core/converter/rbs_converter.rb +88 -0
- data/lib/type_guessr/core/index/location_index.rb +72 -0
- data/lib/type_guessr/core/inference/resolver.rb +664 -0
- data/lib/type_guessr/core/inference/result.rb +41 -0
- data/lib/type_guessr/core/ir/nodes.rb +599 -0
- data/lib/type_guessr/core/logger.rb +43 -0
- data/lib/type_guessr/core/rbs_provider.rb +304 -0
- data/lib/type_guessr/core/registry/method_registry.rb +106 -0
- data/lib/type_guessr/core/registry/variable_registry.rb +87 -0
- data/lib/type_guessr/core/signature_provider.rb +101 -0
- data/lib/type_guessr/core/type_simplifier.rb +64 -0
- data/lib/type_guessr/core/types.rb +425 -0
- data/lib/type_guessr/version.rb +5 -0
- metadata +81 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
|
|
5
|
+
module TypeGuessr
|
|
6
|
+
module Core
|
|
7
|
+
module Inference
|
|
8
|
+
# Represents the result of type inference with reasoning
|
|
9
|
+
# Contains the inferred type and why it was inferred
|
|
10
|
+
class Result
|
|
11
|
+
attr_reader :type, :reason, :source
|
|
12
|
+
|
|
13
|
+
# @param type [Types::Type] The inferred type
|
|
14
|
+
# @param reason [String] Why this type was inferred
|
|
15
|
+
# @param source [Symbol] Source of the type (:gem, :project, :stdlib, :literal, :unknown)
|
|
16
|
+
def initialize(type, reason, source = :unknown)
|
|
17
|
+
@type = type
|
|
18
|
+
@reason = reason
|
|
19
|
+
@source = source
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(Result) &&
|
|
24
|
+
type == other.type &&
|
|
25
|
+
reason == other.reason &&
|
|
26
|
+
source == other.source
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
[type, reason, source].hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"#{type} (#{reason})"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TypeGuessr
|
|
4
|
+
module Core
|
|
5
|
+
module IR
|
|
6
|
+
# Location information for IR nodes
|
|
7
|
+
# @param line [Integer] Line number (1-indexed)
|
|
8
|
+
# @param col_range [Range] Column range
|
|
9
|
+
Loc = Data.define(:line, :col_range)
|
|
10
|
+
|
|
11
|
+
# Pretty print helper for IR nodes (Prism-style tree output)
|
|
12
|
+
module TreeInspect
|
|
13
|
+
BRANCH = "├── "
|
|
14
|
+
LAST_BRANCH = "└── "
|
|
15
|
+
PIPE = "│ "
|
|
16
|
+
SPACE = " "
|
|
17
|
+
|
|
18
|
+
def tree_inspect(indent: "", last: true, root: false)
|
|
19
|
+
lines = if root
|
|
20
|
+
["@ #{self.class.name.split("::").last} (location: #{format_loc})"]
|
|
21
|
+
else
|
|
22
|
+
prefix = last ? LAST_BRANCH : BRANCH
|
|
23
|
+
indent += (last ? SPACE : PIPE)
|
|
24
|
+
["#{indent.delete_suffix(last ? SPACE : PIPE)}#{prefix}@ #{self.class.name.split("::").last} (location: #{format_loc})"]
|
|
25
|
+
end
|
|
26
|
+
lines.concat(tree_inspect_fields(indent))
|
|
27
|
+
lines.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def format_loc
|
|
33
|
+
loc ? "(#{loc.line},#{loc.col_range.begin})-(#{loc.line},#{loc.col_range.end})" : "∅"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tree_field(name, value, indent, last: false)
|
|
37
|
+
prefix = last ? LAST_BRANCH : BRANCH
|
|
38
|
+
case value
|
|
39
|
+
when nil
|
|
40
|
+
"#{indent}#{prefix}#{name}: ∅"
|
|
41
|
+
when Array
|
|
42
|
+
if value.empty?
|
|
43
|
+
"#{indent}#{prefix}#{name}: (length: 0)"
|
|
44
|
+
else
|
|
45
|
+
lines = ["#{indent}#{prefix}#{name}: (length: #{value.size})"]
|
|
46
|
+
value.each_with_index do |item, idx|
|
|
47
|
+
is_last = idx == value.size - 1
|
|
48
|
+
item_indent = indent + (last ? SPACE : PIPE)
|
|
49
|
+
if item.is_a?(TreeInspect)
|
|
50
|
+
lines << item.tree_inspect(indent: item_indent, last: is_last)
|
|
51
|
+
else
|
|
52
|
+
item_prefix = is_last ? LAST_BRANCH : BRANCH
|
|
53
|
+
lines << "#{item_indent}#{item_prefix}#{item.inspect}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
lines.join("\n")
|
|
57
|
+
end
|
|
58
|
+
when Symbol, String, Integer, TrueClass, FalseClass
|
|
59
|
+
"#{indent}#{prefix}#{name}: #{value.inspect}"
|
|
60
|
+
else
|
|
61
|
+
if value.is_a?(TreeInspect)
|
|
62
|
+
lines = ["#{indent}#{prefix}#{name}:"]
|
|
63
|
+
child_indent = indent + (last ? SPACE : PIPE)
|
|
64
|
+
lines << value.tree_inspect(indent: child_indent, last: true)
|
|
65
|
+
lines.join("\n")
|
|
66
|
+
else
|
|
67
|
+
"#{indent}#{prefix}#{name}: #{value.inspect}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def tree_inspect_fields(_indent)
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
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
|
+
# Literal value node
|
|
107
|
+
# @param type [TypeGuessr::Core::Types::Type] The type of the literal
|
|
108
|
+
# @param literal_value [Object, nil] The actual literal value (for Symbol, Integer, String)
|
|
109
|
+
# @param values [Array<Node>, nil] Internal value nodes for compound literals (Hash/Array)
|
|
110
|
+
# @param loc [Loc] Location information
|
|
111
|
+
#
|
|
112
|
+
# Examples: "hello" → String, 123 → Integer, [] → Array, {} → Hash
|
|
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
|
|
116
|
+
include TreeInspect
|
|
117
|
+
|
|
118
|
+
def dependencies
|
|
119
|
+
values || []
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def node_hash
|
|
123
|
+
type_name = type.is_a?(Class) ? type.name.split("::").last : type.class.name.split("::").last
|
|
124
|
+
"lit:#{type_name}:#{loc&.line}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def node_key(scope_id)
|
|
128
|
+
"#{scope_id}:#{node_hash}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def tree_inspect_fields(indent)
|
|
132
|
+
type_str = type.respond_to?(:name) ? type.name : type.class.name.split("::").last
|
|
133
|
+
[
|
|
134
|
+
tree_field(:type, type_str, indent),
|
|
135
|
+
tree_field(:literal_value, literal_value, indent),
|
|
136
|
+
tree_field(:values, values, indent, last: true),
|
|
137
|
+
]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Local variable write node (assignment)
|
|
142
|
+
# @param name [Symbol] Variable name
|
|
143
|
+
# @param value [Node] The node of the assigned value
|
|
144
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
145
|
+
# @param loc [Loc] Location information
|
|
146
|
+
#
|
|
147
|
+
# Note: called_methods is a shared array object that can be mutated during parsing
|
|
148
|
+
LocalWriteNode = Data.define(:name, :value, :called_methods, :loc) do
|
|
149
|
+
include TreeInspect
|
|
150
|
+
|
|
151
|
+
def dependencies
|
|
152
|
+
value ? [value] : []
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def node_hash
|
|
156
|
+
"local_write:#{name}:#{loc&.line}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def node_key(scope_id)
|
|
160
|
+
"#{scope_id}:#{node_hash}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def tree_inspect_fields(indent)
|
|
164
|
+
[
|
|
165
|
+
tree_field(:name, name, indent),
|
|
166
|
+
tree_field(:value, value, indent),
|
|
167
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
168
|
+
]
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Local variable read node (reference)
|
|
173
|
+
# @param name [Symbol] Variable name
|
|
174
|
+
# @param write_node [LocalWriteNode, nil] The LocalWriteNode this read references
|
|
175
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
176
|
+
# @param loc [Loc] Location information
|
|
177
|
+
#
|
|
178
|
+
# Note: called_methods is shared with LocalWriteNode for method-based inference propagation
|
|
179
|
+
LocalReadNode = Data.define(:name, :write_node, :called_methods, :loc) do
|
|
180
|
+
include TreeInspect
|
|
181
|
+
|
|
182
|
+
def dependencies
|
|
183
|
+
write_node ? [write_node] : []
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def node_hash
|
|
187
|
+
"local_read:#{name}:#{loc&.line}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def node_key(scope_id)
|
|
191
|
+
"#{scope_id}:#{node_hash}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def tree_inspect_fields(indent)
|
|
195
|
+
[
|
|
196
|
+
tree_field(:name, name, indent),
|
|
197
|
+
tree_field(:write_node, write_node, indent),
|
|
198
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
199
|
+
]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Instance variable write node (@name = value)
|
|
204
|
+
# @param name [Symbol] Variable name (e.g., :@recipe)
|
|
205
|
+
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
206
|
+
# @param value [Node] The node of the assigned value
|
|
207
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
208
|
+
# @param loc [Loc] Location information
|
|
209
|
+
InstanceVariableWriteNode = Data.define(:name, :class_name, :value, :called_methods, :loc) do
|
|
210
|
+
include TreeInspect
|
|
211
|
+
|
|
212
|
+
def dependencies
|
|
213
|
+
value ? [value] : []
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def node_hash
|
|
217
|
+
"ivar_write:#{name}:#{loc&.line}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def node_key(scope_id)
|
|
221
|
+
"#{scope_id}:#{node_hash}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def tree_inspect_fields(indent)
|
|
225
|
+
[
|
|
226
|
+
tree_field(:name, name, indent),
|
|
227
|
+
tree_field(:class_name, class_name, indent),
|
|
228
|
+
tree_field(:value, value, indent),
|
|
229
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
230
|
+
]
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Instance variable read node (@name)
|
|
235
|
+
# @param name [Symbol] Variable name (e.g., :@recipe)
|
|
236
|
+
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
237
|
+
# @param write_node [InstanceVariableWriteNode, nil] The write node this read references
|
|
238
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
239
|
+
# @param loc [Loc] Location information
|
|
240
|
+
#
|
|
241
|
+
# Note: write_node may be nil at conversion time if assignment appears later.
|
|
242
|
+
# Resolver performs deferred lookup using class_name.
|
|
243
|
+
InstanceVariableReadNode = Data.define(:name, :class_name, :write_node, :called_methods, :loc) do
|
|
244
|
+
include TreeInspect
|
|
245
|
+
|
|
246
|
+
def dependencies
|
|
247
|
+
write_node ? [write_node] : []
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def node_hash
|
|
251
|
+
"ivar_read:#{name}:#{loc&.line}"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def node_key(scope_id)
|
|
255
|
+
"#{scope_id}:#{node_hash}"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def tree_inspect_fields(indent)
|
|
259
|
+
[
|
|
260
|
+
tree_field(:name, name, indent),
|
|
261
|
+
tree_field(:class_name, class_name, indent),
|
|
262
|
+
tree_field(:write_node, write_node, indent),
|
|
263
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
264
|
+
]
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Class variable write node (@@name = value)
|
|
269
|
+
# @param name [Symbol] Variable name (e.g., :@@count)
|
|
270
|
+
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
271
|
+
# @param value [Node] The node of the assigned value
|
|
272
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
273
|
+
# @param loc [Loc] Location information
|
|
274
|
+
ClassVariableWriteNode = Data.define(:name, :class_name, :value, :called_methods, :loc) do
|
|
275
|
+
include TreeInspect
|
|
276
|
+
|
|
277
|
+
def dependencies
|
|
278
|
+
value ? [value] : []
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def node_hash
|
|
282
|
+
"cvar_write:#{name}:#{loc&.line}"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def node_key(scope_id)
|
|
286
|
+
"#{scope_id}:#{node_hash}"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def tree_inspect_fields(indent)
|
|
290
|
+
[
|
|
291
|
+
tree_field(:name, name, indent),
|
|
292
|
+
tree_field(:class_name, class_name, indent),
|
|
293
|
+
tree_field(:value, value, indent),
|
|
294
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
295
|
+
]
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Class variable read node (@@name)
|
|
300
|
+
# @param name [Symbol] Variable name (e.g., :@@count)
|
|
301
|
+
# @param class_name [String, nil] Enclosing class name for deferred resolution
|
|
302
|
+
# @param write_node [ClassVariableWriteNode, nil] The write node this read references
|
|
303
|
+
# @param called_methods [Array<Symbol>] Methods called on this variable (for method-based inference)
|
|
304
|
+
# @param loc [Loc] Location information
|
|
305
|
+
ClassVariableReadNode = Data.define(:name, :class_name, :write_node, :called_methods, :loc) do
|
|
306
|
+
include TreeInspect
|
|
307
|
+
|
|
308
|
+
def dependencies
|
|
309
|
+
write_node ? [write_node] : []
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def node_hash
|
|
313
|
+
"cvar_read:#{name}:#{loc&.line}"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def node_key(scope_id)
|
|
317
|
+
"#{scope_id}:#{node_hash}"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def tree_inspect_fields(indent)
|
|
321
|
+
[
|
|
322
|
+
tree_field(:name, name, indent),
|
|
323
|
+
tree_field(:class_name, class_name, indent),
|
|
324
|
+
tree_field(:write_node, write_node, indent),
|
|
325
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
326
|
+
]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Method parameter node
|
|
331
|
+
# @param name [Symbol] Parameter name
|
|
332
|
+
# @param kind [Symbol] Parameter kind (:required, :optional, :rest, :keyword_required,
|
|
333
|
+
# :keyword_optional, :keyword_rest, :block, :forwarding)
|
|
334
|
+
# @param default_value [Node, nil] Default value node (nil if no default)
|
|
335
|
+
# @param called_methods [Array<Symbol>] Methods called on this parameter (for method-based inference)
|
|
336
|
+
# @param loc [Loc] Location information
|
|
337
|
+
#
|
|
338
|
+
# Note: called_methods is a shared array object that can be mutated during parsing
|
|
339
|
+
ParamNode = Data.define(:name, :kind, :default_value, :called_methods, :loc) do
|
|
340
|
+
include TreeInspect
|
|
341
|
+
|
|
342
|
+
def dependencies
|
|
343
|
+
default_value ? [default_value] : []
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def node_hash
|
|
347
|
+
"param:#{name}:#{loc&.line}"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def node_key(scope_id)
|
|
351
|
+
"#{scope_id}:#{node_hash}"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def tree_inspect_fields(indent)
|
|
355
|
+
[
|
|
356
|
+
tree_field(:name, name, indent),
|
|
357
|
+
tree_field(:kind, kind, indent),
|
|
358
|
+
tree_field(:default_value, default_value, indent),
|
|
359
|
+
tree_field(:called_methods, called_methods, indent, last: true),
|
|
360
|
+
]
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Constant reference node
|
|
365
|
+
# @param name [String] Constant name (e.g., "DEFAULT_NAME", "User::ADMIN")
|
|
366
|
+
# @param dependency [Node] The node where this constant is defined
|
|
367
|
+
# @param loc [Loc] Location information
|
|
368
|
+
ConstantNode = Data.define(:name, :dependency, :loc) do
|
|
369
|
+
include TreeInspect
|
|
370
|
+
|
|
371
|
+
def dependencies
|
|
372
|
+
dependency ? [dependency] : []
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def node_hash
|
|
376
|
+
"const:#{name}:#{loc&.line}"
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def node_key(scope_id)
|
|
380
|
+
"#{scope_id}:#{node_hash}"
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def tree_inspect_fields(indent)
|
|
384
|
+
[
|
|
385
|
+
tree_field(:name, name, indent),
|
|
386
|
+
tree_field(:dependency, dependency, indent, last: true),
|
|
387
|
+
]
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Method call node
|
|
392
|
+
# @param method [Symbol] Method name
|
|
393
|
+
# @param receiver [Node, nil] Receiver node (nil for implicit self)
|
|
394
|
+
# @param args [Array<Node>] Argument nodes
|
|
395
|
+
# @param block_params [Array<BlockParamSlot>] Block parameter slots
|
|
396
|
+
# @param block_body [Node, nil] Block body return node (for inferring block return type)
|
|
397
|
+
# @param has_block [Boolean] Whether a block was provided (even if empty)
|
|
398
|
+
# @param loc [Loc] Location information
|
|
399
|
+
CallNode = Data.define(:method, :receiver, :args, :block_params, :block_body, :has_block, :loc) do
|
|
400
|
+
include TreeInspect
|
|
401
|
+
|
|
402
|
+
def dependencies
|
|
403
|
+
deps = []
|
|
404
|
+
deps << receiver if receiver
|
|
405
|
+
deps.concat(args)
|
|
406
|
+
deps << block_body if block_body
|
|
407
|
+
deps
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def node_hash
|
|
411
|
+
"call:#{method}:#{loc&.line}"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def node_key(scope_id)
|
|
415
|
+
"#{scope_id}:#{node_hash}"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def tree_inspect_fields(indent)
|
|
419
|
+
[
|
|
420
|
+
tree_field(:method, method, indent),
|
|
421
|
+
tree_field(:receiver, receiver, indent),
|
|
422
|
+
tree_field(:args, args, indent),
|
|
423
|
+
tree_field(:block_params, block_params, indent),
|
|
424
|
+
tree_field(:block_body, block_body, indent),
|
|
425
|
+
tree_field(:has_block, has_block, indent, last: true),
|
|
426
|
+
]
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Block parameter slot
|
|
431
|
+
# Represents a parameter slot in a block (e.g., |user| in users.each { |user| ... })
|
|
432
|
+
# @param index [Integer] Parameter index (0-based)
|
|
433
|
+
# @param call_node [CallNode] The call node this slot belongs to
|
|
434
|
+
# @param loc [Loc] Location information for the parameter itself
|
|
435
|
+
BlockParamSlot = Data.define(:index, :call_node, :loc) do
|
|
436
|
+
include TreeInspect
|
|
437
|
+
|
|
438
|
+
def dependencies
|
|
439
|
+
[call_node]
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def node_hash
|
|
443
|
+
"bparam:#{index}:#{loc&.line}"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def node_key(scope_id)
|
|
447
|
+
"#{scope_id}:#{node_hash}"
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def tree_inspect_fields(indent)
|
|
451
|
+
[
|
|
452
|
+
tree_field(:index, index, indent),
|
|
453
|
+
tree_field(:call_node, "(CallNode ref)", indent, last: true),
|
|
454
|
+
]
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Branch merge node
|
|
459
|
+
# Represents the convergence point of multiple branches (if/else, case/when, etc.)
|
|
460
|
+
# The type is the union of all branch types
|
|
461
|
+
# @param branches [Array<Node>] Final nodes from each branch
|
|
462
|
+
# @param loc [Loc] Location information
|
|
463
|
+
MergeNode = Data.define(:branches, :loc) do
|
|
464
|
+
include TreeInspect
|
|
465
|
+
|
|
466
|
+
def dependencies
|
|
467
|
+
branches
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def node_hash
|
|
471
|
+
"merge:#{loc&.line}"
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def node_key(scope_id)
|
|
475
|
+
"#{scope_id}:#{node_hash}"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def tree_inspect_fields(indent)
|
|
479
|
+
[tree_field(:branches, branches, indent, last: true)]
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Method definition node
|
|
484
|
+
# @param name [Symbol] Method name
|
|
485
|
+
# @param class_name [String, nil] Enclosing class name
|
|
486
|
+
# @param params [Array<ParamNode>] Parameter nodes
|
|
487
|
+
# @param return_node [Node] Node representing the return value
|
|
488
|
+
# @param body_nodes [Array<Node>] All nodes in the method body
|
|
489
|
+
# @param loc [Loc] Location information
|
|
490
|
+
# @param singleton [Boolean] true if this is a singleton method (def self.method_name)
|
|
491
|
+
DefNode = Data.define(:name, :class_name, :params, :return_node, :body_nodes, :loc, :singleton) do
|
|
492
|
+
include TreeInspect
|
|
493
|
+
|
|
494
|
+
def dependencies
|
|
495
|
+
deps = params.dup
|
|
496
|
+
deps << return_node if return_node
|
|
497
|
+
deps.concat(body_nodes || [])
|
|
498
|
+
deps
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def node_hash
|
|
502
|
+
"def:#{name}:#{loc&.line}"
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def node_key(scope_id)
|
|
506
|
+
"#{scope_id}:#{node_hash}"
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def tree_inspect_fields(indent)
|
|
510
|
+
[
|
|
511
|
+
tree_field(:name, name, indent),
|
|
512
|
+
tree_field(:class_name, class_name, indent),
|
|
513
|
+
tree_field(:params, params, indent),
|
|
514
|
+
tree_field(:return_node, return_node, indent),
|
|
515
|
+
tree_field(:body_nodes, body_nodes, indent),
|
|
516
|
+
tree_field(:singleton, singleton, indent, last: true),
|
|
517
|
+
]
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Class/Module node - container for methods and other definitions
|
|
522
|
+
# @param name [String] Class or module name
|
|
523
|
+
# @param methods [Array<DefNode>] Method definitions in this class/module
|
|
524
|
+
# @param loc [Loc] Location information
|
|
525
|
+
ClassModuleNode = Data.define(:name, :methods, :loc) do
|
|
526
|
+
include TreeInspect
|
|
527
|
+
|
|
528
|
+
def dependencies
|
|
529
|
+
methods
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def node_hash
|
|
533
|
+
"class:#{name}:#{loc&.line}"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def node_key(scope_id)
|
|
537
|
+
"#{scope_id}:#{node_hash}"
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def tree_inspect_fields(indent)
|
|
541
|
+
[
|
|
542
|
+
tree_field(:name, name, indent),
|
|
543
|
+
tree_field(:methods, methods, indent, last: true),
|
|
544
|
+
]
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# Self reference node
|
|
549
|
+
# @param class_name [String] Name of the enclosing class/module
|
|
550
|
+
# @param singleton [Boolean] Whether this self is in a singleton method context
|
|
551
|
+
# @param loc [Loc] Location information
|
|
552
|
+
SelfNode = Data.define(:class_name, :singleton, :loc) do
|
|
553
|
+
include TreeInspect
|
|
554
|
+
|
|
555
|
+
def dependencies
|
|
556
|
+
[]
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def node_hash
|
|
560
|
+
"self:#{class_name}:#{loc&.line}"
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def node_key(scope_id)
|
|
564
|
+
"#{scope_id}:#{node_hash}"
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def tree_inspect_fields(indent)
|
|
568
|
+
[
|
|
569
|
+
tree_field(:class_name, class_name, indent),
|
|
570
|
+
tree_field(:singleton, singleton, indent, last: true),
|
|
571
|
+
]
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Explicit return statement node
|
|
576
|
+
# @param value [Node, nil] Return value node (nil for bare `return`)
|
|
577
|
+
# @param loc [Loc] Location information
|
|
578
|
+
ReturnNode = Data.define(:value, :loc) do
|
|
579
|
+
include TreeInspect
|
|
580
|
+
|
|
581
|
+
def dependencies
|
|
582
|
+
value ? [value] : []
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def node_hash
|
|
586
|
+
"return:#{loc&.line}"
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def node_key(scope_id)
|
|
590
|
+
"#{scope_id}:#{node_hash}"
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def tree_inspect_fields(indent)
|
|
594
|
+
[tree_field(:value, value, indent, last: true)]
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../ruby_lsp/type_guessr/config"
|
|
4
|
+
|
|
5
|
+
module TypeGuessr
|
|
6
|
+
module Core
|
|
7
|
+
# Unified logging interface for TypeGuessr
|
|
8
|
+
# Uses Config.debug? to control output
|
|
9
|
+
module Logger
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Log debug message with optional context
|
|
13
|
+
# @param msg [String] the debug message
|
|
14
|
+
# @param context [Hash] optional context information
|
|
15
|
+
def debug(msg, context = {})
|
|
16
|
+
return unless debug_enabled?
|
|
17
|
+
|
|
18
|
+
output = "[TypeGuessr:DEBUG] #{msg}"
|
|
19
|
+
output += " #{context.inspect}" unless context.empty?
|
|
20
|
+
warn output
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Log error message with optional exception
|
|
24
|
+
# @param msg [String] the error message
|
|
25
|
+
# @param exception [Exception, nil] optional exception for backtrace
|
|
26
|
+
def error(msg, exception = nil)
|
|
27
|
+
return unless debug_enabled?
|
|
28
|
+
|
|
29
|
+
warn "[TypeGuessr:ERROR] #{msg}"
|
|
30
|
+
return unless exception
|
|
31
|
+
|
|
32
|
+
warn " #{exception.class}: #{exception.message}"
|
|
33
|
+
warn exception.backtrace.first(5).map { |l| " #{l}" }.join("\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if debug mode is enabled
|
|
37
|
+
# @return [Boolean] true if Config.debug? returns true
|
|
38
|
+
def debug_enabled?
|
|
39
|
+
RubyLsp::TypeGuessr::Config.debug?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|