rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -55,35 +55,36 @@ module Rigor
55
55
  # knowledge the single-file run does not have) must not mask a kill the shipped oracle would report.
56
56
  Baseline = Data.define(:own, :dependents)
57
57
 
58
- # @param configuration [Rigor::Configuration]
59
- # @param environment [Rigor::Environment] built once by the caller.
60
- # @param project_scan [Rigor::Analysis::ProjectScan] built once by the caller; adopted per analysis
58
+ # @param environment — built once by the caller.
59
+ # @param project_scan built once by the caller; adopted per analysis
61
60
  # through `prebuilt:`, exactly as {DiagnosticOracle} does.
62
- # @param paths [Array<String>] the measured file set, in canonical order (the seed's span: a class
61
+ # @param paths the measured file set, in canonical order (the seed's span: a class
63
62
  # declared outside it stays unknown, as it does for Tier 1's seed and for {DiscoverySeed}).
64
- # @param dependents [Hash{String => Array<String>}] {DependencyClosure} map, restricted to `paths`.
65
- # @param seed_bundles [Hash{String => Hash}] {DiscoverySeed.bundles} over the same `paths`.
66
- # @param discovery_seed [Hash, nil] the `discovery-seeded-mutation-sites` seed when that feature is also
63
+ # @param dependents {DependencyClosure} map, restricted to `paths`.
64
+ # @param seed_bundles {DiscoverySeed.bundles} over the same `paths`.
65
+ # @param discovery_seed the `discovery-seeded-mutation-sites` seed when that feature is also
67
66
  # adopted, nil otherwise. It goes to the delegated {DiagnosticOracle} verbatim, so the mutated file's
68
67
  # verdict is byte-for-byte the verdict that feature combination produces without this one; its
69
68
  # `param_inferred_types` slot additionally rides the per-mutant closure seed, so an admitted site is
70
69
  # judged with the knowledge that admitted it (issue #260's amended decision). The table is not
71
70
  # refreshed per mutant — the collector is a whole-project pre-pass, and one mutated method body does
72
71
  # not justify re-running it thousands of times.
72
+ # @param rebuild_session — issue #790 — a callable returning a FRESH `[environment, project_scan]`
73
+ # pair, invoked after a run this oracle refused for an analyzer defect. nil means the caller has no
74
+ # way to rebuild; see {#recovering} for what the oracle then does instead.
75
+ # rubocop:disable Metrics/ParameterLists -- the eight are independent collaborators the caller builds
76
+ # once each; an options object would only put a name in front of the same list.
73
77
  def initialize(configuration:, environment:, project_scan:, paths:, dependents:, seed_bundles:,
74
- discovery_seed: nil)
78
+ discovery_seed: nil, rebuild_session: nil)
79
+ # rubocop:enable Metrics/ParameterLists
75
80
  @configuration = configuration
76
- @environment = environment
77
- @project_scan = project_scan
78
81
  @paths = paths
79
82
  @dependents = dependents
80
83
  @seed_bundles = seed_bundles
81
84
  @discovery_seed = discovery_seed
85
+ @rebuild_session = rebuild_session
82
86
  @param_inferred_types = discovery_seed && discovery_seed[:param_inferred_types]
83
- @single = DiagnosticOracle.new(
84
- configuration: configuration, environment: environment, project_scan: project_scan,
85
- discovery_seed: discovery_seed
86
- )
87
+ adopt_session(environment, project_scan)
87
88
  end
88
89
 
89
90
  # The clean baselines a mutant must add a diagnostic to: the mutated file's (the shipped oracle's own,
@@ -91,21 +92,25 @@ module Rigor
91
92
  # never per mutant. The dependents' half is bound through the same buffer machinery a mutant is, so the
92
93
  # clean and mutant runs of the closure differ in exactly one input — the bytes.
93
94
  def baseline(source:, path:)
94
- Baseline.new(
95
- own: @single.baseline(source: source, path: path),
96
- dependents: dependents_signatures(source, path)
97
- )
95
+ recovering do
96
+ Baseline.new(
97
+ own: @single.baseline(source: source, path: path),
98
+ dependents: dependents_signatures(source, path)
99
+ )
100
+ end
98
101
  end
99
102
 
100
103
  # Killed iff the mutant introduces a diagnostic the baseline did not carry — in the mutated file (the
101
104
  # shipped verdict), or, failing that, in any dependent of it (what this feature adds).
102
105
  def killed?(mutant_source:, path:, baseline:)
103
- return true if @single.killed?(mutant_source: mutant_source, path: path, baseline: baseline.own)
106
+ recovering do
107
+ next true if @single.killed?(mutant_source: mutant_source, path: path, baseline: baseline.own)
104
108
 
105
- dependents = @dependents[path] || []
106
- return false if dependents.empty?
109
+ dependents = @dependents[path] || []
110
+ next false if dependents.empty?
107
111
 
108
- dependents_signatures(mutant_source, path).any? { |sig| !baseline.dependents.include?(sig) }
112
+ dependents_signatures(mutant_source, path).any? { |sig| !baseline.dependents.include?(sig) }
113
+ end
109
114
  end
110
115
 
111
116
  # The file set a kill is looked for in: the mutated file plus its measured dependents.
@@ -115,6 +120,45 @@ module Rigor
115
120
 
116
121
  private
117
122
 
123
+ # Issue #790 — a refused mutant is one mutant; a POISONED Environment is every mutant after it.
124
+ #
125
+ # The #784 seam records its failure on the Environment and {Environment::HktRegistryHolder} memoises
126
+ # the degraded registry, while {Environment::FailureSlot} is first-write-wins. So the one mutant that
127
+ # made the scan raise leaves an Environment that reports `rbs.coverage.hkt-scan-failed` on every later
128
+ # run and never re-attempts the scan — the row lands on BOTH sides of the next mutant's set
129
+ # difference, and every later mutant is measured over the degraded universe. Pre-#788 the raise
130
+ # propagated instead of being memoised, so the next demand retried the build and the poisoning did not
131
+ # exist; recovering it is this method.
132
+ #
133
+ # REBUILD rather than reset the slots. A reset would be cheaper, but it has to enumerate every holder
134
+ # the degraded registry fed — the memo itself, the failure slot, and the constant-type cache whose
135
+ # entries were folded under it — and a holder added to {Rigor::Environment} later would silently not
136
+ # be reset, with nothing to go red. The cost objection a reset answers is a rebuild PER MUTANT; this
137
+ # one is paid only after a defect is actually observed, which on a healthy sweep is never.
138
+ #
139
+ # With no `rebuild_session:` the oracle keeps the poisoned Environment and every later mutant is
140
+ # refused by the same guard. That is the deliberate fallback: a sweep that reports `harness_errors`
141
+ # says it could not measure, where the pre-#790 behaviour said "survived" about a universe it had
142
+ # silently degraded.
143
+ def recovering
144
+ yield
145
+ rescue AnalyzerCrashed => e
146
+ adopt_session(*@rebuild_session.call) if e.analyzer_defect? && @rebuild_session
147
+ raise
148
+ end
149
+
150
+ # The delegated single-file oracle is rebuilt with the pair, not handed the new Environment after the
151
+ # fact: it holds its own reference, and an oracle whose two halves disagree about which Environment
152
+ # they measure over is the same split-universe bug one layer down.
153
+ def adopt_session(environment, project_scan)
154
+ @environment = environment
155
+ @project_scan = project_scan
156
+ @single = DiagnosticOracle.new(
157
+ configuration: @configuration, environment: environment, project_scan: project_scan,
158
+ discovery_seed: @discovery_seed
159
+ )
160
+ end
161
+
118
162
  # The diagnostic signatures the dependents of `path` report while `source` stands in for it. Empty (and
119
163
  # analysis-free) when nothing depends on the file — 120 of Rigor's own 349 `lib` files.
120
164
  def dependents_signatures(source, path)
@@ -187,7 +231,7 @@ module Rigor
187
231
  configuration: @configuration, environment: @environment, prebuilt: @project_scan,
188
232
  cache_store: nil, collect_stats: false, buffer: buffer, discovery_seed: seed, analyze_only: paths
189
233
  ).run(paths),
190
- context: "ClosureKillOracle closure analysis of #{paths.join(', ')}"
234
+ context: "ClosureKillOracle closure analysis of #{paths.join(', ')}", environment: @environment
191
235
  )
192
236
  end
193
237
  end
@@ -24,14 +24,13 @@ module Rigor
24
24
  module DependencyClosure
25
25
  module_function
26
26
 
27
- # @param paths [Array<String>] the measured file set, in caller order.
28
- # @param configuration [Rigor::Configuration]
29
- # @param environment [Rigor::Environment] built once by the caller.
30
- # @param cache_store [Rigor::Cache::Store, nil] threaded to the recording run only (its RBS-env and
27
+ # @param paths the measured file set, in caller order.
28
+ # @param environment — built once by the caller.
29
+ # @param cache_store threaded to the recording run only (its RBS-env and
31
30
  # plugin-producer tiers); the per-mutant analyses stay `cache_store: nil` regardless.
32
- # @param workers [Integer] fork-pool workers for the recording pass (the pool records per worker and
31
+ # @param workers fork-pool workers for the recording pass (the pool records per worker and
33
32
  # marshals the records back, so a pooled graph equals the sequential one).
34
- # @return [Hash{String => Array<String>}] frozen `path => sorted dependents`, restricted to `paths`.
33
+ # @return frozen `path => sorted dependents`, restricted to `paths`.
35
34
  def build(paths:, configuration:, environment:, cache_store: nil, workers: 0)
36
35
  runner = Analysis::Runner.new(
37
36
  configuration: configuration, environment: environment, cache_store: cache_store,
@@ -45,8 +44,6 @@ module Rigor
45
44
  # not being measured, so re-analysing it would report a diagnostic against a file the run never
46
45
  # baselined. Self-edges are dropped (the mutated file is the closure's own head), and each list is
47
46
  # sorted so a `--threshold` gate reads the same number whatever order the recording pass finished in.
48
- #
49
- # @return [Hash{String => Array<String>}]
50
47
  def index(dependents, paths)
51
48
  measured = Set.new(paths)
52
49
  paths.to_h do |path|
@@ -17,7 +17,7 @@ module Rigor
17
17
  # disk write). Passing `prebuilt:` disables the run-result cache (whose key digests the *disk* file), so a
18
18
  # mutant is never served a stale clean hit.
19
19
  class DiagnosticOracle
20
- # @param discovery_seed [Hash, nil] issue #260 — the cross-file discovery tables (see {DiscoverySeed})
20
+ # @param discovery_seed issue #260 — the cross-file discovery tables (see {DiscoverySeed})
21
21
  # the per-mutant analysis is seeded with, threaded through to `Runner.new(discovery_seed:)`. Without
22
22
  # it the runner's `prebuilt:` path carries frozen-empty discovery tables, so a receiver whose class is
23
23
  # declared in a *sibling* file reads `Dynamic` and NO mutation at that site can produce a diagnostic —
@@ -53,7 +53,7 @@ module Rigor
53
53
  configuration: @configuration, environment: @environment, prebuilt: @project_scan,
54
54
  cache_store: nil, collect_stats: false, discovery_seed: @discovery_seed
55
55
  ).run_source(source: source, path: path),
56
- context: "DiagnosticOracle re-analysis of #{path}"
56
+ context: "DiagnosticOracle re-analysis of #{path}", environment: @environment
57
57
  )
58
58
  end
59
59
  end
@@ -30,12 +30,12 @@ module Rigor
30
30
  module DiscoverySeed
31
31
  module_function
32
32
 
33
- # @param paths [Array<String>] the measured file set; the seed spans these files only, exactly as Tier 1's
33
+ # @param paths the measured file set; the seed spans these files only, exactly as Tier 1's
34
34
  # seed does. A class declared outside them stays unknown.
35
- # @param environment [Rigor::Environment] the plugin-aware environment, built once by the caller.
36
- # @param target_ruby [String] Prism parse version for the parameter-inference pre-pass.
37
- # @param workers [Integer] worker count for that pre-pass (0 keeps it sequential).
38
- # @return [Hash{Symbol => Object}] frozen seed tables; empty when the paths yield nothing.
35
+ # @param environment the plugin-aware environment, built once by the caller.
36
+ # @param target_ruby Prism parse version for the parameter-inference pre-pass.
37
+ # @param workers worker count for that pre-pass (0 keeps it sequential).
38
+ # @return frozen seed tables; empty when the paths yield nothing.
39
39
  def build(paths:, environment:, target_ruby:, workers: 0)
40
40
  tables = discovery_tables(paths)
41
41
  params = Inference::ParameterInferenceCollector.collect(
@@ -51,8 +51,8 @@ module Rigor
51
51
  # builds. Built ONCE on the parent (before {CLI::MutationForkScan} forks, so children copy-on-write
52
52
  # inherit it), and cheap: ≈0.36s over Rigor's own 349-file `lib`.
53
53
  #
54
- # @param paths [Array<String>] the measured file set, in canonical (caller) order.
55
- # @return [Hash{String => Hash}] per-path bundles, the input {#tables_for_buffer} re-folds.
54
+ # @param paths the measured file set, in canonical (caller) order.
55
+ # @return per-path bundles, the input {#tables_for_buffer} re-folds.
56
56
  def bundles(paths:)
57
57
  Inference::ScopeIndexer.discovered_project_index_incremental(paths, seed_bundles: {}).fetch(:bundles)
58
58
  end
@@ -69,10 +69,10 @@ module Rigor
69
69
  #
70
70
  # ≈15ms over 349 files (one re-walk plus a whole-set fold), against ≈210ms for one mutant's analysis.
71
71
  #
72
- # @param paths [Array<String>] the measured file set, in the same order {#bundles} was built from.
73
- # @param bundles [Hash{String => Hash}] that bundle set.
74
- # @param buffer [Rigor::Analysis::BufferBinding] the mutant binding (logical path → mutant bytes).
75
- # @return [Hash{Symbol => Object}] frozen seed tables.
72
+ # @param paths the measured file set, in the same order {#bundles} was built from.
73
+ # @param bundles that bundle set.
74
+ # @param buffer the mutant binding (logical path → mutant bytes).
75
+ # @return frozen seed tables.
76
76
  def tables_for_buffer(paths:, bundles:, buffer:)
77
77
  index = Inference::ScopeIndexer.discovered_project_index_incremental(
78
78
  paths, seed_bundles: bundles, buffer: buffer
@@ -15,14 +15,12 @@ module Rigor
15
15
  module KillSignature
16
16
  module_function
17
17
 
18
- # @param diagnostic [Rigor::Analysis::Diagnostic]
19
- # @return [Array] the comparison key.
18
+ # @return the comparison key.
20
19
  def of(diagnostic)
21
20
  [diagnostic.rule, diagnostic.path, diagnostic.line, diagnostic.column, diagnostic.message]
22
21
  end
23
22
 
24
- # @param diagnostics [Enumerable<Rigor::Analysis::Diagnostic>]
25
- # @return [Set<Array>] the signature set of `diagnostics`.
23
+ # @return the signature set of `diagnostics`.
26
24
  def signatures_of(diagnostics)
27
25
  diagnostics.to_set { |diagnostic| of(diagnostic) }
28
26
  end
@@ -19,15 +19,13 @@ module Rigor
19
19
  module MeasurementIntegrity
20
20
  module_function
21
21
 
22
- # @return [Boolean] false only when nothing could be measured AND something failed trying.
22
+ # @return false only when nothing could be measured AND something failed trying.
23
23
  def measured?(total:, harness_errors:)
24
24
  !(total.zero? && harness_errors.positive?)
25
25
  end
26
26
 
27
27
  # The report-level companion: a project ratio computed over nothing, where something went wrong.
28
28
  # Distinct from an empty project, which is legitimately vacuous.
29
- #
30
- # @return [Boolean]
31
29
  def ratio_unmeasurable?(grand_total:, unmeasured_files:)
32
30
  grand_total.zero? && unmeasured_files.positive?
33
31
  end
@@ -89,17 +89,15 @@ module Rigor
89
89
  UNIDENTIFIED_ENGINE = "the engine's own source tree could not be digested"
90
90
 
91
91
  class << self
92
- # @param configuration [Rigor::Configuration]
93
- # @param roots [Array<String>] the analysis roots to look for a snapshot under, most-specific first
92
+ # @param roots — the analysis roots to look for a snapshot under, most-specific first
94
93
  # (the command's own path arguments, then the configured `paths:`).
95
- # @param project_scan [Rigor::Analysis::ProjectScan] the prepared whole-project scan.
96
- # @param sampling [Sampling]
97
- # @param feature_ids [Array<String>] the ADOPTED bleeding-edge ids that change this measurement.
98
- # @param seed_inputs [Array<String>, nil] the files the {DiscoverySeed} was built over when it is
94
+ # @param project_scan the prepared whole-project scan.
95
+ # @param feature_ids — the ADOPTED bleeding-edge ids that change this measurement.
96
+ # @param seed_inputs the files the {DiscoverySeed} was built over when it is
99
97
  # active, nil when it is not. The CLI stays the only place that knows a feature id exists.
100
- # @param bypass_reason [String, nil] a caller-side reason to run uncached (`--no-cache`, the closure
98
+ # @param bypass_reason a caller-side reason to run uncached (`--no-cache`, the closure
101
99
  # oracle). Reported verbatim.
102
- # @return [MutationCache] enabled, or a disabled instance carrying `#reason`.
100
+ # @return enabled, or a disabled instance carrying `#reason`.
103
101
  def build(configuration:, roots:, project_scan:, sampling:, feature_ids:, seed_inputs: nil,
104
102
  bypass_reason: nil)
105
103
  return disabled(bypass_reason) if bypass_reason
@@ -263,7 +261,6 @@ module Rigor
263
261
  end
264
262
 
265
263
  # The cached result for `path`, or nil on any miss (including "this cache is disabled").
266
- # @return [MutationScanner::FileResult, nil]
267
264
  def fetch(path)
268
265
  return nil unless enabled?
269
266
  # A path the snapshot never analysed has no recorded `deps[A]`, which means "depends on every project
@@ -87,25 +87,25 @@ module Rigor
87
87
  end
88
88
  end
89
89
 
90
- # @param configuration [Rigor::Configuration]
91
- # @param environment [Rigor::Environment] pre-built once by the caller
92
- # @param project_scan [Rigor::Analysis::ProjectScan] pre-built once
93
- # @param limit [Integer, nil] optional per-file mutation cap (sampled with
90
+ # @param environment — pre-built once by the caller
91
+ # @param project_scan pre-built once
92
+ # @param limit optional per-file mutation cap (sampled with
94
93
  # `seed`); nil analyses every type-relevant mutation (deterministic).
95
- # @param seed [Integer] RNG seed for the optional sample.
96
- # @param oracle [#baseline, #killed?, nil] the kill oracle (ADR-69 Seam 1);
97
- # defaults to the {DiagnosticOracle} (the ADR-62/63 behaviour).
98
- # @param site_selector [:biteable, :all] which sites to mutate (ADR-69
94
+ # @param seed RNG seed for the optional sample.
95
+ # @param oracle the kill oracle (ADR-69 Seam 1) — anything answering
96
+ # `baseline` and `killed?`; defaults to the {DiagnosticOracle} (the
97
+ # ADR-62/63 behaviour).
98
+ # @param site_selector — which sites to mutate (ADR-69
99
99
  # Seam 2). `:biteable` (default) keeps only concrete-type sites Rigor can
100
100
  # bite; `:all` also mutates Dynamic-receiver dispatch sites — use only
101
101
  # with a {TestSuiteOracle} (the fused overlay), never the diagnostic path.
102
- # @param base_scope [Rigor::Scope, nil] the scope site selection judges
102
+ # @param base_scope the scope site selection judges
103
103
  # anchor types against, built once by the caller (see
104
104
  # {Mutator#anchor_base_scope}). `nil` — the default — keeps the bare
105
105
  # single-file empty scope. A caller that seeds cross-file discovery
106
106
  # passes it here; this class stays free of {Rigor::Configuration} and of
107
107
  # bleeding-edge feature ids, which live one layer up in the CLI.
108
- # @param discovery_seed [Hash, nil] issue #260 — the SAME cross-file table
108
+ # @param discovery_seed issue #260 — the SAME cross-file table
109
109
  # set `base_scope` was built from, threaded to the default
110
110
  # {DiagnosticOracle} so an admitted cross-file site is one the oracle can
111
111
  # also kill at. Pass both or neither: a `base_scope` without it measures
@@ -127,9 +127,8 @@ module Rigor
127
127
  )
128
128
  end
129
129
 
130
- # @param path [String] the file to measure (used as the in-memory bind path)
131
- # @param source [String, nil] the file's source; read from disk when nil
132
- # @return [FileResult]
130
+ # @param path the file to measure (used as the in-memory bind path)
131
+ # @param source the file's source; read from disk when nil
133
132
  def scan_file(path, source: nil)
134
133
  source ||= File.read(path, encoding: Encoding::UTF_8)
135
134
  kept = kept_mutations(source, path)
@@ -156,8 +155,6 @@ module Rigor
156
155
  # mutant the type checker did **not** kill, asks `test_oracle` whether the project's test suite catches it.
157
156
  # The expensive suite run is paid only for type-survivors (the gradual short-circuit), so the cost is
158
157
  # proportional to the protection hole.
159
- # @param test_oracle [TestSuiteOracle]
160
- # @return [FusedFileResult]
161
158
  def scan_file_fused(path, test_oracle:, source: nil)
162
159
  source ||= File.read(path, encoding: Encoding::UTF_8)
163
160
  kept = kept_mutations(source, path)
@@ -127,6 +127,7 @@ module Rigor
127
127
  when Type::Constant then CONSTANT_CLASSES[type.value.class] || type.value.class.name
128
128
  when Type::Refined, Type::Difference then concrete_class_name(type.base)
129
129
  when Type::IntegerRange then "Integer"
130
+ when Type::FloatRange then "Float"
130
131
  when Type::App then concrete_class_name(type.bound)
131
132
  end
132
133
  end
@@ -171,7 +172,7 @@ module Rigor
171
172
  # only removes provably-Dynamic sites. Returns [kept, dropped_count]. Builds the scope index from THIS
172
173
  # mutator's parse so anchor node identity matches the keys.
173
174
  #
174
- # @param base_scope [Rigor::Scope, nil] a pre-seeded scope to judge anchors against (see
175
+ # @param base_scope a pre-seeded scope to judge anchors against (see
175
176
  # {#anchor_base_scope}); nil builds the bare empty scope, which is the shipped default.
176
177
  def filter_by_type(mutations, environment:, path:, base_scope: nil)
177
178
  base = anchor_base_scope(environment, path, base_scope)
@@ -16,8 +16,8 @@ module Rigor
16
16
  # standard mutation-testing hazard the `ensure` cannot cover; callers running this in CI accept that, as
17
17
  # `mutant` / Stryker do.)
18
18
  class TestSuiteOracle
19
- # @param command [Array<String>] the test command (the runner hook)
20
- # @param runner [#call, nil] `runner.call(command) -> true iff the suite
19
+ # @param command the test command (the runner hook)
20
+ # @param runner `runner.call(command) -> true iff the suite
21
21
  # passed`. Defaults to shelling out via `system`.
22
22
  def initialize(command:, runner: nil)
23
23
  @command = command
@@ -31,9 +31,9 @@ module Rigor
31
31
  end
32
32
 
33
33
  # Killed iff the mutant turns the suite red. Restores `original` afterward.
34
- # @param path [String] the file to (temporarily) overwrite with the mutant
35
- # @param original [String] the clean bytes to restore
36
- # @param mutant_source [String] the mutated bytes to test against
34
+ # @param path the file to (temporarily) overwrite with the mutant
35
+ # @param original the clean bytes to restore
36
+ # @param mutant_source the mutated bytes to test against
37
37
  def killed?(path:, original:, mutant_source:)
38
38
  File.write(path, mutant_source)
39
39
  !@runner.call(@command)
@@ -31,8 +31,9 @@ module Rigor
31
31
  # - `IncompatibleSignature` — a provided method's signature violates the interface contract (return widened,
32
32
  # or a parameter narrowed). Same rule, signature-specific message.
33
33
  # - `UnresolvedInterface` — the named interface is not loaded (a typo, or the defining library / `sig` set is
34
- # not on the RBS load path). Surfaces as `dynamic.rbs-extended.unresolved` `:info`, the fail-soft channel
35
- # the other directive parsers use, so a bad name never silently disables the author's assertion.
34
+ # not on the RBS load path). Surfaces as `dynamic.rbs-extended.unresolved` at `:warning` (#928): the
35
+ # other directive parsers fail soft to `:info`, but a name the author asserted against must not pass
36
+ # silently — the catalog ships, so an unresolved role is a typo or a missing `sig` set.
36
37
  #
37
38
  # Fail-soft throughout: a class whose own definition cannot be built (RBS error) is skipped rather than
38
39
  # reported.
@@ -60,7 +61,7 @@ module Rigor
60
61
  results
61
62
  end
62
63
 
63
- # @return [Array] zero or more records for one (class, interface) pair.
64
+ # @return zero or more records for one (class, interface) pair.
64
65
  def check_one(rbs_loader, class_name, interface_name, location)
65
66
  interface_def = resolve_interface(rbs_loader, class_name, interface_name)
66
67
  if interface_def.nil?
@@ -48,10 +48,9 @@ module Rigor
48
48
 
49
49
  module_function
50
50
 
51
- # @param sources [Array<Array(String, String)>] `[buffer name, RBS source]` pairs — the project's
51
+ # @param sources `[buffer name, RBS source]` pairs — the project's
52
52
  # `.rbs` files, plus the virtual entries rbs-inline and plugin `source_rbs` synthesis contribute.
53
- # @param registry [Rigor::Effects::Registry] the vocabulary an unknown label is judged against.
54
- # @return [Result]
53
+ # @param registry the vocabulary an unknown label is judged against.
55
54
  #
56
55
  # The `ANNOTATION_HINT` routing test runs here, before any parse: a source with no honoured
57
56
  # payload can contribute neither an envelope nor an unresolved report, so a signature tree with
@@ -94,11 +93,10 @@ module Rigor
94
93
  # class discovery knows", which is a project fact; a gem's class-level tag would have to
95
94
  # distribute over a definition set this reader does not have.
96
95
  #
97
- # @param loader [Rigor::Environment::RbsLoader] the run's loader; it owns the env walk
96
+ # @param loader the run's loader; it owns the env walk
98
97
  # ({Rigor::Environment::RbsLoader#each_annotated_method_member}), so the environment itself never
99
98
  # leaves it.
100
- # @param registry [Rigor::Effects::Registry]
101
- # @return [Hash{String => Rigor::Effects::Envelope}] keyed `Class#m` / `Class.m`
99
+ # @return keyed `Class#m` / `Class.m`
102
100
  def from_loader(loader:, registry:)
103
101
  out = {}
104
102
  loader.each_annotated_method_member do |class_name, member|
@@ -9,18 +9,23 @@ module Rigor
9
9
  # Owns three event streams:
10
10
  #
11
11
  # - `#unresolved_payloads` — `rigor:v1:*` directive payloads the resolver could not turn into a
12
- # {Rigor::Type}. Surface as `dynamic.rbs-extended.unresolved` `:info` diagnostics.
12
+ # {Rigor::Type}. Surface as `dynamic.rbs-extended.unresolved` `:info` diagnostics, except an
13
+ # unresolved `conforms-to` interface, which the aggregator stamps `:warning` (#928).
13
14
  # - `#lossy_projections` — shape-projection type functions (`pick_of` / `omit_of` / `partial_of` /
14
15
  # `required_of` / `readonly_of`) applied to a carrier that does not preserve shape information (anything
15
16
  # other than `Type::HashShape` / `Type::Tuple`). Surface as `dynamic.shape.lossy-projection` `:info`
16
17
  # diagnostics.
17
18
  # - `#hkt_directive_errors` — malformed `rigor:v1:hkt_register` / `rigor:v1:hkt_define` directives the
18
19
  # ADR-20 parser declined. Surface as `dynamic.rbs-extended.hkt-directive-invalid` `:info` diagnostics.
20
+ # - `#deprecated_forms` — payload spellings the grammar still accepts for one deprecation window but no
21
+ # longer displays (ADR-109 WD3: the PHPStan-style `int<a, b>`). Surface as
22
+ # `dynamic.rbs-extended.deprecated-form` `:info` diagnostics naming the replacement.
19
23
  #
20
24
  # Mutable through the run; consumed once by {Rigor::Analysis::Runner} at end-of-run. Each event is
21
25
  # deduplicated by its whole entry — `(payload, path, line, column)` for unresolved, `(head, path, line,
22
- # column)` for lossy-projection, `(message, path, line, column)` for an hkt directive — so a single
23
- # annotation read from many call sites yields one diagnostic.
26
+ # column)` for lossy-projection, `(message, path, line, column)` for an hkt directive, `(payload,
27
+ # replacement, path, line, column)` for a deprecated form — so a single annotation read from many call
28
+ # sites yields one diagnostic.
24
29
  #
25
30
  # The reporter is intentionally thread-safe via a coarse `Mutex` because the inference engine may read the
26
31
  # same method definition from multiple files in parallel; the critical sections are short (Array#include? +
@@ -41,6 +46,7 @@ module Rigor
41
46
  UnresolvedEntry = Data.define(:payload, :path, :line, :column)
42
47
  LossyProjectionEntry = Data.define(:head, :path, :line, :column)
43
48
  HktDirectiveEntry = Data.define(:message, :path, :line, :column)
49
+ DeprecatedFormEntry = Data.define(:payload, :replacement, :path, :line, :column)
44
50
 
45
51
  # Flattens an `RBS::Location` (or anything answering the same readers) to the `(path, line, column)`
46
52
  # triple every entry carries. `column` is 1-based, since `RBS::Location#start_column` is 0-based and
@@ -49,9 +55,6 @@ module Rigor
49
55
  #
50
56
  # Fail-soft by construction, like every parser that calls it: a location that raises while being read
51
57
  # costs the entry its position, never the run.
52
- #
53
- # @param source_location [RBS::Location, nil]
54
- # @return [Array(String, nil, Integer, nil, Integer, nil)]
55
58
  def self.position_of(source_location)
56
59
  return [nil, nil, nil] if source_location.nil?
57
60
 
@@ -68,15 +71,16 @@ module Rigor
68
71
  @unresolved_payloads = []
69
72
  @lossy_projections = []
70
73
  @hkt_directive_errors = []
74
+ @deprecated_forms = []
71
75
  @mutex = Mutex.new
72
76
  end
73
77
 
74
- # @return [Array<UnresolvedEntry>] frozen snapshot of the accumulated unresolved-payload events.
78
+ # @return frozen snapshot of the accumulated unresolved-payload events.
75
79
  def unresolved_payloads
76
80
  @mutex.synchronize { @unresolved_payloads.dup.freeze }
77
81
  end
78
82
 
79
- # @return [Array<LossyProjectionEntry>] frozen snapshot of the accumulated lossy-projection events.
83
+ # @return frozen snapshot of the accumulated lossy-projection events.
80
84
  def lossy_projections
81
85
  @mutex.synchronize { @lossy_projections.dup.freeze }
82
86
  end
@@ -109,7 +113,7 @@ module Rigor
109
113
  end
110
114
  end
111
115
 
112
- # @return [Array<HktDirectiveEntry>] frozen snapshot of the accumulated hkt-directive failures.
116
+ # @return frozen snapshot of the accumulated hkt-directive failures.
113
117
  def hkt_directive_errors
114
118
  @mutex.synchronize { @hkt_directive_errors.dup.freeze }
115
119
  end
@@ -132,11 +136,33 @@ module Rigor
132
136
  end
133
137
  end
134
138
 
139
+ # @return frozen snapshot of the accumulated deprecated-form events.
140
+ def deprecated_forms
141
+ @mutex.synchronize { @deprecated_forms.dup.freeze }
142
+ end
143
+
144
+ # Records a `dynamic.rbs-extended.deprecated-form` event (ADR-109 WD3): `payload` is the spelling the
145
+ # author wrote (`int<5, 10>`), `replacement` the spelling the same type displays as (`Integer[5..10]`),
146
+ # so the row can say exactly what to write instead. The position triple is read exactly as
147
+ # {#record_unresolved}'s is, and every String is frozen for the pool drain.
148
+ def record_deprecated_form(payload:, replacement:, path: nil, line: nil, column: nil)
149
+ entry = DeprecatedFormEntry.new(
150
+ payload: frozen_text(payload), replacement: frozen_text(replacement),
151
+ path: frozen_text(path), line: line, column: column
152
+ )
153
+ @mutex.synchronize do
154
+ return if @deprecated_forms.include?(entry)
155
+
156
+ @deprecated_forms << entry
157
+ end
158
+ end
159
+
135
160
  # True when no events have accumulated. Used by callers that want to skip the diagnostic-emission pass
136
161
  # entirely on the common no-event path.
137
162
  def empty?
138
163
  @mutex.synchronize do
139
- @unresolved_payloads.empty? && @lossy_projections.empty? && @hkt_directive_errors.empty?
164
+ @unresolved_payloads.empty? && @lossy_projections.empty? && @hkt_directive_errors.empty? &&
165
+ @deprecated_forms.empty?
140
166
  end
141
167
  end
142
168