Skip to content

Commit 6c2f00c

Browse files
authored
Merge pull request #3067 from ruby/claude/module-self-type-param-alignment-2wpbja
Align type params across declarations in module-self types and superclass validation
2 parents d89e4ad + e51608f commit 6c2f00c

11 files changed

Lines changed: 273 additions & 13 deletions

File tree

lib/rbs/ast/declarations.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def ==(other)
163163
alias eql? ==
164164

165165
def hash
166-
self.class.hash ^ name.hash ^ args.hash ^ location.hash
166+
self.class.hash ^ name.hash ^ args.hash
167167
end
168168

169169
def to_json(state = nil)

lib/rbs/definition_builder.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,13 @@ def define_instance(definition, type_name, subst, define_class_vars:)
124124
end
125125

126126
entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}"
127-
args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
128127

129128
entry.each_decl do |decl|
130-
subst_ = subst + Substitution.build(decl.type_params.each.map(&:name), args)
129+
if align_params = entry.align_params(decl)
130+
subst_ = subst + align_params
131+
else
132+
subst_ = subst
133+
end
131134

132135
decl.members.each do |member|
133136
case member

lib/rbs/definition_builder/ancestor_builder.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,13 @@ def validate_super_class!(type_name, entry)
179179

180180
super_types = with_super_classes.map do |decl|
181181
super_class = decl.super_class or raise
182-
Types::ClassInstance.new(name: super_class.name, args: super_class.args, location: nil)
182+
args = super_class.args
183+
184+
if align_params = entry.align_params(decl)
185+
args = args.map {|type| type.sub(align_params) }
186+
end
187+
188+
Types::ClassInstance.new(name: super_class.name, args: args, location: nil)
183189
end
184190

185191
super_types.uniq!
@@ -473,10 +479,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included
473479

474480
def mixin_ancestors(entry, type_name, included_modules:, included_interfaces:, extended_modules:, prepended_modules:, extended_interfaces:)
475481
entry.each_decl do |decl|
476-
align_params = Substitution.build(
477-
decl.type_params.each.map(&:name),
478-
entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
479-
)
482+
align_params = entry.align_params(decl)
480483

481484
mixin_ancestors0(decl,
482485
type_name,

lib/rbs/definition_builder/method_builder.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def build_instance(type_name)
104104
type = Types::ClassInstance.new(name: type_name, args: args, location: nil)
105105
Methods.new(type: type).tap do |methods|
106106
entry.each_decl do |decl|
107-
subst = Substitution.build(decl.type_params.each.map(&:name), args)
107+
subst = entry.align_params(decl)
108108
case decl
109109
when AST::Declarations::Base
110110
each_rbs_member_with_accessibility(decl.members) do |member, accessibility|
@@ -115,22 +115,22 @@ def build_instance(type_name)
115115
build_method(
116116
methods,
117117
type,
118-
member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }),
118+
member: subst ? member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }) : member,
119119
accessibility: member.visibility || accessibility
120120
)
121121
when :singleton_instance
122122
build_method(
123123
methods,
124124
type,
125-
member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }),
125+
member: subst ? member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }) : member,
126126
accessibility: :private
127127
)
128128
end
129129
when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
130130
if member.kind == :instance
131131
build_attribute(methods,
132132
type,
133-
member: member.update(type: member.type.sub(subst)),
133+
member: subst ? member.update(type: member.type.sub(subst)) : member,
134134
accessibility: member.visibility || accessibility)
135135
end
136136
when AST::Members::Alias

lib/rbs/environment/class_entry.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ def validate_type_params
6464
end
6565
end
6666
end
67+
68+
def align_params(decl)
69+
entry_params = type_params
70+
decl_param_names = decl.type_params.map(&:name)
71+
72+
return nil if decl_param_names == entry_params.map(&:name)
73+
74+
Substitution.build(
75+
decl_param_names,
76+
entry_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
77+
)
78+
end
6779
end
6880
end
6981
end

lib/rbs/environment/module_entry.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,35 @@ def type_params
4242

4343
def self_types
4444
each_decl.flat_map do |decl|
45-
decl.self_types
45+
self_types = decl.self_types
46+
subst = align_params(decl)
47+
48+
if self_types.empty? || subst.nil?
49+
self_types
50+
else
51+
self_types.map do |self_type|
52+
AST::Declarations::Module::Self.new(
53+
name: self_type.name,
54+
args: self_type.args.map {|type| type.sub(subst) },
55+
location: self_type.location
56+
)
57+
end
58+
end
4659
end.uniq
4760
end
4861

62+
def align_params(decl)
63+
entry_params = type_params
64+
decl_param_names = decl.type_params.map(&:name)
65+
66+
return nil if decl_param_names == entry_params.map(&:name)
67+
68+
Substitution.build(
69+
decl_param_names,
70+
entry_params.map {|param| Types::Variable.new(name: param.name, location: param.location) }
71+
)
72+
end
73+
4974
def validate_type_params
5075
unless context_decls.empty?
5176
first_decl, *rest_decls = each_decl.to_a

sig/environment/class_entry.rbs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ module RBS
4545
# * Raises `GenericParameterMismatchError` if incompatible declaration is detected.
4646
#
4747
def validate_type_params: () -> void
48+
49+
# Returns a substitution that renames the type parameters of the declaration to the entry's type parameters (`#type_params`)
50+
#
51+
# Returns `nil` if the declaration uses the same type parameter names as `#type_params`.
52+
#
53+
def align_params: (declaration | ModuleEntry::declaration) -> Substitution?
4854
end
4955
end
5056
end

sig/environment/module_entry.rbs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,22 @@ module RBS
4444
#
4545
def validate_type_params: () -> void
4646

47+
# Returns the self types of the declarations
48+
#
49+
# The type variables in the self types are aligned to `#type_params`,
50+
# so that the self types from declarations with different type parameter
51+
# names can be compared and used with `#type_params`.
52+
#
53+
# Note that the returned objects may be different from the ones in the
54+
# declarations, but `#location` points to the original declaration.
55+
#
4756
def self_types: () -> Array[AST::Declarations::Module::Self]
57+
58+
# Returns a substitution that renames the type parameters of the declaration to the entry's type parameters (`#type_params`)
59+
#
60+
# Returns `nil` if the declaration uses the same type parameter names as `#type_params`.
61+
#
62+
def align_params: (declaration | ClassEntry::declaration) -> Substitution?
4863
end
4964
end
5065
end

test/rbs/ancestor_builder_test.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,51 @@ module Hello[X] : _I1[Array[X]]
141141
end
142142
end
143143

144+
def test_one_ancestors_module_self_types_type_param_alignment
145+
SignatureManager.new(system_builtin: true) do |manager|
146+
manager.files[Pathname("a.rbs")] = <<EOF
147+
interface _EachItem[out T]
148+
end
149+
150+
interface _FooItem[out T]
151+
end
152+
153+
module M[out A] : _EachItem[A]
154+
end
155+
EOF
156+
manager.files[Pathname("b.rbs")] = <<EOF
157+
module M[out B] : _EachItem[B], _FooItem[Array[B]]
158+
end
159+
EOF
160+
manager.build do |env|
161+
builder = DefinitionBuilder::AncestorBuilder.new(env: env)
162+
163+
builder.one_instance_ancestors(type_name("::M")).tap do |a|
164+
assert_equal type_name("::M"), a.type_name
165+
assert_equal [:A], a.params
166+
167+
# Type parameters in self types are renamed to the primary declaration's type parameters,
168+
# and `_EachItem[A]`/`_EachItem[B]` are deduplicated
169+
assert_equal [
170+
Ancestor::Instance.new(name: type_name("::_EachItem"), args: [parse_type("A", variables: [:A])], source: nil),
171+
Ancestor::Instance.new(name: type_name("::_FooItem"), args: [parse_type("::Array[A]", variables: [:A])], source: nil)
172+
],
173+
a.self_types
174+
175+
# The source of each self type keeps pointing to the original declaration
176+
a.self_types or raise
177+
a.self_types.each do |self_type|
178+
source = self_type.source
179+
assert_instance_of AST::Declarations::Module::Self, source
180+
location = source.location or raise
181+
expected_file = source.name == type_name("::_FooItem") ? "b.rbs" : "a.rbs"
182+
assert_equal expected_file, Pathname(location.buffer.name).basename.to_s
183+
end
184+
end
185+
end
186+
end
187+
end
188+
144189
def test_one_ancestors_module_no_self_type
145190
SignatureManager.new(system_builtin: true) do |manager|
146191
manager.files[Pathname("foo.rbs")] = <<EOF
@@ -407,6 +452,45 @@ class B < ::String
407452
end
408453
end
409454

455+
def test_instance_ancestors_super_class_validation_renamed_params
456+
SignatureManager.new do |manager|
457+
manager.files.merge!(Pathname("foo.rbs") => <<-EOF)
458+
class Base[T]
459+
end
460+
461+
class A[X] < Base[X]
462+
end
463+
464+
class B[X] < Base[X]
465+
end
466+
467+
class B[Y] < Base[Integer]
468+
end
469+
EOF
470+
471+
manager.files.merge!(Pathname("foo2.rbs") => <<-EOF)
472+
class A[Y] < Base[Y]
473+
end
474+
EOF
475+
476+
manager.build do |env|
477+
builder = DefinitionBuilder::AncestorBuilder.new(env: env)
478+
479+
# ::A is valid: the declarations declare the same superclass modulo type parameter renaming.
480+
builder.one_instance_ancestors(type_name("::A")).tap do |a|
481+
assert_equal Ancestor::Instance.new(name: type_name("::Base"), args: [parse_type("X", variables: [:X])], source: :super),
482+
a.super_class
483+
end
484+
485+
# ::B is invalid: the superclass args are different.
486+
error = assert_raises SuperclassMismatchError do
487+
builder.one_instance_ancestors(type_name("::B"))
488+
end
489+
assert_equal error.name, type_name("::B")
490+
end
491+
end
492+
end
493+
410494
def test_singleton_ancestors
411495
SignatureManager.new do |manager|
412496
manager.files[Pathname("foo.rbs")] = <<EOF

test/rbs/definition_builder_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,36 @@ module M : _StringConvertible
375375
end
376376
end
377377

378+
def test_build_instance_module_self_types_type_param_alignment
379+
SignatureManager.new do |manager|
380+
manager.files[Pathname("a.rbs")] = <<EOF
381+
interface _Reader[T]
382+
def read: () -> T
383+
end
384+
385+
module M[A] : _Reader[A]
386+
end
387+
EOF
388+
manager.files[Pathname("b.rbs")] = <<EOF
389+
module M[B] : _Reader[B]
390+
end
391+
EOF
392+
manager.build do |env|
393+
builder = DefinitionBuilder.new(env: env)
394+
395+
builder.build_instance(type_name("::M")).tap do |definition|
396+
assert_instance_of Definition, definition
397+
assert_equal type_name("::M"), definition.type_name
398+
assert_equal [:A], definition.type_params
399+
400+
# The self type from `b.rbs` is aligned to the primary declaration's type parameters
401+
assert_equal Set[:read], Set.new(definition.methods.keys)
402+
assert_method_definition definition.methods[:read], ["() -> A"], accessibility: :public
403+
end
404+
end
405+
end
406+
end
407+
378408
def test_build_instance_class_basic_object
379409
SignatureManager.new do |manager|
380410
manager.build do |env|

0 commit comments

Comments
 (0)