Skip to content

Handle payload superclass redefinition errors during gem RBI validation#2653

Open
zeeshankhan-05 wants to merge 1 commit into
Shopify:mainfrom
zeeshankhan-05:fix-issue-1834
Open

Handle payload superclass redefinition errors during gem RBI validation#2653
zeeshankhan-05 wants to merge 1 commit into
Shopify:mainfrom
zeeshankhan-05:fix-issue-1834

Conversation

@zeeshankhan-05

@zeeshankhan-05 zeeshankhan-05 commented Jun 23, 2026

Copy link
Copy Markdown

Motivation

Fixes #1834.

When Tapioca validates generated gem RBIs, some definitions can redefine a Sorbet payload class with a different superclass. Sorbet reports this as error 5012 and recommends adding --suppress-payload-superclass-redefinition-for=ConstantName to sorbet/config. Tapioca already auto-adjusts RBI strictness for error 4010, but it did not handle 5012 or inform users about the mismatch.

Closes #1834

Implementation

  • Keep the existing --stop-after namer validation pass for error 4010 auto-strictness behavior.
  • When auto_strictness is enabled, run a second full Sorbet pass to detect resolver-level error 5012 (the namer pass alone does not surface this error).
  • Parse the affected constant from Sorbet error output and append --suppress-payload-superclass-redefinition-for=ConstantName to sorbet/config when missing.
  • Use exact-line idempotency (config.lines(chomp: true).include?(flag)) so similar suppressions for other constants do not block the correct entry.
  • Preserve existing sorbet/config content when appending (no stripping of trailing whitespace or blank lines).
  • Print a user-facing message when a suppression is added or when it is already present, so the mismatch is not fixed silently.

Tests

Added an integration test in the describe "strictness" block of spec/tapioca/cli/gem_spec.rb (following maintainer guidance on #1834). The test exercises tapioca gem, verifies the suppression is written to sorbet/config, verifies user-facing output on first and second runs, and confirms idempotency.

Ran locally on the clean branch (1 commit ahead of upstream/main):

  • bin/test spec/tapioca/cli/gem_spec.rb — 67 tests, 494 assertions, 0 failures, 0 errors, 1 skip
  • bin/test spec/tapioca/cli/dsl_spec.rb — 69 tests, 335 assertions, 0 failures, 0 errors, 0 skips
  • bin/typecheck — pass
  • bin/style — pass

An earlier full-suite run (bin/test) completed with 794 tests, 0 failures, 2 errors, and 2 skips. The two errors were unrelated RubyLsp::Tapioca::Addon URI::InvalidURIError failures caused by spaces in the local directory path; they are not related to this change.

Co-authored-by: Cursor <cursoragent@cursor.com>
@zeeshankhan-05 zeeshankhan-05 requested a review from a team as a code owner June 23, 2026 03:37
@zeeshankhan-05

zeeshankhan-05 commented Jun 23, 2026

Copy link
Copy Markdown
Author

Hi @KaanOzkan. I appreciate the help with #1834 about using the describe "strictness" block in spec/tapioca/cli/gem_spec.rb and verifying suppression, user messaging, and idempotency. I followed that approach in this PR. Whenever you have some time, would you be able to review my PR?

@zeeshankhan-05

Copy link
Copy Markdown
Author

I have signed the CLA!

if errors.empty?
payload_superclass_errors = T.let([], T::Array[Spoom::Sorbet::Errors::Error])
if auto_strictness
payload_superclass_res = sorbet(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of 2 calls to Sorbet, I think we just need to use --stop-after resolver instead of namer and we can do one call to look at errors. There should also be a early return if there are no errors so that we don't parse the result twice.

flag = "#{SUPPRESS_PAYLOAD_SUPERCLASS_REDEFINITION_FLAG}=#{constant}"
config_path = Tapioca::SORBET_CONFIG_FILE
config = File.exist?(config_path) ? File.read(config_path) : ""
added = !config.lines(chomp: true).include?(flag)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
added = !config.lines(chomp: true).include?(flag)
flag_already_present = !config.lines(chomp: true).include?(flag)

We can also simplify the branching in the method. For example, you can print in the first conditional instead of creating a separate conditional for printing.

)
payload_superclass_errors = Spoom::Sorbet::Errors::Parser
.parse_string(payload_superclass_res.err || "")
.select { |error| error.code == 5012 }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5012 errors isn't enough of an indicator for payload superclass mismatch as it's used for any parent/subclass mismatch. You can use error.more directly here instead.

We need a new test for this too:

it "must not add a payload suppression for non-payload superclass redefinitions" do
  @project.write!("sorbet/rbi/dsl/non_payload_superclass_conflict.rbi", <<~RBI)
    # typed: true

    class NonPayloadSuperclassConflict < ::Object
    end
  RBI

  @project.write!("sorbet/rbi/gems/bar@0.3.0.rbi", <<~RBI)
    # typed: true

    class NonPayloadSuperclassConflict < ::String
    end
  RBI

  result = @project.tapioca("gem foo")

  refute_includes(
    @project.read("sorbet/config"),
    "--suppress-payload-superclass-redefinition-for=NonPayloadSuperclassConflict",
  )

  assert_empty_stderr(result)
  assert_success_status(result)
end

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.

Automatically resolve superclass redefinition errors while generating gem RBIs

2 participants