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:
-
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.
-
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
Summary
When using Sorbet's experimental packages mode (
--sorbet-packages), tapioca'sdslcommand generates DSL stubs intosorbet/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 — includingBilling::Invoice— become owned by theBillingpackage. Tapioca's DSL stubs for those constants live insorbet/rbi/dsl/billing/invoice.rbi, which belongs to a root catch-all package, not toBilling.Reproduction
Given a pack enrolled in packages mode:
And tapioca's generated DSL stub:
Running
bin/srb tcwith--sorbet-packagesproduces:This happens because
sorbet/rbi/dsl/belongs to the root package, whileBilling::Invoiceis owned by theBillingpackage. 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 theBillingpackage path:Proposed Solution
Add a configuration option to
tapioca dslto generate per-pack DSL stubs inside the pack directory rather than the globalsorbet/rbi/dsl/location. One approach:Pack-aware routing: Detect
__package.rbfiles 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.tapioca.ymlconfig key: An opt-in flag to enable this behaviour without breaking existing setups: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 eachtapioca dslrun.Environment
tapiocaversion: 0.17.10sorbet/sorbet-runtimeversion: 0.6.13055