diff --git a/lib/rbs/ast/declarations.rb b/lib/rbs/ast/declarations.rb index 31be847f49..a989805d95 100644 --- a/lib/rbs/ast/declarations.rb +++ b/lib/rbs/ast/declarations.rb @@ -163,7 +163,7 @@ def ==(other) alias eql? == def hash - self.class.hash ^ name.hash ^ args.hash ^ location.hash + self.class.hash ^ name.hash ^ args.hash end def to_json(state = nil) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index b4d36b721e..715b417a96 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -124,10 +124,13 @@ def define_instance(definition, type_name, subst, 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.each_decl do |decl| - subst_ = subst + Substitution.build(decl.type_params.each.map(&:name), args) + if align_params = entry.align_params(decl) + subst_ = subst + align_params + else + subst_ = subst + end decl.members.each do |member| case member diff --git a/lib/rbs/definition_builder/ancestor_builder.rb b/lib/rbs/definition_builder/ancestor_builder.rb index 122f5f5230..7cc2c75430 100644 --- a/lib/rbs/definition_builder/ancestor_builder.rb +++ b/lib/rbs/definition_builder/ancestor_builder.rb @@ -179,7 +179,13 @@ def validate_super_class!(type_name, entry) super_types = with_super_classes.map do |decl| super_class = decl.super_class or raise - Types::ClassInstance.new(name: super_class.name, args: super_class.args, location: nil) + args = super_class.args + + if align_params = entry.align_params(decl) + args = args.map {|type| type.sub(align_params) } + end + + Types::ClassInstance.new(name: super_class.name, args: args, location: nil) end super_types.uniq! @@ -473,10 +479,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included def mixin_ancestors(entry, type_name, included_modules:, included_interfaces:, extended_modules:, prepended_modules:, extended_interfaces:) entry.each_decl do |decl| - align_params = Substitution.build( - decl.type_params.each.map(&:name), - entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } - ) + align_params = entry.align_params(decl) mixin_ancestors0(decl, type_name, diff --git a/lib/rbs/definition_builder/method_builder.rb b/lib/rbs/definition_builder/method_builder.rb index a4f47e4051..530a9d2089 100644 --- a/lib/rbs/definition_builder/method_builder.rb +++ b/lib/rbs/definition_builder/method_builder.rb @@ -104,7 +104,7 @@ def build_instance(type_name) type = Types::ClassInstance.new(name: type_name, args: args, location: nil) Methods.new(type: type).tap do |methods| entry.each_decl do |decl| - subst = Substitution.build(decl.type_params.each.map(&:name), args) + subst = entry.align_params(decl) case decl when AST::Declarations::Base each_rbs_member_with_accessibility(decl.members) do |member, accessibility| @@ -115,14 +115,14 @@ def build_instance(type_name) build_method( methods, type, - member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), + member: subst ? member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }) : member, accessibility: member.visibility || accessibility ) when :singleton_instance build_method( methods, type, - member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), + member: subst ? member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }) : member, accessibility: :private ) end @@ -130,7 +130,7 @@ def build_instance(type_name) if member.kind == :instance build_attribute(methods, type, - member: member.update(type: member.type.sub(subst)), + member: subst ? member.update(type: member.type.sub(subst)) : member, accessibility: member.visibility || accessibility) end when AST::Members::Alias diff --git a/lib/rbs/environment/class_entry.rb b/lib/rbs/environment/class_entry.rb index 121310cf67..73762330a5 100644 --- a/lib/rbs/environment/class_entry.rb +++ b/lib/rbs/environment/class_entry.rb @@ -64,6 +64,18 @@ def validate_type_params end end end + + def align_params(decl) + entry_params = type_params + decl_param_names = decl.type_params.map(&:name) + + return nil if decl_param_names == entry_params.map(&:name) + + Substitution.build( + decl_param_names, + entry_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } + ) + end end end end diff --git a/lib/rbs/environment/module_entry.rb b/lib/rbs/environment/module_entry.rb index be466169cf..1f65cf4905 100644 --- a/lib/rbs/environment/module_entry.rb +++ b/lib/rbs/environment/module_entry.rb @@ -42,10 +42,35 @@ def type_params def self_types each_decl.flat_map do |decl| - decl.self_types + self_types = decl.self_types + subst = align_params(decl) + + if self_types.empty? || subst.nil? + self_types + else + self_types.map do |self_type| + AST::Declarations::Module::Self.new( + name: self_type.name, + args: self_type.args.map {|type| type.sub(subst) }, + location: self_type.location + ) + end + end end.uniq end + def align_params(decl) + entry_params = type_params + decl_param_names = decl.type_params.map(&:name) + + return nil if decl_param_names == entry_params.map(&:name) + + Substitution.build( + decl_param_names, + entry_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } + ) + end + def validate_type_params unless context_decls.empty? first_decl, *rest_decls = each_decl.to_a diff --git a/sig/environment/class_entry.rbs b/sig/environment/class_entry.rbs index 854e191eb3..ff97cc5c10 100644 --- a/sig/environment/class_entry.rbs +++ b/sig/environment/class_entry.rbs @@ -45,6 +45,12 @@ module RBS # * Raises `GenericParameterMismatchError` if incompatible declaration is detected. # def validate_type_params: () -> void + + # Returns a substitution that renames the type parameters of the declaration to the entry's type parameters (`#type_params`) + # + # Returns `nil` if the declaration uses the same type parameter names as `#type_params`. + # + def align_params: (declaration | ModuleEntry::declaration) -> Substitution? end end end diff --git a/sig/environment/module_entry.rbs b/sig/environment/module_entry.rbs index 1dee2fefed..c556b09791 100644 --- a/sig/environment/module_entry.rbs +++ b/sig/environment/module_entry.rbs @@ -44,7 +44,22 @@ module RBS # def validate_type_params: () -> void + # Returns the self types of the declarations + # + # The type variables in the self types are aligned to `#type_params`, + # so that the self types from declarations with different type parameter + # names can be compared and used with `#type_params`. + # + # Note that the returned objects may be different from the ones in the + # declarations, but `#location` points to the original declaration. + # def self_types: () -> Array[AST::Declarations::Module::Self] + + # Returns a substitution that renames the type parameters of the declaration to the entry's type parameters (`#type_params`) + # + # Returns `nil` if the declaration uses the same type parameter names as `#type_params`. + # + def align_params: (declaration | ClassEntry::declaration) -> Substitution? end end end diff --git a/test/rbs/ancestor_builder_test.rb b/test/rbs/ancestor_builder_test.rb index 4a47432e91..bbd4d2d613 100644 --- a/test/rbs/ancestor_builder_test.rb +++ b/test/rbs/ancestor_builder_test.rb @@ -141,6 +141,51 @@ module Hello[X] : _I1[Array[X]] end end + def test_one_ancestors_module_self_types_type_param_alignment + SignatureManager.new(system_builtin: true) do |manager| + manager.files[Pathname("a.rbs")] = < <<-EOF) +class Base[T] +end + +class A[X] < Base[X] +end + +class B[X] < Base[X] +end + +class B[Y] < Base[Integer] +end + EOF + + manager.files.merge!(Pathname("foo2.rbs") => <<-EOF) +class A[Y] < Base[Y] +end + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + # ::A is valid: the declarations declare the same superclass modulo type parameter renaming. + builder.one_instance_ancestors(type_name("::A")).tap do |a| + assert_equal Ancestor::Instance.new(name: type_name("::Base"), args: [parse_type("X", variables: [:X])], source: :super), + a.super_class + end + + # ::B is invalid: the superclass args are different. + error = assert_raises SuperclassMismatchError do + builder.one_instance_ancestors(type_name("::B")) + end + assert_equal error.name, type_name("::B") + end + end + end + def test_singleton_ancestors SignatureManager.new do |manager| manager.files[Pathname("foo.rbs")] = < T +end + +module M[A] : _Reader[A] +end +EOF + manager.files[Pathname("b.rbs")] = < A"], accessibility: :public + end + end + end + end + def test_build_instance_class_basic_object SignatureManager.new do |manager| manager.build do |env| diff --git a/test/rbs/environment_test.rb b/test/rbs/environment_test.rb index 2943f8ab0e..2198da9261 100644 --- a/test/rbs/environment_test.rb +++ b/test/rbs/environment_test.rb @@ -316,6 +316,88 @@ module Bar : _Animal end end + def test_module_self_type_type_param_alignment + _, _, decls = RBS::Parser.parse_signature(< T +end + +module Foo[A] : _Animal[A] +end + +module Foo[B] : _Animal[B] +end + +module Foo[C] : _Animal[Integer] +end +EOF + + Environment.new.tap do |env| + decls.each do |decl| + env.insert_rbs_decl(decl, context: nil, namespace: RBS::Namespace.root) + end + + foo = env.class_decls[type_name("::Foo")] + + assert_equal [:A], foo.type_params.map(&:name) + + # Self types are aligned to the primary declaration's type parameters, and + # `_Animal[A]` and `_Animal[B]` are deduplicated + assert_equal [ + RBS::AST::Declarations::Module::Self.new( + name: type_name("_Animal"), + args: [RBS::Types::Variable.new(name: :A, location: nil)], + location: nil + ), + RBS::AST::Declarations::Module::Self.new( + name: type_name("_Animal"), + args: [RBS::Types::ClassInstance.new(name: type_name("Integer"), args: [], location: nil)], + location: nil + ), + ], foo.self_types + + # The locations of the self types point to the original declarations + assert_equal ["_Animal[A]", "_Animal[Integer]"], foo.self_types.map {|self_type| self_type.location&.source } + end + end + + def test_module_entry_align_params + _, _, decls = RBS::Parser.parse_signature(<