Skip to content

Commit f0f9873

Browse files
committed
Update DefinitionBuilder
1 parent 467abd8 commit f0f9873

File tree

6 files changed

+218
-2
lines changed

6 files changed

+218
-2
lines changed

lib/rbs/definition_builder.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ def define_instance(definition, type_name, subst, define_class_vars:)
151151
end
152152
end
153153

154+
when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
155+
member.names.each do |name|
156+
ivar_name = :"@#{name}"
157+
attr_type = member.type || Types::Bases::Any.new(location: nil)
158+
159+
insert_variable(
160+
type_name,
161+
definition.instance_variables,
162+
name: ivar_name,
163+
type: attr_type,
164+
source: member
165+
)
166+
end
167+
154168
when AST::Members::InstanceVariable
155169
insert_variable(
156170
type_name,
@@ -766,6 +780,59 @@ def define_method(methods, definition, method, subst, self_type_methods, defined
766780
)
767781

768782
method_definition.annotations.replace(original.annotations)
783+
when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
784+
if duplicated_method = methods[method.name]
785+
raise DuplicatedMethodDefinitionError.new(
786+
type: definition.self_type,
787+
method_name: method.name,
788+
members: [*duplicated_method.members, original]
789+
)
790+
end
791+
792+
attr_type = original.type || Types::Bases::Any.new(location: nil)
793+
method_type =
794+
if method.name.to_s.end_with?("=")
795+
# setter
796+
MethodType.new(
797+
type_params: [],
798+
type: Types::Function.empty(attr_type).update(
799+
required_positionals: [
800+
Types::Function::Param.new(type: attr_type, name: method.name.to_s.chomp("=").to_sym)
801+
]
802+
),
803+
block: nil,
804+
location: original.location
805+
)
806+
else
807+
# getter
808+
MethodType.new(
809+
type_params: [],
810+
type: Types::Function.empty(attr_type),
811+
block: nil,
812+
location: original.location
813+
)
814+
end
815+
816+
if implemented_in
817+
super_method = existing_method
818+
end
819+
820+
method_definition = Definition::Method.new(
821+
super_method: super_method,
822+
defs: [
823+
Definition::Method::TypeDef.new(
824+
type: method_type,
825+
member: original,
826+
defined_in: defined_in,
827+
implemented_in: implemented_in
828+
)
829+
],
830+
accessibility: method.accessibility,
831+
alias_of: nil,
832+
alias_member: nil
833+
)
834+
835+
method_definition.annotations.replace([])
769836
when AST::Ruby::Members::DefMember
770837
if duplicated_method = methods[method.name]
771838
raise DuplicatedMethodDefinitionError.new(

lib/rbs/definition_builder/method_builder.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ def build_instance(type_name)
149149
member: member,
150150
accessibility: :public
151151
)
152+
when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
153+
build_ruby_attribute(methods, type, member: member, accessibility: :public)
152154
end
153155
end
154156
end
@@ -227,6 +229,24 @@ def build_attribute(methods, type, member:, accessibility:)
227229
end
228230
end
229231

232+
def build_ruby_attribute(methods, type, member:, accessibility:)
233+
member.names.each do |name|
234+
if member.is_a?(AST::Ruby::Members::AttrReaderMember) || member.is_a?(AST::Ruby::Members::AttrAccessorMember)
235+
defn = methods.methods[name] ||= Methods::Definition.empty(type: type, name: name)
236+
237+
defn.accessibilities << accessibility
238+
defn.originals << member
239+
end
240+
241+
if member.is_a?(AST::Ruby::Members::AttrWriterMember) || member.is_a?(AST::Ruby::Members::AttrAccessorMember)
242+
defn = methods.methods[:"#{name}="] ||= Methods::Definition.empty(type: type, name: :"#{name}=")
243+
244+
defn.accessibilities << accessibility
245+
defn.originals << member
246+
end
247+
end
248+
end
249+
230250
def build_method(methods, type, member:, accessibility:)
231251
defn = methods.methods[member.name] ||= Methods::Definition.empty(type: type, name: member.name)
232252

lib/rbs/environment.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,33 @@ def resolve_ruby_member(resolver, member, context:)
748748
absolute_type_name(resolver, nil, member.module_name, context: context),
749749
resolved_annotation
750750
)
751+
when AST::Ruby::Members::AttrReaderMember
752+
resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
753+
AST::Ruby::Members::AttrReaderMember.new(
754+
member.buffer,
755+
member.node,
756+
member.name_nodes,
757+
member.leading_comment,
758+
resolved_type_annotation
759+
)
760+
when AST::Ruby::Members::AttrWriterMember
761+
resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
762+
AST::Ruby::Members::AttrWriterMember.new(
763+
member.buffer,
764+
member.node,
765+
member.name_nodes,
766+
member.leading_comment,
767+
resolved_type_annotation
768+
)
769+
when AST::Ruby::Members::AttrAccessorMember
770+
resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
771+
AST::Ruby::Members::AttrAccessorMember.new(
772+
member.buffer,
773+
member.node,
774+
member.name_nodes,
775+
member.leading_comment,
776+
resolved_type_annotation
777+
)
751778
else
752779
raise "Unknown member type: #{member.class}"
753780
end

sig/definition.rbs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ module RBS
1212
| AST::Members::InstanceVariable
1313
| AST::Members::ClassVariable
1414
| AST::Members::ClassInstanceVariable
15+
| AST::Ruby::Members::AttrReaderMember
16+
| AST::Ruby::Members::AttrWriterMember
17+
| AST::Ruby::Members::AttrAccessorMember
1518
attr_reader source: source
1619

1720
def initialize: (parent_variable: Variable?, type: Types::t, declared_in: TypeName, source: source) -> void
@@ -21,7 +24,7 @@ module RBS
2124

2225
class Method
2326
type method_member = AST::Members::MethodDefinition | AST::Members::AttrReader | AST::Members::AttrAccessor | AST::Members::AttrWriter
24-
| AST::Ruby::Members::DefMember
27+
| AST::Ruby::Members::DefMember | AST::Ruby::Members::AttrReaderMember | AST::Ruby::Members::AttrWriterMember | AST::Ruby::Members::AttrAccessorMember
2528

2629
class TypeDef
2730
attr_reader type: MethodType

sig/method_builder.rbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module RBS
1616
#
1717
class Definition
1818
type original = AST::Members::MethodDefinition | AST::Members::Alias | AST::Members::AttrAccessor | AST::Members::AttrWriter | AST::Members::AttrReader
19-
| AST::Ruby::Members::DefMember
19+
| AST::Ruby::Members::DefMember | AST::Ruby::Members::AttrReaderMember | AST::Ruby::Members::AttrWriterMember | AST::Ruby::Members::AttrAccessorMember
2020

2121
type overloading_definition = AST::Members::MethodDefinition | AST::Ruby::Members::DefMember
2222

@@ -77,6 +77,8 @@ module RBS
7777

7878
def build_attribute: (Methods, Methods::instance_type, member: AST::Members::AttrAccessor | AST::Members::AttrReader | AST::Members::AttrWriter, accessibility: Definition::accessibility) -> void
7979

80+
def build_ruby_attribute: (Methods, Methods::instance_type, member: AST::Ruby::Members::AttrReaderMember | AST::Ruby::Members::AttrWriterMember | AST::Ruby::Members::AttrAccessorMember, accessibility: Definition::accessibility) -> void
81+
8082
def build_method: (Methods, Methods::instance_type, member: AST::Members::MethodDefinition | AST::Ruby::Members::DefMember, accessibility: Definition::accessibility) -> void
8183

8284
def each_rbs_member_with_accessibility: (Array[AST::Members::t | AST::Declarations::t], ?accessibility: Definition::accessibility) { (AST::Members::t | AST::Declarations::t, Definition::accessibility) -> void } -> void

test/rbs/definition_builder_test.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3308,4 +3308,101 @@ class A
33083308
end
33093309
end
33103310
end
3311+
3312+
def test_ruby_attribute_members_untyped
3313+
SignatureManager.new do |manager|
3314+
manager.add_ruby_file("attributes.rb", <<~RUBY)
3315+
class AttributeTest
3316+
attr_reader :name
3317+
attr_writer :age
3318+
attr_accessor :email
3319+
end
3320+
RUBY
3321+
3322+
manager.build do |env|
3323+
builder = DefinitionBuilder.new(env: env)
3324+
3325+
builder.build_instance(type_name("::AttributeTest")).tap do |definition|
3326+
# Test attr_reader generates reader method
3327+
assert_method_definition definition.methods[:name], ["() -> untyped"], accessibility: :public
3328+
assert_ivar_definition definition.instance_variables[:@name], "untyped"
3329+
3330+
# Test attr_writer generates writer method
3331+
assert_method_definition definition.methods[:age=], ["(untyped age) -> untyped"], accessibility: :public
3332+
assert_ivar_definition definition.instance_variables[:@age], "untyped"
3333+
3334+
# Test attr_accessor generates both reader and writer methods
3335+
assert_method_definition definition.methods[:email], ["() -> untyped"], accessibility: :public
3336+
assert_method_definition definition.methods[:email=], ["(untyped email) -> untyped"], accessibility: :public
3337+
assert_ivar_definition definition.instance_variables[:@email], "untyped"
3338+
end
3339+
end
3340+
end
3341+
end
3342+
3343+
def test_ruby_attribute_members_typed
3344+
SignatureManager.new do |manager|
3345+
manager.add_ruby_file("typed_attributes.rb", <<~RUBY)
3346+
class TypedAttributeTest
3347+
attr_reader :name #: String
3348+
3349+
attr_writer :age #: Integer
3350+
3351+
attr_accessor :email #: String
3352+
end
3353+
RUBY
3354+
3355+
manager.build do |env|
3356+
builder = DefinitionBuilder.new(env: env)
3357+
3358+
builder.build_instance(type_name("::TypedAttributeTest")).tap do |definition|
3359+
# Test typed attr_reader
3360+
assert_method_definition definition.methods[:name], ["() -> ::String"], accessibility: :public
3361+
assert_ivar_definition definition.instance_variables[:@name], "::String"
3362+
3363+
# Test typed attr_writer
3364+
assert_method_definition definition.methods[:age=], ["(::Integer age) -> ::Integer"], accessibility: :public
3365+
assert_ivar_definition definition.instance_variables[:@age], "::Integer"
3366+
3367+
# Test typed attr_accessor
3368+
assert_method_definition definition.methods[:email], ["() -> ::String"], accessibility: :public
3369+
assert_method_definition definition.methods[:email=], ["(::String email) -> ::String"], accessibility: :public
3370+
assert_ivar_definition definition.instance_variables[:@email], "::String"
3371+
end
3372+
end
3373+
end
3374+
end
3375+
3376+
3377+
def test_ruby_attribute_members_multiple_names
3378+
SignatureManager.new do |manager|
3379+
manager.add_ruby_file("multiple_attributes.rb", <<~RUBY)
3380+
class MultipleAttributeTest
3381+
attr_reader :first_name, :last_name #: String
3382+
3383+
attr_accessor :x, :y #: Integer
3384+
end
3385+
RUBY
3386+
3387+
manager.build do |env|
3388+
builder = DefinitionBuilder.new(env: env)
3389+
3390+
builder.build_instance(type_name("::MultipleAttributeTest")).tap do |definition|
3391+
# Test multiple attr_reader names
3392+
assert_method_definition definition.methods[:first_name], ["() -> ::String"], accessibility: :public
3393+
assert_method_definition definition.methods[:last_name], ["() -> ::String"], accessibility: :public
3394+
assert_ivar_definition definition.instance_variables[:@first_name], "::String"
3395+
assert_ivar_definition definition.instance_variables[:@last_name], "::String"
3396+
3397+
# Test multiple attr_accessor names
3398+
assert_method_definition definition.methods[:x], ["() -> ::Integer"], accessibility: :public
3399+
assert_method_definition definition.methods[:x=], ["(::Integer x) -> ::Integer"], accessibility: :public
3400+
assert_method_definition definition.methods[:y], ["() -> ::Integer"], accessibility: :public
3401+
assert_method_definition definition.methods[:y=], ["(::Integer y) -> ::Integer"], accessibility: :public
3402+
assert_ivar_definition definition.instance_variables[:@x], "::Integer"
3403+
assert_ivar_definition definition.instance_variables[:@y], "::Integer"
3404+
end
3405+
end
3406+
end
3407+
end
33113408
end

0 commit comments

Comments
 (0)