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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../crash_signature"
3
4
  require_relative "../diagnostic"
4
5
  require_relative "../severity_stamp"
5
6
 
@@ -36,12 +37,20 @@ module Rigor
36
37
  # because they do not parse (`[path, first_error_line]` pairs).
37
38
  # @param env_build_failure_snapshot [#call] reader returning the total RBS env-build failure tuple
38
39
  # (`[error_class, first_error_line, conflicting_buffer_names]`) or nil when the env built.
40
+ # @param definition_build_failures_snapshot [#call] issue #696 — reader returning the per-class
41
+ # `RBS::DefinitionBuilder` failures the run observed, as `[class_name, error_class, member,
42
+ # conflicting_buffer_names]` tuples. Empty for a healthy sig set.
43
+ # @param hkt_scan_failure_snapshot [#call] issue #784 — reader returning the `[error_class_name,
44
+ # first_message_line, raw_frame_or_nil, stage]` tuple whichever stage of the HKT-registry build
45
+ # raised, or nil when both built (or were never demanded). `stage` is `:scan` (the RBS `type`-alias
46
+ # scan) or `:overlay` (the plugin-manifest aggregation, #791), and picks the row's wording.
39
47
  # @param conformance_results_snapshot [#call] reader.
40
48
  def initialize(configuration:, rbs_extended_reporter:, boundary_cross_reporter:, # rubocop:disable Metrics/ParameterLists
41
49
  source_rbs_synthesis_reporter:, plugin_registry:, dependency_source_index:,
42
50
  pool_mode:, cached_plugin_prepare_diagnostics:,
43
51
  pre_eval_diagnostics_from_scanner:, synthesized_namespaces_snapshot:,
44
52
  quarantined_signatures_snapshot:, env_build_failure_snapshot:,
53
+ definition_build_failures_snapshot:, hkt_scan_failure_snapshot:,
45
54
  conformance_results_snapshot:)
46
55
  @configuration = configuration
47
56
  @rbs_extended_reporter = rbs_extended_reporter
@@ -55,6 +64,8 @@ module Rigor
55
64
  @synthesized_namespaces_snapshot_reader = synthesized_namespaces_snapshot
56
65
  @quarantined_signatures_snapshot_reader = quarantined_signatures_snapshot
57
66
  @env_build_failure_snapshot_reader = env_build_failure_snapshot
67
+ @definition_build_failures_snapshot_reader = definition_build_failures_snapshot
68
+ @hkt_scan_failure_snapshot_reader = hkt_scan_failure_snapshot
58
69
  @conformance_results_snapshot_reader = conformance_results_snapshot
59
70
  end
60
71
 
@@ -311,9 +322,11 @@ module Rigor
311
322
  # user knows their sig set is malformed (`rbs validate` rejects it) and can fix it at the source.
312
323
  # Authored `:info`: the analysis already succeeded; this is advisory, never a gate. Empty for a
313
324
  # well-formed sig set.
314
- # An unparseable `.rbs` under `signature_paths:` is QUARANTINED so the rest of the env survives
315
- # (PR #50), which means the types it declares are silently absent calls into them read
316
- # `Dynamic[top]`, and the run gets *quieter*, not louder. The stderr banner alone never reached CI:
325
+ # An unparseable or declaration-colliding `.rbs` under `signature_paths:` is QUARANTINED so the
326
+ # rest of the env survives (PR #50; issue #777 extends this to class-vs-module / constant
327
+ # collisions against bundled RBS), which means the types it declares are silently absent calls
328
+ # into them read `Dynamic[top]`, and the run gets *quieter*, not louder. The stderr banner alone
329
+ # never reached CI:
317
330
  # it is not a diagnostic, so it is absent from `--format json` / SARIF / GitHub annotations / the LSP
318
331
  # and cannot move the exit code. This puts it in the diagnostic stream where every channel sees it.
319
332
  #
@@ -351,6 +364,51 @@ module Rigor
351
364
  [build_rbs_environment_build_failed_diagnostic(failure)]
352
365
  end
353
366
 
367
+ # Issue #696 — the third rung of the same ladder, between its two neighbours by consequence: a
368
+ # QUARANTINED file removes what one FILE declared, this removes what one CLASS declared, an
369
+ # ENV-BUILD failure removes everything. `RBS::DefinitionBuilder` raising for a class (typically
370
+ # `DuplicatedMethodDefinitionError`: two signature sources declaring the same method, e.g. a project
371
+ # `sig/` carrying a vendored COPY of a bundled plugin's `.rbs`) leaves the class KNOWN but with no
372
+ # method surface, so every call on it — real methods and typos alike — reads `Dynamic[top]`. When
373
+ # the collision is on a class others inherit, the whole bundled type universe goes with it: the
374
+ # observed shape is thousands of stderr warnings, ZERO diagnostics, and exit 0.
375
+ #
376
+ # Not "drop the class to genuinely-unknown" instead. That also removes diagnostics, so it is not
377
+ # louder; it changes `Dynamic[top]` semantics for every rule that consults `class_known?`; and
378
+ # `class_known?` reads {Environment::RbsLoader#known_class_names_set}, a different table from the
379
+ # definition builder, so it would need a new "known but unbuildable" state regardless. The failure
380
+ # is made REPORTABLE; the class is not made invisible.
381
+ #
382
+ # Authored `:warning`, not `:error`, for the same reason as both neighbours: the collision is
383
+ # typically between the user's `sig/` and Rigor's OWN bundled RBS, so an `:error` default would let
384
+ # a Rigor release turn a green build red with no user change (ADR-5 / AGENTS.md § FP discipline).
385
+ # The `reject-unparseable-signatures` bleeding-edge feature promotes it to `:error`.
386
+ def rbs_definition_build_failed_diagnostics
387
+ failures = definition_build_failures_snapshot
388
+ return [] if failures.nil? || failures.empty?
389
+
390
+ [build_rbs_definition_build_failed_diagnostic(failures)]
391
+ end
392
+
393
+ # Issue #784 — the fourth rung, narrowest consequence: one of the two builds behind
394
+ # `Environment#hkt_registry` raised instead of building. The `:scan` stage is the implicit HKT scan
395
+ # over RBS `type` aliases (ADR-20 WD2's `%a{rigor:v1:hkt_register / hkt_define}` overlay AND any
396
+ # recursive `type` alias in the project's own `.rbs` or an installed `rbs collection`); analysis
397
+ # proceeds over the PRE-scan registry — bundled builtins (`json::value`, …) plus the plugin overlay
398
+ # — so a `type` alias that would have registered as a type constructor reads its bound
399
+ # (`Dynamic[top]`) instead. The `:overlay` stage (#791) is the plugin-manifest aggregation, which
400
+ # sat ABOVE the seam until the run-owned demands made a raise there abort the run; it degrades one
401
+ # step further in and one step narrower — the plugin entries are dropped, the `.rbs` scan still
402
+ # runs. Either way everything else this run reports is unaffected: no class loses its method
403
+ # surface, no signature file is skipped, the environment builds. That is why this sits LAST on the
404
+ # ladder, after its two `rbs.coverage.*` siblings above.
405
+ def rbs_hkt_scan_failed_diagnostics
406
+ failure = hkt_scan_failure_snapshot
407
+ return [] if failure.nil?
408
+
409
+ [build_rbs_hkt_scan_failed_diagnostic(failure)]
410
+ end
411
+
354
412
  def rbs_synthesized_namespace_diagnostics
355
413
  synthesized = synthesized_namespaces_snapshot
356
414
  return [] if synthesized.nil? || synthesized.empty?
@@ -428,11 +486,12 @@ module Rigor
428
486
  path: ".rigor.yml",
429
487
  line: 1,
430
488
  column: 1,
431
- message: "#{quarantined.size} RBS file(s) under `signature_paths:` do not parse and were " \
432
- "SKIPPED: #{sample.join(', ')}#{suffix}. The rest of your RBS environment still " \
433
- "loaded, but the types those files declare are absent — calls into them read " \
434
- "`Dynamic[top]`, so this run is quieter than it should be, not cleaner. Fix the " \
435
- "parse error(s) (`rbs validate`) to restore that coverage.",
489
+ message: "#{quarantined.size} RBS file(s) under `signature_paths:` were SKIPPED " \
490
+ "(unparseable, or duplicated against bundled RBS): #{sample.join(', ')}#{suffix}. " \
491
+ "The rest of your RBS environment still loaded, but the types those files declare " \
492
+ "are absent — calls into them read `Dynamic[top]`, so this run is quieter than it " \
493
+ "should be, not cleaner. Fix the parse error(s) or remove the conflicting " \
494
+ "declaration(s) (`rbs validate`) to restore that coverage.",
436
495
  severity: :warning,
437
496
  rule: "rbs.coverage.quarantined-signature",
438
497
  source_family: :builtin
@@ -462,6 +521,124 @@ module Rigor
462
521
  )
463
522
  end
464
523
 
524
+ # Issue #696. One diagnostic per RUN, not per class: a collision on a widely-inherited class fails
525
+ # every descendant, and 2,709 rows saying the same thing about one `.rbs` line is noise, not
526
+ # visibility.
527
+ #
528
+ # The failed-class list alone is not actionable, and that is what the "First failure" clause is for.
529
+ # A duplicate on `::Object#blank?` fails `String`, `Integer`, `Array` and 1,333 more — every name in
530
+ # that list is a DESCENDANT, and none of them is where the fix goes. The member comes off the error
531
+ # object rather than out of its message ({RbsLoader#definition_build_member}), so it names the
532
+ # culprit and reads the same on a cache hit, where every `RBS::Location` has been dumped to a
533
+ # sentinel. The error class is attributed to that first failure explicitly rather than to all of
534
+ # them: the per-class memo keeps only the first reason, and one run can mix error classes.
535
+ def build_rbs_definition_build_failed_diagnostic(failures)
536
+ sample_size = 5
537
+ files = failures.flat_map { |failure| Array(failure[3]) }.uniq.map { |name| relative_signature_path(name) }
538
+ Diagnostic.new(
539
+ path: ".rigor.yml",
540
+ line: 1,
541
+ column: 1,
542
+ message: "#{failures.size} RBS class definition(s) failed to build: " \
543
+ "#{sampled(failures.map(&:first), sample_size)}." \
544
+ "#{first_failure_clause(failures.first)}#{conflicting_files_clause(files, sample_size)} " \
545
+ "Rigor still treats each class as KNOWN, so calls into it — real methods and typos " \
546
+ "alike — silently read `Dynamic[top]` instead of resolving, and this run is quieter " \
547
+ "than it should be rather than cleaner. Two signature sources declare the same " \
548
+ "member; remove the duplicate declaration (`rbs validate`) to restore type coverage.",
549
+ severity: :warning,
550
+ rule: "rbs.coverage.definition-build-failed",
551
+ source_family: :builtin
552
+ )
553
+ end
554
+
555
+ # `[class_name, error_class, member, buffers]`. The member is nil for the error classes that carry no
556
+ # name at all (`RecursiveAncestorError`), and the clause then names the error class alone rather than
557
+ # inventing a member.
558
+ def first_failure_clause(failure)
559
+ _, error_class, member, = failure
560
+ return " First failure: #{error_class}." if member.nil? || member.empty?
561
+
562
+ " First failure: #{error_class} on `#{member}`."
563
+ end
564
+
565
+ # Identical on a cache HIT: the ADR-54 env cache drops location POSITIONS but keeps each buffer's
566
+ # NAME, so a warm run names the same files a cold one does. Where a name is absent anyway, the
567
+ # reconstructed {RbsLoader::CACHED_LOCATION_BUFFER_NAME} sentinel is filtered out and the clause is
568
+ # omitted, rather than naming `<cached>` as if it were a path.
569
+ def conflicting_files_clause(files, sample_size)
570
+ return "" if files.empty?
571
+
572
+ " Conflicting signature file(s): #{sampled(files, sample_size)}."
573
+ end
574
+
575
+ # "a, b, c, and N more" — the sampling shape every `rbs.coverage.*` message above uses.
576
+ def sampled(names, sample_size)
577
+ sample = names.first(sample_size)
578
+ suffix = names.size > sample_size ? ", and #{names.size - sample_size} more" : ""
579
+ "#{sample.join(', ')}#{suffix}"
580
+ end
581
+
582
+ # Issue #784 — one `:error` row per run, unlike its two `:warning` `rbs.coverage.*` siblings above.
583
+ # Those two are typically a collision between the user's OWN `sig/` and Rigor's bundled RBS, so an
584
+ # `:error` default would let a Rigor release turn a green project red with zero user change (ADR-5
585
+ # / AGENTS.md § FP discipline) — the reason both stay `:warning` by default. This row has no such
586
+ # neighbour: post-#783 the scan itself raising is an ANALYZER defect, never something a user's
587
+ # `sig/` could trigger on its own, so there is no green project this could newly redden. And the
588
+ # run was already non-zero before this row existed — issue #784's seam is what stopped the raise
589
+ # from reaching every file as N identical `internal analyzer error` rows in the first place, and
590
+ # THAT per-file rescue is what `Result#success?` was already reading as a failure; this row only
591
+ # makes the reason legible.
592
+ #
593
+ # One row per RUN, not per file: the scan is one build over the whole `signature_paths:` overlay
594
+ # (memoised — see {Environment#hkt_registry}), so every file that would have demanded it hit the
595
+ # exact same failure, and a row per file would say the same thing N times.
596
+ #
597
+ # `:rbs_build` (via {CrashSignature::RBS_BUILD_FAILURE_RULES}), not `:check_rule`: the analysis ran
598
+ # to completion over a DEGRADED type universe — every rule still fired, unlike `:check_rule`'s
599
+ # whole-file replacement — so a consumer gating on {CrashSignature.discards_file_analysis?} must
600
+ # keep reading this run's diagnostics rather than refuse it as a crash.
601
+ def build_rbs_hkt_scan_failed_diagnostic(failure)
602
+ error_class, first_line, frame, stage = failure
603
+ relative_frame = CrashSignature.relativize_frame(frame)
604
+ frame_clause = relative_frame ? " at #{relative_frame}" : ""
605
+ raised = "(#{error_class}): #{first_line}#{frame_clause}."
606
+ Diagnostic.new(
607
+ path: ".rigor.yml",
608
+ line: 1,
609
+ column: 1,
610
+ message: stage == :overlay ? hkt_overlay_failed_message(raised) : hkt_scan_failed_message(raised),
611
+ severity: :error,
612
+ rule: "rbs.coverage.hkt-scan-failed",
613
+ source_family: :builtin
614
+ )
615
+ end
616
+
617
+ def hkt_scan_failed_message(raised)
618
+ "The implicit HKT scan over RBS `type` aliases raised #{raised} Rigor fell back to the bundled " \
619
+ "and plugin HKT registrations, so a recursive `type` alias in your `.rbs` or an installed " \
620
+ "`rbs collection` no longer registers as a type constructor and reads its bound " \
621
+ "(`Dynamic[top]`) instead — this run is quieter than it should be, not cleaner. " \
622
+ "This is an analyzer defect, not a problem with your signatures; please report it " \
623
+ "with the message above."
624
+ end
625
+
626
+ # Issue #791 — the overlay stage of the same build, worded for the plugin it came from. The scan
627
+ # wording would send a user to their `.rbs` for a defect that is not there: what failed is the
628
+ # aggregation of the loaded plugins' manifest-declared HKT entries, and the message names the
629
+ # plugin whenever the raise was attributable to one (`Plugin::Registry#hkt_overlay_registry` puts
630
+ # the id in the message it re-raises). The degradation is narrower than the scan's, so the fallback
631
+ # sentence differs too: only the plugin entries are missing, and the user's own `.rbs` scan still
632
+ # ran on top of the bundled registrations.
633
+ def hkt_overlay_failed_message(raised)
634
+ "Building the plugin HKT overlay raised #{raised} Rigor skipped every plugin-declared HKT " \
635
+ "registration and analysed with the bundled ones plus your own `.rbs` overlay, so a type " \
636
+ "constructor a plugin declares reads its bound (`Dynamic[top]`) instead — this run is " \
637
+ "quieter than it should be, not cleaner. This is a defect in the named plugin or in Rigor, " \
638
+ "not a problem with your signatures; report it with the message above, or remove the plugin " \
639
+ "from `plugins:` to analyse without it."
640
+ end
641
+
465
642
  # The absolute path is what the loader records; the user thinks in project-relative terms.
466
643
  def relative_signature_path(path)
467
644
  root = "#{Dir.pwd}#{File::SEPARATOR}"
@@ -515,16 +692,19 @@ module Rigor
515
692
  # enable.
516
693
  # - `dynamic.shape.lossy-projection` for every shape-projection type function (`pick_of`, …) applied
517
694
  # to a carrier that loses precision (anything other than `HashShape` / `Tuple`).
695
+ # - `dynamic.rbs-extended.hkt-directive-invalid` for every malformed ADR-20 `rigor:v1:hkt_register` /
696
+ # `rigor:v1:hkt_define` the directive parser declined (issue #785).
518
697
  #
519
- # Both are authored `:info`; the severity profile re-stamps them per project taste. Path / line /
520
- # column come from the annotation's `RBS::Location` when available, falling back to
521
- # `.rigor.yml`-style file-level attribution otherwise.
698
+ # All three are authored `:info`; the severity profile re-stamps them per project taste. Every stream
699
+ # carries its `(path, line, column)` already flattened off the annotation's `RBS::Location`, because
700
+ # all three cross the pool drain channel (see {RbsExtended::Reporter}); an entry with no position
701
+ # falls back to `.rigor.yml`-style file-level attribution.
522
702
  def rbs_extended_reporter_diagnostics
523
703
  return [] if @rbs_extended_reporter.empty?
524
704
 
525
705
  unresolved = @rbs_extended_reporter.unresolved_payloads.map do |entry|
526
- build_reporter_diagnostic(
527
- entry.source_location,
706
+ build_positioned_reporter_diagnostic(
707
+ entry,
528
708
  rule: "dynamic.rbs-extended.unresolved",
529
709
  message: "`RBS::Extended` directive payload could not be resolved: " \
530
710
  "#{entry.payload.inspect}. Check for typos or enable a plugin " \
@@ -533,8 +713,8 @@ module Rigor
533
713
  end
534
714
 
535
715
  lossy = @rbs_extended_reporter.lossy_projections.map do |entry|
536
- build_reporter_diagnostic(
537
- entry.source_location,
716
+ build_positioned_reporter_diagnostic(
717
+ entry,
538
718
  rule: "dynamic.shape.lossy-projection",
539
719
  message: "Shape projection `#{entry.head}` applied to a carrier without a " \
540
720
  "literal shape; the projection degrades to the input type. Author " \
@@ -542,7 +722,22 @@ module Rigor
542
722
  )
543
723
  end
544
724
 
545
- unresolved + lossy
725
+ unresolved + lossy + hkt_directive_diagnostics
726
+ end
727
+
728
+ # Issue #785 — one row per declined HKT directive. The consequence sentence is the point: the parser
729
+ # is fail-soft, so nothing else in the run tells the author that the constructor they registered is
730
+ # not there and that every `App[…]` naming it silently reads its bound.
731
+ def hkt_directive_diagnostics
732
+ @rbs_extended_reporter.hkt_directive_errors.map do |entry|
733
+ build_positioned_reporter_diagnostic(
734
+ entry,
735
+ rule: "dynamic.rbs-extended.hkt-directive-invalid",
736
+ message: "`RBS::Extended` HKT directive was declined: #{entry.message}. The type " \
737
+ "constructor stays unregistered, so an `App[...]` carrier naming it reads " \
738
+ "its bound (`Dynamic[top]`) instead of the type function."
739
+ )
740
+ end
546
741
  end
547
742
 
548
743
  # ADR-32 WD6 — drains the per-run {Plugin::SourceRbsSynthesisReporter} into
@@ -624,6 +819,20 @@ module Rigor
624
819
  )
625
820
  end
626
821
 
822
+ # The {RbsExtended::Reporter} form of the builder above: its three streams carry the position as
823
+ # `(path, line, column)` primitives rather than as the `RBS::Location` a conformance record holds,
824
+ # because they cross the pool drain channel (#785, #805). A missing component falls back exactly as
825
+ # {#location_fields} does for a missing location.
826
+ def build_positioned_reporter_diagnostic(entry, rule:, message:)
827
+ path = entry.path.to_s
828
+ Diagnostic.new(
829
+ path: path.empty? ? ".rigor.yml" : path,
830
+ line: entry.line || 1,
831
+ column: entry.column || 1,
832
+ message: message, severity: :info, rule: rule, source_family: :builtin
833
+ )
834
+ end
835
+
627
836
  def location_fields(source_location)
628
837
  return [".rigor.yml", 1, 1] if source_location.nil?
629
838
 
@@ -684,6 +893,14 @@ module Rigor
684
893
  @env_build_failure_snapshot_reader.call
685
894
  end
686
895
 
896
+ def definition_build_failures_snapshot
897
+ @definition_build_failures_snapshot_reader.call
898
+ end
899
+
900
+ def hkt_scan_failure_snapshot
901
+ @hkt_scan_failure_snapshot_reader.call
902
+ end
903
+
687
904
  def conformance_results_snapshot
688
905
  @conformance_results_snapshot_reader.call
689
906
  end