File: //usr/local/rvm/rubies/default/lib/ruby/gems/3.0.0/gems/typeprof-0.12.0/lib/typeprof/import.rb
require "rbs"
module TypeProf
class RBSReader
def initialize
@repo = RBS::Repository.new
Config.gem_repo_dirs.each do |dir|
@repo.add(Pathname(dir))
end
@env, @builtin_env_json = RBSReader.get_builtin_env
end
@builtin_env = @builtin_env_json = nil
def self.get_builtin_env
unless @builtin_env
@builtin_env = RBS::Environment.new
loader = RBS::EnvironmentLoader.new(repository: @repo)
new_decls = loader.load(env: @builtin_env).map {|decl,| decl }
@builtin_env_json = load_rbs(@builtin_env, new_decls)
end
return @builtin_env.dup, @builtin_env_json
end
def load_builtin
@builtin_env_json
end
def load_library(lib)
loader = RBS::EnvironmentLoader.new(core_root: nil, repository: @repo)
loader.add(library: lib)
case lib
when 'bigdecimal-math'
loader.add(library: 'bigdecimal')
when "yaml"
loader.add(library: "pstore")
loader.add(library: "dbm")
when "logger"
loader.add(library: "monitor")
when "csv"
loader.add(library: "forwardable")
when "prime"
loader.add(library: "singleton")
end
new_decls = loader.load(env: @env).map {|decl,| decl }
RBSReader.load_rbs(@env, new_decls)
end
def load_paths(paths)
loader = RBS::EnvironmentLoader.new(core_root: nil, repository: @repo)
paths.each {|path| loader.add(path: path) }
new_decls = loader.load(env: @env).map {|decl,| decl }
RBSReader.load_rbs(@env, new_decls)
end
def load_rbs_string(name, content)
buffer = RBS::Buffer.new(name: name, content: content)
new_decls = []
RBS::Parser.parse_signature(buffer).each do |decl|
@env << decl
new_decls << decl
end
RBSReader.load_rbs(@env, new_decls)
end
def self.load_rbs(env, new_decls)
all_env = env.resolve_type_names
resolver = RBS::TypeNameResolver.from_env(all_env)
cur_env = RBS::Environment.new
new_decls.each do |decl|
cur_env << env.resolve_declaration(resolver, decl, outer: [], prefix: RBS::Namespace.root)
end
RBS2JSON.new(all_env, cur_env).dump_json
end
end
class RBS2JSON
def initialize(all_env, cur_env)
@all_env, @cur_env = all_env, cur_env
@alias_resolution_stack = {}
end
def dump_json
{
classes: conv_classes,
constants: conv_constants,
globals: conv_globals,
}
end
# constant_name = [Symbol]
#
# { constant_name => type }
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
# gvar_name = Symbol (:$gvar)
#
# { gvar_name => type }
def conv_globals
gvars = {}
@cur_env.global_decls.each do |name, decl|
decl = decl.decl
gvars[name] = conv_type(decl.type)
end
gvars
end
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.params.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
method_types = member.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, member.types.map {|type| type.location.source }]
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)
when RBS::AST::Members::AttrWriter
ty = conv_type(member.type)
attr_methods[[false, member.name]] = attr_method_def(:writer, member.name, ty, visibility)
when RBS::AST::Members::AttrAccessor
ty = conv_type(member.type)
attr_methods[[false, member.name]] = attr_method_def(:accessor, member.name, ty, visibility)
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
mod = conv_type_name(name)
type_args = member.args.map {|type| conv_type(type) }
modules[:include] << [mod, type_args]
else
# including an interface is not supported yet
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 RBS::AST::Declarations::Alias # type alias
when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
when RBS::AST::Declarations::Interface
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
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
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
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
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
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
def attr_method_def(kind, name, ty, visibility)
{
kind: kind,
ivar: name,
ty: ty,
visibility: visibility,
}
end
def conv_block(rbs_block)
type = rbs_block.type
# XXX
raise NotImplementedError unless type.optional_keywords.empty?
raise NotImplementedError unless type.required_keywords.empty?
raise NotImplementedError if type.rest_keywords
req = rbs_block.required
lead_tys = type.required_positionals.map do |type|
conv_type(type.type)
end
opt_tys = type.optional_positionals.map do |type|
conv_type(type.type)
end
ret_ty = conv_type(type.return_type)
[req, lead_tys, opt_tys, ret_ty]
end
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.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::Optional
[:optional, conv_type(ty.type)]
when RBS::Types::Interface
# XXX: Currently, only a few builtin interfaces are supported
case ty.to_s
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::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
def conv_type_name(name)
name.namespace.path + [name.name]
end
end
class Import
def self.import_builtin(scratch)
Import.new(scratch, scratch.rbs_reader.load_builtin).import
end
def self.import_library(scratch, feature)
begin
json = scratch.rbs_reader.load_library(feature)
rescue RBS::EnvironmentLoader::UnknownLibraryError
return nil
rescue RBS::DuplicatedDeclarationError
return true
end
# need cache?
Import.new(scratch, json).import
end
def self.import_rbs_files(scratch, rbs_paths)
rbs_paths = rbs_paths.map {|rbs_path| Pathname(rbs_path) }
Import.new(scratch, scratch.rbs_reader.load_paths(rbs_paths)).import(true)
end
def self.import_rbs_code(scratch, rbs_name, rbs_code)
Import.new(scratch, scratch.rbs_reader.load_rbs_string(rbs_name, rbs_code)).import(true)
end
def initialize(scratch, json)
@scratch = scratch
@json = json
end
def import(explicit = false)
classes = @json[:classes].map do |classpath, cdef|
type_params = cdef[:type_params]
superclass, superclass_type_args = cdef[:superclass]
members = cdef[:members]
name = classpath.last
superclass = path_to_klass(superclass) if superclass
base_klass = path_to_klass(classpath[0..-2])
klass = @scratch.get_constant(base_klass, name)
if klass.is_a?(Type::Any)
klass = @scratch.new_class(base_klass, name, type_params, superclass, nil)
# There builtin classes are needed to interpret RBS declarations
case classpath
when [:NilClass] then Type::Builtin[:nil] = klass
when [:TrueClass] then Type::Builtin[:true] = klass
when [:FalseClass] then Type::Builtin[:false] = klass
when [:Integer] then Type::Builtin[:int] = klass
when [:String] then Type::Builtin[:str] = klass
when [:Symbol] then Type::Builtin[:sym] = klass
when [:Array] then Type::Builtin[:ary] = klass
when [:Hash] then Type::Builtin[:hash] = klass
when [:Proc] then Type::Builtin[:proc] = klass
end
end
[klass, superclass_type_args, members]
end
classes.each do |klass, superclass_type_args, members|
@scratch.add_superclass_type_args!(klass, superclass_type_args&.map {|ty| conv_type(ty) })
modules = members[:modules]
methods = members[:methods]
attr_methods = members[:attr_methods]
ivars = members[:ivars]
cvars = members[:cvars]
rbs_sources = members[:rbs_sources]
modules.each do |kind, mods|
mods.each do |mod, type_args|
type_args = type_args&.map {|ty| conv_type(ty) }
case kind
when :include
@scratch.mix_module(:after, klass, path_to_klass(mod), type_args, false, nil)
when :extend
@scratch.mix_module(:after, klass, path_to_klass(mod), type_args, true, nil)
when :prepend
@scratch.mix_module(:before, klass, path_to_klass(mod), type_args, false, nil)
end
end
end
methods.each do |(singleton, method_name), mdef|
rbs_source = explicit ? rbs_sources[[singleton, method_name]] : nil
mdef = conv_method_def(method_name, mdef, rbs_source)
@scratch.add_method(klass, method_name, singleton, mdef)
end
attr_methods.each do |(singleton, method_name), mdef|
kind = mdef[:kind]
ivar = mdef[:ivar]
ty = conv_type(mdef[:ty]).remove_type_vars
@scratch.add_attr_method(klass, ivar, :"@#{ ivar }", kind, mdef[:visibility], nil)
@scratch.add_ivar_write!(Type::Instance.new(klass), :"@#{ ivar }", ty, nil)
end
ivars.each do |ivar_name, ty|
ty = conv_type(ty).remove_type_vars
@scratch.add_ivar_write!(Type::Instance.new(klass), ivar_name, ty, nil)
end
cvars.each do |ivar_name, ty|
ty = conv_type(ty).remove_type_vars
@scratch.add_cvar_write!(klass, ivar_name, ty, nil)
end
end
@json[:constants].each do |classpath, value|
base_klass = path_to_klass(classpath[0..-2])
value = conv_type(value).remove_type_vars
@scratch.add_constant(base_klass, classpath[-1], value, nil)
end
@json[:globals].each do |name, ty|
ty = conv_type(ty).remove_type_vars
@scratch.add_gvar_write!(name, ty, nil)
end
true
end
def conv_method_def(method_name, mdef, rbs_source)
sig_rets = mdef[:sig_rets].flat_map do |sig_ret|
conv_func(sig_ret)
end
TypedMethodDef.new(sig_rets, rbs_source, mdef[:visibility])
end
def conv_func(sig_ret)
#type_params = sig_ret[:type_params] # XXX
lead_tys = sig_ret[:lead_tys]
opt_tys = sig_ret[:opt_tys]
rest_ty = sig_ret[:rest_ty]
req_kw_tys = sig_ret[:req_kw_tys]
opt_kw_tys = sig_ret[:opt_kw_tys]
rest_kw_ty = sig_ret[:rest_kw_ty]
blk = sig_ret[:blk]
ret_ty = sig_ret[:ret_ty]
lead_tys = lead_tys.map {|ty| conv_type(ty) }
opt_tys = opt_tys.map {|ty| conv_type(ty) }
rest_ty = conv_type(rest_ty) if rest_ty
kw_tys = []
req_kw_tys.each {|key, ty| kw_tys << [true, key, conv_type(ty)] }
opt_kw_tys.each {|key, ty| kw_tys << [false, key, conv_type(ty)] }
kw_rest_ty = conv_type(rest_kw_ty) if rest_kw_ty
blks = conv_block(blk)
ret_ty = conv_type(ret_ty)
blks.map do |blk|
[MethodSignature.new(lead_tys, opt_tys, rest_ty, [], kw_tys, kw_rest_ty, blk), ret_ty]
end
end
def conv_block(blk)
return [Type.nil] unless blk
req, lead_tys, opt_tys, ret_ty = blk
lead_tys = lead_tys.map {|ty| conv_type(ty) }
opt_tys = opt_tys.map {|ty| conv_type(ty) }
msig = MethodSignature.new(lead_tys, opt_tys, nil, [], {}, nil, Type.nil)
ret_ty = conv_type(ret_ty)
ret = [Type::Proc.new(TypedBlock.new(msig, ret_ty), Type::Builtin[:proc])]
ret << Type.nil unless req
ret
end
def conv_type(ty)
case ty.first
when :class then path_to_klass(ty[1])
when :instance then Type::Instance.new(path_to_klass(ty[1]))
when :cell
Type::Cell.new(Type::Cell::Elements.new(ty[2].map {|ty| conv_type(ty) }), conv_type(ty[1]))
when :any then Type.any
when :void then Type::Void.new
when :nil then Type.nil
when :optional then Type.optional(conv_type(ty[1]))
when :bool then Type.bool
when :self then Type::Var.new(:self)
when :int then Type::Instance.new(Type::Builtin[:int])
when :str then Type::Instance.new(Type::Builtin[:str])
when :sym then Type::Symbol.new(ty.last, Type::Instance.new(Type::Builtin[:sym]))
when :true then Type::Instance.new(Type::Builtin[:true])
when :false then Type::Instance.new(Type::Builtin[:false])
when :array
_, path, lead_tys, rest_ty = ty
lead_tys = lead_tys.map {|ty| conv_type(ty) }
rest_ty = conv_type(rest_ty)
base_type = Type::Instance.new(path_to_klass(path))
Type::Array.new(Type::Array::Elements.new(lead_tys, rest_ty), base_type)
when :hash
_, path, (k, v) = ty
Type.gen_hash(Type::Instance.new(path_to_klass(path))) do |h|
k_ty = conv_type(k)
v_ty = conv_type(v)
h[k_ty] = v_ty
end
when :hash_record
_, path, key_tys = ty
Type.gen_hash(Type::Instance.new(path_to_klass(path))) do |h|
key_tys.each do |key, ty|
k_ty = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
v_ty = conv_type(ty)
h[k_ty] = v_ty
end
end
when :union
tys = ty[1]
Type::Union.new(Utils::Set[*tys.map {|ty2| conv_type(ty2) }], nil).normalize # XXX: Array and Hash support
when :var
Type::Var.new(ty[1])
when :proc
msig, ret_ty = conv_func(ty[1]).first # Currently, RBS Proc does not accept a block, so the size should be always one
Type::Proc.new(TypedBlock.new(msig, ret_ty), Type::Instance.new(Type::Builtin[:proc]))
else
pp ty
raise NotImplementedError
end
end
def path_to_klass(path)
klass = Type::Builtin[:obj]
path.each do |name|
klass = @scratch.get_constant(klass, name)
if klass == Type.any
raise TypeProfError.new("A constant `#{ path.join("::") }' is used but not defined in RBS")
end
end
klass
end
end
end