rigortype 0.3.6 → 0.3.8

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 (223) 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/handbook/11-sig-gen.md +24 -14
  15. data/docs/manual/02-cli-reference.md +23 -7
  16. data/docs/manual/04-diagnostics.md +1 -1
  17. data/docs/manual/05-inspecting-types.md +20 -8
  18. data/docs/manual/07-plugins.md +4 -0
  19. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  20. data/docs/manual/plugins/rigor-activerecord.md +74 -1
  21. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  22. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  24. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  25. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  27. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  28. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  29. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  30. data/lib/rigor/analysis/check_rules.rb +383 -48
  31. data/lib/rigor/analysis/crash_signature.rb +188 -0
  32. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  33. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  34. data/lib/rigor/analysis/incremental.rb +28 -0
  35. data/lib/rigor/analysis/incremental_session.rb +63 -16
  36. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  37. data/lib/rigor/analysis/reachability/graph.rb +14 -5
  38. data/lib/rigor/analysis/reachability/scan.rb +7 -3
  39. data/lib/rigor/analysis/result.rb +32 -0
  40. data/lib/rigor/analysis/run_cache_key.rb +41 -1
  41. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +233 -16
  42. data/lib/rigor/analysis/runner/pool_coordinator.rb +266 -47
  43. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  44. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -2
  45. data/lib/rigor/analysis/runner.rb +212 -16
  46. data/lib/rigor/analysis/worker_session.rb +37 -3
  47. data/lib/rigor/bleeding_edge.rb +7 -6
  48. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  49. data/lib/rigor/builtins/imported_refinements.rb +11 -9
  50. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  51. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  52. data/lib/rigor/cache/annotation_location.rb +72 -0
  53. data/lib/rigor/cache/descriptor.rb +42 -2
  54. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  55. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +70 -8
  56. data/lib/rigor/cache/store.rb +17 -1
  57. data/lib/rigor/cli/coverage_command.rb +23 -18
  58. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  59. data/lib/rigor/cli/coverage_scan.rb +47 -6
  60. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  61. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  62. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  63. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  64. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  65. data/lib/rigor/cli/sig_gen_command.rb +47 -0
  66. data/lib/rigor/cli/type_of_command.rb +188 -41
  67. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  68. data/lib/rigor/cli/type_scan_command.rb +10 -1
  69. data/lib/rigor/cli.rb +1 -1
  70. data/lib/rigor/configuration.rb +7 -4
  71. data/lib/rigor/effects/plugin_facts.rb +1 -1
  72. data/lib/rigor/effects/scanner.rb +2 -3
  73. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  74. data/lib/rigor/environment/failure_slot.rb +28 -0
  75. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  76. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  77. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  78. data/lib/rigor/environment/rbs_loader.rb +743 -93
  79. data/lib/rigor/environment.rb +125 -25
  80. data/lib/rigor/inference/acceptance.rb +172 -20
  81. data/lib/rigor/inference/captured_locals.rb +62 -0
  82. data/lib/rigor/inference/content_join.rb +347 -0
  83. data/lib/rigor/inference/def_handle.rb +15 -3
  84. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  85. data/lib/rigor/inference/expression_typer.rb +1020 -141
  86. data/lib/rigor/inference/fork_map.rb +6 -1
  87. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  88. data/lib/rigor/inference/hkt_registry.rb +61 -3
  89. data/lib/rigor/inference/hkt_sugar_translator.rb +87 -0
  90. data/lib/rigor/inference/index_write_widening.rb +48 -0
  91. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  92. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  93. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  94. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  95. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  96. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  97. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  98. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  99. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  100. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  101. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  102. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  103. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  104. data/lib/rigor/inference/mutation_widening.rb +244 -174
  105. data/lib/rigor/inference/narrowing.rb +331 -27
  106. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  107. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  108. data/lib/rigor/inference/precision_scanner.rb +5 -2
  109. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  110. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  111. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  112. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  113. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  114. data/lib/rigor/inference/version_guard.rb +229 -0
  115. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  116. data/lib/rigor/plugin/base.rb +3 -2
  117. data/lib/rigor/plugin/inflector.rb +14 -5
  118. data/lib/rigor/plugin/io_boundary.rb +107 -4
  119. data/lib/rigor/plugin/loader.rb +14 -0
  120. data/lib/rigor/plugin/registry.rb +25 -2
  121. data/lib/rigor/protection/analysis_guard.rb +60 -0
  122. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  123. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  124. data/lib/rigor/protection/discovery_seed.rb +1 -1
  125. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  126. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  127. data/lib/rigor/protection/mutator.rb +121 -18
  128. data/lib/rigor/rbs_extended/envelope_scanner.rb +8 -7
  129. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  130. data/lib/rigor/rbs_extended/reporter.rb +93 -13
  131. data/lib/rigor/rbs_extended.rb +6 -1
  132. data/lib/rigor/reflection.rb +182 -30
  133. data/lib/rigor/scope/discovery_index.rb +39 -1
  134. data/lib/rigor/scope.rb +385 -4
  135. data/lib/rigor/sig_gen/classification.rb +12 -1
  136. data/lib/rigor/sig_gen/generator.rb +320 -85
  137. data/lib/rigor/sig_gen/renderer.rb +14 -12
  138. data/lib/rigor/sig_gen/writer.rb +3 -0
  139. data/lib/rigor/source/constant_path.rb +79 -2
  140. data/lib/rigor/triage/catalogue.rb +1 -1
  141. data/lib/rigor/type/combinator.rb +10 -0
  142. data/lib/rigor/type/maybe.rb +47 -0
  143. data/lib/rigor/type/refined.rb +1 -2
  144. data/lib/rigor/type/result.rb +53 -0
  145. data/lib/rigor/type.rb +2 -0
  146. data/lib/rigor/version.rb +1 -1
  147. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  148. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  149. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  150. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  151. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  152. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  153. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  154. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  155. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  156. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  157. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  158. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  159. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  160. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  161. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  162. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  163. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  164. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  165. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  166. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  167. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  168. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  169. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  170. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  171. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  172. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  173. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  174. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  175. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  176. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  177. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  178. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  179. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  180. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  181. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  182. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  183. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  184. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  185. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  186. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  187. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  188. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  189. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  190. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  191. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  192. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  193. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  194. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  195. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  196. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  197. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  198. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  199. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  200. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  201. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  202. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  203. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  204. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  205. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  206. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  207. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  208. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  209. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  210. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  211. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  212. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  213. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  214. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  215. data/sig/rigor/analysis/baseline.rbs +1 -1
  216. data/sig/rigor/environment.rbs +9 -0
  217. data/sig/rigor/inference.rbs +4 -1
  218. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  219. data/sig/rigor/reflection.rbs +3 -1
  220. data/sig/rigor/scope.rbs +25 -1
  221. data/sig/rigor/type.rbs +95 -1
  222. data/sig/rigor.rbs +16 -0
  223. metadata +71 -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
  # ---------------------------------------------------------------
@@ -95,10 +95,13 @@ states:
95
95
 
96
96
  The `sig.skipped.*` reasons are:
97
97
 
98
- - `sig.skipped.complex-shape` — the method has optional, rest,
99
- keyword, block, or forwarding parameters. The MVP's
100
- body-typing path only handles required positional
101
- parameters; complex shapes need a future slice.
98
+ - `sig.skipped.complex-shape` — reserved for a parameter
99
+ shape the renderer cannot spell. Every shape a `def` can
100
+ declare renders today (optional, rest, trailing, keyword,
101
+ keyword-rest, `...` forwarding, `&block`), so the
102
+ generator does not produce this reason; it stayed
103
+ reserved when the gate that used to fire it for every
104
+ such method was retired (#778).
102
105
  - `sig.skipped.untyped-return` — the method body's last
103
106
  expression types as `Dynamic[top]`. Emitting `untyped` as
104
107
  a tightening would be noise rather than help.
@@ -119,16 +122,25 @@ The `sig.skipped.*` reasons are:
119
122
  The three `sig.generated.*` identifiers
120
123
  (`sig.generated.new-file` / `new-method` / `tighter-return`)
121
124
  are emitted as JSON fields under `--format=json` so CI
122
- gating consumers can route them.
125
+ gating consumers can route them. Every `skipped` row is part
126
+ of the same payload, carrying its `sig.skipped.*` identifier
127
+ as `skip_reason`, so a method missing from your `sig/` has
128
+ its reason next to the rows that did emit. In text mode a
129
+ one-line stderr summary counts the skipped methods per
130
+ reason instead; stdout stays paste-clean.
123
131
 
124
132
  ## What method shapes the generator covers
125
133
 
126
134
  Slice-by-slice (each shipped via a CHANGELOG entry — this
127
135
  list is the current state):
128
136
 
129
- - **Plain instance `def foo`** with required positional
130
- parameters. Both new-method and tighter-return paths
131
- apply.
137
+ - **Plain instance `def foo`** of any parameter shape:
138
+ required, optional, rest, trailing, keyword, keyword-rest,
139
+ `...` forwarding and `&block`. The parameter list mirrors
140
+ the runtime shape with `untyped` in every position (the
141
+ observed union under `--params=observed`), and a block
142
+ renders as `?{ (*untyped) -> untyped }`. Both new-method
143
+ and tighter-return paths apply.
132
144
  - **Singleton-side `def self.foo`** and
133
145
  `class << self; def foo; end`. Rendered as
134
146
  `def self.foo: ...`; matched against
@@ -143,10 +155,8 @@ list is the current state):
143
155
  are recognised as user-authored and never produce a
144
156
  duplicate `def` insertion.
145
157
 
146
- Method shapes the generator does **not** cover yet (and
147
- silently skips):
158
+ Method shapes the generator does **not** cover yet:
148
159
 
149
- - Optional / rest / keyword / block / forwarding parameters.
150
160
  - `define_method(:name) { ... }`.
151
161
  - Methods whose body types as `Dynamic[top]` (the body
152
162
  inference cannot prove a useful return type).
@@ -303,9 +313,9 @@ inference, not a separate analysis.
303
313
 
304
314
  ## Limits today
305
315
 
306
- - Methods with optional / rest / keyword / block /
307
- forwarding parameters silently skip
308
- (`sig.skipped.complex-shape`).
316
+ - A block parameter always renders as the lenient
317
+ `?{ (*untyped) -> untyped }`; a typed block signature
318
+ waits on the engine tracking yield shapes end-to-end.
309
319
  - `define_method` and `Data.define`-specific emission are
310
320
  deferred follow-ups (`Data.define`-derived readers come
311
321
  through if a method body exists).
@@ -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`
@@ -466,6 +475,13 @@ exits `1`: you asked for a write and did not get one. Such a skip
466
475
  is a bug in Rigor's RBS rendering, not in your code — please
467
476
  report it.
468
477
 
478
+ A method the generator declined for any other reason (a body
479
+ that proves only `untyped`, a user-authored declaration it will
480
+ not overwrite) is never a silent absence: under `--format=json`
481
+ it is a `skipped` row of the `candidates` array with its
482
+ `sig.skipped.*` identifier in `skip_reason`, and in text mode a
483
+ one-line stderr summary counts the skipped methods per reason.
484
+
469
485
  ## `rigor lsp`
470
486
 
471
487
  Run the Language Server over stdio. See
@@ -1001,7 +1017,7 @@ Queued today:
1001
1017
 
1002
1018
  | Feature id | Kind | What it changes |
1003
1019
  | --- | --- | --- |
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. |
1020
+ | `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
1021
  | `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
1022
  | `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
1023
  | `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. |
@@ -74,7 +74,7 @@ carries no `documentation_url`.
74
74
  | <a id="rule-effect-envelope-exceeded"></a>`effect.envelope-exceeded` | A method performs an effect its declared envelope does not admit — its proven effect labels (its own body plus everything it calls) are not covered by the `%a{pure}` or `%a{rigor:v1:effect …}` bound written on it or on its class. Opt-in twice over: it needs an `effects:` block in `.rigor.yml` and an envelope you wrote. Positioned at the Ruby `def`. Unproven ("and possibly more") effects never fire, and `mutate.local` is tolerated by every envelope. | high |
75
75
  | <a id="rule-effect-liskov-widened"></a>`effect.liskov-widened` | An override escapes the envelope written on the method it overrides. A `PgRepo` is usable wherever a `Repo` is, so a `%a{rigor:v1:effect io.db}` on `Repo#find` binds `PgRepo#find` too: an implementation may be purer than the bound it inherits, never less pure. Either what the override *does* exceeds the inherited bound, or the envelope the override *declares for itself* is wider than it. Both sides must be authored — nothing fires unless someone wrote an envelope on the ancestor — and only subclassing counts, not `include`. Positioned at the override's `def`. Needs an `effects:` block. | high |
76
76
  | <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or `.rigor.yml` for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
77
- | <a id="rule-effect-annotations-unchecked"></a>`effect.annotations-unchecked` | Your signatures carry `%a{pure}` / `%a{rigor:v1:effect …}` but `.rigor.yml` has no `effects:` block, so nothing checks them. One `:info` per run, positioned at the first annotation. An annotation never turns effect collection on by itself — that would make one line in one file more expensive for every run — so this is how it tells you instead. Add `effects: {}` to opt in, or `disable:` it to keep the annotations documentary. Reads both annotation lanes — `sig/*.rbs` and rbs-inline comments — except on a run that analyses no file at all (a warm `--incremental` with nothing changed), where only the `.rbs` lane is visible. | — |
77
+ | <a id="rule-effect-annotations-unchecked"></a>`effect.annotations-unchecked` | Your signatures carry `%a{pure}` / `%a{rigor:v1:effect …}` but `.rigor.yml` has no `effects:` block, so nothing checks them. One `:info` per run, positioned at the first annotation. An annotation never turns effect collection on by itself — that would make one line in one file more expensive for every run — so this is how it tells you instead. Add `effects: {}` to opt in, or `disable:` it to keep the annotations documentary. Reads both annotation lanes — `sig/*.rbs` and rbs-inline comments — on every run, a warm `--incremental` with nothing changed included. | — |
78
78
  | <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
79
79
  | <a id="rule-suppression-empty"></a>`suppression.empty` | A `# rigor:disable[-file]` comment lists no rules, so it suppresses nothing. | high |
80
80
  | <a id="rule-suppression-unknown-marker"></a>`suppression.unknown-marker` | A comment uses a suppression marker Rigor does not recognise — typically the RuboCop reflex `# rigor:disable-next-line <rule>` or `# rigor:enable <rule>`. Rigor's only markers are `# rigor:disable <rules>` (suppresses on its own line) and `# rigor:disable-file <rules>`, so the comment suppresses nothing. | high |