Class: Ikra::TypeInference::Visitor

Inherits:
AST::Visitor show all
Defined in:
lib/types/inference/ast_inference.rb

Defined Under Namespace

Classes: RestartTypeInferenceError

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AST::Visitor

#visit_array_node, #visit_block_def_node, #visit_class_def_node, #visit_meth_def_node, #visit_node, #visit_program_node, #visit_until_node, #visit_until_post_node, #visit_var_def_node, #visit_while_node, #visit_while_post_node

Constructor Details

#initializeVisitor

Returns a new instance of Visitor.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/types/inference/ast_inference.rb', line 155

def initialize
    # Ikra type -> ClassDefNode
    @classes = {}
    @classes.default_proc = proc do |hash, key|
        hash[key] = AST::ClassDefNode.new(
            name: key.cls.name,
            ruby_class: key.cls)
    end

    # Top of stack is the method that is currently processed
    @work_stack = []

    # Block/method definitions that must be processed
    @worklist = Set.new
end

Instance Attribute Details

#classesObject (readonly)

Returns the value of attribute classes.



153
154
155
# File 'lib/types/inference/ast_inference.rb', line 153

def classes
  @classes
end

Instance Method Details

#all_methodsObject



171
172
173
174
175
# File 'lib/types/inference/ast_inference.rb', line 171

def all_methods
    return classes.values.map do |class_|
        class_.instance_methods
    end.flatten
end

#assert_singleton_type(union_type, expected_type) ⇒ Object



370
371
372
373
374
375
# File 'lib/types/inference/ast_inference.rb', line 370

def assert_singleton_type(union_type, expected_type)
    if union_type.singleton_type != expected_type
        raise AssertionError.new(
            "Expected type #{expected_type} but found #{union_type.singleton_type}")
    end
end

#bindingObject



181
182
183
# File 'lib/types/inference/ast_inference.rb', line 181

def binding
    current_method_or_block.binding
end

#current_method_or_blockObject



185
186
187
# File 'lib/types/inference/ast_inference.rb', line 185

def current_method_or_block
    @work_stack.last
end

#process_block(block_def_node) ⇒ Object

This is used as an entry point for the visitor



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/types/inference/ast_inference.rb', line 190

def process_block(block_def_node)
    Log.info("Type inference: proceed into block(#{Types::UnionType.parameter_hash_to_s(block_def_node.parameters_names_and_types)})")

    @work_stack.push(block_def_node)
    body_ast = block_def_node.body

    # Variables that are not defined inside the block
    predefined_variables = []

    # Add parameters to symbol table (name -> type)
    block_def_node.parameters_names_and_types.each do |name, type|            
        symbol_table.declare_variable(name, type: type)
        predefined_variables.push(name)
    end

    # Add lexical variables to symbol table (name -> type)
    block_def_node.lexical_variables_names_and_types.each do |name, type|
        # Variable might be shadowed by parameter
        symbol_table.ensure_variable_declared(name, type: type, kind: :lexical)
        predefined_variables.push(name)
    end

    begin
        # Infer types
        body_ast.accept(self)
    rescue RestartTypeInferenceError
        # Reset all type information
        symbol_table.clear!
        block_def_node.accept(ClearTypesVisitor.new)

        # Remove block from stack
        @work_stack.pop

        Log.info("Changed AST during type inference. Restarting type inference.")

        # Restart inference
        return process_block(block_def_node)
    end

    # Get local variable definitons
    for variable_name in symbol_table.read_and_written_variables
        if !predefined_variables.include?(variable_name)
            block_def_node.local_variables_names_and_types[variable_name] =
                symbol_table[variable_name]
        end
    end

    return_value_type = symbol_table.return_type 
    Log.info("Type inference: block return type is #{return_value_type.to_s}")

    @work_stack.pop

    return_type = block_def_node.merge_union_type(return_value_type)

    if block_def_node.types_changed?
        # Types changed, do another pass. This is not efficient and there are better
        # ways to do type inference (e.g., constraint solving), but it works for now.
        block_def_node.reset_types_changed
        return process_block(block_def_node)
    else
        return return_type
    end
end

#process_method(method_def_node) ⇒ Object

This is used as an entry point for the visitor



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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
# File 'lib/types/inference/ast_inference.rb', line 255

def process_method(method_def_node)
    Log.info("Type inference: proceed into method #{method_def_node.receiver_type}.#{method_def_node.name}(#{Types::UnionType.parameter_hash_to_s(method_def_node.parameters_names_and_types)})")

    @work_stack.push(method_def_node)
    body_ast = method_def_node.body

    # TODO: handle multiple receiver types
    recv_type = method_def_node.receiver_type

    # Variables that are not defined inside the method
    predefined_variables = []

    # Add parameters to symbol table (name -> type)
    method_def_node.parameters_names_and_types.each do |name, type|
        symbol_table.declare_variable(name, type: type)
        predefined_variables.push(name)
    end

    # Add lexical variables to symbol table (name -> type)
    method_def_node.lexical_variables_names_and_types.each do |name, type|
        # Variable might be shadowed by parameter
        symbol_table.ensure_variable_declared(name, type: type, kind: :lexical)
        predefined_variables.push(name)
    end

    # Add return statements
    body_ast.accept(Translator::LastStatementReturnsVisitor.new)

    # Infer types
    body_ast.accept(self)

    # Get local variable definitons
    for variable_name in symbol_table.read_and_written_variables
        if !predefined_variables.include?(variable_name)
            method_def_node.local_variables_names_and_types[variable_name] =
                symbol_table[variable_name]
        end
    end
    
    return_value_type = symbol_table.return_type 
    Log.info("Type inference: method return type is #{return_value_type.to_s}")

    @work_stack.pop

    return_type = method_def_node.merge_union_type(return_value_type)

    if method_def_node.types_changed?
        # Types changed, do another pass. This is not efficient and there are better
        # ways to do type inference (e.g., constraint solving), but it works for now.
        method_def_node.reset_types_changed
        return process_method(method_def_node)
    else
        return return_type
    end
end

#symbol_tableObject



177
178
179
# File 'lib/types/inference/ast_inference.rb', line 177

def symbol_table
    current_method_or_block.symbol_table
end

#visit_begin_node(node) ⇒ Object



491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/types/inference/ast_inference.rb', line 491

def visit_begin_node(node)
    node.body_stmts[0...-1].each do |stmt|
        stmt.accept(self)
    end

    if node.body_stmts.empty?
        type = Types::UnionType.new
    else
        type = node.body_stmts.last.accept(self)
    end

    node.merge_union_type(type)
end

#visit_bool_node(node) ⇒ Object



429
430
431
# File 'lib/types/inference/ast_inference.rb', line 429

def visit_bool_node(node)
    node.merge_union_type(Types::UnionType.create_bool)
end

#visit_break_node(node) ⇒ Object



462
463
464
# File 'lib/types/inference/ast_inference.rb', line 462

def visit_break_node(node)
    Types::UnionType.create_void
end

#visit_const_node(node) ⇒ Object



377
378
379
380
381
382
383
384
# File 'lib/types/inference/ast_inference.rb', line 377

def visit_const_node(node)
    if not binding
        raise AssertionError.new("Unable to resolve constants without Binding")
    end

    constant = binding.eval(node.identifier.to_s)
    node.merge_union_type(constant.ikra_type.to_union_type)
end

#visit_float_node(node) ⇒ Object



425
426
427
# File 'lib/types/inference/ast_inference.rb', line 425

def visit_float_node(node)
    node.merge_union_type(Types::UnionType.create_float)
end

#visit_for_node(node) ⇒ Object



448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/types/inference/ast_inference.rb', line 448

def visit_for_node(node)
    assert_singleton_type(node.range_from.accept(self), Types::PrimitiveType::Int)
    assert_singleton_type(node.range_to.accept(self), Types::PrimitiveType::Int)

    changed = symbol_table.ensure_variable_declared(node.iterator_identifier, type: Types::UnionType.create_int)
    symbol_table.written!(node.iterator_identifier)
    
    super(node)
    
    # TODO: Should return range

    node.merge_union_type(Types::UnionType.create_int)
end

#visit_hash_node(node) ⇒ Object



443
444
445
446
# File 'lib/types/inference/ast_inference.rb', line 443

def visit_hash_node(node)
    # Use [Types::ClassType] for the moment
    return Hash.to_ikra_type.to_union_type
end

#visit_if_node(node) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/types/inference/ast_inference.rb', line 466

def visit_if_node(node)
    assert_singleton_type(node.condition.accept(self), Types::PrimitiveType::Bool)
    
    type = Types::UnionType.new
    type.expand(node.true_body_stmts.accept(self))       # Begin always has type of last stmt

    if node.false_body_stmts == nil
        type.expand(Types::UnionType.create_void)
    else
        type.expand(node.false_body_stmts.accept(self))
    end

    node.merge_union_type(type)
end

#visit_int_node(node) ⇒ Object



417
418
419
# File 'lib/types/inference/ast_inference.rb', line 417

def visit_int_node(node)
    node.merge_union_type(Types::UnionType.create_int)
end

#visit_ivar_read_node(node) ⇒ Object



411
412
413
414
415
# File 'lib/types/inference/ast_inference.rb', line 411

def visit_ivar_read_node(node)
    cls_type = node.enclosing_class.ruby_class.to_ikra_type
    cls_type.inst_var_read!(node.identifier)
    cls_type.inst_vars_types[node.identifier]
end

#visit_lvar_read_node(node) ⇒ Object



390
391
392
393
394
395
# File 'lib/types/inference/ast_inference.rb', line 390

def visit_lvar_read_node(node)
    symbol_table.read!(node.identifier)

    # Extend type of variable
    return node.merge_union_type(symbol_table[node.identifier])
end

#visit_lvar_write_node(node) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/types/inference/ast_inference.rb', line 397

def visit_lvar_write_node(node)
    type = node.value.accept(self)

    # Declare/extend type in symbol table
    symbol_table.ensure_variable_declared(node.identifier, type: type)
    symbol_table.written!(node.identifier)

    node.variable_type = symbol_table[node.identifier]

    # Extend type of variable
    # Note: Return value of this expression != type of the variable
    node.merge_union_type(type)
end

#visit_method_call(send_node, recv_singleton_type) ⇒ Object

This is not an actual Visitor method. It is called from visit_send_node.



312
313
314
315
316
317
318
319
320
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/types/inference/ast_inference.rb', line 312

def visit_method_call(send_node, recv_singleton_type)
    selector = send_node.selector

    if recv_singleton_type.is_primitive?
        raise NotImplementedError.new("#{recv_singleton_type}.#{selector} not implemented (#{send_node.to_s})")
    end

    parameter_names = recv_singleton_type.method_parameters(selector)
    arg_types = send_node.arguments.map do |arg| arg.get_type end
    ast = recv_singleton_type.method_ast(selector)
    method_visited_before = nil

    if not @classes[recv_singleton_type].has_instance_method?(selector)
        # This method was never visited before
        method_def_node = AST::MethDefNode.new_with_types(
            name: selector,
            body: ast,
            parameters_names_and_types: Hash[*parameter_names.zip(
                Array.new(arg_types.size) do |arg_index|
                    Types::UnionType.new
                end).flatten],
            ruby_method: nil,
            receiver_type: recv_singleton_type,
            method_binding: recv_singleton_type.method_binding(selector))
        @classes[recv_singleton_type].add_instance_method(method_def_node)
        method_visited_before = false
    else
        method_visited_before = true
    end

    method_def_node = @classes[recv_singleton_type].instance_method(selector)

    parameter_types_expanded = parameter_names.map.with_index do |name, index|
        # returns true if expanded 
        method_def_node.parameters_names_and_types[name].expand(arg_types[index])
    end.reduce(:|)

    # Method needs processing if any parameter is expanded (or method was never visited before)
    needs_processing = !method_visited_before or parameter_types_expanded

    # Return value type from the last pass
    # TODO: Have to make a copy here?
    last_return_type = method_def_node.get_type
    
    if needs_processing
        process_method(method_def_node)
    end

    if not last_return_type.include_all?(method_def_node.get_type)
        # Return type was expanded during this pass, reprocess all callers (except for current method)
        @worklist += (method_def_node.callers - [current_method_or_block])
    end

    method_def_node.callers.push(current_method_or_block)

    return method_def_node.get_type
end

#visit_nil_node(node) ⇒ Object



421
422
423
# File 'lib/types/inference/ast_inference.rb', line 421

def visit_nil_node(node)
    node.merge_union_type(Types::UnionType.create_nil)
end

#visit_return_node(node) ⇒ Object



505
506
507
508
509
# File 'lib/types/inference/ast_inference.rb', line 505

def visit_return_node(node)
    type = node.value.accept(self)
    symbol_table.expand_return_type(type)
    node.merge_union_type(type)
end

#visit_root_node(node) ⇒ Object



386
387
388
# File 'lib/types/inference/ast_inference.rb', line 386

def visit_root_node(node)
    node.merge_union_type(node.single_child.accept(self))
end

#visit_send_node(node) ⇒ Object



565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/types/inference/ast_inference.rb', line 565

def visit_send_node(node)
    # TODO: handle self sends
    receiver_type = nil

    if node.receiver == nil
        Logger.warn("No receiver given for node #{node.to_s}")
        receiver_type = Types::UnionType.create_int
    else
        receiver_type = node.receiver.accept(self)
    end

    node.arguments.each do |arg|
        arg.accept(self)
    end
    
    visit_send_node_union_type(receiver_type, node)

    return node.get_type
end

#visit_send_node_singleton_receiver(sing_type, node) ⇒ Object



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/types/inference/ast_inference.rb', line 517

def visit_send_node_singleton_receiver(sing_type, node)
    if RubyIntegration.is_interpreter_only?(sing_type)
        return Types::InterpreterOnlyType.new.to_union_type
    elsif RubyIntegration.has_implementation?(sing_type, node.selector)
        arg_types = node.arguments.map do |arg| arg.get_type end

        begin
            return_type = RubyIntegration.get_return_type(
                sing_type, node.selector, *arg_types, send_node: node)
            return return_type
        rescue RubyIntegration::CycleDetectedError => cycle_error
            # Cannot do further symbolic execution, i.e., kernel fusion here,
            # because we are in a loop.

            # Invoke parallel section: change to `RECV` to `RECV.__call__.to_command`
            node.replace_child(
                node.receiver, 
                AST::SendNode.new(
                    receiver: AST::SendNode.new(
                        receiver: node.receiver, selector: :__call__),
                    selector: :to_command))

            # Start fresh
            raise RestartTypeInferenceError.new
        end
    elsif sing_type.is_a?(Types::StructType)
        # This is a struct type, special type inference rules apply
        return sing_type.get_return_type(node.selector, *node.arguments)
    else
        Log.info("Translate call to ordinary Ruby method #{sing_type}.#{node.selector}")
        return visit_method_call(node, sing_type)
    end
end

#visit_send_node_union_type(receiver_type, node) ⇒ Object



551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/types/inference/ast_inference.rb', line 551

def visit_send_node_union_type(receiver_type, node)
    type = Types::UnionType.new

    for sing_type in receiver_type
        return_type = visit_send_node_singleton_receiver(sing_type, node)
        node.return_type_by_recv_type[sing_type] = return_type
        type.expand(return_type)
    end

    node.merge_union_type(type)

    return type
end

#visit_source_code_expr_node(node) ⇒ Object



511
512
513
514
515
# File 'lib/types/inference/ast_inference.rb', line 511

def visit_source_code_expr_node(node)
    # This is a synthetic node. No type inference. Return the type that was set
    # manually before (if any).
    return node.get_type
end

#visit_string_node(node) ⇒ Object



433
434
435
436
# File 'lib/types/inference/ast_inference.rb', line 433

def visit_string_node(node)
    # Use [Types::ClassType] for the moment
    return node.value.ikra_type.to_union_type
end

#visit_symbol_node(node) ⇒ Object



438
439
440
441
# File 'lib/types/inference/ast_inference.rb', line 438

def visit_symbol_node(node)
    # Use [Types::ClassType] for the moment
    return node.value.ikra_type.to_union_type
end

#visit_ternary_node(node) ⇒ Object



481
482
483
484
485
486
487
488
489
# File 'lib/types/inference/ast_inference.rb', line 481

def visit_ternary_node(node)
    assert_singleton_type(node.condition.accept(self), Types::PrimitiveType::Bool)
    
    type = Types::UnionType.new
    type.expand(node.true_val.accept(self))
    type.expand(node.false_val.accept(self))

    node.merge_union_type(type)
end