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
@@ -44,7 +44,7 @@ module Rigor
44
44
  Resolved.new(
45
45
  gem_name: entry.gem,
46
46
  version: spec.version.to_s,
47
- gem_dir: spec.full_gem_path, # rigor:disable undefined-method
47
+ gem_dir: spec.full_gem_path,
48
48
  mode: entry.mode,
49
49
  roots: entry.roots
50
50
  )
@@ -15,10 +15,9 @@ module Rigor
15
15
  :class_to_gem, :budget_overrun_strategy, :gem_modes, :refinements, :bound_constants,
16
16
  :bound_segments
17
17
 
18
- # @param method_catalog — => Symbol}] the flat
19
- # `(class_name, method_name) → :instance | :singleton` table produced by {Walker.walk}, aggregated
20
- # across every resolved gem in the run. The Index itself stays gem-agnostic — the per-gem
21
- # attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
18
+ # @param method_catalog — the flat `(class_name, method_name) → CatalogEntry` table produced by
19
+ # {Walker.walk}, aggregated across every resolved gem in the run. The Index itself stays gem-agnostic —
20
+ # the per-gem attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
22
21
  # @param budget_exceeded — gem names whose {Walker} run hit the per-gem catalog cap
23
22
  # (slice 4). The Runner consumes this list to emit one `dynamic.dependency-source.budget-exceeded`
24
23
  # warning per gem.
@@ -110,10 +109,9 @@ module Rigor
110
109
  mode_for(class_name) == :full
111
110
  end
112
111
 
113
- # Looks up the recorded method kind for a `(class_name, method_name)` pair. Returns `:instance` /
114
- # `:singleton` when the walker observed a definition under one of the resolved gems' `roots:`, or
115
- # `nil` otherwise. Slice 2b-ii enriches this with the inferred return type so the dispatcher tier can
116
- # build a `Type::Dynamic` directly from the lookup result.
112
+ # Looks up the `CatalogEntry` (kind, and the heuristic return type) for a `(class_name, method_name)` pair.
113
+ # Returns it when the walker observed a definition under one of the resolved gems' `roots:`, or `nil`
114
+ # otherwise.
117
115
  def contribution_for(class_name:, method_name:)
118
116
  @method_catalog[[class_name, method_name]]
119
117
  end
@@ -99,10 +99,10 @@ module Rigor
99
99
  # `Configuration::Dependencies::Entry`).
100
100
  # @param budget — per-gem catalog cap (method-definition count). When unset, defaults
101
101
  # to `UNBOUNDED` for backwards-compatible test paths.
102
- # @return frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
103
- # :instance | :singleton}`) and a `truncated?` flag set when the walker stopped harvesting because
104
- # the budget was reached. Methods of identical name on the same class with different kinds (rare;
105
- # private API mostly) carry the kind that wins the per-class first walk.
102
+ # @return frozen wrapper carrying the catalog (keyed by `[class_name, method_name]`, valued by
103
+ # {CatalogEntry}) and a `truncated?` flag set when the walker stopped harvesting because the budget was
104
+ # reached. Methods of identical name on the same class with different kinds (rare; private API mostly)
105
+ # carry the kind that wins the per-class first walk.
106
106
  def walk(gem_dir:, roots:, budget: UNBOUNDED)
107
107
  harvest = Harvest.new({}, {}, nil, nil, Set.new, Set.new)
108
108
  truncated = false
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Analysis
7
+ # Issue #1967 — the edits `rigor check --verify-incremental-edits` replays on each sampled file, as the text an
8
+ # editor buffer stands in for it with ({IncrementalSession#replay_edit}).
9
+ #
10
+ # - `emptied` — the file with nothing in it. Every declaration and definition it makes disappears, so the replay
11
+ # exercises the edges a removed declaration fires.
12
+ # - `skeleton` — the file's `class` / `module` declarations, nested as written, with every body statement dropped
13
+ # (methods, `refine` blocks, visibility calls, mixins, constants). The declarations stay, so the class-level keys
14
+ # a removed declaration fires stay quiet, and a finer edge such as #2163's `refinement:` is what puts a reader
15
+ # into the closure. Emptying alone does not exercise that one: the broader key it fires re-checks the reader
16
+ # anyway. Not every finer edge is reached: dropping a body still moves a broader key for some of them. A
17
+ # `visibility:` edge is one (#2161's `module_function :two` to `private :two` swap passes both edits with the
18
+ # edge removed), and only an edit that changes a body while keeping it would test it.
19
+ # - `dropped` (#2277) — the file with ONE statement of a `class` / `module` body removed and everything else kept,
20
+ # so the declarations, the other definitions and every broader key stay as they were and a finer edge is the
21
+ # only thing that re-checks a reader. At most {MAX_DROPS} statements per file: the visibility-changing calls
22
+ # (`private`, `module_function`, ... {VISIBILITY_CALLS}) first, a body's own statements before those of the bodies
23
+ # nested in it, since those are what finer edges such as #2161's `visibility:` read; then the other statements,
24
+ # spread evenly over the file. A drop whose remainder does not parse (a heredoc body left behind) gives way to the
25
+ # next candidate. Statements of a `class << self` body are never candidates.
26
+ #
27
+ # No edit is an addition. An edit identical to the file or to an earlier edit, or a file that does not parse
28
+ # (or, for the skeleton, declares nothing), is not replayed.
29
+ module EditReplay
30
+ Edit = Data.define(:label, :source)
31
+
32
+ # The most body-statement drops replayed per file; each is a recheck plus a full analysis.
33
+ MAX_DROPS = 2
34
+
35
+ VISIBILITY_CALLS = %i[
36
+ private public protected module_function private_class_method public_class_method
37
+ private_constant public_constant
38
+ ].freeze
39
+
40
+ module_function
41
+
42
+ # The edits to replay for `source`, in order.
43
+ def edits(source)
44
+ emptied = Edit.new(label: "emptying", source: "")
45
+ reduced = skeleton(source)
46
+ edits = [emptied]
47
+ edits << Edit.new(label: "reducing to its class/module skeleton", source: reduced) unless reduced.empty?
48
+ edits.concat(drops(source))
49
+ edits.reject { |edit| edit.source == source }.uniq(&:source)
50
+ end
51
+
52
+ # Up to {MAX_DROPS} edits, each `source` less one statement of a class / module body, in source order. A drop that
53
+ # leaves text that does not parse is never returned: the next candidate takes its place.
54
+ def drops(source)
55
+ result = Prism.parse(source)
56
+ return [] unless result.success?
57
+
58
+ picked = []
59
+ preferred(drop_candidates(result.value.statements.body)).each do |node|
60
+ edit = drop_edit(source, node)
61
+ picked << [node, edit] if edit
62
+ break if picked.size == MAX_DROPS
63
+ end
64
+ picked.sort_by { |node, _| node.location.start_offset }.map(&:last)
65
+ end
66
+
67
+ # `source` less `node`, or nil when what is left does not parse. The range runs to the furthest heredoc body
68
+ # inside the node, which sits after the node's own location.
69
+ def drop_edit(source, node)
70
+ location = node.location
71
+ from, to = whole_lines(source, location.start_offset, [location.end_offset, heredoc_end(node)].max)
72
+ dropped = source.byteslice(0, from) + source.byteslice(to..)
73
+ return nil unless Prism.parse(dropped).success?
74
+
75
+ Edit.new(label: "dropping the statement at line #{location.start_line} of", source: dropped)
76
+ end
77
+
78
+ # The end offset of the furthest heredoc terminator inside `node` (0 when it holds none).
79
+ def heredoc_end(node)
80
+ own = node.respond_to?(:closing_loc) && node.respond_to?(:opening_loc) &&
81
+ node.opening_loc&.slice&.start_with?("<<")
82
+ ends = node.compact_child_nodes.map { |child| heredoc_end(child) }
83
+ ends << node.closing_loc.end_offset if own && node.closing_loc
84
+ ends.max || 0
85
+ end
86
+
87
+ # The byte range of `start...finish` widened to the lines it alone occupies, so no blank line is left behind.
88
+ def whole_lines(source, start, finish)
89
+ head = source.byteslice(0, start)[/(?<=\A|\n)[ \t]*\z/]
90
+ tail = finish.positive? && source.getbyte(finish - 1) == 10 ? "" : source.byteslice(finish..)[/\A[ \t]*\n/]
91
+ head.nil? || tail.nil? ? [start, finish] : [start - head.bytesize, finish + tail.bytesize]
92
+ end
93
+
94
+ # Every statement directly in a `class` / `module` body of `nodes`, nested bodies included, in the order a body's
95
+ # own statements come before those of the bodies nested in it (a nested declaration is itself a candidate,
96
+ # dropped whole). A `class << self` body is never searched: its statements are not candidates.
97
+ def drop_candidates(nodes)
98
+ nodes.flat_map do |node|
99
+ next [] unless (node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)) &&
100
+ node.body.is_a?(Prism::StatementsNode)
101
+
102
+ body = node.body.body
103
+ [*body, *drop_candidates(body)]
104
+ end
105
+ end
106
+
107
+ # The candidates in the order to try them: the visibility calls, then the other statements from the evenly spread
108
+ # picks outward, then the rest of the other statements.
109
+ def preferred(candidates)
110
+ visibility, others = candidates.partition { |node| visibility_call?(node) }
111
+ spread = Array.new(others.empty? ? 0 : MAX_DROPS) do |index|
112
+ others[(((index * 2) + 1) * others.size) / (MAX_DROPS * 2)]
113
+ end
114
+ [*visibility, *spread, *others].uniq(&:object_id)
115
+ end
116
+
117
+ def visibility_call?(node)
118
+ node.is_a?(Prism::CallNode) && VISIBILITY_CALLS.include?(node.name) &&
119
+ (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
120
+ end
121
+
122
+ # `source`'s `class` / `module` declarations, nested, with their bodies dropped; "" when it does not parse or
123
+ # declares nothing.
124
+ def skeleton(source)
125
+ result = Prism.parse(source)
126
+ return "" unless result.success?
127
+
128
+ lines = declarations(result.value.statements, 0)
129
+ lines.empty? ? "" : "#{lines.join("\n")}\n"
130
+ end
131
+
132
+ # The skeleton lines of the declarations directly in `statements`, indented `depth` levels.
133
+ def declarations(statements, depth)
134
+ return [] unless statements.is_a?(Prism::StatementsNode)
135
+
136
+ statements.body.flat_map do |node|
137
+ next [] unless node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
138
+
139
+ indent = " " * depth
140
+ ["#{indent}#{header_of(node)}", *declarations(node.body, depth + 1), "#{indent}end"]
141
+ end
142
+ end
143
+
144
+ def header_of(node)
145
+ return "module #{node.constant_path.slice}" if node.is_a?(Prism::ModuleNode)
146
+
147
+ superclass = node.superclass
148
+ superclass ? "class #{node.constant_path.slice} < #{superclass.slice}" : "class #{node.constant_path.slice}"
149
+ end
150
+ private_class_method :declarations, :header_of, :drop_candidates, :whole_lines, :drop_edit, :heredoc_end,
151
+ :preferred, :visibility_call?
152
+ end
153
+ end
154
+ end
@@ -139,9 +139,9 @@ module Rigor
139
139
  unique.freeze
140
140
  end
141
141
 
142
- # `fact.target` is `Target | Array[Target]` per the carrier contract. Branching with an early return on
143
- # the `Array` arm lets type narrowing collapse the post-return value to the bare `Target` case, so the
144
- # wrapped tuple is `[Target]` and the union of return paths is exactly `Array[Target]`.
142
+ # `fact.target` is a Target or an Array of them, per the carrier contract. The early return on the Array arm
143
+ # lets type narrowing collapse the post-return value to the bare Target case, so the wrapped one-element
144
+ # Array is what the return paths join into.
145
145
  def fact_targets(fact)
146
146
  target = fact.target
147
147
  return target if target.is_a?(Array)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../inference/global_write_census"
4
+
3
5
  module Rigor
4
6
  module Analysis
5
7
  # ADR-46 slice 2 — the pure set-algebra core of the incremental step, kept side-effect-free and
@@ -132,6 +134,41 @@ module Rigor
132
134
  moved.freeze
133
135
  end
134
136
 
137
+ # Issue #2158 — the `global_census:` keys whose entries moved in this edit: one for every entry that appeared in
138
+ # or vanished from a file's `global_write_census` ({Inference::GlobalWriteCensus.dependency_key}). `paths` MUST
139
+ # include removed files, whose whole census vanished. `before` / `after` map a path to that file's census Set (nil
140
+ # when it records nothing).
141
+ def changed_global_census_keys(paths, before, after)
142
+ moved = Set.new
143
+ paths.each do |path|
144
+ ((before[path] || Set.new) ^ (after[path] || Set.new)).each do |entry|
145
+ moved << "global_census:#{Inference::GlobalWriteCensus.dependency_key(entry)}"
146
+ end
147
+ end
148
+ moved.freeze
149
+ end
150
+
151
+ # The {Inference::ScopeIndexer.visibility_entries} entries that carry a leading marker before `class, slot`.
152
+ MARKED_VISIBILITY_ENTRIES = %i[contested module_function].freeze
153
+
154
+ # Issue #2134 — the `<class>#<method>` slots whose visibility entries or contests moved in this edit: one for
155
+ # every entry that appeared in or vanished from a file's {Inference::ScopeIndexer.visibility_entries}, a
156
+ # singleton slot's `[:singleton, name]` read as `name` (the wildcard slot as `*`), plus `*#<method>`, which the
157
+ # "is the name private anywhere" readers record. That key fires for an entry of any visibility: the merge is
158
+ # last-writer-wins, so dropping a `public :foo` can uncover another file's `private :foo`. `paths` MUST include
159
+ # removed files. Issue #2161 — a `[:module_function, owner, name]` entry fires `<owner>#<name>` the same way.
160
+ def changed_visibility_slots(paths, before, after)
161
+ moved = Set.new
162
+ paths.each do |path|
163
+ ((before[path] || Set.new) ^ (after[path] || Set.new)).each do |entry|
164
+ class_name, slot = MARKED_VISIBILITY_ENTRIES.include?(entry.first) ? entry.drop(1) : entry
165
+ name = slot.is_a?(Array) ? slot.last : slot
166
+ moved << "#{class_name}##{name}" << "*##{name}"
167
+ end
168
+ end
169
+ moved.freeze
170
+ end
171
+
135
172
  # Issue #1120 — the method names whose refinements moved in this edit. A consumer whose answer consulted the
136
173
  # refinement table recorded a `refinement:<method>` edge whichever way it answered, because the answer is a
137
174
  # function of the whole project's refinements of that name. `paths` MUST include removed files, whose whole
@@ -261,8 +261,8 @@ module Rigor
261
261
  end
262
262
  end
263
263
 
264
- # A lockfile is read for its bytes: one absent at the mark must still be absent, one present must still be
265
- # there with its change time before the mark.
264
+ # A lockfile is compared by its presence and change time: one absent at the mark must still be absent, one
265
+ # present must still be there with a change time before the mark.
266
266
  def lockfile_changed?(path)
267
267
  return File.exist?(path) unless present_at_mark?(path)
268
268
 
@@ -54,8 +54,8 @@ module Rigor
54
54
  # a compaction pass — the incremental path never runs one.
55
55
  GENERATION_CAP = RunCacheKey::GENERATION_CAP
56
56
 
57
- # The stored value. `baseline` and `pinned` (each a {Cache::Descriptor}) and `reads` (`{path =>
58
- # Cache::Descriptor}`) are the chain the next writer carries forward, kept apart from the rest of the
57
+ # The stored value. `baseline` and `pinned` (each a {Cache::Descriptor}) and `reads` (keyed by
58
+ # path) are the chain the next writer carries forward, kept apart from the rest of the
59
59
  # dependency descriptor so it knows which rows are which; their entries are the same objects the descriptor
60
60
  # holds, so `Marshal` writes them once. `baseline` and `pinned` hold the rows the last full run recorded for
61
61
  # inputs the incremental path does not re-derive on a recheck (the signature tree, the