t-ruby 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 +221 -0
- data/bin/trc +6 -0
- data/lib/t_ruby/benchmark.rb +592 -0
- data/lib/t_ruby/bundler_integration.rb +569 -0
- data/lib/t_ruby/cache.rb +774 -0
- data/lib/t_ruby/cli.rb +106 -0
- data/lib/t_ruby/compiler.rb +299 -0
- data/lib/t_ruby/config.rb +53 -0
- data/lib/t_ruby/constraint_checker.rb +441 -0
- data/lib/t_ruby/declaration_generator.rb +298 -0
- data/lib/t_ruby/doc_generator.rb +474 -0
- data/lib/t_ruby/error_handler.rb +132 -0
- data/lib/t_ruby/generic_type_parser.rb +68 -0
- data/lib/t_ruby/intersection_type_parser.rb +30 -0
- data/lib/t_ruby/ir.rb +1301 -0
- data/lib/t_ruby/lsp_server.rb +994 -0
- data/lib/t_ruby/package_manager.rb +735 -0
- data/lib/t_ruby/parser.rb +245 -0
- data/lib/t_ruby/parser_combinator.rb +942 -0
- data/lib/t_ruby/rbs_generator.rb +71 -0
- data/lib/t_ruby/runtime_validator.rb +367 -0
- data/lib/t_ruby/smt_solver.rb +1076 -0
- data/lib/t_ruby/type_alias_registry.rb +102 -0
- data/lib/t_ruby/type_checker.rb +770 -0
- data/lib/t_ruby/type_erasure.rb +26 -0
- data/lib/t_ruby/type_inferencer.rb +580 -0
- data/lib/t_ruby/union_type_parser.rb +38 -0
- data/lib/t_ruby/version.rb +5 -0
- data/lib/t_ruby/watcher.rb +320 -0
- data/lib/t_ruby.rb +42 -0
- metadata +87 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class TypeErasure
|
|
5
|
+
def initialize(source)
|
|
6
|
+
@source = source
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def erase
|
|
10
|
+
result = @source.dup
|
|
11
|
+
|
|
12
|
+
# Remove type alias definitions: type AliasName = TypeDefinition
|
|
13
|
+
result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/, '')
|
|
14
|
+
|
|
15
|
+
# Remove parameter type annotations: (name: Type) -> (name)
|
|
16
|
+
# Matches: parameter_name: TypeName
|
|
17
|
+
result = result.gsub(/\b(\w+)\s*:\s*\w+/, '\1')
|
|
18
|
+
|
|
19
|
+
# Remove return type annotations: ): TypeName -> )
|
|
20
|
+
# Matches: ): TypeName or ): TypeName (with spaces/EOF)
|
|
21
|
+
result = result.gsub(/\)\s*:\s*\w+(\s|$)/, ')\1')
|
|
22
|
+
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Represents an inferred type with confidence score
|
|
5
|
+
class InferredType
|
|
6
|
+
attr_reader :type, :confidence, :source, :location
|
|
7
|
+
|
|
8
|
+
# Confidence levels
|
|
9
|
+
HIGH = 1.0
|
|
10
|
+
MEDIUM = 0.7
|
|
11
|
+
LOW = 0.4
|
|
12
|
+
|
|
13
|
+
def initialize(type:, confidence: HIGH, source: :unknown, location: nil)
|
|
14
|
+
@type = type
|
|
15
|
+
@confidence = confidence
|
|
16
|
+
@source = source
|
|
17
|
+
@location = location
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_s
|
|
21
|
+
@type
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def high_confidence?
|
|
25
|
+
@confidence >= HIGH
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def medium_confidence?
|
|
29
|
+
@confidence >= MEDIUM && @confidence < HIGH
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def low_confidence?
|
|
33
|
+
@confidence < MEDIUM
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Type inference engine
|
|
38
|
+
class TypeInferencer
|
|
39
|
+
attr_reader :inferred_types, :warnings
|
|
40
|
+
|
|
41
|
+
# Literal type patterns
|
|
42
|
+
LITERAL_PATTERNS = {
|
|
43
|
+
# String literals
|
|
44
|
+
/^".*"$/ => "String",
|
|
45
|
+
/^'.*'$/ => "String",
|
|
46
|
+
# Integer literals
|
|
47
|
+
/^-?\d+$/ => "Integer",
|
|
48
|
+
/^0x[0-9a-fA-F]+$/ => "Integer",
|
|
49
|
+
/^0b[01]+$/ => "Integer",
|
|
50
|
+
/^0o[0-7]+$/ => "Integer",
|
|
51
|
+
# Float literals
|
|
52
|
+
/^-?\d+\.\d+$/ => "Float",
|
|
53
|
+
/^-?\d+e[+-]?\d+$/i => "Float",
|
|
54
|
+
# Boolean literals
|
|
55
|
+
/^true$/ => "Boolean",
|
|
56
|
+
/^false$/ => "Boolean",
|
|
57
|
+
# Nil literal
|
|
58
|
+
/^nil$/ => "nil",
|
|
59
|
+
# Symbol literals
|
|
60
|
+
/^:\w+$/ => "Symbol",
|
|
61
|
+
# Array literals
|
|
62
|
+
/^\[.*\]$/ => "Array",
|
|
63
|
+
# Hash literals
|
|
64
|
+
/^\{.*\}$/ => "Hash",
|
|
65
|
+
# Regex literals
|
|
66
|
+
/^\/.*\/$/ => "Regexp"
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
# Method return type patterns
|
|
70
|
+
METHOD_RETURN_TYPES = {
|
|
71
|
+
# String methods
|
|
72
|
+
"to_s" => "String",
|
|
73
|
+
"upcase" => "String",
|
|
74
|
+
"downcase" => "String",
|
|
75
|
+
"strip" => "String",
|
|
76
|
+
"chomp" => "String",
|
|
77
|
+
"reverse" => "String",
|
|
78
|
+
"capitalize" => "String",
|
|
79
|
+
"gsub" => "String",
|
|
80
|
+
"sub" => "String",
|
|
81
|
+
"chars" => "Array<String>",
|
|
82
|
+
"split" => "Array<String>",
|
|
83
|
+
"lines" => "Array<String>",
|
|
84
|
+
"bytes" => "Array<Integer>",
|
|
85
|
+
# Integer/Numeric methods
|
|
86
|
+
"to_i" => "Integer",
|
|
87
|
+
"to_int" => "Integer",
|
|
88
|
+
"abs" => "Integer",
|
|
89
|
+
"ceil" => "Integer",
|
|
90
|
+
"floor" => "Integer",
|
|
91
|
+
"round" => "Integer",
|
|
92
|
+
"to_f" => "Float",
|
|
93
|
+
# Array methods
|
|
94
|
+
"length" => "Integer",
|
|
95
|
+
"size" => "Integer",
|
|
96
|
+
"count" => "Integer",
|
|
97
|
+
"first" => nil, # Depends on array type
|
|
98
|
+
"last" => nil,
|
|
99
|
+
"empty?" => "Boolean",
|
|
100
|
+
"any?" => "Boolean",
|
|
101
|
+
"all?" => "Boolean",
|
|
102
|
+
"none?" => "Boolean",
|
|
103
|
+
"include?" => "Boolean",
|
|
104
|
+
"compact" => nil,
|
|
105
|
+
"flatten" => "Array",
|
|
106
|
+
"uniq" => nil,
|
|
107
|
+
"sort" => nil,
|
|
108
|
+
"map" => "Array",
|
|
109
|
+
"select" => "Array",
|
|
110
|
+
"reject" => "Array",
|
|
111
|
+
"reduce" => nil,
|
|
112
|
+
# Hash methods
|
|
113
|
+
"keys" => "Array",
|
|
114
|
+
"values" => "Array",
|
|
115
|
+
"merge" => "Hash",
|
|
116
|
+
"key?" => "Boolean",
|
|
117
|
+
"has_key?" => "Boolean",
|
|
118
|
+
"value?" => "Boolean",
|
|
119
|
+
"has_value?" => "Boolean",
|
|
120
|
+
# Object methods
|
|
121
|
+
"class" => "Class",
|
|
122
|
+
"object_id" => "Integer",
|
|
123
|
+
"hash" => "Integer",
|
|
124
|
+
"frozen?" => "Boolean",
|
|
125
|
+
"nil?" => "Boolean",
|
|
126
|
+
"is_a?" => "Boolean",
|
|
127
|
+
"kind_of?" => "Boolean",
|
|
128
|
+
"instance_of?" => "Boolean",
|
|
129
|
+
"respond_to?" => "Boolean",
|
|
130
|
+
"eql?" => "Boolean",
|
|
131
|
+
"equal?" => "Boolean",
|
|
132
|
+
"inspect" => "String",
|
|
133
|
+
"dup" => nil,
|
|
134
|
+
"clone" => nil
|
|
135
|
+
}.freeze
|
|
136
|
+
|
|
137
|
+
# Operator return types
|
|
138
|
+
OPERATOR_TYPES = {
|
|
139
|
+
# Arithmetic
|
|
140
|
+
"+" => :numeric_or_string,
|
|
141
|
+
"-" => :numeric,
|
|
142
|
+
"*" => :numeric_or_string,
|
|
143
|
+
"/" => :numeric,
|
|
144
|
+
"%" => :numeric,
|
|
145
|
+
"**" => :numeric,
|
|
146
|
+
# Comparison
|
|
147
|
+
"==" => "Boolean",
|
|
148
|
+
"!=" => "Boolean",
|
|
149
|
+
"<" => "Boolean",
|
|
150
|
+
">" => "Boolean",
|
|
151
|
+
"<=" => "Boolean",
|
|
152
|
+
">=" => "Boolean",
|
|
153
|
+
"<=>" => "Integer",
|
|
154
|
+
# Logical
|
|
155
|
+
"&&" => :propagate,
|
|
156
|
+
"||" => :propagate,
|
|
157
|
+
"!" => "Boolean",
|
|
158
|
+
# Bitwise
|
|
159
|
+
"&" => "Integer",
|
|
160
|
+
"|" => "Integer",
|
|
161
|
+
"^" => "Integer",
|
|
162
|
+
"~" => "Integer",
|
|
163
|
+
"<<" => "Integer",
|
|
164
|
+
">>" => "Integer"
|
|
165
|
+
}.freeze
|
|
166
|
+
|
|
167
|
+
def initialize
|
|
168
|
+
@inferred_types = {}
|
|
169
|
+
@warnings = []
|
|
170
|
+
@function_contexts = {}
|
|
171
|
+
@variable_types = {}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Infer type from a literal expression
|
|
175
|
+
def infer_literal(expression)
|
|
176
|
+
expr = expression.to_s.strip
|
|
177
|
+
|
|
178
|
+
LITERAL_PATTERNS.each do |pattern, type|
|
|
179
|
+
if expr.match?(pattern)
|
|
180
|
+
return InferredType.new(
|
|
181
|
+
type: type,
|
|
182
|
+
confidence: InferredType::HIGH,
|
|
183
|
+
source: :literal
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Infer type from method call
|
|
192
|
+
def infer_method_call(receiver_type, method_name)
|
|
193
|
+
return_type = METHOD_RETURN_TYPES[method_name.to_s]
|
|
194
|
+
|
|
195
|
+
if return_type
|
|
196
|
+
return InferredType.new(
|
|
197
|
+
type: return_type,
|
|
198
|
+
confidence: InferredType::HIGH,
|
|
199
|
+
source: :method_call
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Try to infer from receiver type and method
|
|
204
|
+
inferred = infer_from_receiver(receiver_type, method_name)
|
|
205
|
+
return inferred if inferred
|
|
206
|
+
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Infer return type from function body
|
|
211
|
+
def infer_return_type(function_body)
|
|
212
|
+
return_statements = extract_return_statements(function_body)
|
|
213
|
+
|
|
214
|
+
if return_statements.empty?
|
|
215
|
+
# Implicit nil return
|
|
216
|
+
return InferredType.new(
|
|
217
|
+
type: "nil",
|
|
218
|
+
confidence: InferredType::MEDIUM,
|
|
219
|
+
source: :implicit_return
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
types = return_statements.map do |stmt|
|
|
224
|
+
infer_expression_type(stmt[:value])
|
|
225
|
+
end.compact
|
|
226
|
+
|
|
227
|
+
if types.empty?
|
|
228
|
+
return nil
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if types.uniq.length == 1
|
|
232
|
+
# All returns have same type
|
|
233
|
+
return InferredType.new(
|
|
234
|
+
type: types.first.type,
|
|
235
|
+
confidence: InferredType::HIGH,
|
|
236
|
+
source: :return_analysis
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Multiple return types - create union
|
|
241
|
+
unique_types = types.map(&:type).uniq
|
|
242
|
+
union_type = unique_types.join(" | ")
|
|
243
|
+
|
|
244
|
+
InferredType.new(
|
|
245
|
+
type: union_type,
|
|
246
|
+
confidence: InferredType::MEDIUM,
|
|
247
|
+
source: :return_analysis
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Infer parameter types from usage patterns
|
|
252
|
+
def infer_parameter_types(function_body, parameters)
|
|
253
|
+
inferred_params = {}
|
|
254
|
+
|
|
255
|
+
parameters.each do |param|
|
|
256
|
+
param_name = param[:name]
|
|
257
|
+
usages = find_parameter_usages(function_body, param_name)
|
|
258
|
+
inferred_type = infer_from_usages(usages)
|
|
259
|
+
|
|
260
|
+
if inferred_type
|
|
261
|
+
inferred_params[param_name] = inferred_type
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
inferred_params
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Infer generic type parameters
|
|
269
|
+
def infer_generic_params(call_arguments, function_params)
|
|
270
|
+
generic_bindings = {}
|
|
271
|
+
|
|
272
|
+
function_params.each_with_index do |param, idx|
|
|
273
|
+
next unless param[:type]&.include?("<")
|
|
274
|
+
next unless call_arguments[idx]
|
|
275
|
+
|
|
276
|
+
arg_type = infer_expression_type(call_arguments[idx])
|
|
277
|
+
next unless arg_type
|
|
278
|
+
|
|
279
|
+
# Extract generic parameter from function param type
|
|
280
|
+
if param[:type].match?(/^(\w+)<(\w+)>$/)
|
|
281
|
+
match = param[:type].match(/^(\w+)<(\w+)>$/)
|
|
282
|
+
generic_name = match[2]
|
|
283
|
+
|
|
284
|
+
# If arg type is concrete, bind it
|
|
285
|
+
if arg_type.type.match?(/^(\w+)<(\w+)>$/)
|
|
286
|
+
arg_match = arg_type.type.match(/^(\w+)<(\w+)>$/)
|
|
287
|
+
generic_bindings[generic_name] = arg_match[2]
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
generic_bindings
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Infer type narrowing in conditionals
|
|
296
|
+
def infer_narrowed_type(variable, condition)
|
|
297
|
+
# Type guard patterns
|
|
298
|
+
if condition.match?(/#{variable}\.is_a\?\((\w+)\)/)
|
|
299
|
+
match = condition.match(/#{variable}\.is_a\?\((\w+)\)/)
|
|
300
|
+
return InferredType.new(
|
|
301
|
+
type: match[1],
|
|
302
|
+
confidence: InferredType::HIGH,
|
|
303
|
+
source: :type_guard
|
|
304
|
+
)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
if condition.match?(/#{variable}\.nil\?/)
|
|
308
|
+
return InferredType.new(
|
|
309
|
+
type: "nil",
|
|
310
|
+
confidence: InferredType::HIGH,
|
|
311
|
+
source: :nil_check
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
if condition.match?(/#{variable}\.respond_to\?\(:(\w+)\)/)
|
|
316
|
+
# Can't determine exact type, but know it has the method
|
|
317
|
+
return nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
nil
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Infer type of an expression
|
|
324
|
+
def infer_expression_type(expression)
|
|
325
|
+
expr = expression.to_s.strip
|
|
326
|
+
|
|
327
|
+
# Try literal inference first
|
|
328
|
+
literal_type = infer_literal(expr)
|
|
329
|
+
return literal_type if literal_type
|
|
330
|
+
|
|
331
|
+
# Method call inference
|
|
332
|
+
if expr.match?(/\.(\w+)(?:\(.*\))?$/)
|
|
333
|
+
match = expr.match(/(.+)\.(\w+)(?:\(.*\))?$/)
|
|
334
|
+
if match
|
|
335
|
+
receiver = match[1]
|
|
336
|
+
method = match[2]
|
|
337
|
+
receiver_type = infer_expression_type(receiver)
|
|
338
|
+
return infer_method_call(receiver_type&.type, method)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Variable reference - check recorded types
|
|
343
|
+
if @variable_types[expr]
|
|
344
|
+
return @variable_types[expr]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Operator inference
|
|
348
|
+
OPERATOR_TYPES.each do |op, result_type|
|
|
349
|
+
if expr.include?(" #{op} ")
|
|
350
|
+
return infer_operator_result(expr, op, result_type)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Array construction
|
|
355
|
+
if expr.start_with?("[") && expr.end_with?("]")
|
|
356
|
+
return infer_array_type(expr)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Hash construction
|
|
360
|
+
if expr.start_with?("{") && expr.end_with?("}")
|
|
361
|
+
return InferredType.new(
|
|
362
|
+
type: "Hash",
|
|
363
|
+
confidence: InferredType::HIGH,
|
|
364
|
+
source: :literal
|
|
365
|
+
)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
nil
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Record a variable's type for later inference
|
|
372
|
+
def record_variable_type(name, type)
|
|
373
|
+
@variable_types[name] = type
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Get recorded variable type
|
|
377
|
+
def get_variable_type(name)
|
|
378
|
+
@variable_types[name]
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Add a warning about ambiguous inference
|
|
382
|
+
def add_warning(message, location: nil)
|
|
383
|
+
@warnings << { message: message, location: location }
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Clear all state
|
|
387
|
+
def reset
|
|
388
|
+
@inferred_types.clear
|
|
389
|
+
@warnings.clear
|
|
390
|
+
@function_contexts.clear
|
|
391
|
+
@variable_types.clear
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
private
|
|
395
|
+
|
|
396
|
+
def extract_return_statements(body)
|
|
397
|
+
statements = []
|
|
398
|
+
lines = body.split("\n")
|
|
399
|
+
|
|
400
|
+
lines.each_with_index do |line, idx|
|
|
401
|
+
# Explicit return
|
|
402
|
+
if line.match?(/^\s*return\s+(.+)/)
|
|
403
|
+
match = line.match(/^\s*return\s+(.+)/)
|
|
404
|
+
statements << { type: :explicit, value: match[1].strip, line: idx + 1 }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Last expression (implicit return)
|
|
408
|
+
# This is simplified - real implementation would need full parsing
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
statements
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def find_parameter_usages(body, param_name)
|
|
415
|
+
usages = []
|
|
416
|
+
lines = body.split("\n")
|
|
417
|
+
|
|
418
|
+
lines.each_with_index do |line, idx|
|
|
419
|
+
# Method call on parameter
|
|
420
|
+
if line.match?(/#{param_name}\.(\w+)/)
|
|
421
|
+
matches = line.scan(/#{param_name}\.(\w+)/)
|
|
422
|
+
matches.each do |match|
|
|
423
|
+
usages << { type: :method_call, method: match[0], line: idx + 1 }
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Arithmetic operation
|
|
428
|
+
if line.match?(/#{param_name}\s*[+\-*\/%]/)
|
|
429
|
+
usages << { type: :arithmetic, line: idx + 1 }
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Comparison
|
|
433
|
+
if line.match?(/#{param_name}\s*[<>=!]=?/)
|
|
434
|
+
usages << { type: :comparison, line: idx + 1 }
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# String interpolation
|
|
438
|
+
if line.match?(/#\{#{param_name}\}/)
|
|
439
|
+
usages << { type: :string_interpolation, line: idx + 1 }
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
usages
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def infer_from_usages(usages)
|
|
447
|
+
return nil if usages.empty?
|
|
448
|
+
|
|
449
|
+
type_hints = []
|
|
450
|
+
|
|
451
|
+
usages.each do |usage|
|
|
452
|
+
case usage[:type]
|
|
453
|
+
when :arithmetic
|
|
454
|
+
type_hints << "Numeric"
|
|
455
|
+
when :method_call
|
|
456
|
+
method_type = infer_type_from_method(usage[:method])
|
|
457
|
+
type_hints << method_type if method_type
|
|
458
|
+
when :string_interpolation
|
|
459
|
+
# Could be any type (calls to_s)
|
|
460
|
+
when :comparison
|
|
461
|
+
# Most types support comparison
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
return nil if type_hints.empty?
|
|
466
|
+
|
|
467
|
+
# Find most specific type
|
|
468
|
+
most_common = type_hints.group_by(&:itself)
|
|
469
|
+
.max_by { |_, v| v.length }
|
|
470
|
+
&.first
|
|
471
|
+
|
|
472
|
+
if most_common
|
|
473
|
+
InferredType.new(
|
|
474
|
+
type: most_common,
|
|
475
|
+
confidence: InferredType::MEDIUM,
|
|
476
|
+
source: :usage_analysis
|
|
477
|
+
)
|
|
478
|
+
else
|
|
479
|
+
nil
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def infer_type_from_method(method_name)
|
|
484
|
+
# Methods that suggest String type
|
|
485
|
+
string_methods = %w[upcase downcase strip chomp split chars]
|
|
486
|
+
return "String" if string_methods.include?(method_name)
|
|
487
|
+
|
|
488
|
+
# Methods that suggest Array type
|
|
489
|
+
array_methods = %w[each map select reject push pop shift unshift first last]
|
|
490
|
+
return "Array" if array_methods.include?(method_name)
|
|
491
|
+
|
|
492
|
+
# Methods that suggest Hash type
|
|
493
|
+
hash_methods = %w[keys values merge fetch]
|
|
494
|
+
return "Hash" if hash_methods.include?(method_name)
|
|
495
|
+
|
|
496
|
+
# Methods that suggest Numeric type
|
|
497
|
+
numeric_methods = %w[abs ceil floor round to_i to_f times upto downto]
|
|
498
|
+
return "Numeric" if numeric_methods.include?(method_name)
|
|
499
|
+
|
|
500
|
+
nil
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def infer_from_receiver(receiver_type, method_name)
|
|
504
|
+
return nil unless receiver_type
|
|
505
|
+
|
|
506
|
+
case receiver_type
|
|
507
|
+
when "String"
|
|
508
|
+
case method_name.to_s
|
|
509
|
+
when "length", "size" then "Integer"
|
|
510
|
+
when "chars", "split", "lines" then "Array<String>"
|
|
511
|
+
when /^[a-z]/ then "String" # Most String methods return String
|
|
512
|
+
end
|
|
513
|
+
when "Array"
|
|
514
|
+
case method_name.to_s
|
|
515
|
+
when "length", "size", "count" then "Integer"
|
|
516
|
+
when "first", "last" then nil # Depends on element type
|
|
517
|
+
when "join" then "String"
|
|
518
|
+
end
|
|
519
|
+
when "Integer", "Float", "Numeric"
|
|
520
|
+
case method_name.to_s
|
|
521
|
+
when "to_s" then "String"
|
|
522
|
+
when "to_i" then "Integer"
|
|
523
|
+
when "to_f" then "Float"
|
|
524
|
+
when "abs", "ceil", "floor" then receiver_type
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def infer_operator_result(expr, operator, result_type)
|
|
530
|
+
case result_type
|
|
531
|
+
when "Boolean"
|
|
532
|
+
InferredType.new(type: "Boolean", confidence: InferredType::HIGH, source: :operator)
|
|
533
|
+
when "Integer"
|
|
534
|
+
InferredType.new(type: "Integer", confidence: InferredType::HIGH, source: :operator)
|
|
535
|
+
when :numeric
|
|
536
|
+
# Check operand types
|
|
537
|
+
InferredType.new(type: "Numeric", confidence: InferredType::MEDIUM, source: :operator)
|
|
538
|
+
when :numeric_or_string
|
|
539
|
+
# Could be numeric or string concatenation
|
|
540
|
+
InferredType.new(type: "Numeric | String", confidence: InferredType::LOW, source: :operator)
|
|
541
|
+
when :propagate
|
|
542
|
+
# Type propagates from operands
|
|
543
|
+
nil
|
|
544
|
+
else
|
|
545
|
+
nil
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def infer_array_type(expr)
|
|
550
|
+
# Remove brackets
|
|
551
|
+
content = expr[1..-2].strip
|
|
552
|
+
return InferredType.new(type: "Array", confidence: InferredType::HIGH, source: :literal) if content.empty?
|
|
553
|
+
|
|
554
|
+
# Try to infer element types
|
|
555
|
+
elements = split_array_elements(content)
|
|
556
|
+
element_types = elements.map { |e| infer_expression_type(e)&.type }.compact.uniq
|
|
557
|
+
|
|
558
|
+
if element_types.length == 1
|
|
559
|
+
InferredType.new(
|
|
560
|
+
type: "Array<#{element_types.first}>",
|
|
561
|
+
confidence: InferredType::HIGH,
|
|
562
|
+
source: :literal
|
|
563
|
+
)
|
|
564
|
+
elsif element_types.length > 1
|
|
565
|
+
InferredType.new(
|
|
566
|
+
type: "Array<#{element_types.join(' | ')}>",
|
|
567
|
+
confidence: InferredType::MEDIUM,
|
|
568
|
+
source: :literal
|
|
569
|
+
)
|
|
570
|
+
else
|
|
571
|
+
InferredType.new(type: "Array", confidence: InferredType::HIGH, source: :literal)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def split_array_elements(content)
|
|
576
|
+
# Simple split by comma (doesn't handle nested structures well)
|
|
577
|
+
content.split(",").map(&:strip)
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class UnionTypeParser
|
|
5
|
+
def initialize(type_string)
|
|
6
|
+
@type_string = type_string.strip
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def parse
|
|
10
|
+
# Check if it contains pipes (union indicator)
|
|
11
|
+
if @type_string.include?("|")
|
|
12
|
+
parse_union
|
|
13
|
+
else
|
|
14
|
+
parse_simple
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def parse_union
|
|
21
|
+
members = @type_string.split("|").map { |m| m.strip }.compact
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
type: :union,
|
|
25
|
+
members: members,
|
|
26
|
+
has_duplicates: members.length != members.uniq.length,
|
|
27
|
+
unique_members: members.uniq
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_simple
|
|
32
|
+
{
|
|
33
|
+
type: :simple,
|
|
34
|
+
value: @type_string
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|