Skip to content

[perfect-ruby-on-rails] Introduce Steep Typechecking#227

Open
hayat01sh1da wants to merge 31 commits into
masterfrom
hayat01sh1da/perfect-ruby-on-rails/introduce-steep-typechecking
Open

[perfect-ruby-on-rails] Introduce Steep Typechecking#227
hayat01sh1da wants to merge 31 commits into
masterfrom
hayat01sh1da/perfect-ruby-on-rails/introduce-steep-typechecking

Conversation

@hayat01sh1da
Copy link
Copy Markdown
Owner

@hayat01sh1da hayat01sh1da commented Apr 11, 2026

1. Why (Purpose)

Rails applications are dynamically typed, so regressions in controllers, models, mailers, jobs and forms are only caught at runtime or by tests.
Adding rbs-inline (signatures derived from annotated source) together with Steep (the type checker) introduces static verification of method existence, arguments and return types across the codebase.

A key motivation is that an apparently small error count is usually misleading.
When the Rails framework signatures are absent, Steep degrades whole class hierarchies to untyped and silently suppresses everything that depends on them.
The objective is therefore not "make the reported errors disappear" but stand up a complete, reproducible type-checking pipeline so the check is genuinely green — framework types resolved, application code annotated, and the real type defects that surface afterwards actually fixed.

2. What (Procedures)

Introducing the pipeline follows a fixed sequence:

  1. Dependencies: add steep, rbs-inline and rbs_rails to the development/test bundle.
  2. Framework signatures: initialise and install an RBS collection (rbs_collection.yaml + lock) so framework and common-gem signatures are pulled from a shared, version-pinned source.
  3. Generated signatures: register the rbs_rails rake task to emit model and route-helper signatures, and run rbs-inline to emit signatures from annotated application and test code.
  4. Configuration: author the Steepfile with separate targets for application and test code, so permissive test-only signatures never weaken the strictly-checked application code.
  5. Hand-written shims: for libraries with no published RBS and for constructs the generators cannot express (test DSLs, authentication helpers, dynamically defined helpers, individual missing methods),add minimal hand-written signature files, kept in their own directory and loaded only by the target that needs them.
  6. Real fixes: resolve the genuine type defects that become visible once the framework is typed (nil-handling, ambiguous empty collections, unsupported syntax, etc.) by changing code where it is a real bug, or narrowing types where the upstream RBS is incomplete.
  7. Verify and record: run the check until it is green, repeat to confirm stability, and land the work as small semantic commits (dependencies → configuration → generated signatures → hand-written shims → code fixes).

Context-specific note: when the app runs only in containers, every step above is executed inside the container; when model signatures are generated for apps with a database, the database service must be up and prepared first.

3. How (Operation and Maintenance)

3-1. Run the check

Through the project's normal execution environment(locally or inside the container), invoking steep check.

3-2. Regenerate signatures

As a build step — install the RBS collection, run rbs-inline over the source, and run the rbs_rails task for models and routes. This is mandatory on a fresh checkout whenever generated signatures are treated as build output rather than committed.

3-3. Track vs. regenerate

  • Track: the Steepfile, the collection manifest and lock, the rake task registration, and all hand-written shims (force-track them if they live under an otherwise-ignored signature directory).
  • Do not track: the downloaded collection cache, regenerable generated signatures (when the project chooses to rebuild them), and the check's log output (a regenerable artifact).

3-4. Maintenance triggers

  • Upgrading the framework or gems → update and reinstall the collection, then patch shims where collection coverage lags the new version.
  • Adding or changing models or routes → re-run the rbs_rails task.
  • Adding or changing application or test code → re-run rbs-inline.
  • Using new library surface (auth, test DSL, etc.) not yet in a shim → extend the relevant hand-written shim.
  • Type-checker versions may not support the newest Ruby syntax; keep checked code within the supported subset.

4. Pros & Cons

4-2. Pros

  • Genuine end-to-end coverage (framework + application + models + routes), not merely silencing the initially reported errors.
  • Reproducible: signatures are regenerable from version-pinned, committed configuration.
  • The application/test target split keeps application code strictly checked while test-only relaxations stay quarantined.
  • Low behavioural risk: most fixes are signatures and config; code edits are minimal and semantically equivalent.
  • Catches regressions continuously once integrated into the workflow.

4-2. Cons

  • Hand-written shims are a standing maintenance surface and are deliberately permissive — they unblock the checker rather than fully model those libraries.
  • Shared signature collections lag new framework releases; version drift can reintroduce gaps after upgrades.
  • Generated signatures are a required build step, so a fresh clone is not green until regeneration runs.
  • The pipeline adds environment and setup cost (toolchain, and a database/container when those are required to generate signatures).
  • Type-checker support trails the newest Ruby syntax, constraining some modern constructs in checked code.
  • Force-tracking shims inside an otherwise-ignored directory is a deliberate exception to ignore conventions and is easy to overlook.

@hayat01sh1da hayat01sh1da self-assigned this Apr 11, 2026
hayat01sh1da and others added 18 commits April 15, 2026 04:57
Prefer single-quoted strings when you don't need string interpolation or special symbols.
Add an empty line after magic comments.
…ce-steep-typechecking

Co-authored-by: Copilot <copilot@github.com>
@hayat01sh1da hayat01sh1da force-pushed the hayat01sh1da/perfect-ruby-on-rails/introduce-steep-typechecking branch from 8a11a2b to b512cb3 Compare May 4, 2026 18:45
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