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
@@ -62,6 +62,10 @@ module Rigor
62
62
  "disable" => [],
63
63
  "libraries" => [],
64
64
  "signature_paths" => nil,
65
+ # Issue #2517 — a core signature directory loaded IN PLACE OF the `rbs` gem's `core/` (a provider such as
66
+ # rbs-shifter writes one for an older CRuby). `nil` (the default) loads the gem's core. Unlike
67
+ # `signature_paths:`, which only adds, this replaces: the default core is not loaded beside it.
68
+ "core_root" => nil,
65
69
  # The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
66
70
  # whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
67
71
  # declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
@@ -77,10 +81,9 @@ module Rigor
77
81
  "inline_declared" => "write"
78
82
  },
79
83
  # ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
80
- # files that the analyzer walks before per-file inference so patched-method declarations are visible
81
- # across the project (e.g. `lib/core_ext/string_extensions.rb`). Slice 1 plumbing only — listed files
82
- # are validated at config-load time (`pre-eval.file-not-found` on a missing path), but the dispatcher
83
- # tier consuming the registry lands in slice 2.
84
+ # files (or globs) that the analyzer walks before per-file inference so patched-method declarations are
85
+ # visible across the project (e.g. `lib/core_ext/string_extensions.rb`). A missing literal path surfaces
86
+ # as `pre-eval.file-not-found`.
84
87
  "pre_eval" => [],
85
88
  # ADR-22 — baseline file path. nil (default) means no baseline is loaded; the `false` literal is
86
89
  # treated as the explicit-disable form for `.rigor.yml`-side override of an upstream `.rigor.dist.yml`
@@ -209,8 +212,8 @@ module Rigor
209
212
  # set up with `rbs collection install`, the resulting `rbs_collection.lock.yaml` carries the resolved
210
213
  # (gem, version, source) triples and `.gem_rbs_collection/` holds the downloaded `.rbs` files. Rigor
211
214
  # parses the lockfile and auto-feeds each gem's `<collection_root>/<name>/<version>/` directory into
212
- # `RbsLoader`'s `signature_paths:`. Sources of type `stdlib` are skipped because rigor's bundled
213
- # `DEFAULT_LIBRARIES` already covers that surface.
215
+ # `RbsLoader`'s `signature_paths:`. Sources of type `stdlib` install no directory; issue #2946 — they
216
+ # load as RBS libraries, appended to `DEFAULT_LIBRARIES + libraries:`, as the `rbs` CLI loads them.
214
217
  #
215
218
  # `lockfile:` (String, optional): explicit path to `rbs_collection.lock.yaml`. Resolved relative to
216
219
  # the project root when relative.
@@ -229,6 +232,11 @@ module Rigor
229
232
  PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
230
233
  private_constant :PATH_KEYS
231
234
 
235
+ # The single-path sibling of {PATH_KEYS}: resolved the same way, but kept a String rather than wrapped in an
236
+ # Array.
237
+ SCALAR_PATH_KEYS = %w[core_root].freeze
238
+ private_constant :SCALAR_PATH_KEYS
239
+
232
240
  # Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
233
241
  # § "Reserved namespaces" and ADR-99.
234
242
  #
@@ -271,7 +279,8 @@ module Rigor
271
279
 
272
280
  attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
273
281
  :cache_validation, :disabled_rules,
274
- :libraries, :signature_paths, :test_paths, :fold_platform_specific_paths, :parameter_inference,
282
+ :libraries, :signature_paths, :core_root, :test_paths, :fold_platform_specific_paths,
283
+ :parameter_inference,
275
284
  :plugins_isolation,
276
285
  :plugins_io_network, :plugins_io_allowed_paths,
277
286
  :plugins_io_allowed_url_hosts,
@@ -352,7 +361,8 @@ module Rigor
352
361
  # [PHPStan](https://phpstan.org/config-reference#paths).
353
362
  def self.load(path = nil)
354
363
  resolved = path || discover
355
- if resolved.nil? || !File.exist?(resolved)
364
+ loaded = resolved && File.exist?(resolved) ? resolved : nil
365
+ if loaded.nil?
356
366
  data = DEFAULTS
357
367
  effects_key_present = false
358
368
  target_ruby_key_present = false
@@ -361,13 +371,14 @@ module Rigor
361
371
  # `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote
362
372
  # `effects:`" and "the file wrote `effects: false`" collapse to the same value and become
363
373
  # indistinguishable to {#initialize}. This is the only place that distinction still exists.
364
- raw = load_with_includes(resolved)
374
+ raw = load_with_includes(loaded)
365
375
  effects_key_present = raw.key?("effects")
366
376
  target_ruby_key_present = raw.key?("target_ruby")
367
377
  data = DEFAULTS.merge(raw)
368
378
  end
369
379
  new(autowire_default_plugins(data), effects_key_present, target_ruby_key_present || false)
370
- .with_exclude_base(resolved && File.exist?(resolved) ? File.dirname(resolved) : nil)
380
+ .with_exclude_base(loaded && File.dirname(loaded))
381
+ .with_config_path(loaded)
371
382
  end
372
383
 
373
384
  # #1576 — the directory relative `exclude:` patterns are matched against: the directory of the config file in
@@ -376,6 +387,17 @@ module Rigor
376
387
  @base_dir || Dir.pwd
377
388
  end
378
389
 
390
+ # Issue #2742 — the path config-anchored diagnostics are positioned at: the loaded config file's path
391
+ # relative to the working directory (the project-root convention {Analysis::Baseline} uses), or
392
+ # `.rigor.yml` when no file was loaded. A `--config=config/rigor.yml` run names `config/rigor.yml`.
393
+ def config_display_path
394
+ return DEFAULT_PATH if @config_path.nil?
395
+
396
+ Pathname.new(@config_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
397
+ rescue ArgumentError
398
+ @config_path
399
+ end
400
+
379
401
  # ADR-93 WD2 — the one bundled plugin default-wired without a `plugins:` entry. `rigor-rbs-inline` is the
380
402
  # gem; `rbs-inline` is its manifest id (the loader raises on a duplicate id, so both forms count as
381
403
  # "already listed").
@@ -490,6 +512,7 @@ module Rigor
490
512
 
491
513
  out = data.dup
492
514
  PATH_KEYS.each { |key| resolve_path_key!(out, key, base_dir) }
515
+ SCALAR_PATH_KEYS.each { |key| resolve_scalar_path_key!(out, key, base_dir) }
493
516
  resolve_plugins_io_paths!(out, base_dir)
494
517
  out
495
518
  end
@@ -500,6 +523,12 @@ module Rigor
500
523
  out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
501
524
  end
502
525
 
526
+ def self.resolve_scalar_path_key!(out, key, base_dir)
527
+ return unless out.key?(key) && out[key].is_a?(String)
528
+
529
+ out[key] = ConfigPath.absolute(out[key], base_dir)
530
+ end
531
+
503
532
  def self.resolve_plugins_io_paths!(out, base_dir)
504
533
  plugins_io = out["plugins_io"]
505
534
  return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
@@ -582,6 +611,7 @@ module Rigor
582
611
  @libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
583
612
  sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
584
613
  @signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
614
+ @core_root = coerce_core_root(data.fetch("core_root", DEFAULTS.fetch("core_root")))
585
615
  test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
586
616
  @test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
587
617
  @sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
@@ -595,18 +625,13 @@ module Rigor
595
625
  # ADR-67 WD6a — resolve to a strict Boolean so a truthy non-`true` value (e.g. a stray String) does not
596
626
  # silently enable the gate; only the literal `true` activates the check-walk collector pre-pass.
597
627
  @parameter_inference = data.fetch("parameter_inference", DEFAULTS.fetch("parameter_inference")) == true
598
- # Resolved before `@effects` — ADR-103 WD15's "effects-on-by-default" reads {#bleeding_edge_active?},
599
- # which reads `@bleeding_edge_active_ids`.
600
- @bleeding_edge = coerce_bleeding_edge(
601
- data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge"))
602
- )
603
- @bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
604
- @bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
605
628
  # ADR-103 WD13 — presence, not truthiness: `effects:` written with no body parses as nil and still
606
- # means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads.
607
- @effects = coerce_effects(data, effects_key_present: effects_key_present)
608
- coerce_effects_snapshot(@effects)
609
- coerce_effects_policy(@effects)
629
+ # means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads. The raw
630
+ # input is kept so {#with_bleeding_edge} re-derives the effects block from the same source.
631
+ @effects_key_present = effects_key_present
632
+ @effects_written = Ractor.make_shareable(data.fetch("effects", false))
633
+ derive_bleeding_edge(data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge")))
634
+ derive_effects
610
635
  @cache_path = cache.fetch("path").to_s
611
636
  raw_max = cache.fetch("max_bytes")
612
637
  @cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max)
@@ -663,6 +688,10 @@ module Rigor
663
688
  def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
664
689
  {
665
690
  "target_ruby" => target_ruby,
691
+ # Issue #2742 — the loaded file's display path is part of the resolved configuration: two files
692
+ # with identical bodies still anchor their config diagnostics differently, so the run-cache and
693
+ # incremental fingerprints must tell them apart.
694
+ "config_path" => config_display_path,
666
695
  # A stated `target_ruby` turns on the deprecation rules a defaulted one leaves off (#1692), so the run cache
667
696
  # key and the incremental fingerprint must tell the two apart even where the value matches.
668
697
  "target_ruby_explicit" => target_ruby_explicit?,
@@ -672,6 +701,7 @@ module Rigor
672
701
  "disable" => disabled_rules,
673
702
  "libraries" => libraries,
674
703
  "signature_paths" => signature_paths,
704
+ "core_root" => core_root,
675
705
  # `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
676
706
  # incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
677
707
  # invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
@@ -752,15 +782,14 @@ module Rigor
752
782
  # It is a frozen `dup` with the bleeding-edge ivars re-set: `dup` returns an unfrozen shallow copy
753
783
  # (every other ivar is the receiver's deeply-frozen value, safe to share read-only), the replacements
754
784
  # are themselves deeply frozen, and the re-`freeze` keeps the result `Ractor.shareable?` for the worker
755
- # path. **Every ivar `#initialize` derives from `@bleeding_edge` has to be re-derived here** — the
756
- # selector, the severity map, and the active-id set the {#bleeding_edge_active?} predicate reads.
785
+ # path. **Every ivar `#initialize` derives from the bleeding-edge selector has to be re-derived here** — it
786
+ # calls the same {#derive_bleeding_edge} and {#derive_effects} that `#initialize` does, so the selector,
787
+ # the severity map, the active-id set and the `effects:` default (ADR-103 WD15's "effects-on-by-default")
788
+ # all follow the new value, and a derived ivar added to `#initialize` is picked up without a second edit.
757
789
  def with_bleeding_edge(value)
758
- selector = coerce_bleeding_edge(value)
759
790
  copy = dup
760
- copy.instance_variable_set(:@bleeding_edge, selector)
761
- copy.instance_variable_set(:@bleeding_edge_severity_overrides,
762
- BleedingEdge.severity_overrides_for(selector))
763
- copy.instance_variable_set(:@bleeding_edge_active_ids, BleedingEdge.active_ids_for(selector))
791
+ copy.send(:derive_bleeding_edge, value)
792
+ copy.send(:derive_effects)
764
793
  copy.freeze
765
794
  end
766
795
 
@@ -772,6 +801,16 @@ module Rigor
772
801
  copy.freeze
773
802
  end
774
803
 
804
+ # Issue #2742 — a sibling Configuration recording which file {Configuration.load} read, so the
805
+ # diagnostics anchored at "the configuration" name it ({#config_display_path}). `path` is the resolved
806
+ # config path; nil — no file loaded, or a bare {Configuration.new} — keeps the `.rigor.yml` default.
807
+ # Same frozen `dup` shape as {#with_exclude_base}.
808
+ def with_config_path(path)
809
+ copy = dup
810
+ copy.instance_variable_set(:@config_path, path && File.absolute_path(path).freeze)
811
+ copy.freeze
812
+ end
813
+
775
814
  # #1777 — a sibling Configuration with only `baseline:` unset, every other field (including the
776
815
  # explicitness of `target_ruby`) shared with the receiver. `rigor baseline generate` analyses with
777
816
  # this so the generating run sees exactly the configuration `rigor check` will, minus the filter it
@@ -794,19 +833,38 @@ module Rigor
794
833
  #
795
834
  # Same `dup` + re-`freeze` shape as {#with_bleeding_edge}: every other ivar is the receiver's deeply
796
835
  # frozen value, safe to share read-only, and the result stays `Ractor.shareable?` for the worker path.
836
+ # The implicit block is recorded as a written empty one, so a later {#with_bleeding_edge} re-derives
837
+ # it instead of falling back to the absent-key default and dropping it.
797
838
  def with_effects_enabled
798
839
  return self if effects_enabled?
799
840
 
800
841
  copy = dup
801
- copy.instance_variable_set(:@effects, {}.freeze)
802
- # The implicit block defaults like a written one. `rigor effects` emits no diagnostic either way,
803
- # but the two configurations must not differ in a field a later reader could branch on.
804
- copy.instance_variable_set(:@effects_check, true)
842
+ copy.instance_variable_set(:@effects_key_present, true)
843
+ copy.instance_variable_set(:@effects_written, {}.freeze)
844
+ copy.send(:derive_effects)
805
845
  copy.freeze
806
846
  end
807
847
 
808
848
  private
809
849
 
850
+ # ADR-50 § WD2 / ADR-103 WD15 — derives the bleeding-edge selector and the two ivars the
851
+ # {#bleeding_edge_active?} predicate and the severity resolution read. `#initialize` and
852
+ # {#with_bleeding_edge} both call this, so there is one derivation to keep in step.
853
+ def derive_bleeding_edge(value)
854
+ @bleeding_edge = coerce_bleeding_edge(value)
855
+ @bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
856
+ @bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
857
+ end
858
+
859
+ # ADR-103 WD13 / WD15 — derives the `effects:` block and its snapshot and policy ivars from the raw
860
+ # `effects:` input the instance kept (`@effects_written`, `@effects_key_present`). The effects-on-by-default
861
+ # default reads the active-id set, so {#derive_bleeding_edge} must run first.
862
+ def derive_effects
863
+ @effects = coerce_effects(@effects_written, effects_key_present: @effects_key_present)
864
+ coerce_effects_snapshot(@effects)
865
+ coerce_effects_policy(@effects)
866
+ end
867
+
810
868
  # ADR-103 WD13 — the `effects:` block, or nil when the key is absent or explicitly `false`. A
811
869
  # present-but-empty key (`effects:` alone, which YAML parses as nil) and `effects: {}` are the same
812
870
  # thing: collection on, every sub-key defaulted. A non-Hash body is normalised to `{}` rather than
@@ -818,8 +876,7 @@ module Rigor
818
876
  # as `effects: {}` when the "effects-on-by-default" bleeding-edge feature is adopted. An explicit
819
877
  # `effects: false` — `effects_key_present == true` — always stays off regardless of the feature, and a
820
878
  # written block always wins outright: the feature can only fill an *absence*.
821
- def coerce_effects(data, effects_key_present:)
822
- value = data.fetch("effects", false)
879
+ def coerce_effects(value, effects_key_present:)
823
880
  return {}.freeze if value == false && !effects_key_present && effects_forced_on_by_bleeding_edge?
824
881
  return nil if value == false
825
882
 
@@ -1054,7 +1111,7 @@ module Rigor
1054
1111
  end
1055
1112
  end
1056
1113
 
1057
- # `target_ruby` is passed to `Prism.parse_file(path, version:)` at the analyser's three parse sites
1114
+ # `target_ruby` is passed as Prism's `version:` option at the analyser's parse sites
1058
1115
  # (`Analysis::Runner`, `CLI::TypeOfCommand`, `CLI::TypeScanCommand`) so projects that target an older
1059
1116
  # Ruby get parse errors for syntax their target doesn't support. Format validation here is loose —
1060
1117
  # accepts any `<major>.<minor>` or `<major>.<minor>.<patch>` form, plus the literal `"latest"`. Prism
@@ -1097,6 +1154,18 @@ module Rigor
1097
1154
  # ADR-22 WD2 (b) — `baseline: <path>` activates the file; `baseline: false` is the explicit-disable form
1098
1155
  # (useful in `.rigor.yml` to override an upstream `.rigor.dist.yml` that names a baseline). `nil`
1099
1156
  # (default / absent key) is also "no baseline".
1157
+ # Issue #2517 — the directory's existence and contents are checked where the environment is built
1158
+ # ({Environment::CoreRoot.validate!}), not here: `rigor init`, `rigor doctor` and the language server's reload
1159
+ # load a configuration without building an environment, and must still be able to read one naming a core that
1160
+ # is not generated yet.
1161
+ def coerce_core_root(value)
1162
+ return nil if value.nil?
1163
+ raise ConfigurationError, "core_root must be a directory path, got #{value.inspect}" unless value.is_a?(String)
1164
+ raise ConfigurationError, "core_root must not be empty" if value.strip.empty?
1165
+
1166
+ value.dup.freeze
1167
+ end
1168
+
1100
1169
  def coerce_baseline_path(value)
1101
1170
  return nil if value.nil? || value == false
1102
1171
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../type"
4
4
  require_relative "../inference/origin_lookup"
5
+ require_relative "../typing_state"
5
6
  require_relative "attribution"
6
7
  require_relative "envelope_index"
7
8
  require_relative "file_collection"
@@ -24,8 +25,26 @@ module Rigor
24
25
  # `dynamic_origins` cause the typer already recorded. With no `effects:` block nothing calls
25
26
  # {collect_for}, `@active_count` stays zero, and `rigor check` is byte-identical.
26
27
  module Collector
27
- KEY = :__rigor_effects_collector__
28
- private_constant :KEY
28
+ # ADR-129 WD1 — the installed accumulator is a Frame whose contents are a Resource; only {record_root} touches it
29
+ # now, and the call-site decisions are the bound Table {CALLS}. The activation count below is a process switch,
30
+ # outside the typing state (WD5).
31
+ ACCUMULATOR = TypingState.frame(
32
+ :effects_collector,
33
+ contents: :resource, reason: "the accumulator a file's collection installs; each call site records into it",
34
+ # G1′ witness: `Collector.record_call` reads it during typing, so it is not a never-reads switch.
35
+ witness: "spec/fixtures/typing_state_witnesses/effects_collector.rb"
36
+ )
37
+ private_constant :ACCUMULATOR
38
+
39
+ # ADR-129 WD1 — the per-file call-site decisions are a bound Table: each installed accumulator's `calls` is one of
40
+ # its stores, journaled while a capture is open so a later `type_of` memo can validate the reads and replay the
41
+ # writes. The root ({record_root}) stays a touch of the accumulator: it is set once per file, before typing.
42
+ CALLS = TypingState.table(
43
+ :effects_calls,
44
+ bound: true,
45
+ reason: "node => CallRecord per file accumulator; first write wins, and a re-query reads the pinned decision"
46
+ )
47
+ private_constant :CALLS
29
48
 
30
49
  # What one call site decided, as the typer saw it.
31
50
  #
@@ -97,17 +116,18 @@ module Rigor
97
116
  # site imports must not depend on which process typed the file.
98
117
  def collect_for(path, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
99
118
  plugin_facts: PluginFacts.empty, unit_key: nil, unit_owner: nil)
100
- previous = Thread.current[KEY]
101
119
  accumulator = Accumulator.new(path.to_s, attribution: attribution, envelopes: envelopes,
102
120
  plugin_facts: plugin_facts, unit_key: unit_key,
103
121
  unit_owner: unit_owner)
104
- Thread.current[KEY] = accumulator
105
- @mutex.synchronize { @active_count += 1 }
106
- yield
107
- build(accumulator)
108
- ensure
109
- Thread.current[KEY] = previous
110
- @mutex.synchronize { @active_count -= 1 }
122
+ TypingState.current.with(ACCUMULATOR, accumulator) do
123
+ @mutex.synchronize { @active_count += 1 }
124
+ begin
125
+ yield
126
+ build(accumulator)
127
+ ensure
128
+ @mutex.synchronize { @active_count -= 1 }
129
+ end
130
+ end
111
131
  end
112
132
 
113
133
  def active?
@@ -117,8 +137,12 @@ module Rigor
117
137
  # The parsed root of the file being analyzed. Recorded by the analysis body once the parse is known
118
138
  # to have succeeded, so the scan runs over exactly what the typer saw.
119
139
  def record_root(root)
120
- accumulator = Thread.current[KEY]
121
- accumulator.root = root if accumulator && accumulator.root.nil?
140
+ state = TypingState.current
141
+ accumulator = state[ACCUMULATOR]
142
+ return unless accumulator
143
+
144
+ state.touch(ACCUMULATOR)
145
+ accumulator.root = root if accumulator.root.nil?
122
146
  end
123
147
 
124
148
  # One call site's dispatch decision. `receiver` is the type the typer computed for the receiver and
@@ -127,30 +151,38 @@ module Rigor
127
151
  # The `scope.source_path` guard matters: inter-procedural inference re-types a *callee's* body while
128
152
  # the caller's file is being analyzed, and those nodes belong to the callee's own file collection.
129
153
  def record_call(node, receiver, scope)
130
- accumulator = Thread.current[KEY]
154
+ state = TypingState.current
155
+ accumulator = state[ACCUMULATOR]
131
156
  return if accumulator.nil? || accumulator.path != scope.source_path
157
+
132
158
  # First write wins, so a re-query has nothing to add — and asking here rather than inside
133
159
  # {Accumulator#record} is what stops it paying for the `Data` and the origin lookup below.
160
+ calls = accumulator.calls
161
+ state.log_bound_read(CALLS, calls, node, calls.fetch(node, TypingState::ABSENT)) if state.capturing?
134
162
  return if accumulator.recorded?(node)
135
163
 
136
164
  dynamic = dynamic_receiver?(receiver)
137
165
  class_name, kind = descriptor_for(receiver)
138
- accumulator.record(
139
- node,
140
- CallRecord.new(
141
- receiver_class: class_name, kind: kind, dynamic: dynamic,
142
- cause: dynamic ? Inference::OriginLookup.origin_for(scope, node.receiver)&.to_s : nil,
143
- resolved: true
144
- )
166
+ call_record = CallRecord.new(
167
+ receiver_class: class_name, kind: kind, dynamic: dynamic,
168
+ cause: dynamic ? Inference::OriginLookup.origin_for(scope, node.receiver)&.to_s : nil,
169
+ resolved: true
145
170
  )
171
+ accumulator.record(node, call_record)
172
+ state.log_bound_write(CALLS, calls, node, call_record, TypingState::ABSENT)
146
173
  end
147
174
 
148
175
  # The typer's fallback verdict: no tier resolved this call.
149
176
  def record_unresolved(node, source_path)
150
- accumulator = Thread.current[KEY]
177
+ state = TypingState.current
178
+ accumulator = state[ACCUMULATOR]
151
179
  return if accumulator.nil? || accumulator.path != source_path
152
180
 
181
+ calls = accumulator.calls
182
+ existing = calls.fetch(node, TypingState::ABSENT)
183
+ state.log_bound_read(CALLS, calls, node, existing)
153
184
  accumulator.mark_unresolved(node)
185
+ state.log_bound_write(CALLS, calls, node, calls[node], existing) unless TypingState::ABSENT.equal?(existing)
154
186
  end
155
187
 
156
188
  # The class a receiver type projects to, as `[class_name, kind]`. A deliberately small mirror of the
@@ -58,11 +58,9 @@ module Rigor
58
58
  node.is_a?(Prism::SingletonClassNode) || (node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode))
59
59
  end
60
60
 
61
- # A class body.
62
61
  CLASS_BODY = of(:class, false)
63
62
  # A `class << self` body: `self` and the definee are both the singleton class.
64
63
  SINGLETON_CLASS_BODY = of(:singleton_class, true)
65
- # The body of an instance method of the class.
66
64
  INSTANCE_METHOD_BODY = of(:instance, false)
67
65
  # The top level, where `self` is `main`, an instance of `Object`, and a `def` defines `Object#m`.
68
66
  TOP_LEVEL = INSTANCE_METHOD_BODY
@@ -8,7 +8,7 @@ module Rigor
8
8
  # An author-declared upper bound on one method's effect labels (ADR-103 WD1; normative in
9
9
  # `docs/type-specification/effect-labels.md` § Effect envelopes).
10
10
  #
11
- # Four facts and one seam:
11
+ # Its facts:
12
12
  #
13
13
  # - {#owner_key} — the method key the envelope binds (`Class#m` / `Class.m`). A class-level
14
14
  # envelope is read once and {#rebind}ed onto each method of that class discovery knows.
@@ -69,7 +69,7 @@ module Rigor
69
69
  VALIDATION_TRIGGERS = %w[valid? invalid? validate!].freeze
70
70
  SINGLETON_SAVE_TRIGGERS = %w[create create!].freeze
71
71
 
72
- # `initialize` is not a mailer action, and neither is a method a plugin would want double-counted.
72
+ # `initialize` is not a mailer action.
73
73
  NON_ACTION_METHODS = %w[initialize].to_set.freeze
74
74
 
75
75
  module_function
@@ -32,13 +32,10 @@ module Rigor
32
32
  # `foo=`, and deliberately not `==` / `<=` / `!=` / `===`.
33
33
  ATTRIBUTE_WRITER = /\A[a-z_][A-Za-z0-9_]*=\z/
34
34
 
35
- # Every Hash method that mutates its receiver. The widening keeps two tables because they answer two different
36
- # questions of a `HashShape` — `MutationWidening::HASH_MUTATORS` changes the pair set,
37
- # `HashLookupMutation::MUTATORS` changes what a read of the pairs answers — but either is a write to the
38
- # receiver, and that is all this asks: `h.compare_by_identity` raises `FrozenError` on a frozen hash as
39
- # `h.clear` does (unless the hash already compares by identity, when it returns before the check), and a
40
- # method whose one effect was `@h.compare_by_identity` did not read as mutating while this cited the first
41
- # table alone.
35
+ # Every Hash method that mutates its receiver: the union of two widening tables that answer different
36
+ # questions of a `HashShape` (the pair set; what a read of the pairs answers). Either is a write to the
37
+ # receiver, so either counts. `compare_by_identity` lives only in the second, and it raises `FrozenError`
38
+ # on a frozen hash as `h.clear` does, so a method whose one effect is that call must still read as mutating.
42
39
  #
43
40
  # `rehash` is in neither table because it changes no read a shape can state: a shape's keys are literals, which
44
41
  # a rebuild never merges. It still rebuilds the receiver's table in place, and it is the one name spelt here.
@@ -363,7 +363,7 @@ module Rigor
363
363
  # dispatch target is spelled under a different key, and this is the rewrite: the same ancestor walk
364
364
  # {#resolve_owner} performs, on the instance side, for `initialize`.
365
365
  #
366
- # Three boundaries, and each of them can only narrow:
366
+ # Four boundaries, and each of them can only narrow:
367
367
  #
368
368
  # - a project `def self.new` **wins**. It is the definition `Const.new` actually reaches, and a
369
369
  # constructor that overrides `new` is the case where `#initialize` is not the answer. Resolved
@@ -35,8 +35,8 @@ module Rigor
35
35
  # without proposing an unrelated label for a genuinely new spelling.
36
36
  SUGGESTION_DISTANCE_CAP = 2
37
37
 
38
- # The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost, and
39
- # nothing consumes the registry yet, so it stays lazy rather than paying at `require "rigor"`.
38
+ # The shared registry as shipped. Memoised: the YAML parse is a once-per-process cost, so it stays
39
+ # lazy rather than paying at `require "rigor"`.
40
40
  def self.default
41
41
  @default ||= load_file(DATA_PATH)
42
42
  end
@@ -55,7 +55,7 @@ module Rigor
55
55
  #
56
56
  # A count rather than the causes themselves (#434). On redmine the cause renderings were 144,771 of
57
57
  # the file's 293,276 bytes, on lines up to 821 characters — simultaneously the half a reviewer
58
- # cannot read and the half that churns when an unrelated call moves. This file already makes that
58
+ # cannot read and the half that churns when an unrelated call moves. {SnapshotDiff} already makes that
59
59
  # argument about origins ("a record keyed by them would churn on every refactor"); the causes are
60
60
  # the same class of thing and were the one place it was not applied. What a reviewer needs from the
61
61
  # record is the stable fact that the row is not exhaustive and by how much, which is what a count
@@ -8,8 +8,6 @@ module Rigor
8
8
  # A tainted summary reads "these effects, and possibly more": the cause says which knowledge the
9
9
  # analyzer could not have. Taint never produces a finding — diagnostics read the proven lane
10
10
  # only — so this enum is a reporting vocabulary, not a diagnostic one.
11
- #
12
- # Nothing produces causes yet; the collector of #379 is the first writer.
13
11
  module TaintCause
14
12
  # Closed and ordered by how it arises: dispatch the analyzer could not resolve first, then
15
13
  # attribution it had to take on trust, then the collector's own limits.
@@ -586,9 +586,9 @@ module Rigor
586
586
  # #1071 widens the answer per `respond_to` arm: a unit that spelled a dispatcher renders one
587
587
  # conventional `<action>.<fmt>` template per arm rather than one `<action>.html`, so the row is
588
588
  # applied once per arm the walk read, each answered outright (a `responds:` row at the arm's own
589
- # top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html`
590
- # answer survives only for a dispatcher the walk could not name arms on, or one nested under a
591
- # branch — the plain fall-through still exists on the path that skips it.
589
+ # top level) or degenerate (`any` / `all`) standing its arm's edge down. The `<action>.html` answer
590
+ # is kept where no arm was read, and beside the arms when the dispatcher is nested under a branch,
591
+ # because the plain fall-through still exists on the path that skips it.
592
592
  def apply_unit_callees
593
593
  return if @responded || @singleton || @non_public
594
594
  return if @owner_class.nil? || @method_name.nil?
@@ -811,11 +811,9 @@ module Rigor
811
811
  #
812
812
  # **Unless the syntax named the class** (#463). The `Dynamic` exclusion exists because a projected
813
813
  # receiver class is a guess; a written constant path is not one, and {#catalog_target} already
814
- # trusts it for the row lookup. Refusing the posture there made the catalogue's reach a function of
815
- # whether the bundled rbs happens to ship a signature for the constant: `Net::HTTP.get` proves
816
- # `io.net.http` and `Net::IMAP.new` proved nothing, because rbs ships `net-http` and not
817
- # `net-imap`. That is why the `Net::FTP` row has never once fired, and why Redmine's IMAP and POP3
818
- # pollers proved no network at all.
814
+ # trusts it for the row lookup. Refusing the posture there made the catalogue's reach depend on
815
+ # whether the bundled rbs ships a signature for the constant: `Net::HTTP.get` proved `io.net.http`,
816
+ # while `Net::IMAP.new` proved nothing, because rbs ships `net-http` and not `net-imap`.
819
817
  #
820
818
  # The site is then **claimed**, and so discharges, like every other catalogue answer. That is not a
821
819
  # softening of the `dynamic-receiver` rule but its premise failing: the taint says the class the
@@ -974,12 +972,10 @@ module Rigor
974
972
  positional_arity(node).positive?
975
973
  end
976
974
 
977
- # The class the catalogue would look this call up under, as `[owner, singleton, implicit_self]`.
975
+ # The class the catalogue would look this call up under, as `[owner, singleton, implicit, named]`.
978
976
  # Spelled from the syntax where the syntax settles it and from the typer's receiver otherwise;
979
- # `[nil, …]` when neither does.
980
- # `[owner, singleton, implicit, named]`. `named` says the owner came from a **constant path the
981
- # author wrote**, which is what licenses the posture on a receiver the typer could not resolve
982
- # ({#posture_allowed?}).
977
+ # `[nil, …]` when neither does. `named` says the owner came from a **constant path the author wrote**,
978
+ # which is what licenses the posture on a receiver the typer could not resolve ({#posture_allowed?}).
983
979
  def catalog_target(node, record)
984
980
  receiver = node.receiver
985
981
  return ["Kernel", false, true, false] if receiver.nil? || receiver.is_a?(Prism::SelfNode)