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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ VERSION = "0.0.1"
5
+ end