rigortype 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -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`
@@ -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,
@@ -321,12 +330,22 @@ module Rigor
321
330
 
322
331
  # The test roots a run uses: the declared `test_paths:` as written (already resolved against the
323
332
  # config file's directory by {.load}), or — when the key is unset — whichever of
324
- # {CONVENTIONAL_TEST_PATHS} is a directory under `root`, as absolute paths. `root` is the directory
325
- # the run treats as the project root (the CLI's CWD), as for `signature_paths:` auto-detection.
326
- def resolved_test_paths(root: Dir.pwd)
333
+ # {CONVENTIONAL_TEST_PATHS} is a directory under a root, as absolute paths. The roots, unless `root` names
334
+ # one, are the directory of the config file in use, the one `paths:` and a declared `test_paths:` resolve
335
+ # against, and then the working directory, read only when the first holds neither. Auto-detecting against
336
+ # the working directory alone found nothing for a run from a subdirectory, or for the MCP server started
337
+ # elsewhere, though both name the config file (#2078); a config file kept in a subdirectory
338
+ # (`--config=config/rigor.yml`) still finds the project's `spec/` from the project root.
339
+ def resolved_test_paths(root: nil)
327
340
  return test_paths unless test_paths.nil?
328
341
 
329
- CONVENTIONAL_TEST_PATHS.map { |dir| File.absolute_path(dir, root) }.select { |dir| File.directory?(dir) }
342
+ roots = root ? [root] : [exclude_base, Dir.pwd].uniq
343
+ roots.each do |dir|
344
+ found = CONVENTIONAL_TEST_PATHS.map { |test_dir| File.absolute_path(test_dir, dir) }
345
+ .select { File.directory?(it) }
346
+ return found unless found.empty?
347
+ end
348
+ []
330
349
  end
331
350
 
332
351
  # Loads a configuration file.
@@ -480,6 +499,7 @@ module Rigor
480
499
 
481
500
  out = data.dup
482
501
  PATH_KEYS.each { |key| resolve_path_key!(out, key, base_dir) }
502
+ SCALAR_PATH_KEYS.each { |key| resolve_scalar_path_key!(out, key, base_dir) }
483
503
  resolve_plugins_io_paths!(out, base_dir)
484
504
  out
485
505
  end
@@ -490,6 +510,12 @@ module Rigor
490
510
  out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
491
511
  end
492
512
 
513
+ def self.resolve_scalar_path_key!(out, key, base_dir)
514
+ return unless out.key?(key) && out[key].is_a?(String)
515
+
516
+ out[key] = ConfigPath.absolute(out[key], base_dir)
517
+ end
518
+
493
519
  def self.resolve_plugins_io_paths!(out, base_dir)
494
520
  plugins_io = out["plugins_io"]
495
521
  return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
@@ -572,6 +598,7 @@ module Rigor
572
598
  @libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
573
599
  sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
574
600
  @signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
601
+ @core_root = coerce_core_root(data.fetch("core_root", DEFAULTS.fetch("core_root")))
575
602
  test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
576
603
  @test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
577
604
  @sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
@@ -585,18 +612,13 @@ module Rigor
585
612
  # ADR-67 WD6a — resolve to a strict Boolean so a truthy non-`true` value (e.g. a stray String) does not
586
613
  # silently enable the gate; only the literal `true` activates the check-walk collector pre-pass.
587
614
  @parameter_inference = data.fetch("parameter_inference", DEFAULTS.fetch("parameter_inference")) == true
588
- # Resolved before `@effects` — ADR-103 WD15's "effects-on-by-default" reads {#bleeding_edge_active?},
589
- # which reads `@bleeding_edge_active_ids`.
590
- @bleeding_edge = coerce_bleeding_edge(
591
- data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge"))
592
- )
593
- @bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
594
- @bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
595
615
  # ADR-103 WD13 — presence, not truthiness: `effects:` written with no body parses as nil and still
596
- # means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads.
597
- @effects = coerce_effects(data, effects_key_present: effects_key_present)
598
- coerce_effects_snapshot(@effects)
599
- coerce_effects_policy(@effects)
616
+ # means "on", so the key's presence in the loaded data is what {#effects_enabled?} reads. The raw
617
+ # input is kept so {#with_bleeding_edge} re-derives the effects block from the same source.
618
+ @effects_key_present = effects_key_present
619
+ @effects_written = Ractor.make_shareable(data.fetch("effects", false))
620
+ derive_bleeding_edge(data.fetch("bleeding_edge", DEFAULTS.fetch("bleeding_edge")))
621
+ derive_effects
600
622
  @cache_path = cache.fetch("path").to_s
601
623
  raw_max = cache.fetch("max_bytes")
602
624
  @cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max)
@@ -662,6 +684,7 @@ module Rigor
662
684
  "disable" => disabled_rules,
663
685
  "libraries" => libraries,
664
686
  "signature_paths" => signature_paths,
687
+ "core_root" => core_root,
665
688
  # `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
666
689
  # incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
667
690
  # invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
@@ -742,15 +765,14 @@ module Rigor
742
765
  # It is a frozen `dup` with the bleeding-edge ivars re-set: `dup` returns an unfrozen shallow copy
743
766
  # (every other ivar is the receiver's deeply-frozen value, safe to share read-only), the replacements
744
767
  # are themselves deeply frozen, and the re-`freeze` keeps the result `Ractor.shareable?` for the worker
745
- # path. **Every ivar `#initialize` derives from `@bleeding_edge` has to be re-derived here** — the
746
- # selector, the severity map, and the active-id set the {#bleeding_edge_active?} predicate reads.
768
+ # path. **Every ivar `#initialize` derives from the bleeding-edge selector has to be re-derived here** — it
769
+ # calls the same {#derive_bleeding_edge} and {#derive_effects} that `#initialize` does, so the selector,
770
+ # the severity map, the active-id set and the `effects:` default (ADR-103 WD15's "effects-on-by-default")
771
+ # all follow the new value, and a derived ivar added to `#initialize` is picked up without a second edit.
747
772
  def with_bleeding_edge(value)
748
- selector = coerce_bleeding_edge(value)
749
773
  copy = dup
750
- copy.instance_variable_set(:@bleeding_edge, selector)
751
- copy.instance_variable_set(:@bleeding_edge_severity_overrides,
752
- BleedingEdge.severity_overrides_for(selector))
753
- copy.instance_variable_set(:@bleeding_edge_active_ids, BleedingEdge.active_ids_for(selector))
774
+ copy.send(:derive_bleeding_edge, value)
775
+ copy.send(:derive_effects)
754
776
  copy.freeze
755
777
  end
756
778
 
@@ -784,19 +806,38 @@ module Rigor
784
806
  #
785
807
  # Same `dup` + re-`freeze` shape as {#with_bleeding_edge}: every other ivar is the receiver's deeply
786
808
  # frozen value, safe to share read-only, and the result stays `Ractor.shareable?` for the worker path.
809
+ # The implicit block is recorded as a written empty one, so a later {#with_bleeding_edge} re-derives
810
+ # it instead of falling back to the absent-key default and dropping it.
787
811
  def with_effects_enabled
788
812
  return self if effects_enabled?
789
813
 
790
814
  copy = dup
791
- copy.instance_variable_set(:@effects, {}.freeze)
792
- # The implicit block defaults like a written one. `rigor effects` emits no diagnostic either way,
793
- # but the two configurations must not differ in a field a later reader could branch on.
794
- copy.instance_variable_set(:@effects_check, true)
815
+ copy.instance_variable_set(:@effects_key_present, true)
816
+ copy.instance_variable_set(:@effects_written, {}.freeze)
817
+ copy.send(:derive_effects)
795
818
  copy.freeze
796
819
  end
797
820
 
798
821
  private
799
822
 
823
+ # ADR-50 § WD2 / ADR-103 WD15 — derives the bleeding-edge selector and the two ivars the
824
+ # {#bleeding_edge_active?} predicate and the severity resolution read. `#initialize` and
825
+ # {#with_bleeding_edge} both call this, so there is one derivation to keep in step.
826
+ def derive_bleeding_edge(value)
827
+ @bleeding_edge = coerce_bleeding_edge(value)
828
+ @bleeding_edge_severity_overrides = BleedingEdge.severity_overrides_for(@bleeding_edge)
829
+ @bleeding_edge_active_ids = BleedingEdge.active_ids_for(@bleeding_edge)
830
+ end
831
+
832
+ # ADR-103 WD13 / WD15 — derives the `effects:` block and its snapshot and policy ivars from the raw
833
+ # `effects:` input the instance kept (`@effects_written`, `@effects_key_present`). The effects-on-by-default
834
+ # default reads the active-id set, so {#derive_bleeding_edge} must run first.
835
+ def derive_effects
836
+ @effects = coerce_effects(@effects_written, effects_key_present: @effects_key_present)
837
+ coerce_effects_snapshot(@effects)
838
+ coerce_effects_policy(@effects)
839
+ end
840
+
800
841
  # ADR-103 WD13 — the `effects:` block, or nil when the key is absent or explicitly `false`. A
801
842
  # present-but-empty key (`effects:` alone, which YAML parses as nil) and `effects: {}` are the same
802
843
  # thing: collection on, every sub-key defaulted. A non-Hash body is normalised to `{}` rather than
@@ -808,8 +849,7 @@ module Rigor
808
849
  # as `effects: {}` when the "effects-on-by-default" bleeding-edge feature is adopted. An explicit
809
850
  # `effects: false` — `effects_key_present == true` — always stays off regardless of the feature, and a
810
851
  # written block always wins outright: the feature can only fill an *absence*.
811
- def coerce_effects(data, effects_key_present:)
812
- value = data.fetch("effects", false)
852
+ def coerce_effects(value, effects_key_present:)
813
853
  return {}.freeze if value == false && !effects_key_present && effects_forced_on_by_bleeding_edge?
814
854
  return nil if value == false
815
855
 
@@ -1044,7 +1084,7 @@ module Rigor
1044
1084
  end
1045
1085
  end
1046
1086
 
1047
- # `target_ruby` is passed to `Prism.parse_file(path, version:)` at the analyser's three parse sites
1087
+ # `target_ruby` is passed as Prism's `version:` option at the analyser's parse sites
1048
1088
  # (`Analysis::Runner`, `CLI::TypeOfCommand`, `CLI::TypeScanCommand`) so projects that target an older
1049
1089
  # Ruby get parse errors for syntax their target doesn't support. Format validation here is loose —
1050
1090
  # accepts any `<major>.<minor>` or `<major>.<minor>.<patch>` form, plus the literal `"latest"`. Prism
@@ -1087,6 +1127,18 @@ module Rigor
1087
1127
  # ADR-22 WD2 (b) — `baseline: <path>` activates the file; `baseline: false` is the explicit-disable form
1088
1128
  # (useful in `.rigor.yml` to override an upstream `.rigor.dist.yml` that names a baseline). `nil`
1089
1129
  # (default / absent key) is also "no baseline".
1130
+ # Issue #2517 — the directory's existence and contents are checked where the environment is built
1131
+ # ({Environment::CoreRoot.validate!}), not here: `rigor init`, `rigor doctor` and the language server's reload
1132
+ # load a configuration without building an environment, and must still be able to read one naming a core that
1133
+ # is not generated yet.
1134
+ def coerce_core_root(value)
1135
+ return nil if value.nil?
1136
+ raise ConfigurationError, "core_root must be a directory path, got #{value.inspect}" unless value.is_a?(String)
1137
+ raise ConfigurationError, "core_root must not be empty" if value.strip.empty?
1138
+
1139
+ value.dup.freeze
1140
+ end
1141
+
1090
1142
  def coerce_baseline_path(value)
1091
1143
  return nil if value.nil? || value == false
1092
1144
 
@@ -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)
@@ -31,7 +31,7 @@ module Rigor
31
31
  # The list is hard-coded for the MVP because it tracks rigor's bundled coverage 1:1. When a new gem is
32
32
  # vendored under `data/vendored_gem_sigs/` or added to `DEFAULT_LIBRARIES`, add its name here.
33
33
  SKIPPED_GEMS_BY_DEFAULT = Set[
34
- # DEFAULT_LIBRARIES (lib/rigor/environment.rb)
34
+ # DEFAULT_LIBRARIES (lib/rigor/environment/default_libraries.rb)
35
35
  "pathname", "optparse", "json", "yaml", "fileutils",
36
36
  "tempfile", "tmpdir", "stringio", "forwardable",
37
37
  "digest", "securerandom", "uri", "logger", "date",
@@ -100,14 +100,10 @@ module Rigor
100
100
  expected_dirs = expected_gem_dirs(locked_gems)
101
101
  filtered.select do |sig_dir|
102
102
  if git_sourced_layout?(sig_dir)
103
- # A git install's directory name carries no resolvable version (see above), so the lockfile
104
- # filter can only check the gem NAME, not `(name, version)` — but name alone is not enough: a
105
- # gem that used to be `git:`-sourced and has since moved to a released version keeps its old
106
- # `bundler/gems/<repo>-<sha>/` directory sitting in the bundle tree until a `bundle clean`, and
107
- # its NAME is still present in the lockfile (now via the rubygems entry). Matching on name only
108
- # would silently readmit that stale git install — this repo's own `vendor/bundle` had exactly
109
- # this after `binpacker` moved from `git:` to a released gem. `locked_gems` must say this name is
110
- # CURRENTLY git-sourced, not merely present.
103
+ # A git install's directory carries no version, so the lockfile filter can only check the gem NAME.
104
+ # Name alone is not enough: a gem that moved from `git:` to a released version keeps its stale
105
+ # `bundler/gems/` checkout until `bundle clean`, under a name the lockfile still lists. `locked_gems`
106
+ # must say this name is CURRENTLY git-sourced, not merely present.
111
107
  locked = locked_gems[gem_name_from_sig_path(sig_dir)]
112
108
  !!locked&.git_source
113
109
  else
@@ -9,8 +9,8 @@ module Rigor
9
9
  # This is the static fast path for built-ins; `Environment` falls through to `RbsLoader` for all other
10
10
  # names — the two tiers are complementary.
11
11
  #
12
- # See docs/internal-spec/inference-engine.md for the binding contract (every entry below MUST always be
13
- # recognised).
12
+ # See docs/internal-spec/inference-engine.md for the binding contract (the classes in
13
+ # `SLICE_1_BUILT_INS` MUST always be recognised).
14
14
  class ClassRegistry
15
15
  SLICE_1_BUILT_INS = [
16
16
  Integer,
@@ -27,7 +27,7 @@ module Rigor
27
27
  # Common Ruby core classes that user code routinely names by constant reference. Adding them to the
28
28
  # registry lets `nominal_for_name` resolve `Array`, `Hash`, etc. without each call site re-listing
29
29
  # them. The static registry is the cheap fast path; `RbsLoader` extends coverage to all other
30
- # RBS-declared names (Slice 4, landed).
30
+ # RBS-declared names.
31
31
  SLICE_2_BUILT_INS = [
32
32
  Array,
33
33
  Hash,
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../configuration_error"
4
+ require_relative "../rooted_glob"
5
+
6
+ module Rigor
7
+ class Environment
8
+ # Issue #2517 — a core signature directory chosen IN PLACE OF the `rbs` gem's own `core/` (`core_root:` in
9
+ # `.rigor.yml`). The caller is a provider such as rbs-shifter, which rewrites upstream core for an older
10
+ # CRuby (`Array#rfind` gone, `String#strip` without arguments); Rigor accepts the directory and passes it to
11
+ # `RBS::EnvironmentLoader.new(core_root:)`, so the default core is never loaded beside it.
12
+ #
13
+ # An explicit choice is never second-guessed by a fallback: a directory that is missing, unreadable or holds
14
+ # no signature, and an environment that fails to build from it, raise {CoreRootError} instead of analysing
15
+ # against the default core or against a degraded `Dynamic[top]` environment. Both would report a project as
16
+ # clean for the very APIs the selection exists to reject.
17
+ module CoreRoot
18
+ module_function
19
+
20
+ # @param path — the configured directory, already resolved against the config file's directory.
21
+ # @return the absolute directory as a frozen String (`Ractor.shareable?`, so a frozen loader stays shareable).
22
+ # @raise CoreRootError — the directory cannot serve as a core.
23
+ def validate!(path)
24
+ absolute = File.expand_path(path.to_s)
25
+ reason = invalid_reason(absolute)
26
+ raise CoreRootError, "core_root: #{absolute.inspect} #{reason}" if reason
27
+
28
+ absolute.dup.freeze
29
+ end
30
+
31
+ # A selected core inside a signature root, or one containing it, loads its files twice — once as core and once
32
+ # as project signatures — and every class it declares then fails to build on its duplicated methods.
33
+ #
34
+ # @param core_root — a {.validate!}d directory.
35
+ # @param signature_paths — the project's own signature roots.
36
+ # @raise CoreRootError — the two overlap.
37
+ def reject_overlap!(core_root, signature_paths)
38
+ core = File.join(real_path(core_root), "")
39
+ Array(signature_paths).each do |path|
40
+ root = File.join(real_path(path), "")
41
+ next unless core.start_with?(root) || root.start_with?(core)
42
+
43
+ raise CoreRootError, "core_root: #{core_root.inspect} overlaps the signature path #{path.to_s.inspect}, so " \
44
+ "its signatures would load twice; move the core outside the project's signature paths"
45
+ end
46
+ end
47
+
48
+ # Compared resolved, because the project-signature walk does not follow a symlinked directory: a `sig/core`
49
+ # link to a core kept elsewhere loads once, as core, and must not be rejected. A path that does not exist yet
50
+ # resolves through its nearest existing ancestor, so the two sides of a comparison are spelled alike.
51
+ def real_path(path)
52
+ absolute = File.expand_path(path.to_s)
53
+ missing = []
54
+ until File.exist?(absolute) || File.dirname(absolute) == absolute
55
+ missing.unshift(File.basename(absolute))
56
+ absolute = File.dirname(absolute)
57
+ end
58
+ File.join(File.realpath(absolute), *missing)
59
+ rescue SystemCallError
60
+ File.expand_path(path.to_s)
61
+ end
62
+
63
+ def invalid_reason(absolute)
64
+ return "does not exist" unless File.exist?(absolute)
65
+ return "is not a directory" unless File.directory?(absolute)
66
+ return "is not readable" unless File.readable?(absolute)
67
+
68
+ "contains no .rbs signature file" if RootedGlob.glob(absolute, "**/*.rbs").empty?
69
+ end
70
+ end
71
+
72
+ # The selected core cannot be used. A {ConfigurationError}, so the CLI reports it as one `rigor:` line with
73
+ # the usage exit code rather than as a crash.
74
+ class CoreRootError < ConfigurationError
75
+ end
76
+ end
77
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rigor
4
4
  class Environment
5
- # Slice A stdlib expansion. Stdlib libraries that `Environment.for_project` loads on top of RBS core
5
+ # Stdlib libraries that `Environment.for_project` loads on top of RBS core
6
6
  # unless the caller passes an explicit `libraries:` array. Each entry MUST be a stdlib library name
7
7
  # accepted by `RBS::EnvironmentLoader#has_library?`; unknown libraries MUST fail-soft
8
8
  # (`RbsLoader#build_env` already filters through `has_library?`). The default set covers the common