rigortype 0.3.3 → 0.3.5

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/builtins/ruby_core/array.yml +11 -24
  4. data/data/builtins/ruby_core/enumerable.yml +4 -8
  5. data/data/builtins/ruby_core/exception.yml +6 -9
  6. data/data/builtins/ruby_core/hash.yml +13 -20
  7. data/data/builtins/ruby_core/io.yml +6 -9
  8. data/data/builtins/ruby_core/numeric.yml +2 -3
  9. data/data/builtins/ruby_core/proc.yml +4 -8
  10. data/data/builtins/ruby_core/re.yml +6 -9
  11. data/data/builtins/ruby_core/set.yml +2 -3
  12. data/data/builtins/ruby_core/string.yml +4 -7
  13. data/data/builtins/ruby_core/struct.yml +0 -6
  14. data/data/builtins/ruby_core/time.yml +3 -6
  15. data/data/effects/core.yml +860 -0
  16. data/data/effects/registry.yml +95 -0
  17. data/docs/handbook/07-rbs-and-extended.md +106 -5
  18. data/docs/manual/02-cli-reference.md +359 -2
  19. data/docs/manual/03-configuration.md +101 -1
  20. data/docs/manual/04-diagnostics.md +8 -3
  21. data/docs/manual/11-ci.md +37 -0
  22. data/docs/manual/12-caching.md +39 -0
  23. data/docs/manual/16-rbs-extended-annotations.md +90 -0
  24. data/docs/manual/18-removing-dead-code.md +326 -0
  25. data/docs/manual/19-effect-labels.md +671 -0
  26. data/docs/manual/README.md +10 -0
  27. data/docs/manual/ci-templates/README.md +9 -0
  28. data/docs/manual/plugins/rigor-actionmailer.md +5 -0
  29. data/docs/manual/plugins/rigor-activejob.md +47 -0
  30. data/docs/manual/plugins/rigor-factorybot.md +16 -0
  31. data/docs/manual/plugins/rigor-pundit.md +26 -0
  32. data/docs/manual/plugins/rigor-rails-routes.md +41 -2
  33. data/docs/manual/plugins/rigor-rspec-rails.md +5 -0
  34. data/docs/manual/plugins/rigor-rspec.md +8 -0
  35. data/docs/manual/plugins/rigor-sidekiq.md +54 -0
  36. data/lib/rigor/analysis/check_rules/rule_ids.rb +28 -2
  37. data/lib/rigor/analysis/incremental_session.rb +140 -6
  38. data/lib/rigor/analysis/reachability/graph.rb +244 -0
  39. data/lib/rigor/analysis/reachability/plugin_roots.rb +164 -0
  40. data/lib/rigor/analysis/reachability/project_files.rb +58 -0
  41. data/lib/rigor/analysis/reachability/scan.rb +241 -0
  42. data/lib/rigor/analysis/reachability/signature_scan.rb +134 -0
  43. data/lib/rigor/analysis/rule_catalog.rb +168 -3
  44. data/lib/rigor/analysis/run_cache_key.rb +16 -0
  45. data/lib/rigor/analysis/run_cache_probe.rb +69 -1
  46. data/lib/rigor/analysis/runner/declaration_position.rb +38 -0
  47. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +108 -0
  48. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +358 -0
  49. data/lib/rigor/analysis/runner/envelope_messages.rb +61 -0
  50. data/lib/rigor/analysis/runner/pool_coordinator.rb +93 -10
  51. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -4
  52. data/lib/rigor/analysis/runner.rb +429 -10
  53. data/lib/rigor/analysis/worker_session.rb +60 -2
  54. data/lib/rigor/bleeding_edge.rb +22 -0
  55. data/lib/rigor/cache/incremental_snapshot.rb +24 -5
  56. data/lib/rigor/cli/check_command.rb +13 -1
  57. data/lib/rigor/cli/check_runner_factory.rb +4 -1
  58. data/lib/rigor/cli/effects_command.rb +245 -0
  59. data/lib/rigor/cli/effects_diff_renderer.rb +127 -0
  60. data/lib/rigor/cli/effects_explain_renderer.rb +68 -0
  61. data/lib/rigor/cli/effects_renderer.rb +92 -0
  62. data/lib/rigor/cli/effects_report.rb +163 -0
  63. data/lib/rigor/cli/effects_snapshot_command.rb +275 -0
  64. data/lib/rigor/cli/unused_command.rb +288 -0
  65. data/lib/rigor/cli.rb +28 -1
  66. data/lib/rigor/configuration/severity_profile.rb +23 -0
  67. data/lib/rigor/configuration.rb +322 -28
  68. data/lib/rigor/configuration_error.rb +20 -0
  69. data/lib/rigor/effects/attribution.rb +76 -0
  70. data/lib/rigor/effects/catalog.rb +275 -0
  71. data/lib/rigor/effects/collector.rb +219 -0
  72. data/lib/rigor/effects/config_envelopes.rb +185 -0
  73. data/lib/rigor/effects/discharge.rb +69 -0
  74. data/lib/rigor/effects/effect_table.rb +92 -0
  75. data/lib/rigor/effects/entry_points.rb +139 -0
  76. data/lib/rigor/effects/envelope.rb +86 -0
  77. data/lib/rigor/effects/envelope_check.rb +172 -0
  78. data/lib/rigor/effects/envelope_index.rb +157 -0
  79. data/lib/rigor/effects/file_collection.rb +174 -0
  80. data/lib/rigor/effects/framework_units.rb +222 -0
  81. data/lib/rigor/effects/identity.rb +104 -0
  82. data/lib/rigor/effects/inline_anchor.rb +134 -0
  83. data/lib/rigor/effects/label.rb +77 -0
  84. data/lib/rigor/effects/label_intent.rb +73 -0
  85. data/lib/rigor/effects/label_set.rb +136 -0
  86. data/lib/rigor/effects/liskov_check.rb +167 -0
  87. data/lib/rigor/effects/local_ownership.rb +132 -0
  88. data/lib/rigor/effects/method_key.rb +40 -0
  89. data/lib/rigor/effects/mutation_classifier.rb +92 -0
  90. data/lib/rigor/effects/narrowing.rb +202 -0
  91. data/lib/rigor/effects/origin.rb +66 -0
  92. data/lib/rigor/effects/path_finder.rb +89 -0
  93. data/lib/rigor/effects/plugin_facts.rb +384 -0
  94. data/lib/rigor/effects/propagator.rb +335 -0
  95. data/lib/rigor/effects/registry.rb +200 -0
  96. data/lib/rigor/effects/scanner.rb +302 -0
  97. data/lib/rigor/effects/signature_sources.rb +90 -0
  98. data/lib/rigor/effects/snapshot.rb +396 -0
  99. data/lib/rigor/effects/snapshot_diff.rb +265 -0
  100. data/lib/rigor/effects/summary.rb +154 -0
  101. data/lib/rigor/effects/taint_cause.rb +39 -0
  102. data/lib/rigor/effects/unit_scan.rb +647 -0
  103. data/lib/rigor/effects/unknown_label_check.rb +86 -0
  104. data/lib/rigor/effects/unknown_label_report.rb +59 -0
  105. data/lib/rigor/effects.rb +47 -0
  106. data/lib/rigor/environment/rbs_loader.rb +34 -0
  107. data/lib/rigor/environment.rb +8 -1
  108. data/lib/rigor/flow_contribution/element.rb +1 -0
  109. data/lib/rigor/flow_contribution/merge_result.rb +5 -3
  110. data/lib/rigor/flow_contribution/merger.rb +16 -1
  111. data/lib/rigor/flow_contribution.rb +20 -4
  112. data/lib/rigor/inference/expression_typer.rb +18 -4
  113. data/lib/rigor/inference/pre_eval_constants.rb +189 -0
  114. data/lib/rigor/inference/scope_indexer.rb +15 -3
  115. data/lib/rigor/plugin/base.rb +28 -0
  116. data/lib/rigor/plugin/box.rb +18 -2
  117. data/lib/rigor/plugin/effect_ancestry.rb +80 -0
  118. data/lib/rigor/plugin/effect_attribution.rb +208 -0
  119. data/lib/rigor/plugin/effect_edge.rb +101 -0
  120. data/lib/rigor/plugin/effect_entry_points.rb +51 -0
  121. data/lib/rigor/plugin/first_party.rb +57 -0
  122. data/lib/rigor/plugin/isolation.rb +4 -1
  123. data/lib/rigor/plugin/manifest.rb +127 -3
  124. data/lib/rigor/plugin/registry.rb +55 -0
  125. data/lib/rigor/plugin.rb +1 -0
  126. data/lib/rigor/rbs_extended/envelope_scanner.rb +160 -0
  127. data/lib/rigor/rbs_extended.rb +188 -0
  128. data/lib/rigor/reflection.rb +131 -30
  129. data/lib/rigor/version.rb +1 -1
  130. data/lib/rigor.rb +1 -0
  131. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +90 -0
  132. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +7 -1
  133. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/effects.rb +85 -0
  134. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +8 -1
  135. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +152 -0
  136. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +9 -1
  137. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +156 -0
  138. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +113 -0
  139. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +68 -3
  140. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +186 -0
  141. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +12 -1
  142. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +145 -0
  143. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/effects.rb +77 -0
  144. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -1
  145. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +143 -0
  146. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -1
  147. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +401 -29
  148. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +27 -0
  149. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -2
  150. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +151 -0
  151. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +46 -3
  152. data/plugins/rigor-rails/lib/rigor-rails.rb +1 -0
  153. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/effects.rb +47 -0
  154. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +7 -1
  155. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +81 -0
  156. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +13 -1
  157. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +202 -15
  158. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +43 -3
  159. data/plugins/rigor-railties/lib/rigor/plugin/railties/effects.rb +171 -0
  160. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +62 -0
  161. data/plugins/rigor-railties/lib/rigor-railties.rb +3 -0
  162. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +56 -1
  163. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +75 -0
  164. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +122 -0
  165. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +68 -3
  166. data/sig/rigor/analysis/fact_store.rbs +1 -0
  167. data/sig/rigor/inference.rbs +2 -0
  168. data/sig/rigor/rbs_extended.rbs +6 -0
  169. data/sig/rigor.rbs +17 -1
  170. data/skills/rigor-unused-adjudicate/SKILL.md +90 -0
  171. metadata +84 -1
@@ -6,6 +6,7 @@ require_relative "../../environment"
6
6
  require_relative "../diagnostic"
7
7
  require_relative "../worker_session"
8
8
  require_relative "../run_stats"
9
+ require_relative "../../effects/signature_sources"
9
10
  require_relative "../../rbs_extended/conformance_checker"
10
11
  require_relative "../../runtime/jit"
11
12
 
@@ -61,6 +62,11 @@ module Rigor
61
62
  # slice's cross-file reads, marshalled back into `#collected_dependencies` for the runner's graph.
62
63
  @record_dependencies = record_dependencies
63
64
  @collected_dependencies = {}
65
+ # ADR-103 WD13 — effect collection, derived from the configuration exactly as `Runner` and
66
+ # `WorkerSession` derive it. A worker's per-file collections marshal back into
67
+ # `#collected_effects`, so a pooled run's effect graph is the sequential run's graph.
68
+ @record_effects = configuration.effects_enabled?
69
+ @collected_effects = {}
64
70
  @collect_stats = collect_stats
65
71
  @buffer = buffer
66
72
  @environment_override = environment_override
@@ -81,6 +87,11 @@ module Rigor
81
87
  # so the incremental session's dependency graph is refreshed whether analysis ran sequential or pooled.
82
88
  attr_reader :collected_dependencies
83
89
 
90
+ # ADR-103 WD12 — the per-file {Effects::FileCollection}s the fork pool captured this run (empty
91
+ # unless the configuration carries an `effects:` block and a pool run occurred). The runner merges
92
+ # these with its own sequential records before the post-pool fixpoint.
93
+ attr_reader :collected_effects
94
+
84
95
  # ADR-15 Phase 4b — pool mode is enabled when `@workers > 0`. Editor mode (`buffer:` non-nil)
85
96
  # silently overrides pool mode to sequential: per design § "Ractor pool mode", the pool's warm-up
86
97
  # cost dominates one-file wall time, so the pool gains nothing on a per-buffer invocation. The
@@ -113,6 +124,7 @@ module Rigor
113
124
 
114
125
  def analyze_files_sequentially(files, environment)
115
126
  snapshot_project_signature_state(environment)
127
+ snapshot_effect_annotation_carrier(environment&.rbs_loader)
116
128
  result = files.flat_map { |path| @analyze_file.call(path, environment) }
117
129
  if @collect_stats
118
130
  loader = environment.rbs_loader
@@ -147,6 +159,27 @@ module Rigor
147
159
  @snapshots.conformance_results = RbsExtended::ConformanceChecker.scan(loader)
148
160
  end
149
161
 
162
+ # #441 — the inline stratum of `effect.annotations-unchecked`, carried out of the analysis path as
163
+ # small plain data, for the same reason the block above is: the environment stays a LOCAL so it goes
164
+ # GC-eligible when the path returns, and the residual pass runs after it.
165
+ #
166
+ # {Runner#effect_annotation_residual_diagnostics} used to read the loader off `@run_environment`,
167
+ # which only the ADR-45 result-cacheable path ever assigns — so a `--no-cache`, `--workers N` or
168
+ # `--incremental` run reported a `.rbs` annotation and stayed silent about the identical one written
169
+ # as an rbs-inline comment, while a default run reported both. The environment was there either way;
170
+ # nothing was carrying it. {Effects::SignatureSources.annotated_carrier} reduces it to at most ONE
171
+ # synthesized buffer, which is all a one-`:info`-per-run pass can spend.
172
+ #
173
+ # Skipped when collection is on — then {Runner::EffectEnvelopePass} reads the loader directly and
174
+ # this pass is gated off — so a collecting run pays nothing, and a project with no synthesizing
175
+ # plugin answers with an empty array and no regex at all.
176
+ def snapshot_effect_annotation_carrier(loader)
177
+ virtual = @record_effects ? nil : loader&.virtual_rbs
178
+ @snapshots.effect_annotation_carrier = Effects::SignatureSources.annotated_carrier(virtual)
179
+ rescue StandardError
180
+ @snapshots.effect_annotation_carrier = [].freeze
181
+ end
182
+
150
183
  # Sequential-mode environment resolver. Returns the supplied `environment:` override (with the
151
184
  # runner's fresh per-run reporter pair attached so dispatcher events route to THIS runner's
152
185
  # diagnostics) when present; otherwise builds a fresh Environment per-call via
@@ -179,8 +212,12 @@ module Rigor
179
212
  # per-worker dependency records back, and the Ractor path additionally lacks the cross-file
180
213
  # `project_scope_seed` its worker would need to record complete edges. Without `fork` (Windows) a
181
214
  # recording run degrades to sequential, which records correctly through `@analyze_file`.
215
+ #
216
+ # An effects run (ADR-103 WD13) is pinned for exactly the same reason — the Ractor messages carry
217
+ # no side-table channel — and degrades the same way. The degrade is sound rather than merely safe:
218
+ # the sequential fallback still collects, so the effect graph is complete either way.
182
219
  def dispatch_pool(files)
183
- if @record_dependencies
220
+ if @record_dependencies || @record_effects
184
221
  return analyze_files_in_fork_pool(files) if Process.respond_to?(:fork)
185
222
 
186
223
  return analyze_files_sequentially_fallback(
@@ -250,6 +287,14 @@ module Rigor
250
287
  # replayed into the runner-side accumulators via their `record_*` APIs, which dedupe on the same
251
288
  # keys as a single-session run would. Net result: reporter state is identical to the sequential
252
289
  # path.
290
+ #
291
+ # **This backend cannot analyse a file under rbs 4.x, and the blocker is upstream** (#414).
292
+ # `RBS::Namespace.[]` interns every namespace through a process-wide flyweight cache held in
293
+ # module ivars (`@intern_trie_absolute` / `@intern_trie_relative`, mutable by design), and reading
294
+ # an unshareable module ivar from a non-main Ractor is `Ractor::IsolationError` — so every file a
295
+ # worker touches comes back as an internal analyzer error. What the code below fixes is the
296
+ # failure MODE, not the backend: the run used to hang forever instead of saying anything. Reviving
297
+ # the backend needs an upstream change, which is why `pool_backend` keeps `fork` as the default.
253
298
  def analyze_files_in_pool(files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
254
299
  # Pre-warm class-level lazy memos on the MAIN Ractor. `Environment::ClassRegistry.default` is the
255
300
  # default kwarg threaded through `Environment.new` inside each worker session; lazy-initialising
@@ -303,15 +348,30 @@ module Rigor
303
348
  end
304
349
  end
305
350
 
351
+ # #414 — every worker is monitored BEFORE any file is dispatched, so a worker that dies at
352
+ # construction is still observed. `Ractor#monitor` posts `:exited` / `:aborted` to this
353
+ # Ractor's default port, which is the same mailbox the workers push their results to.
354
+ pool.each { |worker| worker.monitor(Ractor.current.default_port) }
355
+
306
356
  files.each_with_index { |path, index| pool[index % pool.size].send(path) }
307
357
  pool.each { |worker| worker.send(nil) }
308
358
 
309
359
  prepare_diagnostics = nil
310
360
  results_by_path = {}
311
- done_count = 0
312
-
313
- while done_count < pool.size
361
+ terminated = 0
362
+
363
+ # Bounded by TERMINATION, not by `:done`. A worker that dies never says `:done`, and the loop
364
+ # that counted `:done` blocked on `Ractor.receive` for as long as the process lived — the whole
365
+ # backend hung that way once `Environment.for_project` started raising `Ractor::IsolationError`
366
+ # inside `WorkerSession#initialize` (#414). Monitor notices are bare Symbols; worker messages
367
+ # are Arrays, so the two never collide.
368
+ while terminated < pool.size
314
369
  message = Ractor.receive
370
+ if message.is_a?(Symbol)
371
+ terminated += 1
372
+ next
373
+ end
374
+
315
375
  case message.first
316
376
  when :prepare
317
377
  prepare_diagnostics ||= message.last
@@ -319,13 +379,27 @@ module Rigor
319
379
  results_by_path[message[1]] = message[2]
320
380
  when :done
321
381
  merge_worker_reporters(message.last)
322
- done_count += 1
323
382
  end
324
383
  end
325
384
 
326
- pool.each(&:join)
385
+ # `join` re-raises an abnormal worker's exception as `Ractor::RemoteError`; the degrade below is
386
+ # this path's answer to that, so the raise carries no information the coordinator acts on.
387
+ pool.each do |worker|
388
+ worker.join
389
+ rescue Ractor::Error
390
+ nil
391
+ end
392
+
393
+ # The files no worker reported — every file of a worker that died, and any file in flight when
394
+ # it did. Re-analysed in process, exactly as the fork backend re-analyses a dead child's slice.
395
+ degraded = files.reject { |path| results_by_path.key?(path) }
396
+ unless degraded.empty?
397
+ environment = build_runner_environment(source_files: files)
398
+ degraded.each { |path| results_by_path[path] = @analyze_file.call(path, environment) }
399
+ end
327
400
 
328
- Array(prepare_diagnostics) + files.flat_map { |path| results_by_path.fetch(path, []) }
401
+ diagnostics = Array(prepare_diagnostics) + files.flat_map { |path| results_by_path.fetch(path, []) }
402
+ degraded.empty? ? diagnostics : diagnostics.unshift(pool_degraded_diagnostic(degraded.size, "ractor"))
329
403
  end
330
404
 
331
405
  # ADR-15 Amendment (2026-05-20) — fork-based worker pool, the active backend for `workers > 0`.
@@ -357,6 +431,7 @@ module Rigor
357
431
  # Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
358
432
  # rather than each rebuilding it after the fork.
359
433
  session.environment.rbs_loader&.prewarm
434
+ snapshot_effect_annotation_carrier(session.environment.rbs_loader)
360
435
  snapshot_fork_pool_stats(session) if @collect_stats
361
436
 
362
437
  worker_count = [@workers, files.size].min
@@ -378,11 +453,12 @@ module Rigor
378
453
  # The degraded slice was re-analysed on the parent session, so its dependency records live there,
379
454
  # not in a child payload — fold them in alongside the successful children's.
380
455
  @collected_dependencies.merge!(session.drain_dependencies) if @record_dependencies
456
+ @collected_effects.merge!(session.drain_effects) if @record_effects
381
457
  end
382
458
 
383
459
  diagnostics = Array(session.prepare_diagnostics) +
384
460
  files.flat_map { |path| results_by_path.fetch(path, []) }
385
- degraded.empty? ? diagnostics : diagnostics.unshift(fork_degraded_diagnostic(degraded.size))
461
+ degraded.empty? ? diagnostics : diagnostics.unshift(pool_degraded_diagnostic(degraded.size, "fork"))
386
462
  end
387
463
 
388
464
  # Child-process body for {#analyze_files_in_fork_pool}. Analyses the slice with the
@@ -399,6 +475,9 @@ module Rigor
399
475
  results = slice.to_h { |path| [path, session.analyze(path)] }
400
476
  payload = { results: results, reporters: session.drain_reporters,
401
477
  dependencies: session.drain_dependencies }
478
+ # ADR-103 WD13 — the effects slot exists only on a collecting run, so a normal `--workers N` run
479
+ # marshals exactly the payload it marshalled before the feature existed.
480
+ payload[:effects] = session.drain_effects if @record_effects
402
481
  File.binwrite(out_path, Marshal.dump(payload))
403
482
  exit!(0)
404
483
  rescue StandardError
@@ -430,6 +509,7 @@ module Rigor
430
509
  results_by_path.merge!(payload.fetch(:results))
431
510
  merge_worker_reporters(payload.fetch(:reporters))
432
511
  @collected_dependencies.merge!(payload.fetch(:dependencies, {})) if @record_dependencies
512
+ @collected_effects.merge!(payload.fetch(:effects, {})) if @record_effects
433
513
  else
434
514
  degraded.concat(child[:slice])
435
515
  end
@@ -448,10 +528,12 @@ module Rigor
448
528
  nil
449
529
  end
450
530
 
451
- def fork_degraded_diagnostic(count)
531
+ # Both pool backends degrade the same way — the files a dead worker owed are re-analysed in
532
+ # process — so they say so the same way, naming which backend it was (#414).
533
+ def pool_degraded_diagnostic(count, backend)
452
534
  Diagnostic.new(
453
535
  path: ".rigor.yml", line: 1, column: 1,
454
- message: "fork pool degraded: #{count} file(s) re-analysed in-process " \
536
+ message: "#{backend} pool degraded: #{count} file(s) re-analysed in-process " \
455
537
  "after a worker exited abnormally",
456
538
  severity: :warning, rule: "pool-degraded", source_family: :builtin
457
539
  )
@@ -482,6 +564,7 @@ module Rigor
482
564
  # per-file analysis runs on the coordinator, identical to the default sequential path.
483
565
  def analyze_files_sequentially_fallback(files, reason:)
484
566
  environment = build_runner_environment
567
+ snapshot_effect_annotation_carrier(environment.rbs_loader)
485
568
  diagnostics = files.flat_map { |path| @analyze_file.call(path, environment) }
486
569
  loader = environment.rbs_loader
487
570
  @snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
@@ -3,10 +3,11 @@
3
3
  module Rigor
4
4
  module Analysis
5
5
  class Runner
6
- # Mutable per-run holder for the four end-of-pass snapshots that the analysis paths compute as a side
6
+ # Mutable per-run holder for the end-of-pass snapshots that the analysis paths compute as a side
7
7
  # effect and the rest of the run reads back: the RBS `class_decl_paths` / `signature_paths` tables
8
- # (consumed by {RunStats}), the synthesized-namespace name list, and the `conforms-to` scan results
9
- # (consumed by the diagnostic aggregator).
8
+ # (consumed by {RunStats}), the synthesized-namespace name list, the `conforms-to` scan results
9
+ # (consumed by the diagnostic aggregator), and the effect-annotation carrier (#441 — consumed by
10
+ # {EffectAnnotationResidualPass} on the runs that never store the environment).
10
11
  #
11
12
  # The snapshots are written by whichever analysis path ran ({PoolCoordinator} sequential / fork-pool /
12
13
  # fallback) and read by the {Runner} and {DiagnosticAggregator}. A shared mutable holder keeps the
@@ -16,7 +17,7 @@ module Rigor
16
17
  class RunSnapshots
17
18
  attr_accessor :class_decl_paths, :signature_paths,
18
19
  :synthesized_namespaces, :quarantined_signatures, :conformance_results,
19
- :env_build_failure
20
+ :env_build_failure, :effect_annotation_carrier
20
21
 
21
22
  # Constructor defaults match the {Runner} constructor: the pre-seed values `build_run_stats` /
22
23
  # `pre_file_diagnostics` read before the first analysis path runs are frozen empties. The
@@ -29,6 +30,7 @@ module Rigor
29
30
  @quarantined_signatures = [].freeze
30
31
  @conformance_results = [].freeze
31
32
  @env_build_failure = nil
33
+ @effect_annotation_carrier = [].freeze
32
34
  end
33
35
 
34
36
  # Per-`#run` reset. Mirrors the original `#run` body, which reset these to NON-frozen empties (distinct
@@ -40,6 +42,7 @@ module Rigor
40
42
  @quarantined_signatures = []
41
43
  @conformance_results = []
42
44
  @env_build_failure = nil
45
+ @effect_annotation_carrier = [].freeze
43
46
  end
44
47
  end
45
48
  end