rigortype 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -20
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/docs/handbook/01-getting-started.md +311 -0
- data/docs/handbook/02-everyday-types.md +337 -0
- data/docs/handbook/03-narrowing.md +359 -0
- data/docs/handbook/04-tuples-and-shapes.md +321 -0
- data/docs/handbook/05-methods-and-blocks.md +339 -0
- data/docs/handbook/06-classes.md +305 -0
- data/docs/handbook/07-rbs-and-extended.md +427 -0
- data/docs/handbook/08-understanding-errors.md +373 -0
- data/docs/handbook/09-plugins.md +241 -0
- data/docs/handbook/10-sorbet.md +347 -0
- data/docs/handbook/11-sig-gen.md +312 -0
- data/docs/handbook/12-lightweight-hkt.md +333 -0
- data/docs/handbook/README.md +275 -0
- data/docs/handbook/appendix-elixir.md +370 -0
- data/docs/handbook/appendix-go.md +399 -0
- data/docs/handbook/appendix-java-csharp.md +470 -0
- data/docs/handbook/appendix-liskov.md +580 -0
- data/docs/handbook/appendix-mypy.md +370 -0
- data/docs/handbook/appendix-phpstan.md +338 -0
- data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
- data/docs/handbook/appendix-rust.md +446 -0
- data/docs/handbook/appendix-steep.md +336 -0
- data/docs/handbook/appendix-type-theory.md +1662 -0
- data/docs/handbook/appendix-typeprof.md +416 -0
- data/docs/handbook/appendix-typescript.md +332 -0
- data/docs/install.md +189 -0
- data/docs/llms.txt +72 -0
- data/docs/manual/01-installation.md +342 -0
- data/docs/manual/02-cli-reference.md +557 -0
- data/docs/manual/03-configuration.md +152 -0
- data/docs/manual/04-diagnostics.md +206 -0
- data/docs/manual/05-inspecting-types.md +109 -0
- data/docs/manual/06-baseline.md +104 -0
- data/docs/manual/07-plugins.md +92 -0
- data/docs/manual/08-skills.md +143 -0
- data/docs/manual/09-editor-integration.md +245 -0
- data/docs/manual/10-mcp-server.md +532 -0
- data/docs/manual/11-ci.md +274 -0
- data/docs/manual/12-caching.md +116 -0
- data/docs/manual/13-troubleshooting.md +120 -0
- data/docs/manual/14-rails-quickstart.md +332 -0
- data/docs/manual/15-type-protection-coverage.md +204 -0
- data/docs/manual/16-rbs-extended-annotations.md +190 -0
- data/docs/manual/17-driving-improvement.md +160 -0
- data/docs/manual/README.md +87 -0
- data/docs/manual/ci-templates/README.md +58 -0
- data/docs/manual/plugins/README.md +86 -0
- data/docs/manual/plugins/rigor-actioncable.md +78 -0
- data/docs/manual/plugins/rigor-actionmailer.md +74 -0
- data/docs/manual/plugins/rigor-actionpack.md +80 -0
- data/docs/manual/plugins/rigor-activejob.md +58 -0
- data/docs/manual/plugins/rigor-activerecord.md +102 -0
- data/docs/manual/plugins/rigor-activestorage.md +74 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
- data/docs/manual/plugins/rigor-devise.md +70 -0
- data/docs/manual/plugins/rigor-dry-schema.md +56 -0
- data/docs/manual/plugins/rigor-dry-struct.md +60 -0
- data/docs/manual/plugins/rigor-dry-types.md +59 -0
- data/docs/manual/plugins/rigor-dry-validation.md +62 -0
- data/docs/manual/plugins/rigor-factorybot.md +76 -0
- data/docs/manual/plugins/rigor-graphql.md +89 -0
- data/docs/manual/plugins/rigor-hanami.md +83 -0
- data/docs/manual/plugins/rigor-mangrove.md +73 -0
- data/docs/manual/plugins/rigor-minitest.md +86 -0
- data/docs/manual/plugins/rigor-pundit.md +72 -0
- data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
- data/docs/manual/plugins/rigor-rails-routes.md +94 -0
- data/docs/manual/plugins/rigor-rails.md +44 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
- data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
- data/docs/manual/plugins/rigor-rspec.md +86 -0
- data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
- data/docs/manual/plugins/rigor-sidekiq.md +78 -0
- data/docs/manual/plugins/rigor-sinatra.md +61 -0
- data/docs/manual/plugins/rigor-sorbet.md +63 -0
- data/docs/manual/plugins/rigor-statesman.md +75 -0
- data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
- data/exe/rigor +1 -1
- data/lib/rigor/analysis/incremental_session.rb +4 -2
- data/lib/rigor/analysis/run_stats.rb +13 -1
- data/lib/rigor/analysis/runner.rb +54 -12
- data/lib/rigor/cli/check_command.rb +26 -3
- data/lib/rigor/cli/coverage_command.rb +67 -92
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration.rb +12 -0
- data/lib/rigor/environment/rbs_loader.rb +27 -0
- data/lib/rigor/environment.rb +49 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/inference/statement_evaluator.rb +27 -0
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +98 -38
- data/lib/rigor/protection/mutator.rb +21 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ask/SKILL.md +172 -0
- data/skills/rigor-doctor/SKILL.md +87 -0
- data/skills/rigor-editor-setup/SKILL.md +114 -0
- data/skills/rigor-mcp-setup/SKILL.md +117 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
- data/skills/rigor-next-steps/SKILL.md +113 -0
- data/skills/rigor-plugin-tune/SKILL.md +79 -0
- data/skills/rigor-protection-uplift/SKILL.md +133 -0
- data/skills/rigor-rbs-setup/SKILL.md +128 -0
- data/skills/rigor-upgrade/SKILL.md +79 -0
- metadata +120 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Appendix — Coming from Steep
|
|
2
|
+
|
|
3
|
+
[Steep](https://github.com/soutaro/steep) is the established
|
|
4
|
+
Ruby static type checker, and the de-facto reference
|
|
5
|
+
implementation of RBS-driven analysis. If you have used Steep,
|
|
6
|
+
the most important thing to know is that **Rigor reads the
|
|
7
|
+
same `.rbs` files** — your existing signatures port over
|
|
8
|
+
unchanged. The two tools are complementary, not exclusive.
|
|
9
|
+
|
|
10
|
+
This appendix is for users who already think in Steep
|
|
11
|
+
vocabulary and want to know which Rigor concept matches which
|
|
12
|
+
Steep concept.
|
|
13
|
+
|
|
14
|
+
## The five-second pitch
|
|
15
|
+
|
|
16
|
+
| Question | Steep | Rigor |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Source of types | `.rbs` files (mandatory at boundaries) | `.rbs` files (optional — inference fills gaps) |
|
|
19
|
+
| Annotations in `.rb` | `# @type` comments, type assertions | Almost none — `assert_type` / `dump_type` are introspection helpers |
|
|
20
|
+
| Coverage requirement | Steepfile's `check`/`signature` directives demand annotated targets | None — `rigor check lib` works with zero `.rbs` |
|
|
21
|
+
| Default for unannotated code | Errors when you ask Steep to check it | Inferred precisely or `Dynamic[top]` |
|
|
22
|
+
| Tool focus | Strong typing on opt-in surface | Best-effort precision on every file |
|
|
23
|
+
| Diagnostic philosophy | Surface all type-shape mismatches | Stay silent unless the bug is provable |
|
|
24
|
+
|
|
25
|
+
If Steep's slogan is "Ruby with optional manifest types,"
|
|
26
|
+
Rigor's is "Ruby with proven facts." The two are designed for
|
|
27
|
+
overlapping but distinct workflows.
|
|
28
|
+
|
|
29
|
+
## Both consume RBS: the common ground
|
|
30
|
+
|
|
31
|
+
This is the headline. RBS is Ruby's standard signature
|
|
32
|
+
language; both Steep and Rigor read it as their canonical
|
|
33
|
+
type source. A `.rbs` file you wrote for Steep works in Rigor
|
|
34
|
+
without changes:
|
|
35
|
+
|
|
36
|
+
```rbs
|
|
37
|
+
# sig/slug.rbs
|
|
38
|
+
class Slug
|
|
39
|
+
def normalise: (String) -> String
|
|
40
|
+
def self.default_length: () -> Integer
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Steep checks the body of `Slug#normalise` against this sig and
|
|
45
|
+
errors when the return type drifts. Rigor checks the same
|
|
46
|
+
thing under the `def.return-type-mismatch` rule (Chapter 8).
|
|
47
|
+
Both tools agree on the contract.
|
|
48
|
+
|
|
49
|
+
The tools diverge on what they layer on top:
|
|
50
|
+
|
|
51
|
+
- **Steep** adds method-body type-checking and a strict
|
|
52
|
+
"every method on the path must have a sig" expectation
|
|
53
|
+
(configurable, but the default).
|
|
54
|
+
- **Rigor** adds inference everywhere (so missing sigs
|
|
55
|
+
produce `Dynamic[top]`, not errors), refinement carriers,
|
|
56
|
+
constant folding, and plugin-side narrowing.
|
|
57
|
+
|
|
58
|
+
## Type vocabulary — the RBS-level mapping is identity
|
|
59
|
+
|
|
60
|
+
Because both tools speak RBS, the type vocabulary at the
|
|
61
|
+
declaration level is the same:
|
|
62
|
+
|
|
63
|
+
| RBS form | Steep | Rigor |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `String` | `String` | `Nominal[String]` (display: `String`) |
|
|
66
|
+
| `Integer?` | `Integer \| nil` | `Integer \| Constant<nil>` (display: `Integer?`) |
|
|
67
|
+
| `Array[Integer]` | `Array[Integer]` | `Array[Integer]` |
|
|
68
|
+
| `[Integer, String]` (tuple) | tuple | `Tuple[Integer, String]` |
|
|
69
|
+
| `{name: String, age: Integer}` (record) | record | `HashShape{name: String, age: Integer}` |
|
|
70
|
+
| `_Comparable` (interface) | structural | structural facet |
|
|
71
|
+
| `untyped` | `untyped` | `Dynamic[top]` (display: `untyped`) |
|
|
72
|
+
| `bot` | `bot` | `Bot` |
|
|
73
|
+
| `top` | `top` | `Top` |
|
|
74
|
+
| `bool` | `bool` | `Constant<true> \| Constant<false>` (display: `bool`) |
|
|
75
|
+
| `void` | `void` | `void` |
|
|
76
|
+
|
|
77
|
+
Rigor's internal type carriers (`Type::Constant`,
|
|
78
|
+
`Type::IntegerRange`, `Type::Refined`, `Type::Tuple`,
|
|
79
|
+
`Type::HashShape`) do NOT exist in Steep's surface. They are
|
|
80
|
+
**erased to the RBS-equivalent** at the boundary, so a method
|
|
81
|
+
declared `-> String` in RBS still satisfies its caller's
|
|
82
|
+
expectation even if Rigor knows the result is
|
|
83
|
+
`non-empty-lowercase-string` internally.
|
|
84
|
+
|
|
85
|
+
This erasure contract is documented at
|
|
86
|
+
[`docs/type-specification/rbs-erasure.md`](../type-specification/rbs-erasure.md).
|
|
87
|
+
|
|
88
|
+
## Annotations in `.rb` source
|
|
89
|
+
|
|
90
|
+
Steep recognises a small set of in-source type annotations:
|
|
91
|
+
|
|
92
|
+
| Steep `.rb` annotation | Rigor equivalent |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `# @type var x: Integer` | (no analogue in core) |
|
|
95
|
+
| `# @type self: Foo` | `T.bind(self, Foo)` via `rigor-sorbet` plugin |
|
|
96
|
+
| `# @type method foo: () -> String` | RBS file declaration |
|
|
97
|
+
| `_ = x` (type cast) | `T.cast(x, T)` via `rigor-sorbet` plugin |
|
|
98
|
+
|
|
99
|
+
Rigor deliberately does NOT ship in-source annotation comments
|
|
100
|
+
in core. The reasoning (ADR-0, ADR-5, robustness principle):
|
|
101
|
+
|
|
102
|
+
1. **`.rb` files stay clean for runtime developers.** Authors
|
|
103
|
+
who do not care about types do not see type comments.
|
|
104
|
+
2. **Annotations belong at the boundary.** Rigor's stance is
|
|
105
|
+
that the public contract lives in `.rbs`, not at every
|
|
106
|
+
variable assignment.
|
|
107
|
+
3. **Inference covers most variables.** When `x = some_call`,
|
|
108
|
+
Rigor knows the return type of `some_call` — there is
|
|
109
|
+
nothing to annotate.
|
|
110
|
+
|
|
111
|
+
When you genuinely want in-source assertions (you are
|
|
112
|
+
migrating from Steep or Sorbet, or you have a complex
|
|
113
|
+
narrowing the engine cannot follow), the `rigor-sorbet`
|
|
114
|
+
plugin is the supported path — see Chapter 10.
|
|
115
|
+
|
|
116
|
+
## Steepfile vs `.rigor.yml`
|
|
117
|
+
|
|
118
|
+
| Steep `Steepfile` | Rigor `.rigor.yml` |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `target :lib do ... end` | `paths: [lib]` |
|
|
121
|
+
| `check "lib"` | covered by `paths:` |
|
|
122
|
+
| `signature "sig"` | `signature_paths: [sig]` (auto-detected when omitted) |
|
|
123
|
+
| `library "set", "json"` | `rbs_collection.lock.yaml` (RBS gem-collection) — same mechanism Steep uses |
|
|
124
|
+
| `configure_code_diagnostics` | `severity_overrides:`, `severity_profile:` |
|
|
125
|
+
| Multiple targets per Steepfile | Multiple `paths:` entries (single profile per project) |
|
|
126
|
+
|
|
127
|
+
The biggest config difference: Steep's per-target structure
|
|
128
|
+
lets you check `lib/` strictly and `app/` permissively in the
|
|
129
|
+
same project. Rigor's profile is project-wide, with per-rule
|
|
130
|
+
and per-file overrides for granularity.
|
|
131
|
+
|
|
132
|
+
## Severity model
|
|
133
|
+
|
|
134
|
+
Both tools have severity controls; the shapes are slightly
|
|
135
|
+
different.
|
|
136
|
+
|
|
137
|
+
| Steep | Rigor |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `configure_code_diagnostics(D::Ruby.strict)` per target | `severity_profile: strict` project-wide |
|
|
140
|
+
| `D::Ruby.lenient` / `default` / `strict` / `all_error` | `lenient` / `balanced` / `strict` |
|
|
141
|
+
| Per-diagnostic severity in Steepfile | `severity_overrides:` in `.rigor.yml` |
|
|
142
|
+
| `D::Ruby::UnknownConstant = :error` | `severity_overrides: { call.undefined-method: error }` |
|
|
143
|
+
|
|
144
|
+
The rule identifiers do not align 1:1 — Steep's are class
|
|
145
|
+
names, Rigor's are dotted families. The conceptual model is
|
|
146
|
+
the same: a default level, plus per-rule promotion / demotion.
|
|
147
|
+
|
|
148
|
+
## Diagnostic vocabulary
|
|
149
|
+
|
|
150
|
+
Steep's diagnostic catalogue and Rigor's overlap for the same
|
|
151
|
+
underlying conditions, but the names differ.
|
|
152
|
+
|
|
153
|
+
| Steep | Rigor |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| `Ruby::NoMethod` | `call.undefined-method` |
|
|
156
|
+
| `Ruby::ArgumentTypeMismatch` | `call.argument-type-mismatch` |
|
|
157
|
+
| `Ruby::IncompatibleAssignment` | (covered by `def.ivar-write-mismatch` for instance variables; locals are not flagged) |
|
|
158
|
+
| `Ruby::MethodBodyTypeMismatch` | `def.return-type-mismatch` |
|
|
159
|
+
| `Ruby::UnknownConstant` | (covered by `call.undefined-method` against the receiver class) |
|
|
160
|
+
| `Ruby::UnexpectedKeywordArgument` | `call.argument-type-mismatch` (keyword binding flows through the same rule) |
|
|
161
|
+
| `Ruby::IncompatibleTypeCase` | (no direct analogue today) |
|
|
162
|
+
|
|
163
|
+
A practical implication: a project that runs both Steep and
|
|
164
|
+
Rigor will see overlapping diagnostics on shape errors and
|
|
165
|
+
complementary diagnostics on the things each tool catches that
|
|
166
|
+
the other does not. The
|
|
167
|
+
[`docs/notes/20260503-steep-cross-check-triage.md`](../notes/20260503-steep-cross-check-triage.md)
|
|
168
|
+
note is a worked example — Steep and Rigor were run against
|
|
169
|
+
the same project and the diagnostic streams categorised.
|
|
170
|
+
|
|
171
|
+
## Suppression
|
|
172
|
+
|
|
173
|
+
| Steep | Rigor |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `# steep:ignore` | `# rigor:disable all` |
|
|
176
|
+
| `# steep:ignore Ruby::NoMethod` | `# rigor:disable call.undefined-method` |
|
|
177
|
+
| (no file-scope syntax) | `# rigor:disable-file <rule>` |
|
|
178
|
+
| `Steepfile`: per-target `ignore_paths:` (path-scoped) | `.rigor.yml`: `exclude:` (path-scoped); `disable:` is the rule-scoped axis |
|
|
179
|
+
|
|
180
|
+
Rigor's suppression vocabulary is closer to PHPStan's and
|
|
181
|
+
RuboCop's than to Steep's, but the intent matches.
|
|
182
|
+
|
|
183
|
+
## "No annotations needed" — the largest practical difference
|
|
184
|
+
|
|
185
|
+
Steep, by default, expects every method on the checked path
|
|
186
|
+
to have an RBS sig (or to opt out via `# @type` annotations).
|
|
187
|
+
Running `steep check` on a project with no `sig/` directory
|
|
188
|
+
produces lots of "missing sig" reports.
|
|
189
|
+
|
|
190
|
+
Rigor, by default, infers what it can and stays silent when it
|
|
191
|
+
cannot. Running `rigor check lib` on a project with no `sig/`
|
|
192
|
+
directory produces a small number of high-confidence
|
|
193
|
+
diagnostics — the methods Rigor was able to prove unsound from
|
|
194
|
+
the body alone.
|
|
195
|
+
|
|
196
|
+
This is by design (ADR-0). The two tools serve different
|
|
197
|
+
adoption stages:
|
|
198
|
+
|
|
199
|
+
- **Greenfield, type-discipline-from-day-one project.** Steep
|
|
200
|
+
is excellent. Write the RBS first; check the body against
|
|
201
|
+
it.
|
|
202
|
+
- **Existing codebase, gradual hardening.** Rigor is excellent.
|
|
203
|
+
Start with zero `.rbs`, get diagnostics on the worst bugs
|
|
204
|
+
immediately, add `.rbs` only where inference cannot see far
|
|
205
|
+
enough.
|
|
206
|
+
- **Both at once.** Run them side by side. They share input
|
|
207
|
+
(the same RBS). Steep's diagnostic stream and Rigor's
|
|
208
|
+
diagnostic stream complement each other.
|
|
209
|
+
|
|
210
|
+
## What Steep has and Rigor does not
|
|
211
|
+
|
|
212
|
+
- **`@type` comments in source.** Whatever your stance on
|
|
213
|
+
in-source annotations, Steep ships a richer surface for
|
|
214
|
+
them. `# @type var x: Integer`, `# @type self: Foo`, and
|
|
215
|
+
the `_ = x` cast operator have no Rigor-core equivalent.
|
|
216
|
+
The `rigor-sorbet` plugin fills the gap (Chapter 10).
|
|
217
|
+
- **Method-body type checking against declared params.** Steep
|
|
218
|
+
enforces "every reference to parameter `x` inside the body
|
|
219
|
+
agrees with the declared `x: Integer`." Rigor's analogous
|
|
220
|
+
check is `def.return-type-mismatch`; the parameter-side check
|
|
221
|
+
is comparable but more conservative (RBS-erased view).
|
|
222
|
+
- **Tighter generics inference.** Steep's generic instantiation
|
|
223
|
+
in chained calls is more aggressive than Rigor's today.
|
|
224
|
+
- **Diagnostic taxonomy maturity.** Steep's diagnostic
|
|
225
|
+
catalogue has had more years to settle; Rigor's is smaller
|
|
226
|
+
and growing.
|
|
227
|
+
|
|
228
|
+
## What Rigor has and Steep does not
|
|
229
|
+
|
|
230
|
+
- **Inference without RBS.** A `lib/` directory with zero
|
|
231
|
+
`.rbs` files produces useful Rigor output. Steep needs sigs.
|
|
232
|
+
- **Refinement carriers with automatic narrowing.**
|
|
233
|
+
`non-empty-string` from `unless s.empty?`, `positive-int`
|
|
234
|
+
from `n > 0`, etc.
|
|
235
|
+
- **Constant folding through method calls.** `"foo".upcase`
|
|
236
|
+
resolves to `Constant<"FOO">`, not just `String`. Steep's
|
|
237
|
+
literal types are narrower than Rigor's.
|
|
238
|
+
- **Plugin-side return-type contributions.** Steep does not
|
|
239
|
+
have an equivalent to Rigor's `dynamic_return` —
|
|
240
|
+
if a domain DSL's return type depends on the literal first
|
|
241
|
+
argument, Rigor models it; Steep does not.
|
|
242
|
+
- **Sorbet-input adapter.** A `rigor-sorbet` migration is
|
|
243
|
+
zero-cost for projects mid-Sorbet (`sig { ... }` blocks and
|
|
244
|
+
RBI files become inputs to Rigor's catalog). Steep does not
|
|
245
|
+
read Sorbet sigs.
|
|
246
|
+
- **Cache-driven incremental analysis.** Rigor's per-file
|
|
247
|
+
cache survives across runs and across machine boundaries
|
|
248
|
+
(ADR-6). Steep's incremental story is improving but not
|
|
249
|
+
yet at parity.
|
|
250
|
+
|
|
251
|
+
## A coexistence pattern
|
|
252
|
+
|
|
253
|
+
A common, low-friction setup for a project that wants both
|
|
254
|
+
checkers:
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
# .rigor.yml
|
|
258
|
+
paths: [lib]
|
|
259
|
+
severity_profile: balanced
|
|
260
|
+
# signature_paths is auto-detected; sig/ is shared with Steep
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# Steepfile
|
|
265
|
+
target :lib do
|
|
266
|
+
check "lib"
|
|
267
|
+
signature "sig"
|
|
268
|
+
configure_code_diagnostics D::Ruby.default
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Both tools read the same `sig/`. CI runs `steep check` and
|
|
273
|
+
`rigor check lib` as separate steps. Each tool's
|
|
274
|
+
output goes to its own annotation channel. When they disagree
|
|
275
|
+
on the same line, the standing rule is: **if Steep flags it
|
|
276
|
+
and Rigor does not, investigate**. Steep tends to surface sig
|
|
277
|
+
drift that Rigor's RBS-erasure consciously absorbs; Rigor
|
|
278
|
+
tends to surface body-level facts that Steep does not check.
|
|
279
|
+
|
|
280
|
+
## A migration vignette
|
|
281
|
+
|
|
282
|
+
Suppose you maintain a project that has been on Steep for two
|
|
283
|
+
years. The `sig/` tree is comprehensive; `# @type` annotations
|
|
284
|
+
appear in a handful of files where inference fell short. You
|
|
285
|
+
want to add Rigor without uprooting anything.
|
|
286
|
+
|
|
287
|
+
Steps:
|
|
288
|
+
|
|
289
|
+
1. **Add Rigor as a dev dependency.** No changes to `sig/`.
|
|
290
|
+
2. **Run `rigor check lib` once.** You will see a
|
|
291
|
+
small number of new diagnostics — typically narrowing-aware
|
|
292
|
+
findings Steep does not produce (`flow.always-truthy-condition`,
|
|
293
|
+
`def.return-type-mismatch` against an `RBS::Extended`-tightened
|
|
294
|
+
return). Triage as bugs vs noise.
|
|
295
|
+
3. **Decide what to do with `# @type` annotations.** Rigor
|
|
296
|
+
ignores them (they are comments to the parser). Two
|
|
297
|
+
options:
|
|
298
|
+
a. Leave them — Steep keeps using them, Rigor ignores
|
|
299
|
+
them. No-op coexistence.
|
|
300
|
+
b. Convert to `T.let` / `T.cast` from the `rigor-sorbet`
|
|
301
|
+
plugin if you want Rigor to honour the assertion as
|
|
302
|
+
well.
|
|
303
|
+
4. **Add Rigor to CI.** Both checkers run; both gates must
|
|
304
|
+
pass before merge.
|
|
305
|
+
5. **Optionally tighten existing sigs with `RBS::Extended`.**
|
|
306
|
+
Steep treats `%a{rigor:v1:...}` as ordinary RBS comments;
|
|
307
|
+
Rigor treats them as refinement directives. The same
|
|
308
|
+
`.rbs` file produces stricter Rigor output and unchanged
|
|
309
|
+
Steep output.
|
|
310
|
+
|
|
311
|
+
The migration is low-friction because the
|
|
312
|
+
foundational assumption (RBS as the contract language) is
|
|
313
|
+
shared.
|
|
314
|
+
|
|
315
|
+
## What's next
|
|
316
|
+
|
|
317
|
+
You probably do not need to read the rest of this appendix
|
|
318
|
+
section sequentially. Three useful pointers:
|
|
319
|
+
|
|
320
|
+
- [Chapter 7 — RBS and `RBS::Extended`](07-rbs-and-extended.md)
|
|
321
|
+
if you want to see how the directive grammar layers on top
|
|
322
|
+
of the RBS you already write.
|
|
323
|
+
- [Chapter 8 — Understanding errors](08-understanding-errors.md)
|
|
324
|
+
for the rule catalogue, severity profiles, and baseline
|
|
325
|
+
diffing — the analogue to Steep's diagnostic config.
|
|
326
|
+
- [`docs/notes/20260503-steep-cross-check-triage.md`](../notes/20260503-steep-cross-check-triage.md)
|
|
327
|
+
for a worked side-by-side run of Steep and Rigor on the
|
|
328
|
+
same project (the project itself).
|
|
329
|
+
|
|
330
|
+
If you want to compare against another tool, the sibling
|
|
331
|
+
appendix pages cover [TypeProf](appendix-typeprof.md) — Ruby's
|
|
332
|
+
inference-first tool, the closest cousin to Rigor's own
|
|
333
|
+
`sig-gen` — plus [TypeScript](appendix-typescript.md),
|
|
334
|
+
[PHPStan](appendix-phpstan.md), [mypy](appendix-mypy.md),
|
|
335
|
+
[Java / C#](appendix-java-csharp.md), [Rust](appendix-rust.md),
|
|
336
|
+
[Go](appendix-go.md), and [Elixir](appendix-elixir.md).
|