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
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeGuessr
4
+ module Core
5
+ module Converter
6
+ # Literal and type inference helpers for PrismConverter
7
+ class PrismConverter
8
+ private def convert_literal(prism_node)
9
+ type = literal_type_for(prism_node)
10
+ literal_value = extract_literal_value(prism_node)
11
+ IR::LiteralNode.new(type, literal_value, nil, [], convert_loc(prism_node.location))
12
+ end
13
+
14
+ # Extract the actual value from a literal node (for Symbol, Integer, String)
15
+ private def extract_literal_value(prism_node)
16
+ case prism_node
17
+ when Prism::SymbolNode
18
+ prism_node.value.to_sym
19
+ when Prism::IntegerNode
20
+ prism_node.value
21
+ when Prism::StringNode
22
+ prism_node.content
23
+ end
24
+ end
25
+
26
+ private def convert_array_literal(prism_node, context)
27
+ type = array_element_type_for(prism_node)
28
+
29
+ # Convert each element to an IR node
30
+ value_nodes = prism_node.elements.filter_map do |elem|
31
+ next if elem.nil?
32
+
33
+ case elem
34
+ when Prism::SplatNode
35
+ # *arr → convert to CallNode for to_a
36
+ splat_expr = convert(elem.expression, context)
37
+ IR::CallNode.new(:to_a, splat_expr, [], [], nil, false, [], convert_loc(elem.location))
38
+ else
39
+ convert(elem, context)
40
+ end
41
+ end
42
+
43
+ IR::LiteralNode.new(type, nil, value_nodes.empty? ? nil : value_nodes, [], convert_loc(prism_node.location))
44
+ end
45
+
46
+ private def convert_hash_literal(prism_node, context)
47
+ type = hash_element_types_for(prism_node)
48
+ build_hash_literal_node(prism_node, type, context)
49
+ end
50
+
51
+ # Convert KeywordHashNode (keyword arguments in method calls like `foo(a: 1, b: x)`)
52
+ private def convert_keyword_hash(prism_node, context)
53
+ type = infer_keyword_hash_type(prism_node)
54
+ build_hash_literal_node(prism_node, type, context)
55
+ end
56
+
57
+ # Shared helper for hash-like nodes (HashNode, KeywordHashNode)
58
+ private def build_hash_literal_node(prism_node, type, context)
59
+ value_nodes = prism_node.elements.filter_map do |elem|
60
+ case elem
61
+ when Prism::AssocNode
62
+ convert(elem.value, context)
63
+ when Prism::AssocSplatNode
64
+ convert(elem.value, context)
65
+ end
66
+ end
67
+
68
+ IR::LiteralNode.new(type, nil, value_nodes.empty? ? nil : value_nodes, [], convert_loc(prism_node.location))
69
+ end
70
+
71
+ # Infer type for KeywordHashNode (always has symbol keys)
72
+ private def infer_keyword_hash_type(keyword_hash_node)
73
+ return Types::HashShape.new({}) if keyword_hash_node.elements.empty?
74
+
75
+ fields = keyword_hash_node.elements.each_with_object({}) do |elem, hash|
76
+ next unless elem.is_a?(Prism::AssocNode) && elem.key.is_a?(Prism::SymbolNode)
77
+
78
+ hash[elem.key.value.to_sym] = literal_type_for(elem.value)
79
+ end
80
+ Types::HashShape.new(fields)
81
+ end
82
+
83
+ private def literal_type_for(prism_node)
84
+ case prism_node
85
+ when Prism::IntegerNode
86
+ Types::ClassInstance.for("Integer")
87
+ when Prism::FloatNode
88
+ Types::ClassInstance.for("Float")
89
+ when Prism::StringNode, Prism::InterpolatedStringNode
90
+ Types::ClassInstance.for("String")
91
+ when Prism::SymbolNode
92
+ Types::ClassInstance.for("Symbol")
93
+ when Prism::TrueNode
94
+ Types::ClassInstance.for("TrueClass")
95
+ when Prism::FalseNode
96
+ Types::ClassInstance.for("FalseClass")
97
+ when Prism::NilNode
98
+ Types::ClassInstance.for("NilClass")
99
+ when Prism::ArrayNode
100
+ # Infer element type from array contents
101
+ array_element_type_for(prism_node)
102
+ when Prism::HashNode
103
+ hash_element_types_for(prism_node)
104
+ when Prism::RangeNode
105
+ range_element_type_for(prism_node)
106
+ when Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode
107
+ Types::ClassInstance.for("Regexp")
108
+ when Prism::ImaginaryNode
109
+ Types::ClassInstance.for("Complex")
110
+ when Prism::RationalNode
111
+ Types::ClassInstance.for("Rational")
112
+ when Prism::XStringNode, Prism::InterpolatedXStringNode
113
+ Types::ClassInstance.for("String")
114
+ else
115
+ Types::Unknown.instance
116
+ end
117
+ end
118
+
119
+ private def range_element_type_for(range_node)
120
+ left_type = range_node.left ? literal_type_for(range_node.left) : nil
121
+ right_type = range_node.right ? literal_type_for(range_node.right) : nil
122
+
123
+ types = [left_type, right_type].compact
124
+
125
+ # No bounds at all (shouldn't happen in valid Ruby, but handle gracefully)
126
+ return Types::RangeType.new if types.empty?
127
+
128
+ unique_types = types.uniq
129
+
130
+ element_type = if unique_types.size == 1
131
+ unique_types.first
132
+ else
133
+ Types::Union.new(unique_types)
134
+ end
135
+
136
+ Types::RangeType.new(element_type)
137
+ end
138
+
139
+ private def array_element_type_for(array_node)
140
+ return Types::TupleType.new([]) if array_node.elements.empty?
141
+
142
+ element_types = array_node.elements.filter_map do |elem|
143
+ literal_type_for(elem) unless elem.nil?
144
+ end
145
+
146
+ return Types::ArrayType.new if element_types.empty?
147
+
148
+ if element_types.any?(Types::Unknown)
149
+ # Splat or unknown elements → widen to ArrayType(Union)
150
+ unique_types = element_types.uniq
151
+ Types::ArrayType.new(Types::Union.new(unique_types))
152
+ else
153
+ Types::TupleType.new(element_types)
154
+ end
155
+ end
156
+
157
+ private def hash_element_types_for(hash_node)
158
+ return Types::HashShape.new({}) if hash_node.elements.empty?
159
+
160
+ # Check if all keys are symbols for HashShape
161
+ all_symbol_keys = hash_node.elements.all? do |elem|
162
+ elem.is_a?(Prism::AssocNode) && elem.key.is_a?(Prism::SymbolNode)
163
+ end
164
+
165
+ if all_symbol_keys
166
+ # Build HashShape with field types
167
+ fields = {}
168
+ hash_node.elements.each do |elem|
169
+ next unless elem.is_a?(Prism::AssocNode) && elem.key.is_a?(Prism::SymbolNode)
170
+
171
+ field_name = elem.key.value.to_sym
172
+ field_type = literal_type_for(elem.value)
173
+ fields[field_name] = field_type
174
+ end
175
+ Types::HashShape.new(fields)
176
+ else
177
+ # Non-symbol keys or mixed keys - return HashType
178
+ key_types = []
179
+ value_types = []
180
+
181
+ hash_node.elements.each do |elem|
182
+ case elem
183
+ when Prism::AssocNode
184
+ key_types << literal_type_for(elem.key) if elem.key
185
+ value_types << literal_type_for(elem.value) if elem.value
186
+ end
187
+ end
188
+
189
+ return Types::HashType.new if key_types.empty? && value_types.empty?
190
+
191
+ # Deduplicate types
192
+ unique_key_types = key_types.uniq
193
+ unique_value_types = value_types.uniq
194
+
195
+ key_type = if unique_key_types.size == 1
196
+ unique_key_types.first
197
+ elsif unique_key_types.empty?
198
+ Types::Unknown.instance
199
+ else
200
+ Types::Union.new(unique_key_types)
201
+ end
202
+
203
+ value_type = if unique_value_types.size == 1
204
+ unique_value_types.first
205
+ elsif unique_value_types.empty?
206
+ Types::Unknown.instance
207
+ else
208
+ Types::Union.new(unique_value_types)
209
+ end
210
+
211
+ Types::HashType.new(key_type, value_type)
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end