Class: Yadriggy::RubyTypeChecker

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

Overview

Type checker for Ruby. Most values are typed as DynType but local variables are identified. So type(ast) returns a LocalVarType object if ast is of a local variable. A LocalVarType is a Type object that represents not only the value's type but also the fact that the value comes from a local variable.

The type of a free variable is the type of the current value of that variable. TypeChecker#type returns an InstanceType object.

This checker also attempts to recursivly trace a call-graph to reify and type-check the ASTs of the called methods.

Direct Known Subclasses

Py::PyTypeChecker, RubyTypeInferer

Constant Summary

Constants included from Yadriggy

DynType, Undef, VERSION, Void

Instance Method Summary collapse

Methods inherited from TypeChecker

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

Methods inherited from Checker

#ast, #ast_env, #check, #check_all, #check_later, #error!, #error_found!, #error_messages, #errors?, #last_error, #make_base_env, #proceed, rule

Methods included from Yadriggy

debug, debug=, define_syntax, reify, reset_pry

Constructor Details

#initialize(syntax = nil) ⇒ RubyTypeChecker

Returns a new instance of RubyTypeChecker.



21
22
23
24
# File 'lib/yadriggy/ruby_typecheck.rb', line 21

def initialize(syntax=nil)
  super()
  @syntax = syntax
end

Instance Method Details

#bind_local_var(env, ast, var_type) ⇒ Object

Helper methods for rules They can be overridden.



369
370
371
372
373
374
# File 'lib/yadriggy/ruby_typecheck.rb', line 369

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

#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.



411
412
413
414
415
# File 'lib/yadriggy/ruby_typecheck.rb', line 411

def get_call_expr_type(call_ast, type_env, method_name)
  arg_types = call_ast.args.map {|t| type(t) }
  get_call_expr_type_with_argtypes(call_ast, type_env, method_name,
                                   arg_types)
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.



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/yadriggy/ruby_typecheck.rb', line 90

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. If its source code is not found, #get_return_type reports an error.

This method #get_return_type 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:



509
510
511
512
513
514
515
516
517
# File 'lib/yadriggy/ruby_typecheck.rb', line 509

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_ruby_classes(type_env, arg_types, recv_type, method_name) ⇒ ResultType

Computes the type of the Call expression by searching the receiver class for the called method.

Parameters:

  • type_env (TypeEnv)

    a type environment.

  • arg_types (Array<Type>)

    the types of the actual arguments.

  • recv_type (Type)

    the receiver type.

  • method_name (String|Symbol)

    the name of the called method.

Returns:

Raises:

  • (CheckError)

    if the method is not found in the receiver class.



481
482
483
484
485
486
487
488
489
# File 'lib/yadriggy/ruby_typecheck.rb', line 481

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

#type_args_and_block(call_ast) ⇒ Object



226
227
228
229
# File 'lib/yadriggy/ruby_typecheck.rb', line 226

def type_args_and_block(call_ast)
  call_ast.args.each {|t| type(t) }
  type(call_ast.block)
end

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



376
377
378
379
380
381
382
383
384
385
# File 'lib/yadriggy/ruby_typecheck.rb', line 376

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



387
388
389
# File 'lib/yadriggy/ruby_typecheck.rb', line 387

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

#type_parameters(an_ast, tenv) ⇒ Object



391
392
393
394
395
396
397
398
399
400
401
# File 'lib/yadriggy/ruby_typecheck.rb', line 391

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