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
46 changes: 44 additions & 2 deletions docs/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,35 @@ class Calculator
end
```

### Method visibility

Inline RBS detects `public` and `private` calls, and applies the visibility to the methods, like RBS.

```ruby
class Calculator
def add(x, y) = x + y # public

private

def helper = nil # private

public

def sub(x, y) = x - y # public

private def secret = nil # private
end
```

- `private`/`public` without arguments switches the visibility of the instance methods defined after the call
- `private def ...`/`public def ...` applies the visibility to that method definition

Note that the visibility is applied only when the method is called without receiver, and the symbol arguments, like `private :foo, :bar`, are ignored.

### Current Limitations

- Method visibility declaration is not supported yet
- `private :foo, :bar` style visibility declaration is not supported
- `protected`, `private_class_method`, and `module_function` are not supported
- The `class << self` syntax is not supported
- Top-level method definitions (outside any class/module) are not supported
- Method definitions with a non-self receiver (e.g. `def obj.foo`) are not supported
Expand Down Expand Up @@ -429,9 +455,25 @@ end

The attribute definitions are ignored because the names are given by string literals and local variables.

### Attribute visibility

The attributes respect the visibility declaration, the same as [method definitions](#method-visibility).

```ruby
class Person
attr_reader :name #: String

private

attr_accessor :password #: String

private attr_writer :token #: String
end
```

### Current Limitations

- Attribute visibility is not supported yet. All attributes are _public_
- `private :foo, :bar` style visibility declaration is not supported
- Top-level attribute definitions (outside any class/module) are not supported

## Mixin
Expand Down
46 changes: 42 additions & 4 deletions lib/rbs/ast/ruby/members.rb
Original file line number Diff line number Diff line change
Expand Up @@ -552,14 +552,16 @@ class DefMember < Base
attr_reader :kind
attr_reader :method_type
attr_reader :leading_comment
attr_reader :visibility

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

def singleton?
Expand Down Expand Up @@ -596,7 +598,8 @@ def type_fingerprint
kind.to_s,
name.to_s,
method_type.type_fingerprint,
leading_comment&.as_comment&.string
leading_comment&.as_comment&.string,
visibility&.to_s
]
end
end
Expand Down Expand Up @@ -652,13 +655,15 @@ class AttributeMember < Base
attr_reader :name_nodes
attr_reader :type_annotation
attr_reader :leading_comment
attr_reader :visibility

def initialize(buffer, node, name_nodes, leading_comment, type_annotation)
def initialize(buffer, node, name_nodes, leading_comment, type_annotation, visibility: nil)
super(buffer)
@node = node
@name_nodes = name_nodes
@leading_comment = leading_comment
@type_annotation = type_annotation
@visibility = visibility
end

def names
Expand Down Expand Up @@ -687,7 +692,8 @@ def type_fingerprint
self.class.name,
names.map(&:to_s),
type_annotation&.type_fingerprint,
leading_comment&.as_comment&.string
leading_comment&.as_comment&.string,
visibility&.to_s
]
end
end
Expand All @@ -701,6 +707,38 @@ class AttrWriterMember < AttributeMember
class AttrAccessorMember < AttributeMember
end

class VisibilityMember < Base
attr_reader :node

def initialize(buffer, node)
super(buffer)
@node = node
end

def location
rbs_location(node.location)
end

def type_fingerprint
[
"members/visibility",
self.class.name
]
end
end

class PublicMember < VisibilityMember
def visibility
:public
end
end

class PrivateMember < VisibilityMember
def visibility
:private
end
end

class InstanceVariableMember < Base
attr_reader :annotation

Expand Down
4 changes: 2 additions & 2 deletions lib/rbs/definition_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ def define_method(methods, definition, method, subst, self_type_methods, defined
method_definition = Definition::Method.new(
super_method: existing_method,
defs: existing_method.defs.map { |defn| defn.update(implemented_in: implemented_in) },
accessibility: :public,
accessibility: method.accessibility,
alias_of: existing_method.alias_of,
alias_member: nil
)
Expand All @@ -913,7 +913,7 @@ def define_method(methods, definition, method, subst, self_type_methods, defined
method_definition = Definition::Method.new(
super_method: existing_method,
defs: defs,
accessibility: :public,
accessibility: method.accessibility,
alias_of: nil,
alias_member: nil
)
Expand Down
16 changes: 8 additions & 8 deletions lib/rbs/definition_builder/method_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def build_instance(type_name)
subst = entry.align_params(decl)
case decl
when AST::Declarations::Base
each_rbs_member_with_accessibility(decl.members) do |member, accessibility|
each_member_with_accessibility(decl.members) do |member, accessibility|
case member
when AST::Members::MethodDefinition
case member.kind
Expand Down Expand Up @@ -147,19 +147,19 @@ def build_instance(type_name)
end
end
when AST::Ruby::Declarations::Base
decl.members.each do |member|
each_member_with_accessibility(decl.members) do |member, accessibility|
case member
when AST::Ruby::Members::DefMember
if member.instance?
build_method(
methods,
type,
member: member,
accessibility: :public
accessibility: member.visibility || accessibility
)
end
when AST::Ruby::Members::AttrReaderMember, AST::Ruby::Members::AttrWriterMember, AST::Ruby::Members::AttrAccessorMember
build_ruby_attribute(methods, type, member: member, accessibility: :public)
build_ruby_attribute(methods, type, member: member, accessibility: member.visibility || accessibility)
end
end
end
Expand Down Expand Up @@ -192,7 +192,7 @@ def build_singleton(type_name)
end
when AST::Ruby::Members::DefMember
if member.singleton?
build_method(methods, type, member: member, accessibility: :public)
build_method(methods, type, member: member, accessibility: member.visibility || :public)
end
end
end
Expand Down Expand Up @@ -271,12 +271,12 @@ def build_method(methods, type, member:, accessibility:)
end
end

def each_rbs_member_with_accessibility(members, accessibility: :public)
def each_member_with_accessibility(members, accessibility: :public)
members.each do |member|
case member
when AST::Members::Public
when AST::Members::Public, AST::Ruby::Members::PublicMember
accessibility = :public
when AST::Members::Private
when AST::Members::Private, AST::Ruby::Members::PrivateMember
accessibility = :private
else
yield member, accessibility
Expand Down
14 changes: 10 additions & 4 deletions lib/rbs/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,8 @@ def resolve_ruby_member(resolver, member, context:)
member.node,
member.method_type.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) },
member.leading_comment,
kind: member.kind
kind: member.kind,
visibility: member.visibility
)
when AST::Ruby::Members::IncludeMember
resolved_annotation = member.annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
Expand Down Expand Up @@ -829,7 +830,8 @@ def resolve_ruby_member(resolver, member, context:)
member.node,
member.name_nodes,
member.leading_comment,
resolved_type_annotation
resolved_type_annotation,
visibility: member.visibility
)
when AST::Ruby::Members::AttrWriterMember
resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
Expand All @@ -838,7 +840,8 @@ def resolve_ruby_member(resolver, member, context:)
member.node,
member.name_nodes,
member.leading_comment,
resolved_type_annotation
resolved_type_annotation,
visibility: member.visibility
)
when AST::Ruby::Members::AttrAccessorMember
resolved_type_annotation = member.type_annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }
Expand All @@ -847,8 +850,11 @@ def resolve_ruby_member(resolver, member, context:)
member.node,
member.name_nodes,
member.leading_comment,
resolved_type_annotation
resolved_type_annotation,
visibility: member.visibility
)
when AST::Ruby::Members::PublicMember, AST::Ruby::Members::PrivateMember
member
when AST::Ruby::Members::InstanceVariableMember
resolved_annotation = member.annotation.map_type_name {|name| absolute_type_name(resolver, nil, name, context: context) }
AST::Ruby::Members::InstanceVariableMember.new(
Expand Down
50 changes: 45 additions & 5 deletions lib/rbs/inline_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def self.parse(buffer, prism)
end

class Parser < Prism::Visitor
attr_reader :module_nesting, :result, :comments
attr_reader :module_nesting, :result, :comments, :current_visibility

include AST::Ruby::Helpers::ConstantHelper
include AST::Ruby::Helpers::LocationHelper
Expand All @@ -64,6 +64,7 @@ def initialize(result)
@result = result
@module_nesting = []
@comments = CommentAssociation.build(result.buffer, result.prism_result)
@current_visibility = nil
end

def buffer
Expand All @@ -89,6 +90,14 @@ def push_module_nesting(mod)
module_nesting.pop()
end

def push_visibility(visibility)
old_visibility = current_visibility
@current_visibility = visibility
yield
ensure
@current_visibility = old_visibility
end

def skip_node?(node)
if ref = comments.leading_block(node)
if ref.block.each_paragraph([]).any? { _1.is_a?(AST::Ruby::Annotations::SkipAnnotation) }
Expand Down Expand Up @@ -225,7 +234,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, kind: kind)
defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block, kind: kind, visibility: current_visibility)
current.members << defn

# Skip other comments in `def` node
Expand Down Expand Up @@ -264,6 +273,13 @@ def visit_call_node(node)
"Top-level attribute definition is not supported"
)
end
when :public, :private
case current = current_module
when AST::Ruby::Declarations::ClassDecl, AST::Ruby::Declarations::ModuleDecl
parse_visibility_call(node)
else
visit_child_nodes(node)
end
else
visit_child_nodes(node)
end
Expand Down Expand Up @@ -386,18 +402,42 @@ def parse_attribute_call(node)
# Create the appropriate member type
member = case node.name
when :attr_reader
AST::Ruby::Members::AttrReaderMember.new(buffer, node, name_nodes, leading_block, type_annotation)
AST::Ruby::Members::AttrReaderMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility)
when :attr_writer
AST::Ruby::Members::AttrWriterMember.new(buffer, node, name_nodes, leading_block, type_annotation)
AST::Ruby::Members::AttrWriterMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility)
when :attr_accessor
AST::Ruby::Members::AttrAccessorMember.new(buffer, node, name_nodes, leading_block, type_annotation)
AST::Ruby::Members::AttrAccessorMember.new(buffer, node, name_nodes, leading_block, type_annotation, visibility: current_visibility)
else
raise "Unexpected attribute method: #{node.name}"
end

current_module!.members << member
end

def parse_visibility_call(node)
if node.arguments && !node.arguments.arguments.empty?
# `private def foo; end`, `private attr_reader :foo`
visibility = node.name == :public ? :public : :private #: AST::Members::visibility
push_visibility(visibility) do
visit_child_nodes(node)
end
else
return if skip_node?(node)

member =
case node.name
when :public
AST::Ruby::Members::PublicMember.new(buffer, node)
when :private
AST::Ruby::Members::PrivateMember.new(buffer, node)
else
raise "Unexpected visibility method: #{node.name}"
end

current_module!.members << member
end
end

def parse_constant_declaration(node)
# Create TypeName for the constant
unless constant_name = constant_as_type_name(node)
Expand Down
Loading
Loading