rigortype 0.4.5 → 0.4.7

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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -11,6 +11,8 @@ require_relative "../project_scan"
11
11
  require_relative "../worker_session"
12
12
  require_relative "../template_units"
13
13
  require_relative "../template_unit_collector"
14
+ require_relative "../../project_environment"
15
+ require_relative "scan_environment"
14
16
 
15
17
  module Rigor
16
18
  module Analysis
@@ -100,7 +102,7 @@ module Rigor
100
102
  synthetic_method_index = Inference::SyntheticMethodScanner.scan(
101
103
  plugin_registry: plugin_registry,
102
104
  paths: expansion.fetch(:files),
103
- environment: nil,
105
+ environment: scan_environment(plugin_registry, dependency_source_index, expansion),
104
106
  fact_store: shared_fact_store(plugin_registry),
105
107
  buffer: @buffer
106
108
  )
@@ -135,7 +137,9 @@ module Rigor
135
137
  # collectors walk the tree, and the AST is dropped before the next file (peak RSS stays flat).
136
138
  def discover(expansion:)
137
139
  build_discovery(
138
- Inference::ScopeIndexer.discovered_project_index_for_paths(expansion.fetch(:files), buffer: @buffer)
140
+ Inference::ScopeIndexer.discovered_project_index_for_paths(
141
+ expansion.fetch(:files), buffer: @buffer, uncertain: uncertain_paths(expansion)
142
+ )
139
143
  )
140
144
  end
141
145
 
@@ -145,11 +149,18 @@ module Rigor
145
149
  # (`seed_bundles` empty) this is `#discover` plus a fresh bundle set — the discovery index is identical.
146
150
  def discover_from_bundles(expansion:, seed_bundles:)
147
151
  index = Inference::ScopeIndexer.discovered_project_index_incremental(
148
- expansion.fetch(:files), seed_bundles: seed_bundles, buffer: @buffer
152
+ expansion.fetch(:files), seed_bundles: seed_bundles, buffer: @buffer, uncertain: uncertain_paths(expansion)
149
153
  )
150
154
  [build_discovery(index), index.fetch(:bundles)]
151
155
  end
152
156
 
157
+ # Issue #2021 — the files only the `require_relative` walk added ({Runner#required_outside_paths}): their
158
+ # declarations resolve constants and their methods exist, but every method contribution is `possible`, so no
159
+ # return type, arity or visibility is read from them.
160
+ def uncertain_paths(expansion)
161
+ expansion.fetch(:uncertain, Inference::ScopeIndexer::EMPTY_PATH_SET)
162
+ end
163
+
153
164
  # Builds the {Discovery} bundle from a `{ classes:, def_index: }` index hash (shared by the plain and
154
165
  # seed-bundle discovery paths).
155
166
  def build_discovery(index)
@@ -294,6 +305,37 @@ module Rigor
294
305
 
295
306
  private
296
307
 
308
+ # Issue #476 — the environment the Tier B (trait registry) half of the synthetic-method scan reads, or
309
+ # nil when no loaded plugin registers a trait registry (the scan then has no use for one, and Tier C's
310
+ # template and nested-class lanes behave exactly as they did). The view is lazy ({ScanEnvironment}): it
311
+ # builds an {Environment} on the first question a matching call site asks. That environment is built
312
+ # WITHOUT `synthetic_method_index:` — the index is this scan's output and only the run's real
313
+ # environment stores it; the loader never reads it — and without the diagnostic reporters, which the
314
+ # run's real environment attaches (a second set here would double-report). Every input that decides
315
+ # what the loader resolves (libraries, signature paths, plugin signatures, the bundle / collection
316
+ # axes, required features) is the one the real build passes, so the module a trait names resolves to
317
+ # the same RBS the dispatcher later promotes against.
318
+ def scan_environment(plugin_registry, dependency_source_index, expansion)
319
+ return nil if plugin_registry.nil? || plugin_registry.empty?
320
+ return nil unless plugin_registry.plugins.any? { |plugin| plugin.manifest.trait_registries.any? }
321
+
322
+ files = expansion.fetch(:files)
323
+ ScanEnvironment.new do
324
+ Environment.for_project(
325
+ libraries: @configuration.libraries,
326
+ signature_paths: @configuration.signature_paths,
327
+ cache_store: @cache_store,
328
+ plugin_registry: plugin_registry,
329
+ dependency_source_index: dependency_source_index,
330
+ source_files: files,
331
+ required_features: ProjectEnvironment.required_features(
332
+ @configuration, files, sources: ProjectEnvironment.buffer_sources(@buffer)
333
+ ),
334
+ **ProjectEnvironment.dependency_discovery_options(@configuration)
335
+ )
336
+ end
337
+ end
338
+
297
339
  def trust_refusals(registry, baseline)
298
340
  registry.plugins.filter_map do |plugin|
299
341
  summary = plugin.io_boundary.refusal_summary
@@ -336,8 +378,8 @@ module Rigor
336
378
  # Builds the {Rigor::Plugin::TrustPolicy} for this run. Trusted gems are the gem-name half of every
337
379
  # entry in `Configuration#plugins`. Allowed read roots default to the project root (CWD), the
338
380
  # project's signature_paths, and each trusted gem's `Gem::Specification#full_gem_path`, plus any
339
- # extras the user listed under `plugins_io.allowed_paths`. Slice 2 keeps `network_policy` `:disabled`
340
- # — the only value the configuration accepts today.
381
+ # extras the user listed under `plugins_io.allowed_paths`. `network_policy` is `plugins_io.network`
382
+ # (`:disabled` or `:allowlist`).
341
383
  def build_trust_policy
342
384
  trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
343
385
  roots = [Dir.pwd]
@@ -367,7 +409,7 @@ module Rigor
367
409
  return nil if gem_name.nil? || gem_name.empty?
368
410
 
369
411
  spec = Gem.loaded_specs[gem_name]
370
- spec&.full_gem_path # rigor:disable undefined-method
412
+ spec&.full_gem_path
371
413
  rescue StandardError
372
414
  nil
373
415
  end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Analysis
5
+ class Runner
6
+ # ADR-116 WD3 (#1195) — the run-level fact registry. A run-level fact is a fact about the RUN rather than
7
+ # about one file: the project-signature state, the definition-build failures, the HKT-scan outcome, the
8
+ # `RunStats` RBS tables, the effect-annotation carrier. Each is declared ONCE below, and every path that
9
+ # used to hand-list the slots — the {RunSnapshots} store, the {PoolCoordinator} backends, the
10
+ # {WorkerSession} drain, the {DiagnosticAggregator} rows and the incremental snapshot's
11
+ # `RunLevelRows` — reads the declarations instead.
12
+ #
13
+ # A {Fact} carries:
14
+ #
15
+ # - `name` and `empty` — the store key and its value before anything is recorded;
16
+ # - `merge` — how a recorded value folds into the slot: {Merge.assign}, {Merge.first_wins} (#784) or
17
+ # {Merge.union_by_class} (#696);
18
+ # - `source` — `:environment` (read off a coordinator-side environment before the per-file loop, so a
19
+ # fork child inherits the demand it makes), `:analysis` (read after the loop, and drained out of each
20
+ # pool worker, because the per-file analysis is what demands it) or `:stats` (`RunStats` telemetry,
21
+ # read only when stats are collected);
22
+ # - `gate` — for an `:environment` fact, which environment it is read from; an empty-closure run
23
+ # resolves one per gate, lazily, and every other path hands the same environment to both;
24
+ # - `demand` — for an `:analysis` fact, whether collecting it makes a demand of its own (the HKT
25
+ # registry build, #784) rather than reading what the analysis already demanded. A path that must not
26
+ # pay for a demand (the Ractor prewarm, an empty closure with a replay in hand) drains only the others;
27
+ # - `replay` — whether a narrowed incremental run replays the previous full run's value
28
+ # ({Cache::IncrementalSnapshot::RunLevelRows});
29
+ # - `row` / `position` — the {DiagnosticAggregator} method that turns the slot into diagnostics, and
30
+ # where that row sits in the post-analysis stream (docs/type-specification/diagnostic-policy.md fixes
31
+ # the order). Nil for a fact no diagnostic reads.
32
+ #
33
+ # The declaration ORDER is the collection order: {.harvest} reads the `:environment` facts in it, and
34
+ # that order is load-bearing (the stand-downs are read before the env-forcing reads, the conformance
35
+ # scan — which demands definitions — last). The row order is `position`, independently.
36
+ #
37
+ # Rows whose producer is not a slot here — the reporter streams, which the reporters snapshot and
38
+ # absorb themselves, and the two plugin tables the coordinator collects — are declared as {Row}s so
39
+ # the whole post-analysis order lives in one place.
40
+ #
41
+ # Everything here is frozen data plus module functions, never a closure: {WorkerSession#drain_reporters}
42
+ # reads these declarations inside a Ractor worker, and a drained value has to stay Marshal-clean (#805)
43
+ # for the fork backend.
44
+ module RunLevelFacts
45
+ Fact = Data.define(:name, :empty, :merge, :source, :gate, :demand, :replay, :row, :position)
46
+ Row = Data.define(:builder, :position)
47
+
48
+ # The predicates an `:environment` collector consults, taken by the coordinator when it harvests.
49
+ Context = Data.define(:project_signature_paths, :plugin_signature_paths, :record_effects)
50
+
51
+ # How a recorded value folds into its slot.
52
+ module Merge
53
+ module_function
54
+
55
+ def assign(_current, incoming)
56
+ incoming
57
+ end
58
+
59
+ # Issue #784 — the HKT scan is one build over the overlay every worker shares, so the first tuple
60
+ # recorded is the run's outcome.
61
+ def first_wins(current, incoming)
62
+ current.nil? ? incoming : current
63
+ end
64
+
65
+ # Issue #696 / #1902 — each pool worker holds its own loader, so the run's set is the union, one
66
+ # tuple per class chosen by {Environment::RbsLoader.definition_build_detail_order} rather than by
67
+ # arrival.
68
+ def union_by_class(current, incoming)
69
+ return current if incoming.nil? || incoming.empty?
70
+
71
+ (current + incoming)
72
+ .group_by(&:first)
73
+ .map { |_, candidates| candidates.min_by { |c| Environment::RbsLoader.definition_build_detail_order(c) } }
74
+ .freeze
75
+ end
76
+ end
77
+
78
+ # One collector per fact, named after it: `(environment, context) -> value`.
79
+ module Collect
80
+ module_function
81
+
82
+ # Issue #610 — gated on a loaded plugin contributing signatures, not on `signature_paths:`: the
83
+ # source a plugin's `sig/` stands down against is typically an `rbs collection install`.
84
+ def signature_standdowns(environment, context)
85
+ return [] unless context.plugin_signature_paths
86
+
87
+ loader = environment&.rbs_loader
88
+ return [] if loader.nil? || !loader.respond_to?(:deferred_signature_paths)
89
+ return [] if loader.deferred_signature_paths.empty?
90
+
91
+ loader.signature_standdowns
92
+ end
93
+
94
+ # Issue #1713 — not behind the `signature_paths:` gate either; the loader answers without building
95
+ # its env when no gated directory is active.
96
+ def vendored_standdowns(environment, _context)
97
+ loader = environment&.rbs_loader
98
+ loader.respond_to?(:vendored_standdowns) ? loader.vendored_standdowns : []
99
+ end
100
+
101
+ # The four below read the project's OWN `signature_paths:` state, and each forces the otherwise-lazy
102
+ # env build, so they are gated on the project declaring `signature_paths:`.
103
+ def synthesized_namespaces(environment, context)
104
+ return [] unless context.project_signature_paths
105
+
106
+ environment&.rbs_loader&.synthesized_namespaces || []
107
+ end
108
+
109
+ # Read after the env-forcing read above: the collision derivation reads the BUILT env.
110
+ def quarantined_signatures(environment, context)
111
+ return [] unless context.project_signature_paths
112
+
113
+ environment&.rbs_loader&.quarantined_signatures || []
114
+ end
115
+
116
+ def env_build_failure(environment, context)
117
+ return nil unless context.project_signature_paths
118
+
119
+ environment&.rbs_loader&.env_build_failure
120
+ end
121
+
122
+ # Read LAST of the signature state: the scan demands the definition of every `rigor:v1:conforms-to`
123
+ # class, and a resulting definition-build failure has to be in the loader before the `:analysis`
124
+ # read of {.definition_build_failures}.
125
+ def conformance_results(environment, context)
126
+ return [] unless context.project_signature_paths
127
+
128
+ RbsExtended::ConformanceChecker.scan(environment&.rbs_loader)
129
+ end
130
+
131
+ # #441 — the inline stratum of `effect.annotations-unchecked`. Skipped when effect collection is on:
132
+ # {EffectEnvelopePass} reads the loader directly then.
133
+ def effect_annotation_carrier(environment, context)
134
+ virtual = context.record_effects ? nil : environment&.rbs_loader&.virtual_rbs
135
+ Effects::SignatureSources.annotated_carrier(virtual)
136
+ rescue StandardError
137
+ [].freeze
138
+ end
139
+
140
+ # Issue #696 — read after the loop: definition builds are lazy, per class, on first demand. NOT
141
+ # gated on `signature_paths:` like the signature state: nothing is forced here (this reads what the
142
+ # analysis already demanded), and two BUNDLED sources can collapse a class with no project sig set
143
+ # in sight (the `bigdecimal` / `BigMath` shape, #299).
144
+ def definition_build_failures(environment, _context)
145
+ environment&.rbs_loader&.definition_build_failures || []
146
+ end
147
+
148
+ # Issue #784 — the run demands the registry once itself, so the outcome does not depend on which
149
+ # files were analysed. One build, one outcome whoever demands it, so a Rigor-owned demand cannot
150
+ # change what is reported (unlike #696's per-class list).
151
+ def hkt_scan_failure(environment, _context)
152
+ return nil if environment.nil?
153
+
154
+ environment.hkt_registry
155
+ environment.hkt_scan_failure
156
+ end
157
+
158
+ def class_decl_paths(environment, _context)
159
+ environment&.rbs_loader&.class_decl_paths || {}.freeze
160
+ end
161
+
162
+ def signature_paths(environment, _context)
163
+ environment&.rbs_loader&.signature_paths || [].freeze
164
+ end
165
+ end
166
+
167
+ EMPTY_LIST = [].freeze
168
+ EMPTY_TABLE = {}.freeze
169
+
170
+ FACTS = [
171
+ Fact.new(name: :signature_standdowns, empty: EMPTY_LIST, merge: :assign, source: :environment,
172
+ gate: :signature_state, demand: false, replay: false,
173
+ row: :rbs_plugin_signature_stood_down_diagnostics, position: 60),
174
+ Fact.new(name: :vendored_standdowns, empty: EMPTY_LIST, merge: :assign, source: :environment,
175
+ gate: :signature_state, demand: false, replay: false,
176
+ row: :rbs_vendored_signature_stood_down_diagnostics, position: 70),
177
+ Fact.new(name: :synthesized_namespaces, empty: EMPTY_LIST, merge: :assign, source: :environment,
178
+ gate: :signature_state, demand: false, replay: false,
179
+ row: :rbs_synthesized_namespace_diagnostics, position: 50),
180
+ Fact.new(name: :quarantined_signatures, empty: EMPTY_LIST, merge: :assign, source: :environment,
181
+ gate: :signature_state, demand: false, replay: false,
182
+ row: :rbs_quarantined_signature_diagnostics, position: 10),
183
+ Fact.new(name: :env_build_failure, empty: nil, merge: :assign, source: :environment,
184
+ gate: :signature_state, demand: false, replay: false,
185
+ row: :rbs_environment_build_failed_diagnostics, position: 20),
186
+ Fact.new(name: :conformance_results, empty: EMPTY_LIST, merge: :assign, source: :environment,
187
+ gate: :signature_state, demand: false, replay: false,
188
+ row: :conforms_to_diagnostics, position: 80),
189
+ # Read by {EffectAnnotationResidualPass}, outside this stream.
190
+ Fact.new(name: :effect_annotation_carrier, empty: EMPTY_LIST, merge: :assign, source: :environment,
191
+ gate: :annotation_carrier, demand: false, replay: false, row: nil, position: nil),
192
+ Fact.new(name: :definition_build_failures, empty: EMPTY_LIST, merge: :union_by_class, source: :analysis,
193
+ gate: nil, demand: false, replay: true,
194
+ row: :rbs_definition_build_failed_diagnostics, position: 30),
195
+ Fact.new(name: :hkt_scan_failure, empty: nil, merge: :first_wins, source: :analysis,
196
+ gate: nil, demand: true, replay: true,
197
+ row: :rbs_hkt_scan_failed_diagnostics, position: 40),
198
+ Fact.new(name: :class_decl_paths, empty: EMPTY_TABLE, merge: :assign, source: :stats,
199
+ gate: nil, demand: false, replay: false, row: nil, position: nil),
200
+ Fact.new(name: :signature_paths, empty: EMPTY_LIST, merge: :assign, source: :stats,
201
+ gate: nil, demand: false, replay: false, row: nil, position: nil)
202
+ ].freeze
203
+
204
+ # The rows no slot above produces. The reporter streams follow the `rbs.coverage.*` ladder and the
205
+ # conformance rows; the plugin-authored run disclosures (#1051) come before the engine-authored trust
206
+ # refusal envelope (#959), which is read last so every per-file plugin call has recorded its refusals.
207
+ ROWS = [
208
+ Row.new(builder: :unresolved_payload_diagnostics, position: 90),
209
+ Row.new(builder: :lossy_projection_diagnostics, position: 100),
210
+ Row.new(builder: :hkt_directive_diagnostics, position: 110),
211
+ Row.new(builder: :deprecated_form_diagnostics, position: 120),
212
+ Row.new(builder: :boundary_cross_diagnostics, position: 130),
213
+ Row.new(builder: :source_rbs_synthesis_diagnostics, position: 140),
214
+ Row.new(builder: :plugin_run_disclosure_diagnostics, position: 150),
215
+ Row.new(builder: :plugin_trust_refusal_diagnostics, position: 160)
216
+ ].freeze
217
+
218
+ # The per-run reporter accumulators, by drain key. Each reporter snapshots and absorbs its own
219
+ # streams (`#snapshot` / `#absorb`), so a new stream inside one touches that reporter and its row.
220
+ REPORTERS = %i[rbs_extended boundary_cross source_rbs_synthesis].freeze
221
+
222
+ BY_NAME = FACTS.to_h { |fact| [fact.name, fact] }.freeze
223
+
224
+ # Read inside a Ractor worker's drain, so every constant here is deeply shareable.
225
+ Ractor.make_shareable(FACTS)
226
+ Ractor.make_shareable(ROWS)
227
+ Ractor.make_shareable(BY_NAME)
228
+
229
+ module_function
230
+
231
+ def facts
232
+ FACTS
233
+ end
234
+
235
+ def fact(name)
236
+ BY_NAME.fetch(name)
237
+ end
238
+
239
+ def names(source: nil)
240
+ (source ? FACTS.select { |fact| fact.source == source } : FACTS).map(&:name)
241
+ end
242
+
243
+ def replayable_names
244
+ FACTS.select(&:replay).map(&:name)
245
+ end
246
+
247
+ def empty_values
248
+ FACTS.to_h { |fact| [fact.name, fact.empty] }
249
+ end
250
+
251
+ # The post-analysis diagnostic builders, in their contract order.
252
+ def row_builders
253
+ (FACTS.select(&:row).map { |fact| Row.new(builder: fact.row, position: fact.position) } + ROWS)
254
+ .sort_by(&:position)
255
+ .map(&:builder)
256
+ end
257
+
258
+ # Collects every fact of `source`, in declaration order, from the environment the block yields for
259
+ # its gate (one call per gate, on first need). `:analysis` and `:stats` facts take the block's
260
+ # environment with a nil gate.
261
+ def harvest(source, context)
262
+ environments = {}
263
+ FACTS.each_with_object({}) do |fact, collected|
264
+ next unless fact.source == source
265
+
266
+ environment = environments.fetch(fact.gate) { environments[fact.gate] = yield(fact.gate) }
267
+ collected[fact.name] = Collect.public_send(fact.name, environment, context)
268
+ end
269
+ end
270
+
271
+ # The `:analysis` facts as an analysing process reports them: the {WorkerSession} drain, and the
272
+ # coordinator's own environment on the paths that analyse on it. Plain tuples — Marshal-clean and
273
+ # shareable.
274
+ #
275
+ # @param demanding — nil drains every `:analysis` fact; false only those that read what was already
276
+ # demanded; true only those that make a demand of their own.
277
+ def drain(environment, demanding: nil)
278
+ FACTS.each_with_object({}) do |fact, drained|
279
+ next unless fact.source == :analysis
280
+ next unless demanding.nil? || fact.demand == demanding
281
+
282
+ drained[fact.name] = Collect.public_send(fact.name, environment, nil)
283
+ end
284
+ end
285
+
286
+ # The `:analysis` facts out of a drained hash, for {RunSnapshots#absorb}.
287
+ def drained_facts(drained)
288
+ names(source: :analysis).each_with_object({}) do |name, facts|
289
+ facts[name] = drained[name] if drained.key?(name)
290
+ end
291
+ end
292
+
293
+ def snapshot_reporters(reporters)
294
+ REPORTERS.to_h { |name| [name, reporters.fetch(name).snapshot] }
295
+ end
296
+
297
+ # A drain shape that lacks a reporter's key records nothing for it.
298
+ def absorb_reporters(reporters, drained)
299
+ REPORTERS.each { |name| reporters.fetch(name).absorb(drained[name]) }
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -1,68 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "run_level_facts"
4
+
3
5
  module Rigor
4
6
  module Analysis
5
7
  class Runner
6
- # Mutable per-run holder for the end-of-pass snapshots that the analysis paths compute as a side
7
- # effect and the rest of the run reads back: the RBS `class_decl_paths` / `signature_paths` tables
8
- # (consumed by {RunStats}), the synthesized-namespace name list, the `conforms-to` scan results
9
- # (consumed by the diagnostic aggregator), and the effect-annotation carrier (#441 — consumed by
10
- # {EffectAnnotationResidualPass} on the runs that never store the environment).
8
+ # Mutable per-run store for the run-level facts ({RunLevelFacts}): one slot per declared fact, keyed by
9
+ # its name. The analysis paths write it ({PoolCoordinator} — sequential, fork-pool, Ractor-pool,
10
+ # fallback, empty closure) and the rest of the run reads it back: the {DiagnosticAggregator} rows,
11
+ # {RunStats}, {EffectAnnotationResidualPass} and the incremental snapshot's `RunLevelRows`.
12
+ #
13
+ # A shared mutable holder keeps the write/read timing of the original inline ivars while letting the
14
+ # writer and the readers live in separate objects without a back-reference cycle.
11
15
  #
12
- # The snapshots are written by whichever analysis path ran ({PoolCoordinator} sequential / fork-pool /
13
- # fallback) and read by the {Runner} and {DiagnosticAggregator}. A shared mutable holder keeps the
14
- # write/read timing bit-for-bit with the original inline ivars (the same pattern the reporter
15
- # accumulators use) while letting the writer and readers live in separate objects without a
16
- # back-reference cycle.
16
+ # The analysis paths write through {#absorb}, which folds each value in by its fact's declared merge
17
+ # rule. `[]=` and the per-fact `name=` writers (defined from the declarations, like the readers) are a
18
+ # plain assignment that bypasses the merge rule: they exist for specs that seed a slot, and no
19
+ # production path calls them.
17
20
  class RunSnapshots
18
- attr_accessor :class_decl_paths, :signature_paths,
19
- :synthesized_namespaces, :quarantined_signatures, :signature_standdowns, :vendored_standdowns,
20
- :conformance_results,
21
- :env_build_failure, :definition_build_failures, :hkt_scan_failure,
22
- :effect_annotation_carrier
23
-
24
- # Constructor defaults match the {Runner} constructor: the pre-seed values `build_run_stats` /
25
- # `pre_file_diagnostics` read before the first analysis path runs are frozen empties. The
26
- # `env_build_failure` slot is nil (no failure) rather than an empty collection — it holds a single
27
- # `[error_class, first_line, buffer_names]` tuple or nothing. Its per-class sibling
28
- # `definition_build_failures` (#696) holds a LIST, because a collapsed universe fails many classes,
29
- # and is accumulated ACROSS pool workers rather than assigned once — see
30
- # {PoolCoordinator#merge_worker_reporters}. `hkt_scan_failure` (#784) is nil-or-tuple like
31
- # `env_build_failure`, not a list like `definition_build_failures`: the scan is ONE build over the
32
- # whole `signature_paths:` overlay, not a per-class one, so it has exactly one outcome per run.
33
- # `signature_standdowns` (#610) is the quarantine slot's twin for the plugin-contributed files that
34
- # stood down against a colliding generic arity: a list, assigned once from the loader as
35
- # `quarantined_signatures` is, and derived from the final env so a cache HIT carries it too.
36
- # `vendored_standdowns` (#1713) is the same shape of slot for what a required-feature-gated vendored
37
- # directory left out because another signature source declares it.
38
21
  def initialize
39
- @class_decl_paths = {}.freeze
40
- @signature_paths = [].freeze
41
- @synthesized_namespaces = [].freeze
42
- @quarantined_signatures = [].freeze
43
- @signature_standdowns = [].freeze
44
- @vendored_standdowns = [].freeze
45
- @conformance_results = [].freeze
46
- @env_build_failure = nil
47
- @definition_build_failures = [].freeze
48
- @hkt_scan_failure = nil
49
- @effect_annotation_carrier = [].freeze
22
+ @values = RunLevelFacts.empty_values
50
23
  end
51
24
 
52
- # Per-`#run` reset. Mirrors the original `#run` body, which reset these to NON-frozen empties (distinct
53
- # from the frozen constructor defaults) at the top of each run.
25
+ # Per-`#run` reset to every fact's declared empty value.
54
26
  def reset_for_run
55
- @class_decl_paths = {}.freeze
56
- @signature_paths = []
57
- @synthesized_namespaces = []
58
- @quarantined_signatures = []
59
- @signature_standdowns = []
60
- @vendored_standdowns = []
61
- @conformance_results = []
62
- @env_build_failure = nil
63
- @definition_build_failures = []
64
- @hkt_scan_failure = nil
65
- @effect_annotation_carrier = [].freeze
27
+ @values = RunLevelFacts.empty_values
28
+ end
29
+
30
+ def [](name)
31
+ RunLevelFacts.fact(name)
32
+ @values[name]
33
+ end
34
+
35
+ def []=(name, value)
36
+ RunLevelFacts.fact(name)
37
+ @values[name] = value
38
+ end
39
+
40
+ # Folds `facts` (`{name => value}`) into the store, each by its fact's merge rule.
41
+ def absorb(facts)
42
+ facts.each do |name, value|
43
+ merge = RunLevelFacts.fact(name).merge
44
+ @values[name] = RunLevelFacts::Merge.public_send(merge, @values[name], value)
45
+ end
46
+ self
47
+ end
48
+
49
+ def to_h
50
+ @values.dup
51
+ end
52
+
53
+ RunLevelFacts.facts.each do |fact|
54
+ define_method(fact.name) { @values[fact.name] }
55
+ define_method(:"#{fact.name}=") { |value| @values[fact.name] = value }
66
56
  end
67
57
  end
68
58
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Analysis
5
+ class Runner
6
+ # Issue #476 — the read-only, lazily built view of the project's RBS universe that the ADR-16 Tier B
7
+ # (trait registry) half of {Inference::SyntheticMethodScanner} reads.
8
+ #
9
+ # The scanner needs exactly two answers from an environment: `#rbs_loader` (to explode a trait module such
10
+ # as `Devise::Models::Recoverable` into its instance-method names) and `#class_ordering` (to decide
11
+ # whether a project class descends from an RBS-known `receiver_constraint` such as
12
+ # `ActiveRecord::Base`). Neither reads the synthetic-method index — `Environment.for_project` only stores
13
+ # that index, the RBS loader never consults it — so the chicken-and-egg the issue describes (the env
14
+ # consumes the scan's output) does not reach the loader: the view builds an environment WITHOUT the
15
+ # index, the scan runs against it, and the run's real environment is built afterwards WITH the index.
16
+ #
17
+ # The build is deferred to the first question. A scan whose trait registries never match a call site
18
+ # (no `devise` call in any class body) never asks, so it never pays the environment build. The build is
19
+ # fail-soft: a raise leaves the view answering "unknown", which is what the nil environment answered
20
+ # before — Tier B emits nothing rather than aborting the pre-pass.
21
+ class ScanEnvironment
22
+ # @param builder — called at most once, returns the {Rigor::Environment} (without a synthetic index).
23
+ def initialize(&builder)
24
+ @builder = builder
25
+ @built = false
26
+ @environment = nil
27
+ end
28
+
29
+ def rbs_loader
30
+ environment&.rbs_loader
31
+ end
32
+
33
+ def class_ordering(lhs, rhs)
34
+ env = environment
35
+ env.nil? ? :unknown : env.class_ordering(lhs, rhs)
36
+ end
37
+
38
+ private
39
+
40
+ def environment
41
+ return @environment if @built
42
+
43
+ @built = true
44
+ @environment = @builder.call
45
+ rescue StandardError
46
+ @environment = nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Analysis
5
+ class Runner
6
+ # ADR-116 WD3 (#1195) — what one analysing {WorkerSession} hands back to the {PoolCoordinator}: its
7
+ # per-file results and every drain, taken in one place ({.harvest}) and folded in by one
8
+ # ({PoolCoordinator#absorb_worker_payload}). A fork child marshals it to its payload file, the Ractor
9
+ # backend sends it with `:done`, and the fork backend's degrade path harvests the parent session the
10
+ # same way.
11
+ #
12
+ # Every field is Marshal-clean (#805) and, on the Ractor backend, copyable: the drains are frozen
13
+ # `Data` entries and Hashes of Strings. `effects` is nil unless the run collects effects (ADR-103 WD13).
14
+ #
15
+ # The refusals are carried but not absorbed: their merge order is the file order of the whole run, so
16
+ # each backend merges them itself (#1900).
17
+ WorkerPayload = Data.define(:results, :reporters, :dependencies, :analysis_reads, :analysis_disclosures,
18
+ :disclosures, :refusals, :effects) do
19
+ # Drains `session` in the order the fork child always has: the reporters (which demand the HKT
20
+ # registry) before the refusal delta, so a refusal that demand makes is reported.
21
+ def self.harvest(session, results: {}, record_effects: false)
22
+ new(
23
+ results: results,
24
+ reporters: session.drain_reporters,
25
+ dependencies: session.drain_dependencies,
26
+ analysis_reads: session.drain_analysis_reads,
27
+ analysis_disclosures: session.drain_analysis_disclosures,
28
+ disclosures: session.drain_run_disclosures,
29
+ refusals: session.drain_trust_refusals,
30
+ effects: record_effects ? session.drain_effects : nil
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end