Shopify / Shopify/tapioca

dsl command generates stubs in sorbet/rbi/dsl/ which are incompatible with Sorbet packages mode (--sorbet-packages)

Open
#2,586 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

help-wanted
Dominant language
Ruby
Stars
873
Forks
164
Avg merge
4d 27m
Merged PRs (30d)
9

Description

Summary

When using Sorbet's experimental packages mode (--sorbet-packages), tapioca's dsl command generates DSL stubs into sorbet/rbi/dsl/. If the class being stubbed belongs to an enrolled package, Sorbet raises error 5084 ("Superclasses may only be set on constants in the package that owns them") because the stub file lives in the root package but modifies constants owned by a different enrolled package.

Background

Sorbet packages mode enforces that only the package that owns a constant can set its superclass or add mixins to it. Ownership is determined by which __package.rb's directory hierarchy the file lives under.

When a pack (e.g., packs/payments/billing/) is enrolled into packages mode via a __package.rb, its constants — including Billing::Invoice — become owned by the Billing package. Tapioca's DSL stubs for those constants live in sorbet/rbi/dsl/billing/invoice.rbi, which belongs to a root catch-all package, not to Billing.

Reproduction

Given a pack enrolled in packages mode:

packs/payments/billing/
├── __package.rb              # class Billing < PackageSpec
└── app/models/
    └── billing/
        └── invoice.rb        # class Billing::Invoice < ApplicationRecord

And tapioca's generated DSL stub:

sorbet/rbi/dsl/billing/invoice.rbi
# sorbet/rbi/dsl/billing/invoice.rbi
# typed: true

class Billing::Invoice
  include GeneratedAssociationMethods   # 5084 here
  extend GeneratedAssociationMethods::ClassMethods  # 5084 here
end

class Billing::Invoice::DraftState < Billing::Invoice  # 5084 here
end

Running bin/srb tc with --sorbet-packages produces:

sorbet/rbi/dsl/billing/invoice.rbi:4: Superclasses may only be set on constants in the package that owns them https://srb.help/5084
sorbet/rbi/dsl/billing/invoice.rbi:5: `include` may only be used on constants in the package that owns them https://srb.help/5084
sorbet/rbi/dsl/billing/invoice.rbi:8: Superclasses may only be set on constants in the package that owns them https://srb.help/5084

This happens because sorbet/rbi/dsl/ belongs to the root package, while Billing::Invoice is owned by the Billing package. Sorbet's resolver enforces that only the owning package's files may set superclasses or add mixins to its constants.

Expected Behavior

Tapioca should support generating DSL stubs co-located with the pack they belong to, so that files modifying Billing::* constants live within the Billing package path:

packs/payments/billing/sorbet/rbi/dsl/billing/invoice.rbi
Proposed Solution

Add a configuration option to tapioca dsl to generate per-pack DSL stubs inside the pack directory rather than the global sorbet/rbi/dsl/ location. One approach:

  1. Pack-aware routing: Detect __package.rb files and, for each generated stub, check whether the constant's source file lives under a pack root. If so, write the stub to <pack_root>/sorbet/rbi/dsl/ instead of the global directory.

  2. tapioca.yml config key: An opt-in flag to enable this behaviour without breaking existing setups:

dsl:
  packaged_output: true   # route stubs into <pack_root>/sorbet/rbi/dsl/ when a __package.rb is present
Workaround

Globally suppress error 5084 (--suppress-error-code=5084), which defeats the purpose of packages mode enforcement, or manually move generated stubs into the relevant pack directory after each tapioca dsl run.

Environment
  • tapioca version: 0.17.10
  • sorbet / sorbet-runtime version: 0.6.13055

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the tapioca dsl command's output routing and the described __package.rb hierarchy; compare the global sorbet/rbi/dsl/ path with the expected per-pack path. Done means an opt-in configuration can place stubs for constants owned by an enrolled pack inside that pack, while preserving existing output behavior and avoiding Sorbet error 5084.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
cli, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.