Class: Yadriggy::RubyTypeChecker

Inherits:
TypeChecker show all
Defined in:
lib/yadriggy/ruby_typecheck.rb

Overview

Type checker for Ruby

Direct Known Subclasses

RubyTypeInferer

Constant Summary

Constants included from Yadriggy

DynType, Undef, VERSION, Void

Instance Method Summary collapse

Methods inherited from TypeChecker

#add_typedef, #check, #error_group, #make_base_env, #type, #type_as, #type_assert, #type_assert_false, #type_assert_later, #type_env, #typecheck, #typedef

Methods inherited from Checker

all_rules, #apply_typing_rule, #ast, #ast_env, #check, #check_all, check_init_class, #check_later, #error, #error_found!, #error_group, find_rule_entry, #make_base_env, #proceed, rule

Methods included from Yadriggy

debug, debug=, define_syntax, reify

Constructor Details

#initialize(syntax = nil) ⇒ RubyTypeChecker

Returns a new instance of RubyTypeChecker.



10
11
12
13
14
# File 'lib/yadriggy/ruby_typecheck.rb', line 10

def initialize(syntax=nil)
  super()
  @syntax = syntax
  @referred_objects = Set.new
end

Instance Method Details

#bind_local_var(env, ast, var_type) ⇒ Object

Helper methods for rules They can be overridden.



338
339
340
341
342
343
# File 'lib/yadriggy/ruby_typecheck.rb', line 338

def bind_local_var(env, ast, var_type)
  unless var_type.nil?
    env.bind_name(ast, LocalVarType.new(var_type.copy(OptionalRole), ast))
    @typetable[ast] = var_type
  end
end

#clear_referencesObject

Makes the references set empty. The references set is a set of constants returned by #references.



25
26
27
# File 'lib/yadriggy/ruby_typecheck.rb', line 25

def clear_references
  @referred_objects = Set.new
end

#get_call_expr_type(call_ast, type_env, method_name) ⇒ ResultType

Computes the type of Call expression. If it finds ‘method_name` in `type_env`, it returns its type recorded in `type_env`.

Parameters:

  • call_ast (Call)

    an AST.

  • type_env (TypeEnv)

    a type environment.

  • method_name (String|Symbol)

    a method name.

Returns:

  • (ResultType)

    the type of the resulting value.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/yadriggy/ruby_typecheck.rb', line 380

def get_call_expr_type(call_ast, type_env, method_name)
  arg_types = call_ast.args.map {|t| type(t) }
  type(call_ast.block_arg)
  type(call_ast.block)

  if call_ast.receiver.nil?
    found_t = type_env.bound_name?(method_name)
    unless found_t.nil?
      recv_type = DynType
    else
      recv_obj = call_ast.get_receiver_object
      recv_type = if recv_obj.nil?
                    if type_env.context.nil?
                      DynType
                    else
                      RubyClass[type_env.context]    # self's type
                    end
                  else
                    InstanceType.new(recv_obj)
                  end
    end
  else
    found_t = nil
    recv_type = type(call_ast.receiver)
  end

  if !found_t.nil?
    found_t
  elsif DynType == recv_type || DynType == recv_type.exact_type
    DynType
  else
    lookup_builtin(recv_type, method_name) ||
    lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
  end
end

#get_name_type(name_ast, tenv) ⇒ Object

Gets the type of a given name, which may be a local variable or a free variable.

Parameters:

  • name_ast (Name)

    an AST.

  • tenv (TypeEnv)

    a type environment.



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/yadriggy/ruby_typecheck.rb', line 75

def get_name_type(name_ast, tenv)
  type = tenv.bound_name?(name_ast)
  if type
    type
  else
    v = name_ast.value
    if v == Undef
      DynType
    else
      InstanceType.new(v)
    end
  end
end

#get_return_type(an_ast, mthd, new_tenv, arg_types) ⇒ ResultType

Type-checks whether the argument types match parameter types. It returns a Yadriggy::ResultType.

Override this method to delimit reification. The implementation in this class reifies any method. The method does not have to return a ResultType, which can be used in a later phase to obtain the invoked method. This method is invoked by rule(Call). See rule(Call) for more details.

Parameters:

  • an_ast (Call)

    the Call node.

  • mthd (Proc|Method|UnboundMethod)

    the method invoked by an_ast. if ‘mthd` is nil, #get_return_type reports an error.

  • new_tenv (TypeEnv)

    a type environment.

  • arg_types (Array<Type>)

    the types of the actual arguments.

Returns:



457
458
459
460
461
462
463
464
465
466
# File 'lib/yadriggy/ruby_typecheck.rb', line 457

def get_return_type(an_ast, mthd, new_tenv, arg_types)
  m_ast = an_ast.root.reify(mthd)
  type_assert_false(m_ast.nil?, "no source code: for #{mthd}")
  (@syntax.check(m_ast.tree) || @syntax.raise_error) if @syntax

  mtype = MethodType.role(type(m_ast.tree, new_tenv))
  type_assert(mtype, 'not a method type')
  type_assert_params(mtype.params, arg_types, 'argument type mismatch')
  mtype.result
end

#lookup_builtin(recv_type, method_name) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/yadriggy/ruby_typecheck.rb', line 417

def lookup_builtin(recv_type, method_name)
  et = recv_type.exact_type
  if DynType == et
    nil
  else
    mt = typedef(et)&.[](method_name)
    if mt.nil?
      nil
    else
      MethodType.role(mt)&.result
    end
  end
end

#lookup_ruby_classes(type_env, arg_types, recv_type, method_name) ⇒ Object



432
433
434
435
436
437
438
439
440
# File 'lib/yadriggy/ruby_typecheck.rb', line 432

def lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
  begin
    mth = Type.get_instance_method_object(recv_type, method_name)
  rescue CheckError => evar
    error_found!(ast, evar.message)
  end
  new_tenv = type_env.new_base_tenv(recv_type.exact_type)
  get_return_type(ast, mth, new_tenv, arg_types)
end

#referencesSet<Object>

Returns all the constants that the type-checked code has referred to. Numbers and classes are excluced.

Returns:

  • (Set<Object>)

    all the constants that the type-checked code has referred to. Numbers and classes are excluced.



18
19
20
# File 'lib/yadriggy/ruby_typecheck.rb', line 18

def references()
  @referred_objects
end

#type_assert_params(params, args, errmsg = '') ⇒ Object



345
346
347
348
349
350
351
352
353
354
# File 'lib/yadriggy/ruby_typecheck.rb', line 345

def type_assert_params(params, args, errmsg='')
  unless params == DynType
    type_assert(params.is_a?(Array), errmsg)
    type_assert(args.is_a?(Array), errmsg)
    type_assert(params.length <= args.length, errmsg)  # ignore keyword params
    params.each_with_index do |p, i|
      type_assert_subsume(p, args[i], errmsg)
    end
  end
end

#type_assert_subsume(expected_type, actual_type, errmsg = '') ⇒ Object



356
357
358
# File 'lib/yadriggy/ruby_typecheck.rb', line 356

def type_assert_subsume(expected_type, actual_type, errmsg='')
  type_assert(actual_type <= expected_type, errmsg)
end

#type_parameters(an_ast, tenv) ⇒ Object



360
361
362
363
364
365
366
367
368
369
370
# File 'lib/yadriggy/ruby_typecheck.rb', line 360

def type_parameters(an_ast, tenv)
  an_ast.params.each {|v| bind_local_var(tenv, v, DynType) }
  an_ast.optionals.each {|v| bind_local_var(tenv, v[0], DynType) }
  bind_local_var(tenv, an_ast.rest_of_params,
                 DynType) unless an_ast.rest_of_params.nil?
  an_ast.keywords.each {|v| bind_local_var(tenv, v, DynType) }
  bind_local_var(tenv, an_ast.rest_of_keywords,
                 DynType) unless an_ast.rest_of_keywords.nil?
  bind_local_var(tenv, an_ast.block_param,
                 DynType) unless an_ast.block_param.nil?
end