rigortype 0.3.6 → 0.3.7

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 (213) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
  14. data/docs/manual/02-cli-reference.md +16 -7
  15. data/docs/manual/05-inspecting-types.md +20 -8
  16. data/docs/manual/07-plugins.md +4 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  18. data/docs/manual/plugins/rigor-activerecord.md +74 -1
  19. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  20. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  21. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  22. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  24. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  25. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  26. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  28. data/lib/rigor/analysis/check_rules.rb +383 -48
  29. data/lib/rigor/analysis/crash_signature.rb +116 -0
  30. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  31. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  32. data/lib/rigor/analysis/incremental.rb +28 -0
  33. data/lib/rigor/analysis/incremental_session.rb +44 -9
  34. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  35. data/lib/rigor/analysis/reachability/graph.rb +14 -5
  36. data/lib/rigor/analysis/reachability/scan.rb +7 -3
  37. data/lib/rigor/analysis/result.rb +32 -0
  38. data/lib/rigor/analysis/run_cache_key.rb +41 -1
  39. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
  40. data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
  41. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  42. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
  43. data/lib/rigor/analysis/runner.rb +147 -10
  44. data/lib/rigor/analysis/worker_session.rb +10 -1
  45. data/lib/rigor/bleeding_edge.rb +7 -6
  46. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  47. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  48. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  49. data/lib/rigor/cache/descriptor.rb +42 -2
  50. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  51. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
  52. data/lib/rigor/cache/store.rb +10 -1
  53. data/lib/rigor/cli/coverage_command.rb +23 -18
  54. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  55. data/lib/rigor/cli/coverage_scan.rb +47 -6
  56. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  57. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  58. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  59. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  60. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  61. data/lib/rigor/cli/sig_gen_command.rb +18 -0
  62. data/lib/rigor/cli/type_of_command.rb +188 -41
  63. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  64. data/lib/rigor/cli/type_scan_command.rb +10 -1
  65. data/lib/rigor/cli.rb +1 -1
  66. data/lib/rigor/configuration.rb +7 -4
  67. data/lib/rigor/effects/plugin_facts.rb +1 -1
  68. data/lib/rigor/effects/scanner.rb +2 -3
  69. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  70. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  71. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  72. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  73. data/lib/rigor/environment/rbs_loader.rb +525 -46
  74. data/lib/rigor/environment.rb +38 -15
  75. data/lib/rigor/inference/acceptance.rb +172 -20
  76. data/lib/rigor/inference/captured_locals.rb +62 -0
  77. data/lib/rigor/inference/content_join.rb +347 -0
  78. data/lib/rigor/inference/def_handle.rb +15 -3
  79. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  80. data/lib/rigor/inference/expression_typer.rb +1020 -141
  81. data/lib/rigor/inference/fork_map.rb +6 -1
  82. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  83. data/lib/rigor/inference/hkt_registry.rb +46 -0
  84. data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
  85. data/lib/rigor/inference/index_write_widening.rb +48 -0
  86. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  87. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  88. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  89. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  90. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  91. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  92. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  93. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  94. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  95. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  96. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  97. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  98. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  99. data/lib/rigor/inference/mutation_widening.rb +244 -174
  100. data/lib/rigor/inference/narrowing.rb +331 -27
  101. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  102. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  103. data/lib/rigor/inference/precision_scanner.rb +5 -2
  104. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  105. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  106. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  107. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  108. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  109. data/lib/rigor/inference/version_guard.rb +229 -0
  110. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  111. data/lib/rigor/plugin/base.rb +3 -2
  112. data/lib/rigor/plugin/inflector.rb +14 -5
  113. data/lib/rigor/plugin/io_boundary.rb +107 -4
  114. data/lib/rigor/plugin/loader.rb +14 -0
  115. data/lib/rigor/protection/analysis_guard.rb +60 -0
  116. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  117. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  118. data/lib/rigor/protection/discovery_seed.rb +1 -1
  119. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  120. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  121. data/lib/rigor/protection/mutator.rb +121 -18
  122. data/lib/rigor/reflection.rb +182 -30
  123. data/lib/rigor/scope/discovery_index.rb +39 -1
  124. data/lib/rigor/scope.rb +385 -4
  125. data/lib/rigor/sig_gen/classification.rb +12 -1
  126. data/lib/rigor/sig_gen/generator.rb +254 -5
  127. data/lib/rigor/sig_gen/renderer.rb +1 -6
  128. data/lib/rigor/sig_gen/writer.rb +3 -0
  129. data/lib/rigor/source/constant_path.rb +79 -2
  130. data/lib/rigor/triage/catalogue.rb +1 -1
  131. data/lib/rigor/type/combinator.rb +10 -0
  132. data/lib/rigor/type/maybe.rb +47 -0
  133. data/lib/rigor/type/refined.rb +1 -2
  134. data/lib/rigor/type/result.rb +53 -0
  135. data/lib/rigor/type.rb +2 -0
  136. data/lib/rigor/version.rb +1 -1
  137. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  138. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  139. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  140. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  141. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  142. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  143. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  144. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  145. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  146. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  147. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  148. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  149. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  150. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  151. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  152. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  153. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  154. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  156. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  157. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  158. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  159. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  160. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  161. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  162. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  163. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  164. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  165. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  166. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  167. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  168. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  169. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  170. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  171. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  172. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  173. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  174. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  175. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  176. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  177. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  178. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  179. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  180. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  181. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  182. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  183. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  184. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  185. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  186. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  187. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  188. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  189. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  190. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  191. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  192. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  193. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  194. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  195. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  196. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  197. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  198. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  199. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  200. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  201. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  202. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  203. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  204. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  205. data/sig/rigor/analysis/baseline.rbs +1 -1
  206. data/sig/rigor/environment.rbs +3 -0
  207. data/sig/rigor/inference.rbs +4 -1
  208. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  209. data/sig/rigor/reflection.rbs +3 -1
  210. data/sig/rigor/scope.rbs +25 -1
  211. data/sig/rigor/type.rbs +95 -1
  212. data/sig/rigor.rbs +16 -0
  213. metadata +69 -3
@@ -78,6 +78,8 @@ class NilClass
78
78
  def presence: () -> nil
79
79
  def try: (*untyped) -> nil
80
80
  def try!: (*untyped) -> nil
81
+ def duplicable?: () -> true
82
+ def instance_values: () -> Hash[String, untyped]
81
83
  end
82
84
 
83
85
  class TrueClass
@@ -244,6 +246,77 @@ class Float
244
246
  def gigabytes: () -> Float
245
247
  end
246
248
 
249
+ # ---------------------------------------------------------------
250
+ # ActiveSupport::Duration — issue #632
251
+ # ---------------------------------------------------------------
252
+
253
+ # Declaring this class at all is normally the wrong move: `Duration` forwards any method its own class
254
+ # doesn't define to the wrapped numeric via `method_missing` (audited against ActiveSupport 8.1.3.1's
255
+ # `lib/active_support/duration.rb`), so an incomplete declaration would turn every omitted member into a
256
+ # false `call.undefined-method` for every project that locks activesupport. It is safe here only because
257
+ # `ActiveSupport::Duration` is listed in `Rigor::Analysis::CheckRules::GEM_OVERLAY_OPEN_RECEIVERS`
258
+ # (`lib/rigor/analysis/check_rules.rb`) — an open-receiver membership for exactly this bundle, gated on this
259
+ # overlay directory actually having been loaded, since this overlay has no plugin manifest of its own for
260
+ # an ADR-26 `open_receivers:` entry to live on (issue #660 tracks whether that gap gets a first-class
261
+ # mechanism instead of this constant). What follows is the reader surface, mirroring
262
+ # `rigor-activesupport-core-ext/sig/active_support/core_ext.rbs` (parity enforced by
263
+ # `spec/rigor/environment/activesupport_overlay_parity_spec.rb`); every method not listed here stays
264
+ # undeclared and silently `Dynamic` rather than guessed.
265
+ module ActiveSupport
266
+ class Duration
267
+ # The `ago` family (issue #659). `since(time = ::Time.current)` and `ago(time = ::Time.current)` are
268
+ # the only two real methods; `from_now` / `after` alias the first and `until` / `before` the second,
269
+ # so all six share one signature. They were declared once before and removed, because typing them
270
+ # `Time` while the Rails `Time` INSTANCE surface was undeclared made `1.hour.ago.to_fs(:db)` —
271
+ # ordinary Rails — a false `call.undefined-method` on the returned `Time`, which
272
+ # `GEM_OVERLAY_OPEN_RECEIVERS` cannot suppress since it protects a Duration receiver, not a `Time`
273
+ # one. #658 declared that surface and #670 the `Date` / `DateTime` twins, so the chains resolve now.
274
+ #
275
+ # `Time` is the right class rather than a compromise: under a zone these answer an
276
+ # `ActiveSupport::TimeWithZone`, and Rails overrides `TimeWithZone#is_a?` to answer true for
277
+ # `::Time`. A `Time | ActiveSupport::TimeWithZone` union was measured and rejected — it types every
278
+ # downstream call `Dynamic[top]`. The with-an-argument form stays `untyped` because the runtime
279
+ # return depends on the argument's class AND on whether the duration carries a date-scale part
280
+ # (`(1.month).ago(Date.today)` answers a `Date`; `(30.minutes).ago(Date.today)` a `TimeWithZone`).
281
+ def ago: () -> Time
282
+ | (untyped time) -> untyped
283
+ def until: () -> Time
284
+ | (untyped time) -> untyped
285
+ def before: () -> Time
286
+ | (untyped time) -> untyped
287
+ def since: () -> Time
288
+ | (untyped time) -> untyped
289
+ def from_now: () -> Time
290
+ | (untyped time) -> untyped
291
+ def after: () -> Time
292
+ | (untyped time) -> untyped
293
+
294
+ # `to_i` / its `in_seconds` alias return `@value.to_i` — always Integer.
295
+ def to_i: () -> Integer
296
+ def in_seconds: () -> Integer
297
+
298
+ # `to_f` — always Float.
299
+ def to_f: () -> Float
300
+
301
+ # `in_minutes` / `in_hours` / `in_days` / `in_weeks` / `in_months` / `in_years` — each
302
+ # `in_seconds / SECONDS_PER_*.to_f`, always Float.
303
+ def in_minutes: () -> Float
304
+ def in_hours: () -> Float
305
+ def in_days: () -> Float
306
+ def in_weeks: () -> Float
307
+ def in_months: () -> Float
308
+ def in_years: () -> Float
309
+
310
+ # Always a String.
311
+ def iso8601: (?precision: Integer?) -> String
312
+
313
+ # A `dup` of the internal parts hash — Integer-valued for a duration built from Integer multipliers,
314
+ # but real runtime Float for a fractional one (`1.5.seconds.parts == {seconds: 1.5}`); `Integer |
315
+ # Float` is what stays honest without costing the common `parts[:days] * 2` chain anything.
316
+ def parts: () -> Hash[Symbol, Integer | Float]
317
+ end
318
+ end
319
+
247
320
  # ---------------------------------------------------------------
248
321
  # Time — singleton + instance extensions
249
322
  # ---------------------------------------------------------------
@@ -253,6 +326,26 @@ class Time
253
326
  def self.zone: () -> untyped # ActiveSupport::TimeZone | nil
254
327
  def self.zone=: (String | Symbol | untyped) -> untyped
255
328
 
329
+ # Issue #658, `core_ext/time/zones` + `core_ext/time/calculations` — the rest of the singleton
330
+ # surface those two files add, none of it typed by rbs. `zone_default` is the `attr_accessor`
331
+ # behind `Time.zone`'s fallback; `find_zone!` raises on a bad zone and `find_zone` rescues to
332
+ # `nil`; `use_zone` swaps the zone around a block and answers the block's value. All four hand
333
+ # back or take an `ActiveSupport::TimeZone`, which this overlay does not declare, so they stay
334
+ # `untyped` rather than guessing a carrier.
335
+ def self.zone_default: () -> untyped
336
+ def self.zone_default=: (untyped) -> untyped
337
+ def self.use_zone: (untyped time_zone) { () -> untyped } -> untyped
338
+ def self.find_zone!: (untyped time_zone) -> untyped
339
+ def self.find_zone: (untyped time_zone) -> untyped
340
+ # `COMMON_YEAR_DAYS_IN_MONTH[month]` answers `nil` for a month outside 1..12, but the declared
341
+ # return stays `Integer`: `Integer?` would make `Time.days_in_month(2, 2024) - 1` — how every real
342
+ # call site uses it — a nil-arithmetic diagnostic, the more expensive of the two on correct code.
343
+ def self.days_in_month: (Integer month, ?Integer year) -> Integer
344
+ def self.days_in_year: (?Integer year) -> Integer
345
+ # `Time.rfc3339(str)` builds a `Time` from the parsed parts. Distinct from the `rfc3339` INSTANCE
346
+ # alias below.
347
+ def self.rfc3339: (String str) -> Time
348
+
256
349
  # `core_ext/time/conversions` — nothing to declare. `Time.parse`, `Time.httpdate` and `Time.iso8601`
257
350
  # all come from stdlib `time`, which rbs types; ActiveSupport only re-opens them. Declaring them
258
351
  # here raised `RBS::DuplicatedMethodDefinitionError` and collapsed the whole `Time` definition to
@@ -279,8 +372,11 @@ class Time
279
372
  def change: (**untyped) -> Time
280
373
  def at_beginning_of_day: () -> Time
281
374
  def at_end_of_day: () -> Time
282
- def at_beginning_of_week: () -> Time
283
- def at_end_of_week: () -> Time
375
+ # `at_beginning_of_week` / `at_end_of_week` are `alias`es of `beginning_of_week` / `end_of_week`
376
+ # in `DateAndTime::Calculations` and take the same optional `start_day` — declared zero-arity,
377
+ # `time.at_beginning_of_week(:sunday)` drew an arity diagnostic on correct Rails code.
378
+ def at_beginning_of_week: (?Symbol start_day) -> Time
379
+ def at_end_of_week: (?Symbol start_day) -> Time
284
380
  def at_midnight: () -> Time
285
381
  def at_noon: () -> Time
286
382
  def midday: () -> Time
@@ -293,6 +389,131 @@ class Time
293
389
  def advance: (untyped options) -> Time
294
390
  def all_day: () -> Range[Time]
295
391
  def acts_like_time?: () -> true
392
+
393
+ # -------------------------------------------------------------
394
+ # Issue #658 — the rest of the Rails `Time` INSTANCE surface
395
+ # -------------------------------------------------------------
396
+ #
397
+ # `Time` is a CORE class, fully RBS-known and therefore CLOSED: a name this file does not declare
398
+ # is reported `call.undefined-method`. On a closed class an OMISSION is a false positive on
399
+ # correct Rails code exactly as much as a wrong return type is, and there is no gradual middle —
400
+ # making `Time` an open receiver would suppress the genuine diagnostic project-wide. So every row
401
+ # below was audited name by name against the vendored activesupport-8.1.3.1 sources:
402
+ # `core_ext/date_and_time/calculations.rb` (the module `Time`, `Date` and `DateTime` all include),
403
+ # `core_ext/time/calculations.rb`, `core_ext/time/conversions.rb`,
404
+ # `core_ext/date_and_time/zones.rb` and `core_ext/date_and_time/compatibility.rb`.
405
+ #
406
+ # Every row was also checked against the assembled environment for a name rbs already declares:
407
+ # a second full declaration raises `RBS::DuplicatedMethodDefinitionError` and collapses the WHOLE
408
+ # class (the `Time#utc?` note above). That is why `to_time`, `xmlschema`, `localtime`, `getlocal`,
409
+ # `utc`, `getutc`, `utc?`, `utc_offset`, `usec`, `nsec`, `subsec`, `to_f` and `to_i` are absent
410
+ # even though ActiveSupport re-opens some of them.
411
+
412
+ # `core_ext/date_and_time/calculations` — the shared module; each method is `advance` / `change`
413
+ # on the receiver, so on a `Time` receiver each answers `Time`.
414
+ def today?: () -> bool
415
+ def tomorrow?: () -> bool
416
+ def next_day?: () -> bool
417
+ def yesterday?: () -> bool
418
+ def prev_day?: () -> bool
419
+ def past?: () -> bool
420
+ def future?: () -> bool
421
+ def on_weekend?: () -> bool
422
+ def on_weekday?: () -> bool
423
+ # `untyped` parameters: `self < date_or_time` accepts `Time`, `Date`, `DateTime` and
424
+ # `ActiveSupport::TimeWithZone` alike through ActiveSupport's `<=>` override.
425
+ def before?: (untyped date_or_time) -> bool
426
+ def after?: (untyped date_or_time) -> bool
427
+ # `Numeric` and not `Integer`: `advance` divmods a fractional `days:` / `weeks:` on purpose.
428
+ def days_ago: (Numeric days) -> Time
429
+ def days_since: (Numeric days) -> Time
430
+ def weeks_ago: (Numeric weeks) -> Time
431
+ def weeks_since: (Numeric weeks) -> Time
432
+ def months_ago: (Numeric months) -> Time
433
+ def months_since: (Numeric months) -> Time
434
+ def years_ago: (Numeric years) -> Time
435
+ def years_since: (Numeric years) -> Time
436
+ def at_beginning_of_month: () -> Time
437
+ def at_beginning_of_year: () -> Time
438
+ def at_end_of_month: () -> Time
439
+ def at_end_of_year: () -> Time
440
+ def beginning_of_quarter: () -> Time
441
+ def at_beginning_of_quarter: () -> Time
442
+ def end_of_quarter: () -> Time
443
+ def at_end_of_quarter: () -> Time
444
+ # `(month / 3.0).ceil` — `Float#ceil` with no argument is Integer.
445
+ def quarter: () -> Integer
446
+ def next_week: (?Symbol given_day_in_next_week, ?same_time: bool) -> Time
447
+ def prev_week: (?Symbol start_day, ?same_time: bool) -> Time
448
+ def last_week: (?Symbol start_day, ?same_time: bool) -> Time
449
+ def next_weekday: () -> Time
450
+ def prev_weekday: () -> Time
451
+ def last_weekday: () -> Time
452
+ def days_to_week_start: (?Symbol start_day) -> Integer
453
+ def all_week: (?Symbol start_day) -> Range[Time]
454
+ def monday: () -> Time
455
+ def sunday: () -> Time
456
+ def next_quarter: () -> Time
457
+ def prev_quarter: () -> Time
458
+ def last_quarter: () -> Time
459
+ def last_month: () -> Time
460
+ def last_year: () -> Time
461
+ def all_month: () -> Range[Time]
462
+ def all_quarter: () -> Range[Time]
463
+ def all_year: () -> Range[Time]
464
+ def next_occurring: (Symbol day_of_week) -> Time
465
+ def prev_occurring: (Symbol day_of_week) -> Time
466
+
467
+ # `core_ext/time/calculations` — the `Time`-only half. `seconds_since_midnight` ends in
468
+ # `+ (usec / 1.0e+6)`, so it is Float even at exactly midnight; `seconds_until_end_of_day` is
469
+ # `end_of_day.to_i - to_i`, Integer. `sec_fraction` is a plain alias of `subsec` and carries
470
+ # `subsec`'s own core type — CRuby answers the Integer `0` for a whole-second receiver.
471
+ def seconds_since_midnight: () -> Float
472
+ def seconds_until_end_of_day: () -> Integer
473
+ def sec_fraction: () -> (0 | Rational)
474
+ # `middle_of_day` is `change(hour: 12)`; `midday` / `noon` / `at_noon` above and `at_midday` /
475
+ # `at_middle_of_day` here are its aliases.
476
+ def middle_of_day: () -> Time
477
+ def at_midday: () -> Time
478
+ def at_middle_of_day: () -> Time
479
+ def at_beginning_of_hour: () -> Time
480
+ def at_end_of_hour: () -> Time
481
+ def at_beginning_of_minute: () -> Time
482
+ def at_end_of_minute: () -> Time
483
+ # `Time`'s OWN redefinitions (`advance(days: -days)` and friends), taking an optional count —
484
+ # not the zero-argument `Date` methods of the same name that rbs's `stdlib/date` types.
485
+ def prev_day: (?Numeric days) -> Time
486
+ def next_day: (?Numeric days) -> Time
487
+ def prev_month: (?Numeric months) -> Time
488
+ def next_month: (?Numeric months) -> Time
489
+ def prev_year: (?Numeric years) -> Time
490
+ def next_year: (?Numeric years) -> Time
491
+
492
+ # `core_ext/time/conversions`. `to_fs` / `to_formatted_s` always answer a String whatever the
493
+ # `Time::DATE_FORMATS` entry returns: the lambda branch ends in `.to_s` and the fallback is
494
+ # `to_s`. `formatted_offset` is String in both of its branches; its `colon` is `boolish` and not
495
+ # `bool` because the body is plain truthiness (`colon ? WITH_COLON : WITHOUT_COLON`), and
496
+ # `alternate_utc_string` is `untyped` because it is returned as-is and callers print whatever
497
+ # they like there. `rfc3339` is
498
+ # `alias_method :rfc3339, :xmlschema`, so it takes `xmlschema`'s optional `fraction_digits`; rbs
499
+ # types `Time#xmlschema` but not `Time#rfc3339`, so this is an addition and not a collision.
500
+ def to_fs: (?Symbol format) -> String
501
+ def to_formatted_s: (?Symbol format) -> String
502
+ def formatted_offset: (?boolish colon, ?untyped alternate_utc_string) -> String
503
+ def rfc3339: (?Integer fraction_digits) -> String
504
+
505
+ # `core_ext/date_and_time/zones`. Answers an `ActiveSupport::TimeWithZone`, which nothing in this
506
+ # overlay declares, so the return is `untyped` rather than a guess — the same answer
507
+ # `DateTime#in_time_zone` gives below. The parameter is `untyped` because `Time.find_zone!` also
508
+ # accepts an `ActiveSupport::TimeZone`, a `TZInfo::Timezone` and an offset `Duration`
509
+ # (`Time.find_zone! -5.hours` is the documented spelling).
510
+ def in_time_zone: (?untyped zone) -> untyped
511
+
512
+ # `core_ext/date_and_time/compatibility` — `mattr_accessor
513
+ # :utc_to_local_returns_utc_offset_times, instance_writer: false` defines an instance READER on
514
+ # every class including the module. Rarely called on an instance, but public, and `Time` is
515
+ # closed, so the omission would be the false positive.
516
+ def utc_to_local_returns_utc_offset_times: () -> bool
296
517
  end
297
518
 
298
519
  # ---------------------------------------------------------------
@@ -300,15 +521,16 @@ end
300
521
  # ---------------------------------------------------------------
301
522
 
302
523
  class Date
524
+ # The whole date-constructing singleton surface `core_ext/date/calculations.rb`'s `class << self`
525
+ # adds. `self.end_of_week`, `self.beginning_of_month`, `self.end_of_month`,
526
+ # `self.beginning_of_year` and `self.end_of_year` were declared here until #762 and do not exist —
527
+ # `Date` only INCLUDES `DateAndTime::Calculations`, so those five are instance methods, below. The
528
+ # declarations made `Date.end_of_month`, a real `NoMethodError`, type-check as a `Date`, and
529
+ # `DateTime` inherited all five. `self.beginning_of_week` exists but is the week-start
530
+ # configuration reader, declared with that group at the bottom of this class.
303
531
  def self.current: () -> Date
304
532
  def self.yesterday: () -> Date
305
533
  def self.tomorrow: () -> Date
306
- def self.beginning_of_week: () -> Date
307
- def self.end_of_week: () -> Date
308
- def self.beginning_of_month: () -> Date
309
- def self.end_of_month: () -> Date
310
- def self.beginning_of_year: () -> Date
311
- def self.end_of_year: () -> Date
312
534
 
313
535
  def yesterday: () -> Date
314
536
  def tomorrow: () -> Date
@@ -344,6 +566,87 @@ class Date
344
566
  # without it `date.to_time(:utc)` — correct Rails code — draws an arity diagnostic (#449). `| ...`
345
567
  # appends this overload ahead of the stdlib one, so both arities resolve.
346
568
  def to_time: (?Symbol form) -> Time | ...
569
+
570
+ # Issue #670 — the rest of the Rails `Date` instance surface, plus the week-start configuration
571
+ # singletons. `Date` is CLOSED (rbs's `stdlib/date` types it fully), so an OMISSION here is a false
572
+ # positive on correct Rails code exactly as much as a wrong return type is. Audited against the
573
+ # vendored activesupport-8.1.3.1 sources and every return confirmed by calling it on a real `Date`
574
+ # receiver; the plugin twin carries the per-method reasoning and the purity verdicts, which the
575
+ # overlay deliberately does not (see the header). The six `:nodoc:` operator-alias artefacts
576
+ # (`plus_with{,out}_duration`, `minus_with{,out}_duration`, `compare_with{,out}_coercion`) are left
577
+ # out on purpose, as are the `Object`- / `Module`-level core_ext methods that reach `Date` because
578
+ # they reach every class (#673).
579
+ def today?: () -> bool
580
+ def tomorrow?: () -> bool
581
+ def next_day?: () -> bool
582
+ def yesterday?: () -> bool
583
+ def prev_day?: () -> bool
584
+ def past?: () -> bool
585
+ def future?: () -> bool
586
+ def on_weekend?: () -> bool
587
+ def on_weekday?: () -> bool
588
+ def before?: (untyped date_or_time) -> bool
589
+ def after?: (untyped date_or_time) -> bool
590
+ def days_ago: (Numeric days) -> Date
591
+ def days_since: (Numeric days) -> Date
592
+ def weeks_ago: (Numeric weeks) -> Date
593
+ def weeks_since: (Numeric weeks) -> Date
594
+ def months_ago: (Numeric months) -> Date
595
+ def months_since: (Numeric months) -> Date
596
+ def years_ago: (Numeric years) -> Date
597
+ def years_since: (Numeric years) -> Date
598
+ def at_beginning_of_month: () -> Date
599
+ def at_end_of_month: () -> Date
600
+ def at_beginning_of_year: () -> Date
601
+ def at_end_of_year: () -> Date
602
+ def beginning_of_quarter: () -> Date
603
+ def at_beginning_of_quarter: () -> Date
604
+ def end_of_quarter: () -> Date
605
+ def at_end_of_quarter: () -> Date
606
+ def quarter: () -> Integer
607
+ def next_week: (?Symbol given_day_in_next_week, ?same_time: bool) -> Date
608
+ def prev_week: (?Symbol start_day, ?same_time: bool) -> Date
609
+ def last_week: (?Symbol start_day, ?same_time: bool) -> Date
610
+ def next_weekday: () -> Date
611
+ def days_to_week_start: (?Symbol start_day) -> Integer
612
+ def all_week: (?Symbol start_day) -> Range[Date]
613
+ def at_beginning_of_week: (?Symbol start_day) -> Date
614
+ def at_end_of_week: (?Symbol start_day) -> Date
615
+ def prev_weekday: () -> Date
616
+ def last_weekday: () -> Date
617
+ def monday: () -> Date
618
+ def sunday: () -> Date
619
+ def next_quarter: () -> Date
620
+ def prev_quarter: () -> Date
621
+ def last_quarter: () -> Date
622
+ def last_month: () -> Date
623
+ def last_year: () -> Date
624
+ def all_month: () -> Range[Date]
625
+ def all_quarter: () -> Range[Date]
626
+ def all_year: () -> Range[Date]
627
+ def next_occurring: (Symbol day_of_week) -> Date
628
+ def prev_occurring: (Symbol day_of_week) -> Date
629
+ def change: (untyped options) -> Date
630
+ def in: (Numeric seconds) -> Time
631
+ def middle_of_day: () -> Time
632
+ def midday: () -> Time
633
+ def noon: () -> Time
634
+ def at_midday: () -> Time
635
+ def at_noon: () -> Time
636
+ def at_middle_of_day: () -> Time
637
+ def to_fs: (?Symbol format) -> String
638
+ def to_formatted_s: (?Symbol format) -> String
639
+ def readable_inspect: () -> String
640
+ def default_inspect: () -> String
641
+ def in_time_zone: (?untyped zone) -> untyped
642
+ # The week-start configuration group. `self.beginning_of_week` is its READER and answers the week
643
+ # start (`:monday` by default), not a date — it was `() -> Date` until #762, which made every
644
+ # correct Symbol use of the value look wrong and every incorrect `Date` use look right.
645
+ def self.beginning_of_week: () -> Symbol
646
+ def self.beginning_of_week=: (Symbol week_start) -> Symbol
647
+ def self.beginning_of_week_default: () -> Symbol?
648
+ def self.beginning_of_week_default=: (Symbol? week_start) -> Symbol?
649
+ def self.find_beginning_of_week!: (Symbol week_start) -> Symbol
347
650
  end
348
651
 
349
652
  # ---------------------------------------------------------------
@@ -480,10 +783,29 @@ end
480
783
  # ---------------------------------------------------------------
481
784
 
482
785
  class DateTime
786
+ # Issue #670. `DateTime < Date`, so every row the `Date` block above declares is INHERITED here —
787
+ # and for most of `DateAndTime::Calculations` the inherited return is WRONG, because the shared
788
+ # module's `first_hour` / `last_hour` helpers branch on `acts_like?(:time)` (false on `Date`, true
789
+ # on `DateTime`) and compose each class's own `advance` / `change`. `DateTime.now.beginning_of_month`
790
+ # answered `Date` on master while returning a `DateTime` at runtime. So most of this block is
791
+ # overrides, and declaring the missing surface on `Date` alone would have converted the
792
+ # undefined-method false positives into wrong-return ones instead of fixing them.
793
+ #
794
+ # Rows already correct through inheritance are deliberately absent: `today?` / `past?` / `before?` /
795
+ # `on_weekend?` (bool), `quarter` / `days_to_week_start` (Integer), `to_fs` / `to_formatted_s` /
796
+ # `readable_inspect` / `default_inspect` (String), `acts_like_date?` (true). The sharpest is at the
797
+ # singleton — `DateTime.yesterday` / `.tomorrow` really do answer a `Date` (their bodies are a
798
+ # hardcoded `::Date.current.…`), so the inherited rows are right and overriding them would have been
799
+ # the false positive. `DateTime.current` is the one that is wrong inherited, and it is overridden.
483
800
  def utc: () -> Time
484
- # NOTE: `DateTime#to_time` is NOT declared here — stdlib `date` already types it, and re-declaring it
485
- # collapsed the whole `DateTime` definition (see the `Time#utc?` note above).
486
- def in_time_zone: (?String | Symbol zone) -> untyped
801
+ def getgm: () -> Time
802
+ def getutc: () -> Time
803
+ def gmtime: () -> Time
804
+ # NOTE: `DateTime#to_time` is NOT declared here — stdlib `date` already types it, and re-declaring
805
+ # it collapsed the whole `DateTime` definition (see the `Time#utc?` note above). The
806
+ # `in_time_zone` PARAMETER widened from `String | Symbol` to `untyped` in #670: `Time.find_zone!`
807
+ # also accepts a `TimeZone`, a `TZInfo::Timezone` and an offset `Duration`.
808
+ def in_time_zone: (?untyped zone) -> untyped
487
809
  def yesterday: () -> DateTime
488
810
  def tomorrow: () -> DateTime
489
811
  def ago: (Numeric seconds) -> DateTime
@@ -495,6 +817,82 @@ class DateTime
495
817
  def beginning_of_minute: () -> DateTime
496
818
  def end_of_minute: () -> DateTime
497
819
  def acts_like_time?: () -> true
820
+ def advance: (untyped options) -> DateTime
821
+ def change: (untyped options) -> DateTime
822
+ def in: (Numeric seconds) -> DateTime
823
+ def midnight: () -> DateTime
824
+ def at_midnight: () -> DateTime
825
+ def at_beginning_of_day: () -> DateTime
826
+ def at_end_of_day: () -> DateTime
827
+ def middle_of_day: () -> DateTime
828
+ def midday: () -> DateTime
829
+ def noon: () -> DateTime
830
+ def at_midday: () -> DateTime
831
+ def at_noon: () -> DateTime
832
+ def at_middle_of_day: () -> DateTime
833
+ def at_beginning_of_hour: () -> DateTime
834
+ def at_end_of_hour: () -> DateTime
835
+ def at_beginning_of_minute: () -> DateTime
836
+ def at_end_of_minute: () -> DateTime
837
+ def seconds_since_midnight: () -> Integer
838
+ def seconds_until_end_of_day: () -> Integer
839
+ def subsec: () -> Rational
840
+ def to_f: () -> Float
841
+ def to_i: () -> Integer
842
+ def usec: () -> Integer
843
+ def nsec: () -> Integer
844
+ def utc?: () -> bool
845
+ def utc_offset: () -> Integer
846
+ def localtime: (?untyped utc_offset) -> Time
847
+ def getlocal: (?untyped utc_offset) -> Time
848
+ def days_ago: (Numeric days) -> DateTime
849
+ def days_since: (Numeric days) -> DateTime
850
+ def weeks_ago: (Numeric weeks) -> DateTime
851
+ def weeks_since: (Numeric weeks) -> DateTime
852
+ def months_ago: (Numeric months) -> DateTime
853
+ def months_since: (Numeric months) -> DateTime
854
+ def years_ago: (Numeric years) -> DateTime
855
+ def years_since: (Numeric years) -> DateTime
856
+ def beginning_of_month: () -> DateTime
857
+ def at_beginning_of_month: () -> DateTime
858
+ def end_of_month: () -> DateTime
859
+ def at_end_of_month: () -> DateTime
860
+ def beginning_of_year: () -> DateTime
861
+ def at_beginning_of_year: () -> DateTime
862
+ def end_of_year: () -> DateTime
863
+ def at_end_of_year: () -> DateTime
864
+ def beginning_of_quarter: () -> DateTime
865
+ def at_beginning_of_quarter: () -> DateTime
866
+ def end_of_quarter: () -> DateTime
867
+ def at_end_of_quarter: () -> DateTime
868
+ def next_quarter: () -> DateTime
869
+ def prev_quarter: () -> DateTime
870
+ def last_quarter: () -> DateTime
871
+ def last_month: () -> DateTime
872
+ def last_year: () -> DateTime
873
+ def prev_weekday: () -> DateTime
874
+ def last_weekday: () -> DateTime
875
+ def monday: () -> DateTime
876
+ def sunday: () -> DateTime
877
+ def next_occurring: (Symbol day_of_week) -> DateTime
878
+ def prev_occurring: (Symbol day_of_week) -> DateTime
879
+ def all_day: () -> Range[DateTime]
880
+ def all_month: () -> Range[DateTime]
881
+ def all_quarter: () -> Range[DateTime]
882
+ def all_year: () -> Range[DateTime]
883
+ def beginning_of_week: (?Symbol start_day) -> DateTime
884
+ def end_of_week: (?Symbol start_day) -> DateTime
885
+ def at_beginning_of_week: (?Symbol start_day) -> DateTime
886
+ def at_end_of_week: (?Symbol start_day) -> DateTime
887
+ def next_week: (?Symbol given_day_in_next_week, ?same_time: bool) -> DateTime
888
+ def prev_week: (?Symbol start_day, ?same_time: bool) -> DateTime
889
+ def last_week: (?Symbol start_day, ?same_time: bool) -> DateTime
890
+ def next_weekday: () -> DateTime
891
+ def all_week: (?Symbol start_day) -> Range[DateTime]
892
+ def formatted_offset: (?boolish colon, ?untyped alternate_utc_string) -> String
893
+ def utc_to_local_returns_utc_offset_times: () -> bool
894
+ def self.current: () -> DateTime
895
+ def self.civil_from_format: (untyped utc_or_local, Integer year, ?Integer month, ?Integer day, ?Integer hour, ?Integer min, ?Integer sec) -> DateTime
498
896
  end
499
897
 
500
898
  # ---------------------------------------------------------------
@@ -114,16 +114,25 @@ missing file.
114
114
 
115
115
  ## `rigor type-of`
116
116
 
117
- Print the inferred type at one source position.
117
+ Print inferred types at one or more source positions.
118
118
 
119
119
  ```sh
120
- rigor type-of FILE:LINE:COL
121
- rigor type-of FILE LINE COL
120
+ rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]
121
+ rigor type-of [options] FILE LINE COL
122
122
  ```
123
123
 
124
- Accepts the position as a single `file:line:col` triple or as
125
- three arguments. `--format=json` emits a machine-readable
126
- form; `--trace` records fail-soft fallbacks. The editor-mode
124
+ The colon form is repeatable and keeps argument order while
125
+ parsing and scope-indexing each file once. Omit `COL` to print a
126
+ table of up to 40 expressions that start on that line, outermost
127
+ first at each 1-based column; the table marks when further
128
+ expressions were omitted. The legacy three-argument form accepts
129
+ one exact position.
130
+
131
+ `--format=json` keeps one result as the original flat object and
132
+ wraps several results in a `results` array. Line queries add a
133
+ `line_enumerations` array whose `shown` and `total` counts make
134
+ truncation explicit. `--trace` records fail-soft fallbacks,
135
+ after the rows of a line table in text output. The editor-mode
127
136
  `--tmp-file` / `--instead-of` pair is accepted as on `check`.
128
137
 
129
138
  ## `rigor trace`
@@ -1001,7 +1010,7 @@ Queued today:
1001
1010
 
1002
1011
  | Feature id | Kind | What it changes |
1003
1012
  | --- | --- | --- |
1004
- | `reject-unparseable-signatures` | severity | An unparseable `.rbs` under `signature_paths:` **fails the run** (`rbs.coverage.quarantined-signature` → `error`) instead of being skipped with a warning. |
1013
+ | `reject-unparseable-signatures` | severity | A broken RBS set **fails the run** instead of degrading it silently: an unparseable `.rbs` under `signature_paths:` (`rbs.coverage.quarantined-signature` → `error`), a declaration that collides on resolve and collapses the whole environment (`rbs.coverage.environment-build-failed` → `error`), and a duplicate method definition that collapses one class's method surface (`rbs.coverage.definition-build-failed` → `error`). |
1005
1014
  | `use-of-void-value` | severity | Using a value recovered from an author-declared `-> void` return in value context is reported as `static.value-use.void` (`warning`). |
1006
1015
  | `discovery-seeded-mutation-sites` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) measures against the same cross-file project discovery Tier 1 already uses — both when picking the sites and when deciding whether a breakage was caught — so a call on a project class declared in a *sibling* file is measured instead of dropped, and a breakage there can actually be caught. **Adds sites to the denominator, so the reported effectiveness ratio moves** — check it against any `--threshold` you pin in CI before adopting. |
1007
1016
  | `dependent-closure-kill-oracle` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) counts a breakage as caught when the diagnostic appears anywhere in the mutated file **or the files that depend on it**, instead of in the mutated file alone — so changing what a method returns counts as caught when the error lands in its callers. Can only **add** kills, so the ratio moves up or not at all; it costs about a third more wall time per mutant, and a ratio measured under it is not comparable with one measured without it. |
@@ -63,19 +63,31 @@ syntax-highlighted for a tty — through
63
63
  colorizer; `--no-color` (and the `NO_COLOR` environment
64
64
  variable) disable the colour.
65
65
 
66
- ## `rigor type-of` — one position
66
+ ## `rigor type-of` — exact positions or a whole line
67
67
 
68
- When you only need one expression's type — typically while
69
- chasing down why a diagnostic did or did not fire — query a
70
- single position:
68
+ When you need a few expression types — typically while chasing
69
+ down why a diagnostic did or did not fire — query the exact
70
+ positions together so Rigor loads the project and each source
71
+ file once:
71
72
 
72
73
  ```sh
73
- rigor type-of lib/example.rb:12:8
74
+ rigor type-of lib/example.rb:12:8 lib/example.rb:12:14
74
75
  ```
75
76
 
76
- `--format=json` emits a machine-readable result for tooling.
77
- This is the same query the editor integration answers on
78
- hover.
77
+ Leave off the column to avoid counting it by hand. Rigor prints
78
+ a table of the first 40 expressions starting on the line,
79
+ outermost first at each 1-based column, and marks a truncated
80
+ table:
81
+
82
+ ```sh
83
+ rigor type-of lib/example.rb:12
84
+ ```
85
+
86
+ `--format=json` emits a machine-readable result for tooling: one
87
+ result stays a flat object, while several results use a `results`
88
+ array. Line queries add `line_enumerations` metadata with the
89
+ shown and total expression counts. An exact position is the same
90
+ query the editor integration answers on hover.
79
91
 
80
92
  ## `rigor trace` — watch the inference happen
81
93
 
@@ -49,6 +49,10 @@ each plugin's options — but the families today are:
49
49
  `rigor-factorybot`.
50
50
  - **dry-rb** — `rigor-dry-types`, `rigor-dry-schema`,
51
51
  `rigor-dry-struct`, `rigor-dry-validation`.
52
+ - **FFI** — `rigor-ffi` and sub-plugins (`rigor-ffi-rzmq`,
53
+ `rigor-rbnacl`, `rigor-sassc`, `rigor-ethon`). For plugin authors,
54
+ `rigor-ffi` provides the `ffi_binding_recognizer` class DSL on
55
+ `Rigor::Plugin::Base` to recognize custom binding definitions.
52
56
  - **Other ecosystems** — `rigor-sinatra`, `rigor-hanami`,
53
57
  `rigor-devise`, `rigor-pundit`, `rigor-sidekiq`,
54
58
  `rigor-graphql`, `rigor-statesman`, `rigor-sorbet`,
@@ -56,6 +56,43 @@ plugins:
56
56
  view_search_paths: ["app/views"] # default
57
57
  ```
58
58
 
59
+ ## What it types
60
+
61
+ Inside a controller, `params`, `request`, `session`, `flash` and
62
+ `cookies` type as their Action Pack classes, and so do the chains
63
+ built on them:
64
+
65
+ ```ruby
66
+ request.post? # bool — and so do get? / put? / patch? / delete? /
67
+ # head? / options? / trace? / link? / unlink? /
68
+ # xhr? / xml_http_request? / ssl? / local? / form_data?
69
+ flash.now # ActionDispatch::Flash::FlashNow
70
+ flash.keep # ActionDispatch::Flash::FlashHash
71
+ flash[:notice] = "hi" # "hi" — an assignment is its right-hand side
72
+ ```
73
+
74
+ Rigor ships **no signature** for these Action Pack classes, on
75
+ purpose: the receiver becomes concrete (so `rigor coverage
76
+ --protection` counts the site) while the method surface stays
77
+ lenient, so `request.headers`, `flash.now[:alert] = x` and anything
78
+ else the framework adds resolve without a diagnostic. A partial
79
+ signature would be worse than none — every member it omitted would
80
+ become a false `call.undefined-method`.
81
+
82
+ The predicates are typed `bool` — the union of `true` and `false` —
83
+ which is both the real contract (every one of them is an `==`,
84
+ `match?` or `include?` in Rails or Rack) and the reason they are safe
85
+ to type at all: a condition that folds needs to prove *one* constant,
86
+ and a union of both never does. `return unless request.post?` and
87
+ `mode = request.get? ? :a : :b` read exactly as they did before.
88
+
89
+ `request.format` is **not** typed. That inertness argument is
90
+ narrower than it looks — it holds for a union of the two boolean
91
+ constants, not for a union of ordinary classes, which is nil-free and
92
+ so *can* fold a condition — and `Mime::NullType`, the value `format`
93
+ returns when there is no format, answers `nil?` with `true` while
94
+ being a real object. Typing it needs a nil-aware answer.
95
+
59
96
  ## Limitations
60
97
 
61
98
  - **Implicit-self helpers only.** `*_path` / `*_url` calls with an
@@ -69,6 +106,30 @@ plugins:
69
106
  knows what `rigor-rails-routes` published, and `permit`
70
107
  validation only what `rigor-activerecord` published — enabling
71
108
  those producers widens what this plugin can check.
109
+ - **`params[:key]` stays untyped.** Inside a controller, `params`
110
+ types as `ActionController::Parameters`, and so does the result
111
+ of every builder method that always returns one — `require`,
112
+ `permit`, `permit!`, `expect`, `slice`, `slice!`, `except`,
113
+ `without`, `extract!`, `merge`, `merge!`, `reverse_merge`,
114
+ `reverse_merge!`, `with_defaults`, `with_defaults!`, `compact`,
115
+ `compact_blank`, `deep_dup` — so a chain built from them keeps a
116
+ concrete receiver throughout. A subscript read is deliberately
117
+ left untyped: `params[:missing]` is `nil` at runtime, and a type
118
+ that says otherwise would let the flow rules fold live
119
+ conditions (`if params[:q]`, `url.nil?`) to a constant and report
120
+ working code. Methods whose result depends on the call — `dig`,
121
+ `fetch`, `compact!`, and the block-less `select` / `reject` /
122
+ `transform_keys` / `transform_values` — are untyped for the same
123
+ reason.
124
+ - **`flash[:key]` and `session[:key]` stay untyped too**, for that
125
+ same reason and measured the same way. Both are leaf reads that
126
+ return whatever was stored — or `nil` for a key that is not set.
127
+ A non-nil type folds `mode = flash[:notice] ? … : …` to one arm and
128
+ reports the live guard after it; a nullable one puts
129
+ `call.possible-nil-receiver` on `note = flash[:notice];
130
+ note.upcase`. Writing through them is unaffected: `flash[:k] = v`
131
+ is `v` because that is what an assignment expression means in Ruby,
132
+ with no rule needed.
72
133
 
73
134
  ## Plugin internals
74
135