Skip to content

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

@dduugg

Description

@dduugg

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    help-wantedWe support this change, and welcome community contributions for it.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions