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,6 +2,8 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../type"
6
+
5
7
  module Rigor
6
8
  module Inference
7
9
  # Marks a value whose *nil-freeness* rests on Rigor's deliberate choice to ignore core RBS's
@@ -18,7 +20,7 @@ module Rigor
18
20
  # that happens to have been produced optimistically.
19
21
  #
20
22
  # Issue #286: the `if` / `unless` branch elision is one consumer of `Narrowing.predicate_certainty`; the
21
- # other two are `flow.always-truthy-condition` and the `&&` / `||` `constant_value_polarity` gate, and the
23
+ # other two are `flow.always-truthy-condition` and `StatementEvaluator#right_operand_dead?`, and the
22
24
  # spec passage above binds all three. This channel is what lets a certainty judgment tell the two apart.
23
25
  #
24
26
  # Issue #313: the mark is attached to a *value* — the call node that produced it, or the local / ivar it
@@ -102,24 +104,147 @@ module Rigor
102
104
  # `StatementEvaluator` and `AlwaysTruthyConditionCollector` all route here, so the three consumers the
103
105
  # spec binds cannot drift apart.
104
106
  #
105
- # Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read (or
106
- # a write in value position, `if (x = MAP[k])`) resolves through, then a safe-navigation call and the
107
- # predicate-fold derivation issue #313 added.
107
+ # Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read resolves
108
+ # through, then a write in value position and a safe-navigation call and the predicate-fold derivation issue
109
+ # #313 added.
108
110
  def resolve(node, scope)
109
111
  return nil if node.nil? || scope.nil?
110
112
 
111
- recorded = scope.optimistic_origins[node]
113
+ recorded = scope.optimistic_origin(node)
112
114
  return recorded if recorded
113
115
 
116
+ resolve_node(node, scope)
117
+ end
118
+
119
+ # The shape dispatch {.resolve} applies once the node-level mark is ruled out.
120
+ def resolve_node(node, scope)
114
121
  case node
115
- when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode then scope.optimistic_local(node.name)
116
- when Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode then scope.optimistic_ivar(node.name)
122
+ when Prism::LocalVariableReadNode then scope.optimistic_local(node.name)
123
+ when Prism::InstanceVariableReadNode then scope.optimistic_ivar(node.name)
124
+ when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
125
+ Prism::GlobalVariableWriteNode, Prism::ClassVariableWriteNode,
126
+ Prism::MultiWriteNode, Prism::LocalVariableOrWriteNode,
127
+ Prism::InstanceVariableOrWriteNode, Prism::ClassVariableOrWriteNode,
128
+ Prism::GlobalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
129
+ Prism::InstanceVariableAndWriteNode, Prism::ClassVariableAndWriteNode,
130
+ Prism::GlobalVariableAndWriteNode
131
+ resolve_write_form(node, scope)
117
132
  when Prism::AndNode, Prism::OrNode then resolve(node.left, scope) || resolve(node.right, scope)
118
- when Prism::CallNode then resolve_through_safe_navigation(node, scope) || resolve_through_predicate(node, scope)
133
+ when Prism::CallNode
134
+ resolve_attribute_write(node, scope) || resolve_through_safe_navigation(node, scope) ||
135
+ resolve_through_predicate(node, scope)
119
136
  when Prism::ParenthesesNode then resolve_through_parentheses(node, scope)
120
137
  end
121
138
  end
122
139
 
140
+ # The write forms in value position: the plain and multiple writes whose value is the
141
+ # right-hand side, and the `||=` / `&&=` spellings whose old binding participates.
142
+ def resolve_write_form(node, scope)
143
+ case node
144
+ when Prism::LocalVariableOrWriteNode, Prism::InstanceVariableOrWriteNode,
145
+ Prism::ClassVariableOrWriteNode, Prism::GlobalVariableOrWriteNode
146
+ resolve_or_write(node, scope)
147
+ when Prism::LocalVariableAndWriteNode, Prism::InstanceVariableAndWriteNode,
148
+ Prism::ClassVariableAndWriteNode, Prism::GlobalVariableAndWriteNode
149
+ resolve_and_write(node, scope)
150
+ else
151
+ resolve_write(node, scope)
152
+ end
153
+ end
154
+
155
+ # Issue #2617 — a write in value position (`if (v = h[k])`) yields its right-hand side, so the write's own
156
+ # optimism is the RHS's. The binding lookup alone could not see it from the scope a certainty consumer
157
+ # reads: the `if`'s entry scope predates the predicate's evaluation, so `if (v = h[k])` found no mark for
158
+ # `v` there and `flow.always-truthy-condition` fired on the optimistic read. Resolving `node.value` reaches
159
+ # the mark recorded on the read itself, which lives in the side table every scope shares; the binding lookup
160
+ # stays as the fallback for the post-write scope `branch_certainty` passes.
161
+ def resolve_write(node, scope)
162
+ resolve(node.value, scope) || optimistic_write_binding(node, scope)
163
+ end
164
+
165
+ # Only locals and ivars carry a binding mark; a global or class-variable write, and a
166
+ # multiple assignment's slots (marked through {.destructuring_marks} instead), have no
167
+ # `optimistic_*` table entry to fall back to, so they answer nil here.
168
+ def optimistic_write_binding(node, scope)
169
+ case node
170
+ when Prism::LocalVariableWriteNode then scope.optimistic_local(node.name)
171
+ when Prism::InstanceVariableWriteNode then scope.optimistic_ivar(node.name)
172
+ end
173
+ end
174
+
175
+ # Issue #3054 — `v ||= rhs` yields the OLD binding whenever it is truthy, so the write's
176
+ # value is the right-hand side's only on the paths where the old value is falsey. When the
177
+ # old binding is provably always truthy — its type has no falsey inhabitant and it carries
178
+ # no optimistic mark — the right-hand side is never reached and the expression's verdict is
179
+ # the old value's own proof, so resolving through `node.value` would silence a correct
180
+ # always-truthy verdict (`v = "x"; if (v ||= h[k])` still folds to `"x"`). Otherwise the
181
+ # verdict, when one folds, rests on the right-hand side's bet.
182
+ def resolve_or_write(node, scope)
183
+ binding_type, binding_mark = or_write_binding(node, scope)
184
+ return nil if always_truthy_type?(binding_type) && binding_mark.nil?
185
+
186
+ resolve(node.value, scope)
187
+ end
188
+
189
+ # Issue #3054 — `v &&= rhs` yields the right-hand side whenever the old binding is truthy;
190
+ # the old value itself wins only on its falsey edge, a result no always-truthy verdict can
191
+ # rest on. So the right-hand side's mark covers every constant verdict the expression folds
192
+ # to, joined by the old binding's own mark for the case where the miss — not the rvalue — is
193
+ # what makes the condition live (`v = h[k]; if (v &&= "lit")`).
194
+ def resolve_and_write(node, scope)
195
+ resolve(node.value, scope) || and_write_binding_mark(node, scope)
196
+ end
197
+
198
+ # The `[type, mark]` pair an `||=` reads its old value through: the scope binding and the
199
+ # optimistic mark it carries. Class- and global-variable or-writes have no mark table — the
200
+ # mark channel covers locals and ivars only — so their mark is always nil.
201
+ def or_write_binding(node, scope)
202
+ case node
203
+ when Prism::LocalVariableOrWriteNode
204
+ [scope.local(node.name), scope.optimistic_local(node.name)]
205
+ when Prism::InstanceVariableOrWriteNode
206
+ [scope.ivar(node.name), scope.optimistic_ivar(node.name)]
207
+ when Prism::ClassVariableOrWriteNode
208
+ [scope.cvar(node.name), nil]
209
+ when Prism::GlobalVariableOrWriteNode
210
+ [scope.global(node.name), nil]
211
+ end
212
+ end
213
+
214
+ # The old binding's optimistic mark for an `&&=` — the same two tables {.or_write_binding}
215
+ # reads, with no type: `&&=` needs the mark alone.
216
+ def and_write_binding_mark(node, scope)
217
+ case node
218
+ when Prism::LocalVariableAndWriteNode then scope.optimistic_local(node.name)
219
+ when Prism::InstanceVariableAndWriteNode then scope.optimistic_ivar(node.name)
220
+ end
221
+ end
222
+
223
+ # Issue #3054 — `recv.x = rhs` and `recv[i] = rhs` yield the assigned value, which Prism
224
+ # spells as the call's last argument (`x=` takes it alone, `[]=` trails the indices). The
225
+ # compound spellings never reach here: `+=` / `[]=` operators and `||=` / `&&=` on an
226
+ # attribute or index are `CallOperatorWriteNode` / `IndexOperatorWriteNode` /
227
+ # `CallOrWriteNode` / `CallAndWriteNode` / `IndexOrWriteNode` / `IndexAndWriteNode`, not
228
+ # `CallNode`, and their value is the old slot's, not the right-hand side's.
229
+ def resolve_attribute_write(node, scope)
230
+ resolve(node.arguments&.arguments&.last, scope) if node.attribute_write?
231
+ end
232
+
233
+ # Whether `type` proves every inhabitant truthy — the empty falsey fragment of
234
+ # `Narrowing.narrow_falsey`, mirrored carrier for carrier so this leaf module need not
235
+ # require `Narrowing` (which already requires this file). A `nil` binding (unbound) and
236
+ # every carrier the falsey fragment cannot empty (`Top`, `Dynamic`, ranges, refinements,
237
+ # differences) answer false: only a proof counts.
238
+ def always_truthy_type?(type)
239
+ case type
240
+ when Type::Constant then !(type.value.nil? || type.value == false)
241
+ when Type::Nominal then !%w[NilClass FalseClass].include?(type.class_name)
242
+ when Type::Union then type.members.all? { |member| always_truthy_type?(member) }
243
+ when Type::Singleton, Type::Tuple, Type::HashShape then true
244
+ else type.is_a?(Type::Bot)
245
+ end
246
+ end
247
+
123
248
  # `recv&.m` is `nil` exactly when `recv` is (or when `m` answers `nil`), so it restates `recv`'s presence
124
249
  # and is as optimistic as `recv`. Only the call carrying the `&.` is derived: Ruby skips that one call and
125
250
  # no more, so `recv&.m.n` sends `n` to the `nil` and raises (or answers `NilClass#n`), and neither it nor
@@ -169,7 +294,7 @@ module Rigor
169
294
  # predicate's folded boolean only when the miss answers the other boolean (or cannot be told), so
170
295
  # `!recv&.empty?` — `!false` on a hit, `!nil` on a miss — stays `true`, and so does `x = recv&.empty?; !x`.
171
296
  def miss_answer(node, scope)
172
- return nil if scope.optimistic_origins[node]
297
+ return nil if scope.optimistic_origin(node)
173
298
 
174
299
  case node
175
300
  when Prism::LocalVariableReadNode then scope.optimistic_local_miss(node.name)
@@ -23,7 +23,7 @@ module Rigor
23
23
  def origin_for(scope, node)
24
24
  return nil if node.nil?
25
25
 
26
- scope.dynamic_origins[node] || propagated(scope, node)
26
+ scope.dynamic_origin(node) || propagated(scope, node)
27
27
  end
28
28
 
29
29
  def propagated(scope, node)
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "mutation_widening"
5
+ require_relative "receiver_alias"
6
+ require_relative "../type"
7
+
8
+ module Rigor
9
+ module Inference
10
+ # Issue #2499 rule 3 (part of #2494) — the stdlib calls that fill a String the caller passes in. After
11
+ # `io.read(n, buf)`, `io.readpartial(n, buf)`, `io.sysread(n, buf)` and `io.read_nonblock(n, buf)` the buffer
12
+ # holds what was read, and after `StringIO.new(s)` every later write through the StringIO lands in `s`. The
13
+ # argument (a local, instance, class or global variable read) is widened as an in-place `replace` of it would
14
+ # widen it, so a `buf = +""` seed stops reading `""` after the call.
15
+ #
16
+ # A built-in catalogue keyed by method name and argument position, not a receiver-typed dispatch: a method of
17
+ # another class with the same name and a second positional argument loses only precision on that argument.
18
+ module OutBufferMutation
19
+ # Method name => the zero-based position of the buffer argument.
20
+ READERS = { read: 1, readpartial: 1, sysread: 1, read_nonblock: 1 }.freeze
21
+ private_constant :READERS
22
+
23
+ module_function
24
+
25
+ # `scope` with the buffer argument of `call_node` widened, or `scope` itself when the call is no such call or the
26
+ # argument holds no String: `Rails.cache.read(key, opts)` passes an options Hash that keeps its shape.
27
+ def floor(call_node, scope)
28
+ buffer = buffer_argument(call_node)
29
+ return scope if buffer.nil?
30
+
31
+ getter, = MutationWidening::ALIAS_ACCESSORS[buffer.class]
32
+ return scope unless string_carrier?(scope.public_send(getter, ReceiverAlias.read_name(buffer)))
33
+
34
+ MutationWidening.widen_alias_read(:replace, buffer, scope)
35
+ end
36
+
37
+ # A `Constant<String>` or the `String` nominal, or a union of nothing else.
38
+ def string_carrier?(type)
39
+ case type
40
+ when Type::Constant then type.value.is_a?(String)
41
+ when Type::Nominal then type.class_name == "String"
42
+ when Type::Union then type.members.all? { |member| string_carrier?(member) }
43
+ else false
44
+ end
45
+ end
46
+
47
+ def buffer_argument(call_node)
48
+ arguments = call_node.arguments&.arguments
49
+ return nil if arguments.nil?
50
+
51
+ position = READERS[call_node.name]
52
+ position = 0 if position.nil? && string_io_new?(call_node)
53
+ return nil if position.nil?
54
+
55
+ argument = arguments[position]
56
+ MutationWidening::ALIAS_ACCESSORS.key?(argument.class) ? argument : nil
57
+ end
58
+
59
+ def string_io_new?(call_node)
60
+ return false unless call_node.name == :new
61
+
62
+ receiver = call_node.receiver
63
+ case receiver
64
+ when Prism::ConstantReadNode then receiver.name == :StringIO
65
+ when Prism::ConstantPathNode then receiver.parent.nil? && receiver.name == :StringIO
66
+ else false
67
+ end
68
+ end
69
+ private_class_method :buffer_argument, :string_io_new?, :string_carrier?
70
+ end
71
+ end
72
+ end
@@ -95,11 +95,10 @@ module Rigor
95
95
  # may type in a later round once its own argument resolves). Unresolved call sites do not
96
96
  # contribute.
97
97
  #
98
- # What it does NOT do yet (deferred — the check-wiring slice): feeding the table into the
99
- # `check` walk (only `coverage --protection` consumes it today), keyword / optional / rest
100
- # / block parameters, inherited-method receivers, top-level helpers, and a rigorous
101
- # closed-call-site-set proof (the union is optimistic over resolved sites — acceptable
102
- # because the only consumer is the protection metric, which runs no diagnostics).
98
+ # What it does NOT do yet: typing keyword / optional / rest / block parameters themselves,
99
+ # inherited-method receivers, top-level helpers, and a rigorous closed-call-site-set proof
100
+ # (the union is optimistic over resolved sites; the `check` walk tolerates that because an
101
+ # inferred parameter is only a lower bound, which the in-body rules decline on, WD6b).
103
102
  class ParameterInferenceCollector
104
103
  EMPTY = {}.freeze
105
104
 
@@ -120,7 +119,7 @@ module Rigor
120
119
  # @param files — project `.rb` paths to scan for call sites.
121
120
  # @param target_ruby — Prism parse target.
122
121
  # @param max_rounds — the WD5 fixpoint cap (1 = single-level).
123
- # @return => Hash{Symbol => Rigor::Type}}] frozen.
122
+ # @return the inferred parameter table, frozen.
124
123
  def self.collect(files:, environment:, target_ruby: nil, max_rounds: DEFAULT_ROUNDS, workers: 0)
125
124
  new(files: files, environment: environment, target_ruby: target_ruby,
126
125
  max_rounds: max_rounds, workers: workers).collect
@@ -13,7 +13,7 @@ module Rigor
13
13
  #
14
14
  # ADR-17 slice 2 gave `pre_eval:` a project-wide **method** registry ({ProjectPatchedMethods}). A
15
15
  # constant declared in the same file stayed invisible across the file boundary: `Scope#in_source_constants`
16
- # is per-file and is deliberately not part of `Runner#project_scope_seed_tables`, so only class-shaped
16
+ # was per-file and kept out of `Runner#project_scope_seed_tables`, so only class-shaped
17
17
  # constants (a `class` / `module` declaration, or one of the four meta-new forms `ScopeIndexer`'s
18
18
  # `record_class_new_constant_decl` promotes) ever crossed. `TIMEOUT = 30` in a listed file read as
19
19
  # `Dynamic[top]` everywhere else.
@@ -168,7 +168,7 @@ module Rigor
168
168
  end
169
169
 
170
170
  def classify_dynamic(type)
171
- type.static_facet.is_a?(Type::Top) ? :dynamic_top : :dynamic_specific
171
+ Type::Combinator.untyped?(type) ? :dynamic_top : :dynamic_specific
172
172
  end
173
173
 
174
174
  def worst_of(members)
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "../type"
4
4
  require_relative "external_ancestor_resolution"
5
+ require_relative "../typing_state"
6
+ require_relative "../memo_audit"
5
7
 
6
8
  module Rigor
7
9
  module Inference
@@ -19,8 +21,14 @@ module Rigor
19
21
  NO_TARGETS = [].freeze
20
22
  private_constant :NO_TARGETS
21
23
 
22
- MEMO_KEY = :__rigor_project_method_ownership_memo__
23
- private_constant :MEMO_KEY
24
+ # ADR-129 WD1 — a run-scoped Memo keyed on the identity of the discovery index and the environment, the only
25
+ # inputs the buckets' answers read.
26
+ MEMO = TypingState.memo(
27
+ :project_method_ownership,
28
+ run_scoped: true, audit: ["project method defines", "block runs", "ancestry non-escaping"],
29
+ reason: "answer buckets keyed on the discovery index and environment identities"
30
+ )
31
+ private_constant :MEMO
24
32
 
25
33
  module_function
26
34
 
@@ -61,9 +69,15 @@ module Rigor
61
69
  return false if scope.nil?
62
70
 
63
71
  by_method = (memo(scope)[kind][class_name] ||= {})
64
- return by_method[method_sym] if by_method.key?(method_sym)
72
+ if by_method.key?(method_sym)
73
+ return MemoAudit.check("project method defines", by_method[method_sym], class_name, method_sym) do
74
+ compute_defines?(class_name, method_sym, kind, scope)
75
+ end
76
+ end
65
77
 
66
- by_method[method_sym] = compute_defines?(class_name, method_sym, kind, scope)
78
+ by_method[method_sym] = MemoAudit.key_audit("project method defines") do
79
+ compute_defines?(class_name, method_sym, kind, scope)
80
+ end
67
81
  end
68
82
 
69
83
  # One slot per thread, keyed on the identity of the scope's frozen discovery index and its environment —
@@ -71,15 +85,19 @@ module Rigor
71
85
  # changes: the shape of `ExpressionTyper#class_graph_buckets`, whose rationale applies unchanged. A
72
86
  # `Scope` merges each file's discovery with the project pre-pass, so the slot turns over per file, and
73
87
  # an ADR-46 dependency the first lookup recorded belongs to the file every later hit serves. Pool
74
- # workers are separate processes, and a Ractor has its own `Thread.current`. The `:ancestry` bucket
75
- # belongs to {ClosureEscapeAnalyzer}'s ancestry step.
88
+ # workers are separate processes, and a Ractor has its own {TypingState}. The `:ancestry` bucket
89
+ # belongs to {ClosureEscapeAnalyzer}'s ancestry step. The `:block_runs` bucket belongs to
90
+ # {BlockRunCount}.
76
91
  def memo(scope)
92
+ return new_buckets if MemoAudit.enabled? && MemoAudit.suspended?
93
+
77
94
  discovery = scope.discovery
78
95
  environment = scope.environment
79
- slot = Thread.current[MEMO_KEY]
96
+ state = TypingState.current
97
+ slot = state[MEMO]
80
98
  unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
81
- slot = [discovery, environment, { instance: {}, singleton: {}, ancestry: {} }]
82
- Thread.current[MEMO_KEY] = slot
99
+ slot = [discovery, environment, new_buckets]
100
+ state[MEMO] = slot
83
101
  end
84
102
  slot[2]
85
103
  end
@@ -87,6 +105,9 @@ module Rigor
87
105
  class << self
88
106
  private
89
107
 
108
+ # Empty buckets: a new slot's, and a throwaway set while an audit recomputation bypasses the memo.
109
+ def new_buckets = { instance: {}, singleton: {}, ancestry: {}, block_runs: {}.compare_by_identity }
110
+
90
111
  def class_target(class_name, kind)
91
112
  class_name.nil? ? nil : [[class_name.to_s, kind]]
92
113
  end
@@ -36,6 +36,9 @@ module Rigor
36
36
  # `farther` is set only on an entry {#lookup_through_ancestors} returns: the other definers of the name farther
37
37
  # on the walk (entries, or {SOURCE_DEFINER} for a project-source `def`), which {#callable?} reads before it
38
38
  # refuses the call.
39
+ #
40
+ # `kind` stays truthful on a copy {#singleton_copy_entry} returns: the `module_function` singleton copy is a
41
+ # `:singleton` method even though the body it copied is an instance `def`.
39
42
  Entry = Data.define(:class_name, :method_name, :kind, :source_path, :source_line, :return_type, :alias_of,
40
43
  :visibility, :farther) do
41
44
  def initialize(class_name:, method_name:, kind:, source_path:, source_line:, return_type: nil, alias_of: nil,
@@ -49,7 +52,7 @@ module Rigor
49
52
  # written, with no lexical resolution, as a patch's class name is.
50
53
  Mixin = Data.define(:owner, :module_name, :kind)
51
54
 
52
- attr_reader :by_key, :write_census, :mixins, :overrides
55
+ attr_reader :by_key, :write_census, :mixins, :overrides, :singleton_copies
53
56
 
54
57
  # Each owner's prepended and included modules, the most recent first.
55
58
  def self.mixins_by_owner(mixins)
@@ -69,7 +72,12 @@ module Rigor
69
72
  # @param overrides — issue #1708: `[class_name, method_name, kind] => visibility` for a named visibility call
70
73
  # (`public :kp` in `class Integer`) on a method the class inherits rather than binds, which Ruby copies onto
71
74
  # the class with that visibility.
72
- def initialize(entries: [], write_census: GlobalWriteCensus::EMPTY, mixins: [], overrides: {})
75
+ # @param singleton_copies — issue #2108: `[class_name, method_name]` pairs a `module_function :x` wrote for a
76
+ # name the class inherits rather than binds — Ruby's public singleton copy of the instance method the name
77
+ # resolves to. {#lookup_through_ancestors} answers a singleton-side call for one through
78
+ # {#singleton_copy_entry}.
79
+ def initialize(entries: [], write_census: GlobalWriteCensus::EMPTY, mixins: [], overrides: {},
80
+ singleton_copies: [])
73
81
  @by_key = entries.each_with_object({}) do |entry, acc|
74
82
  key = [entry.class_name, entry.method_name, entry.kind]
75
83
  acc[key] = entry
@@ -78,6 +86,7 @@ module Rigor
78
86
  @mixins = mixins.dup.freeze
79
87
  @mixins_of = ProjectPatchedMethods.mixins_by_owner(@mixins)
80
88
  @overrides = overrides.dup.freeze
89
+ @singleton_copies = singleton_copies.to_set.freeze
81
90
  freeze
82
91
  end
83
92
 
@@ -121,10 +130,18 @@ module Rigor
121
130
  end
122
131
 
123
132
  entry = @by_key[[owner, method_name, side]]
133
+ if entry.nil? && found.nil? && side == :singleton
134
+ copy = singleton_copy_entry(owner, method_name, rbs_loader, source_defines)
135
+ # Issue #2108 — a singleton copy is a real method on `#<Class:owner>` at this exact ancestor
136
+ # position: Ruby's lookup ends there, so nothing farther on the walk can answer `owner.x`.
137
+ # Return it now — collecting `farther` would only make {#callable?} decline the refusal Ruby
138
+ # makes of a private or protected copy.
139
+ return apply_override(copy, override) if copy
140
+ end
124
141
  if found
125
142
  farther << entry if entry
126
143
  elsif entry
127
- found = override ? entry.with(class_name: override.first, visibility: override.last) : entry
144
+ found = apply_override(entry, override)
128
145
  elsif (visibility = @overrides[[owner, method_name, side]])
129
146
  override ||= [owner, visibility]
130
147
  end
@@ -137,9 +154,12 @@ module Rigor
137
154
 
138
155
  # Issue #1708 — whether Ruby lets a call reach `entry`. A public method answers any call; a private one only a
139
156
  # call with no receiver or with `self` as it (`explicit` is false for those); a protected one also a call from
140
- # a method whose `self` is an instance of the entry's owner. `self_type` is the caller's `self` (`nil` at the
141
- # top level, where it is `main`, an Object); a `self` this cannot name (the default) may be one, so a
142
- # protected entry answers it.
157
+ # a method whose `self` is an instance of the entry's owner — for a `:singleton` entry, the owner itself or a
158
+ # class whose singleton chain holds it (`Kernel.a1` under `class << self; protected :a1` is refused from
159
+ # `main`, an Object, and from another class's singleton method, and answered from Kernel's own and from a
160
+ # `module_function` def of Kernel's, whose self is typed as an instance of Kernel though the copy runs with
161
+ # self = Kernel). `self_type` is the caller's `self` (`nil` at the top level, where it is `main`, an Object);
162
+ # a `self` this cannot name (the default) may be one, so a protected entry answers it.
143
163
  #
144
164
  # It never refuses a call some other definer of the name may answer: the walk's order is an approximation
145
165
  # (a module two owners mix in is spliced at the nearer one, where Ruby keeps the first include and skips the
@@ -152,7 +172,7 @@ module Rigor
152
172
  allowed =
153
173
  case entry.visibility
154
174
  when :private, :module_function then false
155
- when :protected then self_kind_of?(self_type, entry.class_name, rbs_loader)
175
+ when :protected then self_kind_of?(self_type, entry.class_name, entry.kind, rbs_loader)
156
176
  else true
157
177
  end
158
178
  return true if allowed
@@ -171,18 +191,50 @@ module Rigor
171
191
 
172
192
  private
173
193
 
174
- # Whether `self_type` may be an instance of `owner`: false only when the ancestors of a class RBS knows,
175
- # with the patches' mixins, do not hold it.
176
- def self_kind_of?(self_type, owner, rbs_loader)
177
- name, kind =
194
+ # Whether `self_type` may reach `owner`'s `entry_kind` method: false only when the ancestors of a class RBS
195
+ # knows, with the patches' mixins, do not hold `[owner, entry_kind]` — an instance of the owner for an
196
+ # instance method, the owner itself or a class whose singleton chain holds it for a singleton one. For a
197
+ # singleton entry a self whose class IS the owner answers too: a `module_function` / `extend self` def is
198
+ # typed `Nominal[owner]` though its singleton copy runs with self = owner, so `name == owner` may be the
199
+ # owner object itself (issue #3056).
200
+ def self_kind_of?(self_type, owner, entry_kind, rbs_loader)
201
+ name, side =
178
202
  if self_type.nil? then ["Object", :instance]
179
203
  elsif self_type.respond_to?(:class_name) && self_type.class_name
180
204
  [self_type.class_name.to_s, self_type.is_a?(Type::Singleton) ? :singleton : :instance]
181
205
  end
182
206
  return true if name.nil? || rbs_loader.nil?
183
207
 
184
- keys = ancestor_keys(name, kind, rbs_loader)
185
- keys == [[name, kind]] || keys.include?([owner, :instance])
208
+ keys = ancestor_keys(name, side, rbs_loader)
209
+ keys == [[name, side]] || keys.include?([owner, entry_kind]) ||
210
+ (entry_kind == :singleton && side == :instance && name == owner)
211
+ end
212
+
213
+ # `entry` as the walk's recorded `override` ([owner, visibility], from a nearer row's named
214
+ # visibility call) leaves it: the override's owner as `class_name` and its visibility — or `entry`
215
+ # itself when the walk passed no override on the way.
216
+ def apply_override(entry, override)
217
+ override ? entry.with(class_name: override.first, visibility: override.last) : entry
218
+ end
219
+
220
+ # Issue #2108 — the entry a `module_function :x` on a name `owner` inherits answers `owner.x` with. Ruby
221
+ # copies the instance method the name resolves to onto the owner's singleton as a public method, so the
222
+ # copy is the entry this walk finds for the name on the owner's INSTANCE ancestors, re-attributed to the
223
+ # owner, re-kinded `:singleton` and made public — unless a later named visibility call (`private_class_method
224
+ # :x`, or `private :x` in `class << self`) recorded the pair's singleton-side override, which Ruby applies to
225
+ # the copy. The instance walk's `farther` definers are dropped: the copy is a real method on
226
+ # `#<Class:owner>` that ends the singleton-side lookup, so no instance-chain definer can answer `owner.x`,
227
+ # and keeping them would only make {#callable?} decline a refusal Ruby makes. nil when no copy is
228
+ # recorded for `[owner, method_name]`, or when the instance walk finds no patch entry — an RBS or
229
+ # project-source method the registry does not hold, where the singleton walk then answers whatever else
230
+ # it reaches, as before.
231
+ def singleton_copy_entry(owner, method_name, rbs_loader, source_defines)
232
+ return nil unless @singleton_copies.include?([owner, method_name])
233
+
234
+ copy = lookup_through_ancestors(class_name: owner, method_name: method_name, kind: :instance,
235
+ rbs_loader: rbs_loader, source_defines: source_defines)
236
+ copy&.with(class_name: owner, kind: :singleton, farther: nil,
237
+ visibility: @overrides[[owner, method_name, :singleton]] || :public)
186
238
  end
187
239
 
188
240
  def ancestor_keys(class_name, kind, rbs_loader)