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,1076 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module SMT
5
+ #==========================================================================
6
+ # Logical Formulas
7
+ #==========================================================================
8
+
9
+ # Base class for all formulas
10
+ class Formula
11
+ def &(other)
12
+ And.new(self, other)
13
+ end
14
+
15
+ def |(other)
16
+ Or.new(self, other)
17
+ end
18
+
19
+ def !
20
+ Not.new(self)
21
+ end
22
+
23
+ def implies(other)
24
+ Implies.new(self, other)
25
+ end
26
+
27
+ def iff(other)
28
+ Iff.new(self, other)
29
+ end
30
+
31
+ def free_variables
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def substitute(bindings)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def simplify
40
+ self
41
+ end
42
+
43
+ def to_cnf
44
+ raise NotImplementedError
45
+ end
46
+ end
47
+
48
+ # Boolean constant
49
+ class BoolConst < Formula
50
+ attr_reader :value
51
+
52
+ def initialize(value)
53
+ @value = value
54
+ end
55
+
56
+ def free_variables
57
+ Set.new
58
+ end
59
+
60
+ def substitute(_bindings)
61
+ self
62
+ end
63
+
64
+ def simplify
65
+ self
66
+ end
67
+
68
+ def to_cnf
69
+ @value ? [[]] : [[]]
70
+ end
71
+
72
+ def ==(other)
73
+ other.is_a?(BoolConst) && other.value == @value
74
+ end
75
+
76
+ def to_s
77
+ @value.to_s
78
+ end
79
+ end
80
+
81
+ TRUE = BoolConst.new(true)
82
+ FALSE = BoolConst.new(false)
83
+
84
+ # Propositional variable
85
+ class Variable < Formula
86
+ attr_reader :name
87
+
88
+ def initialize(name)
89
+ @name = name.to_s
90
+ end
91
+
92
+ def free_variables
93
+ Set.new([@name])
94
+ end
95
+
96
+ def substitute(bindings)
97
+ bindings[@name] || self
98
+ end
99
+
100
+ def to_cnf
101
+ [[@name]]
102
+ end
103
+
104
+ def ==(other)
105
+ other.is_a?(Variable) && other.name == @name
106
+ end
107
+
108
+ def hash
109
+ @name.hash
110
+ end
111
+
112
+ def eql?(other)
113
+ self == other
114
+ end
115
+
116
+ def to_s
117
+ @name
118
+ end
119
+ end
120
+
121
+ # Negation
122
+ class Not < Formula
123
+ attr_reader :operand
124
+
125
+ def initialize(operand)
126
+ @operand = operand
127
+ end
128
+
129
+ def free_variables
130
+ @operand.free_variables
131
+ end
132
+
133
+ def substitute(bindings)
134
+ Not.new(@operand.substitute(bindings))
135
+ end
136
+
137
+ def simplify
138
+ inner = @operand.simplify
139
+ case inner
140
+ when BoolConst
141
+ BoolConst.new(!inner.value)
142
+ when Not
143
+ inner.operand
144
+ else
145
+ Not.new(inner)
146
+ end
147
+ end
148
+
149
+ def to_cnf
150
+ case @operand
151
+ when Variable
152
+ [["!#{@operand.name}"]]
153
+ when Not
154
+ @operand.operand.to_cnf
155
+ when And
156
+ # De Morgan: !(A & B) = !A | !B
157
+ Or.new(Not.new(@operand.left), Not.new(@operand.right)).to_cnf
158
+ when Or
159
+ # De Morgan: !(A | B) = !A & !B
160
+ And.new(Not.new(@operand.left), Not.new(@operand.right)).to_cnf
161
+ else
162
+ [["!#{@operand}"]]
163
+ end
164
+ end
165
+
166
+ def ==(other)
167
+ other.is_a?(Not) && other.operand == @operand
168
+ end
169
+
170
+ def to_s
171
+ "!#{@operand}"
172
+ end
173
+ end
174
+
175
+ # Conjunction
176
+ class And < Formula
177
+ attr_reader :left, :right
178
+
179
+ def initialize(left, right)
180
+ @left = left
181
+ @right = right
182
+ end
183
+
184
+ def free_variables
185
+ @left.free_variables | @right.free_variables
186
+ end
187
+
188
+ def substitute(bindings)
189
+ And.new(@left.substitute(bindings), @right.substitute(bindings))
190
+ end
191
+
192
+ def simplify
193
+ l = @left.simplify
194
+ r = @right.simplify
195
+
196
+ return FALSE if l == FALSE || r == FALSE
197
+ return r if l == TRUE
198
+ return l if r == TRUE
199
+ return l if l == r
200
+
201
+ And.new(l, r)
202
+ end
203
+
204
+ def to_cnf
205
+ @left.to_cnf + @right.to_cnf
206
+ end
207
+
208
+ def ==(other)
209
+ other.is_a?(And) && other.left == @left && other.right == @right
210
+ end
211
+
212
+ def to_s
213
+ "(#{@left} && #{@right})"
214
+ end
215
+ end
216
+
217
+ # Disjunction
218
+ class Or < Formula
219
+ attr_reader :left, :right
220
+
221
+ def initialize(left, right)
222
+ @left = left
223
+ @right = right
224
+ end
225
+
226
+ def free_variables
227
+ @left.free_variables | @right.free_variables
228
+ end
229
+
230
+ def substitute(bindings)
231
+ Or.new(@left.substitute(bindings), @right.substitute(bindings))
232
+ end
233
+
234
+ def simplify
235
+ l = @left.simplify
236
+ r = @right.simplify
237
+
238
+ return TRUE if l == TRUE || r == TRUE
239
+ return r if l == FALSE
240
+ return l if r == FALSE
241
+ return l if l == r
242
+
243
+ Or.new(l, r)
244
+ end
245
+
246
+ def to_cnf
247
+ left_cnf = @left.to_cnf
248
+ right_cnf = @right.to_cnf
249
+
250
+ # Distribute: (A & B) | C = (A | C) & (B | C)
251
+ result = []
252
+ left_cnf.each do |left_clause|
253
+ right_cnf.each do |right_clause|
254
+ result << (left_clause + right_clause).uniq
255
+ end
256
+ end
257
+ result
258
+ end
259
+
260
+ def ==(other)
261
+ other.is_a?(Or) && other.left == @left && other.right == @right
262
+ end
263
+
264
+ def to_s
265
+ "(#{@left} || #{@right})"
266
+ end
267
+ end
268
+
269
+ # Implication
270
+ class Implies < Formula
271
+ attr_reader :antecedent, :consequent
272
+
273
+ def initialize(antecedent, consequent)
274
+ @antecedent = antecedent
275
+ @consequent = consequent
276
+ end
277
+
278
+ def free_variables
279
+ @antecedent.free_variables | @consequent.free_variables
280
+ end
281
+
282
+ def substitute(bindings)
283
+ Implies.new(@antecedent.substitute(bindings), @consequent.substitute(bindings))
284
+ end
285
+
286
+ def simplify
287
+ # A -> B = !A | B
288
+ Or.new(Not.new(@antecedent), @consequent).simplify
289
+ end
290
+
291
+ def to_cnf
292
+ # A -> B = !A | B
293
+ Or.new(Not.new(@antecedent), @consequent).to_cnf
294
+ end
295
+
296
+ def ==(other)
297
+ other.is_a?(Implies) && other.antecedent == @antecedent && other.consequent == @consequent
298
+ end
299
+
300
+ def to_s
301
+ "(#{@antecedent} -> #{@consequent})"
302
+ end
303
+ end
304
+
305
+ # Biconditional
306
+ class Iff < Formula
307
+ attr_reader :left, :right
308
+
309
+ def initialize(left, right)
310
+ @left = left
311
+ @right = right
312
+ end
313
+
314
+ def free_variables
315
+ @left.free_variables | @right.free_variables
316
+ end
317
+
318
+ def substitute(bindings)
319
+ Iff.new(@left.substitute(bindings), @right.substitute(bindings))
320
+ end
321
+
322
+ def simplify
323
+ # A <-> B = (A -> B) & (B -> A)
324
+ And.new(Implies.new(@left, @right), Implies.new(@right, @left)).simplify
325
+ end
326
+
327
+ def to_cnf
328
+ And.new(Implies.new(@left, @right), Implies.new(@right, @left)).to_cnf
329
+ end
330
+
331
+ def ==(other)
332
+ other.is_a?(Iff) && other.left == @left && other.right == @right
333
+ end
334
+
335
+ def to_s
336
+ "(#{@left} <-> #{@right})"
337
+ end
338
+ end
339
+
340
+ #==========================================================================
341
+ # Type Constraints
342
+ #==========================================================================
343
+
344
+ # Type variable
345
+ class TypeVar < Formula
346
+ attr_reader :name, :bounds
347
+
348
+ def initialize(name, bounds: nil)
349
+ @name = name.to_s
350
+ @bounds = bounds # { upper: Type, lower: Type }
351
+ end
352
+
353
+ def free_variables
354
+ Set.new([@name])
355
+ end
356
+
357
+ def substitute(bindings)
358
+ bindings[@name] || self
359
+ end
360
+
361
+ def to_cnf
362
+ [[@name]]
363
+ end
364
+
365
+ def ==(other)
366
+ other.is_a?(TypeVar) && other.name == @name
367
+ end
368
+
369
+ def hash
370
+ @name.hash
371
+ end
372
+
373
+ def eql?(other)
374
+ self == other
375
+ end
376
+
377
+ def to_s
378
+ @name
379
+ end
380
+ end
381
+
382
+ # Subtype constraint: A <: B (A is subtype of B)
383
+ class Subtype < Formula
384
+ attr_reader :subtype, :supertype
385
+
386
+ def initialize(subtype, supertype)
387
+ @subtype = subtype
388
+ @supertype = supertype
389
+ end
390
+
391
+ def free_variables
392
+ vars = Set.new
393
+ vars.add(@subtype.name) if @subtype.is_a?(TypeVar)
394
+ vars.add(@supertype.name) if @supertype.is_a?(TypeVar)
395
+ vars
396
+ end
397
+
398
+ def substitute(bindings)
399
+ sub = @subtype.is_a?(TypeVar) ? (bindings[@subtype.name] || @subtype) : @subtype
400
+ sup = @supertype.is_a?(TypeVar) ? (bindings[@supertype.name] || @supertype) : @supertype
401
+ Subtype.new(sub, sup)
402
+ end
403
+
404
+ def simplify
405
+ self
406
+ end
407
+
408
+ def to_cnf
409
+ [["#{@subtype}<:#{@supertype}"]]
410
+ end
411
+
412
+ def ==(other)
413
+ other.is_a?(Subtype) && other.subtype == @subtype && other.supertype == @supertype
414
+ end
415
+
416
+ def to_s
417
+ "#{@subtype} <: #{@supertype}"
418
+ end
419
+ end
420
+
421
+ # Type equality: A = B
422
+ class TypeEqual < Formula
423
+ attr_reader :left, :right
424
+
425
+ def initialize(left, right)
426
+ @left = left
427
+ @right = right
428
+ end
429
+
430
+ def free_variables
431
+ vars = Set.new
432
+ vars.add(@left.name) if @left.is_a?(TypeVar)
433
+ vars.add(@right.name) if @right.is_a?(TypeVar)
434
+ vars
435
+ end
436
+
437
+ def substitute(bindings)
438
+ l = @left.is_a?(TypeVar) ? (bindings[@left.name] || @left) : @left
439
+ r = @right.is_a?(TypeVar) ? (bindings[@right.name] || @right) : @right
440
+ TypeEqual.new(l, r)
441
+ end
442
+
443
+ def simplify
444
+ return TRUE if @left == @right
445
+ self
446
+ end
447
+
448
+ def to_cnf
449
+ [["#{@left}=#{@right}"]]
450
+ end
451
+
452
+ def ==(other)
453
+ other.is_a?(TypeEqual) && other.left == @left && other.right == @right
454
+ end
455
+
456
+ def to_s
457
+ "#{@left} = #{@right}"
458
+ end
459
+ end
460
+
461
+ # Instance constraint: T has property P
462
+ class HasProperty < Formula
463
+ attr_reader :type_var, :property, :property_type
464
+
465
+ def initialize(type_var, property, property_type)
466
+ @type_var = type_var
467
+ @property = property
468
+ @property_type = property_type
469
+ end
470
+
471
+ def free_variables
472
+ vars = Set.new
473
+ vars.add(@type_var.name) if @type_var.is_a?(TypeVar)
474
+ vars.add(@property_type.name) if @property_type.is_a?(TypeVar)
475
+ vars
476
+ end
477
+
478
+ def substitute(bindings)
479
+ tv = @type_var.is_a?(TypeVar) ? (bindings[@type_var.name] || @type_var) : @type_var
480
+ pt = @property_type.is_a?(TypeVar) ? (bindings[@property_type.name] || @property_type) : @property_type
481
+ HasProperty.new(tv, @property, pt)
482
+ end
483
+
484
+ def to_cnf
485
+ [["#{@type_var}.#{@property}:#{@property_type}"]]
486
+ end
487
+
488
+ def to_s
489
+ "#{@type_var} has #{@property}: #{@property_type}"
490
+ end
491
+ end
492
+
493
+ #==========================================================================
494
+ # Concrete Types for Solver
495
+ #==========================================================================
496
+
497
+ class ConcreteType
498
+ attr_reader :name
499
+
500
+ def initialize(name)
501
+ @name = name
502
+ end
503
+
504
+ def ==(other)
505
+ other.is_a?(ConcreteType) && other.name == @name
506
+ end
507
+
508
+ def hash
509
+ @name.hash
510
+ end
511
+
512
+ def eql?(other)
513
+ self == other
514
+ end
515
+
516
+ def to_s
517
+ @name
518
+ end
519
+ end
520
+
521
+ #==========================================================================
522
+ # SAT Solver (DPLL Algorithm)
523
+ #==========================================================================
524
+
525
+ class SATSolver
526
+ attr_reader :assignments, :conflicts
527
+
528
+ def initialize
529
+ @assignments = {}
530
+ @conflicts = []
531
+ end
532
+
533
+ # Solve CNF formula
534
+ def solve(cnf)
535
+ @assignments = {}
536
+ @conflicts = []
537
+
538
+ dpll(cnf.dup, {})
539
+ end
540
+
541
+ private
542
+
543
+ def dpll(clauses, assignment)
544
+ # Unit propagation
545
+ loop do
546
+ unit = find_unit_clause(clauses)
547
+ break unless unit
548
+
549
+ var, value = parse_literal(unit)
550
+ assignment[var] = value
551
+ clauses = propagate(clauses, var, value)
552
+
553
+ return nil if clauses.any?(&:empty?) # Conflict
554
+ end
555
+
556
+ # All clauses satisfied
557
+ return assignment if clauses.empty?
558
+
559
+ # Check for empty clause (conflict)
560
+ return nil if clauses.any?(&:empty?)
561
+
562
+ # Choose variable
563
+ var = choose_variable(clauses)
564
+ return assignment unless var
565
+
566
+ # Try true
567
+ result = dpll(propagate(clauses.dup, var, true), assignment.merge(var => true))
568
+ return result if result
569
+
570
+ # Try false
571
+ dpll(propagate(clauses.dup, var, false), assignment.merge(var => false))
572
+ end
573
+
574
+ def find_unit_clause(clauses)
575
+ clauses.each do |clause|
576
+ return clause.first if clause.length == 1
577
+ end
578
+ nil
579
+ end
580
+
581
+ def parse_literal(literal)
582
+ if literal.start_with?("!")
583
+ [literal[1..], false]
584
+ else
585
+ [literal, true]
586
+ end
587
+ end
588
+
589
+ def propagate(clauses, var, value)
590
+ result = []
591
+
592
+ clauses.each do |clause|
593
+ # If clause contains literal with matching polarity, clause is satisfied
594
+ satisfied = clause.any? do |lit|
595
+ lit_var, lit_value = parse_literal(lit)
596
+ lit_var == var && lit_value == value
597
+ end
598
+
599
+ next if satisfied
600
+
601
+ # Remove literals with opposite polarity
602
+ new_clause = clause.reject do |lit|
603
+ lit_var, lit_value = parse_literal(lit)
604
+ lit_var == var && lit_value != value
605
+ end
606
+
607
+ result << new_clause
608
+ end
609
+
610
+ result
611
+ end
612
+
613
+ def choose_variable(clauses)
614
+ # VSIDS-like heuristic: choose most frequent variable
615
+ counts = Hash.new(0)
616
+
617
+ clauses.each do |clause|
618
+ clause.each do |lit|
619
+ var, = parse_literal(lit)
620
+ counts[var] += 1
621
+ end
622
+ end
623
+
624
+ counts.max_by { |_, v| v }&.first
625
+ end
626
+ end
627
+
628
+ #==========================================================================
629
+ # Type Constraint Solver
630
+ #==========================================================================
631
+
632
+ class ConstraintSolver
633
+ attr_reader :constraints, :solution, :errors
634
+
635
+ # Type hierarchy (built-in)
636
+ TYPE_HIERARCHY = {
637
+ "Integer" => ["Numeric", "Object"],
638
+ "Float" => ["Numeric", "Object"],
639
+ "Numeric" => ["Object"],
640
+ "String" => ["Object"],
641
+ "Array" => ["Enumerable", "Object"],
642
+ "Hash" => ["Enumerable", "Object"],
643
+ "Enumerable" => ["Object"],
644
+ "Boolean" => ["Object"],
645
+ "Symbol" => ["Object"],
646
+ "nil" => ["Object"],
647
+ "Object" => []
648
+ }.freeze
649
+
650
+ def initialize
651
+ @constraints = []
652
+ @solution = {}
653
+ @errors = []
654
+ @type_vars = {}
655
+ end
656
+
657
+ # Create a new type variable
658
+ def fresh_var(prefix = "T")
659
+ name = "#{prefix}#{@type_vars.length}"
660
+ var = TypeVar.new(name)
661
+ @type_vars[name] = var
662
+ var
663
+ end
664
+
665
+ # Add constraint
666
+ def add_constraint(constraint)
667
+ @constraints << constraint
668
+ end
669
+
670
+ # Add subtype constraint
671
+ def add_subtype(sub, sup)
672
+ add_constraint(Subtype.new(sub, sup))
673
+ end
674
+
675
+ # Add equality constraint
676
+ def add_equal(left, right)
677
+ add_constraint(TypeEqual.new(left, right))
678
+ end
679
+
680
+ # Solve all constraints
681
+ def solve
682
+ @solution = {}
683
+ @errors = []
684
+
685
+ # Phase 1: Unification
686
+ unified = unify_constraints
687
+
688
+ # Phase 2: Subtype checking
689
+ check_subtypes(unified) if @errors.empty?
690
+
691
+ # Phase 3: Instantiation
692
+ instantiate_remaining if @errors.empty?
693
+
694
+ {
695
+ success: @errors.empty?,
696
+ solution: @solution,
697
+ errors: @errors
698
+ }
699
+ end
700
+
701
+ # Check if type A is subtype of type B
702
+ def subtype?(sub, sup)
703
+ return true if sub == sup
704
+ return true if sup.to_s == "Object"
705
+ return true if sub.to_s == "nil" # nil is subtype of everything (nullable)
706
+
707
+ sub_name = sub.is_a?(ConcreteType) ? sub.name : sub.to_s
708
+ sup_name = sup.is_a?(ConcreteType) ? sup.name : sup.to_s
709
+
710
+ # Check type hierarchy
711
+ ancestors = TYPE_HIERARCHY[sub_name] || []
712
+ return true if ancestors.include?(sup_name)
713
+
714
+ # Check transitive closure
715
+ ancestors.any? { |a| subtype?(ConcreteType.new(a), sup) }
716
+ end
717
+
718
+ # Infer type from constraints
719
+ def infer(var)
720
+ @solution[var.name] || @solution[var.to_s]
721
+ end
722
+
723
+ private
724
+
725
+ def unify_constraints
726
+ worklist = @constraints.dup
727
+
728
+ while (constraint = worklist.shift)
729
+ case constraint
730
+ when TypeEqual
731
+ result = unify(constraint.left, constraint.right)
732
+ if result
733
+ # Apply substitution to remaining constraints
734
+ worklist = worklist.map { |c| c.substitute(result) }
735
+ @solution.merge!(result)
736
+ else
737
+ @errors << "Cannot unify #{constraint.left} with #{constraint.right}"
738
+ end
739
+ end
740
+ end
741
+
742
+ @constraints.reject { |c| c.is_a?(TypeEqual) }
743
+ end
744
+
745
+ def unify(left, right)
746
+ return {} if left == right
747
+
748
+ # If left is type variable, bind it
749
+ if left.is_a?(TypeVar)
750
+ return nil if occurs_check(left, right)
751
+ return { left.name => right }
752
+ end
753
+
754
+ # If right is type variable, bind it
755
+ if right.is_a?(TypeVar)
756
+ return nil if occurs_check(right, left)
757
+ return { right.name => left }
758
+ end
759
+
760
+ # Both are concrete types
761
+ if left.is_a?(ConcreteType) && right.is_a?(ConcreteType)
762
+ return {} if left.name == right.name
763
+ return nil
764
+ end
765
+
766
+ nil
767
+ end
768
+
769
+ def occurs_check(var, type)
770
+ return false unless type.respond_to?(:free_variables)
771
+ type.free_variables.include?(var.name)
772
+ end
773
+
774
+ def check_subtypes(remaining_constraints)
775
+ remaining_constraints.each do |constraint|
776
+ case constraint
777
+ when Subtype
778
+ sub = resolve_type(constraint.subtype)
779
+ sup = resolve_type(constraint.supertype)
780
+
781
+ # Skip if either is still a TypeVar (unresolved)
782
+ next if sub.is_a?(TypeVar) || sup.is_a?(TypeVar)
783
+
784
+ unless subtype?(sub, sup)
785
+ @errors << "Type #{sub} is not a subtype of #{sup}"
786
+ end
787
+ end
788
+ end
789
+ end
790
+
791
+ def resolve_type(type)
792
+ case type
793
+ when TypeVar
794
+ @solution[type.name] || type
795
+ when ConcreteType
796
+ type
797
+ else
798
+ ConcreteType.new(type.to_s)
799
+ end
800
+ end
801
+
802
+ def instantiate_remaining
803
+ @type_vars.each do |name, var|
804
+ next if @solution[name]
805
+
806
+ # Default to Object if no constraints
807
+ @solution[name] = ConcreteType.new("Object")
808
+ end
809
+ end
810
+ end
811
+
812
+ #==========================================================================
813
+ # Type Inference Engine using SMT
814
+ #==========================================================================
815
+
816
+ class TypeInferenceEngine
817
+ attr_reader :solver, :type_env
818
+
819
+ def initialize
820
+ @solver = ConstraintSolver.new
821
+ @type_env = {} # Variable name -> Type
822
+ end
823
+
824
+ # Infer types for a method
825
+ def infer_method(method_ir)
826
+ param_types = {}
827
+ return_type = nil
828
+
829
+ # Create type variables for parameters without annotations
830
+ method_ir.params.each do |param|
831
+ if param.type_annotation
832
+ param_types[param.name] = type_from_ir(param.type_annotation)
833
+ else
834
+ param_types[param.name] = @solver.fresh_var("P_#{param.name}")
835
+ end
836
+ @type_env[param.name] = param_types[param.name]
837
+ end
838
+
839
+ # Create type variable for return type if not annotated
840
+ return_type = if method_ir.return_type
841
+ type_from_ir(method_ir.return_type)
842
+ else
843
+ @solver.fresh_var("R_#{method_ir.name}")
844
+ end
845
+
846
+ # Analyze body to generate constraints
847
+ if method_ir.body
848
+ infer_body(method_ir.body, return_type)
849
+ end
850
+
851
+ # Solve constraints
852
+ result = @solver.solve
853
+
854
+ if result[:success]
855
+ # Build inferred signature
856
+ inferred_params = param_types.transform_values do |type|
857
+ resolve_type(type, result[:solution])
858
+ end
859
+
860
+ inferred_return = resolve_type(return_type, result[:solution])
861
+
862
+ {
863
+ success: true,
864
+ params: inferred_params,
865
+ return_type: inferred_return
866
+ }
867
+ else
868
+ {
869
+ success: false,
870
+ errors: result[:errors]
871
+ }
872
+ end
873
+ end
874
+
875
+ # Generate constraints from method body
876
+ def infer_body(body_ir, expected_return)
877
+ case body_ir
878
+ when IR::Block
879
+ body_ir.statements.each do |stmt|
880
+ infer_statement(stmt, expected_return)
881
+ end
882
+ when IR::Return
883
+ if body_ir.value
884
+ value_type = infer_expression(body_ir.value)
885
+ @solver.add_subtype(value_type, expected_return)
886
+ end
887
+ end
888
+ end
889
+
890
+ # Infer statement
891
+ def infer_statement(stmt, expected_return)
892
+ case stmt
893
+ when IR::Assignment
894
+ value_type = infer_expression(stmt.value)
895
+ @type_env[stmt.target] = value_type
896
+
897
+ if stmt.type_annotation
898
+ annotated = type_from_ir(stmt.type_annotation)
899
+ @solver.add_subtype(value_type, annotated)
900
+ end
901
+ when IR::Return
902
+ if stmt.value
903
+ value_type = infer_expression(stmt.value)
904
+ @solver.add_subtype(value_type, expected_return)
905
+ end
906
+ when IR::Conditional
907
+ infer_expression(stmt.condition)
908
+ infer_body(stmt.then_branch, expected_return) if stmt.then_branch
909
+ infer_body(stmt.else_branch, expected_return) if stmt.else_branch
910
+ end
911
+ end
912
+
913
+ # Infer expression type
914
+ def infer_expression(expr)
915
+ case expr
916
+ when IR::Literal
917
+ ConcreteType.new(literal_type(expr.literal_type))
918
+ when IR::VariableRef
919
+ @type_env[expr.name] || @solver.fresh_var("V_#{expr.name}")
920
+ when IR::MethodCall
921
+ infer_method_call(expr)
922
+ when IR::BinaryOp
923
+ infer_binary_op(expr)
924
+ when IR::ArrayLiteral
925
+ infer_array_literal(expr)
926
+ else
927
+ @solver.fresh_var("E")
928
+ end
929
+ end
930
+
931
+ private
932
+
933
+ def type_from_ir(ir_type)
934
+ case ir_type
935
+ when IR::SimpleType
936
+ ConcreteType.new(ir_type.name)
937
+ when IR::GenericType
938
+ # Simplified: just use base type for now
939
+ ConcreteType.new(ir_type.base)
940
+ when IR::UnionType
941
+ # Create fresh var with union constraint
942
+ @solver.fresh_var("U")
943
+ when IR::NullableType
944
+ # T | nil
945
+ @solver.fresh_var("N")
946
+ else
947
+ @solver.fresh_var("T")
948
+ end
949
+ end
950
+
951
+ def resolve_type(type, solution)
952
+ case type
953
+ when TypeVar
954
+ resolved = solution[type.name]
955
+ resolved ? resolve_type(resolved, solution) : "Object"
956
+ when ConcreteType
957
+ type.name
958
+ else
959
+ type.to_s
960
+ end
961
+ end
962
+
963
+ def literal_type(lit_type)
964
+ case lit_type
965
+ when :string then "String"
966
+ when :integer then "Integer"
967
+ when :float then "Float"
968
+ when :boolean then "Boolean"
969
+ when :symbol then "Symbol"
970
+ when :nil then "nil"
971
+ when :array then "Array"
972
+ when :hash then "Hash"
973
+ else "Object"
974
+ end
975
+ end
976
+
977
+ def infer_method_call(call)
978
+ # Get receiver type
979
+ receiver_type = if call.receiver
980
+ infer_expression(call.receiver)
981
+ else
982
+ @type_env["self"] || ConcreteType.new("Object")
983
+ end
984
+
985
+ # Look up method return type
986
+ return_type = lookup_method_type(receiver_type, call.method_name)
987
+ return_type || @solver.fresh_var("M_#{call.method_name}")
988
+ end
989
+
990
+ def lookup_method_type(receiver, method)
991
+ # Built-in method types
992
+ method_types = {
993
+ "to_s" => ConcreteType.new("String"),
994
+ "to_i" => ConcreteType.new("Integer"),
995
+ "to_f" => ConcreteType.new("Float"),
996
+ "length" => ConcreteType.new("Integer"),
997
+ "size" => ConcreteType.new("Integer"),
998
+ "empty?" => ConcreteType.new("Boolean"),
999
+ "nil?" => ConcreteType.new("Boolean")
1000
+ }
1001
+
1002
+ method_types[method.to_s]
1003
+ end
1004
+
1005
+ def infer_binary_op(expr)
1006
+ left_type = infer_expression(expr.left)
1007
+ right_type = infer_expression(expr.right)
1008
+
1009
+ case expr.operator
1010
+ when "+", "-", "*", "/", "%"
1011
+ # Numeric operations
1012
+ @solver.add_subtype(left_type, ConcreteType.new("Numeric"))
1013
+ @solver.add_subtype(right_type, ConcreteType.new("Numeric"))
1014
+ ConcreteType.new("Numeric")
1015
+ when "==", "!=", "<", ">", "<=", ">="
1016
+ ConcreteType.new("Boolean")
1017
+ when "&&", "||"
1018
+ ConcreteType.new("Boolean")
1019
+ else
1020
+ @solver.fresh_var("Op")
1021
+ end
1022
+ end
1023
+
1024
+ def infer_array_literal(expr)
1025
+ if expr.elements.empty?
1026
+ ConcreteType.new("Array")
1027
+ else
1028
+ element_type = @solver.fresh_var("E")
1029
+ expr.elements.each do |elem|
1030
+ elem_type = infer_expression(elem)
1031
+ @solver.add_subtype(elem_type, element_type)
1032
+ end
1033
+ ConcreteType.new("Array")
1034
+ end
1035
+ end
1036
+ end
1037
+
1038
+ #==========================================================================
1039
+ # DSL for building constraints
1040
+ #==========================================================================
1041
+
1042
+ module DSL
1043
+ def var(name)
1044
+ Variable.new(name)
1045
+ end
1046
+
1047
+ def type_var(name, bounds: nil)
1048
+ TypeVar.new(name, bounds: bounds)
1049
+ end
1050
+
1051
+ def concrete(name)
1052
+ ConcreteType.new(name)
1053
+ end
1054
+
1055
+ def subtype(sub, sup)
1056
+ Subtype.new(sub, sup)
1057
+ end
1058
+
1059
+ def type_equal(left, right)
1060
+ TypeEqual.new(left, right)
1061
+ end
1062
+
1063
+ def has_property(type, prop, prop_type)
1064
+ HasProperty.new(type, prop, prop_type)
1065
+ end
1066
+
1067
+ def all(*constraints)
1068
+ constraints.reduce { |acc, c| acc & c }
1069
+ end
1070
+
1071
+ def any(*constraints)
1072
+ constraints.reduce { |acc, c| acc | c }
1073
+ end
1074
+ end
1075
+ end
1076
+ end