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