Skip to content

Keep visibility from bare sections in exported gem RBIs - #2751

Open
dduugg wants to merge 1 commit into
Shopify:mainfrom
dduugg:dduugg/flatten-exported-rbi-visibility
Open

dduugg wants to merge 1 commit into
Shopify:mainfrom
dduugg:dduugg/flatten-exported-rbi-visibility

Conversation

@dduugg

@dduugg dduugg commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Motivation

When a gem exports RBIs, methods declared under a bare private or protected section come out public in the generated gem RBI if tapioca doesn't also see them at runtime.

The RBI parser records a bare private as a standalone RBI::Private node and leaves the following methods with public visibility. RBI::Rewriters::Merge only carries over scopes and indexable nodes, so Gemfile::GemSpec#exported_rbi_tree drops those visibility nodes when it merges the exported files.

In this repo's own bundle, rubydex hits this since it is a native extension and many of its methods only come from its exported RBI. Today we generate public self.new on six classes whose exported RBI makes it private (Rubydex::Declaration, Rubydex::Definition, Rubydex::Document, and others), public helpers in Rubydex::Linter::Helpers::PathHelpers, and a public Rubydex::Linter::CustomRule#add_diagnostic that the gem declares protected.

I ran into this while reviewing #2743, whose private method filter can't see these methods.

Implementation

Call flatten_visibilities! on each parsed exported file before merging it, so every method carries its own visibility, which is what tapioca's pipeline produces and what the formatter's nest_non_public_members expects at print time.

I kept this in tapioca rather than changing RBI::Rewriters::Merge: bare visibility is positional, so the merge can't keep it meaningfully once nodes are reordered, and rbi already provides flatten_visibilities! for consumers that need per-method visibility. Method#compatible_with? doesn't compare visibility, so this can't introduce new merge conflicts.

Tests

Extended the existing "must generate a gem RBI with the ones exported from the gem by default" spec with a method declared only under a bare private in an exported RBI. It fails without the change (the method is generated as public) and passes with it. Regenerating rbi, spoom, prism, and rubydex with and without the change only differs for rubydex, as described above.

The RBI parser records a bare `private` or `protected` as a standalone
node and leaves the following methods public. The merge in
`exported_rbi_tree` drops those standalone nodes, so methods declared
only in an exported RBI were generated as public. Flatten each
exported file's visibilities onto its methods before merging.

This branch has not been deployed

No deployments
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