Skip to content

Support public/private visibility in inline RBS declarations - #3253

Open
ksss wants to merge 1 commit into
ruby:masterfrom
ksss:inline-visibility
Open

ksss wants to merge 1 commit into
ruby:masterfrom
ksss:inline-visibility

Conversation

@ksss

@ksss ksss commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Inline RBS declarations ignored method visibility, so every method and attribute defined in Ruby was public. This PR detects public/private calls in class/module bodies and applies them the same way RBS does.

class Foo
  def pub = 1

  private

  # @rbs () -> Integer
  def hidden = 2          # private

  private def also_hidden = 3   # private
  private attr_reader :secret   # private
end

Approach

Two new shapes of visibility, both mirroring what RBS already has:

  • private/public without arguments becomes AST::Ruby::Members::PrivateMember/PublicMember (subclasses of VisibilityMember), and switches the visibility of the instance methods and attributes defined after it, like private/public members in RBS.
  • private def foo / private attr_reader :foo is recorded as visibility on the DefMember/AttributeMember, like private def foo: ... in RBS. The parser keeps a current_visibility while visiting the arguments of the call, so # @rbs annotations and #: assertions on the def keep working.

Consumers:

  • MethodBuilder runs Ruby declarations through the same each_member_with_accessibility loop as RBS declarations (renamed from each_rbs_member_with_accessibility), using member.visibility || accessibility.
  • DefinitionBuilder uses the accessibility MethodBuilder computed instead of hardcoded :public for DefMember, so an unannotated private def foo overriding a public parent method is private.
  • Environment#resolve_ruby_member carries visibility through, and passes the visibility members as they are.
  • type_fingerprint includes the visibility, so changing it triggers re-type-checking.

As in Ruby and RBS, private without arguments does not affect def self. methods. private def self.foo is treated as a private singleton method, matching how RBS parses private def self.foo: ....

Out of scope

Documented as limitations in docs/inline.md:

  • private :foo, :bar (symbol arguments) is ignored
  • protected, private_class_method, and module_function

Verification with Steep

Steep takes the visibility from RBS::Definition::Method#private?, so no change on the Steep side is needed. With check(inline: true) on the snippet above plus:

Foo.new.pub
Foo.new.hidden
Foo.new.also_hidden
rbs steep check
master No type error detected
this branch Ruby::NoMethod on Foo.new.hidden and Foo.new.also_hidden

Tests

  • test/rbs/inline_parser_test.rb: bare private/public members, private def, private attr_*, @rbs skip, top-level, symbol arguments, and explicit receiver cases
  • test/rbs/definition_builder_test.rb: accessibility of instance/singleton methods and attributes, public def in a private section, overriding a public parent method with private def
  • test/rbs/inline_type_fingerprint_test.rb: visibility changes the fingerprint

🤖 Generated with Claude Code

Inline RBS declarations ignored the visibility of methods and attributes,
so every method defined in Ruby was `public`.

Detect the `public`/`private` calls in class/module bodies:

- `private`/`public` without arguments is added as
  `AST::Ruby::Members::PrivateMember`/`PublicMember`, and switches the
  visibility of the instance methods and attributes defined after it, the
  same as `private`/`public` members in RBS
- `private def foo`/`private attr_reader :foo` is recorded as the
  `visibility` of the `DefMember`/`AttributeMember`, the same as
  `private def foo: ...` in RBS

`MethodBuilder` tracks the visibility members in Ruby declarations with the
same loop as the RBS ones, and `DefinitionBuilder` uses the accessibility
it gives instead of hardcoded `:public`.

`private :foo, :bar`, `protected`, `private_class_method`, and
`module_function` are out of scope and documented as limitations.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant