123456789_123456789_123456789_123456789_123456789_

Class: TypeProf::RBS2JSON

Relationships & Source Files
Inherits: Object
Defined in: lib/typeprof/import.rb

Constant Summary

Class Method Summary

Instance Method Summary

Constructor Details

.new(all_env, cur_env) ⇒ RBS2JSON

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 128

def initialize(all_env, cur_env)
  @all_env, @cur_env = all_env, cur_env
  @alias_resolution_stack = {}
end

Instance Method Details

#attr_method_def(kind, name, ty, visibility)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 431

def attr_method_def(kind, name, ty, visibility)
  {
    kind: kind,
    ivar: name,
    ty: ty,
    visibility: visibility,
  }
end

#attr_rbs_source(member)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 440

def attr_rbs_source(member)
  [
    member.name.to_s,
    member.type.location.source,
    [member.location.name, CodeRange.from_rbs(member.location)],
  ]
end

#conv_block(rbs_block)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 448

def conv_block(rbs_block)
  blk = rbs_block.type

  lead_tys = blk.required_positionals.map {|type| conv_type(type.type) }
  opt_tys = blk.optional_positionals.map {|type| conv_type(type.type) }
  rest_ty = blk.rest_positionals
  rest_ty = conv_type(rest_ty.type) if rest_ty
  opt_kw_tys = blk.optional_keywords.to_h {|key, type| [key, conv_type(type.type)] }
  req_kw_tys = blk.required_keywords.to_h {|key, type| [key, conv_type(type.type)] }
  rest_kw_ty = blk.rest_keywords
  rest_kw_ty = conv_type(rest_kw_ty.type) if rest_kw_ty

  ret_ty = conv_type(blk.return_type)

  {
    required_block: rbs_block.required,
    lead_tys: lead_tys,
    opt_tys: opt_tys,
    rest_ty: rest_ty,
    req_kw_tys: req_kw_tys,
    opt_kw_tys: opt_kw_tys,
    rest_kw_ty: rest_kw_ty,
    blk: blk,
    ret_ty: ret_ty,
  }
end

#conv_classes

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 168

def conv_classes
  json = {}

  each_class_decl do |name, decls|
    klass = conv_type_name(name)
    super_class_name, super_class_args = get_super_class(name, decls)
    if super_class_name
      name = conv_type_name(super_class_name)
      type_args = super_class_args.map {|type| conv_type(type) }
      superclass = [name, type_args]
    end

    type_params = nil
    modules = { include: [], extend: [], prepend: [] }
    methods = {}
    attr_methods = {}
    ivars = {}
    cvars = {}
    rbs_sources = {}
    visibility = true

    decls.each do |decl|
      decl = decl.decl

      type_params2 = decl.type_params
      # A hack to deal with the imcompatibility between rbs 1.8 and 2.0
      type_params2 = type_params2.params if type_params2.respond_to?(:params)
      type_params2 = type_params2.map {|param| [param.name, param.variance] }
      raise "inconsistent type parameter declaration" if type_params && type_params != type_params2
      type_params = type_params2

      decl.members.each do |member|
        case member
        when RBS::AST::Members::MethodDefinition
          name = member.name

          if member.respond_to?(:overloads)
            types = member.overloads.map {|overload| overload.method_type }
          else
            types = member.types
          end
          method_types = types.map do |method_type|
            case method_type
            when RBS::MethodType then method_type
            when :super then raise NotImplementedError
            end
          end

          method_def = conv_method_def(method_types, visibility)
          rbs_source = [
            (member.kind == :singleton ? "self." : "") + member.name.to_s,
            types.map {|type| type.location.source },
            [member.location.name, CodeRange.from_rbs(member.location)],
          ]
          if member.instance?
            methods[[false, name]] = method_def
            rbs_sources[[false, name]] = rbs_source
          end
          if member.singleton?
            methods[[true, name]] = method_def
            rbs_sources[[true, name]] = rbs_source
          end
        when RBS::AST::Members::AttrReader
          ty = conv_type(member.type)
          attr_methods[[false, member.name]] = attr_method_def(:reader, member.name, ty, visibility)
          rbs_sources[[false, member.name]] = attr_rbs_source(member)
        when RBS::AST::Members::AttrWriter
          ty = conv_type(member.type)
          attr_methods[[false, member.name]] = attr_method_def(:writer, member.name, ty, visibility)
          rbs_sources[[false, member.name]] = attr_rbs_source(member)
        when RBS::AST::Members::AttrAccessor
          ty = conv_type(member.type)
          attr_methods[[false, member.name]] = attr_method_def(:accessor, member.name, ty, visibility)
          rbs_sources[[false, member.name]] = attr_rbs_source(member)
        when RBS::AST::Members::Alias
          # XXX: an alias to attr methods?
          if member.instance?
            method_def = methods[[false, member.old_name]]
            methods[[false, member.new_name]] = method_def if method_def
          end
          if member.singleton?
            method_def = methods[[true, member.old_name]]
            methods[[true, member.new_name]] = method_def if method_def
          end

        when RBS::AST::Members::Include
          name = member.name
          if name.kind == :class
            # including a module
            mod = conv_type_name(name)
            type_args = member.args.map {|type| conv_type(type) }
            modules[:include] << [mod, type_args]
          else
            # including an interface
            mod = conv_type_name(name)
            type_args = member.args.map {|type| conv_type(type) }
            modules[:include] << [mod, type_args]
          end

        when RBS::AST::Members::Extend
          name = member.name
          if name.kind == :class
            mod = conv_type_name(name)
            type_args = member.args.map {|type| conv_type(type) }
            modules[:extend] << [mod, type_args]
          else
            # extending a module with an interface is not supported yet
          end

        when RBS::AST::Members::Prepend
          name = member.name
          if name.kind == :class
            mod = conv_type_name(name)
            type_args = member.args.map {|type| conv_type(type) }
            modules[:prepend] << [mod, type_args]
          else
            # extending a module with an interface is not supported yet
          end

        when RBS::AST::Members::InstanceVariable
          ivars[member.name] = conv_type(member.type)
        when RBS::AST::Members::ClassVariable
          cvars[member.name] = conv_type(member.type)

        when RBS::AST::Members::Public
          visibility = true
        when RBS::AST::Members::Private
          visibility = false

        # The following declarations are ignoreable because they are handled in other level
        when RBS::AST::Declarations::Constant
        when AliasDecl # type alias
        when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
        when RBS::AST::Declarations::Interface
        when TypeAlias

        else
          warn "Importing #{ member.class.name } is not supported yet"
        end
      end
    end

    json[klass] = {
      type_params: type_params,
      superclass: superclass,
      members: {
        modules: modules,
        methods: methods,
        attr_methods: attr_methods,
        ivars: ivars,
        cvars: cvars,
        rbs_sources: rbs_sources,
      },
    }
  end

  json
end

#conv_constants

constant_name = [Symbol]

{ constant_name => type }

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 144

def conv_constants
  constants = {}
  @cur_env.constant_decls.each do |name, decl|
    klass = conv_type_name(name)
    constants[klass] = conv_type(decl.decl.type)
  end
  constants
end

#conv_func(type_params, func, block)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 404

def conv_func(type_params, func, block)
  blk = block ? conv_block(block) : nil

  lead_tys = func.required_positionals.map {|type| conv_type(type.type) }
  opt_tys = func.optional_positionals.map {|type| conv_type(type.type) }
  rest_ty = func.rest_positionals
  rest_ty = conv_type(rest_ty.type) if rest_ty
  opt_kw_tys = func.optional_keywords.to_h {|key, type| [key, conv_type(type.type)] }
  req_kw_tys = func.required_keywords.to_h {|key, type| [key, conv_type(type.type)] }
  rest_kw_ty = func.rest_keywords
  rest_kw_ty = conv_type(rest_kw_ty.type) if rest_kw_ty

  ret_ty = conv_type(func.return_type)

  {
    type_params: type_params,
    lead_tys: lead_tys,
    opt_tys: opt_tys,
    rest_ty: rest_ty,
    req_kw_tys: req_kw_tys,
    opt_kw_tys: opt_kw_tys,
    rest_kw_ty: rest_kw_ty,
    blk: blk,
    ret_ty: ret_ty,
  }
end

#conv_globals

gvar_name = Symbol (:$gvar)

{ gvar_name => type }

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 156

def conv_globals
  gvars = {}
  @cur_env.global_decls.each do |name, decl|
    decl = decl.decl
    gvars[name] = conv_type(decl.type)
  end
  gvars
end

#conv_method_def(rbs_method_types, visibility)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 394

def conv_method_def(rbs_method_types, visibility)
  sig_rets = rbs_method_types.map do |method_type|
    conv_func(method_type.type_params, method_type.type, method_type.block)
  end
  {
    sig_rets: sig_rets,
    visibility: visibility,
  }
end

#conv_type(ty)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 475

def conv_type(ty)
  case ty
  when RBS::Types::ClassSingleton
    [:class, conv_type_name(ty.name)]
  when RBS::Types::ClassInstance
    klass = conv_type_name(ty.name)
    case klass
    when [:Array]
      raise if ty.args.size != 1
      [:array, [:Array], [], conv_type(ty.args.first)]
    when [:Hash]
      raise if ty.args.size != 2
      key, val = ty.args
      [:hash, [:Hash], [conv_type(key), conv_type(val)]]
    when [:Enumerator]
      raise if ty.args.size != 2
      [:array, [:Enumerator], [], conv_type(ty.args.first)]
    else
      if ty.args.empty?
        [:instance, klass]
      else
        [:cell, [:instance, klass], ty.args.map {|ty| conv_type(ty) }]
      end
    end
  when RBS::Types::Bases::Bool   then [:bool]
  when RBS::Types::Bases::Any    then [:any]
  when RBS::Types::Bases::Top    then [:any]
  when RBS::Types::Bases::Void   then [:void]
  when RBS::Types::Bases::Self   then [:self]
  when RBS::Types::Bases::Nil    then [:nil]
  when RBS::Types::Bases::Bottom then [:union, []]
  when RBS::Types::Variable      then [:var, ty.name]
  when RBS::Types::Tuple
    tys = ty.types.map {|ty2| conv_type(ty2) }
    [:array, [:Array], tys, [:union, []]]
  when RBS::Types::Literal
    case ty.literal
    when Integer then [:int]
    when String  then [:str]
    when true    then [:true]
    when false   then [:false]
    when Symbol  then [:sym, ty.literal]
    else
      p ty.literal
      raise NotImplementedError
    end
  when RBS::Types::Alias
    if @alias_resolution_stack[ty.name]
      [:any]
    else
      begin
        @alias_resolution_stack[ty.name] = true
        alias_decl = (@all_env.respond_to?(:alias_decls) ? @all_env.alias_decls : @all_env.type_alias_decls)[ty.name]
        alias_decl ? conv_type(alias_decl.decl.type) : [:any]
      ensure
        @alias_resolution_stack.delete(ty.name)
      end
    end
  when RBS::Types::Union
    [:union, ty.types.map {|ty2| conv_type(ty2) }.compact]
  when RBS::Types::Intersection
    [:intersection, ty.types.map {|ty2| conv_type(ty2) }.compact]
  when RBS::Types::Optional
    [:optional, conv_type(ty.type)]
  when RBS::Types::Interface
    # XXX: Currently, only a few builtin interfaces are supported
    case ty.to_s
    when "::_ToS" then [:str]
    when "::_ToStr" then [:str]
    when "::_ToInt" then [:int]
    when "::_ToAry[U]" then [:array, [:Array], [], [:var, :U]]
    else
      [:instance, conv_type_name(ty.name)]
    end
  when RBS::Types::Bases::Instance then [:any] # XXX: not implemented yet
  when RBS::Types::Bases::Class then [:any] # XXX: not implemented yet
  when RBS::Types::Record
    [:hash_record, [:Hash], ty.fields.map {|key, ty| [key, conv_type(ty)] }]
  when RBS::Types::Proc
    [:proc, conv_func(nil, ty.type, nil)]
  else
    warn "unknown RBS type: %p" % ty.class
    [:any]
  end
end

#conv_type_name(name)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 561

def conv_type_name(name)
  name.namespace.path + [name.name]
end

#dump_json

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 133

def dump_json
  {
    classes: conv_classes,
    constants: conv_constants,
    globals: conv_globals,
  }
end

#each_class_decl

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 327

def each_class_decl
  # topological sort
  #   * superclasses and modules appear earlier than their subclasses (Object is earlier than String)
  #   * namespace module appers earlier than its children (Process is earlier than Process::Status)
  visited = {}
  queue = @cur_env.class_decls.keys.map {|name| [:visit, name] }.reverse
  until queue.empty?
    event, name = queue.pop
    case event
    when :visit
      if !visited[name]
        visited[name] = true
        queue << [:new, name]
        @all_env.class_decls[name].decls.each do |decl|
          decl = decl.decl
          next if decl.is_a?(RBS::AST::Declarations::Module)
          each_reference(decl) {|name| queue << [:visit, name] }
        end
        queue << [:visit, name.namespace.to_type_name] if !name.namespace.empty?
      end
    when :new
      decls = @cur_env.class_decls[name]
      yield name, decls.decls if decls
    end
  end

  @cur_env.interface_decls.each do |name, decl|
    yield name, [decl]
  end
end

#each_reference(decl) {|decl.name| ... }

Yields:

  • (decl.name)
[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 358

def each_reference(decl, &blk)
  yield decl.name
  if decl.super_class
    name = decl.super_class.name
  else
    name = RBS::BuiltinNames::Object.name
  end
  return if decl.name == RBS::BuiltinNames::BasicObject.name
  return if decl.name == name
  decls = @all_env.class_decls[name]
  if decls
    decls.decls.each do |decl|
      each_reference(decl.decl, &blk)
    end
  end
end

#get_super_class(name, decls)

[ GitHub ]

  
# File 'lib/typeprof/import.rb', line 375

def get_super_class(name, decls)
  return nil if name == RBS::BuiltinNames::BasicObject.name

  decls.each do |decl|
    decl = decl.decl
    case decl
    when RBS::AST::Declarations::Class
      super_class = decl.super_class
      return super_class.name, super_class.args if super_class
    when RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
      return nil
    else
      raise "unknown declaration: %p" % decl.class
    end
  end

  return RBS::BuiltinNames::Object.name, []
end