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
@@ -50,11 +50,18 @@ module Rigor
50
50
  # passage is the precedent for stopping at one hop rather than building a provenance channel through
51
51
  # the return memo.
52
52
  #
53
- # Deliberately NOT covered, and a separate change: a value copied into a local (`m = MODE; m == :x`),
54
- # into an ivar (`@mode = MODE` in `initialize`), or through a same-file constant alias
55
- # (`MODE2 = AppConfig::MODE`). Each needs flow provenance ADR-58's `r = @x` stamping is the shape —
56
- # rather than a syntactic root walk, and approximating it here is exactly the ad-hoc widening ADR-58's
57
- # *Non-transitivity* passage forbids. Two hops (`live? -> prod? -> MODE`) also stay uncovered.
53
+ # - a value COPIED out of such a constant before the predicate reads it (issue #667) into a local
54
+ # (`m = AppConfig::MODE; m == :x`), into an ivar (`@mode = AppConfig::MODE` in `initialize`), or
55
+ # through a same-file alias (`MODE2 = AppConfig::MODE`). A copy has no spelling for the root walk to
56
+ # recognise, so the provenance is stamped on the value where the copy happens and travels with it:
57
+ # the first two on the `Scope` flow carrier `published_constant_sourced` (dropped by any rebinding,
58
+ # unioned at a join), the alias on the per-file `published_constant_alias_names` census table, which
59
+ # needs no flow machinery at all because a constant write is not flow. This walk only READS those
60
+ # marks — the widening ADR-58's *Non-transitivity* passage forbids is inferring one AT a consumer,
61
+ # and the establishing transitions are named in
62
+ # `docs/internal-spec/inference-engine.md` § "The truthiness withholding".
63
+ #
64
+ # Deliberately NOT covered: two interprocedural hops (`live? -> prod? -> MODE`).
58
65
  module PublishedConstantGuard
59
66
  # A defensive depth cap against a pathological chain (the walk is otherwise linear in chain length).
60
67
  MAX_DEPTH = 64
@@ -66,13 +73,15 @@ module Rigor
66
73
 
67
74
  module_function
68
75
 
69
- # @return [Boolean] true when the rule MUST withhold its firing.
76
+ # @return true when the rule MUST withhold its firing.
70
77
  def rooted?(node, scope, depth = 0)
71
78
  return false if node.nil? || depth > MAX_DEPTH || scope.nil?
72
79
 
73
80
  case node
74
81
  when Prism::ConstantReadNode then scope.published_constant?(node.name.to_s)
75
82
  when Prism::ConstantPathNode then published_path?(node, scope)
83
+ when Prism::LocalVariableReadNode then scope.published_constant_sourced?(:local, node.name)
84
+ when Prism::InstanceVariableReadNode then scope.published_constant_sourced?(:ivar, node.name)
76
85
  when Prism::CallNode then rooted_call?(node, scope, depth)
77
86
  else rooted_through_composition?(node, scope, depth)
78
87
  end
@@ -137,7 +137,7 @@ module Rigor
137
137
  # first segment appears here as KNOWN and stays silent: these ids are legitimate suppression /
138
138
  # `severity_overrides:` vocabulary the light rule-id table cannot enumerate (plugins load dynamically;
139
139
  # aggregator ids live in the engine-heavy runner), so under-warning is the FP-safe direction.
140
- NON_CHECK_DIAGNOSTIC_FAMILIES = %w[rbs_extended dynamic rbs pre-eval plugin].freeze
140
+ NON_CHECK_DIAGNOSTIC_FAMILIES = %w[rbs_extended dynamic rbs pre-eval plugin plugin_trust].freeze
141
141
 
142
142
  # Bare (dot-less) diagnostic ids the engine emits outside the catalogue (see the `rule:` literals in
143
143
  # `Analysis::Runner` / `Runner::DiagnosticAggregator`). A token equal to one of these is treated as
@@ -40,11 +40,26 @@ module Rigor
40
40
 
41
41
  CLASS_OR_MODULE_NODE_CLASSES = [Prism::ClassNode, Prism::ModuleNode].freeze
42
42
 
43
+ # Issue #909 — the receivers whose block form opens a class body that is NOT the lexically enclosing
44
+ # class: `Class.new do … end`, `Module.new`, `Struct.new`, `Data.define`. A `def` in such a block
45
+ # belongs to the anonymous class the call returns, so its ivar writes are a different store from the
46
+ # enclosing class's.
47
+ DETACHED_CLASS_FACTORIES = {
48
+ Class: %i[new],
49
+ Module: %i[new],
50
+ Struct: %i[new],
51
+ Data: %i[define]
52
+ }.freeze
53
+ private_constant :DETACHED_CLASS_FACTORIES
54
+
55
+ EMPTY_METHODS = [].freeze
56
+ private_constant :EMPTY_METHODS
57
+
43
58
  # The immutable per-node descent context. Recomputed for each child from its parent's context as the
44
59
  # walk descends; collectors read only the fields they declared a need for.
45
- Context = Data.define(:in_loop_or_block, :qualified_prefix, :inside_def) do
60
+ Context = Data.define(:in_loop_or_block, :qualified_prefix, :inside_def, :detached_ivar_facet) do
46
61
  def self.root
47
- new(in_loop_or_block: false, qualified_prefix: [], inside_def: false)
62
+ new(in_loop_or_block: false, qualified_prefix: [], inside_def: false, detached_ivar_facet: false)
48
63
  end
49
64
  end
50
65
 
@@ -58,9 +73,13 @@ module Rigor
58
73
  # - `:inside_def` (`inside_def`) — lexically inside a `DefNode`; IvarWrite collects only at a `def`
59
74
  # that sits directly in a class / module body (its legacy walk `return`s at the first `def` it
60
75
  # meets, so a nested def is never reached).
76
+ # - `:detached_ivar_facet` (`detached_ivar_facet`) — under a `class << self` body or an
77
+ # anonymous-class factory block, where the enclosing class name does not name the ivar store the
78
+ # body writes to.
61
79
  GATE_CONTEXT_PREDICATES = {
62
80
  loop_or_block: :in_loop_or_block,
63
- inside_def: :inside_def
81
+ inside_def: :inside_def,
82
+ detached_ivar_facet: :detached_ivar_facet
64
83
  }.freeze
65
84
 
66
85
  # A per-node driver over a fixed collector set: holds the compiled `node_class => [[collector,
@@ -124,14 +143,30 @@ module Rigor
124
143
  LOOP_OR_BLOCK_NODE_CLASSES.any? { |klass| node.is_a?(klass) }
125
144
  inside_def = context.inside_def || node.is_a?(Prism::DefNode)
126
145
  qualified_prefix = extend_prefix(node, context.qualified_prefix)
146
+ detached_ivar_facet = context.detached_ivar_facet || detached_ivar_facet?(node)
127
147
 
128
148
  Context.new(
129
149
  in_loop_or_block: in_loop_or_block,
130
150
  qualified_prefix: qualified_prefix,
131
- inside_def: inside_def
151
+ inside_def: inside_def,
152
+ detached_ivar_facet: detached_ivar_facet
132
153
  )
133
154
  end
134
155
 
156
+ # True for a node whose descendants write ivars to a store the enclosing class name does not name:
157
+ # a `class << self` body (the class object's own ivars) or a call that builds an anonymous class
158
+ # from a block. Every singleton-class body counts, `class << Konstant` included: whichever object's
159
+ # singleton it opens, it is not the instance facet the enclosing prefix names.
160
+ def detached_ivar_facet?(node)
161
+ return true if node.is_a?(Prism::SingletonClassNode)
162
+ return false unless node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode)
163
+
164
+ receiver = node.receiver
165
+ return false unless receiver.is_a?(Prism::ConstantReadNode)
166
+
167
+ DETACHED_CLASS_FACTORIES.fetch(receiver.name, EMPTY_METHODS).include?(node.name)
168
+ end
169
+
135
170
  private
136
171
 
137
172
  def collector_gates(collector)
@@ -29,7 +29,7 @@ module Rigor
29
29
  @root = root
30
30
  end
31
31
 
32
- # @return [Set<String>] qualified names to exclude from the rule.
32
+ # @return qualified names to exclude from the rule.
33
33
  def open_class_names
34
34
  names = Set.new
35
35
  walk(@root, [], names)
@@ -58,7 +58,7 @@ module Rigor
58
58
  # Legacy single-collector walk — the oracle the shadow harness (`RIGOR_SHADOW_RULE_WALK=1`) compares
59
59
  # the shared {RuleWalk} traversal against. Tracks the lexical class / module prefix itself, exactly
60
60
  # as {RuleWalk} threads `qualified_prefix`.
61
- # @return [Array<Result>] one entry per provably-shadowed rescue clause.
61
+ # @return one entry per provably-shadowed rescue clause.
62
62
  def collect(root)
63
63
  walk(root, [])
64
64
  @results.freeze
@@ -42,7 +42,7 @@ module Rigor
42
42
  DEFENSIVE_EXIT_CALLS = %i[raise fail throw abort exit].freeze
43
43
 
44
44
  # @!attribute kind
45
- # @return [Symbol] `:disjoint` (this clause's type is disjoint
45
+ # @return `:disjoint` (this clause's type is disjoint
46
46
  # from the still-possible subject), `:prior_exhaustion` (an
47
47
  # earlier clause already covered the subject), or
48
48
  # `:exhausted_else` (every subject value is covered, so the
@@ -63,7 +63,7 @@ module Rigor
63
63
 
64
64
  # Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
65
65
  # {RuleWalk} against; deleted when Track B completes.
66
- # @return [Array<Result>] one entry per provably-dead `when` clause.
66
+ # @return one entry per provably-dead `when` clause.
67
67
  def collect(root)
68
68
  walk(root, in_loop_or_block: false)
69
69
  @results.freeze
@@ -52,7 +52,6 @@ module Rigor
52
52
  # Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
53
53
  # {RuleWalk} against. Walks the whole subtree once, checking each consumer node's value slots.
54
54
  # Returns one {Result} per value-context use of a recovered-`void` call.
55
- # @return [Array<Result>]
56
55
  def collect(root)
57
56
  walk(root)
58
57
  @results.freeze
@@ -83,11 +83,8 @@ module Rigor
83
83
  # have already produced `scope_index` through
84
84
  # `Rigor::Inference::ScopeIndexer.index(root, default_scope:)`.
85
85
  #
86
- # @param path [String] used to populate
86
+ # @param path used to populate
87
87
  # `Diagnostic#path`; the rule does not open files.
88
- # @param root [Prism::Node]
89
- # @param scope_index [Hash{Prism::Node => Rigor::Scope}]
90
- # @return [Array<Rigor::Analysis::Diagnostic>]
91
88
  #
92
89
  # ADR-53 B4 — when `node_collectors` is supplied, the converged
93
90
  # {Plugin::NodeRuleWalk} traversal has already populated the built-in
@@ -490,7 +487,7 @@ module Rigor
490
487
  /\A#\s*rigor:(?<marker>disable-(?!file(?![\w-]))[\w-]+|enable(?:-[\w-]+)?)(?![\w-])(?<rest>.*)/
491
488
  private_constant :UNKNOWN_SUPPRESSION_MARKER
492
489
 
493
- # @return [Array<(Hash{Integer => Set}, Set)>] pair of
490
+ # @return pair of
494
491
  # `(line_suppressions, file_suppressions)`. Line
495
492
  # suppressions are keyed by source line number; file
496
493
  # suppressions apply to every line.
@@ -717,7 +714,7 @@ module Rigor
717
714
  # because the real type is empty (the real `DRb` has
718
715
  # `start_service`), so enumerating it to prove a call
719
716
  # "undefined" would be a false positive.
720
- return nil if unenumerable_receiver?(class_name, scope)
717
+ return nil if unenumerable_receiver?(receiver_type, class_name, scope)
721
718
 
722
719
  # Slice 7 phase 12 — suppress when the user has
723
720
  # declared the method in source (`def` /
@@ -976,10 +973,32 @@ module Rigor
976
973
  # the scalar rule enumerated `Module`'s own RBS instead and reported them.
977
974
  #
978
975
  # Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has eight
979
- # other callers (arity, the `raise` verdicts), and widening all of them is a separate change with
980
- # its own evidence to gather.
981
- def unenumerable_receiver?(class_name, scope)
982
- METACLASS_ARMS.include?(class_name) || unbounded_receiver_surface?(class_name, scope)
976
+ # other callers, and #742 had no evidence that any of them spoke on these receivers.
977
+ #
978
+ # Issue #879 supplied it for exactly one — `call.wrong-arity`, which decided `v.hexdigest(1, 2, 3)`
979
+ # against `Digest::Instance`'s declaration on the same run where `v.no_such_method_zzz` was declined
980
+ # as unenumerable. One receiver, one run, two answers about whether that surface can be read. The
981
+ # reporter could make no other caller speak: `raise` on a module- or `Class`-typed operand is silent,
982
+ # and `Class`-typed `.new` with a wrong arity is silent too, so the other seven stay on
983
+ # `unbounded_receiver_surface?` until something demonstrates otherwise.
984
+ #
985
+ # Arity takes {#unenumerable_receiver_shape?}, not this whole predicate: ADR-26's open receivers are
986
+ # handled for the signature-reading rules by {#unauthoritative_inherited_signature?}, which is
987
+ # deliberately narrower — a method the open class DECLARES itself keeps its arity check.
988
+ def unenumerable_receiver?(receiver_type, class_name, scope)
989
+ unenumerable_receiver_shape?(receiver_type, class_name, scope) ||
990
+ unbounded_receiver_surface?(class_name, scope)
991
+ end
992
+
993
+ # The receiver shapes whose method surface cannot be enumerated at all, whatever the environment
994
+ # declares: a generic metaclass value, and an instance-side mixin-module type — an instance of some
995
+ # unknown class that includes the module, which may declare the name with any arity of its own
996
+ # (#739's reasoning, applied to the signature rather than to the method's existence).
997
+ #
998
+ # Instance-side only, for #739's reason: `Singleton[M]` on a namespace module is a real, enumerable
999
+ # surface, and `M.helper(1, 2, 3)` must keep firing.
1000
+ def unenumerable_receiver_shape?(receiver_type, class_name, scope)
1001
+ METACLASS_ARMS.include?(class_name) || module_mixin_receiver?(receiver_type, scope)
983
1002
  end
984
1003
 
985
1004
  # Issue #746 — a class that includes a module the environment does not know. `SudoMode::Form`
@@ -1030,7 +1049,7 @@ module Rigor
1030
1049
  # about it instead of bailing. `Type::Refined` carries string-family
1031
1050
  # refinements (`lowercase-string`, …) over an explicit `.base`;
1032
1051
  # `Type::IntegerRange` carries the bounded-int refinements
1033
- # (`non-negative-int`, `positive-int`, `int<1,5>`), every one an
1052
+ # (`non-negative-int`, `positive-int`, `Integer[1..5]`), every one an
1034
1053
  # Integer; `Type::Difference` (`A - B`) carries the non-empty /
1035
1054
  # non-zero refinements (`non-empty-string` = `String - ""`,
1036
1055
  # `non-empty-array` = `Array - []`, `non-zero-int` = `Integer - 0`)
@@ -1038,6 +1057,7 @@ module Rigor
1038
1057
  # base (minuend) class dispatches.
1039
1058
  when Type::Refined, Type::Difference then concrete_class_name(type.base)
1040
1059
  when Type::IntegerRange then "Integer"
1060
+ when Type::FloatRange then "Float"
1041
1061
  end
1042
1062
  end
1043
1063
 
@@ -1097,7 +1117,11 @@ module Rigor
1097
1117
  # audit), so declaring even its reader surface makes it RBS-known — and, without this, every member
1098
1118
  # the declaration omits would become a false `call.undefined-method` for every project that locks
1099
1119
  # activesupport, the overwhelming majority of which never opt into the plugin at all.
1100
- GEM_OVERLAY_OPEN_RECEIVERS = Set["ActiveSupport::Duration"].freeze
1120
+ # `ActiveSupport::Concern` joined it in #534 item 7. `class_methods do … end` synthesizes a
1121
+ # `ClassMethods` constant and `append_features` / `prepend_features` are part of the real surface,
1122
+ # none of which the two-line declaration enumerates — and a concern module is the receiver of that
1123
+ # surface in every Rails app, so the same argument applies with the same force.
1124
+ GEM_OVERLAY_OPEN_RECEIVERS = Set["ActiveSupport::Duration", "ActiveSupport::Concern"].freeze
1101
1125
  private_constant :GEM_OVERLAY_OPEN_RECEIVERS
1102
1126
 
1103
1127
  # ADR-26 — whether `class_name` is declared "open" by a
@@ -1441,6 +1465,12 @@ module Rigor
1441
1465
  class_name = concrete_class_name(receiver_type)
1442
1466
  return nil if class_name.nil?
1443
1467
 
1468
+ # Issue #879 — the receiver whose surface `call.undefined-method` already refuses to enumerate.
1469
+ # Deciding an arity against a declaration this rule cannot prove the receiver actually runs is the
1470
+ # same unsoundness in signature form: the includer may declare the name with a wider arity, and
1471
+ # then the call is correct code.
1472
+ return nil if unenumerable_receiver_shape?(receiver_type, class_name, scope)
1473
+
1444
1474
  kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
1445
1475
  # `Struct.new(:a, :b).new(...)` chained: the inner
1446
1476
  # `Struct.new(...)` is an anonymous Struct *subclass* whose
@@ -1458,6 +1488,7 @@ module Rigor
1458
1488
 
1459
1489
  method_def = trustworthy_signature(receiver_type, class_name, call_node, scope)
1460
1490
  return nil if method_def.nil?
1491
+ return nil if undeclared_constructor?(class_name, call_node, kind, method_def)
1461
1492
 
1462
1493
  arity_envelope = compute_arity_envelope(method_def)
1463
1494
  return nil if arity_envelope.nil?
@@ -1469,6 +1500,25 @@ module Rigor
1469
1500
  build_arity_diagnostic(path, call_node, class_name, min, max, actual)
1470
1501
  end
1471
1502
 
1503
+ # Issue #917 — no loaded RBS declares a constructor for this class. The definition builder
1504
+ # synthesizes `.new` from the nearest `initialize` in the ancestry, and when nothing declares
1505
+ # one that is `BasicObject#initialize: () -> void`, so `.new` derives a nullary envelope that
1506
+ # is an artifact of the fallback rather than a statement about the class. `Gem::Specification`
1507
+ # is declared in full by the vendored rubygems sigs except for its constructor, and every
1508
+ # argument at the real one was reported. The rule needs "this class declares a nullary
1509
+ # constructor", and only a `defined_in` naming a class other than `BasicObject` is that
1510
+ # evidence — except on `BasicObject` and `Object` themselves, where the declaration is their
1511
+ # own and `.new` really does take no arguments.
1512
+ def undeclared_constructor?(class_name, call_node, kind, method_def)
1513
+ return false unless kind == :singleton && call_node.name == :new
1514
+ return false unless method_def.respond_to?(:defined_in)
1515
+
1516
+ defined_in = method_def.defined_in
1517
+ return false unless defined_in.nil? || strip_root(defined_in.to_s) == "BasicObject"
1518
+
1519
+ !%w[BasicObject Object].include?(strip_root(class_name))
1520
+ end
1521
+
1472
1522
  # True for the outer `.new` of a chained `Struct.new(...).new`:
1473
1523
  # `class_name`/`kind` already pin the receiver to
1474
1524
  # `Singleton[Struct]`, and the receiver node is itself a
@@ -2385,8 +2435,10 @@ module Rigor
2385
2435
  # overload requires the dispatcher's full
2386
2436
  # acceptance plumbing.
2387
2437
  # - The selected overload must have NO
2388
- # rest_positionals, NO required keywords, NO
2389
- # trailing positionals.
2438
+ # rest_positionals and NO trailing positionals
2439
+ # the two shapes under which argument N no longer
2440
+ # binds to positional parameter N. Keyword
2441
+ # parameters do not disqualify it (#673).
2390
2442
  # - The call must use plain positional arguments
2391
2443
  # (no splat / kw / block-pass / forwarded).
2392
2444
  # - Per-argument: skip when EITHER side is `Dynamic`
@@ -2478,13 +2530,46 @@ module Rigor
2478
2530
  # (a single-concrete-class argument every overload rejects). See
2479
2531
  # {#multi_overload_argument_mismatch}.
2480
2532
  def argument_mismatch(method_types, call_node, scope, param_overrides)
2481
- if method_types.size == 1
2482
- first_argument_mismatch(method_types.first, call_node, scope, param_overrides)
2483
- else
2484
- multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
2533
+ mismatch =
2534
+ if method_types.size == 1
2535
+ first_argument_mismatch(method_types.first, call_node, scope, param_overrides)
2536
+ else
2537
+ multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
2538
+ end
2539
+ return nil if mismatch && keyword_bearing?(method_types) && nil_tainted?(mismatch[:actual])
2540
+
2541
+ mismatch
2542
+ end
2543
+
2544
+ # Issue #673 widened {#argument_check_eligible?} to admit keyword-bearing signatures, whose
2545
+ # POSITIONAL arguments had gone unchecked. The widening is staged, and this is the half held
2546
+ # back: on such a signature only an argument refuted on its CLASS fires; one carrying `nil`
2547
+ # does not.
2548
+ #
2549
+ # Measured, not assumed. Across redmine, mastodon, tdiary-core, faraday, liquid and
2550
+ # rubocop-ast the widening produced exactly two new verdicts, both on correct code and both
2551
+ # resting on nullability: `Addrinfo.foreach(host, nil, nil, :STREAM)` (redmine — a nil service
2552
+ # is legal at runtime; rbs declares the parameter `String | ::Integer`) and
2553
+ # `Nokogiri::HTML5(html)` guarded by `return if html.nil?` one line above (mastodon — the guard
2554
+ # is on a method call, so the type stays `Dynamic[top]?`). Zero class-refuted new verdicts were
2555
+ # false. Nullability is the property a hand-written signature most often over-declares, so it
2556
+ # stays behind the old envelope until it has evidence of its own.
2557
+ #
2558
+ # Signatures WITHOUT keywords are untouched: `"a" + nil` fires exactly as before.
2559
+ def keyword_bearing?(method_types)
2560
+ method_types.any? do |method_type|
2561
+ function = method_type.type
2562
+ next false unless function.respond_to?(:required_keywords)
2563
+
2564
+ !function.required_keywords.empty? || !function.optional_keywords.empty? ||
2565
+ !function.rest_keywords.nil?
2485
2566
  end
2486
2567
  end
2487
2568
 
2569
+ def nil_tainted?(actual)
2570
+ nil_member?(actual) || (actual.is_a?(Type::Union) && union_contains_nil?(actual))
2571
+ end
2572
+
2488
2573
  # Multi-overload argument-type-mismatch. The dispatcher's per-overload
2489
2574
  # acceptance plumbing is not run here; instead the FP-safe shape mirrors
2490
2575
  # the "absent on every arm" union-undefined-method rule: an argument is
@@ -2509,8 +2594,8 @@ module Rigor
2509
2594
  arguments = call_node.arguments&.arguments || []
2510
2595
  arguments.each_with_index do |arg, index|
2511
2596
  arg_type = scope.type_of(arg)
2512
- params = overload_positional_params(method_types, index)
2513
- next if params.nil? # arity divergence — some overload lacks a param here
2597
+ params = checkable_overload_params(method_types, index, param_overrides, scope)
2598
+ next if params.nil?
2514
2599
 
2515
2600
  mismatch =
2516
2601
  if nil_member?(arg_type) # pure nil only — not a `T | nil` union
@@ -2523,6 +2608,19 @@ module Rigor
2523
2608
  nil
2524
2609
  end
2525
2610
 
2611
+ # The parameter set at `index` that a verdict may rest on, or nil when there is none — the two
2612
+ # reasons being arity divergence (an overload with no param there, which is `call.wrong-arity`'s
2613
+ # concern) and an overload whose param names a class carrying no definition ({#stub_typed_param?},
2614
+ # issue #661). Both channels below fire only when EVERY overload rejects, so either one is enough
2615
+ # to unseat that premise.
2616
+ def checkable_overload_params(method_types, index, param_overrides, scope)
2617
+ params = overload_positional_params(method_types, index)
2618
+ return nil if params.nil?
2619
+ return nil if params.any? { |param| stub_typed_param?(param, param_overrides, scope) }
2620
+
2621
+ params
2622
+ end
2623
+
2526
2624
  # The nil channel: a pure `nil` argument no overload admits (ADR-58
2527
2625
  # parity excuses a declaration-sourced ivar nil).
2528
2626
  def nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
@@ -2568,6 +2666,53 @@ module Rigor
2568
2666
  param.map_type { |type| type.sub(substitution) }
2569
2667
  end
2570
2668
 
2669
+ # ADR-26 WD8's PARAMETER-side twin (issue #661). `sig/app.rbs` declaring
2670
+ # `(ActiveSupport::TimeWithZone) -> void` in a project that never brings ActiveSupport's RBS in
2671
+ # names a class that carries no definition anywhere in the environment. Rigor still refuted
2672
+ # `App.take_twz(Time.now)` against it — `expected ActiveSupport::TimeWithZone, got Time` on a call
2673
+ # that works — because the acceptance walk compares nominals and `Time`'s ancestry does not list a
2674
+ # name nothing declares.
2675
+ #
2676
+ # WD8 already declines the two signature rules on such a RECEIVER, on the ground that there is
2677
+ # nothing authoritative to check the call against. It is the same one position over: with no
2678
+ # ancestry there is no fact that could make an argument satisfy the parameter either, so the
2679
+ # rejection is the missing signature restated as an error rather than a finding about the program.
2680
+ # Under AGENTS.md's false-positives-outrank-worst-case rule the honest answer is to decline, and
2681
+ # the report it costs is one the analyzer never had evidence for.
2682
+ #
2683
+ # Both shapes the missing name arrives in are covered, deliberately as ONE criterion. A name an
2684
+ # INSTANCE-method signature references is minted as an empty stub so the class builds
2685
+ # ({Environment::RbsLoader.stub_missing_referenced_types}) and reads back as RBS-known; the same
2686
+ # name in a SINGLETON signature is not, because that pass mirrors rbs's own membership test and
2687
+ # `validate_type_params` never reaches singleton members. That asymmetry is an artifact of which
2688
+ # pass ran, not of what is knowable, and the issue's own repro is the singleton half.
2689
+ #
2690
+ # A `rigor:v1:param` override is a real Rigor type the author wrote against the call site's own
2691
+ # method, so it is consulted first and checked normally.
2692
+ def stub_typed_param?(param, param_overrides, scope)
2693
+ return false if param_overrides[param.name]
2694
+
2695
+ undefined_param_class?(param.type, scope)
2696
+ end
2697
+
2698
+ # Only the arms a verdict can rest on directly: a union member, an optional's inner type, an
2699
+ # expanded alias, and the class instance / singleton itself. Deliberately NOT recursive into type
2700
+ # ARGUMENTS — `Array[Missing]` is still refuted by an `Integer` argument on the `Array` alone, and
2701
+ # declining there would be a lost report bought for nothing.
2702
+ def undefined_param_class?(rbs_type, scope)
2703
+ case rbs_type
2704
+ when RBS::Types::Union then rbs_type.types.any? { |member| undefined_param_class?(member, scope) }
2705
+ when RBS::Types::Optional then undefined_param_class?(rbs_type.type, scope)
2706
+ when RBS::Types::Alias
2707
+ expanded = scope.environment&.rbs_loader&.expand_type_alias(rbs_type)
2708
+ !expanded.nil? && undefined_param_class?(expanded, scope)
2709
+ when RBS::Types::ClassInstance, RBS::Types::ClassSingleton
2710
+ name = rbs_type.name
2711
+ synthesized_stub_receiver?(name, scope) || !Rigor::Reflection.rbs_class_known?(name.to_s, scope: scope)
2712
+ else false
2713
+ end
2714
+ end
2715
+
2571
2716
  # The class names whose instances `nil` IS — `NilClass` and every
2572
2717
  # ancestor. A parameter typed as any other class instance rejects nil.
2573
2718
  NIL_COMPATIBLE_CLASS_NAMES = %w[NilClass Object BasicObject Kernel].to_set.freeze
@@ -2770,6 +2915,8 @@ module Rigor
2770
2915
  # the original translated-acceptance check, with a `rigor:v1:param`
2771
2916
  # override taking precedence over the RBS-declared type.
2772
2917
  def single_argument_mismatch(param, arg, scope, param_overrides)
2918
+ return nil if stub_typed_param?(param, param_overrides, scope)
2919
+
2773
2920
  arg_type = scope.type_of(arg)
2774
2921
 
2775
2922
  if nil_member?(arg_type)
@@ -2824,17 +2971,29 @@ module Rigor
2824
2971
  Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual).yes?
2825
2972
  end
2826
2973
 
2974
+ # What this rule needs is that argument N binds to positional
2975
+ # parameter N, so the only disqualifiers are the two shapes that
2976
+ # break that mapping: a rest positional (arguments past it belong
2977
+ # to the rest, not to a later parameter) and trailing positionals
2978
+ # (the last arguments bind backwards from the end).
2979
+ #
2980
+ # Issue #673 — a keyword parameter used to disqualify the whole
2981
+ # signature, which silently unchecked the POSITIONAL arguments of
2982
+ # every keyword-bearing signature in the bundle:
2983
+ # `time.beginning_of_week("monday")` fired while
2984
+ # `time.next_week("monday")`, `(?Symbol, ?same_time: bool)`, did not,
2985
+ # though both raise the same way at runtime. Keywords cannot shift a
2986
+ # positional argument's index, and a call that PASSES keywords never
2987
+ # reaches here at all — `plain_positional_call?` rejects a
2988
+ # `KeywordHashNode` argument (`**opts` included) before the signature
2989
+ # is consulted.
2827
2990
  def argument_check_eligible?(function)
2828
2991
  # See `arity_eligible?`: `UntypedFunction` lacks
2829
2992
  # the per-arity accessors. Treat it as ineligible
2830
2993
  # for argument-type-mismatch diagnostics.
2831
2994
  return false unless function.respond_to?(:required_keywords)
2832
2995
 
2833
- function.rest_positionals.nil? &&
2834
- function.required_keywords.empty? &&
2835
- function.optional_keywords.empty? &&
2836
- function.rest_keywords.nil? &&
2837
- function.trailing_positionals.empty?
2996
+ function.rest_positionals.nil? && function.trailing_positionals.empty?
2838
2997
  end
2839
2998
 
2840
2999
  def translate_param_type(rbs_type, environment)
@@ -2911,6 +3070,14 @@ module Rigor
2911
3070
  #
2912
3071
  # - Skips methods without an RBS declaration. The rule has no contract to compare against for
2913
3072
  # source-only methods.
3073
+ # - Skips a `def` whose only declaration was resolved through an ANCESTOR. `Reflection`'s lookup
3074
+ # reads the fully resolved method table, so a signature written about a base class answers for
3075
+ # every subclass that inherits the name; comparing an override's body against it reports the
3076
+ # override wrong for disagreeing with a contract that was never written about it (#744, and
3077
+ # the four redmine sites in #856). ADR-35 already specifies this rule as comparing a method's
3078
+ # OWN declared return, and the sibling override rules gate on `defined_on?` — this one did not.
3079
+ # The inherited-declaration signal belongs to the ADR-35 family, which requires both sides
3080
+ # authored. Decided in ADR-110 WD3.
2914
3081
  # - Skips methods whose enclosing class isn't a `Type::Singleton` self_type that we can name
2915
3082
  # (top-level / module-level methods land outside the rule).
2916
3083
  # - Skips methods whose body's last expression is absent or types as `Dynamic[top]` (the
@@ -2978,6 +3145,7 @@ module Rigor
2978
3145
  Reflection.singleton_method_definition(self_type.class_name, def_node.name, scope: scope)
2979
3146
  end
2980
3147
  return nil if method_def.nil?
3148
+ return nil unless defined_on?(method_def, self_type.class_name)
2981
3149
 
2982
3150
  override = Rigor::RbsExtended.read_return_type_override(method_def, environment: scope.environment)
2983
3151
  return override if override
@@ -3156,9 +3324,15 @@ module Rigor
3156
3324
  names
3157
3325
  end
3158
3326
 
3327
+ # Issue #732 — the predicate is asked of the SCOPE, not re-implemented here. This rule kept its own
3328
+ # three-table copy until #723 widened the scope's with `discovered_methods` and left this one behind,
3329
+ # so the two disagreed about exactly one shape (a project class with no instance def, no superclass
3330
+ # and no include — `class Base; def self.build = :built; end`) with nothing observing the fork. #682
3331
+ # made `Scope` the single owner of the candidate ORDER; this makes it the single owner of the
3332
+ # predicate that filters those candidates too.
3159
3333
  def resolve_override_ancestor_name(scope, subclass_qualified, raw_ancestor)
3160
3334
  resolved = scope.ancestor_name_candidates(subclass_qualified, raw_ancestor)
3161
- .find { |candidate| known_user_class?(scope, candidate) }
3335
+ .find { |candidate| scope.known_user_class?(candidate) }
3162
3336
  return resolved if resolved
3163
3337
 
3164
3338
  # ADR-46 slice 3 — the override checker reads the class graph
@@ -3172,12 +3346,6 @@ module Rigor
3172
3346
  nil
3173
3347
  end
3174
3348
 
3175
- def known_user_class?(scope, name)
3176
- scope.discovered_superclasses.key?(name) ||
3177
- scope.discovered_def_nodes.key?(name) ||
3178
- scope.discovered_includes.key?(name)
3179
- end
3180
-
3181
3349
  def build_override_visibility_diagnostic(path, def_node, parent_class, parent_visibility, override_visibility)
3182
3350
  Diagnostic.from_name_loc(
3183
3351
  def_node,
@@ -87,12 +87,13 @@ module Rigor
87
87
  # user — every rule fired — but NOT a valid measurement of Rigor itself, so a harness that scores
88
88
  # Rigor's behaviour should treat one of these as a crash finding the way it treats `:check_rule`, or
89
89
  # a mutant that re-breaks the HKT scan (#776 was one) scores as a measurement over a silently degraded
90
- # universe. What consults it today: `tool/mutation`'s fuzz crash detector — on the severity-resolved
91
- # stream, so a `severity_overrides: rbs: off` hides the row from it. What does NOT yet: the ADR-69
92
- # kill oracle (`Protection::AnalysisGuard` reads `Result#crashed?`, which excludes every
93
- # `:rbs_build` row), and both harnesses still reuse one Environment across mutants, which memoises
94
- # the degraded registry after the first defect. Arming the oracle, inspecting the pre-severity row,
95
- # and resetting the Environment are issue #790.
90
+ # universe. Both Rigor-measuring harnesses consult it (#790): `tool/mutation`'s fuzz crash detector
91
+ # and {Protection::AnalysisGuard}, the ADR-69 kill oracles' refusal. Neither reads it alone the row
92
+ # is severity-stamped like any other, so `severity_overrides:` can drop it from a run's diagnostics
93
+ # entirely, and each harness also reads {Rigor::Environment#hkt_scan_failure}, the pre-severity record
94
+ # the row is derived from. Both also replace their Environment after a defect: the registry build is
95
+ # memoised and the failure slot is first-write-wins, so an Environment that degraded once stays
96
+ # degraded for every later mutant.
96
97
  ANALYZER_DEFECT_RULES = %w[
97
98
  rbs.coverage.hkt-scan-failed
98
99
  ].freeze
@@ -103,9 +104,6 @@ module Rigor
103
104
  # into. Built here, not at either rescue site, so the two twins cannot drift (issue #665) — and so
104
105
  # the appended crash frame is derived identically on the sequential and pooled paths. Keeps the
105
106
  # {CHECK_RULE_MESSAGE_PREFIX} prefix every consumer matches on; the frame is a trailing hint.
106
- #
107
- # @param error [StandardError]
108
- # @return [String]
109
107
  def check_rule_message(error)
110
108
  base = "#{CHECK_RULE_MESSAGE_PREFIX}: #{error.class}: #{error.message}"
111
109
  frame = crash_frame(error)
@@ -117,9 +115,6 @@ module Rigor
117
115
  # bundled or third-party plugin also lives under `lib/rigor/<plugin>/`, so a frame here says where
118
116
  # the raise was, never whose defect it is. Falls back to the raw top frame when the crash is entirely
119
117
  # inside a dependency, and to nil when there is no backtrace at all.
120
- #
121
- # @param error [Exception]
122
- # @return [String, nil]
123
118
  def crash_frame(error)
124
119
  frames = error.backtrace
125
120
  return nil if frames.nil? || frames.empty?
@@ -132,15 +127,11 @@ module Rigor
132
127
  # (the fork pool) rather than deriving it fresh from a live `Exception` — can relativize the frame it
133
128
  # already has without re-deriving `.crash_frame`'s "which frame" choice. Nil-safe, and a no-op (`sub`
134
129
  # never matches) on a frame with no `lib/rigor/` segment at all.
135
- #
136
- # @param frame [String, nil]
137
- # @return [String, nil]
138
130
  def relativize_frame(frame)
139
131
  frame&.sub(%r{\A.*/(lib/rigor/)}, '\1')
140
132
  end
141
133
 
142
- # @param diagnostic [Rigor::Analysis::Diagnostic]
143
- # @return [Symbol, nil] `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
134
+ # @return `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
144
135
  def reason(diagnostic)
145
136
  return :check_rule if diagnostic.message.to_s.start_with?(CHECK_RULE_MESSAGE_PREFIX)
146
137
  return :plugin if plugin_isolation_row?(diagnostic)
@@ -152,8 +143,6 @@ module Rigor
152
143
  # True when `diagnostic` is the rescue row that REPLACED a file's analysis — the only shape after which
153
144
  # the run's diagnostics say nothing about the code. NOT a general "something went wrong" predicate; see
154
145
  # the class doc for why `:plugin` and `:rbs_build` are excluded.
155
- #
156
- # @param diagnostic [Rigor::Analysis::Diagnostic]
157
146
  def discards_file_analysis?(diagnostic)
158
147
  reason(diagnostic) == DISCARDS_FILE_ANALYSIS_REASON
159
148
  end
@@ -162,17 +151,12 @@ module Rigor
162
151
  # measurement of Rigor — see {ANALYZER_DEFECT_RULES}. Orthogonal to {.discards_file_analysis?}: the
163
152
  # user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz does,
164
153
  # the ADR-69 kill oracle does not yet — #790).
165
- #
166
- # @param diagnostic [Rigor::Analysis::Diagnostic]
167
154
  def analyzer_defect?(diagnostic)
168
155
  ANALYZER_DEFECT_RULES.include?(diagnostic.rule)
169
156
  end
170
157
 
171
158
  # A one-line "<reason> at <path>:<line>: <message>" for a failure message, so whoever reads the raise
172
159
  # sees which shape fired and where without re-deriving it.
173
- #
174
- # @param diagnostic [Rigor::Analysis::Diagnostic]
175
- # @return [String]
176
160
  def describe(diagnostic)
177
161
  "#{reason(diagnostic) || :unknown} at #{diagnostic.path}:#{diagnostic.line}: #{diagnostic.message}"
178
162
  end