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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -20
  3. data/data/core_overlay/numeric.rbs +33 -0
  4. data/data/core_overlay/pathname.rbs +25 -0
  5. data/data/core_overlay/string_scanner.rbs +28 -0
  6. data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
  7. data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
  8. data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
  9. data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
  10. data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
  11. data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
  12. data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
  13. data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
  14. data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
  15. data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
  16. data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
  17. data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
  18. data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
  19. data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
  20. data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
  21. data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
  22. data/data/vendored_gem_sigs/redis/future.rbs +5 -0
  23. data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
  24. data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
  25. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
  26. data/docs/handbook/01-getting-started.md +311 -0
  27. data/docs/handbook/02-everyday-types.md +337 -0
  28. data/docs/handbook/03-narrowing.md +359 -0
  29. data/docs/handbook/04-tuples-and-shapes.md +321 -0
  30. data/docs/handbook/05-methods-and-blocks.md +339 -0
  31. data/docs/handbook/06-classes.md +305 -0
  32. data/docs/handbook/07-rbs-and-extended.md +427 -0
  33. data/docs/handbook/08-understanding-errors.md +373 -0
  34. data/docs/handbook/09-plugins.md +241 -0
  35. data/docs/handbook/10-sorbet.md +347 -0
  36. data/docs/handbook/11-sig-gen.md +312 -0
  37. data/docs/handbook/12-lightweight-hkt.md +333 -0
  38. data/docs/handbook/README.md +275 -0
  39. data/docs/handbook/appendix-elixir.md +370 -0
  40. data/docs/handbook/appendix-go.md +399 -0
  41. data/docs/handbook/appendix-java-csharp.md +470 -0
  42. data/docs/handbook/appendix-liskov.md +580 -0
  43. data/docs/handbook/appendix-mypy.md +370 -0
  44. data/docs/handbook/appendix-phpstan.md +338 -0
  45. data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
  46. data/docs/handbook/appendix-rust.md +446 -0
  47. data/docs/handbook/appendix-steep.md +336 -0
  48. data/docs/handbook/appendix-type-theory.md +1662 -0
  49. data/docs/handbook/appendix-typeprof.md +416 -0
  50. data/docs/handbook/appendix-typescript.md +332 -0
  51. data/docs/install.md +189 -0
  52. data/docs/llms.txt +72 -0
  53. data/docs/manual/01-installation.md +342 -0
  54. data/docs/manual/02-cli-reference.md +557 -0
  55. data/docs/manual/03-configuration.md +152 -0
  56. data/docs/manual/04-diagnostics.md +206 -0
  57. data/docs/manual/05-inspecting-types.md +109 -0
  58. data/docs/manual/06-baseline.md +104 -0
  59. data/docs/manual/07-plugins.md +92 -0
  60. data/docs/manual/08-skills.md +143 -0
  61. data/docs/manual/09-editor-integration.md +245 -0
  62. data/docs/manual/10-mcp-server.md +532 -0
  63. data/docs/manual/11-ci.md +274 -0
  64. data/docs/manual/12-caching.md +116 -0
  65. data/docs/manual/13-troubleshooting.md +120 -0
  66. data/docs/manual/14-rails-quickstart.md +332 -0
  67. data/docs/manual/15-type-protection-coverage.md +204 -0
  68. data/docs/manual/16-rbs-extended-annotations.md +190 -0
  69. data/docs/manual/17-driving-improvement.md +160 -0
  70. data/docs/manual/README.md +87 -0
  71. data/docs/manual/ci-templates/README.md +58 -0
  72. data/docs/manual/plugins/README.md +86 -0
  73. data/docs/manual/plugins/rigor-actioncable.md +78 -0
  74. data/docs/manual/plugins/rigor-actionmailer.md +74 -0
  75. data/docs/manual/plugins/rigor-actionpack.md +80 -0
  76. data/docs/manual/plugins/rigor-activejob.md +58 -0
  77. data/docs/manual/plugins/rigor-activerecord.md +102 -0
  78. data/docs/manual/plugins/rigor-activestorage.md +74 -0
  79. data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
  80. data/docs/manual/plugins/rigor-devise.md +70 -0
  81. data/docs/manual/plugins/rigor-dry-schema.md +56 -0
  82. data/docs/manual/plugins/rigor-dry-struct.md +60 -0
  83. data/docs/manual/plugins/rigor-dry-types.md +59 -0
  84. data/docs/manual/plugins/rigor-dry-validation.md +62 -0
  85. data/docs/manual/plugins/rigor-factorybot.md +76 -0
  86. data/docs/manual/plugins/rigor-graphql.md +89 -0
  87. data/docs/manual/plugins/rigor-hanami.md +83 -0
  88. data/docs/manual/plugins/rigor-mangrove.md +73 -0
  89. data/docs/manual/plugins/rigor-minitest.md +86 -0
  90. data/docs/manual/plugins/rigor-pundit.md +72 -0
  91. data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
  92. data/docs/manual/plugins/rigor-rails-routes.md +94 -0
  93. data/docs/manual/plugins/rigor-rails.md +44 -0
  94. data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
  95. data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
  96. data/docs/manual/plugins/rigor-rspec.md +86 -0
  97. data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
  98. data/docs/manual/plugins/rigor-sidekiq.md +78 -0
  99. data/docs/manual/plugins/rigor-sinatra.md +61 -0
  100. data/docs/manual/plugins/rigor-sorbet.md +63 -0
  101. data/docs/manual/plugins/rigor-statesman.md +75 -0
  102. data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
  103. data/exe/rigor +1 -1
  104. data/lib/rigor/analysis/incremental_session.rb +4 -2
  105. data/lib/rigor/analysis/run_stats.rb +13 -1
  106. data/lib/rigor/analysis/runner.rb +54 -12
  107. data/lib/rigor/cli/check_command.rb +26 -3
  108. data/lib/rigor/cli/coverage_command.rb +67 -92
  109. data/lib/rigor/cli/coverage_mutation.rb +149 -0
  110. data/lib/rigor/cli/docs_command.rb +248 -0
  111. data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
  112. data/lib/rigor/cli/fused_protection_report.rb +76 -0
  113. data/lib/rigor/cli/skill_command.rb +103 -41
  114. data/lib/rigor/cli/skill_describe.rb +346 -0
  115. data/lib/rigor/cli.rb +25 -3
  116. data/lib/rigor/config_audit.rb +152 -0
  117. data/lib/rigor/configuration.rb +12 -0
  118. data/lib/rigor/environment/rbs_loader.rb +27 -0
  119. data/lib/rigor/environment.rb +49 -1
  120. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
  121. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
  122. data/lib/rigor/inference/scope_indexer.rb +87 -89
  123. data/lib/rigor/inference/statement_evaluator.rb +27 -0
  124. data/lib/rigor/plugin/isolation.rb +5 -5
  125. data/lib/rigor/plugin/loader.rb +4 -2
  126. data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
  127. data/lib/rigor/protection/mutation_scanner.rb +98 -38
  128. data/lib/rigor/protection/mutator.rb +21 -0
  129. data/lib/rigor/protection/test_suite_oracle.rb +68 -0
  130. data/lib/rigor/signature_path_audit.rb +92 -0
  131. data/lib/rigor/version.rb +1 -1
  132. data/skills/rigor-ask/SKILL.md +172 -0
  133. data/skills/rigor-doctor/SKILL.md +87 -0
  134. data/skills/rigor-editor-setup/SKILL.md +114 -0
  135. data/skills/rigor-mcp-setup/SKILL.md +117 -0
  136. data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
  137. data/skills/rigor-next-steps/SKILL.md +113 -0
  138. data/skills/rigor-plugin-tune/SKILL.md +79 -0
  139. data/skills/rigor-protection-uplift/SKILL.md +133 -0
  140. data/skills/rigor-rbs-setup/SKILL.md +128 -0
  141. data/skills/rigor-upgrade/SKILL.md +79 -0
  142. metadata +120 -1
@@ -0,0 +1,305 @@
1
+ # Classes
2
+
3
+ This chapter covers class-side typing — what `self` means in
4
+ different positions, how constants are resolved, and how
5
+ Rigor reads `attr_*`, `Data.define`, and `Struct.new`
6
+ declarations. It is more reference than narrative: read it
7
+ through once, then jump back to the section you need.
8
+
9
+ ## Instance-side and class-side `self`
10
+
11
+ Inside an instance method body, `self` is a `Nominal[T]` of
12
+ the enclosing class:
13
+
14
+ ```ruby
15
+ class User
16
+ def name
17
+ self # Nominal[User]
18
+ end
19
+ end
20
+ ```
21
+
22
+ Inside a singleton method body (`def self.foo` or
23
+ `def User.foo`), `self` is a `Singleton[T]` — the class
24
+ object itself, not an instance:
25
+
26
+ ```ruby
27
+ class User
28
+ def self.find(id)
29
+ self # Singleton[User]
30
+ end
31
+ end
32
+
33
+ User # Singleton[User]
34
+ User.find(1) # Nominal[User] (declared by RBS)
35
+ User.new # Nominal[User]
36
+ ```
37
+
38
+ The distinction matters for method dispatch: instance methods
39
+ run on `Nominal[User]`, singleton methods run on
40
+ `Singleton[User]`. Rigor reads the right side of the colon in
41
+ RBS sigs (`def self.find: (Integer) -> User`) to know which
42
+ side a method lives on.
43
+
44
+ ## Constants
45
+
46
+ Constant lookup walks four sources, in this order:
47
+
48
+ 1. **Lexical scope.** If `Foo` is referenced inside
49
+ `class A; module B; ...`, Rigor looks for `A::B::Foo`,
50
+ `A::Foo`, `Foo`.
51
+ 2. **RBS-core and bundled stdlib.** `String`, `Integer`,
52
+ `Symbol`, `Array`, `Pathname`, `URI`, `OptParse`, `JSON`,
53
+ `YAML`, etc.
54
+ 3. **Project RBS.** `sig/` files in your project add to the
55
+ lookup.
56
+ 4. **In-source class discovery.** When no RBS exists, Rigor
57
+ walks `class Foo`, `module Bar`, and constant assignments
58
+ (`MAX = 100`).
59
+
60
+ ```ruby
61
+ MAX = 100
62
+ class Counter
63
+ def initial = MAX
64
+ end
65
+
66
+ Counter.new.initial # Constant<100> — the constant value
67
+ # propagates through the in-source
68
+ # class lookup
69
+ ```
70
+
71
+ For constants whose right-hand side Rigor can fold, the
72
+ constant carries a `Constant<value>` type. For others, it
73
+ carries the wider RBS-erased form.
74
+
75
+ ## `attr_reader`, `attr_writer`, `attr_accessor`
76
+
77
+ Rigor reads `attr_*` declarations and treats them as method
78
+ definitions. The reader's return type matches the
79
+ corresponding ivar's inferred type:
80
+
81
+ ```ruby
82
+ class User
83
+ attr_reader :name
84
+
85
+ def initialize(name)
86
+ @name = name
87
+ end
88
+ end
89
+
90
+ u = User.new("Alice")
91
+ u.name # Constant<"Alice"> — through in-source dispatch +
92
+ # ivar tracking
93
+ ```
94
+
95
+ `attr_writer` exposes the setter; `attr_accessor` exposes
96
+ both. The setter's argument type is whatever the call site
97
+ provides. The `def.ivar-write-mismatch` rule (v0.1.2) checks
98
+ that two writes to the same ivar in the same class body
99
+ agree on the concrete class — see
100
+ [Chapter 8 — Understanding errors](08-understanding-errors.md)
101
+ for the rule's exact contract; it lets you catch an
102
+ accidental rebind from `String` to `Array` in the same class
103
+ without authoring an explicit ivar type.
104
+
105
+ ## Instance variables across methods
106
+
107
+ Rigor accumulates ivar facts across all methods in a class:
108
+
109
+ ```ruby
110
+ class Counter
111
+ def initialize
112
+ @count = 0 # @count: Constant<0> after init
113
+ end
114
+
115
+ def bump
116
+ @count += 1 # @count rebound to int<1, max>
117
+ end
118
+
119
+ def value
120
+ @count # int<0, max> (union of seen writes)
121
+ end
122
+ end
123
+ ```
124
+
125
+ The ivar type at each read site is the union of every
126
+ statically-visible write — including writes from a different
127
+ method on the same class.
128
+
129
+ ## `Data.define`
130
+
131
+ `Data.define` produces a small immutable struct. Rigor
132
+ recognises the declaration and surfaces the constructor
133
+ arity, the per-field accessors, and the resulting class
134
+ type:
135
+
136
+ ```ruby
137
+ Point = Data.define(:x, :y)
138
+
139
+ p = Point.new(x: 3, y: 4)
140
+ assert_type("Point(x: 3, y: 4)", p)
141
+ assert_type("3", p.x)
142
+ assert_type("4", p.y)
143
+ ```
144
+
145
+ Member reads are not the only thing that folds. The standard
146
+ `Data` projections stay precise too — `p[:x]`, `p.to_h`,
147
+ `p.deconstruct`, `p.members`, and `p.with(x: 9)` all carry the
148
+ per-member types through:
149
+
150
+ ```ruby
151
+ p.to_h # HashShape{x: 3, y: 4}
152
+ p.members # Tuple[Constant<:x>, Constant<:y>]
153
+ p.with(x: 9).x # Constant<9>
154
+ ```
155
+
156
+ The folding covers all three definition forms — a constant
157
+ (`Point = Data.define(...)`), the `class X < Data.define(...)`
158
+ subclass idiom, and a bare local — and both positional and
159
+ keyword construction.
160
+
161
+ The discovery walks `define_method`-style block bodies too,
162
+ so `Point = Data.define(:x, :y) do ... end` still works,
163
+ including a block-defined `def initialize(...)` whose
164
+ arguments override the synthesised keyword-argument
165
+ constructor (v0.1.2). The same rule covers
166
+ `Const = Struct.new(*Symbol) do ... end` — block-body method
167
+ discovery composes uniformly across both shapes.
168
+
169
+ ## `Struct.new`
170
+
171
+ `Struct.new(*Symbol)` produces a positional-arg constructor
172
+ plus the same accessors as `Data.define`. Rigor folds struct
173
+ member reads too, but — because a `Struct` is mutable — only
174
+ where the value cannot have changed:
175
+
176
+ ```ruby
177
+ Coord = Struct.new(:x, :y)
178
+
179
+ # A never-mutated struct local folds its member reads.
180
+ c = Coord.new(10, 20)
181
+ assert_type("10", c.x)
182
+ assert_type("20", c.y)
183
+
184
+ # A local that is mutated, aliased, or escapes is not fold-safe —
185
+ # its reads degrade to Dynamic, never a stale value.
186
+ m = Coord.new(1, 2)
187
+ m.x = 9
188
+ assert_type("Dynamic[top]", m.x)
189
+ ```
190
+
191
+ Because `Struct` accessors are also writers, the fold is gated:
192
+ a member read off a freshly constructed instance (`Coord.new(1,
193
+ 2).x`) or a local the analysis proves is never written, aliased,
194
+ or passed away folds to the member's type; anything else widens
195
+ to `Dynamic[top]`. `Data` is read-only, so its reads always fold.
196
+
197
+ ## Inheritance and method resolution
198
+
199
+ When you call a method on `Nominal[Subclass]`, Rigor walks
200
+ the class hierarchy: subclass's RBS / in-source body first,
201
+ then each ancestor's RBS / body, then included modules in
202
+ their declaration order. The first one to define the method
203
+ wins.
204
+
205
+ The hierarchy is read from:
206
+
207
+ - RBS `class Foo < Bar` declarations.
208
+ - In-source `class Foo < Bar` lines.
209
+ - `include` / `prepend` / `extend` calls Rigor walked.
210
+
211
+ When the hierarchy is statically incomplete (a class
212
+ references a parent Rigor cannot locate), the receiver type
213
+ falls back to the deepest known ancestor — never to
214
+ `Dynamic[top]` for a class Rigor saw the declaration of.
215
+
216
+ ## `class` and `singleton(C)` types
217
+
218
+ Method signatures sometimes return "the class object itself":
219
+
220
+ ```rbs
221
+ class Foo
222
+ def self.factory: () -> Foo # returns an instance
223
+ def self.subclasses: () -> Array[singleton(Foo)] # returns class objects
224
+ end
225
+ ```
226
+
227
+ `singleton(Foo)` is the type of the class object `Foo`.
228
+ `Singleton[Foo]` (Rigor's internal carrier display form) is
229
+ the same idea. `Foo` (in `Array[Foo]`) means "an instance of
230
+ `Foo`" / `Nominal[Foo]`.
231
+
232
+ Calling an instance method on a `singleton(Foo)` is an error
233
+ unless `Foo` itself defines that singleton method — `String`
234
+ is `singleton(String)`, `String#upcase` is on instances, so
235
+ `String.upcase` flags `call.undefined-method`.
236
+
237
+ ## Custom `case_eq` (`===`)
238
+
239
+ Rigor recognises `===` for `Class` / `Module` / `Range` /
240
+ `Regexp` — these are the standard `case x; when …` shapes.
241
+ Custom `case_eq` implementations on user classes are NOT
242
+ recognised:
243
+
244
+ ```ruby
245
+ class IPv4
246
+ def self.===(s)
247
+ s.match?(/\A\d+\.\d+\.\d+\.\d+\z/)
248
+ end
249
+ end
250
+
251
+ case some_input
252
+ when IPv4
253
+ # Rigor does not narrow `some_input` here — IPv4.=== is a
254
+ # user-defined case-equality, which the engine cannot prove
255
+ # narrows a specific class.
256
+ some_input
257
+ end
258
+ ```
259
+
260
+ For these cases, write an explicit `is_a?` / `respond_to?`
261
+ guard, or use an `RBS::Extended` `predicate-if-true` directive
262
+ on the `===` method (see [Chapter 7](07-rbs-and-extended.md)).
263
+
264
+ ## Constant-decl alias classes
265
+
266
+ Some Ruby idioms create a class alias by constant assignment:
267
+
268
+ ```ruby
269
+ YAML = Psych
270
+ ```
271
+
272
+ When the right-hand side is itself a class, Rigor follows the
273
+ alias for receiver typing — `YAML.load(...)` is treated as
274
+ `Psych.load(...)`. Method-existence checks deliberately stay
275
+ silent on the aliased name, however; the analyzer cannot
276
+ distinguish a deliberate alias from an accidental shadowing
277
+ without more context, so `YAML.unknown` does not fire
278
+ `call.undefined-method`. Use the canonical name when you need
279
+ the diagnostic.
280
+
281
+ ## Modules
282
+
283
+ `module M; def foo; end; end` is structurally similar to a
284
+ class for typing purposes. Methods are looked up the same
285
+ way; `include M` adds `M`'s methods to the including class's
286
+ hierarchy.
287
+
288
+ `extend self`-style mixin patterns (`module_function` /
289
+ `extend self`) are recognised — both instance-side and
290
+ singleton-side surface the same methods.
291
+
292
+ ## `protected` and `private`
293
+
294
+ Rigor reads visibility modifiers and respects them in the
295
+ limited context of `def.method-visibility-mismatch` rules
296
+ (future). Today, calling a private method on an external
297
+ receiver does not fire a diagnostic — visibility is more a
298
+ concern for `rubocop-style` linters than a type-system
299
+ question.
300
+
301
+ ## What's next
302
+
303
+ Chapter 7 covers RBS and `RBS::Extended` — the external
304
+ signature surface that takes you beyond what inference alone
305
+ can prove.