type-guessr 0.0.2 → 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 +3 -3
- data/lib/ruby_lsp/type_guessr/addon.rb +4 -5
- data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +17 -0
- data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +3 -3
- data/lib/ruby_lsp/type_guessr/debug_server.rb +2 -2
- 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 +46 -40
- data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
- data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +90 -16
- data/lib/type-guessr.rb +2 -13
- data/lib/type_guessr/core/cache/gem_signature_cache.rb +3 -2
- data/lib/type_guessr/core/cache.rb +5 -0
- data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +2 -2
- 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 +9 -1682
- data/lib/type_guessr/core/converter/rbs_converter.rb +15 -1
- 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.rb +3 -0
- data/lib/type_guessr/core/inference/resolver.rb +206 -208
- data/lib/type_guessr/core/inference.rb +4 -0
- data/lib/type_guessr/core/ir.rb +3 -0
- data/lib/type_guessr/core/logger.rb +3 -5
- data/lib/type_guessr/core/registry/method_registry.rb +9 -0
- data/lib/type_guessr/core/registry/signature_registry.rb +73 -16
- data/lib/type_guessr/core/registry.rb +6 -0
- data/lib/type_guessr/core/type_serializer.rb +18 -14
- data/lib/type_guessr/core/type_simplifier.rb +5 -5
- data/lib/type_guessr/core/types.rb +64 -22
- data/lib/type_guessr/core.rb +29 -0
- data/lib/type_guessr/mcp/server.rb +55 -46
- data/lib/type_guessr/mcp/standalone_runtime.rb +70 -110
- data/lib/type_guessr/version.rb +1 -1
- metadata +25 -5
- data/.mcp.json +0 -9
|
@@ -12,6 +12,12 @@ module TypeGuessr
|
|
|
12
12
|
# This class only handles conversion (RBS → internal types).
|
|
13
13
|
# Type variable substitution is handled separately by Type#substitute.
|
|
14
14
|
class RBSConverter
|
|
15
|
+
# @param class_type_params [Hash{String => Array<Symbol>}] class-level type parameter names
|
|
16
|
+
# e.g., { "Set" => [:A], "Enumerator" => [:Elem, :Return] }
|
|
17
|
+
def initialize(class_type_params = {})
|
|
18
|
+
@class_type_params = class_type_params
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
# Convert RBS type to internal type system
|
|
16
22
|
# @param rbs_type [RBS::Types::t] the RBS type
|
|
17
23
|
# @return [Types::Type] internal type representation (TypeVariables preserved)
|
|
@@ -69,7 +75,15 @@ module TypeGuessr
|
|
|
69
75
|
return Types::RangeType.new(element_type)
|
|
70
76
|
end
|
|
71
77
|
|
|
72
|
-
# For other generic types,
|
|
78
|
+
# For other generic types, build type_params hash from class definition
|
|
79
|
+
param_names = @class_type_params[class_name]
|
|
80
|
+
if param_names && rbs_type.args.any?
|
|
81
|
+
type_params = param_names.zip(rbs_type.args).to_h do |name, arg|
|
|
82
|
+
[name, arg ? convert(arg) : Types::Unknown.instance]
|
|
83
|
+
end
|
|
84
|
+
return Types::ClassInstance.for(class_name, type_params)
|
|
85
|
+
end
|
|
86
|
+
|
|
73
87
|
Types::ClassInstance.for(class_name)
|
|
74
88
|
end
|
|
75
89
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TypeGuessr
|
|
4
|
+
module Core
|
|
5
|
+
module Converter
|
|
6
|
+
# Node registration and location conversion methods for PrismConverter
|
|
7
|
+
class PrismConverter
|
|
8
|
+
private def convert_loc(prism_location)
|
|
9
|
+
prism_location.start_offset
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Register node in location_index and registries during conversion
|
|
13
|
+
# This eliminates the need for a separate tree traversal after conversion
|
|
14
|
+
private def register_node(node, context)
|
|
15
|
+
return unless context.location_index
|
|
16
|
+
|
|
17
|
+
case node
|
|
18
|
+
when IR::DefNode
|
|
19
|
+
# DefNode uses singleton-adjusted method_scope for registration
|
|
20
|
+
method_scope = singleton_scope_for(context.current_class_name || "", singleton: node.singleton)
|
|
21
|
+
context.location_index.add(context.file_path, node, method_scope)
|
|
22
|
+
register_method(node, context)
|
|
23
|
+
|
|
24
|
+
# Register params (created directly, not via convert)
|
|
25
|
+
# Use method scope with method name for params
|
|
26
|
+
param_scope = method_scope.empty? ? "##{node.name}" : "#{method_scope}##{node.name}"
|
|
27
|
+
node.params&.each do |param|
|
|
28
|
+
context.location_index.add(context.file_path, param, param_scope)
|
|
29
|
+
end
|
|
30
|
+
when IR::ClassModuleNode
|
|
31
|
+
# ClassModuleNode uses parent scope for registration
|
|
32
|
+
context.location_index.add(context.file_path, node, context.scope_id)
|
|
33
|
+
register_class_module(node, context)
|
|
34
|
+
when IR::CallNode
|
|
35
|
+
context.location_index.add(context.file_path, node, context.scope_id)
|
|
36
|
+
# Register block params (created directly, not via convert)
|
|
37
|
+
node.block_params&.each do |param|
|
|
38
|
+
context.location_index.add(context.file_path, param, context.scope_id)
|
|
39
|
+
end
|
|
40
|
+
when IR::InstanceVariableWriteNode
|
|
41
|
+
context.location_index.add(context.file_path, node, context.scope_id)
|
|
42
|
+
context.ivar_registry&.register(node.class_name, node.name, node, file_path: context.file_path)
|
|
43
|
+
when IR::ClassVariableWriteNode
|
|
44
|
+
context.location_index.add(context.file_path, node, context.scope_id)
|
|
45
|
+
context.cvar_registry&.register(node.class_name, node.name, node, file_path: context.file_path)
|
|
46
|
+
else
|
|
47
|
+
# All other nodes (MergeNode, LiteralNode, etc.)
|
|
48
|
+
context.location_index.add(context.file_path, node, context.scope_id)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Register method in method_registry
|
|
53
|
+
# Only registers top-level methods; class methods are handled by register_class_module
|
|
54
|
+
private def register_method(node, context)
|
|
55
|
+
return unless context.method_registry
|
|
56
|
+
|
|
57
|
+
# Only register top-level methods (no class context)
|
|
58
|
+
return unless (context.current_class_name || "").empty?
|
|
59
|
+
|
|
60
|
+
context.method_registry.register("", node.name.to_s, node, file_path: context.file_path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Register methods from a class/module in method_registry
|
|
64
|
+
private def register_class_module(node, context)
|
|
65
|
+
return unless context.method_registry
|
|
66
|
+
|
|
67
|
+
# Build the full class path from parent context + node name
|
|
68
|
+
parent_path = context.current_class_name || ""
|
|
69
|
+
class_path = parent_path.empty? ? node.name : "#{parent_path}::#{node.name}"
|
|
70
|
+
|
|
71
|
+
# Register each method in the class (nested classes are handled recursively via convert)
|
|
72
|
+
node.methods&.each do |method|
|
|
73
|
+
next if method.is_a?(IR::ClassModuleNode)
|
|
74
|
+
|
|
75
|
+
method_scope = singleton_scope_for(class_path, singleton: method.singleton)
|
|
76
|
+
context.method_registry.register(method_scope, method.name.to_s, method, file_path: context.file_path)
|
|
77
|
+
|
|
78
|
+
# module_function: also register as singleton method
|
|
79
|
+
if method.module_function
|
|
80
|
+
singleton_scope = singleton_scope_for(class_path, singleton: true)
|
|
81
|
+
context.method_registry.register(singleton_scope, method.name.to_s, method, file_path: context.file_path)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Build singleton class scope for method registration/lookup
|
|
87
|
+
# Singleton methods use "<Class:ClassName>" suffix to match RubyIndexer convention
|
|
88
|
+
# @param scope [String] Base scope (e.g., "RBS::Environment")
|
|
89
|
+
# @param singleton [Boolean] Whether the method is a singleton method
|
|
90
|
+
# @return [String] Scope with singleton class suffix if applicable
|
|
91
|
+
private def singleton_scope_for(scope, singleton:)
|
|
92
|
+
return scope unless singleton
|
|
93
|
+
|
|
94
|
+
parent_name = IR.extract_last_name(scope) || "Object"
|
|
95
|
+
scope.empty? ? "<Class:Object>" : "#{scope}::<Class:#{parent_name}>"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TypeGuessr
|
|
4
|
+
module Core
|
|
5
|
+
module Converter
|
|
6
|
+
# Variable read/write and compound assignment methods for PrismConverter
|
|
7
|
+
class PrismConverter
|
|
8
|
+
private def convert_local_variable_write(prism_node, context)
|
|
9
|
+
value_node = convert(prism_node.value, context)
|
|
10
|
+
write_node = IR::LocalWriteNode.new(prism_node.name, value_node, [], convert_loc(prism_node.location))
|
|
11
|
+
context.register_variable(prism_node.name, write_node)
|
|
12
|
+
write_node
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private def convert_local_variable_read(prism_node, context)
|
|
16
|
+
# Look up the most recent assignment
|
|
17
|
+
write_node = context.lookup_variable(prism_node.name)
|
|
18
|
+
|
|
19
|
+
IR::LocalReadNode.new(
|
|
20
|
+
prism_node.name,
|
|
21
|
+
write_node,
|
|
22
|
+
# Share called_methods array for method-based inference
|
|
23
|
+
# nil case: rescue binding (=> e), pattern matching binding, etc. (not yet implemented)
|
|
24
|
+
write_node&.called_methods || [],
|
|
25
|
+
convert_loc(prism_node.location)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private def convert_instance_variable_write(prism_node, context)
|
|
30
|
+
value_node = convert(prism_node.value, context)
|
|
31
|
+
class_name = context.current_class_name
|
|
32
|
+
|
|
33
|
+
write_node = IR::InstanceVariableWriteNode.new(
|
|
34
|
+
prism_node.name,
|
|
35
|
+
class_name,
|
|
36
|
+
value_node,
|
|
37
|
+
# Share called_methods with value node for type propagation
|
|
38
|
+
# nil case: value is an unhandled node type (convert() returns nil)
|
|
39
|
+
value_node&.called_methods || [],
|
|
40
|
+
convert_loc(prism_node.location)
|
|
41
|
+
)
|
|
42
|
+
# Register at class level so it's visible across methods
|
|
43
|
+
context.register_instance_variable(prism_node.name, write_node)
|
|
44
|
+
write_node
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private def convert_instance_variable_read(prism_node, context)
|
|
48
|
+
# Look up from class level first
|
|
49
|
+
write_node = context.lookup_instance_variable(prism_node.name)
|
|
50
|
+
|
|
51
|
+
IR::InstanceVariableReadNode.new(
|
|
52
|
+
prism_node.name,
|
|
53
|
+
context.current_class_name,
|
|
54
|
+
write_node,
|
|
55
|
+
# Share called_methods array for method-based inference
|
|
56
|
+
# nil case: instance variable read before any assignment in current file
|
|
57
|
+
write_node&.called_methods || [],
|
|
58
|
+
convert_loc(prism_node.location)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private def convert_class_variable_write(prism_node, context)
|
|
63
|
+
value_node = convert(prism_node.value, context)
|
|
64
|
+
|
|
65
|
+
write_node = IR::ClassVariableWriteNode.new(
|
|
66
|
+
prism_node.name,
|
|
67
|
+
context.current_class_name,
|
|
68
|
+
value_node,
|
|
69
|
+
# Share called_methods with value node for type propagation
|
|
70
|
+
# nil case: value is an unhandled node type (e.g., LambdaNode)
|
|
71
|
+
value_node&.called_methods || [],
|
|
72
|
+
convert_loc(prism_node.location)
|
|
73
|
+
)
|
|
74
|
+
context.register_variable(prism_node.name, write_node)
|
|
75
|
+
write_node
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private def convert_class_variable_read(prism_node, context)
|
|
79
|
+
write_node = context.lookup_variable(prism_node.name)
|
|
80
|
+
|
|
81
|
+
IR::ClassVariableReadNode.new(
|
|
82
|
+
prism_node.name,
|
|
83
|
+
context.current_class_name,
|
|
84
|
+
write_node,
|
|
85
|
+
# Share called_methods array for method-based inference
|
|
86
|
+
# nil case: class variable read before any assignment in current file
|
|
87
|
+
write_node&.called_methods || [],
|
|
88
|
+
convert_loc(prism_node.location)
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Compound assignment: x ||= value
|
|
93
|
+
# Result type is union of original and new value type
|
|
94
|
+
private def convert_local_variable_or_write(prism_node, context)
|
|
95
|
+
convert_or_write(prism_node, context, :local)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Compound assignment: x &&= value
|
|
99
|
+
# Result type is union of original and new value type
|
|
100
|
+
private def convert_local_variable_and_write(prism_node, context)
|
|
101
|
+
convert_and_write(prism_node, context, :local)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Compound assignment: x += value, x -= value, etc.
|
|
105
|
+
# Result type depends on the operator method return type
|
|
106
|
+
private def convert_local_variable_operator_write(prism_node, context)
|
|
107
|
+
convert_operator_write(prism_node, context, :local)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private def convert_instance_variable_or_write(prism_node, context)
|
|
111
|
+
convert_or_write(prism_node, context, :instance)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private def convert_instance_variable_and_write(prism_node, context)
|
|
115
|
+
convert_and_write(prism_node, context, :instance)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private def convert_instance_variable_operator_write(prism_node, context)
|
|
119
|
+
convert_operator_write(prism_node, context, :instance)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Generic ||= handler
|
|
123
|
+
# x ||= value means: if x is nil/false, x = value, else keep x
|
|
124
|
+
# Uses OrNode to apply truthiness filtering (removes nil/false from LHS)
|
|
125
|
+
private def convert_or_write(prism_node, context, kind)
|
|
126
|
+
original_node = lookup_by_kind(prism_node.name, kind, context)
|
|
127
|
+
value_node = convert(prism_node.value, context)
|
|
128
|
+
|
|
129
|
+
or_node = if original_node
|
|
130
|
+
IR::OrNode.new(
|
|
131
|
+
original_node,
|
|
132
|
+
value_node,
|
|
133
|
+
[],
|
|
134
|
+
convert_loc(prism_node.location)
|
|
135
|
+
)
|
|
136
|
+
else
|
|
137
|
+
value_node
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
write_node = create_write_node(prism_node.name, kind, or_node, context, prism_node.location)
|
|
141
|
+
register_by_kind(prism_node.name, write_node, kind, context)
|
|
142
|
+
write_node
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Generic &&= handler
|
|
146
|
+
# x &&= value means: if x is truthy, x = value, else keep x
|
|
147
|
+
# Type is union of original type and value type
|
|
148
|
+
private def convert_and_write(prism_node, context, kind)
|
|
149
|
+
original_node = lookup_by_kind(prism_node.name, kind, context)
|
|
150
|
+
value_node = convert(prism_node.value, context)
|
|
151
|
+
|
|
152
|
+
# Create merge node for union type (original | value)
|
|
153
|
+
branches = []
|
|
154
|
+
branches << original_node if original_node
|
|
155
|
+
branches << value_node
|
|
156
|
+
|
|
157
|
+
merge_node = if branches.size == 1
|
|
158
|
+
branches.first
|
|
159
|
+
else
|
|
160
|
+
IR::MergeNode.new(
|
|
161
|
+
branches,
|
|
162
|
+
[],
|
|
163
|
+
convert_loc(prism_node.location)
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
write_node = create_write_node(prism_node.name, kind, merge_node, context, prism_node.location)
|
|
168
|
+
register_by_kind(prism_node.name, write_node, kind, context)
|
|
169
|
+
write_node
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Generic operator write handler (+=, -=, *=, etc.)
|
|
173
|
+
# x += value is equivalent to x = x.+(value)
|
|
174
|
+
# Type is the return type of the operator method
|
|
175
|
+
private def convert_operator_write(prism_node, context, kind)
|
|
176
|
+
original_node = lookup_by_kind(prism_node.name, kind, context)
|
|
177
|
+
value_node = convert(prism_node.value, context)
|
|
178
|
+
|
|
179
|
+
# Create a call node representing x.operator(value)
|
|
180
|
+
call_node = IR::CallNode.new(
|
|
181
|
+
prism_node.binary_operator, original_node, [value_node], [], nil, false, [], convert_loc(prism_node.location)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Create write node with call result as value
|
|
185
|
+
write_node = create_write_node(prism_node.name, kind, call_node, context, prism_node.location)
|
|
186
|
+
register_by_kind(prism_node.name, write_node, kind, context)
|
|
187
|
+
write_node
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Helper to create the appropriate write node type based on kind
|
|
191
|
+
private def create_write_node(name, kind, value, context, location)
|
|
192
|
+
loc = convert_loc(location)
|
|
193
|
+
case kind
|
|
194
|
+
when :local
|
|
195
|
+
IR::LocalWriteNode.new(name, value, [], loc)
|
|
196
|
+
when :instance
|
|
197
|
+
IR::InstanceVariableWriteNode.new(name, context.current_class_name, value, [], loc)
|
|
198
|
+
when :class
|
|
199
|
+
IR::ClassVariableWriteNode.new(name, context.current_class_name, value, [], loc)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Helper to lookup variable by kind
|
|
204
|
+
private def lookup_by_kind(name, kind, context)
|
|
205
|
+
case kind
|
|
206
|
+
when :instance
|
|
207
|
+
context.lookup_instance_variable(name)
|
|
208
|
+
else
|
|
209
|
+
context.lookup_variable(name)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Helper to register variable by kind
|
|
214
|
+
private def register_by_kind(name, node, kind, context)
|
|
215
|
+
case kind
|
|
216
|
+
when :instance
|
|
217
|
+
context.register_instance_variable(name, node)
|
|
218
|
+
else
|
|
219
|
+
context.register_variable(name, node)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|