Skip to content

Clarify self?. method semantics #910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 22, 2022
Merged
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
2 changes: 0 additions & 2 deletions core/kernel.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,6 @@
#
%a{annotate:rdoc:source:from=object.c}
module Kernel : BasicObject
private

# <!--
# rdoc-file=vm_backtrace.c
# - caller(start=1, length=nil) -> array or nil
Expand Down
6 changes: 4 additions & 2 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,16 @@ An instance variable definition consists of the name of an instance variable and

Method definition has several syntax variations.

You can write `self.` or `self?.` before the name of the method to specify the kind of method: instance, singleton, or both instance and singleton.
You can write `self.` or `self?.` before the name of the method to specify the kind of method: instance, singleton, or module function.

```
def to_s: () -> String # Defines a instance method
def self.new: () -> AnObject # Defines singleton method
def self?.sqrt: (Numeric) -> Numeric # self? is for `module_function`s
```

`self?` method definition adds two methods: a public singleton method and a private instance method, which is equivalent to `module_function` in Ruby.

The method type can be connected with `|`s to define an overloaded method.

```
Expand Down Expand Up @@ -404,7 +406,7 @@ alias collect map # `#collect` has the same ty

### `public`, `private`

`public` and `private` allows specifying the visibility of methods.
`public` and `private` allows specifying the visibility of instance methods.

These work only as _statements_, not per-method specifier.

Expand Down
2 changes: 1 addition & 1 deletion lib/rbs/definition_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ def define_methods(definition, interface_methods:, methods:, super_interface_met
defs: original_method.defs.map do |defn|
defn.update(defined_in: definition.type_name, implemented_in: definition.type_name)
end,
accessibility: method_def.accessibility,
accessibility: original_method.accessibility,
alias_of: original_method
)
else
Expand Down
42 changes: 27 additions & 15 deletions lib/rbs/definition_builder/method_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ def original
end

def accessibility
accessibilities[0]
if original.is_a?(AST::Members::Alias)
raise "alias member doesn't have accessibility"
else
accessibilities[0] or raise
end
end

def self.empty(name:, type:)
Expand Down Expand Up @@ -102,11 +106,21 @@ def build_instance(type_name)
each_member_with_accessibility(d.decl.members) do |member, accessibility|
case member
when AST::Members::MethodDefinition
if member.instance?
build_method(methods,
type,
member: member.update(types: member.types.map {|type| type.sub(subst) }),
accessibility: accessibility)
case member.kind
when :instance
build_method(
methods,
type,
member: member.update(types: member.types.map {|type| type.sub(subst) }),
accessibility: accessibility
)
when :singleton_instance
build_method(
methods,
type,
member: member.update(types: member.types.map {|type| type.sub(subst) }),
accessibility: :private
)
end
when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
if member.kind == :instance
Expand All @@ -117,7 +131,7 @@ def build_instance(type_name)
end
when AST::Members::Alias
if member.kind == :instance
build_alias(methods, type, member: member, accessibility: accessibility)
build_alias(methods, type, member: member)
end
end
end
Expand All @@ -134,19 +148,19 @@ def build_singleton(type_name)

Methods.new(type: type).tap do |methods|
entry.decls.each do |d|
each_member_with_accessibility(d.decl.members) do |member, accessibility|
d.decl.members.each do |member|
case member
when AST::Members::MethodDefinition
if member.singleton?
build_method(methods, type, member: member, accessibility: accessibility)
build_method(methods, type, member: member, accessibility: :public)
end
when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor
if member.kind == :singleton
build_attribute(methods, type, member: member, accessibility: accessibility)
build_attribute(methods, type, member: member, accessibility: :public)
end
when AST::Members::Alias
if member.kind == :singleton
build_alias(methods, type, member: member, accessibility: accessibility)
build_alias(methods, type, member: member)
end
end
end
Expand All @@ -168,18 +182,16 @@ def build_interface(type_name)
when AST::Members::MethodDefinition
build_method(methods, type, member: member, accessibility: :public)
when AST::Members::Alias
build_alias(methods, type, member: member, accessibility: :public)
build_alias(methods, type, member: member)
end
end
end.validate!
end
end

def build_alias(methods, type, member:, accessibility:)
def build_alias(methods, type, member:)
defn = methods.methods[member.new_name] ||= Methods::Definition.empty(type: type, name: member.new_name)

defn.originals << member
defn.accessibilities << accessibility
end

def build_attribute(methods, type, member:, accessibility:)
Expand Down
9 changes: 5 additions & 4 deletions sig/method_builder.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module RBS

class Definition
type original = AST::Members::MethodDefinition | AST::Members::Alias | AST::Members::AttrAccessor | AST::Members::AttrWriter | AST::Members::AttrReader
type accessibility = :public | :private

type accessibility = RBS::Definition::accessibility

attr_reader name: Symbol
attr_reader type: instance_type
Expand Down Expand Up @@ -59,11 +60,11 @@ module RBS

def build_interface: (TypeName) -> Methods

def build_alias: (Methods, Methods::instance_type, member: AST::Members::Alias, accessibility: Methods::Definition::accessibility) -> void
def build_alias: (Methods, Methods::instance_type, member: AST::Members::Alias) -> void

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

def build_method: (Methods, Methods::instance_type, member: AST::Members::MethodDefinition, accessibility: Methods::Definition::accessibility) -> void
def build_method: (Methods, Methods::instance_type, member: AST::Members::MethodDefinition, accessibility: Definition::accessibility) -> void

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

Expand Down
99 changes: 99 additions & 0 deletions test/rbs/definition_builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2000,4 +2000,103 @@ def test_expand_alias2
end
end
end

def test_singleton_public_private
SignatureManager.new do |manager|
manager.files.merge!(Pathname("foo.rbs") => <<-EOF)
class C
private
def self.a: () -> void
end

module M
private
def self.b: () -> void
end
EOF
manager.build do |env|
builder = DefinitionBuilder.new(env: env)

builder.build_singleton(type_name("::C")).tap do |definition|
definition.methods[:a].tap do |a|
assert_predicate a, :public?
end
end

builder.build_singleton(type_name("::M")).tap do |definition|
definition.methods[:b].tap do |b|
assert_predicate b, :public?
end
end
end
end
end

def test_module_function
SignatureManager.new do |manager|
manager.files.merge!(Pathname("foo.rbs") => <<-EOF)
class C
def self?.a: () -> void
end

module M
def self?.b: () -> void
end
EOF
manager.build do |env|
builder = DefinitionBuilder.new(env: env)

builder.build_singleton(type_name("::C")).tap do |definition|
definition.methods[:a].tap do |a|
assert_predicate a, :public?
end
end

builder.build_instance(type_name("::C")).tap do |definition|
definition.methods[:a].tap do |a|
assert_predicate a, :private?
end
end

builder.build_singleton(type_name("::M")).tap do |definition|
definition.methods[:b].tap do |b|
assert_predicate b, :public?
end
end

builder.build_instance(type_name("::M")).tap do |definition|
definition.methods[:b].tap do |b|
assert_predicate b, :private?
end
end
end
end
end

def test_alias_visibility
SignatureManager.new do |manager|
manager.files.merge!(Pathname("foo.rbs") => <<-EOF)
class C
def self?.a: () -> void

public

alias b a
end
EOF
manager.build do |env|
builder = DefinitionBuilder.new(env: env)

builder.build_instance(type_name("::C")).tap do |definition|
definition.methods[:a].tap do |a|
assert_predicate a, :private?
end

definition.methods[:b].tap do |b|
assert_predicate b, :private?
end
end
end
end
end
end
19 changes: 11 additions & 8 deletions test/rbs/method_builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def bar: () -> String
assert_equal :bar, member.name
end

assert_equal [:public], bar.accessibilities
assert_equal [], bar.accessibilities
end
end
end
Expand Down Expand Up @@ -233,7 +233,7 @@ def self.bar: () -> String
assert_equal :bar, member.name
end

assert_equal [:public], bar.accessibilities
assert_equal [], bar.accessibilities
end
end
end
Expand Down Expand Up @@ -292,18 +292,21 @@ def world: () -> Integer
builder.build_interface(type_name("::_Foo")).tap do |methods|
assert_equal parse_type("::_Foo"), methods.type

methods.methods[:world].tap do |hello|
assert_instance_of MethodBuilder::Methods::Definition, hello
methods.methods[:world].tap do |world|
assert_instance_of MethodBuilder::Methods::Definition, world

assert_instance_of AST::Members::Alias, hello.original
assert_equal :hello, hello.original.old_name
assert_instance_of AST::Members::Alias, world.original
assert_equal :hello, world.original.old_name

assert_any!(hello.overloads, size: 1) do |member|
assert_any!(world.overloads, size: 1) do |member|
assert_instance_of AST::Members::MethodDefinition, member
assert_equal [parse_method_type("() -> ::Integer")], member.types
end

assert_equal :public, hello.accessibility
assert_equal [], world.accessibilities
assert_raises do
world.accessibility
end
end
end
end
Expand Down