rigortype 0.4.6 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
@@ -2,21 +2,21 @@
2
2
 
3
3
  module Rigor
4
4
  class Environment
5
- # Frozen, `Ractor.shareable?` read-only RBS query facade.
5
+ # Frozen read-only RBS query facade.
6
6
  # [ADR-15](../../../docs/adr/15-ractor-concurrency.md) Phase 2b extracts the read-only surface of
7
7
  # {RbsLoader} into this carrier so future Ractor-isolated workers can share one Reflection across the
8
8
  # pool while keeping the per-Ractor mutable accelerator state (per-process memo Hashes) where it belongs.
9
9
  #
10
10
  # Backing tables (all frozen at construction):
11
11
  #
12
- # - `known_class_names` — `Set<String>` of every class / module / alias name in the loaded RBS
13
- # environment. Top-level prefixed (`"::Hash"`); plain queries normalise via {#normalise}.
14
- # - `instance_definitions` — `Hash<String, RBS::Definition>` keyed on `RBS::TypeName#to_s` (top-level
12
+ # - `known_class_names` — every class / module / alias name in the loaded RBS environment.
13
+ # Top-level prefixed (`"::Hash"`); the query methods accept either form.
14
+ # - `instance_definitions` — `RBS::Definition` per class, keyed on `RBS::TypeName#to_s` (top-level
15
15
  # prefixed).
16
16
  # - `singleton_definitions` — same shape, singleton side.
17
- # - `type_param_names` — `Hash<String, Array<Symbol>>` of declared type parameters per class.
18
- # - `constant_types` — `Hash<String, Rigor::Type>` of translated constant declarations.
19
- # - `ancestor_names` — `Hash<String, Array<String>>` of normalised ancestor chains per class.
17
+ # - `type_param_names` — declared type parameters per class.
18
+ # - `constant_types` — translated constant declarations.
19
+ # - `ancestor_names` — normalised ancestor chains per class.
20
20
  #
21
21
  # Each `Reflection` instance is `frozen?` at construction — every cached table is frozen, `self` is
22
22
  # frozen. **NOT** `Ractor.shareable?`: the `instance_definitions` / `singleton_definitions` tables hold
@@ -296,17 +296,14 @@ module Rigor
296
296
  name.start_with?(TOKEN_PREFIX) ? name.delete_prefix(TOKEN_PREFIX) : nil
297
297
  end
298
298
 
299
- # The vendored directory basenames a library list activates.
300
299
  def active_dirs(library_names)
301
300
  library_names.filter_map { |name| VENDORED_DIRS[feature_of_token(name)] }
302
301
  end
303
302
 
304
- # Whether `dir_basename` is one of the gated directories.
305
303
  def gated_dir?(dir_basename)
306
304
  VENDORED_DIRS.value?(dir_basename)
307
305
  end
308
306
 
309
- # The feature a gated directory loads for.
310
307
  def feature_for_dir(dir_basename)
311
308
  VENDORED_DIRS.key(dir_basename)
312
309
  end
@@ -28,7 +28,7 @@ module Rigor
28
28
  #
29
29
  # The directories are resolved at load time, on the main Ractor: `RbConfig::CONFIG` and `Gem.default_dir`
30
30
  # read non-shareable constants and raise `Ractor::IsolationError` inside a pool worker. The scan itself runs
31
- # lazily (about 0.1 s), memoised per loader, and only for a class about to be reported.
31
+ # lazily (about 0.1 s), memoised per process, and only for a class about to be reported.
32
32
  module RubyLibraryRoots
33
33
  STDLIB_ROOT = begin
34
34
  ::RBS::Repository::DEFAULT_STDLIB_ROOT.to_s.dup.freeze
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require_relative "../rooted_glob"
5
+ require_relative "gated_signature_plan"
6
+
7
+ module Rigor
8
+ class Environment
9
+ # Issue #2470 — a `data/vendored_gem_sigs/<gem>/` member another signature source also declares stands down,
10
+ # so the other source's declaration binds instead of the two failing the class's definition build.
11
+ #
12
+ # The vendored directories load on `RBS::EnvironmentLoader`'s batch path, ahead of the project's
13
+ # `signature_paths:` (its own `sig/`, an `rbs collection install`, a gem's `sig/`). A file there that reopens
14
+ # a vendored class and declares one of its methods again — the activerecord 8.1 collection's `patch.rbs`
15
+ # writes `def async_exec` on `PG::Connection`, where the vendored `pg.rbs` has `alias async_exec exec` — made
16
+ # `RBS::DefinitionBuilder` raise `DuplicatedMethodDefinitionError` for the class, which Rigor keeps known, so
17
+ # every call into it read `Dynamic[top]`.
18
+ #
19
+ # {.build} is the #1713 member rule ({GatedSignaturePlan}) applied to the ungated directories, planned from
20
+ # the signature files themselves rather than from a trial build: the vendored copy is Rigor's guess at a gem's
21
+ # surface, and the project chose the other source, so the other source wins (`RbsLoader.build_env_for`'s
22
+ # stated precedence). Only a directory with a member to leave out is planned; the rest stay on the batch path.
23
+ # An overload continuation (`def m: ... | ...`) declares nothing of its own and keeps the vendored base.
24
+ #
25
+ # The plan is a pure function of the vendored files and the signature files, both of which the env-cache key
26
+ # digests, so a cold and a warm run build the same environment.
27
+ module VendoredSignaturePlan
28
+ # `dirs` — the vendored directories (Pathnames) to keep off the batch path; `sources` — their files as
29
+ # `[buffer, directives, decls]`, the taken members left out, to add right after the batch; `takers` — the
30
+ # signature files a member stood down to. A member stands down only to a file that stays in the env: the
31
+ # caller re-plans without each taker the build quarantined (`RbsLoader.build_env_attempt`), or the method
32
+ # would have no declaration at all.
33
+ Plan = Data.define(:dirs, :sources, :takers)
34
+ EMPTY = Plan.new(dirs: [].freeze, sources: [].freeze, takers: Set.new.freeze)
35
+
36
+ # A `class` / `module` header, after any `%a{...}` / `%a(...)` / `%a[...]` annotations on the same line.
37
+ ANNOTATED_HEADER = /^\s*(?:%a(?:\{[^}]*\}|\([^)]*\)|\[[^\]]*\])\s*)*(?:class|module)\s+(?:::)?/
38
+
39
+ module_function
40
+
41
+ # @param roots — the ungated vendored directories this build loads.
42
+ # @param signature_files — the absolute paths of the signature files that outrank them.
43
+ # @param excluded — signature files to plan without: takers a previous build of this env quarantined.
44
+ def build(roots, signature_files, excluded = Set.new)
45
+ files = signature_files.reject { |file| excluded.include?(file) }
46
+ return EMPTY if roots.empty? || files.empty?
47
+
48
+ parsed = roots.to_h { |root| [root, parsed_sources(root)] }
49
+ taken = taken_keys(files, header_pattern(parsed.values.flatten(1)))
50
+ return EMPTY if taken.empty?
51
+
52
+ takers = Set.new
53
+ planned = parsed.filter_map do |root, sources|
54
+ kept = plan_sources(sources, taken, takers)
55
+ [root, kept] if kept
56
+ end
57
+ return EMPTY if planned.empty?
58
+
59
+ Plan.new(dirs: planned.map(&:first), sources: planned.flat_map(&:last), takers: takers.freeze)
60
+ end
61
+
62
+ # A vendored directory's files, parsed, under the Pathname buffer names the loader's batch path gives them,
63
+ # in the set `RBS::EnvironmentLoader#add(path:)` reads: every `.rbs` under it.
64
+ def parsed_sources(root)
65
+ RootedGlob.glob(root, "**/*.rbs").sort.filter_map { |file| RbsLoader.parse_signature_file(Pathname(file)) }
66
+ end
67
+
68
+ # `sources` with the taken members left out, or nil when nothing is taken from any of them.
69
+ def plan_sources(sources, taken, takers)
70
+ kept = sources.map do |buffer, directives, decls|
71
+ [buffer, directives, plan_decls(decls, nil, taken, takers)]
72
+ end
73
+ kept unless kept.zip(sources).all? { |(_, _, after), (_, _, before)| unchanged?(after, before) }
74
+ end
75
+
76
+ # A header naming one of the vendored top-level types, which every declaration of a vendored type (nested or
77
+ # `A::B`-qualified) starts from. A pre-filter only: the matches are parsed.
78
+ def header_pattern(parsed)
79
+ heads = parsed.flat_map do |_buffer, _directives, decls|
80
+ decls.filter_map { |decl| GatedSignaturePlan.type_declaration?(decl) && decl.name.to_s.delete_prefix("::") }
81
+ end
82
+ segments = heads.map { |name| Regexp.escape(name.split("::").first) }.uniq
83
+ return nil if segments.empty?
84
+
85
+ /#{ANNOTATED_HEADER}(?:#{segments.join('|')})\b/
86
+ end
87
+
88
+ # `{type name => {[method, kind] => Set[file]}}` over the signature files that declare a vendored top-level
89
+ # type.
90
+ def taken_keys(signature_files, pattern)
91
+ taken = Hash.new { |hash, name| hash[name] = Hash.new { |keys, key| keys[key] = Set.new } }
92
+ return taken if pattern.nil?
93
+
94
+ signature_files.sort.each do |file|
95
+ text = RbsLoader.read_signature_text(file)
96
+ next if text.nil? || !text.match?(pattern)
97
+
98
+ decls = RbsLoader.parse_signature_source(file, text)
99
+ collect_keys(decls, nil, taken, file) if decls
100
+ end
101
+ taken
102
+ end
103
+
104
+ def collect_keys(decls, namespace, taken, file)
105
+ decls.each do |decl|
106
+ next unless GatedSignaturePlan.type_declaration?(decl)
107
+
108
+ name = GatedSignaturePlan.qualify(namespace, decl.name)
109
+ decl.members.each do |member|
110
+ RbsLoader.member_method_keys(member).each { |key| taken[name][key] << file }
111
+ end
112
+ collect_keys(decl.members, name, taken, file)
113
+ end
114
+ end
115
+
116
+ # `decls` with every member `taken` declares for its type left out, nested types planned the same way, and
117
+ # the files each left-out member stood down to added to `takers`. A declaration nothing is taken from is
118
+ # returned as itself, which is how {.build} tells a directory unchanged.
119
+ def plan_decls(decls, namespace, taken, takers)
120
+ decls.map do |decl|
121
+ next decl unless GatedSignaturePlan.type_declaration?(decl)
122
+
123
+ plan_type(decl, GatedSignaturePlan.qualify(namespace, decl.name), taken, takers)
124
+ end
125
+ end
126
+
127
+ def unchanged?(after, before)
128
+ after.size == before.size && after.zip(before).all? { |a, b| a.equal?(b) }
129
+ end
130
+
131
+ def plan_type(decl, name, taken, takers)
132
+ keys = taken.fetch(name, nil)
133
+ members = decl.members.filter_map do |member|
134
+ if GatedSignaturePlan.type_declaration?(member)
135
+ plan_type(member, GatedSignaturePlan.qualify(name, member.name), taken, takers)
136
+ elsif keys
137
+ plan_member(member, keys, takers)
138
+ else
139
+ member
140
+ end
141
+ end
142
+ return decl if unchanged?(members, decl.members)
143
+
144
+ GatedSignaturePlan.rebuild(decl, members: members)
145
+ end
146
+
147
+ def plan_member(member, keys, takers)
148
+ RbsLoader.member_method_keys(member).each { |key| takers.merge(keys[key]) if keys.key?(key) }
149
+ GatedSignaturePlan.untaken(member, keys)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -16,6 +16,7 @@ require_relative "environment/bundle_sig_discovery"
16
16
  require_relative "environment/lockfile_resolver"
17
17
  require_relative "environment/installed_gem_set"
18
18
  require_relative "environment/rbs_collection_discovery"
19
+ require_relative "environment/project_path_gems"
19
20
  require_relative "environment/source_rbs_synthesis"
20
21
  require_relative "plugin/isolation"
21
22
  require_relative "cache/engine_source"
@@ -39,7 +40,7 @@ module Rigor
39
40
  DEFAULT_PROJECT_SIG_DIR = "sig"
40
41
  private_constant :DEFAULT_PROJECT_SIG_DIR
41
42
 
42
- # `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required below) so the
43
+ # `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required above) so the
43
44
  # ADR-87 WD4 boot-slimming probe can read it without loading the inference engine.
44
45
 
45
46
  # ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id that ships the SAME core-ext RBS. When
@@ -146,7 +147,7 @@ module Rigor
146
147
  # code (`Plugin::Registry#hkt_overlay_registry` reads each manifest); before this it sat one line ABOVE
147
148
  # the #784 rescue, so a raise there escaped {#hkt_registry} entirely. Post-#788 that is not a per-file
148
149
  # crash storm but an uncaught abort: {HktRegistryHolder#fetch} memoises only on success, so the raise
149
- # is re-attempted at every demand, and the run-owned demands (`Runner::PoolCoordinator#hkt_scan_outcome`
150
+ # is re-attempted at every demand, and the run-owned demands (`Runner::RunLevelFacts::Collect.hkt_scan_failure`
150
151
  # after the file loop, `Analysis::WorkerSession#drain_reporters`) sit outside any rescue.
151
152
  #
152
153
  # The degradation is narrower than the scan's: only the plugin overlay is dropped, and the RBS scan
@@ -265,8 +266,10 @@ module Rigor
265
266
  # `nil` (the default), the canonical project layout `<root>/sig` is used if it exists, otherwise no
266
267
  # signature path is loaded.
267
268
  # @param cache_store — persistent cache threaded into the underlying
268
- # {Environment::RbsLoader} so constant lookups (and, in later v0.0.9 slices, other reflection
269
- # artefacts) consult the cache. Pass `nil` (the default) to skip caching for this environment.
269
+ # {Environment::RbsLoader} so constant lookups consult the cache. Pass `nil` (the default) to skip
270
+ # caching for this environment.
271
+ # @param core_root — issue #2517 — a core signature directory loaded in place of the `rbs` gem's `core/`
272
+ # ({Configuration#core_root}); nil (the default) loads the gem's core.
270
273
  # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
271
274
  def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil, # rubocop:disable Metrics/AbcSize
272
275
  plugin_registry: nil, dependency_source_index: nil,
@@ -276,7 +279,8 @@ module Rigor
276
279
  bundler_lockfile: nil,
277
280
  rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
278
281
  synthetic_method_index: nil, project_patched_methods: nil,
279
- source_files: [], locked_gems: nil, required_features: nil)
282
+ source_files: [], locked_gems: nil, required_features: nil, core_root: nil,
283
+ paths: nil, exclude_patterns: nil, exclude_base: nil)
280
284
  resolved_paths = project_signature_roots(signature_paths, root: root)
281
285
  # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
282
286
  # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
@@ -318,7 +322,11 @@ module Rigor
318
322
  # Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
319
323
  merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s) +
320
324
  feature_tokens(required_features, source_files, libraries)).uniq
321
- skip_gem_names = collection_skip_gem_names(merged_libraries)
325
+ # Issue #2441 — plus the project's own path gems ({ProjectPathGems}), whose collection copy would
326
+ # double-declare the project's `sig/`.
327
+ project_gems = ProjectPathGems.names(locked, paths: paths, root: root, exclude_patterns: exclude_patterns,
328
+ exclude_base: exclude_base || root)
329
+ skip_gem_names = collection_skip_gem_names(merged_libraries) + project_gems.to_a
322
330
  collection_paths = RbsCollectionDiscovery.discover(
323
331
  lockfile_path: rbs_collection_lockfile,
324
332
  project_root: root,
@@ -338,8 +346,10 @@ module Rigor
338
346
  # opt-in plugin twin is REACHABLE — loaded as a plugin, or wired in by `signature_paths:` (#672) —
339
347
  # so the two never both declare the methods. The paths ride in `loader_signature_paths`, so the env
340
348
  # cache descriptor digests them for free.
349
+ # Issue #2441 — the project's own path gems are neither missing RBS nor overlay-eligible: the project's
350
+ # `sig/` declares them, and an overlay (`data/gem_overlay/activesupport`) would collide with it.
341
351
  missing_gems, overlay_paths = missing_gems_and_overlay_paths(
342
- locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
352
+ locked: locked.except(*project_gems), default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
343
353
  rbs_collection_paths: collection_paths, plugin_registry: plugin_registry,
344
354
  signature_paths: resolved_paths, bundle_root: bundle_root
345
355
  )
@@ -366,7 +376,8 @@ module Rigor
366
376
  # the bundle walk and `rbs collection install` all stay authoritative.
367
377
  deferred_signature_paths: plugin_sig_paths,
368
378
  project_signature_paths: resolved_paths,
369
- required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths)
379
+ required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths),
380
+ core_root: core_root
370
381
  )
371
382
  # Issues #824 / #1075 — the rows ADR-112 WD5's consistency rule reports for members the project's
372
383
  # own `sig/` and an inline source both declare: an error per contradiction (or per refinement that
@@ -376,6 +387,10 @@ module Rigor
376
387
  # on every run, so the member-level outcome needs no plumbing of its own. Costs nothing for a project
377
388
  # with no inline RBS: the loader's reader short-circuits on an empty `virtual_rbs`.
378
389
  record_member_consistency(loader, source_rbs_synthesis_reporter, root)
390
+ # Issue #2517 — a selected core is built here, not lazily. The env is first demanded from inside a file's
391
+ # analysis, under rescues that turn any raise into one `internal analyzer error` row per file; building it
392
+ # now raises the {CoreRootError} once, before analysis, where the CLI reports it as a configuration error.
393
+ loader.verify_core_root!
379
394
  # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
380
395
  # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
381
396
  # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
@@ -492,7 +507,7 @@ module Rigor
492
507
  end
493
508
 
494
509
  # ADR-72 — resolve the bundled RBS overlay directories to load for this project. A gem is eligible when
495
- # it is classified `:missing` (see {missing_rbs_gem_rows}) and its conflicting opt-in plugin twin (if
510
+ # it is classified `:missing` (see {missing_rows}) and its conflicting opt-in plugin twin (if
496
511
  # any) is NOT already reachable. Returns `[Pathname]`, deterministically ordered, or `[]` when no
497
512
  # eligible gem.
498
513
  #
@@ -644,6 +659,8 @@ module Rigor
644
659
  def record_member_undecided(reporter, record, root)
645
660
  plugin_id, path = split_virtual_source_name(record.virtual_name)
646
661
  return if path.nil?
662
+ return record_variable_undecided(reporter, record, root, plugin_id, path) if
663
+ MemberConsistency.variable_kind?(record.kind)
647
664
 
648
665
  reporter.record(
649
666
  plugin_id: plugin_id, path: path, kind: :not_honoured,
@@ -656,8 +673,22 @@ module Rigor
656
673
  )
657
674
  end
658
675
 
676
+ # Issue #2440 — an instance variable declared by both sides with different types. `sig/` binds
677
+ # (the coordinator's ruling on #2440, ADR-5): the row is `:info`, never `rbs.contradicting-signature`.
678
+ def record_variable_undecided(reporter, record, root, plugin_id, path)
679
+ reporter.record(
680
+ plugin_id: plugin_id, path: path, kind: :not_honoured,
681
+ message: "`#{member_label(record)}` is also declared in " \
682
+ "`#{path_relative_to(record.signature_path, root)}` with a different type " \
683
+ "(#{record.detail}), so the `.rbs` declaration binds and the inline one was dropped. " \
684
+ "RBS rejects two declarations of one instance variable, so only one can stay; " \
685
+ "regenerate the committed `.rbs` (`rbs-inline --output`) or align the two types."
686
+ )
687
+ end
688
+
689
+ # `Demo#m` / `Demo.m` for a method, `Demo#@x` / `Demo.@x` for an instance / class instance variable.
659
690
  def member_label(record)
660
- separator = record.kind == :singleton ? "." : "#"
691
+ separator = %i[singleton class_instance_variable].include?(record.kind) ? "." : "#"
661
692
  "#{record.class_name}#{separator}#{record.method_name}"
662
693
  end
663
694
 
@@ -26,13 +26,13 @@ module Rigor
26
26
  #
27
27
  # ## Composition rules (ADR-2)
28
28
  #
29
- # - `:return_type` — Intersect via `Type::Combinator.intersection`; collapse to bot raises
30
- # `:return_type_collapse`.
29
+ # - `:return_type` — Intersect via `Type::Combinator.intersection`; collapse to bot records a
30
+ # `:return_type_collapse` conflict.
31
31
  # - `:truthy_fact` / `:falsey_fact` / `:post_return_fact` — Edge-local; accumulate while deduping by
32
32
  # payload equality.
33
33
  # - `:mutation` / `:invalidation` / `:role` — Union; dedupe by equality.
34
- # - `:exception` — Single-valued. Two non-`nil` non-equal exceptional effects raise
35
- # `:exceptional_disagreement`.
34
+ # - `:exception` — Single-valued. Two non-`nil` non-equal exceptional effects record an
35
+ # `:exceptional_disagreement` conflict.
36
36
  #
37
37
  # ## Cross-tier contradictions
38
38
  #
@@ -75,41 +75,66 @@ module Rigor
75
75
  }.freeze
76
76
  private_constant :TYPE_HANDLERS
77
77
 
78
- UNREAD_HASH_REFUSED_KEY = :__rigor_acceptance_unread_hash_refused__
79
- private_constant :UNREAD_HASH_REFUSED_KEY
80
-
81
- # Whether a `maybe` for `param` against `arg` rests on a record that could not read a hash's entries: a
82
- # `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`. The record cannot prove the key set
83
- # of either, and its `maybe` climbs through whatever holds the record. `OverloadSelector`'s strict pass
84
- # reads that `maybe` as no evidence for the overload, so this re-asks with each such record answering
85
- # `no` and reports whether the pair then fails. A `maybe` from anywhere else (a subclass only the Ruby
86
- # source declares, a splat under a parameter that is no record) survives the re-ask and still counts.
87
- def maybe_rests_on_unread_hash?(param, arg)
88
- return false unless contains_type?(param) { |type| type.is_a?(Type::HashShape) }
89
- return false unless contains_type?(arg) { |type| unread_hash?(type) }
90
-
91
- refusing_unread_hashes { accepts(param, arg, mode: :gradual).no? }
78
+ UNREAD_SHAPE_REFUSED_KEY = :__rigor_acceptance_unread_shape_refused__
79
+ private_constant :UNREAD_SHAPE_REFUSED_KEY
80
+
81
+ # Whether a `maybe` for `param` against `arg` rests on a shape that could not read its argument: a record
82
+ # against a `Hash` with a gradual arm ({#gradual_hash?}) or an open `HashShape`, or a tuple against an
83
+ # `Array` of unknown arity ({#array_carrier?}). The shape cannot prove the key set or the length, and its
84
+ # `maybe` climbs through whatever holds it. `OverloadSelector`'s strict pass reads that `maybe` as no
85
+ # evidence for the overload, so this re-asks with each such shape answering `no` and reports whether the
86
+ # pair then fails. A `maybe` from anywhere else (a subclass only the Ruby source declares, a splat under a
87
+ # parameter that is no shape) survives the re-ask and still counts.
88
+ def maybe_rests_on_unread_shape?(param, arg)
89
+ return false unless rests_on_unread_hash?(param, arg) || rests_on_unread_array?(param, arg)
90
+
91
+ refusing_unread(:all) { accepts(param, arg, mode: :gradual).no? }
92
92
  end
93
93
 
94
+ # Whether any of `types` holds an `Array` a tuple cannot read ({#array_carrier?}), so a match may rest on
95
+ # the tuple's `maybe`. `OverloadSelector`'s gradual pass asks before re-matching under
96
+ # {#refusing_unread_arrays}.
97
+ def unread_array_in?(types)
98
+ types.any? { |type| contains_type?(type) { |inner| array_carrier?(inner) } }
99
+ end
100
+
101
+ # Runs the block with each tuple answering `no`, not `maybe`, for an `Array` of unknown arity. A record's
102
+ # `maybe` for an unread hash is untouched.
103
+ def refusing_unread_arrays(&) = refusing_unread(:array, &)
104
+
94
105
  # rubocop:disable-next Metrics/ClassLength
95
106
  class << self
96
107
  private
97
108
 
98
- # The flag lives on the thread, not on a parameter, because the record may sit any number of `accepts`
109
+ def rests_on_unread_hash?(param, arg)
110
+ contains_type?(param) { |type| type.is_a?(Type::HashShape) } &&
111
+ contains_type?(arg) { |type| unread_hash?(type) }
112
+ end
113
+
114
+ def rests_on_unread_array?(param, arg)
115
+ contains_type?(param) { |type| type.is_a?(Type::Tuple) } &&
116
+ contains_type?(arg) { |type| array_carrier?(type) }
117
+ end
118
+
119
+ # The flag lives on the thread, not on a parameter, because the shape may sit any number of `accepts`
99
120
  # calls below the one asked (a tuple element, a `Hash` value, a union member), and those calls go
100
- # through each carrier's `Type#accepts`. It is set only for the duration of the block.
101
- def refusing_unread_hashes
102
- previous = Thread.current[UNREAD_HASH_REFUSED_KEY]
103
- Thread.current[UNREAD_HASH_REFUSED_KEY] = true
121
+ # through each carrier's `Type#accepts`. It is set only for the duration of the block. `kind` is `:all`
122
+ # (records and tuples) or `:array` (tuples only).
123
+ def refusing_unread(kind)
124
+ previous = Thread.current[UNREAD_SHAPE_REFUSED_KEY]
125
+ Thread.current[UNREAD_SHAPE_REFUSED_KEY] = kind
104
126
  yield
105
127
  ensure
106
- Thread.current[UNREAD_HASH_REFUSED_KEY] = previous
128
+ Thread.current[UNREAD_SHAPE_REFUSED_KEY] = previous
107
129
  end
108
130
 
109
- # The `maybe` a record answers for a hash whose entries it cannot read, or `no` inside
110
- # {#refusing_unread_hashes}.
111
- def unread_hash_maybe(mode, reason)
112
- return hash_shape_no(mode, "#{reason} (refused as evidence)") if Thread.current[UNREAD_HASH_REFUSED_KEY]
131
+ # The `maybe` a shape of `kind` (`:hash` or `:array`) answers for an argument it cannot read, or `no`
132
+ # inside {#refusing_unread} for that kind.
133
+ def unread_shape_maybe(mode, reason, kind)
134
+ refused = Thread.current[UNREAD_SHAPE_REFUSED_KEY]
135
+ if refused == :all || refused == kind
136
+ return Type::AcceptsResult.no(mode: mode, reasons: "#{reason} (refused as evidence)")
137
+ end
113
138
 
114
139
  Type::AcceptsResult.maybe(mode: mode, reasons: reason)
115
140
  end
@@ -469,9 +494,10 @@ module Rigor
469
494
  end
470
495
 
471
496
  # Returns `other_type` rewritten so its type_args have the same arity as `self_type.type_args`, or
472
- # `nil` if no projection is known. Today only the Hash → Enumerable projection is hand-rolled; a
473
- # general RBS-driven implementation that consults `definition.ancestors[i].args` for arbitrary
474
- # subclass / module-include relations is the principled follow-up.
497
+ # `nil` if no projection is known. Today the Hash → Enumerable and ArithmeticSequence → Enumerator
498
+ # projections are hand-rolled; a general RBS-driven implementation that consults
499
+ # `definition.ancestors[i].args` for arbitrary subclass / module-include relations is the principled
500
+ # follow-up.
475
501
  def project_to_target_arity(self_type, other_type)
476
502
  return nil if self_type.type_args.size == other_type.type_args.size
477
503
  return nil if self_type.type_args.empty? || other_type.type_args.empty?
@@ -881,8 +907,8 @@ module Rigor
881
907
  # `Refined[base, predicate]` accepts another type X when the base accepts the *base* of X *and* X is
882
908
  # provably contained in the predicate's value set. The base check is delegated to
883
909
  # `accepts(self.base, X.base)` so handlers like `accepts_nominal` see Nominal-vs-Nominal and return
884
- # their normal answer (the inner `accepts_nominal` does not register `Refined` / `Difference` as
885
- # direct other-shapes — projecting to the base is what makes the comparison meaningful).
910
+ # their normal answer (`accepts_nominal` projects a `Refined` / `Difference` other onto its base,
911
+ # which is what makes the comparison meaningful).
886
912
  #
887
913
  # Provability rules in gradual mode (the conservative analogue of `accepts_difference`):
888
914
  #
@@ -1003,9 +1029,12 @@ module Rigor
1003
1029
 
1004
1030
  # Tuple[A1..An] accepts:
1005
1031
  # - Tuple[B1..Bn] when arities match and each Ai accepts Bi (covariant per-position).
1006
- # - Anything else: no (we cannot prove the arity from a generic nominal alone).
1032
+ # - An `Array[E]` (see {#array_element_verdict}): maybe, or no on an element contradiction.
1033
+ # - Anything else: no.
1007
1034
  def accepts_tuple(self_type, other_type, mode)
1008
1035
  unless other_type.is_a?(Type::Tuple)
1036
+ return array_element_verdict(self_type, other_type, mode) if array_carrier?(other_type)
1037
+
1009
1038
  return Type::AcceptsResult.no(
1010
1039
  mode: mode,
1011
1040
  reasons: "Tuple does not accept #{other_type.class}"
@@ -1025,6 +1054,38 @@ module Rigor
1025
1054
  combine_arg_results(per_element, mode)
1026
1055
  end
1027
1056
 
1057
+ # An `Array` nominal, directly or as the base of a `Difference` (`non-empty-array[E]`). Its arity is
1058
+ # unknown, so a tuple cannot reject it on length: `URI.encode_www_form` takes
1059
+ # `Enumerable[[_ToS, _ToS]]` and is handed the `Array[Array[String]]` that `URI.decode_www_form`
1060
+ # answers. An `Array` subclass is not read, as {#gradual_hash?} does not read a `Hash` subclass.
1061
+ def array_carrier?(type)
1062
+ nominal = type.is_a?(Type::Difference) ? type.base : type
1063
+ nominal.is_a?(Type::Nominal) && nominal.class_name == "Array"
1064
+ end
1065
+
1066
+ # `maybe`, unless the element type rules the tuple out at any length: some position accepts no member
1067
+ # of `E` (`[Integer, Integer]` never takes an `Array[String]`). A raw `Array` stays `maybe`, and so does
1068
+ # an element with a gradual arm, since no position rejects a `Dynamic` member. The accepted cost (ADR-5)
1069
+ # is that a wrong-length `Array[E]` passed or returned where a tuple is declared goes unreported.
1070
+ def array_element_verdict(self_type, other_type, mode)
1071
+ nominal = other_type.is_a?(Type::Difference) ? other_type.base : other_type
1072
+ element = nominal.type_args.first
1073
+ unless element.nil?
1074
+ members = ContentJoin.union_members(element)
1075
+ refused = self_type.elements.find do |formal|
1076
+ members.all? { |member| accepts(formal, member, mode: mode).no? }
1077
+ end
1078
+ if refused
1079
+ return Type::AcceptsResult.no(
1080
+ mode: mode,
1081
+ reasons: "tuple member #{refused.describe(:short)} rejects #{element.describe(:short)}"
1082
+ )
1083
+ end
1084
+ end
1085
+
1086
+ unread_shape_maybe(mode, "tuple arity unknown for #{other_type.describe(:short)}", :array)
1087
+ end
1088
+
1028
1089
  # HashShape{k1: T1, ...} accepts another HashShape when every required key of self is required on
1029
1090
  # the other side and Ti accepts Ui (depth covariant). Optional keys may be absent on the other side;
1030
1091
  # when present, their values are checked. A closed self rejects known extra keys, and answers at most
@@ -1056,7 +1117,7 @@ module Rigor
1056
1117
  result = combine_arg_results(per_entry, mode)
1057
1118
  return result unless result.yes? && self_type.closed? && other_type.open?
1058
1119
 
1059
- unread_hash_maybe(mode, "HashShape closed target cannot check open source")
1120
+ unread_shape_maybe(mode, "HashShape closed target cannot check open source", :hash)
1060
1121
  end
1061
1122
 
1062
1123
  # A `Hash` nominal — directly or as the base of a `Difference` (`non-empty-hash[K, V]`) — that is raw or
@@ -1085,7 +1146,7 @@ module Rigor
1085
1146
  refused = refused_value_keys(self_type, value_type, mode)
1086
1147
  return hash_shape_no(mode, "Hash value type refused for required keys: #{refused.inspect}") if refused.any?
1087
1148
 
1088
- unread_hash_maybe(mode, "HashShape cannot check #{other_type.describe(:short)}")
1149
+ unread_shape_maybe(mode, "HashShape cannot check #{other_type.describe(:short)}", :hash)
1089
1150
  end
1090
1151
 
1091
1152
  def unholdable_keys(self_type, key_type, mode)
@@ -211,9 +211,8 @@ module Rigor
211
211
  cursor
212
212
  end
213
213
 
214
- # `|*rest|` binds an Array of the leftover positional arguments. The expected-types array is
215
- # per-position, not per-rest; we cannot reliably pick a single element type for rest, so we default to
216
- # `Array[Dynamic[Top]]`. Element-type precision for rest parameters is deferred (demand-gated).
214
+ # `|*rest|` binds an Array of the leftover positional arguments: `Array[Dynamic[Top]]`, unless an
215
+ # auto-splat answered a rest type ({BlockAutoSplat}). The expected types are per-position, not per-rest.
217
216
  def bind_rest(params_node, bindings)
218
217
  rest = params_node.rest
219
218
  return unless rest.respond_to?(:name) && rest&.name