class TypedRb::AstParser

Custom parser for type signatures. It will transform the signature string into a hash with the type information.

Public Instance Methods

ast(ruby_code) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 51
def ast(ruby_code)
  Parser::CurrentRuby.parse(ruby_code)
end
parse(expr) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 55
def parse(expr)
  map(ast(expr), ParsingContext.new)
end

Private Instance Methods

build_send_message(receiver, message, args, node, _context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 316
def build_send_message(receiver, message, args, node, _context)
  if message == :typesig
  # ignore
  else
    if receiver.nil? && (message == :fail || message == :raise)
      TmError.new(node)
    else
      if args.last.is_a?(Array) && args.last.first == :block_pass
        block_pass = args.pop
        tm_send = TmSend.new(receiver, message, args, node)
        tm_send.with_block(block_pass.last)
        tm_send
      else
        TmSend.new(receiver, message, args, node)
      end
    end
  end
end
map(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 61
def map(node, context)
  if @ignore_node
    @ignore_node = false
    return TmNil.new(node)
  end
  if node
    sexp = node.to_sexp
    sexp = "#{sexp[0..50]} ... #{sexp[-50..-1]}" if sexp.size > 100
    TypedRb.log(binding, :debug, "Parsing node #{node}:\n#{sexp}")
  end
  case node.type
  when :rewritten
    node.rewritten
  when :nil
    TmNil.new(node)
  when :module
    parse_module(node, context)
  when :class
    parse_class(node, context)
  when :def
    parse_def(node, context)
  when :defs
    parse_defs(node, context)
  when :ivar
    parse_instance_var(node, context)
  when :ivasgn
    parse_instance_var_assign(node, context)
  when :lvar
    TmVar.new(node.children.first, node)
  when :lvasgn
    parse_lvasgn(node, context)
  when :gvar
    parse_global_var(node, context)
  when :gvasgn
    parse_global_var_assign(node, context)
  when :masgn
    parse_mass_assign(node, context)
  when :begin, :kwbegin
    parse_begin(node, context)
  when :rescue
    parse_try(node, context)
  when :resbody
    parse_rescue(node, context)
  when :int
    TmInt.new(node)
  when :array
    parse_array_literal(node, context)
  when :hash
    parse_hash_literal(node, context)
  when :true, :false
    TmBoolean.new(node)
  when :str
    TmString.new(node)
  when :float
    TmFloat.new(node)
  when :sym
    TmSymbol.new(node)
  when :regexp
    parse_regexp(node, context)
  when :if
    parse_if_then_else(node, context)
  when :case
    parse_case_when(node, context)
  when :block
    parse_block(node, context)
  when :send
    parse_send(node, context)
  when :yield
    parse_yield(node, context)
  when :const
    TmConst.new(parse_const(node), node)
  when :casgn
    map(node.children.last, context)
  when :sclass
    parse_sclass(node, context)
  when :dstr
    parse_string_interpolation(node, context)
  when :dsym
    parse_symbol_interpolation(node, context)
  when :return
    parse_return(node, context)
  when :self
    parse_self(node, context)
  when :or
    parse_boolean_operation(:or, node, context)
  when :and
    parse_boolean_operation(:and, node, context)
  when :block_pass
    parse_block_pass(node, context)
  when :or_asgn, :and_asgn
    parse_boolean_asgn(node, context)
  when :op_asgn
    parse_op_asgn(node, context)
  when :defined?
    TmDefined.new(map(node.children.first, context), node)
  when :zsuper
    TmSuper.new(nil, node)
  when :super
    parse_super_with_args(node, context)
  when :while, :while_post, :until, :until_post
    parse_while(node, context)
  when :for
    parse_for(node, context)
  when :irange, :erange
    parse_range(node, context)
  when :break
    parse_break(node, context)
  when :next
    parse_next(node, context)
  else
    fail TermParsingError.new("Unknown term #{node.type}: #{node.to_sexp}", node)
  end
end
parse_args(args, context, node) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 284
def parse_args(args, context, node)
  args.map do |arg|
    case arg.type
    when :arg
      [:arg, arg.children.last]
    when :optarg
      [:optarg, arg.children.first, map(arg.children.last, context)]
    when :blockarg
      [:blockarg, arg.children.first]
    when :restarg
      [:restarg, arg.children.last]
    when :mlhs
      [:mlhs, TmMlhs.new(arg.children.map { |n| n.children.last }, arg)]
    else
      fail Types::TypeParsingError.new("Unknown type of arg '#{arg.type}'", node)
    end
  end
end
parse_array_literal(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 490
def parse_array_literal(node, context)
  TmArrayLiteral.new(node.children.map do |child|
                       map(child, context)
                     end, node)
end
parse_begin(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 458
def parse_begin(node, context)
  mapped = node.children.map do |child_node|
    map(child_node, context)
  end
  sequencing = TmSequencing.new(mapped, node)
  if sequencing.terms.size == 1
    sequencing.terms.first
  else
    sequencing
  end
end
parse_block(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 213
def parse_block(node, context)
  if node.children[0].type == :send && node.children[0].children[1] == :lambda
    parse_lambda(node, context)
  elsif node.children[0].type == :send && node.children[0].children[0] && node.children[0].children[0].children[1] == :Proc
    parse_proc(node, context)
  else
    parse_send_block(node, context)
  end
end
parse_block_pass(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 223
def parse_block_pass(node, context)
  passed = if node.children.first.type == :sym
             symbol = node.children.first.children.first
             map(ast("Proc.new { |obj| obj.#{symbol} }"), context)
           else
             map(node.children.first, context)
           end
  [:block_pass, passed]
end
parse_boolean_asgn(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 535
def parse_boolean_asgn(node, context)
  lhs = node.children.first
  rhs = map(node.children.last, context)
  case lhs.type
  when :ivasgn
    ivar = TmInstanceVar.new(lhs.children.first, lhs)
    TmBooleanOperator.new(:or, ivar, TmInstanceVarAssignment.new(ivar, rhs, node), node)
  when :gvasgn
    gvar = TmGlobalVar.new(lhs.children.first, lhs)
    TmBooleanOperator.new(:or, gvar, TmGlobalVarAssignment.new(gvar, rhs, node), node)
  when :lvasgn
    TmLocalVarAsgn.new(lhs.children.first.to_s, rhs, node)
  when :send
    receiver = map(lhs.children.first, context)
    message = lhs.children.last
    attr_reader = build_send_message(receiver, message, [], lhs, context)
    attr_writer = build_send_message(receiver, "#{message}=", [rhs], lhs, context)
    TmBooleanOperator.new(:or, attr_reader, attr_writer, node)
  end
end
parse_boolean_operation(operation, node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 528
def parse_boolean_operation(operation, node, context)
  TmBooleanOperator.new(operation,
                        map(node.children.first, context),
                        map(node.children.last, context),
                        node)
end
parse_break(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 514
def parse_break(node, context)
  elements = node.children.map { |element| map(element, context) }
  TmBreak.new(elements, node)
end
parse_case_when(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 424
def parse_case_when(node, context)
  case_statement = map(node.children.first, context)
  when_statements = node.children.drop(1).compact.select { |statement| statement.type == :when }
  default_statement = node.children.drop(1).compact.reverse.find { |statement| statement.type != :when }
  when_statements = when_statements.map do |statement|
    [
      statement,
      map(statement.children[0], context),
      map(statement.children[1], context)
    ]
  end
  default_statement = map(default_statement, context) if default_statement
  TmCaseWhen.new(node, case_statement, when_statements, default_statement)
end
parse_class(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 348
def parse_class(node, context)
  fail Types::TypeParsingError.new('Nil value parsing class') if node.nil? # No explicit class -> Object by default
  class_name = parse_const(node.children[0])
  super_class_name = parse_const(node.children[1]) || 'Object'
  context.with_type([:class, class_name, super_class_name]) do
    class_body = map(node.children[2], context) if node.children[2]
    TmClass.new(context.path_name, super_class_name, class_body, node)
  end
end
parse_const(const_node, accum = []) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 370
def parse_const(const_node, accum = [])
  return nil if const_node.nil?
  accum << const_node.children.last
  if const_node.children.first.nil?
    accum.reverse.join('::')
  else
    parse_const(const_node.children.first, accum)
  end
end
parse_def(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 380
def parse_def(node, context)
  fun_name, args, body = node.children
  owner = :self if context.singleton_class?
  parse_fun(owner, fun_name, args, body, node, context)
end
parse_defs(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 386
def parse_defs(node, context)
  owner, fun_name, args, body = node.children
  parse_fun(owner, fun_name, args, body, node, context)
end
parse_for(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 446
def parse_for(node, context)
  lhs, rhs, body = node.children
  lhs_chilren = []
  lhs.children.each do |child|
    lhs_chilren << child
  end
  lhs_chilren << RewrittenWrapper.new(TmSend.new(TmSend.new(map(rhs, context),:each, [], node), :next, [], node))

  lhs_wrapped = AssignationWrapper.new(lhs, lhs_chilren)
  TmFor.new(map(lhs_wrapped, context), map(body, context), node)
end
parse_fun(owner, fun_name, args, body, node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 391
def parse_fun(owner, fun_name, args, body, node, context)
  if args.type != :args
    fail Types::TypeParsingError.new("Error parsing function args [#{args}]", node)
  end
  # parse the owner of the function
  owner = if owner.nil? || owner == :self
            owner
          elsif owner.type == :const
            TmConst.new(parse_const(owner), node)
          elsif owner.type == :self
            owner
          else
            map(owner, context)
          end
  tm_body = if body.nil?
              TmNil.new(node)
            else
              map(body, context)
            end
  parsed_args = parse_args(args.children, context, node)
  TmFun.new(owner, fun_name, parsed_args, tm_body, node)
end
parse_global_var(node, _context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 189
def parse_global_var(node, _context)
  TmGlobalVar.new(node.children.first, node)
end
parse_global_var_assign(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 193
def parse_global_var_assign(node, context)
  gvar = TmGlobalVar.new(node.children.first, node)
  TmGlobalVarAssignment.new(gvar, map(node.children.last, context), node)
end
parse_hash_literal(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 496
def parse_hash_literal(node, context)
  pairs = node.children.map do |pair|
    [map(pair.children.first, context), map(pair.children.last, context)]
  end
  TmHashLiteral.new(pairs, node)
end
parse_if_then_else(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 414
def parse_if_then_else(node, context)
  cond_expr, then_expr, else_expr = node.children
  then_expr_term = then_expr.nil? ? then_expr : map(then_expr, context)
  else_expr_term = else_expr.nil? ? else_expr : map(else_expr, context)
  TmIfElse.new(node,
               map(cond_expr, context),
               then_expr_term,
               else_expr_term)
end
parse_instance_var(node, _context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 180
def parse_instance_var(node, _context)
  TmInstanceVar.new(node.children.first, node)
end
parse_instance_var_assign(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 184
def parse_instance_var_assign(node, context)
  ivar = TmInstanceVar.new(node.children.first, node)
  TmInstanceVarAssignment.new(ivar, map(node.children.last, context), node)
end
parse_lambda(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 240
def parse_lambda(node, context)
  args = node.children[1]
  body = node.children[2]
  if args.type != :args
    fail Types::TypeParsingError.new("Error parsing function args [#{args}]", node)
  end
  args = parse_args(args.children, context, node)
  body = map(body, context)

  # TODO: deal with abs with a provided type, like block passed to typed functions.
  TmAbs.new(args,
            body,
            :lambda, # no type for the lambda so far.
            node)
end
parse_lvasgn(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 272
def parse_lvasgn(node, context)
  lhs, rhs = node.children
  TmLocalVarAsgn.new(lhs.to_s, map(rhs, context), node)
end
parse_mass_assign(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 277
def parse_mass_assign(node, context)
  # each children is a :lvasgn
  lhs = node.children.first.children
  rhs = map(node.children.last, context)
  TmMassAsgn.new(lhs, rhs, node)
end
parse_module(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 340
def parse_module(node, context)
  module_name = parse_const(node.children[0])
  context.with_type([:module, module_name]) do
    module_body = map(node.children[1], context) if node.children[1]
    TmModule.new(context.path_name, module_body, node)
  end
end
parse_next(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 519
def parse_next(node, context)
  elements = node.children.map { |element| map(element, context) }
  TmNext.new(elements, node)
end
parse_op_asgn(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 556
def parse_op_asgn(node, context)
  lvalue, message, arg_expr  = node.children
  lreceiver = lvalue.children.first
  arg = map(arg_expr, context)
  rvalue =  case lvalue.type
            when :lvasgn
              TmSend.new(TmVar.new(lreceiver, lvalue),
                         message, [arg], node)
            when :ivasgn
              TmSend.new(TmInstanceVar.new(lreceiver, lvalue),
                         message, [arg], node)
            when :gvasgn
              TmSend.new(TmGlobalVar.new(lreceiver, lvalue),
                         message, [arg], node)
            when :casgn
              constant_name = parse_const(lvalue)
              return TmSend.new(TmConst.new(constant_name, lvalue), message, [arg], node)
            when :send
              return TmSend.new(map(lvalue, context), message, [arg], node)
            else
              fail Types::TypeParsingError.new("Unknown += operator application for node '#{lvalue.type}'", node)
            end

  case lvalue.type
  when :lvasgn
    TmLocalVarAsgn.new(lvalue, rvalue, node)
  when :ivasgn
    TmInstanceVarAssignment.new(TmInstanceVar.new(lreceiver, lvalue), rvalue, node)
  when :gvasgn
    TmGlobalVarAssignment.new(TmGlobalVar.new(lreceiver, lvalue), rvalue, node)
  end
end
parse_proc(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 256
def parse_proc(node, context)
  args = node.children[1]
  body = node.children[2]
  if args.type != :args
    fail Types::TypeParsingError.new("Error parsing function args [#{args}]", node)
  end
  args = parse_args(args.children, context, node)
  body = map(body, context)

  # TODO: deal with abs with a provided type, like block passed to typed functions.
  TmAbs.new(args,
            body,
            :proc, # no type for the lambda so far.
            node)
end
parse_range(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 503
def parse_range(node, context)
  start_range = map(node.children.first, context)
  end_range = map(node.children.last, context)
  TmRangeLiteral.new(start_range, end_range, node)
end
parse_regexp(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 208
def parse_regexp(node, context)
  # ignore the regular expression options
  TmRegexp.new(map(node.children[0], context), nil, node)
end
parse_rescue(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 476
def parse_rescue(node, context)
  catched_types = if node.children[0].nil?
                    []
                  else
                    node.children[0].children.map do |node|
                      map(node, context)
                    end
                  end
  assigned_exception = node.children[1].nil? ? nil : node.children[1].children[0]

  rescue_body = node.children[2].nil? ? nil : map(node.children[2], context)
  TmRescue.new(catched_types, assigned_exception, rescue_body)
end
parse_return(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 509
def parse_return(node, context)
  elements = node.children.map { |element| map(element, context) }
  TmReturn.new(elements, node)
end
parse_sclass(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 358
def parse_sclass(node, context)
  class_name = if node.children[0].type == :self
                 :self
               else
                 parse_const(node.children[0])
               end
  context.with_type([:self, class_name]) do
    class_body = map(node.children[1], context)
    TmSClass.new(class_name, class_body, node)
  end
end
parse_self(node, _context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 524
def parse_self(node, _context)
  TmSelf.new(node)
end
parse_send(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 303
def parse_send(node, context)
  children = node.children
  message = children[1]
  if message == :ts_ignore
    @ignore_node = true
    return
  end
  receiver_node = children[0]
  receiver = receiver_node.nil? ? receiver_node : map(receiver_node, context)
  args = (children.drop(2) || []).map { |arg| map(arg, context) }
  build_send_message(receiver, message, args, node, context)
end
parse_send_block(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 233
def parse_send_block(node, context)
  block = parse_lambda(node, context)
  send = parse_send(node.children[0], context)
  send.with_block(block)
  send
end
parse_string_interpolation(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 198
def parse_string_interpolation(node, context)
  units = node.children.map { |child| map(child, context) }
  TmStringInterpolation.new(units, node)
end
parse_super_with_args(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 175
def parse_super_with_args(node, context)
  args = node.children.map { |arg_node| map(arg_node, context) }
  TmSuper.new(args, node)
end
parse_symbol_interpolation(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 203
def parse_symbol_interpolation(node, context)
  units = node.children.map { |child| map(child, context) }
  TmSymbolInterpolation.new(units, node)
end
parse_try(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 470
def parse_try(node, context)
  try_term = map(node.children.first, context)
  rescue_terms = node.children.drop(1).compact.map { |term| map(term, context) }
  TmTry.new(try_term, rescue_terms, node)
end
parse_while(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 439
def parse_while(node, context)
  condition, body = node.children
  condition_expr = map(condition, context)
  body_expr = body ? map(body, context) : nil
  TmWhile.new(condition_expr, body_expr, node)
end
parse_yield(node, context) click to toggle source
# File lib/typed/runtime/ast_parser.rb, line 335
def parse_yield(node, context)
  args = node.children
  TmSend.new(nil, :yield, args.map { |arg| map(arg, context) }, node)
end