rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -39,12 +39,10 @@ module Rigor
39
39
  # lexical receiver class equals or inherits from this constraint.
40
40
  # - `method_name` — Symbol naming the DSL method (e.g. `:devise`).
41
41
  # - `symbol_arg_position` — `:rest` (all positional Symbol args are traits, slice 3a's only supported
42
- # form) or a non-negative Integer (the index of a single trait symbol — reserved for future shapes;
43
- # not yet honoured by the scanner).
44
- # - `modules_by_symbol` — Hash<Symbol, String>. Maps each recognised trait symbol to a fully-qualified
45
- # module name. Symbols not in the table fall through (silent skip; the scanner emits a
46
- # `macro.tier_b.unknown-trait` `:info` provenance marker per WD9 / WD13).
47
- # - `always_included` — Array<String>. Fully-qualified module names that are added to every call site
42
+ # form) or a non-negative Integer (the index of a single trait symbol).
43
+ # - `modules_by_symbol` — Maps each recognised trait symbol to a fully-qualified module name. Symbols not
44
+ # in the table are silently skipped.
45
+ # - `always_included` — Fully-qualified module names that are added to every call site
48
46
  # (even when no symbols match). Mirrors Devise's `always_include` modules.
49
47
  #
50
48
  # ## Floor / ceiling per ADR-16 WD13
@@ -53,7 +51,7 @@ module Rigor
53
51
  # methods into the existing `SyntheticMethodIndex` (slice 2b primitive). The synthesised methods adopt
54
52
  # the module's authored RBS return types — Tier B is NOT subject to the Tier C `Dynamic[T]` floor
55
53
  # because the source-of-truth (the module's authored RBS) is not a manifest-declared string. Per ADR-5
56
- # robustness, the substrate does not fabricate precision; it simply replays the modules's signatures.
54
+ # robustness, the substrate does not fabricate precision; it simply replays the module's signatures.
57
55
  #
58
56
  # **Out of scope for slice 3** (deferred follow-ups):
59
57
  # - `class_methods_module:` per-trait (Devise's `ClassMethods` extend-pattern); slice 3 covers instance
@@ -106,8 +104,7 @@ module Rigor
106
104
  end
107
105
 
108
106
  # @return fully-qualified module name for the given trait symbol, or nil when the
109
- # registry doesn't know the symbol (caller emits a tier_b.unknown-trait provenance marker and falls
110
- # through).
107
+ # registry doesn't know the symbol (the caller skips it).
111
108
  def module_for(symbol)
112
109
  modules_by_symbol[symbol.to_sym]
113
110
  end
@@ -168,9 +168,8 @@ module Rigor
168
168
  end
169
169
  private :assign_additional_initializers
170
170
 
171
- # ADR-103 WD2 / WD6 / WD10 / WD14 — the effect contract's five fields, assigned together and outside
172
- # `assign_fields` (which already carries the maximum positional arity).
173
- # The six `effect_*` fields validate together, so {#initialize} carries one call rather than six.
171
+ # ADR-103 WD2 / WD6 / WD10 / WD14 — the six `effect_*` fields validate together here, so {#initialize}
172
+ # carries one call rather than six.
174
173
  def validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
175
174
  effect_entry_points, effect_ancestry)
176
175
  validate_effect_root!(effect_root)
@@ -305,11 +304,15 @@ module Rigor
305
304
  FirstParty.bundled?(id)
306
305
  end
307
306
 
308
- # Whether the plugin contributes anything to the effect surfaces at all. The compiled per-run tables
309
- # skip a plugin that answers false, so a project whose plugins predate this contract pays nothing.
307
+ # Whether the manifest declares anything for the effect surfaces — a convenience predicate over the
308
+ # manifest-declared fields only, true exactly when a `Registry::Contribution` built from these
309
+ # declarations would not be empty (`Contribution#empty?` reads the same five fields,
310
+ # `effect_ancestry:` included). A plugin that overrides the `Base#effect_*` readers instead of
311
+ # declaring rows here (rigor-sidekiq) can contribute while this answers false. The compiled
312
+ # per-run tables filter on `Contribution#empty?` themselves; nothing consults this predicate.
310
313
  def effects?
311
314
  !effect_labels.empty? || !effect_attributions.empty? || !effect_edges.empty? ||
312
- !effect_entry_points.empty?
315
+ !effect_entry_points.empty? || !effect_ancestry.empty?
313
316
  end
314
317
 
315
318
  def ==(other)
@@ -475,10 +478,9 @@ module Rigor
475
478
  end
476
479
  end
477
480
 
478
- # ADR-13 slice 2 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the
479
- # parser consults (in slice 3) when an RBS::Extended payload's named- or generic-type head misses the
480
- # built-in registry. Slice 2 carries the declarations on the manifest and the registry exposes them in
481
- # registration order; the parser integration that actually drives the chain lands in slice 3.
481
+ # ADR-13 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the parser
482
+ # consults when an RBS::Extended payload's named- or generic-type head misses the built-in registry. The
483
+ # registry exposes them in registration order, and `Environment#build_name_scope` builds the chain.
482
484
  def validate_type_node_resolvers!(resolvers)
483
485
  validate_array_of!("type_node_resolvers", resolvers, "Rigor::Plugin::TypeNodeResolver instances") do |r|
484
486
  r.is_a?(TypeNodeResolver)
@@ -486,8 +488,7 @@ module Rigor
486
488
  end
487
489
 
488
490
  # ADR-16 slice 1a — `block_as_methods:` declares the Tier A substrate entries the plugin contributes.
489
- # Slice 1a carries the declarations on the manifest; the engine hook that actually narrows
490
- # `Scope#self_type` for matching blocks arrives in a subsequent slice.
491
+ # The engine hook that narrows `Scope#self_type` for matching blocks is `Inference::MacroBlockSelfType`.
491
492
  def validate_block_as_methods!(entries)
492
493
  validate_array_of!("block_as_methods", entries, "Rigor::Plugin::Macro::BlockAsMethod instances") do |e|
493
494
  e.is_a?(Macro::BlockAsMethod)
@@ -495,8 +496,7 @@ module Rigor
495
496
  end
496
497
 
497
498
  # ADR-16 slice 2a — `heredoc_templates:` declares the Tier C substrate entries (heredoc-template
498
- # synthesis on class-level DSL calls). Slice 2a carries the declarations on the manifest; the pre-pass +
499
- # `SyntheticMethodIndex` that actually emit synthetic methods arrive in slice 2b.
499
+ # synthesis on class-level DSL calls). `Inference::SyntheticMethodScanner` emits the synthetic methods.
500
500
  def validate_heredoc_templates!(entries)
501
501
  validate_array_of!("heredoc_templates", entries, "Rigor::Plugin::Macro::HeredocTemplate instances") do |e|
502
502
  e.is_a?(Macro::HeredocTemplate)
@@ -515,8 +515,8 @@ module Rigor
515
515
  end
516
516
 
517
517
  # ADR-16 slice 3a — `trait_registries:` declares the Tier B substrate entries (trait-inlining via
518
- # bundled module registry). Slice 3a carries the declarations on the manifest; the scanner + per-method
519
- # explosion through `SyntheticMethodIndex` (slice 2b primitive) arrives in slice 3b.
518
+ # bundled module registry). `Inference::SyntheticMethodScanner#collect_trait_registries` explodes them
519
+ # per method through `SyntheticMethodIndex`.
520
520
  def validate_trait_registries!(entries)
521
521
  validate_array_of!("trait_registries", entries, "Rigor::Plugin::Macro::TraitRegistry instances") do |e|
522
522
  e.is_a?(Macro::TraitRegistry)
@@ -4,6 +4,7 @@ require_relative "blueprint"
4
4
  # Issue #806 — {Registry#guarded_manifest} constructs one. `Rigor::Plugin::LoadError` MUST be defined by
5
5
  # then: an unresolved bare `LoadError` in this lexical scope silently falls back to Ruby's own ::LoadError.
6
6
  require_relative "load_error"
7
+ require_relative "../typing_state"
7
8
 
8
9
  module Rigor
9
10
  module Plugin
@@ -20,6 +21,10 @@ module Rigor
20
21
  # exactly the order the ungated walk produced — diagnostics stay byte-identical. The receiver-class
21
22
  # ancestry match for `dynamic_return` still happens per-dispatch inside `Plugin::Base#dynamic_return_type`.
22
23
  class ContributionIndex
24
+ TypingState.memo_field(self, :@owns_receiver_memo,
25
+ reason: "keyed on the environment's identity and the class name; the plugins' " \
26
+ "owns_receivers set is fixed at construction")
27
+
23
28
  # Ordered (registry-order) subsets relevant to each collector, and the membership sets used to gate the
24
29
  # paths within a plugin. `for_file_diagnostics` is the subset the runner's per-file diagnostic loop
25
30
  # visits: plugins overriding `#diagnostics_for_file` or declaring at least one `node_rule`.
@@ -103,8 +108,8 @@ module Rigor
103
108
  # The categorisation sets + the ordered per-collector subsets.
104
109
  def compile_memberships(plugins)
105
110
  plugins.each { |p| reject_legacy_flow_hook!(p) }
106
- @dynamic = plugins.reject { |p| p.class.dynamic_returns.empty? }.to_set
107
- @narrowing_facts = plugins.reject { |p| p.class.narrowing_facts_rules.empty? }.to_set
111
+ @dynamic = plugins.reject { |p| p.class.dynamic_returns.empty? }.to_set.freeze
112
+ @narrowing_facts = plugins.reject { |p| p.class.narrowing_facts_rules.empty? }.to_set.freeze
108
113
  compile_collector_subsets(plugins)
109
114
  end
110
115
 
@@ -214,10 +219,16 @@ module Rigor
214
219
  # tie-breaker.
215
220
  #
216
221
  # ADR-15 Phase 3 — alongside the instantiated `plugins`, the registry carries `blueprints`: a frozen,
217
- # Ractor-shareable `Array<Blueprint>` that records how to re-instantiate the same plugin set in a worker
222
+ # Ractor-shareable list of {Blueprint} that records how to re-instantiate the same plugin set in a worker
218
223
  # Ractor. The eventual Phase 4 pool ships `blueprints` across the boundary and calls {.materialize}
219
224
  # per-Ractor; the live `plugins` carriage on the coordinator registry stays unchanged.
220
225
  class Registry
226
+ TypingState.memo_field(self, :@contracts_by_path, :@effect_memo,
227
+ reason: "keyed on the path, and on the one effect-contribution compile; the contract " \
228
+ "set, glob semantics and plugin set are fixed for the run (ADR-52 WD1), and the " \
229
+ "compile, which may read project facts across the I/O boundary, rests on the " \
230
+ "run's stable-filesystem premise, as FileDigest's run tables do")
231
+
221
232
  attr_reader :plugins, :load_errors, :blueprints, :contribution_index, :resolved_gem_paths
222
233
 
223
234
  # @param plugins — instantiated plugin instances in deterministic order.
@@ -556,8 +567,8 @@ module Rigor
556
567
  # Issue #806 — {#safe_manifest} with a receipt. The construction-time aggregates degraded silently
557
568
  # when a manifest read raised, so a plugin that contributed nothing looked identical to a plugin that
558
569
  # was never asked; the load-error channel is where "a plugin could not be loaded" already lives, and
559
- # `Analysis::Runner` turns each entry into one `plugin_loader.load-error` row naming the plugin. The
560
- # plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
570
+ # `Analysis::Runner` turns each entry into one `load-error` row (source family `plugin_loader`) naming
571
+ # the plugin. The plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
561
572
  def guarded_manifest(plugin, errors)
562
573
  plugin.manifest
563
574
  rescue StandardError => e
@@ -25,9 +25,8 @@ module Rigor
25
25
  # documents the trust model the boundary enforces.
26
26
  #
27
27
  # ADR-9 slice 2 adds `fact_store`: the per-run cross-plugin `Plugin::FactStore`. Producer plugins publish
28
- # their facts in `#prepare(services)` (slice 3); consumer plugins read in `#diagnostics_for_file` via
29
- # `services.fact_store.read(...)`. A fresh `FactStore` instance is constructed per Services when none is
30
- # supplied — the runner threads its own instance in once slice 3 wires `#prepare` invocation.
28
+ # their facts in `#prepare(services)`; consumer plugins read in `#diagnostics_for_file` via
29
+ # `services.fact_store.read(...)`. A fresh `FactStore` is constructed when none is supplied.
31
30
  class Services
32
31
  attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy, :fact_store
33
32
 
@@ -61,6 +61,17 @@ module Rigor
61
61
  def empty?
62
62
  @entries.empty?
63
63
  end
64
+
65
+ # The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
66
+ def snapshot
67
+ entries
68
+ end
69
+
70
+ # Replays one worker's {#snapshot}, `kind:` (#824) and `line:` (#1075) included; the dedup above
71
+ # collapses every worker's copy. Nil records nothing.
72
+ def absorb(snapshot)
73
+ Array(snapshot).each { |entry| record(**entry.to_h) }
74
+ end
64
75
  end
65
76
  end
66
77
  end
@@ -19,8 +19,8 @@ module Rigor
19
19
  # - `trusted_gems`: gem names the user has authorised. Derived from the `plugins:` section of `.rigor.yml`
20
20
  # plus any gems they reach transitively. Used today for documentation and future trust diagnostics.
21
21
  # - `allowed_read_roots`: absolute paths plugin code may read from through the {IoBoundary}. The default
22
- # set covers the project root, the project's `signature_paths`, the active `Gemfile.lock`, and each
23
- # trusted gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
22
+ # set covers the project root, the project's `signature_paths`, and each trusted
23
+ # gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
24
24
  # `plugins_io.allowed_paths:`.
25
25
  # - `network_policy`: one of {VALID_NETWORK_POLICIES}. `:disabled` (default) makes
26
26
  # {IoBoundary#open_url} always raise. `:allowlist` (v0.1.2) consults `allowed_url_hosts` on every fetch —
@@ -45,6 +45,52 @@ module Rigor
45
45
  freeze
46
46
  end
47
47
 
48
+ # Builds the run's policy from the project configuration — the one derivation `rigor check`
49
+ # ({Analysis::Runner}), pooled workers ({Analysis::WorkerSession}) and `rigor unused`'s plugin roots
50
+ # ({Analysis::Reachability::PluginRoots}) share, so every `Plugin::Services` container enforces the
51
+ # same scope (#2586).
52
+ #
53
+ # `trusted_gems` is the gem-name half of every `Configuration#plugins` entry. `allowed_read_roots`
54
+ # is the project root (CWD), every `signature_paths` entry, each trusted gem's
55
+ # `Gem.loaded_specs` `full_gem_path` (silently skipped when the gem has no loaded spec), and every
56
+ # `plugins_io.allowed_paths` entry. `network_policy` / `allowed_url_hosts` come from
57
+ # `plugins_io.network` / `plugins_io.allowed_url_hosts`.
58
+ def self.for_configuration(configuration)
59
+ trusted_gems = configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
60
+ roots = [Dir.pwd]
61
+ Array(configuration.signature_paths).each { |sp| roots << File.absolute_path(sp) }
62
+ trusted_gems.each do |gem_name|
63
+ path = trusted_gem_root(gem_name)
64
+ roots << path if path
65
+ end
66
+ configuration.plugins_io_allowed_paths.each { |p| roots << File.absolute_path(p) }
67
+
68
+ new(
69
+ trusted_gems: trusted_gems,
70
+ allowed_read_roots: roots,
71
+ network_policy: configuration.plugins_io_network,
72
+ allowed_url_hosts: configuration.plugins_io_allowed_url_hosts
73
+ )
74
+ end
75
+
76
+ def self.trusted_gem_name(entry)
77
+ case entry
78
+ when String then entry
79
+ when Hash then entry["gem"] || entry["id"]
80
+ end
81
+ end
82
+ private_class_method :trusted_gem_name
83
+
84
+ def self.trusted_gem_root(gem_name)
85
+ return nil if gem_name.nil? || gem_name.empty?
86
+
87
+ spec = Gem.loaded_specs[gem_name]
88
+ spec&.full_gem_path
89
+ rescue StandardError
90
+ nil
91
+ end
92
+ private_class_method :trusted_gem_root
93
+
48
94
  # @return true when the absolute path falls inside any allowed read root. Symlinks are
49
95
  # resolved through `File.absolute_path` only (no `realpath`); plugins with adversarial intent are out of
50
96
  # scope per ADR-2.
@@ -28,10 +28,8 @@ module Rigor
28
28
  # Resolvers SHOULD be stateless and re-entrant; the registry builds the chain once per
29
29
  # `Analysis::Runner.run` and may consult any resolver multiple times for the same node.
30
30
  class TypeNodeResolver
31
- # @param node — the parser-emitted node the chain is
32
- # asking about.
33
- # @param scope — companion value object (slice 3); slice 2 invocations MAY pass
34
- # `nil` because the chain doesn't exist yet.
31
+ # @param node — the parser-emitted node the chain is asking about.
32
+ # @param scope — the scope the chain was invoked with.
35
33
  # @return resolved type, or `nil` to fall through.
36
34
  def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
37
35
  nil
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
@@ -215,7 +215,10 @@ module Rigor
215
215
  end,
216
216
  "patched_overrides" => project_scan.project_patched_methods.overrides.map do |key, visibility|
217
217
  [*key, visibility].map(&:to_s)
218
- end
218
+ end,
219
+ "patched_singleton_copies" => project_scan.project_patched_methods.singleton_copies.map do |owner, name|
220
+ [owner.to_s, name.to_s]
221
+ end.sort
219
222
  }
220
223
  Digest::SHA256.hexdigest(JSON.generate(payload))
221
224
  rescue StandardError
@@ -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