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
|
@@ -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
|