# frozen_string_literal: true
module RBS
class DefinitionBuilder
attr_reader :env
attr_reader :ancestor_builder
attr_reader :method_builder
attr_reader :instance_cache
attr_reader :singleton_cache
attr_reader :singleton0_cache
attr_reader :interface_cache
def initialize(env:, ancestor_builder: nil, method_builder: nil)
@env = env
@ancestor_builder = ancestor_builder || AncestorBuilder.new(env: env)
@method_builder = method_builder || MethodBuilder.new(env: env)
@instance_cache = {}
@singleton_cache = {}
@singleton0_cache = {}
@interface_cache = {}
end
def ensure_namespace!(namespace, location:)
namespace.ascend do |ns|
unless ns.empty?
NoTypeFoundError.check!(ns.to_type_name, env: env, location: location)
end
end
end
def define_interface(definition, type_name, subst)
included_interfaces = ancestor_builder.interface_ancestors(type_name).ancestors #: Array[Definition::Ancestor::Instance]
included_interfaces = included_interfaces.reject {|ancestor| ancestor.source == nil }
interface_methods = interface_methods(included_interfaces)
methods = method_builder.build_interface(type_name)
import_methods(definition, type_name, methods, interface_methods, subst, nil)
end
def build_interface(type_name)
try_cache(type_name, cache: interface_cache) do
entry = env.interface_decls[type_name] or raise "Unknown name for build_interface: #{type_name}"
declaration = entry.decl
ensure_namespace!(type_name.namespace, location: declaration.location)
type_params = declaration.type_params.each.map(&:name)
type_args = Types::Variable.build(type_params)
self_type = Types::Interface.new(name: type_name, args: type_args, location: nil)
subst = Substitution.build(type_params, type_args)
ancestors = ancestor_builder.interface_ancestors(type_name)
Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
methods = method_builder.build_interface(type_name)
one_ancestors = ancestor_builder.one_interface_ancestors(type_name)
validate_type_params(definition, methods: methods, ancestors: one_ancestors)
define_interface(definition, type_name, subst)
end
end
end
def tapp_subst(name, args)
params =
case
when name.interface?
entry = env.interface_decls[name] or raise "Unknown interface name: #{name}"
entry.decl.type_params
when name.alias?
entry = env.type_alias_decls[name] or raise "Unknown alias name: #{name}"
entry.decl.type_params
when name.class?
entry = env.class_decls[name] or raise "Unknown module name: #{name}"
entry.type_params
else
raise
end
AST::TypeParam.application(params, args) || Substitution.new()
end
def define_instance(definition, type_name, subst, define_class_vars:)
one_ancestors = ancestor_builder.one_instance_ancestors(type_name)
methods = method_builder.build_instance(type_name)
self_type_methods = one_ancestors.each_self_type.with_object({}) do |self_type, hash| #$ Hash[Symbol, Definition::Method]
self_type.args.each do |arg|
validate_type_presence(arg)
end
self_type_defn = self_type.name.interface? ? build_interface(self_type.name) : build_instance(self_type.name)
s = subst + tapp_subst(self_type.name, self_type.args)
self_type_defn.methods.each do |method_name, method_def|
hash[method_name] = method_def.sub(s)
end
end
one_ancestors.each_included_module do |mod|
mod.args.each do |arg|
validate_type_presence(arg)
end
define_instance(definition, mod.name, subst + tapp_subst(mod.name, mod.args), define_class_vars: define_class_vars)
end
all_interfaces = one_ancestors.each_included_interface.flat_map do |interface|
other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
[interface, *other_interfaces]
end
interface_methods = interface_methods(all_interfaces)
import_methods(definition, type_name, methods, interface_methods, subst, self_type_methods)
one_ancestors.each_prepended_module do |mod|
mod.args.each do |arg|
validate_type_presence(arg)
end
define_instance(definition, mod.name, subst + tapp_subst(mod.name, mod.args), define_class_vars: define_class_vars)
end
entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}"
args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
entry.decls.each do |d|
subst_ = subst + Substitution.build(d.decl.type_params.each.map(&:name), args)
d.decl.members.each do |member|
case member
when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter
if member.kind == :instance
ivar_name = case member.ivar_name
when false
nil
else
member.ivar_name || :"@#{member.name}"
end
if ivar_name
insert_variable(
type_name,
definition.instance_variables,
name: ivar_name,
type: member.type.sub(subst_),
source: member
)
end
end
when AST::Members::InstanceVariable
insert_variable(
type_name,
definition.instance_variables,
name: member.name,
type: member.type.sub(subst_),
source: member
)
when AST::Members::ClassVariable
if define_class_vars
insert_variable(type_name, definition.class_variables, name: member.name, type: member.type, source: member)
end
end
end
end
end
def build_instance(type_name)
type_name = env.normalize_module_name(type_name)
try_cache(type_name, cache: instance_cache) do
entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}"
ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location)
ancestors = ancestor_builder.instance_ancestors(type_name)
args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
self_type = Types::ClassInstance.new(name: type_name, args: args, location: nil)
Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
one_ancestors = ancestor_builder.one_instance_ancestors(type_name)
methods = method_builder.build_instance(type_name)
validate_type_params definition, methods: methods, ancestors: one_ancestors
if entry.is_a?(Environment::ClassEntry)
if super_class = one_ancestors.super_class
super_class.is_a?(Definition::Ancestor::Instance) or raise
build_instance(super_class.name).tap do |defn|
unless super_class.args.empty?
super_class.args.each do |arg|
validate_type_presence(arg)
end
subst = tapp_subst(super_class.name, super_class.args)
defn = defn.sub(subst)
end
definition.methods.merge!(defn.methods)
definition.instance_variables.merge!(defn.instance_variables)
definition.class_variables.merge!(defn.class_variables)
end
end
end
if entry.is_a?(Environment::ModuleEntry)
if self_types = one_ancestors.self_types
self_types.each do |ans|
ans.args.each do |arg|
validate_type_presence(arg)
end
subst = tapp_subst(ans.name, ans.args)
if ans.name.interface?
define_interface(definition, ans.name, subst)
else
define_instance(definition, ans.name, subst, define_class_vars: true)
end
end
end
end
define_instance(definition, type_name, Substitution.new, define_class_vars: true)
end
end
end
# Builds a definition for singleton without .new method.
#
def build_singleton0(type_name)
try_cache type_name, cache: singleton0_cache do
entry = env.class_decls[type_name] or raise "Unknown name for build_singleton0: #{type_name}"
ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location)
ancestors = ancestor_builder.singleton_ancestors(type_name)
self_type = Types::ClassSingleton.new(name: type_name, location: nil)
Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
one_ancestors = ancestor_builder.one_singleton_ancestors(type_name)
methods = method_builder.build_singleton(type_name)
if super_class = one_ancestors.super_class
case super_class
when Definition::Ancestor::Instance
defn = build_instance(super_class.name)
when Definition::Ancestor::Singleton
defn = build_singleton0(super_class.name)
end
definition.methods.merge!(defn.methods)
definition.instance_variables.merge!(defn.instance_variables)
end
one_ancestors.each_extended_module do |mod|
mod.args.each do |arg|
validate_type_presence(arg)
end
subst = tapp_subst(mod.name, mod.args)
define_instance(definition, mod.name, subst, define_class_vars: false)
end
all_interfaces = one_ancestors.each_extended_interface.flat_map do |interface|
other_interfaces = ancestor_builder.interface_ancestors(interface.name).ancestors #: Array[Definition::Ancestor::Instance]
other_interfaces = other_interfaces.select {|ancestor| ancestor.source }
[interface, *other_interfaces]
end
interface_methods = interface_methods(all_interfaces)
import_methods(definition, type_name, methods, interface_methods, Substitution.new, nil)
entry.decls.each do |d|
d.decl.members.each do |member|
case member
when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter
if member.kind == :singleton
ivar_name = case member.ivar_name
when false
nil
else
member.ivar_name || :"@#{member.name}"
end
if ivar_name
insert_variable(type_name, definition.instance_variables, name: ivar_name, type: member.type, source: member)
end
end
when AST::Members::ClassInstanceVariable
insert_variable(type_name, definition.instance_variables, name: member.name, type: member.type, source: member)
end
end
end
instance_definition = build_instance(type_name)
definition.class_variables.replace(instance_definition.class_variables)
end
end
end
def build_singleton(type_name)
type_name = env.normalize_module_name(type_name)
try_cache type_name, cache: singleton_cache do
entry = env.class_decls[type_name] or raise "Unknown name for build_singleton: #{type_name}"
ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location)
ancestors = ancestor_builder.singleton_ancestors(type_name)
self_type = Types::ClassSingleton.new(name: type_name, location: nil)
Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition|
definition0 = build_singleton0(type_name)
definition.methods.merge!(definition0.methods)
definition.instance_variables.merge!(definition0.instance_variables)
definition.class_variables.merge!(definition0.class_variables)
if entry.is_a?(Environment::ClassEntry)
new_method = definition.methods[:new]
if new_method.defs.all? {|d| d.defined_in == BuiltinNames::Class.name }
# The method is _untyped new_.
alias_methods = definition.methods.each.with_object([]) do |entry, array|
# @type var method: Definition::Method?
name, method = entry
while method
if method.alias_of == new_method
array << name
break
end
method = method.alias_of
end
end
instance = build_instance(type_name)
initialize = instance.methods[:initialize]
if initialize
class_params = entry.type_params
# Inject a virtual _typed new_.
initialize_defs = initialize.defs
typed_new = Definition::Method.new(
super_method: new_method,
defs: initialize_defs.map do |initialize_def|
method_type = initialize_def.type
class_type_param_vars = Set.new(class_params.map(&:name))
method_type_param_vars = Set.new(method_type.type_params.map(&:name))
if class_type_param_vars.intersect?(method_type_param_vars)
new_method_param_names = method_type.type_params.map do |method_param|
if class_type_param_vars.include?(method_param.name)
Types::Variable.fresh(method_param.name).name
else
method_param.name
end
end
sub = Substitution.build(
method_type.type_params.map(&:name),
Types::Variable.build(new_method_param_names)
)
method_params = class_params + AST::TypeParam.rename(method_type.type_params, new_names: new_method_param_names)
method_type = method_type
.update(type_params: [])
.sub(sub)
.update(type_params: method_params)
else
method_type = method_type
.update(type_params: class_params + method_type.type_params)
end
method_type = method_type.update(
type: method_type.type.with_return_type(
Types::ClassInstance.new(
name: type_name,
args: entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) },
location: nil
)
)
)
Definition::Method::TypeDef.new(
type: method_type,
member: initialize_def.member,
defined_in: initialize_def.defined_in,
implemented_in: initialize_def.implemented_in
).tap do |type_def|
type_def.overload_annotations.replace(initialize_def.overload_annotations)
end
end,
accessibility: :public,
alias_of: nil,
alias_member: nil
)
definition.methods[:new] = typed_new
alias_methods.each do |alias_name|
definition.methods[alias_name] = definition.methods[alias_name].update(
alias_of: typed_new,
defs: typed_new.defs
)
end
end
end
end
end
end
end
def interface_methods(interface_ancestors)
interface_methods = {} #: interface_methods
interface_ancestors.each do |mod|
source =
case mod.source
when AST::Members::Include, AST::Members::Extend
mod.source
else
raise "Interface mixin must be include/extend: #{mod.source.inspect}"
end
methods = method_builder.build_interface(mod.name)
interface_methods[mod] = [methods, source]
end
interface_methods
end
def validate_params_with(type_params, result:)
type_params.each do |param|
unless param.unchecked?
unless result.compatible?(param.name, with_annotation: param.variance)
yield param
end
end
end
end
def source_location(source, decl)
case source
when nil
decl.location
when :super
case decl
when AST::Declarations::Class
decl.super_class&.location
end
else
source.location
end
end
def validate_type_params(definition, ancestors:, methods:)
type_params = definition.type_params_decl
calculator = VarianceCalculator.new(builder: self)
param_names = type_params.each.map(&:name)
ancestors.each_ancestor do |ancestor|
case ancestor
when Definition::Ancestor::Instance
result = calculator.in_inherit(name: ancestor.name, args: ancestor.args, variables: param_names)
validate_params_with(type_params, result: result) do |param|
decl = case entry = definition.entry
when Environment::ModuleEntry, Environment::ClassEntry
entry.primary.decl
when Environment::SingleEntry
entry.decl
end
raise InvalidVarianceAnnotationError.new(
type_name: definition.type_name,
param: param,
location: source_location(ancestor.source, decl)
)
end
end
end
methods.each do |defn|
next if defn.name == :initialize
method_types = case original = defn.original
when AST::Members::MethodDefinition
original.overloads.map(&:method_type)
when AST::Members::AttrWriter, AST::Members::AttrReader, AST::Members::AttrAccessor
if defn.name.to_s.end_with?("=")
[
MethodType.new(
type_params: [],
type: Types::Function.empty(original.type).update(
required_positionals: [
Types::Function::Param.new(type: original.type, name: original.name)
]
),
block: nil,
location: original.location
)
]
else
[
MethodType.new(
type_params: [],
type: Types::Function.empty(original.type),
block: nil,
location: original.location
)
]
end
when AST::Members::Alias
nil
when nil
nil
end
if method_types
method_types.each do |method_type|
merged_params = type_params
.reject {|param| method_type.type_param_names.include?(param.name) }
.concat(method_type.type_params)
result = calculator.in_method_type(method_type: method_type, variables: param_names)
validate_params_with(merged_params, result: result) do |param|
raise InvalidVarianceAnnotationError.new(
type_name: definition.type_name,
param: param,
location: method_type.location
)
end
end
end
end
end
def insert_variable(type_name, variables, name:, type:, source:)
variables[name] = Definition::Variable.new(
parent_variable: variables[name],
type: type,
declared_in: type_name,
source: source
)
validate_variable(variables[name])
end
def validate_variable(var)
return unless var.parent_variable
# Ignore attrs
variables = [] #: Array[Definition::Variable]
tmp_var = var
while tmp_var
variables << tmp_var if tmp_var.source.is_a?(AST::Members::Var)
tmp_var = tmp_var.parent_variable
end
# Duplicates should be eliminated, so there can't be more than 3.
return unless variables.length == 2
l, r = variables #: [Definition::Variable, Definition::Variable]
case l.source
when AST::Members::InstanceVariable
if r.source.instance_of?(AST::Members::InstanceVariable) && l.declared_in == r.declared_in
raise InstanceVariableDuplicationError.new(type_name: l.declared_in, variable_name: l.source.name, location: l.source.location)
end
when AST::Members::ClassInstanceVariable
if r.source.instance_of?(AST::Members::ClassInstanceVariable) && l.declared_in == r.declared_in
raise ClassInstanceVariableDuplicationError.new(type_name: l.declared_in, variable_name: l.source.name, location: l.source.location)
end
end
end
def import_methods(definition, module_name, module_methods, interfaces_methods, subst, self_type_methods)
new_methods = {} #: Hash[Symbol, Definition::Method]
interface_method_duplicates = Set[] #: Set[Symbol]
interfaces_methods.each do |interface, (methods, member)|
unless interface.args.empty?
methods.type.is_a?(Types::Interface) or raise
interface.args.each do |arg|
validate_type_presence(arg)
end
type_params = env.interface_decls.fetch(interface.name).decl.type_params
if s = AST::TypeParam.application(type_params, interface.args)
subst_ = subst + s
else
subst_ = subst
end
else
subst_ = subst
end
methods.each do |method|
if interface_method_duplicates.include?(method.name)
member.is_a?(AST::Members::Include) || member.is_a?(AST::Members::Extend) or raise
raise DuplicatedInterfaceMethodDefinitionError.new(
type: definition.self_type,
method_name: method.name,
member: member
)
end
interface_method_duplicates << method.name
define_method(
new_methods,
definition,
method,
subst_,
nil,
defined_in: interface.name,
implemented_in: module_name
)
end
end
module_methods.each do |method|
define_method(
new_methods,
definition,
method,
subst,
self_type_methods,
defined_in: module_name,
implemented_in: module_name.interface? ? nil : module_name
)
end
definition.methods.merge!(new_methods)
end
def define_method(methods, definition, method, subst, self_type_methods, defined_in:, implemented_in: defined_in)
existing_method = methods[method.name] || definition.methods[method.name]
case original = method.original
when AST::Members::Alias
original_method = methods[original.old_name] || definition.methods[original.old_name] || self_type_methods&.fetch(original.old_name, nil)
unless original_method
raise UnknownMethodAliasError.new(
type_name: definition.type_name,
original_name: original.old_name,
aliased_name: original.new_name,
location: original.location
)
end
method_definition = Definition::Method.new(
super_method: existing_method,
defs: original_method.defs.map do |defn|
defn.update(defined_in: defined_in, implemented_in: implemented_in)
end,
accessibility: original_method.accessibility,
alias_of: original_method,
alias_member: original
)
method_definition.annotations.replace(original.annotations)
when AST::Members::MethodDefinition
if duplicated_method = methods[method.name]
raise DuplicatedMethodDefinitionError.new(
type: definition.self_type,
method_name: method.name,
members: [original, *duplicated_method.members]
)
end
defs = original.overloads.map do |overload|
Definition::Method::TypeDef.new(
type: subst.empty? ? overload.method_type : overload.method_type.sub(subst),
member: original,
defined_in: defined_in,
implemented_in: implemented_in
).tap do |type_def|
# Keep the original annotations given to overloads.
type_def.overload_annotations.replace(overload.annotations)
end
end
# @type var accessibility: RBS::Definition::accessibility
accessibility =
if original.instance? && [:initialize, :initialize_copy, :initialize_clone, :initialize_dup, :respond_to_missing?].include?(method.name)
:private
else
method.accessibility
end
# Skip setting up `super_method` if `implemented_in` is `nil`, that means the type doesn't have implementation.
# This typically happens if the type is an interface.
if implemented_in
super_method = existing_method
end
method_definition = Definition::Method.new(
super_method: super_method,
defs: defs,
accessibility: accessibility,
alias_of: nil,
alias_member: nil
)
method_definition.annotations.replace(original.annotations)
when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
if duplicated_method = methods[method.name]
raise DuplicatedMethodDefinitionError.new(
type: definition.self_type,
method_name: method.name,
members: [*duplicated_method.members, original]
)
end
attr_type = original.type.sub(subst)
method_type =
if method.name.to_s.end_with?("=")
# setter
MethodType.new(
type_params: [],
type: Types::Function.empty(attr_type).update(
required_positionals: [
Types::Function::Param.new(type: attr_type, name: original.name)
]
),
block: nil,
location: original.location
)
else
# getter
MethodType.new(
type_params: [],
type: Types::Function.empty(attr_type),
block: nil,
location: original.location
)
end
if implemented_in
super_method = existing_method
end
method_definition = Definition::Method.new(
super_method: super_method,
defs: [
Definition::Method::TypeDef.new(
type: method_type,
member: original,
defined_in: defined_in,
implemented_in: implemented_in
)
],
accessibility: method.accessibility,
alias_of: nil,
alias_member: nil
)
method_definition.annotations.replace(original.annotations)
when nil
# Overloading method definition only
case
when methods.key?(method.name)
# The method is defined in an interface
super_method = methods[method.name].super_method
when definition.methods.key?(method.name)
# The method is defined in the super class
super_method = existing_method
else
# Cannot find any non-overloading method
raise InvalidOverloadMethodError.new(
type_name: definition.type_name,
method_name: method.name,
kind: :instance,
members: method.overloads
)
end
method_definition = Definition::Method.new(
super_method: super_method,
defs: existing_method.defs.map do |defn|
defn.update(implemented_in: implemented_in)
end,
accessibility: existing_method.accessibility,
alias_of: existing_method.alias_of,
alias_member: nil
)
method_definition.annotations.replace(existing_method.annotations)
end
method.overloads.each do |overloading_def|
overloading_def.overloads.reverse_each do |overload|
type_def = Definition::Method::TypeDef.new(
type: subst.empty? ? overload.method_type : overload.method_type.sub(subst),
member: overloading_def,
defined_in: defined_in,
implemented_in: implemented_in
)
type_def.overload_annotations.replace(overload.annotations)
method_definition.defs.unshift(type_def)
end
method_definition.annotations.concat(overloading_def.annotations)
end
method_definition.defs.each do |type_def|
type_def.member_annotations.replace(method_definition.annotations)
end
methods[method.name] = method_definition
end
def try_cache(type_name, cache:)
cache[type_name] ||= yield
end
def expand_alias(type_name)
expand_alias2(type_name, [])
end
def expand_alias1(type_name)
type_name = env.normalize_type_name(type_name)
entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"
as = entry.decl.type_params.each.map { Types::Bases::Any.new(location: nil) }
expand_alias2(type_name, as)
end
def expand_alias2(type_name, args)
type_name = env.normalize_type_name(type_name)
entry = env.type_alias_decls[type_name] or raise "Unknown alias name: #{type_name}"
ensure_namespace!(type_name.namespace, location: entry.decl.location)
params = entry.decl.type_params.each.map(&:name)
unless params.size == args.size
as = "[#{args.join(", ")}]" unless args.empty?
ps = "[#{params.join(", ")}]" unless params.empty?
raise "Invalid type application: type = #{type_name}#{as}, decl = #{type_name}#{ps}"
end
type = entry.decl.type
unless params.empty?
subst = Substitution.build(params, args)
type = type.sub(subst)
end
type
end
def update(env:, except:, ancestor_builder:)
method_builder = self.method_builder.update(env: env, except: except)
DefinitionBuilder.new(env: env, ancestor_builder: ancestor_builder, method_builder: method_builder).tap do |builder|
builder.instance_cache.merge!(instance_cache)
builder.singleton_cache.merge!(singleton_cache)
builder.singleton0_cache.merge!(singleton0_cache)
builder.interface_cache.merge!(interface_cache)
except.each do |name|
builder.instance_cache.delete(name)
builder.singleton_cache.delete(name)
builder.singleton0_cache.delete(name)
builder.interface_cache.delete(name)
end
end
end
def validate_type_presence(type)
case type
when Types::ClassInstance, Types::ClassSingleton, Types::Interface, Types::Alias
validate_type_name(type.name, type.location)
end
type.each_type do |type|
validate_type_presence(type)
end
end
def validate_type_name(name, location)
name = name.absolute! unless name.absolute?
return if env.type_name?(env.normalize_type_name(name))
raise NoTypeFoundError.new(type_name: name, location: location)
end
end
end