Class: TypeChecker

Inherits:
SexpProcessor
  • Object
show all
Defined in:
lib/type_checker.rb

Overview

TypeChecker inferences types for sexps using type unification.

TypeChecker expects sexps rewritten with Rewriter, and outputs TypedSexps.

Nodes marked as ‘unsupported’ do not do correct type-checking of all the pieces of the node. They generate possibly incorrect output, that is all.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTypeChecker

:nodoc:



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/type_checker.rb', line 108

def initialize # :nodoc:
  super
  @env = Environment.new
  @genv = Environment.new
  @functions = FunctionTable.new
  self.auto_shift_type = true
  self.strict = true
  self.expected = TypedSexp

  self.unsupported = [:alias, :alloca, :argscat, :argspush, :attrset, :back_ref, :bmethod, :break, :case, :cdecl, :cfunc, :cref, :cvdecl, :dasgn, :defs, :dmethod, :dot2, :dot3, :dregx, :dregx_once, :dsym, :dxstr, :evstr, :fbody, :fcall, :flip2, :flip3, :for, :ifunc, :last, :masgn, :match, :match2, :match3, :memo, :method, :module, :newline, :next, :nth_ref, :op_asgn1, :op_asgn2, :op_asgn_and, :opt_n, :postexe, :redo, :retry, :sclass, :svalue, :to_ary, :undef, :until, :valias, :vcall, :when, :xstr, :zarray, :zsuper]

  bootstrap
end

Instance Attribute Details

#envObject (readonly)

Environment containing local variables



64
65
66
# File 'lib/type_checker.rb', line 64

def env
  @env
end

#functionsObject (readonly)

Function table



74
75
76
# File 'lib/type_checker.rb', line 74

def functions
  @functions
end

#genvObject (readonly)

The global environment contains global variables and constants.



69
70
71
# File 'lib/type_checker.rb', line 69

def genv
  @genv
end

Class Method Details

.process(klass, method = nil) ⇒ Object

Yet another utility method - this time the official one, although we don’t like the implementation at this stage.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/type_checker.rb', line 92

def self.process(klass, method=nil)
  processor = self.new
  rewriter = Rewriter.new
  sexp = ParseTree.new.parse_tree(klass, method)
  sexp = [sexp] unless Array === sexp.first

  result = []
  sexp.each do |exp|
    # TODO: we need a composite processor to chain these cleanly
    sexp = rewriter.process(exp)
    result << processor.process(sexp)
  end

  result
end

.translate(klass, method = nil) ⇒ Object

Utility method that translates a class and optional method name to a type checked sexp. Mostly used for testing.



84
85
86
# File 'lib/type_checker.rb', line 84

def self.translate(klass, method = nil)
  self.new.translate(klass, method)
end

Instance Method Details

#bootstrapObject

Runs the bootstrap stage, which runs over $bootstrap and converts each entry into a full fledged method signature registered in the type checker. This is where the basic knowledge for lower level types (in C) comes from.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/type_checker.rb', line 128

def bootstrap
  @genv.add :$stdin, Type.file
  @genv.add :$stdout, Type.file
  @genv.add :$stderr, Type.file

  ObjectSpace.each_object(Class) do |klass|
    next if klass.name =~ /::/ # only 2 classes is core, but many others
    @genv.add klass.name.intern, Type.fucked
  end

  $bootstrap.each do |name,signatures|
    # FIX: Using Type.send because it must go through method_missing, not new
    signatures.each do |signature|
      lhs_type = Type.send(signature[0])
      return_type = Type.send(signature[-1])
      arg_types = signature[1..-2].map { |t| Type.send(t) }
      @functions.add_function(name, Type.function(lhs_type, arg_types, return_type))
    end
  end
end

#process_and(exp) ⇒ Object

Logical and unifies its two arguments, then returns a bool sexp.



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/type_checker.rb', line 152

def process_and(exp)
  rhs = process exp.shift
  lhs = process exp.shift

  rhs_type = rhs.sexp_type
  lhs_type = lhs.sexp_type

  rhs_type.unify lhs_type
  rhs_type.unify Type.bool

  return t(:and, rhs, lhs, Type.bool)
end

#process_arglist(exp) ⇒ Object

Arg list stuff



187
188
189
190
191
192
# File 'lib/type_checker.rb', line 187

def process_arglist(exp)
  args = process_array exp
  args[0] = :arglist

  args
end

#process_args(exp) ⇒ Object

Args list adds each variable to the local variable table with unknown types, then returns an untyped args list of name/type pairs.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/type_checker.rb', line 169

def process_args(exp)
  formals = t(:args)
  types = []

  until exp.empty? do
    arg = exp.shift
    type = Type.unknown
    @env.add arg, type
    formals << t(arg, type)
    types << type
  end

  return formals
end

#process_array(exp) ⇒ Object

Array processes each item in the array, then returns an untyped sexp.



197
198
199
200
201
202
203
204
205
206
# File 'lib/type_checker.rb', line 197

def process_array(exp)
  types = []
  vars = t(:array)
  until exp.empty? do
    var = process exp.shift
    vars << var
    types << var.sexp_type
  end
  vars
end

#process_attrasgn(exp) ⇒ Object

Attrasgn processes its rhs and lhs, then returns an untyped sexp. – TODO rewrite this in Rewriter echo “self.blah=7” | parse_tree_show -f

> [:attrasgn, [:self], :blah=, [:array, [:lit, 7]]]



215
216
217
218
219
220
221
222
# File 'lib/type_checker.rb', line 215

def process_attrasgn(exp)
  rhs = process exp.shift
  name = exp.shift
  lhs = process exp.shift

  # TODO: since this is an ivar, we need to figger out their var system. :/
  return t(:attrasgn, rhs, name, lhs)
end

#process_begin(exp) ⇒ Object

Begin processes the body, then returns an untyped sexp.



227
228
229
230
231
# File 'lib/type_checker.rb', line 227

def process_begin(exp)
  body = process exp.shift
  # shouldn't be anything to unify
  return t(:begin, body)
end

#process_block(exp) ⇒ Object

Block processes each sexp in the block, then returns an unknown-typed sexp.



237
238
239
240
241
242
243
# File 'lib/type_checker.rb', line 237

def process_block(exp)
  nodes = t(:block, Type.unknown)
  until exp.empty? do
    nodes << process(exp.shift)
  end
  nodes
end

#process_block_arg(exp) ⇒ Object

Block arg is currently unsupported. Returns an unmentionably-typed sexp. – TODO do something more sensible



251
252
253
# File 'lib/type_checker.rb', line 251

def process_block_arg(exp)
  t(:block_arg, exp.shift, Type.fucked)
end

#process_block_pass(exp) ⇒ Object

Block pass is currently unsupported. Returns a typed sexp. – TODO: we might want to look at rewriting this into a call variation. echo “class E; def e(&b); blah(&b); end; end” | parse_tree_show

> [:defn, :e, [:scope, [:block, [:args], [:block_arg, :b], [:block_pass, [:lvar, :b], [:fcall, :blah]]]]]



262
263
264
265
266
# File 'lib/type_checker.rb', line 262

def process_block_pass(exp)
  block = process exp.shift
  call  = process exp.shift
  t(:block_pass, block, call)
end

#process_call(exp) ⇒ Object

Call unifies the actual function paramaters against the formal function paramaters, if a function type signature already exists in the function table. If no type signature for the function name exists, the function is added to the function list.

Returns a sexp returned to the type of the function return value, or unknown if it has not yet been determined.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/type_checker.rb', line 277

def process_call(exp)
  lhs = process exp.shift     # can be nil
  name = exp.shift
  args = process exp.shift

  arg_types = if args.nil? then
                []
              else
                if args.first == :arglist then
                  args.sexp_types
                elsif args.first == :splat then
                  [args.sexp_type]
                else
                  raise "That's not a Ruby Sexp you handed me, I'm freaking out on: #{args.inspect}"
                end
              end

  if name == :=== then
    rhs = args[1]
    raise "lhs of === may not be nil" if lhs.nil?
    raise "rhs of === may not be nil" if rhs.nil?
    raise "Help! I can't figure out what kind of #=== comparison to use" if
      lhs.sexp_type.unknown? and rhs.sexp_type.unknown?
    equal_type = lhs.sexp_type.unknown? ? rhs.sexp_type : lhs.sexp_type
    name = "case_equal_#{equal_type.list_type}".intern
  end

  return_type = Type.unknown
  lhs_type = lhs.nil? ? Type.unknown : lhs.sexp_type # TODO: maybe void instead of unknown

  function_type = Type.function(lhs_type, arg_types, return_type)
  @functions.unify(name, function_type) do
    @functions.add_function(name, function_type)
    $stderr.puts "\nWARNING: function #{name} called w/o being defined. Registering #{function_type.inspect}" if $DEBUG
  end
  return_type = function_type.list_type.return_type

  return t(:call, lhs, name, args, return_type)
end

#process_class(exp) ⇒ Object

Class adds the class name to the global environment, processes all of the methods in the class. Returns a zclass-typed sexp.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/type_checker.rb', line 321

def process_class(exp)
  name = exp.shift
  superclass = exp.shift

  @genv.add name, Type.zclass

  result = t(:class, Type.zclass)
  result << name
  result << superclass
    
  @env.scope do
    # HACK: not sure this is the right place, maybe genv instead?
    klass = eval(name.to_s) # HACK do proper lookup - ugh
    klass.constants.each do |c|
      const_type = case klass.const_get(c)
                   when Fixnum then
                     Type.long
                   when String then
                     Type.str
                   else
                     Type.unknown
                   end
      @env.add c.intern, const_type
    end

    until exp.empty? do
      result << process(exp.shift)
    end
  end

  return result
end

#process_colon2(exp) ⇒ Object

Colon 2 returns a zclass-typed sexp



357
358
359
360
# File 'lib/type_checker.rb', line 357

def process_colon2(exp) # (Module::Class/Module)
  name = exp.shift
  return t(:colon2, name, Type.zclass)
end

#process_colon3(exp) ⇒ Object

Colon 3 returns a zclass-typed sexp



365
366
367
368
# File 'lib/type_checker.rb', line 365

def process_colon3(exp) # (::OUTER_CONST)
  name = exp.shift
  return t(:colon2, name, Type.const)
end

#process_const(exp) ⇒ Object

Const looks up the type of the const in the global environment, then returns a sexp of that type.

Const is partially unsupported. – TODO :const isn’t supported anywhere.



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/type_checker.rb', line 378

def process_const(exp)
  c = exp.shift
  if c.to_s =~ /^[A-Z]/ then
    # TODO: validate that it really is a const?
    type = @genv.lookup(c) rescue @env.lookup(c)
    return t(:const, c, type)
  else
    raise "I don't know what to do with const #{c.inspect}. It doesn't look like a class."
  end
  raise "need to finish process_const in #{self.class}"
end

#process_cvar(exp) ⇒ Object

Class variables are currently unsupported. Returns an unknown-typed sexp. – TODO support class variables



395
396
397
398
399
# File 'lib/type_checker.rb', line 395

def process_cvar(exp)
  # TODO: we should treat these as globals and have them in the top scope
  name = exp.shift
  return t(:cvar, name, Type.unknown)
end

#process_cvasgn(exp) ⇒ Object

Class variable assignment – TODO support class variables



406
407
408
409
410
# File 'lib/type_checker.rb', line 406

def process_cvasgn(exp)
  name = exp.shift
  val = process exp.shift
  return t(:cvasgn, name, val, Type.unknown)
end

#process_dasgn_curr(exp) ⇒ Object

Dynamic variable assignment adds the unknown type to the local environment then returns an unknown-typed sexp.



416
417
418
419
420
421
422
# File 'lib/type_checker.rb', line 416

def process_dasgn_curr(exp)
  name = exp.shift
  type = Type.unknown
  @env.add name, type # HACK lookup before adding like lasgn

  return t(:dasgn_curr, name, type)
end

#process_defined(exp) ⇒ Object

Defined? processes the body, then returns a bool-typed sexp.



427
428
429
430
# File 'lib/type_checker.rb', line 427

def process_defined(exp)
  thing = process exp.shift
  return t(:defined, thing, Type.bool)
end

#process_defn(exp) ⇒ Object

Defn adds the formal argument types to the local environment and attempts to unify itself against the function table. If no function exists in the function table, defn adds itself.

Defn returns a function-typed sexp.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/type_checker.rb', line 439

def process_defn(exp)
  name = exp.shift
  unprocessed_args = exp.shift
  args = body = function_type = nil

  @env.scope do
    args = process unprocessed_args

    begin
      body = process exp.shift
    rescue TypeError => err
      puts "Error in method #{name}, trying to unify, whole body blew out"
      raise
    end

    # Function might already have been defined by a :call node.
    # TODO: figure out the receiver type? Is that possible at this stage?
    function_type = Type.function Type.unknown, args.sexp_types, Type.unknown
    @functions.unify(name, function_type) do
      @functions.add_function(name, function_type)
      $stderr.puts "\nWARNING: Registering function #{name}: #{function_type.inspect}" if $DEBUG

    end
  end

  return_type = function_type.list_type.return_type

  # Drill down and find all return calls, unify each one against the
  # registered function return value. That way they all have to
  # return the same type. If we don't end up finding any returns,
  # set the function return type to void.

  return_count = 0
  body.each_of_type(:return) do |sub_exp|
    begin
      return_type.unify sub_exp[1].sexp_type
      return_count += 1
    rescue TypeError => err
      puts "Error in method #{name}, trying to unify #{sub_exp.inspect} against #{return_type.inspect}"
      raise
    end
  end
  if return_count == 0 then
    begin
      return_type.unify Type.void
    rescue TypeError => err
      puts "Error in method #{name}, trying to unify #{function_type.inspect} against Type.void"
      raise
    end
  end

  # TODO: bad API, clean
  raise "wrong" if
    args.sexp_types.size != function_type.list_type.formal_types.size
  args.sexp_types.each_with_index do |type, i|
    type.unify function_type.list_type.formal_types[i]
  end

  return t(:defn, name, args, body, function_type)
end

#process_dstr(exp) ⇒ Object

Dynamic string processes all the elements of the body and returns a string-typed sexp.



504
505
506
507
508
509
510
511
# File 'lib/type_checker.rb', line 504

def process_dstr(exp)
  out = t(:dstr, exp.shift, Type.str)
  until exp.empty? do
    result = process exp.shift
    out << result
  end
  return out
end

#process_dvar(exp) ⇒ Object

Dynamic variable lookup looks up the variable in the local environment and returns a sexp of that type.



517
518
519
520
521
# File 'lib/type_checker.rb', line 517

def process_dvar(exp)
  name = exp.shift
  type = @env.lookup name
  return t(:dvar, name, type)
end

#process_ensure(exp) ⇒ Object

Ensure processes the res and the ensure, and returns an untyped sexp.



526
527
528
529
530
531
# File 'lib/type_checker.rb', line 526

def process_ensure(exp)
  res = process exp.shift
  ens = process exp.shift

  t(:ensure, res, ens)
end

#process_error(exp) ⇒ Object

DOC



536
537
538
# File 'lib/type_checker.rb', line 536

def process_error(exp) # :nodoc:
  t(:error, exp.shift)
end

#process_false(exp) ⇒ Object

False returns a bool-typed sexp.



543
544
545
# File 'lib/type_checker.rb', line 543

def process_false(exp)
  return t(:false, Type.bool)
end

#process_gasgn(exp) ⇒ Object

Global variable assignment gets stored in the global assignment.



550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/type_checker.rb', line 550

def process_gasgn(exp)
  var = exp.shift
  val = process exp.shift

  var_type = @genv.lookup var rescue nil
  if var_type.nil? then
    @genv.add var, val.sexp_type
  else
    val.sexp_type.unify var_type
  end

  return t(:gasgn, var, val, val.sexp_type)
end

#process_gvar(exp) ⇒ Object

Global variables get looked up in the global environment. If they are found, a sexp of that type is returned, otherwise the unknown type is added to the global environment and an unknown-typed sexp is returned.



569
570
571
572
573
574
575
576
577
# File 'lib/type_checker.rb', line 569

def process_gvar(exp)
  name = exp.shift
  type = @genv.lookup name rescue nil
  if type.nil? then
    type = Type.unknown
    @genv.add name, type
  end
  return t(:gvar, name, type)
end

#process_hash(exp) ⇒ Object

Hash (inline hashes) are not supported. Returns an unmentionably-typed sexp. – TODO support inline hashes



585
586
587
588
589
590
591
# File 'lib/type_checker.rb', line 585

def process_hash(exp)
  result = t(:hash, Type.fucked)
  until exp.empty? do
    result << process(exp.shift)
  end
  return result
end

#process_iasgn(exp) ⇒ Object

Instance variable assignment is currently unsupported. Does no unification and returns an untyped sexp



597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/type_checker.rb', line 597

def process_iasgn(exp)
  var = exp.shift
  val = process exp.shift

  var_type = @env.lookup var rescue nil
  if var_type.nil? then
    @env.add var, val.sexp_type
  else
    val.sexp_type.unify var_type
  end

  return t(:iasgn, var, val, val.sexp_type)
end

#process_if(exp) ⇒ Object

If unifies the condition against the bool type, then unifies the return types of the then and else expressions against each other. Returns a sexp typed the same as the then and else expressions.



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/type_checker.rb', line 616

def process_if(exp)
  cond_exp = process exp.shift
  then_exp = process exp.shift
  else_exp = process exp.shift rescue nil # might be empty

  cond_exp.sexp_type.unify Type.bool
  begin
    then_exp.sexp_type.unify else_exp.sexp_type unless then_exp.nil? or else_exp.nil?
  rescue TypeError => err
    puts "Error unifying #{then_exp.inspect} with #{else_exp.inspect}"
    raise
  end

  # FIX: at least document this
  type = then_exp.sexp_type unless then_exp.nil?
  type = else_exp.sexp_type unless else_exp.nil?

  return t(:if, cond_exp, then_exp, else_exp, type)
end

#process_iter(exp) ⇒ Object

Iter unifies the dynamic variables against the call args (dynamic variables are used in the iter body) and returns a void-typed sexp.



640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'lib/type_checker.rb', line 640

def process_iter(exp)
  call_exp = process exp.shift
  dargs_exp = process exp.shift
  body_exp = process exp.shift

  lhs = call_exp[1] # FIX
  if lhs.nil? then
    # We're an fcall getting passed a block.
    return t(:iter, call_exp, dargs_exp, body_exp, call_exp.sexp_type)
  else
    Type.unknown_list.unify lhs.sexp_type # force a list type, lhs must be Enum
    Type.new(lhs.sexp_type.list_type).unify dargs_exp.sexp_type # pull out type

    return t(:iter, call_exp, dargs_exp, body_exp, Type.void)
  end
end

#process_ivar(exp) ⇒ Object

Instance variables are currently unsupported. Returns an unknown-typed sexp. – TODO support instance variables



663
664
665
666
667
668
669
670
671
672
673
# File 'lib/type_checker.rb', line 663

def process_ivar(exp)
  name = exp.shift

  var_type = @env.lookup name rescue nil
  if var_type.nil? then
    var_type = Type.unknown
    @env.add name, var_type
  end

  return t(:ivar, name, var_type)
end

#process_lasgn(exp) ⇒ Object

Local variable assignment unifies the variable type from the environment with the assignment expression, and returns a sexp of that type. If there is no local variable in the environment, one is added with the type of the assignment expression and a sexp of that type is returned.



681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# File 'lib/type_checker.rb', line 681

def process_lasgn(exp)
  name = exp.shift
  arg_exp = nil
  arg_type = nil
  var_type = @env.lookup name rescue nil

  sub_exp = exp.shift
  sub_exp_type = sub_exp.first
  arg_exp = process sub_exp

 # if we've got an array in there, unify everything in it.
  if sub_exp_type == :array then
    arg_type = arg_exp.sexp_types
    arg_type = arg_type.inject(Type.unknown) do |t1, t2|
      t1.unify t2
    end
    arg_type = arg_type.dup # singleton type
    arg_type.list = true
  else
    arg_type = arg_exp.sexp_type
  end

  if var_type.nil? then
    @env.add name, arg_type
    var_type = arg_type
  else
    var_type.unify arg_type
  end

  return t(:lasgn, name, arg_exp, var_type)
end

#process_lit(exp) ⇒ Object

Literal values return a sexp typed to match the literal expression.



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/type_checker.rb', line 716

def process_lit(exp)
  value = exp.shift
  type = nil

  case value
  when Fixnum then
    type = Type.long
  when Float then
    type = Type.float
  when Symbol then
    type = Type.symbol
  else
    raise "Bug! no: Unknown literal #{value}:#{value.class}"
  end

  return t(:lit, value, type)
end

#process_lvar(exp) ⇒ Object

Local variables get looked up in the local environment and a sexp of that type is returned.



738
739
740
741
742
# File 'lib/type_checker.rb', line 738

def process_lvar(exp)
  name = exp.shift
  t = @env.lookup name
  return t(:lvar, name, t)
end

#process_nil(exp) ⇒ Object

Nil returns a value-typed sexp.



747
748
749
750
751
# File 'lib/type_checker.rb', line 747

def process_nil(exp)
  # don't do a fucking thing until... we have something to do
  # HACK: wtf to do here? (what type is nil?!?!)
  return t(:nil, Type.value)
end

#process_not(exp) ⇒ Object

Not unifies the type of its expression against bool, then returns a bool-typed sexp.



757
758
759
760
761
# File 'lib/type_checker.rb', line 757

def process_not(exp)
  thing = process exp.shift
  thing.sexp_type.unify Type.bool
  return t(:not, thing, Type.bool)
end

#process_op_asgn_or(exp) ⇒ Object

||= operator is currently unsupported. Returns an untyped sexp. – TODO support ||=



768
769
770
771
772
773
774
# File 'lib/type_checker.rb', line 768

def process_op_asgn_or(exp)
  ivar = process exp.shift
  iasgn = process exp.shift
  body = process exp.shift
  # TODO: probably need to unify all three? or at least the first two...
  return t(:op_asgn_or, ivar, iasgn, body)
end

#process_or(exp) ⇒ Object

Or unifies the left and right hand sides with bool, then returns a bool-typed sexp.



780
781
782
783
784
785
786
787
788
789
790
791
# File 'lib/type_checker.rb', line 780

def process_or(exp)
  rhs = process exp.shift
  lhs = process exp.shift

  rhs_type = rhs.sexp_type
  lhs_type = lhs.sexp_type

  rhs_type.unify lhs_type
  rhs_type.unify Type.bool

  return t(:or, rhs, lhs, Type.bool)
end

#process_resbody(exp) ⇒ Object

Rescue body returns an unknown-typed sexp.



796
797
798
799
800
801
802
803
804
805
806
807
# File 'lib/type_checker.rb', line 796

def process_resbody(exp)
  o1 = process exp.shift
  o2 = process exp.shift
  o3 = exp.empty? ? nil : process(exp.shift)

  result = t(:resbody, Type.unknown) # void?
  result << o1
  result << o2 unless o2.nil?
  result << o3 unless o3.nil?
  
  return result
end

#process_rescue(exp) ⇒ Object

Rescue unifies the begin, rescue and ensure types, and returns an untyped sexp. – FIX isn’t used anywhere



815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
# File 'lib/type_checker.rb', line 815

def process_rescue(exp)
  # TODO: I think there is also an else stmt. Should make it
  # mandatory, not optional.
  # TODO: test me
  try_block = process exp.shift
  rescue_block = process exp.shift
  ensure_block = process exp.shift

  try_type = try_block.sexp_type
  rescue_type = rescue_block.sexp_type
  ensure_type = ensure_block.sexp_type # FIX: not sure if I should unify

  try_type.unify rescue_type
  try_type.unify ensure_type

  return t(:rescue, try_block, rescue_block, ensure_block, try_type)
end

#process_return(exp) ⇒ Object

Return returns a void typed sexp.



836
837
838
839
840
# File 'lib/type_checker.rb', line 836

def process_return(exp)
  result = t(:return, Type.void) # TODO why void - cuz this is a keyword
  result << process(exp.shift) unless exp.empty?
  return result
end

#process_scope(exp) ⇒ Object

Scope returns a void-typed sexp.



845
846
847
848
849
850
851
# File 'lib/type_checker.rb', line 845

def process_scope(exp)
  return t(:scope, Type.void) if exp.empty?

  body = process exp.shift

  return t(:scope, body, Type.void)
end

#process_self(exp) ⇒ Object

Self is currently unsupported. Returns an unknown-typed sexp. – TODO support self



858
859
860
# File 'lib/type_checker.rb', line 858

def process_self(exp)
  return t(:self, Type.unknown)
end

#process_splat(exp) ⇒ Object

Splat is currently unsupported. Returns an unknown-typed sexp. – TODO support splat, maybe like :array?



867
868
869
870
# File 'lib/type_checker.rb', line 867

def process_splat(exp)
  value = process exp.shift
  return t(:splat, value, Type.unknown) # TODO: probably value_list?
end

#process_str(exp) ⇒ Object

String literal returns a string-typed sexp.



875
876
877
# File 'lib/type_checker.rb', line 875

def process_str(exp)
  return t(:str, exp.shift, Type.str)
end

#process_super(exp) ⇒ Object

Super is currently unsupported. Returns an unknown-typed sexp. – TODO support super



884
885
886
887
888
# File 'lib/type_checker.rb', line 884

def process_super(exp)
  args = process exp.shift
  # TODO try to look up the method in our superclass?
  return t(:super, args, Type.unknown)
end

#process_true(exp) ⇒ Object

True returns a bool-typed sexp.



893
894
895
# File 'lib/type_checker.rb', line 893

def process_true(exp)
  return t(:true, Type.bool)
end

#process_while(exp) ⇒ Object

While unifies the condition with bool, then returns an untyped sexp.



900
901
902
903
904
905
906
# File 'lib/type_checker.rb', line 900

def process_while(exp)
  cond = process exp.shift
  body = process exp.shift
  is_precondition = exp.shift
  Type.bool.unify cond.sexp_type
  return t(:while, cond, body, is_precondition)
end

#process_yield(exp) ⇒ Object

Yield is currently unsupported. Returns a unmentionably-typed sexp.



911
912
913
914
915
916
917
# File 'lib/type_checker.rb', line 911

def process_yield(exp)
  result = t(:yield, Type.fucked)
  until exp.empty? do
    result << process(exp.shift)
  end
  return result
end

#translate(klass, method = nil) ⇒ Object



919
920
921
922
923
924
925
# File 'lib/type_checker.rb', line 919

def translate(klass, method = nil)
  @@parser = ParseTree.new(false) unless defined? @@parser
  @@rewriter = Rewriter.new unless defined? @@rewriter
  sexp = @@parser.parse_tree_for_method klass, method
  sexp = @@rewriter.process sexp
  self.process sexp
end