rigortype 0.3.0 → 0.3.1

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/array.yml +416 -392
  4. data/data/builtins/ruby_core/file.yml +42 -42
  5. data/data/builtins/ruby_core/hash.yml +302 -302
  6. data/data/builtins/ruby_core/io.yml +191 -191
  7. data/data/builtins/ruby_core/numeric.yml +321 -366
  8. data/data/builtins/ruby_core/proc.yml +124 -124
  9. data/data/builtins/ruby_core/range.yml +21 -21
  10. data/data/builtins/ruby_core/rational.yml +39 -39
  11. data/data/builtins/ruby_core/re.yml +65 -65
  12. data/data/builtins/ruby_core/set.yml +106 -106
  13. data/data/builtins/ruby_core/struct.yml +14 -14
  14. data/data/core_overlay/string_scanner.rbs +6 -5
  15. data/docs/handbook/01-getting-started.md +22 -34
  16. data/docs/handbook/06-classes.md +1 -1
  17. data/docs/handbook/07-rbs-and-extended.md +76 -101
  18. data/docs/handbook/08-understanding-errors.md +114 -247
  19. data/docs/handbook/09-plugins.md +54 -144
  20. data/docs/handbook/README.md +5 -3
  21. data/docs/handbook/appendix-liskov.md +4 -2
  22. data/docs/handbook/appendix-phpstan.md +14 -7
  23. data/docs/handbook/appendix-steep.md +4 -2
  24. data/docs/handbook/appendix-type-theory.md +3 -1
  25. data/docs/manual/02-cli-reference.md +32 -0
  26. data/docs/manual/04-diagnostics.md +36 -4
  27. data/docs/manual/06-baseline.md +35 -1
  28. data/docs/manual/08-skills.md +6 -1
  29. data/docs/manual/09-editor-integration.md +3 -2
  30. data/docs/manual/plugins/rigor-actioncable.md +32 -0
  31. data/docs/manual/plugins/rigor-devise.md +4 -2
  32. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +21 -2
  33. data/lib/rigor/analysis/check_rules.rb +34 -13
  34. data/lib/rigor/analysis/run_cache_key.rb +10 -0
  35. data/lib/rigor/analysis/runner.rb +2 -1
  36. data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
  37. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
  38. data/lib/rigor/cache/store.rb +99 -24
  39. data/lib/rigor/cli/check_command.rb +12 -6
  40. data/lib/rigor/cli/check_invocation.rb +84 -0
  41. data/lib/rigor/cli/doctor_command.rb +6 -8
  42. data/lib/rigor/cli/skill_command.rb +21 -1
  43. data/lib/rigor/cli/skill_deep_probe.rb +172 -0
  44. data/lib/rigor/cli/skill_describe.rb +75 -9
  45. data/lib/rigor/environment/default_libraries.rb +5 -4
  46. data/lib/rigor/environment.rb +10 -1
  47. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -1
  48. data/lib/rigor/language_server/buffer_resolution.rb +6 -3
  49. data/lib/rigor/language_server/buffer_table.rb +46 -6
  50. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -0
  51. data/lib/rigor/language_server/incremental_sync.rb +159 -0
  52. data/lib/rigor/language_server/server.rb +19 -9
  53. data/lib/rigor/language_server.rb +1 -0
  54. data/lib/rigor/plugin/base.rb +29 -2
  55. data/lib/rigor/sig_gen/writer.rb +183 -47
  56. data/lib/rigor/version.rb +1 -1
  57. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
  58. data/sig/rigor/cache.rbs +6 -0
  59. data/sig/rigor/inference/void_origin.rbs +18 -0
  60. data/sig/rigor/plugin/base.rbs +4 -3
  61. metadata +7 -3
@@ -78,7 +78,9 @@ catalogue of bugs:
78
78
  - arithmetic that can be proved to raise (`5 / 0`);
79
79
  - arguments whose type does not satisfy a refined parameter
80
80
  contract;
81
- - a few more, all listed in
81
+ - a few more, all catalogued in
82
+ [manual — Diagnostics](../manual/04-diagnostics.md) and
83
+ explained in
82
84
  [Chapter 8 — Understanding errors](08-understanding-errors.md).
83
85
 
84
86
  Rigor does **not** ask you to write type annotations in
@@ -170,9 +172,11 @@ on the offending line:
170
172
  The same identifier also drives the `disable:` and
171
173
  `severity_overrides:` config keys, and family wildcards work
172
174
  (`# rigor:disable call` suppresses every `call.*` rule on that
173
- line). The full list of families and rules, and when to reach
174
- for each suppression mechanism, is in
175
- [Chapter 8 — Understanding errors](08-understanding-errors.md).
175
+ line). The full list of families and rules is in
176
+ [manual Diagnostics](../manual/04-diagnostics.md);
177
+ [Chapter 8 — Understanding errors](08-understanding-errors.md)
178
+ is where to read about choosing between the suppression
179
+ mechanisms.
176
180
 
177
181
  ## The "no annotations" stance
178
182
 
@@ -278,36 +282,20 @@ non-default behaviours: extra `paths`, an alternative
278
282
  If you used the [AI-assisted setup](#the-fast-path-let-an-ai-agent-set-it-up),
279
283
  the `rigor-project-init` skill already wrote one for you. To
280
284
  write a starter by hand, `rigor init` emits `.rigor.dist.yml`
281
- — the project default that gets committed:
282
-
283
- ```yaml
284
- target_ruby: "3.4" # your project's Ruby not Rigor's own 4.0
285
-
286
- paths:
287
- - lib
288
-
289
- # signature_paths: [sig] # auto-detected when omitted
290
-
291
- severity_profile: balanced
292
-
293
- # severity_overrides:
294
- # call.argument-type-mismatch: warning
295
-
296
- # disable: []
297
-
298
- # plugins: []
299
- ```
300
-
301
- That is all most projects need. The remaining mechanics —
302
- editor autocomplete from the bundled JSON schema, the
303
- `.rigor.yml` vs `.rigor.dist.yml` precedence rule, `includes:`
304
- composition, and how path-bearing keys resolve relative to the
305
- declaring file — are covered in
306
- [Configuration](../manual/03-configuration.md). The one rule
307
- worth knowing up front: when a developer keeps a local
308
- `.rigor.yml`, it is the *sole* source of config for their runs
309
- (the two files are never merged automatically), so to extend
310
- the shared default it must list it under `includes:`.
285
+ — the project default that gets committed, with `target_ruby`,
286
+ `paths`, and a `severity_profile` filled in and the rest
287
+ commented out. That is all most projects need; the key
288
+ reference, the JSON-schema editor integration, and `includes:`
289
+ composition are in
290
+ [Configuration](../manual/03-configuration.md).
291
+
292
+ Two things are worth knowing up front, because they surprise
293
+ people. `target_ruby` is *your project's* Ruby, not the 4.0
294
+ Rigor itself runs on — those are independent on purpose. And
295
+ when a developer keeps a local `.rigor.yml`, it is the *sole*
296
+ source of config for their runs (the two files are never merged
297
+ automatically), so to extend the shared default it must list it
298
+ under `includes:`.
311
299
 
312
300
  ## What's next
313
301
 
@@ -97,7 +97,7 @@ both. The setter's argument type is whatever the call site
97
97
  provides. The `def.ivar-write-mismatch` rule (v0.1.2) checks
98
98
  that two writes to the same ivar in the same class body
99
99
  agree on the concrete class — see
100
- [Chapter 8 Understanding errors](08-understanding-errors.md)
100
+ [manualDiagnostics](../manual/04-diagnostics.md)
101
101
  for the rule's exact contract; it lets you catch an
102
102
  accidental rebind from `String` to `Array` in the same class
103
103
  without authoring an explicit ivar type.
@@ -100,27 +100,24 @@ Rigor sees a tightening.
100
100
 
101
101
  ## The directive grammar
102
102
 
103
- `RBS::Extended` lives at
103
+ There are seven per-method directives, and they divide by
104
+ *when* the fact they carry becomes true: `return:` and `param:`
105
+ retype the signature itself, `predicate-if-true` /
106
+ `predicate-if-false` narrow a variable across the branches of a
107
+ condition, and `assert` / `assert-if-true` / `assert-if-false`
108
+ narrow one after the call returns. Each is one
109
+ `%a{rigor:v1:…}` annotation above the `def` it refines; they
110
+ stack, and order does not matter.
111
+
112
+ The exhaustive table — every directive, its payload syntax, and
113
+ what the `<type>` slot accepts (RBS class names, refinement
114
+ payloads, the parameterised and bounded forms, and where `~T`
115
+ negation is and is not allowed) — is
116
+ [manual — RBS::Extended annotations](../manual/16-rbs-extended-annotations.md#per-method-directives);
117
+ the normative rules for conflicts, merging and provenance are
104
118
  [`docs/type-specification/rbs-extended.md`](../type-specification/rbs-extended.md).
105
- The per-method directives:
106
-
107
- | Directive | Says |
108
- | --- | --- |
109
- | `%a{rigor:v1:return: <type>}` | Tighten the method's return type. |
110
- | `%a{rigor:v1:param: <name> is <type>}` | Tighten a parameter's accepted type at the call site, AND narrow the local in the body. |
111
- | `%a{rigor:v1:assert <name> is <type>}` | After this method returns, the named local in the caller's scope is `<type>`. |
112
- | `%a{rigor:v1:predicate-if-true <name> is <type>}` | When this method returns truthy, the named local in the caller's scope is `<type>`. (Symmetric `predicate-if-false`.) |
113
- | `%a{rigor:v1:assert-if-true <name> is <type>}` | When this method returns a truthy value, the named local in the caller's scope is `<type>`. (Symmetric `assert-if-false` for `false` / `nil` returns.) |
114
-
115
- The `<type>` slot accepts:
116
-
117
- - **RBS class names** — `String`, `Integer`, `::Foo::Bar`.
118
- - **Imported refinement names** —
119
- `non-empty-string`, `lowercase-string`, `numeric-string`,
120
- `int<5, 10>`, `non-empty-array[Integer]`, `literal-string`,
121
-
122
- - **Negation `~T`** — `~lowercase-string` means
123
- "non-lowercase-string."
119
+ The rest of this chapter works through the directives one
120
+ example at a time.
124
121
 
125
122
  ## Refinement names
126
123
 
@@ -140,9 +137,8 @@ A short reference:
140
137
  ## Declaring conformance — `conforms-to`
141
138
 
142
139
  The directives above attach to a `def`. One more attaches to a
143
- `class` / `module` declaration and asserts the whole class
144
- satisfies a named structural interface, as a checked design
145
- assertion — whether or not any call site exercises it:
140
+ `class` / `module` declaration and asserts that the whole class
141
+ satisfies a named structural interface:
146
142
 
147
143
  ```rbs
148
144
  %a{rigor:v1:conforms-to _RewindableStream}
@@ -153,13 +149,21 @@ end
153
149
  ```
154
150
 
155
151
  If `MyBuffer` is missing a method the `_RewindableStream`
156
- interface requires (or the signature is incompatible), Rigor
157
- reports `rbs_extended.unsatisfied-conformance`; a class that
158
- satisfies the interface is silent. Multiple `conforms-to`
159
- directives on one class combine like an intersection of
160
- interfaces. The directive is purely additive implicit
161
- structural compatibility at call sites keeps working with or
162
- without it.
152
+ interface requires, Rigor reports
153
+ `rbs_extended.unsatisfied-conformance`; a class that satisfies
154
+ the interface is silent.
155
+
156
+ The reason to reach for this is that Rigor already checks
157
+ structural compatibility *implicitly*, wherever a value flows
158
+ into a position that needs a structural interface — so a class
159
+ nobody currently passes anywhere is never checked at all.
160
+ `conforms-to` turns the contract into a design assertion that
161
+ holds whether or not a call site exercises it, which is what a
162
+ library wants when the structural shape is the point. It is
163
+ purely additive: nothing that type-checked before stops doing
164
+ so because you added it. The stacking and diagnostic semantics
165
+ are in
166
+ [manual — `conforms-to`](../manual/16-rbs-extended-annotations.md#conforms-to--a-checked-structural-contract).
163
167
 
164
168
  ## Worked example: an assertion gate
165
169
 
@@ -181,6 +185,45 @@ end
181
185
  The runtime side is whatever `assert_non_empty` does (raise
182
186
  on empty, log, ...) — Rigor only reads the directive.
183
187
 
188
+ ## Worked example: asserting a negative
189
+
190
+ An assertion payload can be negated with `~T`, which is how you
191
+ model the "this is definitely not nil any more" helper every
192
+ codebase grows:
193
+
194
+ ```rbs
195
+ # sig/asserts.rbs
196
+ class Asserts
197
+ %a{rigor:v1:assert x is ~nil}
198
+ def self.not_nil: (untyped x) -> void
199
+ end
200
+ ```
201
+
202
+ ```ruby
203
+ # lib/configure.rb
204
+ def configure(maybe)
205
+ Asserts.not_nil(maybe)
206
+ # maybe: (~nil), so .upcase resolves on the narrowed type
207
+ maybe.upcase
208
+ end
209
+ ```
210
+
211
+ The target can also be the receiver itself — name it with
212
+ `self`, and the fact lands on the object the method was called
213
+ on:
214
+
215
+ ```rbs
216
+ class Connection
217
+ %a{rigor:v1:assert self is Connected}
218
+ def assert_connected!: () -> void
219
+ end
220
+ ```
221
+
222
+ If PHPDoc's `@phpstan-assert` family is your mental model for
223
+ all of this, the reading is nearly one-for-one; the mapping
224
+ table is in
225
+ [appendix: Coming from PHPStan](appendix-phpstan.md#the-phpstan-assert-family).
226
+
184
227
  ## Worked example: a type predicate
185
228
 
186
229
  ```rbs
@@ -343,74 +386,6 @@ dynamic boundaries (deserialisation, `eval`, plugin entry
343
386
  points). The static analysis you lose is made up by the
344
387
  honesty of admitting "this could be anything."
345
388
 
346
- ## Coming from PHPStan? The `@phpstan-assert` family
347
-
348
- If you are familiar with PHPStan's PHPDoc annotations,
349
- Rigor's `RBS::Extended` directives map directly onto the
350
- post-return / conditional narrowing primitives PHPStan calls
351
- "asserts" and "type-specifying functions." The behaviour is
352
- identical:
353
-
354
- > "After this method returns, the named argument is `T`."
355
-
356
- That is `@phpstan-assert` in PHPStan and
357
- `%a{rigor:v1:assert}` in Rigor.
358
-
359
- | PHPStan PHPDoc | Rigor RBS::Extended | Effect |
360
- | --- | --- | --- |
361
- | `@phpstan-assert T $x` | `%a{rigor:v1:assert x is T}` | After this method returns normally, the caller's `x` is `T`. |
362
- | `@phpstan-assert-if-true T $x` | `%a{rigor:v1:predicate-if-true x is T}` | If this method returns truthy, the caller's `x` is `T`. |
363
- | `@phpstan-assert-if-false T $x` | `%a{rigor:v1:predicate-if-false x is T}` | If this method returns falsey, the caller's `x` is `T`. |
364
- | `@phpstan-assert !T $x` | `%a{rigor:v1:assert x is ~T}` | After this method returns, the caller's `x` is **not** `T` (negation form). |
365
- | `@phpstan-assert-if-true !T $x` | `%a{rigor:v1:predicate-if-true x is ~T}` | Conditional negation. Symmetric with `predicate-if-false`. |
366
-
367
- Worked example — the canonical "assertNotNull" pattern from
368
- PHPStan's docs:
369
-
370
- ```rbs
371
- # sig/asserts.rbs
372
- class Asserts
373
- %a{rigor:v1:assert x is ~nil}
374
- def self.not_nil: (untyped x) -> void
375
- end
376
- ```
377
-
378
- ```ruby
379
- # lib/configure.rb
380
- def configure(maybe)
381
- Asserts.not_nil(maybe)
382
- # maybe: (~nil), so .upcase resolves on the narrowed type
383
- maybe.upcase
384
- end
385
- ```
386
-
387
- Self-targeted forms are supported too — the PHPStan
388
- analogue would be a method on `$this` that narrows
389
- `$this`. Name the receiver with `self`:
390
-
391
- ```rbs
392
- class Connection
393
- %a{rigor:v1:assert self is Connected}
394
- def assert_connected!: () -> void
395
- end
396
- ```
397
-
398
- Rigor's directive grammar covers what PHPStan ships in the
399
- `@phpstan-assert*` family. The directives **only fire from
400
- RBS** (per ADR-5: strict on returns, lenient on parameters);
401
- in PHPStan-land you can also write `@phpstan-assert` in
402
- PHPDoc directly above the function — Rigor's equivalent is
403
- the same RBS file's `def` line.
404
-
405
- If you need plugin-side equivalents (when the assertion is
406
- recognised by **call shape** rather than by sig — PHPStan's
407
- "Type-Specifying Extensions"), see
408
- [Chapter 9](09-plugins.md). The plugin contract surfaces
409
- the same `Fact(target_kind: :self)` and
410
- `Fact(target_kind: :parameter)` carriers that the directives
411
- use, so a plugin author writes the equivalent of a PHPStan
412
- `StaticMethodTypeSpecifyingExtension` from Ruby.
413
-
414
389
  ## When RBS cannot help — the plugin escape hatch
415
390
 
416
391
  When a method's behaviour depends on the **shape of its
@@ -422,6 +397,6 @@ see [Chapter 9](09-plugins.md) and the
422
397
 
423
398
  ## What's next
424
399
 
425
- Chapter 8 covers the rule catalogue — what each diagnostic
426
- means, when it fires, and how to suppress it when it is wrong
427
- or noisy.
400
+ Chapter 8 is about reading a diagnostic — what each rule
401
+ family claims, why one fires when you did not expect it, and
402
+ which layer to reach for when you want it quieter.