module RBS class DefinitionBuilder attr_reader :env attr_reader :type_name_resolver 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:) @env = env @type_name_resolver = TypeNameResolver.from_env(env) @ancestor_builder = AncestorBuilder.new(env: env) @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 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) self_type = Types::Interface.new( name: type_name, args: Types::Variable.build(declaration.type_params.each.map(&:name)), location: nil ) ancestors = ancestor_builder.interface_ancestors(type_name) Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition| ancestor_builder.one_interface_ancestors(type_name).included_interfaces.each do |mod| defn = build_interface(mod.name) subst = Substitution.build(defn.type_params, mod.args) defn.methods.each do |name, method| definition.methods[name] = method.sub(subst) end end methods = method_builder.build_interface(type_name) methods.each do |defn| method = case defn.original when AST::Members::MethodDefinition defs = defn.original.types.map do |method_type| Definition::Method::TypeDef.new( type: method_type, member: defn.original, defined_in: type_name, implemented_in: nil ) end Definition::Method.new( super_method: nil, defs: defs, accessibility: :public, alias_of: nil ) when AST::Members::Alias unless definition.methods.key?(defn.original.old_name) raise UnknownMethodAliasError.new( original_name: defn.original.old_name, aliased_name: defn.original.new_name, location: defn.original.location ) end original_method = definition.methods[defn.original.old_name] Definition::Method.new( super_method: nil, defs: original_method.defs.map do |defn| defn.update(implemented_in: nil, defined_in: type_name) end, accessibility: :public, alias_of: original_method ) when nil unless definition.methods.key?(defn.name) raise InvalidOverloadMethodError.new( type_name: type_name, method_name: defn.name, kind: :instance, members: defn.overloads ) end definition.methods[defn.name] end defn.overloads.each do |overload| defs = overload.types.map do |method_type| Definition::Method::TypeDef.new( type: method_type, member: overload, defined_in: type_name, implemented_in: nil ) end method.defs.unshift(*defs) end definition.methods[defn.name] = method end end end end def build_instance(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) case entry when Environment::ClassEntry, Environment::ModuleEntry ancestors = ancestor_builder.instance_ancestors(type_name) self_type = Types::ClassInstance.new(name: type_name, args: Types::Variable.build(entry.type_params.each.map(&: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_instance_ancestors(type_name) methods = method_builder.build_instance(type_name) validate_type_params definition, methods: methods, ancestors: one_ancestors if super_class = one_ancestors.super_class defn = build_instance(super_class.name) merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, super_class.args), keep_super: true) end if one_ancestors.self_types one_ancestors.self_types.each do |ans| defn = if ans.name.interface? build_interface(ans.name) else build_instance(ans.name) end # Successor interface method overwrites. merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, ans.args), keep_super: true) end end one_ancestors.included_modules.each do |mod| defn = build_instance(mod.name) merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, mod.args)) end interface_methods = {} one_ancestors.included_interfaces.each do |mod| defn = build_interface(mod.name) subst = Substitution.build(defn.type_params, mod.args) defn.methods.each do |name, method| if interface_methods.key?(name) raise DuplicatedInterfaceMethodDefinitionError.new( type: self_type, method_name: name, member: mod.source ) end merge_method(type_name, interface_methods, name, method, subst, implemented_in: type_name) end end define_methods(definition, interface_methods: interface_methods, methods: methods, super_interface_method: entry.is_a?(Environment::ModuleEntry)) 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 == :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) end end when AST::Members::InstanceVariable insert_variable(type_name, definition.instance_variables, name: member.name, type: member.type) when AST::Members::ClassVariable insert_variable(type_name, definition.class_variables, name: member.name, type: member.type) end end end one_ancestors.prepended_modules.each do |mod| defn = build_instance(mod.name) merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, mod.args)) end end 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) case entry when Environment::ClassEntry, Environment::ModuleEntry 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) if super_class = one_ancestors.super_class case super_class when Definition::Ancestor::Instance defn = build_instance(super_class.name) merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, super_class.args), keep_super: true) when Definition::Ancestor::Singleton defn = build_singleton0(super_class.name) merge_definition(src: defn, dest: definition, subst: Substitution.new, keep_super: true) end end one_ancestors.extended_modules.each do |mod| defn = build_instance(mod.name) merge_definition(src: defn, dest: definition, subst: Substitution.build(defn.type_params, mod.args)) end interface_methods = {} one_ancestors.extended_interfaces.each do |mod| defn = build_interface(mod.name) subst = Substitution.build(defn.type_params, mod.args) defn.methods.each do |name, method| if interface_methods.key?(name) raise DuplicatedInterfaceMethodDefinitionError.new( type: self_type, method_name: name, member: mod.source ) end merge_method(type_name, interface_methods, name, method, subst, implemented_in: type_name) end end methods = method_builder.build_singleton(type_name) define_methods(definition, interface_methods: interface_methods, methods: methods, super_interface_method: false) 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) end end when AST::Members::ClassInstanceVariable insert_variable(type_name, definition.instance_variables, name: member.name, type: member.type) when AST::Members::ClassVariable insert_variable(type_name, definition.class_variables, name: member.name, type: member.type) end end end end end end end def build_singleton(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) case entry when Environment::ClassEntry, Environment::ModuleEntry ancestors = ancestor_builder.singleton_ancestors(type_name) self_type = Types::ClassSingleton.new(name: type_name, location: nil) instance_type = Types::ClassInstance.new( name: type_name, args: entry.type_params.each.map { Types::Bases::Any.new(location: nil) }, location: nil ) Definition.new(type_name: type_name, entry: entry, self_type: self_type, ancestors: ancestors).tap do |definition| def0 = build_singleton0(type_name) subst = Substitution.build([], [], instance_type: instance_type) merge_definition(src: def0, dest: definition, subst: subst, keep_super: true) 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_. instance = build_instance(type_name) initialize = instance.methods[:initialize] if initialize class_params = entry.type_params.each.map(&:name) # Inject a virtual _typed new_. initialize_defs = initialize.defs definition.methods[: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) method_type_param_vars = Set.new(method_type.type_params) if class_type_param_vars.intersect?(method_type_param_vars) renamed_method_params = method_type.type_params.map do |name| if class_type_param_vars.include?(name) Types::Variable.fresh(name).name else name end end method_params = class_params + renamed_method_params sub = Substitution.build(method_type.type_params, Types::Variable.build(renamed_method_params)) else method_params = class_params + method_type.type_params sub = Substitution.build([], []) end method_type = method_type.map_type {|ty| ty.sub(sub) } method_type = method_type.update( type_params: method_params, type: method_type.type.with_return_type( Types::ClassInstance.new( name: type_name, args: Types::Variable.build(entry.type_params.each.map(&:name)), 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 ) end, accessibility: :public, annotations: [], alias_of: nil ) end end end end end end end def validate_params_with(type_params, result:) type_params.each do |param| unless param.skip_validation unless result.compatible?(param.name, with_annotation: param.variance) yield param end end 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| location = case source = ancestor.source when nil definition.entry.primary.decl.location when :super definition.entry.primary.decl.super_class.location else source.location end raise InvalidVarianceAnnotationError.new( type_name: definition.type_name, param: param, location: location ) end end end methods.each do |defn| next if defn.name == :initialize method_types = case original = defn.original when AST::Members::MethodDefinition original.types 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 else raise end if method_types method_types.each do |method_type| result = calculator.in_method_type(method_type: method_type, variables: param_names) validate_params_with(type_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:) variables[name] = Definition::Variable.new( parent_variable: variables[name], type: type, declared_in: type_name ) end def define_methods(definition, interface_methods:, methods:, super_interface_method:) methods.each do |method_def| method_name = method_def.name original = method_def.original if original.is_a?(AST::Members::Alias) existing_method = interface_methods[method_name] || definition.methods[method_name] original_method = interface_methods[original.old_name] || definition.methods[original.old_name] unless original_method raise UnknownMethodAliasError.new( original_name: original.old_name, aliased_name: original.new_name, location: original.location ) end method = Definition::Method.new( super_method: existing_method, defs: original_method.defs.map do |defn| defn.update(defined_in: definition.type_name, implemented_in: definition.type_name) end, accessibility: method_def.accessibility, alias_of: original_method ) else if interface_methods.key?(method_name) interface_method = interface_methods[method_name] if method_def.original raise DuplicatedMethodDefinitionError.new( type: definition.self_type, method_name: method_name, members: [method_def.original] ) end definition.methods[method_name] = interface_method end existing_method = definition.methods[method_name] case original when AST::Members::MethodDefinition defs = original.types.map do |method_type| Definition::Method::TypeDef.new( type: method_type, member: original, defined_in: definition.type_name, implemented_in: definition.type_name ) end accessibility = if method_name == :initialize :private else method_def.accessibility end method = Definition::Method.new( super_method: existing_method, defs: defs, accessibility: accessibility, alias_of: nil ) when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor method_type = if method_name.to_s.end_with?("=") # setter 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: nil ) else # getter MethodType.new( type_params: [], type: Types::Function.empty(original.type), block: nil, location: nil ) end defs = [ Definition::Method::TypeDef.new( type: method_type, member: original, defined_in: definition.type_name, implemented_in: definition.type_name ) ] method = Definition::Method.new( super_method: existing_method, defs: defs, accessibility: method_def.accessibility, alias_of: nil ) when nil unless definition.methods.key?(method_name) raise InvalidOverloadMethodError.new( type_name: definition.type_name, method_name: method_name, kind: :instance, members: method_def.overloads ) end if !super_interface_method && existing_method.defs.any? {|defn| defn.defined_in.interface? } super_method = existing_method.super_method else super_method = existing_method end method = Definition::Method.new( super_method: super_method, defs: existing_method.defs.map do |defn| defn.update(implemented_in: definition.type_name) end, accessibility: existing_method.accessibility, alias_of: existing_method.alias_of ) end end method_def.overloads.each do |overload| defs = overload.types.map do |method_type| Definition::Method::TypeDef.new( type: method_type, member: overload, defined_in: definition.type_name, implemented_in: definition.type_name ) end method.defs.unshift(*defs) end definition.methods[method_name] = method end interface_methods.each do |name, method| unless methods.methods.key?(name) merge_method(definition.type_name, definition.methods, name, method, Substitution.new) end end end def merge_definition(src:, dest:, subst:, implemented_in: :keep, keep_super: false) src.methods.each do |name, method| merge_method(dest.type_name, dest.methods, name, method, subst, implemented_in: implemented_in, keep_super: keep_super) end src.instance_variables.each do |name, variable| merge_variable(dest.instance_variables, name, variable, subst, keep_super: keep_super) end src.class_variables.each do |name, variable| merge_variable(dest.class_variables, name, variable, subst, keep_super: keep_super) end end def merge_variable(variables, name, variable, sub, keep_super: false) super_variable = variables[name] variables[name] = Definition::Variable.new( parent_variable: keep_super ? variable.parent_variable : super_variable, type: sub.empty? ? variable.type : variable.type.sub(sub), declared_in: variable.declared_in ) end def merge_method(type_name, methods, name, method, sub, implemented_in: :keep, keep_super: false) defs = method.defs.yield_self do |defs| if sub.empty? && implemented_in == :keep defs else defs.map do |defn| defn.update( type: sub.empty? ? defn.type : defn.type.sub(sub), implemented_in: case implemented_in when :keep defn.implemented_in when nil nil else implemented_in end ) end end end super_method = methods[name] methods[name] = Definition::Method.new( super_method: keep_super ? method.super_method : super_method, accessibility: method.accessibility, defs: defs, alias_of: method.alias_of ) end def try_cache(type_name, cache:) cached = _ = cache[type_name] case cached when Definition cached when false raise when nil cache[type_name] = false begin cache[type_name] = yield rescue => ex cache.delete(type_name) raise ex end else raise end end def expand_alias(type_name) entry = env.alias_decls[type_name] or raise "Unknown name for expand_alias: #{type_name}" ensure_namespace!(type_name.namespace, location: entry.decl.location) entry.decl.type end end end