Skip to content

Fix nested declarations in rbs prototype rbi#3029

Merged
soutaro merged 3 commits into
ruby:masterfrom
infiton:fix/rbi-nested-declarations
Jul 27, 2026
Merged

Fix nested declarations in rbs prototype rbi#3029
soutaro merged 3 commits into
ruby:masterfrom
infiton:fix/rbi-nested-declarations

Conversation

@infiton

@infiton infiton commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix rbs prototype rbi so nested Sorbet RBI declarations produce valid, semantically accurate RBS.

The converter previously flattened every class and module into a top-level declaration while leaving relative references unchanged. For example:

module Demo
  class Parent; end
  class Child < Parent; end
end

was emitted as class Demo::Child < Parent, where Parent no longer resolves relative to Demo. The same problem affected mixins and types in method signatures.

This PR preserves the RBI's lexical declaration nesting using the existing nested-declaration representation in the RBS AST. It also fixes the related conversion bugs found in the same reproduction.

Changes

  • Preserve nested class and module declarations instead of flattening them.
    • Relative superclasses, mixins, and signature types retain their lexical resolution context.
    • Absolute and explicitly qualified declaration names remain intact.
  • Emit nested constant declarations in their containing declaration.
    • This prevents duplicated paths such as Demo::Demo::Modes::VALUE.
  • Ignore the Sorbet-only T::Helpers extension, alongside T::Sig and T::Generic.
  • Convert T::Class[...] to untyped.
    • RBS has no exact generic equivalent for an arbitrary class object whose instances have the specified type.
    • Falling back avoids emitting an unresolved T::Class while not inventing an unsupported structural constraint.
  • Track class << self context so contained definitions become singleton methods.
  • Convert attr_reader, attr_writer, and attr_accessor, consuming their pending sig.
    • This prevents an attribute signature from becoming an overload on the following method.
  • Track declaration visibility and emit private/public transitions.
    • RBS does not model protected, so it uses the conservative private fallback.

Compatibility

This only changes RBI-to-RBS prototype conversion. It does not change the RBS language, parser, validator, or the meaning of existing .rbs files.

RBS::Prototype::RBI#decls now returns a nested AST for nested RBI declarations instead of a flattened list. For an empty declaration such as module Foo; module Bar; end; end, both forms define the same RBS constants, but callers that inspect the AST structure directly will observe the corrected shape. For declarations containing relative references, retaining that nesting is required for semantic correctness.

Tests

Regression tests cover each behavior independently and load the complete reported reproduction into an RBS environment, building its instance and singleton definitions.

bundle exec rake test TEST=test/rbs/rbi_prototype_test.rb
33 tests, 33 assertions, 0 failures, 0 errors

bundle exec rubocop lib/rbs/prototype/rbi.rb test/rbs/rbi_prototype_test.rb
2 files inspected, no offenses detected

infiton added 2 commits July 21, 2026 13:21
Assisted-By: devx/464d5233-705f-406e-a5d8-ad2479522aa9
Assisted-By: devx/464d5233-705f-406e-a5d8-ad2479522aa9
@infiton
infiton marked this pull request as ready for review July 21, 2026 22:36
RBS visibility sections do not apply to singleton members, so
`sync_visibility` inserted `public`/`private` markers that had no effect
and only added noise around `def self.` definitions.

Skip the section markers in singleton context, and give singleton members
their own member level visibility instead, which RBS does honor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Su358NxUABy9ejxwg9ph7B
@soutaro soutaro added this to the RBS 4.1 milestone Jul 27, 2026
@soutaro

soutaro commented Jul 27, 2026

Copy link
Copy Markdown
Member

@infiton Thank you for opening this PR. 🙏

I pushed a fix for visibility handling of singleton members. RBS visibility sections (private/public on their own line) don't apply to singleton members — only member level visibility does — so the sections this PR emitted around def self. had no effect, and the private from a class << self body was misleading. Singleton members now get their own visibility instead, as private def self.foo: () -> void, and a test covering methods and attributes comes with it.

@soutaro
soutaro added this pull request to the merge queue Jul 27, 2026
Merged via the queue into ruby:master with commit 4ca6229 Jul 27, 2026
26 of 27 checks passed
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.

3 participants