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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/array.yml +416 -392
- data/data/builtins/ruby_core/file.yml +42 -42
- data/data/builtins/ruby_core/hash.yml +302 -302
- data/data/builtins/ruby_core/io.yml +191 -191
- data/data/builtins/ruby_core/numeric.yml +321 -366
- data/data/builtins/ruby_core/proc.yml +124 -124
- data/data/builtins/ruby_core/range.yml +21 -21
- data/data/builtins/ruby_core/rational.yml +39 -39
- data/data/builtins/ruby_core/re.yml +65 -65
- data/data/builtins/ruby_core/set.yml +106 -106
- data/data/builtins/ruby_core/struct.yml +14 -14
- data/data/core_overlay/string_scanner.rbs +6 -5
- data/docs/handbook/01-getting-started.md +22 -34
- data/docs/handbook/06-classes.md +1 -1
- data/docs/handbook/07-rbs-and-extended.md +76 -101
- data/docs/handbook/08-understanding-errors.md +114 -247
- data/docs/handbook/09-plugins.md +54 -144
- data/docs/handbook/README.md +5 -3
- data/docs/handbook/appendix-liskov.md +4 -2
- data/docs/handbook/appendix-phpstan.md +14 -7
- data/docs/handbook/appendix-steep.md +4 -2
- data/docs/handbook/appendix-type-theory.md +3 -1
- data/docs/manual/02-cli-reference.md +32 -0
- data/docs/manual/04-diagnostics.md +36 -4
- data/docs/manual/06-baseline.md +35 -1
- data/docs/manual/08-skills.md +6 -1
- data/docs/manual/09-editor-integration.md +3 -2
- data/docs/manual/plugins/rigor-actioncable.md +32 -0
- data/docs/manual/plugins/rigor-devise.md +4 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +34 -13
- data/lib/rigor/analysis/run_cache_key.rb +10 -0
- data/lib/rigor/analysis/runner.rb +2 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
- data/lib/rigor/cache/store.rb +99 -24
- data/lib/rigor/cli/check_command.rb +12 -6
- data/lib/rigor/cli/check_invocation.rb +84 -0
- data/lib/rigor/cli/doctor_command.rb +6 -8
- data/lib/rigor/cli/skill_command.rb +21 -1
- data/lib/rigor/cli/skill_deep_probe.rb +172 -0
- data/lib/rigor/cli/skill_describe.rb +75 -9
- data/lib/rigor/environment/default_libraries.rb +5 -4
- data/lib/rigor/environment.rb +10 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -1
- data/lib/rigor/language_server/buffer_resolution.rb +6 -3
- data/lib/rigor/language_server/buffer_table.rb +46 -6
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -0
- data/lib/rigor/language_server/incremental_sync.rb +159 -0
- data/lib/rigor/language_server/server.rb +19 -9
- data/lib/rigor/language_server.rb +1 -0
- data/lib/rigor/plugin/base.rb +29 -2
- data/lib/rigor/sig_gen/writer.rb +183 -47
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
- data/sig/rigor/cache.rbs +6 -0
- data/sig/rigor/inference/void_origin.rbs +18 -0
- data/sig/rigor/plugin/base.rbs +4 -3
- 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
|
|
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
|
|
174
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
data/docs/handbook/06-classes.md
CHANGED
|
@@ -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
|
-
[
|
|
100
|
+
[manual — Diagnostics](../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
|
-
|
|
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
|
|
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
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
structural compatibility
|
|
162
|
-
|
|
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
|
|
426
|
-
|
|
427
|
-
|
|
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.
|