rigortype 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -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,8 @@ 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 "library_probe"
20
+ require_relative "environment/project_path_gems"
19
21
  require_relative "environment/source_rbs_synthesis"
20
22
  require_relative "plugin/isolation"
21
23
  require_relative "cache/engine_source"
@@ -39,7 +41,7 @@ module Rigor
39
41
  DEFAULT_PROJECT_SIG_DIR = "sig"
40
42
  private_constant :DEFAULT_PROJECT_SIG_DIR
41
43
 
42
- # `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required below) so the
44
+ # `DEFAULT_LIBRARIES` is defined in the light `environment/default_libraries.rb` (required above) so the
43
45
  # ADR-87 WD4 boot-slimming probe can read it without loading the inference engine.
44
46
 
45
47
  # ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id that ships the SAME core-ext RBS. When
@@ -48,6 +50,19 @@ module Rigor
48
50
  # `RbsCoverageReport` reports.
49
51
  GEM_OVERLAY_PLUGIN_IDS = { "activesupport" => "activesupport-core-ext" }.freeze
50
52
 
53
+ # Issue #2857 — the `RbsCoverageReport` sources each gem's bundled overlay may load under, when they
54
+ # differ from the ADR-72 `:missing` default every unlisted overlay keeps. `addressable` is the first
55
+ # exception: its overlay exists to CORRECT the `Addressable::URI.parse` signature `rbs collection`
56
+ # ships (`parse(nil)` returns nil; the collection declares `(String | URI) -> URI`), so it loads only
57
+ # under `:rbs_collection` coverage, where the deferred-signature plan composes it over the collection
58
+ # copy as an overload continuation. Under `:missing` coverage — no sigs at all — it stays unloaded:
59
+ # the overlay's partial `Addressable::URI` declaration would make every member it omits a false
60
+ # `call.undefined-method`, a worse answer than the `Dynamic` silence a sig-less gem already has.
61
+ # Frozen arrays, not Sets: `Ractor.shareable?` rejects a Set even frozen (ractor_readiness_spec).
62
+ GEM_OVERLAY_COVERAGE_SOURCES = { "addressable" => [:rbs_collection].freeze }.freeze
63
+ GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES = [:missing].freeze
64
+ private_constant :GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES
65
+
51
66
  attr_reader :class_registry, :rbs_loader, :plugin_registry, :dependency_source_index,
52
67
  :reporters, :name_scope,
53
68
  :synthetic_method_index, :project_patched_methods
@@ -117,6 +132,13 @@ module Rigor
117
132
  # stage that failed and the other stage still runs, so this getter never raises into its callers —
118
133
  # neither into a file's `analyze_body` rescue nor out of the run-owned demands, which have no rescue
119
134
  # above them at all.
135
+ # Issue #2883 — builds the RBS environment now, if it is not built yet, so typing never builds it. A no-op for an
136
+ # environment with no loader. Answers the environment.
137
+ def prepare_for_typing!
138
+ rbs_loader&.prepare_for_typing!
139
+ self
140
+ end
141
+
120
142
  def hkt_registry
121
143
  @hkt_registry_holder.fetch do
122
144
  pre_scan = pre_scan_hkt_registry
@@ -146,7 +168,7 @@ module Rigor
146
168
  # code (`Plugin::Registry#hkt_overlay_registry` reads each manifest); before this it sat one line ABOVE
147
169
  # the #784 rescue, so a raise there escaped {#hkt_registry} entirely. Post-#788 that is not a per-file
148
170
  # 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`
171
+ # is re-attempted at every demand, and the run-owned demands (`Runner::RunLevelFacts::Collect.hkt_scan_failure`
150
172
  # after the file loop, `Analysis::WorkerSession#drain_reporters`) sit outside any rescue.
151
173
  #
152
174
  # The degradation is narrower than the scan's: only the plugin overlay is dropped, and the RBS scan
@@ -246,11 +268,14 @@ module Rigor
246
268
  end
247
269
 
248
270
  class << self
271
+ # Issue #2883 — the shared default environment is built here too, not on the first `type_of` that needs it, so
272
+ # code that types against it (`Scope.empty`, the narrowing defaults, the incremental session's summary
273
+ # evaluator) never builds it from inside typing. A non-main Ractor keeps the lazy build.
249
274
  def default
250
275
  @default ||= new(
251
276
  rbs_loader: RbsLoader.default,
252
277
  hkt_registry: Builtins::HktBuiltins.registry
253
- ).freeze
278
+ ).freeze.tap { |environment| environment.prepare_for_typing! if Ractor.main? }
254
279
  end
255
280
 
256
281
  # Builds an Environment that consults the project's local signatures and any opt-in stdlib libraries on
@@ -265,8 +290,14 @@ module Rigor
265
290
  # `nil` (the default), the canonical project layout `<root>/sig` is used if it exists, otherwise no
266
291
  # signature path is loaded.
267
292
  # @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.
293
+ # {Environment::RbsLoader} so constant lookups consult the cache. Pass `nil` (the default) to skip
294
+ # caching for this environment.
295
+ # @param core_root — issue #2517 — a core signature directory loaded in place of the `rbs` gem's `core/`
296
+ # ({Configuration#core_root}); nil (the default) loads the gem's core.
297
+ # @param eager — issue #2883 — whether the RBS environment is built here, before anything types. Typing then
298
+ # never builds it, so the core-root Frame the build reads (ADR-129 G1') is never read inside a `type_of`. The
299
+ # one caller that passes `false` is the runner's pre-cache build, which a warm result-cache hit must not pay
300
+ # for; it calls {#prepare_for_typing!} on the analysis path.
270
301
  # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
271
302
  def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil, # rubocop:disable Metrics/AbcSize
272
303
  plugin_registry: nil, dependency_source_index: nil,
@@ -276,7 +307,8 @@ module Rigor
276
307
  bundler_lockfile: nil,
277
308
  rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
278
309
  synthetic_method_index: nil, project_patched_methods: nil,
279
- source_files: [], locked_gems: nil, required_features: nil)
310
+ source_files: [], locked_gems: nil, required_features: nil, core_root: nil,
311
+ paths: nil, exclude_patterns: nil, exclude_base: nil, eager: true)
280
312
  resolved_paths = project_signature_roots(signature_paths, root: root)
281
313
  # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
282
314
  # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
@@ -309,38 +341,55 @@ module Rigor
309
341
  )
310
342
  # O4 Layer 3 slice 2 — when `rbs collection install` has been run for the target project, parse the
311
343
  # resulting `rbs_collection.lock.yaml` and feed each gem's `<collection_path>/<name>/<version>/`
312
- # directory into `signature_paths:`. Stdlib-typed entries are skipped (already covered by
313
- # `DEFAULT_LIBRARIES`), as are gems whose RBS rigor already loads from another source —
344
+ # directory into `signature_paths:`. Stdlib-typed entries have no such directory; they load as libraries
345
+ # (issue #2946, below). Skipped too are gems whose RBS rigor already loads from another source —
314
346
  # stdlib-extracted default gems (`cgi`, `logger`, …, shipped by the collection under a `git` source)
315
347
  # and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
316
348
  # both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
317
349
  # `RBS::DuplicatedDeclarationError` hazard).
318
350
  # Issue #1700 — plus the gated vendored directories this run's source activates ({#feature_tokens}).
319
- merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s) +
351
+ # Issue #2946 — plus the lockfile's `source: { type: stdlib }` entries, as libraries ({BaseLibraries.resolve}).
352
+ merged_libraries = (BaseLibraries.resolve(libraries, rbs_collection_lockfile: rbs_collection_lockfile,
353
+ rbs_collection_auto_detect: rbs_collection_auto_detect,
354
+ root: root) +
320
355
  feature_tokens(required_features, source_files, libraries)).uniq
321
- skip_gem_names = collection_skip_gem_names(merged_libraries)
356
+ # Issue #2441 — plus the project's own path gems ({ProjectPathGems}), whose collection copy would
357
+ # double-declare the project's `sig/`.
358
+ project_gems = ProjectPathGems.names(locked, paths: paths, root: root, exclude_patterns: exclude_patterns,
359
+ exclude_base: exclude_base || root)
360
+ skip_gem_names = collection_skip_gem_names(merged_libraries) + project_gems.to_a
322
361
  collection_paths = RbsCollectionDiscovery.discover(
323
362
  lockfile_path: rbs_collection_lockfile,
324
363
  project_root: root,
325
364
  auto_detect: rbs_collection_auto_detect,
326
365
  skip_gem_names: skip_gem_names
327
366
  ).map(&:to_s)
367
+ # Issue #3030 — a gem the project's `signature_paths:` already loads a copy of (a gem_rbs_collection working
368
+ # tree next to its `_test/` lockfile) loads from there alone. The coverage classification below still sees
369
+ # the whole list: the gem has RBS either way.
370
+ covered_collection_paths = collection_paths
371
+ collection_paths = RbsCollectionDiscovery.without_signature_path_copies(collection_paths, resolved_paths)
372
+ # Issue #2978 — the collection copies of the locked gems whose own `sig/` the bundle discovery loaded.
373
+ behind_gem_sig_paths = collection_paths_behind_gem_sigs(collection_paths, gem_sig_paths, locked)
328
374
  # ADR-25 — RBS signature directories contributed by loaded plugins via their manifest
329
375
  # `signature_paths:`. Resolved to absolute dirs by `Plugin::Base#signature_paths`; additive, ranked
330
376
  # below the user's explicit `signature_paths:` and above the opportunistic bundle / collection
331
377
  # discovery. A duplicate-declaration conflict degrades through the same O7 failure-memo path.
332
378
  plugin_sig_paths = plugin_registry ? plugin_registry.signature_paths.map(&:to_s) : []
333
- # ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem that ships no RBS through any
334
- # resolution path (`:missing`) and has a Rigor-bundled overlay, load that overlay so its core-class
335
- # extensions resolve (e.g. `Integer#minutes` on a Rails project) — turning a systematic false
336
- # `call.undefined-method` into a no-op while a project WITHOUT the gem still sees the genuine
337
- # diagnostic. Appended last so any RBS the project already supplies wins, and skipped for a gem whose
338
- # opt-in plugin twin is REACHABLE — loaded as a plugin, or wired in by `signature_paths:` (#672) —
339
- # so the two never both declare the methods. The paths ride in `loader_signature_paths`, so the env
340
- # cache descriptor digests them for free.
379
+ # ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem whose coverage source its
380
+ # overlay may load under ({GEM_OVERLAY_COVERAGE_SOURCES} — `:missing` for every overlay but
381
+ # addressable's `:rbs_collection` correction, #2857) and that has a Rigor-bundled overlay, load that
382
+ # overlay so its core-class extensions resolve (e.g. `Integer#minutes` on a Rails project) —
383
+ # turning a systematic false `call.undefined-method` into a no-op while a project WITHOUT the gem
384
+ # still sees the genuine diagnostic. Appended last so any RBS the project already supplies wins,
385
+ # and skipped for a gem whose opt-in plugin twin is REACHABLE — loaded as a plugin, or wired in by
386
+ # `signature_paths:` (#672) — so the two never both declare the methods. The paths ride in
387
+ # `loader_signature_paths`, so the env cache descriptor digests them for free.
388
+ # Issue #2441 — the project's own path gems are neither missing RBS nor overlay-eligible: the project's
389
+ # `sig/` declares them, and an overlay (`data/gem_overlay/activesupport`) would collide with it.
341
390
  missing_gems, overlay_paths = missing_gems_and_overlay_paths(
342
- locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
343
- rbs_collection_paths: collection_paths, plugin_registry: plugin_registry,
391
+ locked: locked.except(*project_gems), default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
392
+ rbs_collection_paths: covered_collection_paths, plugin_registry: plugin_registry,
344
393
  signature_paths: resolved_paths, bundle_root: bundle_root
345
394
  )
346
395
  loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
@@ -364,9 +413,19 @@ module Rigor
364
413
  # Issue #610 — the plugin-contributed subset is loaded LAST and may stand down against a colliding
365
414
  # generic arity. Only the bundled plugins' own `sig/` is deferred: the user's `signature_paths:`,
366
415
  # the bundle walk and `rbs collection install` all stay authoritative.
367
- deferred_signature_paths: plugin_sig_paths,
416
+ # Issue #2974 — the ADR-72 gem overlays are Rigor-shipped too, and deferred with them: an overlay member
417
+ # the project's `sig/` also declares (`Integer#ordinalize`, `ActiveSupport::NumericWithFormat#to_fs`)
418
+ # stands down member by member (#2422) instead of failing the class's definition build.
419
+ deferred_signature_paths: plugin_sig_paths + overlay_paths,
368
420
  project_signature_paths: resolved_paths,
369
- required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths)
421
+ # Issue #2978 — a gem's collection copy loads behind the gem's own `sig/`: each member that `sig/` (or any
422
+ # other eagerly loaded signature file) declares stands down from the copy (#2422), and the rest loads.
423
+ subordinate_signature_paths: behind_gem_sig_paths,
424
+ # Issue #3030 — the collection gem directories, loaded as rbs loads a library: without the files below a
425
+ # `_`-prefixed directory (each gem's `_test/` fixtures).
426
+ collection_signature_paths: collection_paths,
427
+ required_features: required_features || RequiredFeatures.scan(source_files, sig_paths: resolved_paths),
428
+ core_root: core_root
370
429
  )
371
430
  # Issues #824 / #1075 — the rows ADR-112 WD5's consistency rule reports for members the project's
372
431
  # own `sig/` and an inline source both declare: an error per contradiction (or per refinement that
@@ -376,6 +435,12 @@ module Rigor
376
435
  # on every run, so the member-level outcome needs no plumbing of its own. Costs nothing for a project
377
436
  # with no inline RBS: the loader's reader short-circuits on an empty `virtual_rbs`.
378
437
  record_member_consistency(loader, source_rbs_synthesis_reporter, root)
438
+ # Issue #2517 — a selected core is built here, not lazily. The env is first demanded from inside a file's
439
+ # analysis, under rescues that turn any raise into one `internal analyzer error` row per file; building it
440
+ # now raises the {CoreRootError} once, before analysis, where the CLI reports it as a configuration error.
441
+ # Issue #2883 — the default core is built here too (`eager:`), so no `type_of` builds the environment.
442
+ # A non-main Ractor worker keeps the lazy build: the `rbs` gem's unshareable constants are unreadable there.
443
+ eager && Ractor.main? ? loader.prepare_for_typing! : loader.verify_core_root!
379
444
  # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
380
445
  # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
381
446
  # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
@@ -399,10 +464,20 @@ module Rigor
399
464
  # The project-owned `sig/` roots a run reads: the explicit `signature_paths:` when given, else the
400
465
  # auto-detected `<root>/sig` (or none). Public so {Cache::IncrementalSnapshot} digests exactly the
401
466
  # directories the environment loads, including the auto-detected one a `nil` `signature_paths:` hides.
467
+ # The default branch is {project_signature_root_candidates} narrowed to the directories that exist.
402
468
  def project_signature_roots(signature_paths, root: Dir.pwd)
403
469
  signature_paths || default_signature_paths(root)
404
470
  end
405
471
 
472
+ # Issue #1619 — the single spelling of "the configured `signature_paths:`, else the conventional `sig`
473
+ # directory", as the CANDIDATES a run watches whether or not they exist yet. {project_signature_roots}
474
+ # narrows this to what the loader reads (a default only once it is a directory); the run slot's presence
475
+ # rows and the incremental write guard take it whole, because a `sig/` appearing mid-run must move the
476
+ # fingerprint and refuse the slot write — a question only a row for a path that need not exist can ask.
477
+ def project_signature_root_candidates(signature_paths)
478
+ signature_paths || [DEFAULT_PROJECT_SIG_DIR]
479
+ end
480
+
406
481
  private
407
482
 
408
483
  # Issue #1700 — the `RequiredFeatures` library tokens for the gated vendored directories this run asks for:
@@ -416,13 +491,31 @@ module Rigor
416
491
  # The gem names an `rbs collection` copy must not double-declare against: the library list and every
417
492
  # vendored directory except a `RequiredFeatures`-gated one. A gated directory stands down for any other
418
493
  # copy of its declarations (`GatedSignatureGuard`), so the collection's copy is kept and wins.
494
+ #
495
+ # Issue #2971 — a `libraries:` name that is not an RBS library (`foogem`, collection-installed) is dropped by
496
+ # the loader's `has_library?` filter, so it declares nothing to double-declare against; it stays out of the
497
+ # skip set so the collection's copy still loads. Issue #2978 — that holds for a gem whose own `sig/` the bundle
498
+ # loaded too: its collection copy loads behind that `sig/` ({.collection_paths_behind_gem_sigs}).
419
499
  def collection_skip_gem_names(library_names)
420
- library_names + RbsLoader.vendored_gem_names.reject { |name| RequiredFeatures.gated_dir?(name) }
500
+ LibraryProbe.loadable(library_names) + RbsLoader.vendored_gem_names.reject do |name|
501
+ RequiredFeatures.gated_dir?(name)
502
+ end
503
+ end
504
+
505
+ # Issue #2978 — the `collection_paths` (`<collection>/<name>/<version>/`) of the locked gems whose own `sig/` is
506
+ # among `gem_sig_paths` ({BundleSigDiscovery.locked_names_of}, keyed by name and version).
507
+ def collection_paths_behind_gem_sigs(collection_paths, gem_sig_paths, locked)
508
+ return [] if collection_paths.empty? || gem_sig_paths.empty?
509
+
510
+ names = BundleSigDiscovery.locked_names_of(gem_sig_paths.map { |dir| Pathname.new(dir) }, locked).to_set
511
+ collection_paths.select { |path| names.include?(Pathname.new(path).parent.basename.to_s) }
421
512
  end
422
513
 
423
514
  def default_signature_paths(root)
424
- sig = Pathname(root) / DEFAULT_PROJECT_SIG_DIR
425
- sig.directory? ? [sig] : []
515
+ project_signature_root_candidates(nil).filter_map do |candidate|
516
+ path = Pathname(root) / candidate.to_s
517
+ path if path.directory?
518
+ end
426
519
  end
427
520
 
428
521
  # ADR-82 WD9 — the resolved bundle root for the missing-gem constant index, doubling (ADR-90) as the
@@ -445,9 +538,12 @@ module Rigor
445
538
  bundle_root
446
539
  end
447
540
 
448
- # The `:missing` classification (locked gems with no RBS through any resolution path), computed once
449
- # and consumed twice: as `[gem_name, version, variants]` entries for the ADR-82 WD9 constant-ownership
450
- # index, and as the eligible-gem set for the ADR-72 overlay resolution. Returns `[entries, overlay_paths]`.
541
+ # The per-gem RBS coverage classification (locked gems against every resolution path), computed
542
+ # once and consumed twice: its `:missing` rows become `[gem_name, version, variants]` entries for
543
+ # the ADR-82 WD9 constant-ownership index, and the full row set feeds the ADR-72 overlay resolution,
544
+ # which admits a gem when its row's source is one the gem's overlay may load under
545
+ # ({GEM_OVERLAY_COVERAGE_SOURCES} — `:missing` by default, `:rbs_collection` for addressable).
546
+ # Returns `[entries, overlay_paths]`.
451
547
  def missing_gems_and_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
452
548
  rbs_collection_paths:, plugin_registry:, signature_paths: [],
453
549
  bundle_root: nil)
@@ -455,10 +551,10 @@ module Rigor
455
551
  return unlocked_missing_gems(default_libraries, bundle_sig_paths, rbs_collection_paths, bundle_root)
456
552
  end
457
553
 
458
- rows = missing_rows(locked, default_libraries, bundle_sig_paths, rbs_collection_paths)
554
+ coverage = coverage_rows(locked, default_libraries, bundle_sig_paths, rbs_collection_paths)
555
+ rows = coverage.select { |row| row.source == :missing }
459
556
  overlays = gem_overlay_paths(
460
- missing_gem_names: rows.map(&:gem_name), plugin_registry: plugin_registry,
461
- signature_paths: signature_paths
557
+ coverage: coverage, plugin_registry: plugin_registry, signature_paths: signature_paths
462
558
  )
463
559
  [index_entries(rows, locked), overlays]
464
560
  end
@@ -485,14 +581,20 @@ module Rigor
485
581
  end
486
582
 
487
583
  def missing_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
584
+ coverage_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
585
+ .select { |row| row.source == :missing }
586
+ end
587
+
588
+ def coverage_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
488
589
  RbsCoverageReport.classify(
489
590
  locked_gems: gems, default_libraries: default_libraries,
490
591
  bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
491
- ).select { |row| row.source == :missing }
592
+ )
492
593
  end
493
594
 
494
595
  # 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
596
+ # its coverage row's source is one the gem's overlay may load under ({GEM_OVERLAY_COVERAGE_SOURCES};
597
+ # `:missing` for every overlay the map does not name) and its conflicting opt-in plugin twin (if
496
598
  # any) is NOT already reachable. Returns `[Pathname]`, deterministically ordered, or `[]` when no
497
599
  # eligible gem.
498
600
  #
@@ -506,20 +608,29 @@ module Rigor
506
608
  # all); `RbsLoader.bundled_overlay_twin_signatures_loaded?` answers the filesystem half, beside the
507
609
  # overlay-side answer to the same question, and carries the reasoning for why it asks what an entry
508
610
  # LOADS rather than what it looks like.
509
- def gem_overlay_paths(missing_gem_names:, plugin_registry:, signature_paths: [])
510
- return [] if missing_gem_names.empty?
611
+ def gem_overlay_paths(coverage:, plugin_registry:, signature_paths: [])
612
+ return [] if coverage.empty?
511
613
 
512
614
  loaded_ids = plugin_registry ? plugin_registry.ids.to_set : Set.new
513
- eligible = missing_gem_names.reject do |gem_name|
514
- plugin_id = GEM_OVERLAY_PLUGIN_IDS[gem_name]
515
- next false unless plugin_id
615
+ eligible = coverage.filter_map do |row|
616
+ next unless overlay_coverage_source?(row.gem_name, row.source)
617
+
618
+ plugin_id = GEM_OVERLAY_PLUGIN_IDS[row.gem_name]
619
+ next if plugin_id &&
620
+ (loaded_ids.include?(plugin_id) ||
621
+ RbsLoader.bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths))
516
622
 
517
- loaded_ids.include?(plugin_id) ||
518
- RbsLoader.bundled_overlay_twin_signatures_loaded?(plugin_id, signature_paths)
519
- end.sort
623
+ row.gem_name
624
+ end.uniq.sort
520
625
  RbsLoader.gem_overlay_sig_paths(eligible)
521
626
  end
522
627
 
628
+ # Whether `source` — a `RbsCoverageReport` source — is one `gem_name`'s bundled overlay may load
629
+ # under: the gem's {GEM_OVERLAY_COVERAGE_SOURCES} entry when it has one, else ADR-72's `:missing`.
630
+ def overlay_coverage_source?(gem_name, source)
631
+ (GEM_OVERLAY_COVERAGE_SOURCES[gem_name] || GEM_OVERLAY_DEFAULT_COVERAGE_SOURCES).include?(source)
632
+ end
633
+
523
634
  # ADR-32 WD4 + WD5 — for each project source file, invoke every plugin-registered synthesizer once and
524
635
  # collect non-nil returns. The returned array is `[[virtual_filename, rbs_source], ...]`; the loader
525
636
  # threads it through to `RbsLoader.new(virtual_rbs: ...)`.
@@ -644,6 +755,8 @@ module Rigor
644
755
  def record_member_undecided(reporter, record, root)
645
756
  plugin_id, path = split_virtual_source_name(record.virtual_name)
646
757
  return if path.nil?
758
+ return record_variable_undecided(reporter, record, root, plugin_id, path) if
759
+ MemberConsistency.variable_kind?(record.kind)
647
760
 
648
761
  reporter.record(
649
762
  plugin_id: plugin_id, path: path, kind: :not_honoured,
@@ -656,8 +769,22 @@ module Rigor
656
769
  )
657
770
  end
658
771
 
772
+ # Issue #2440 — an instance variable declared by both sides with different types. `sig/` binds
773
+ # (the coordinator's ruling on #2440, ADR-5): the row is `:info`, never `rbs.contradicting-signature`.
774
+ def record_variable_undecided(reporter, record, root, plugin_id, path)
775
+ reporter.record(
776
+ plugin_id: plugin_id, path: path, kind: :not_honoured,
777
+ message: "`#{member_label(record)}` is also declared in " \
778
+ "`#{path_relative_to(record.signature_path, root)}` with a different type " \
779
+ "(#{record.detail}), so the `.rbs` declaration binds and the inline one was dropped. " \
780
+ "RBS rejects two declarations of one instance variable, so only one can stay; " \
781
+ "regenerate the committed `.rbs` (`rbs-inline --output`) or align the two types."
782
+ )
783
+ end
784
+
785
+ # `Demo#m` / `Demo.m` for a method, `Demo#@x` / `Demo.@x` for an instance / class instance variable.
659
786
  def member_label(record)
660
- separator = record.kind == :singleton ? "." : "#"
787
+ separator = %i[singleton class_instance_variable].include?(record.kind) ? "." : "#"
661
788
  "#{record.class_name}#{separator}#{record.method_name}"
662
789
  end
663
790
 
@@ -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
  #