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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/ruby_lsp/type_guessr/addon.rb +4 -5
  4. data/lib/ruby_lsp/type_guessr/code_index_adapter.rb +17 -0
  5. data/lib/ruby_lsp/type_guessr/{graph_builder.rb → debug_graph_builder.rb} +3 -3
  6. data/lib/ruby_lsp/type_guessr/debug_server.rb +2 -2
  7. data/lib/ruby_lsp/type_guessr/dsl/activerecord_adapter.rb +404 -0
  8. data/lib/ruby_lsp/type_guessr/dsl/ar_schema_watcher.rb +96 -0
  9. data/lib/ruby_lsp/type_guessr/dsl/ar_type_mapper.rb +51 -0
  10. data/lib/ruby_lsp/type_guessr/dsl.rb +3 -0
  11. data/lib/ruby_lsp/type_guessr/dsl_type_registrar.rb +60 -0
  12. data/lib/ruby_lsp/type_guessr/hover.rb +46 -40
  13. data/lib/ruby_lsp/type_guessr/rails_server_addon.rb +83 -0
  14. data/lib/ruby_lsp/type_guessr/runtime_adapter.rb +90 -16
  15. data/lib/type-guessr.rb +2 -13
  16. data/lib/type_guessr/core/cache/gem_signature_cache.rb +3 -2
  17. data/lib/type_guessr/core/cache.rb +5 -0
  18. data/lib/{ruby_lsp/type_guessr → type_guessr/core}/config.rb +2 -2
  19. data/lib/type_guessr/core/converter/call_converter.rb +161 -0
  20. data/lib/type_guessr/core/converter/container_mutation_converter.rb +241 -0
  21. data/lib/type_guessr/core/converter/context.rb +144 -0
  22. data/lib/type_guessr/core/converter/control_flow_converter.rb +425 -0
  23. data/lib/type_guessr/core/converter/definition_converter.rb +246 -0
  24. data/lib/type_guessr/core/converter/literal_converter.rb +217 -0
  25. data/lib/type_guessr/core/converter/prism_converter.rb +9 -1682
  26. data/lib/type_guessr/core/converter/rbs_converter.rb +15 -1
  27. data/lib/type_guessr/core/converter/registration.rb +100 -0
  28. data/lib/type_guessr/core/converter/variable_converter.rb +225 -0
  29. data/lib/type_guessr/core/converter.rb +4 -0
  30. data/lib/type_guessr/core/index.rb +3 -0
  31. data/lib/type_guessr/core/inference/resolver.rb +206 -208
  32. data/lib/type_guessr/core/inference.rb +4 -0
  33. data/lib/type_guessr/core/ir.rb +3 -0
  34. data/lib/type_guessr/core/logger.rb +3 -5
  35. data/lib/type_guessr/core/registry/method_registry.rb +9 -0
  36. data/lib/type_guessr/core/registry/signature_registry.rb +73 -16
  37. data/lib/type_guessr/core/registry.rb +6 -0
  38. data/lib/type_guessr/core/type_serializer.rb +18 -14
  39. data/lib/type_guessr/core/type_simplifier.rb +5 -5
  40. data/lib/type_guessr/core/types.rb +64 -22
  41. data/lib/type_guessr/core.rb +29 -0
  42. data/lib/type_guessr/mcp/server.rb +55 -46
  43. data/lib/type_guessr/mcp/standalone_runtime.rb +70 -110
  44. data/lib/type_guessr/version.rb +1 -1
  45. metadata +25 -5
  46. 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, return ClassInstance (ignore type args for now)
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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "converter/prism_converter"
4
+ require_relative "converter/rbs_converter"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "index/location_index"