class Yadriggy::RubyTypeInferer
Type
checker for Ruby with type inference. The type system is slightly different from Ruby's one. For example, once an integer is assigned to a local variable, assigning a String value to it causes a type error.
Public Instance Methods
@api private
# File lib/yadriggy/ruby_typeinfer.rb, line 230 def binary_type(bin_expr, right_t, left_t) op = bin_expr.op case op when :'&&', :'||', :and, :or # not overridable return UnionType.new([right_t, left_t]) when :>, :>=, :<, :<=, :==, :===, :!= if left_t <= RubyClass::Numeric return RubyClass::Boolean end when :**, :*, :/, :%, :+, :- if left_t <= RubyClass::Numeric if left_t <= RubyClass::Float || right_t <= RubyClass::Float return RubyClass::Float else return RubyClass::Integer end end when :<<, :>>, :&, :|, :^ return RubyClass::Integer if left_t <= RubyClass::Integer # when :=~, :!~, :<=> end if left_t <= RubyClass::String if op == :% || op == :+ || op == :<< return RubyClass::String elsif op == :=~ || op == :<=> return UnionType.new(RubyClass::Integer, RubyClass::NilClass) elsif op == :!~ return RubyClass::Boolean end end call_expr = Call.make(receiver: bin_expr.left, name: op, args: [bin_expr.right], parent: bin_expr.parent) return get_call_expr_type(call_expr, type_env, op) end
Binds a local variable name to a type. @param [TypeEnv] env a type environment. @param [ASTnode] ast a local variable name. @param [Type] var_type a type. @param [Boolean] is_def true if the variable is initialized there.
# File lib/yadriggy/ruby_typeinfer.rb, line 20 def bind_local_var(env, ast, var_type, is_def=true) unless var_type.nil? t = if UnionType.role(var_type) ts = UnionType.role(var_type).types UnionType.new(ts.map {|t| to_non_instance_type(t) }) else ins_t = InstanceType.role(var_type) to_non_instance_type(var_type) end lvt = LocalVarType.new(t.copy(OptionalRole), is_def ? ast : nil) env.bind_name(ast, lvt) @typetable[ast] = lvt end end
Obtains the type of the given instance variable `ivar` declared in the given class (i.e. module) or the instance object `key`. If the type of `ivar` is not defined, `value_type` is recorded as its type.
@param [Module|Object] key the key when looking into the typedef table. @param [InstanceVariable] ivar an instance variable. @param [Boolean] is_valid_type true if `value_type` is valid. @param [Type] value_type the type suggested for `ivar`. @return [Type] the type of `ivar`.
# File lib/yadriggy/ruby_typeinfer.rb, line 193 def get_instance_variable_type(key, ivar, is_valid_type, value_type) td = add_typedef(key) ivar_t = td[ivar] if ivar_t.nil? td[ivar] = value_type else type_assert_subsume(ivar_t, value_type, "bad type value for #{ivar.name}") if is_valid_type ivar_t end end
@api private Overrides {RubyTypeChecker#get_return_type}.
# File lib/yadriggy/ruby_typeinfer.rb, line 304 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 m_ast.tree.params.each_with_index do |p, i| bind_local_var(new_tenv, p, arg_types[i]) end nparams = m_ast.tree.params.length m_ast.tree.optionals.each_with_index do |p, i| bind_local_var(new_tenv, p, arg_types[nparams + i]) end 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
@api private
# File lib/yadriggy/ruby_typeinfer.rb, line 134 def is_attr_accessor?(tenv, name) self_t = type_env.context !self_t.nil? && (self_t.method_defined?(name) || self_t.private_method_defined?(name)) end
@api private When the initial value of a variable is an InstanceType
, the type of the variable has to be a RubyCass type corresponding to that instance type. The variable type can be set to that InstanceType
only when it is guaranteed that the value of the variable is never changed later.
# File lib/yadriggy/ruby_typeinfer.rb, line 42 def to_non_instance_type(t) ins_t = InstanceType.role(t) if ins_t.nil? t else ins_t.supertype end end
@api private Overriding and overloading.
# File lib/yadriggy/ruby_typeinfer.rb, line 93 def type_assign(left_expr, ast_op, right_expr, rtype, ast_parent) if ast_op != :'=' # if op is += etc. ltype = type(left_expr) LocalVarType.role(ltype)&.definition = left_expr ltype elsif left_expr.is_a?(IdentifierOrCall) vtype = type_env.bound_name?(left_expr) if vtype.nil? # if a new name is found, method_name = left_expr.name + '=' if is_attr_accessor?(type_env, method_name.to_sym) # self.name=()? call_expr = Call.make(name: method_name, args: [ right_expr ], parent: ast_parent) get_call_expr_type_with_argtypes(call_expr, type_env, method_name, [ rtype ]) else bind_local_var(type_env, left_expr, rtype) end else type_assert(rtype <= vtype, 'incompatible assignment type') LocalVarType.role(vtype)&.definition = left_expr vtype end elsif left_expr.is_a?(Call) && left_expr.op == :'.' # obj.name=()? method_name = left_expr.name.name + '=' call_expr = Call.make(receiver: left_expr.receiver, name: method_name, args: [ right_expr ], parent: ast_parent) get_call_expr_type_with_argtypes(call_expr, type_env, method_name, [ rtype ]) elsif left_expr.is_a?(InstanceVariable) # @var = ..., @@cvar = ..., @var += ... get_instance_variable_type(type_env.context, left_expr, true, InstanceType.role(rtype)&.supertype || rtype) elsif left_expr.is_a?(GlobalVariable) get_instance_variable_type(:global_variables, left_expr, true, InstanceType.role(rtype)&.supertype || rtype) else type(left_expr) # a[i] = ..., <expr> = ... or <expr> += ... end end
@api private Overriding and overloading
# File lib/yadriggy/ruby_typeinfer.rb, line 70 def type_multi_assign(ast_left, ast_op, ast_right, ast_parent) if ast_right.is_a?(Array) rtypes = ast_right.map {|e| type(e) } ast_left.each_with_index do |v, i| if i < rtypes.size type_assign(v, ast_op, ast_right[i], rtypes[i], ast_parent) else # passing "ast_right" is wrong but it will not be used.. type_assign(v, ast_op, ast_right, RubyClass::NilClass, ast_parent) end end else type(ast_right) ast_left.each_with_index do |v, i| # passing "ast_right" is wrong but it will not be used.. type_assign(v, ast_op, ast_right, DynType, ast_parent) end end DynType end