rigortype 0.4.6 → 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 (417) 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 +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
data/lib/rigor/plugin.rb CHANGED
@@ -21,14 +21,12 @@ require_relative "plugin/first_party"
21
21
  module Rigor
22
22
  module Plugin
23
23
  @registered = {}
24
- # ADR-88 WD4b — process-global map `gem name (String) => [registered plugin id, ...]`, recorded by the
25
- # loader the FIRST time a gem's `require` runs its body and registers plugins. A second in-process
26
- # `Loader.load` of the same bare-string (`id:`-less) entry sees `require` return false (the gem is already
27
- # loaded, so its `Rigor::Plugin.register` calls do not re-run) and an empty newly-registered delta — the
28
- # loader recovers the gem's plugin id(s) from this memo instead of failing with "did not register any
29
- # plugin". Populated by {.record_gem_registration}, read by {.ids_for_gem}. This is what makes a second
30
- # in-process analysis (the `--verify-incremental` full-run oracle, the incremental session's subset
31
- # re-analysis, the LSP's re-loads) load the same plugins the first run did.
24
+ # ADR-88 WD4b — process-global map `gem name (String) => [plugin id, ...]`, recorded by the loader on a gem's
25
+ # first `require`. A second in-process `Loader.load` of the same bare-string entry sees `require` return false
26
+ # and an empty newly-registered delta; the loader then recovers the ids from this memo instead of failing with
27
+ # "did not register any plugin". Populated by {.record_gem_registration}, read by {.ids_for_gem}. This is what
28
+ # lets a second in-process analysis (the `--verify-incremental` oracle, the incremental subset re-analysis,
29
+ # the LSP's re-loads) load the same plugins the first run did.
32
30
  @gem_registrations = {}
33
31
  @mutex = Mutex.new
34
32
 
@@ -26,7 +26,11 @@ module Rigor
26
26
  "rails" => %w[
27
27
  actioncable actionmailer actionpack actiontext actionview activejob activemodel
28
28
  activerecord activestorage activesupport railties
29
- ].freeze
29
+ ].freeze,
30
+ # `rspec` and `rspec-rails` are the same shape: a project names the umbrella and never `rspec-core`, the
31
+ # gem rigor-rspec targets. rspec-rails depends on the three rspec gems listed here.
32
+ "rspec" => %w[rspec-core rspec-expectations rspec-mocks].freeze,
33
+ "rspec-rails" => %w[rspec-core rspec-expectations rspec-mocks].freeze
30
34
  }.freeze
31
35
 
32
36
  class << self
@@ -72,6 +72,9 @@ module Rigor
72
72
  required_features: features,
73
73
  **dependency_discovery_options(configuration)
74
74
  )
75
+ rescue Environment::CoreRootError
76
+ # Issue #2517 — an unusable selected core is the user's to fix, never a reason to type against the default.
77
+ raise
75
78
  rescue StandardError
76
79
  bare(configuration, features)
77
80
  end
@@ -111,8 +114,15 @@ module Rigor
111
114
  # The dependency-discovery axes `check` reads off the configuration, as a keyword hash. Extracted so the
112
115
  # list lives in one place: every command that types against the project's real dependencies passes exactly
113
116
  # these, and adding an axis to `Environment.for_project` means adding it here once.
117
+ #
118
+ # Issue #2517 — `core_root:` rides here too although it is not discovered: it is the other half of the RBS
119
+ # universe `check` reads off the configuration, and every site that builds one already passes this hash.
114
120
  def dependency_discovery_options(configuration)
115
121
  {
122
+ core_root: configuration.core_root,
123
+ paths: configuration.paths,
124
+ exclude_patterns: configuration.exclude_patterns,
125
+ exclude_base: configuration.exclude_base,
116
126
  bundler_bundle_path: configuration.bundler_bundle_path,
117
127
  bundler_auto_detect: configuration.bundler_auto_detect,
118
128
  bundler_lockfile: configuration.bundler_lockfile,
@@ -166,17 +176,21 @@ module Rigor
166
176
  required_features: features,
167
177
  **dependency_discovery_options(configuration)
168
178
  )
179
+ rescue Environment::CoreRootError
180
+ raise
169
181
  rescue StandardError
170
182
  minimal(configuration, features)
171
183
  end
172
184
 
173
185
  # The last floor: RBS core plus the project's own signature paths, which cannot fail on anything outside
174
- # the project itself.
186
+ # the project itself. The core is still the selected one (issue #2517): a floor that dropped it would type
187
+ # the project against APIs its Ruby does not have.
175
188
  def minimal(configuration, features = [])
176
189
  Environment.for_project(
177
190
  libraries: configuration.libraries,
178
191
  signature_paths: configuration.signature_paths,
179
- required_features: features
192
+ required_features: features,
193
+ core_root: configuration.core_root
180
194
  )
181
195
  end
182
196
  end
@@ -8,7 +8,7 @@ module Rigor
8
8
  # nothing about the code that was analysed.
9
9
  #
10
10
  # A named class, not a bare RuntimeError: {MutationScanner#classify} already folds a per-mutant
11
- # `StandardError` into the `:harness_error` bucket, and the scanner's file-level rescue must catch THIS
11
+ # `StandardError` into the `harness_errors` bucket, and the scanner's file-level rescue must catch THIS
12
12
  # and nothing else, so the two failure modes stay distinguishable at every site that handles either.
13
13
  #
14
14
  # Issue #790 — it says WHICH of the two refusals below fired, because they want different recovery. A
@@ -4,6 +4,7 @@ require_relative "../inference/parameter_inference_collector"
4
4
  require_relative "../inference/scope_indexer"
5
5
  require_relative "../inference/project_seed_tables"
6
6
  require_relative "../cache/incremental_snapshot"
7
+ require_relative "../cache/file_digest"
7
8
  require_relative "../analysis/path_expansion"
8
9
  require_relative "../analysis/buffer_binding"
9
10
 
@@ -107,11 +108,40 @@ module Rigor
107
108
  paths = Analysis::PathExpansion.configured_files(configuration)
108
109
  return nil if paths.empty?
109
110
 
110
- bundles = snapshot_bundles(configuration)
111
+ payload = snapshot_payload(configuration)
112
+ bundles = payload&.seed_bundles
111
113
  return nil if bundles.nil? || bundles.empty?
112
114
 
113
- tables_for_buffer(paths: respell_for_buffer(paths, buffer), bundles: bundles, buffer: buffer,
114
- constant_sources: true)
115
+ spelled = respell_for_buffer(paths, buffer)
116
+ tables = tables_for_buffer(paths: spelled, bundles: bundles, buffer: buffer, constant_sources: true)
117
+ with_snapshot_param_table(tables, payload.param_table, configuration,
118
+ paths: spelled, bundles: bundles, buffer: buffer)
119
+ end
120
+
121
+ # Issue #2282 — under `parameter_inference: true` the snapshot also carries the call-site parameter table the
122
+ # writing run seeded its analysis from (`param_table`, ADR-67 WD6c), so a probe reads `@v = v` as `check`
123
+ # does. The table is whole-project (a callee's parameter type comes from callers in other files), so it is
124
+ # only as fresh as every file: it rides along ONLY when the fold re-walked nothing, that is, every path is a
125
+ # snapshot bundle with an unchanged digest and no path was added, removed or replaced by the editor buffer.
126
+ # Otherwise it is dropped, as {Analysis::IncrementalSession#fresh_param_table} recomputes rather than reuses
127
+ # it. Absent when the gate is off or the table is empty, which keeps the bundle-only seed.
128
+ def with_snapshot_param_table(tables, param_table, configuration, paths:, bundles:, buffer:)
129
+ return tables unless configuration.parameter_inference
130
+ return tables if param_table.nil? || param_table.empty?
131
+ return tables unless snapshot_current?(paths, bundles, buffer)
132
+
133
+ tables.merge(param_inferred_types: param_table).freeze
134
+ end
135
+
136
+ def snapshot_current?(paths, bundles, buffer)
137
+ return false unless paths.size == bundles.size
138
+
139
+ paths.all? do |path|
140
+ cached = bundles[path]
141
+ cached && cached[:digest] == Cache::FileDigest.hexdigest(buffer ? buffer.resolve(path) : path)
142
+ end
143
+ rescue SystemCallError
144
+ false
115
145
  end
116
146
 
117
147
  # The configured file list with the entry that reaches `buffer`'s file respelled as the buffer's logical
@@ -128,12 +158,21 @@ module Rigor
128
158
  end
129
159
  end
130
160
 
131
- def snapshot_bundles(configuration)
161
+ # The bare single-file scope, or that scope with `seed`'s tables folded in. The one place `rigor type-of` and the
162
+ # language server's editor queries (#2282) fold a seed into a scope.
163
+ def seeded_scope(environment:, seed:)
164
+ base = Scope.empty(environment: environment)
165
+ return base if seed.nil? || seed.empty?
166
+
167
+ base.with_discovery(base.discovery.with(**seed))
168
+ end
169
+
170
+ def snapshot_payload(configuration)
132
171
  fingerprint = Cache::IncrementalSnapshot.fingerprint(configuration: configuration, roots: configuration.paths)
133
172
  return nil if fingerprint.nil?
134
173
 
135
174
  loaded = Cache::IncrementalSnapshot.new(root: configuration.cache_path).load_any(fingerprints: [fingerprint])
136
- loaded&.last&.seed_bundles
175
+ loaded&.last
137
176
  end
138
177
 
139
178
  # The whole-project discovery tables, from the single-walk combined pass (one parse per file) that
@@ -138,7 +138,7 @@ module Rigor
138
138
  QUOTES = ['"', "'"].freeze
139
139
  # Mutating an argument to a universal-equality method is always an equivalent mutant: Ruby's `==` / `<=>`
140
140
  # family returns false / nil on a type mismatch rather than raising, so the engine exempts them
141
- # (`UNIVERSAL_EQUALITY_METHODS`). Skip them to keep survivors meaningful.
141
+ # (`CheckRules::UNIVERSAL_EQUALITY_METHODS`; keep the two in step). Skip them to keep survivors meaningful.
142
142
  UNIVERSAL_EQUALITY = %w[== != eql? equal? <=>].freeze
143
143
 
144
144
  # Every operator the mutator knows. Each maps to the diagnostic rule family it is *engineered* to trip when
@@ -52,11 +52,9 @@ module Rigor
52
52
  # `.rbs` files, plus the virtual entries rbs-inline and plugin `source_rbs` synthesis contribute.
53
53
  # @param registry — the vocabulary an unknown label is judged against.
54
54
  #
55
- # The `ANNOTATION_HINT` routing test runs here, before any parse: a source with no honoured
56
- # payload can contribute neither an envelope nor an unresolved report, so a signature tree with
57
- # no effect annotation is answered by one regex per file — which is what that hint's own
58
- # documentation promises, and what every warm envelope-lane run was paying a full
59
- # `RBS::Parser.parse_signature` per file for.
55
+ # A source with no honoured payload can contribute neither an envelope nor an unresolved report, so the
56
+ # `ANNOTATION_HINT` test skips it before any parse: one regex per file, not a full
57
+ # `RBS::Parser.parse_signature`.
60
58
  def scan(sources:, registry:)
61
59
  reporter = Reporter.new
62
60
  methods = {}
@@ -128,7 +126,7 @@ module Rigor
128
126
  def walk(decl, prefix, methods, classes, registry, reporter)
129
127
  return unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
130
128
 
131
- nested = prefix + [decl.name.to_s.sub(/\A::/, "")]
129
+ nested = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
132
130
  class_name = nested.join("::")
133
131
  envelope = RbsExtended.read_effect_envelope(
134
132
  decl.annotations, owner_key: class_name, source: :class_annotation,
@@ -287,17 +287,13 @@ module Rigor
287
287
  source_location.respond_to?(:start_line) ? source_location.start_line : nil
288
288
  end
289
289
 
290
- # Issue #785 — routes a declined directive to the per-run reporter. The FIRST arm is the one that
291
- # matters: the production reporter is {RbsExtended::Reporter}, whose surface is `record_unresolved` /
292
- # `record_lossy_projection` / `record_hkt_error` — it has neither `#record` nor `#<<`, so before this
293
- # arm existed every call here fell off the end of the `if` and every malformed HKT directive was
294
- # dropped in every real run, while the doc comments claimed an `:info` entry was recorded. The
295
- # position is flattened at record time through {RbsExtended::Reporter.position_of}, the one place
296
- # every stream flattens through since #805, because the entry has to reach the pool drain channel as
297
- # primitives (see {RbsExtended::Reporter}).
290
+ # Issue #785 — routes a declined directive to the per-run reporter. The first arm matters: the production
291
+ # {RbsExtended::Reporter} has `record_hkt_error` and neither `#record` nor `#<<`, so before this arm existed
292
+ # every malformed HKT directive was dropped in real runs. The position is flattened at record time through
293
+ # {RbsExtended::Reporter.position_of} (see {RbsExtended::Reporter} on why the entry carries primitives).
298
294
  #
299
- # The `#record` / `#<<` arms stay for the collecting doubles the directive specs and plugin authors
300
- # use, which is the contract `scan_rbs_loader`'s `@param reporter [#record, nil]` documents.
295
+ # The `#record` / `#<<` arms serve the collecting doubles the directive specs and plugin authors use; the
296
+ # contract is the one `scan_rbs_loader` documents for its `reporter` parameter.
301
297
  def record_hkt_error(reporter, message, source_location)
302
298
  return if reporter.nil?
303
299
 
@@ -48,6 +48,16 @@ module Rigor
48
48
  HktDirectiveEntry = Data.define(:message, :path, :line, :column)
49
49
  DeprecatedFormEntry = Data.define(:payload, :replacement, :path, :line, :column)
50
50
 
51
+ # Every stream, by its reader, with the record method that replays one of its entries: an entry's
52
+ # members are that method's keywords. {#snapshot} and {#absorb} walk this table, so a new stream is
53
+ # declared here and nowhere else in the pool drain (ADR-116 WD3).
54
+ STREAMS = {
55
+ unresolved_payloads: :record_unresolved,
56
+ lossy_projections: :record_lossy_projection,
57
+ hkt_directive_errors: :record_hkt_error,
58
+ deprecated_forms: :record_deprecated_form
59
+ }.freeze
60
+
51
61
  # Flattens an `RBS::Location` (or anything answering the same readers) to the `(path, line, column)`
52
62
  # triple every entry carries. `column` is 1-based, since `RBS::Location#start_column` is 0-based and
53
63
  # diagnostics are not. Each component is `nil` when the location cannot supply it, and the diagnostic
@@ -160,9 +170,23 @@ module Rigor
160
170
  # True when no events have accumulated. Used by callers that want to skip the diagnostic-emission pass
161
171
  # entirely on the common no-event path.
162
172
  def empty?
163
- @mutex.synchronize do
164
- @unresolved_payloads.empty? && @lossy_projections.empty? && @hkt_directive_errors.empty? &&
165
- @deprecated_forms.empty?
173
+ STREAMS.each_key.all? { |stream| public_send(stream).empty? }
174
+ end
175
+
176
+ # The drain a pool worker hands back: every stream's frozen entries by reader name. Marshal-clean and
177
+ # shareable, since each entry carries `(path, line, column)` primitives (#785, #805).
178
+ def snapshot
179
+ STREAMS.each_key.to_h { |stream| [stream, public_send(stream)] }
180
+ end
181
+
182
+ # Replays one worker's {#snapshot} through the record methods, whose dedup collapses the copies every
183
+ # worker hands over to the single row a `--workers=0` run prints. A missing stream (an older drain
184
+ # shape) or a nil snapshot records nothing.
185
+ def absorb(snapshot)
186
+ return if snapshot.nil?
187
+
188
+ STREAMS.each do |stream, record|
189
+ Array(snapshot[stream]).each { |entry| public_send(record, **entry.to_h) }
166
190
  end
167
191
  end
168
192
 
@@ -513,7 +513,7 @@ module Rigor
513
513
  Type::Nominal.new(normalized)
514
514
  end
515
515
 
516
- # Returned for `rigor:v1:param: <name> <refinement>`. The parameter name is a Ruby identifier (Symbol); the
516
+ # Returned for `rigor:v1:param: <name> <refinement>`. The parameter name is a Ruby identifier; the
517
517
  # type is any `Rigor::Type` the refinement parser resolves (bare kebab-case name, parameterised form, or
518
518
  # `Integer[a..b]` range, or the deprecated `int<a, b>` — the same grammar the `return:` directive accepts).
519
519
  ParamOverride = Data.define(:param_name, :type)
@@ -554,7 +554,7 @@ module Rigor
554
554
  end
555
555
 
556
556
  # Convenience reader for call sites that want to look up a single override by parameter name. Returns a
557
- # frozen Hash<Symbol, Rigor::Type>; missing keys mean "use the RBS-declared type". Callers MUST treat the
557
+ # frozen map from parameter name to type; missing keys mean "use the RBS-declared type". Callers MUST treat the
558
558
  # hash as read-only.
559
559
  def param_type_override_map(method_def, environment: nil)
560
560
  overrides = read_param_type_overrides(method_def, environment: environment)
@@ -728,7 +728,6 @@ module Rigor
728
728
  NO_EFFECT_LABELS = [].freeze
729
729
  private_constant :NO_EFFECT_LABELS
730
730
 
731
- # Whether `string` is the bare `%a{pure}` annotation.
732
731
  def pure_annotation?(string)
733
732
  !string.nil? && PURE_ANNOTATION_PATTERN.match?(string)
734
733
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../run_scoped_slots"
4
+
3
5
  module Rigor
4
6
  # {Reflection}'s constant-ancestor walk ([#354](https://github.com/rigortype/rigor/issues/354)), split out for
5
7
  # the reader rather than for the loader: the whole-spelling ladder's step 2 (`reflection.rb`) and the
@@ -8,11 +10,11 @@ module Rigor
8
10
  # keeps the walk behind the facade its callers already read.
9
11
  module Reflection
10
12
  # #354 — thread-local slot for the per-run ancestor-scope memo. See {.ancestor_constant_scopes}.
11
- ANCESTOR_SCOPES_KEY = :__rigor_ancestor_constant_scopes__
13
+ ANCESTOR_SCOPES_KEY = RunScopedSlots.register(:__rigor_ancestor_constant_scopes__)
12
14
  private_constant :ANCESTOR_SCOPES_KEY
13
15
 
14
16
  # The recording run's slot. See {.recorded_ancestor_constant_scopes}.
15
- RECORDED_ANCESTOR_SCOPES_KEY = :__rigor_recorded_ancestor_constant_scopes__
17
+ RECORDED_ANCESTOR_SCOPES_KEY = RunScopedSlots.register(:__rigor_recorded_ancestor_constant_scopes__)
16
18
  private_constant :RECORDED_ANCESTOR_SCOPES_KEY
17
19
 
18
20
  module_function
@@ -228,7 +230,7 @@ module Rigor
228
230
  end
229
231
  private_class_method :ancestry_unknown_before?
230
232
 
231
- # Ruby places these after every entry of a chain, so a constant they own is the top level's (step 4).
233
+ # Ruby places these after every entry of a chain, so a constant they own is the top level's.
232
234
  IMPLICIT_ANCESTORS = %w[Object Kernel BasicObject].freeze
233
235
  private_constant :IMPLICIT_ANCESTORS
234
236
 
@@ -20,7 +20,7 @@ module Rigor
20
20
 
21
21
  module_function
22
22
 
23
- # @param root — a directory path (String or Pathname), taken literally
23
+ # @param root — a directory path, taken literally
24
24
  # @param pattern — a `Dir.glob` pattern relative to `root`
25
25
  # @return the matches spelled under `root`, in `Dir.glob` order; empty when `root` is not a directory
26
26
  def glob(root, pattern)
@@ -32,5 +32,19 @@ module Rigor
32
32
  def literal(name)
33
33
  name.to_s.gsub(METACHARACTERS, "\\\\\\0")
34
34
  end
35
+
36
+ # A configured file path (#2222), absolute or relative to the working directory, as the `[root, pattern]` pair
37
+ # that `glob(root, pattern)` reads as exactly that file. A relative path stays under the working directory, as
38
+ # it always did. An absolute one is split into its own directory and a literal basename: `File.join(".", "/abs")`
39
+ # reads `./abs`, so an absolute path joined under `"."` matched nothing.
40
+ #
41
+ # @param path — a file path, taken literally
42
+ # @return `[root, pattern]`: `"."` and the literal path when relative, else the directory and the literal basename
43
+ def split_file(path)
44
+ path = path.to_s
45
+ return [".", literal(path)] unless File.absolute_path?(path)
46
+
47
+ [File.dirname(path), literal(File.basename(path))]
48
+ end
35
49
  end
36
50
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ # Issues #2412 and #2399 — the registry of per-thread memo slots whose contents belong to one analysis run. An
5
+ # owner registers its `Thread.current` key where it names it (`KEY = RunScopedSlots.register(:key)`), and each
6
+ # entry point that runs an analysis calls {.release} in its `ensure`, so a finished run, or one that raised,
7
+ # leaves none of its scopes, discovery tables or environment behind on the thread.
8
+ #
9
+ # A slot that holds its run until the next run overwrites it retains one run per thread. A slot whose value
10
+ # reaches its own key retains every run, as `LoopValueRecord`'s `ObjectSpace::WeakKeyMap` did: the map holds
11
+ # its values strongly, and a recorded scope reaches the parse tree that holds the key node. Releasing at run end
12
+ # bounds both. Inside a run nothing changes: every memo keeps its key and its hit rate, and a slot read outside
13
+ # a run (a language-server hover on the main thread) refills on its first miss.
14
+ #
15
+ # Only memo slots belong here. A slot a `with_*` frame installs and restores around a block is already empty
16
+ # when the frame returns, and clearing it from inside a frame would break the frame.
17
+ #
18
+ # The registry is a module-level array filled at require time, so it is for the main Ractor only.
19
+ module RunScopedSlots
20
+ @keys = []
21
+
22
+ # Adds `key` to the registry and returns it, so the owner's constant keeps its value.
23
+ def self.register(key)
24
+ @keys << key unless @keys.include?(key)
25
+ key
26
+ end
27
+
28
+ # Every registered key, in registration order.
29
+ def self.keys = @keys.dup.freeze
30
+
31
+ # Empties every registered slot on the calling thread.
32
+ def self.release
33
+ thread = Thread.current
34
+ @keys.each { |key| thread[key] = nil }
35
+ nil
36
+ end
37
+ end
38
+ end
@@ -37,7 +37,7 @@ module Rigor
37
37
  # *remaining* deadline rather than restarting the window; see its own
38
38
  # comment for why that distinction is worth the bookkeeping.
39
39
  module Jit
40
- # Opt-out switch: `RIGOR_DISABLE_YJIT=1` disables both entry points.
40
+ # Opt-out switch: `RIGOR_DISABLE_YJIT=1` disables every entry point.
41
41
  DISABLE_ENV = "RIGOR_DISABLE_YJIT"
42
42
 
43
43
  # Advanced / calibration override of the {enable_after} deadline, in
@@ -61,6 +61,13 @@ module Rigor
61
61
  )
62
62
 
63
63
  class DiscoveryIndex
64
+ # Issue #2330 — identity as a hash key. `Data` hashes and compares structurally, which over these project-sized
65
+ # tables is both costly and wrong for a per-thread memo keyed by "this very index" (an `ObjectSpace::WeakKeyMap`
66
+ # holds the memo while the index lives and drops it with it). `==` stays structural.
67
+ def hash = object_id.hash
68
+
69
+ def eql?(other) = equal?(other)
70
+
64
71
  EMPTY_NODE_TABLE = {}.compare_by_identity.freeze
65
72
  EMPTY_TABLE = {}.freeze
66
73
  EMPTY_NAME_SET = Set.new.freeze
@@ -423,10 +430,9 @@ module Rigor
423
430
  struct_member_layouts: EMPTY_TABLE,
424
431
  # ADR-67 WD3 — the call-site parameter-inference table, keyed by `[class_name, method_name, kind]` (the
425
432
  # same `(class, method, kind)` triple {Inference::ParameterInferenceCollector} records and that
426
- # `build_method_entry_scope` reconstructs from the lexical class path). The value is a
427
- # `{param_name(Symbol) => Rigor::Type}` map of the union of resolved call-site argument types. Empty on
428
- # every normal run; only the `coverage --protection` collection pass populates it today, so a `check` run
429
- # leaves it empty and seeds nothing (byte-identical).
433
+ # `build_method_entry_scope` reconstructs from the lexical class path). The value is the
434
+ # union of resolved call-site argument types per parameter name. Empty unless `parameter_inference:` is
435
+ # on or the `coverage --protection` collection pass runs; a run with neither seeds nothing (byte-identical).
430
436
  param_inferred_types: EMPTY_TABLE,
431
437
  # ADR-84 WD2 — the run-scope identity token `Analysis::Runner#run_analysis` mints per run (a frozen
432
438
  # bare Object) and seeds through `project_scope_seed_tables`. The user-method return memo keys its
@@ -474,7 +480,6 @@ module Rigor
474
480
  # possible fact carries, and a Runner's initial state. Copy it (`dup`) before mutating.
475
481
  EMPTY_SIBLINGS = SIBLINGS.each_value.to_h { |sibling| [sibling, EMPTY.public_send(sibling)] }.freeze
476
482
 
477
- # The all-empty value of a paired member or sibling.
478
483
  def self.empty_value(name)
479
484
  EMPTY.public_send(name)
480
485
  end
@@ -499,7 +504,6 @@ module Rigor
499
504
  end
500
505
  end
501
506
 
502
- # Whether `name` is a member or sibling of {SIBLINGS}.
503
507
  def self.paired?(name)
504
508
  PARTNERS.key?(name)
505
509
  end
@@ -522,6 +526,15 @@ module Rigor
522
526
  recorded == kind || (!recorded.nil? && recorded == METHOD_KIND_BOTH)
523
527
  end
524
528
 
529
+ # Issue #681 / #707 — the `Module.nesting` the declaration walk recorded for `def_node`, or nil when it recorded
530
+ # none: {#discovered_def_nestings} first, then its rehydration for a def served from an ADR-85 seed bundle, whose
531
+ # node {Inference::DefNodeResolver} mints from its own parse ({Inference::DefNodeResolver.rehydrated_nesting}).
532
+ # The one reader of the table outside its owners; `ExpressionTyper#recorded_def_nesting` says why the order is
533
+ # safe.
534
+ def recorded_def_nesting(def_node)
535
+ discovered_def_nestings[def_node] || Inference::DefNodeResolver.rehydrated_nesting(def_node)
536
+ end
537
+
525
538
  # ADR-119 WD1 — whether `path` (`[class, name]`, or `[class, [kind, name]]` for an envelope) is a contested
526
539
  # entry of `member`, one of the single-valued members {SIBLINGS} pairs. Internal to the candidate-set read.
527
540
  def contested?(member, path)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../run_scoped_slots"
4
+
3
5
  module Rigor
4
6
  class Scope
5
7
  # ADR-24 (amended for #1567, #1568, #1570 and #1571) — Ruby's method resolution order for one project class or
@@ -494,7 +496,7 @@ module Rigor
494
496
  end
495
497
  private :compute_levels
496
498
 
497
- MEMO_KEY = :__rigor_resolution_chain__
499
+ MEMO_KEY = RunScopedSlots.register(:__rigor_resolution_chain__)
498
500
  private_constant :MEMO_KEY
499
501
 
500
502
  FLAVORS = %i[methods constants arity].freeze