Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion lib/rbs/ast/ruby/members.rb
Original file line number Diff line number Diff line change
Expand Up @@ -549,17 +549,27 @@ class DefMember < Base

attr_reader :name
attr_reader :node
attr_reader :kind
attr_reader :method_type
attr_reader :leading_comment

def initialize(buffer, name, node, method_type, leading_comment)
def initialize(buffer, name, node, method_type, leading_comment, kind: :instance)
super(buffer)
@name = name
@node = node
@kind = kind
@method_type = method_type
@leading_comment = leading_comment
end

def singleton?
kind == :singleton
end

def instance?
kind == :instance
end

def location
rbs_location(node.location)
end
Expand All @@ -583,6 +593,7 @@ def name_location
def type_fingerprint
[
"members/def",
kind.to_s,
name.to_s,
method_type.type_fingerprint,
leading_comment&.as_comment&.string
Expand Down
18 changes: 12 additions & 6 deletions lib/rbs/definition_builder/method_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,14 @@ def build_instance(type_name)
decl.members.each do |member|
case member
when AST::Ruby::Members::DefMember
build_method(
methods,
type,
member: member,
accessibility: :public
)
if member.instance?
build_method(
methods,
type,
member: member,
accessibility: :public
)
end
when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
build_ruby_attribute(methods, type, member: member, accessibility: :public)
end
Expand Down Expand Up @@ -181,6 +183,10 @@ def build_singleton(type_name)
if member.kind == :singleton
build_alias(methods, type, member: member)
end
when AST::Ruby::Members::DefMember
if member.singleton?
build_method(methods, type, member: member, accessibility: :public)
end
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/rbs/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,8 @@ def resolve_ruby_member(resolver, member, context:)
member.name,
member.node,
member.method_type.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) },
member.leading_comment
member.leading_comment,
kind: member.kind
)
when AST::Ruby::Members::IncludeMember
resolved_annotation = member.annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
Expand Down
8 changes: 5 additions & 3 deletions lib/rbs/inline_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,16 @@ def visit_class_or_module_body(decl, node)
def visit_def_node(node)
return if skip_node?(node)

if node.receiver
if node.receiver && !node.receiver.is_a?(Prism::SelfNode)
diagnostics << Diagnostic::NotImplementedYet.new(
rbs_location(node.receiver.location),
"Singleton method definition is not supported yet"
"Method definition with non-self receiver is not supported"
)
return
end

kind = node.receiver ? :singleton : :instance #: :singleton | :instance

case current = current_module
when AST::Ruby::Declarations::ClassDecl, AST::Ruby::Declarations::ModuleDecl
leading_block = comments.leading_block!(node)
Expand All @@ -223,7 +225,7 @@ def visit_def_node(node)
method_type, leading_unuseds, trailing_unused = AST::Ruby::Members::MethodTypeAnnotation.build(leading_block, trailing_block, [], node)
report_unused_annotation(trailing_unused, *leading_unuseds)

defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block)
defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block, kind: kind)
current.members << defn

# Skip other comments in `def` node
Expand Down
7 changes: 6 additions & 1 deletion sig/ast/ruby/members.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ module RBS

attr_reader name: Symbol
attr_reader node: Prism::DefNode
attr_reader kind: :instance | :singleton
attr_reader method_type: MethodTypeAnnotation
attr_reader leading_comment: CommentBlock?

def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation, CommentBlock? leading_comment) -> void
def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation, CommentBlock? leading_comment, ?kind: :instance | :singleton) -> void

def singleton?: () -> bool

def instance?: () -> bool

def location: () -> Location

Expand Down
183 changes: 180 additions & 3 deletions test/rbs/inline_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,195 @@ def foo; end
assert_empty result.declarations
end

def test_error__def__singleton
def test_parse__def__singleton
result = parse(<<~RUBY)
module Foo
def self.foo; end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :foo, member.name
assert_predicate member, :singleton?
refute_predicate member, :instance?
assert_equal :singleton, member.kind
end
end
end

def test_parse__def__singleton__colon_type
result = parse(<<~RUBY)
class Foo
#: () -> void
def self.hello
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :hello, member.name
assert_predicate member, :singleton?
assert_equal ["() -> void"], member.overloads.map { _1.method_type.to_s }
end
end
end

def test_parse__def__singleton__rbs_annotation
result = parse(<<~RUBY)
class Foo
# @rbs () -> void
def self.hello
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :hello, member.name
assert_predicate member, :singleton?
assert_equal ["() -> void"], member.overloads.map { _1.method_type.to_s }
end
end
end

def test_parse__def__singleton__with_params
result = parse(<<~RUBY)
class Foo
# @rbs n: Integer
# @rbs return: Integer
def self.double(n)
n * 2
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :double, member.name
assert_predicate member, :singleton?
assert_equal ["(Integer n) -> Integer"], member.overloads.map { _1.method_type.to_s }
end
end
end

def test_parse__def__singleton__mixed_with_instance
result = parse(<<~RUBY)
class Foo
#: () -> void
def self.class_method
end

#: () -> String
def instance_method
""
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
assert_equal 2, decl.members.size

decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :class_method, member.name
assert_predicate member, :singleton?
assert_equal ["() -> void"], member.overloads.map { _1.method_type.to_s }
end

decl.members[1].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :instance_method, member.name
assert_predicate member, :instance?
assert_equal ["() -> String"], member.overloads.map { _1.method_type.to_s }
end
end
end

def test_parse__def__singleton__skip
result = parse(<<~RUBY)
class Foo
# @rbs skip
def self.skipped_method
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
assert_empty decl.members
end
end

def test_error__def__singleton__non_self_receiver
result = parse(<<~RUBY)
module Foo
def other_obj.foo; end
end
RUBY

assert_equal 1, result.diagnostics.size
assert_any!(result.diagnostics) do |diagnostic|
assert_instance_of RBS::InlineParser::Diagnostic::NotImplementedYet, diagnostic
assert_equal "self", diagnostic.location.source
assert_equal "Singleton method definition is not supported yet", diagnostic.message
assert_equal "other_obj", diagnostic.location.source
assert_equal "Method definition with non-self receiver is not supported", diagnostic.message
end
end

def test_parse__def__singleton__in_module
result = parse(<<~RUBY)
module Foo
#: () -> void
def self.module_method
end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :module_method, member.name
assert_predicate member, :singleton?
assert_equal ["() -> void"], member.overloads.map { _1.method_type.to_s }
end
end
end

def test_parse__def__instance_method_kind
result = parse(<<~RUBY)
class Foo
def instance_method; end
end
RUBY

assert_empty result.diagnostics

result.declarations[0].tap do |decl|
decl.members[0].tap do |member|
assert_instance_of RBS::AST::Ruby::Members::DefMember, member
assert_equal :instance_method, member.name
assert_predicate member, :instance?
refute_predicate member, :singleton?
assert_equal :instance, member.kind
end
end
end

Expand Down
Loading