rigortype 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -5,6 +5,7 @@ require "prism"
5
5
  require_relative "../type"
6
6
  require_relative "../source/node_children"
7
7
  require_relative "return_barrier"
8
+ require_relative "optimistic_origin"
8
9
 
9
10
  module Rigor
10
11
  module Inference
@@ -21,14 +22,20 @@ module Rigor
21
22
  #
22
23
  # Extracted from `Rigor::SigGen::Generator#infer_return_type` so `LineTypeCollector` (`rigor annotate`'s
23
24
  # def-line annotator) and the sig-generator share one source of truth.
25
+ #
26
+ # `keep_lookup_nil: true` is the reading a signature needs (#2197). The engine reads a lookup that can miss
27
+ # (`Hash#[]`, `Array#first`, the {OptimisticOrigin} family) as nil-free on purpose, so `if h[k]` stays
28
+ # silent, but a `-> V` written into `sig/` is a contract that every caller's `nil` check is dead code. Each
29
+ # returned expression whose value is such a lookup therefore contributes `nil` beside its type. Only the
30
+ # value positions a lookup's `nil` reaches the return through are followed ({#optimistic_value?}).
24
31
  module DefReturnTyper
25
32
  module_function
26
33
 
27
- def call(def_node, scope_index)
34
+ def call(def_node, scope_index, keep_lookup_nil: false)
28
35
  body = def_node.body
29
36
  return nil if body.nil?
30
37
 
31
- last = body_last_expression(body)
38
+ last = primary_value_expression(body)
32
39
  return nil if last.nil?
33
40
 
34
41
  inner_scope = scope_index[last] || scope_index[body] || scope_index[def_node]
@@ -37,11 +44,195 @@ module Rigor
37
44
  last_type = safe_type_of(inner_scope, last)
38
45
  return nil if last_type.nil?
39
46
 
40
- union_with_explicit_returns(body, last_type, scope_index)
47
+ lookup_context = keep_lookup_nil ? LookupContext.new(scope_index, truthy_guards(body)) : nil
48
+ last_type = with_lookup_nil(last_type, last, inner_scope, lookup_context) if lookup_context
49
+ arm_types = rescue_arm_types(body, inner_scope, scope_index, lookup_context)
50
+ return nil if arm_types.nil?
51
+
52
+ union_with_explicit_returns(body, Type::Combinator.union(last_type, *arm_types), scope_index,
53
+ lookup_context: lookup_context)
41
54
  rescue StandardError
42
55
  nil
43
56
  end
44
57
 
58
+ # Issue #2410 — the value a def body ends on when no exception escapes its primary path. A def-level
59
+ # `rescue … else … end` body is a `BeginNode` whose else-clause, when present, replaces the body's value (the
60
+ # ensure-clause's value is discarded, so it never counts).
61
+ def primary_value_expression(body)
62
+ return body_last_expression(body) unless body.is_a?(Prism::BeginNode) && body.else_clause
63
+
64
+ body_last_expression(body.else_clause.statements)
65
+ end
66
+
67
+ # Issue #2410 — the value of each arm of a def-level rescue chain, which the method returns when that arm
68
+ # completes. An empty arm returns `nil`; an arm ending in `raise`, `retry` or `return` types as bottom and so
69
+ # adds nothing here (a `return`'s value is collected with the other explicit returns). Nil when an arm cannot
70
+ # be typed: the body's value alone would be a narrower, wrong contract.
71
+ def rescue_arm_types(body, inner_scope, scope_index, lookup_context)
72
+ return [] unless body.is_a?(Prism::BeginNode)
73
+
74
+ types = []
75
+ arm = body.rescue_clause
76
+ while arm
77
+ type = rescue_arm_type(arm, inner_scope, scope_index, lookup_context)
78
+ return nil if type.nil?
79
+
80
+ types << type
81
+ arm = arm.subsequent
82
+ end
83
+ types
84
+ end
85
+
86
+ def rescue_arm_type(arm, inner_scope, scope_index, lookup_context)
87
+ value = body_last_expression(arm.statements)
88
+ return Type::Combinator.constant_of(nil) if value.nil?
89
+
90
+ scope = scope_index[value] || scope_index[arm] || inner_scope
91
+ type = safe_type_of(scope, value)
92
+ return nil if type.nil?
93
+
94
+ lookup_context ? with_lookup_nil(type, value, scope, lookup_context) : type
95
+ end
96
+
97
+ # What {#optimistic_value?} reads beside a node's scope: the engine's per-node scopes, and for each lookup
98
+ # under a guard the source of the guarding predicates ({#truthy_guards}).
99
+ LookupContext = Data.define(:scope_index, :guards)
100
+
101
+ def with_lookup_nil(type, node, scope, context = nil)
102
+ return type unless optimistic_value?(node, scope, context)
103
+
104
+ Type::Combinator.union(type, Type::Combinator.constant_of(nil))
105
+ end
106
+
107
+ # Whether `node`'s value can be the `nil` an optimistic lookup's miss returns: the lookup itself (a call
108
+ # the dispatcher marked, or `recv&.m` over one), a local or ivar bound to one, or a branch or operand that
109
+ # hands its value on. `a && b` hands on either operand (a falsy `a`, a missed lookup's `nil` included, is
110
+ # the result); `a || b` hands on `b` only, since `a` is returned only when truthy, so `h[k] || 0` stays
111
+ # nil-free. `x ||= v` and `x &&= v` hand on `v` and, for `&&=`, the binding. A call ON the lookup
112
+ # (`h[k].to_s`) is a different value.
113
+ #
114
+ # Each child is read under the scope the engine recorded for it (`scope_index`), so a branch a guard narrows
115
+ # (`v ? v : "d"`, `return "z" unless v`) reads the binding the narrowing left, not the enclosing one.
116
+ def optimistic_value?(node, scope, context = nil)
117
+ scope = context&.scope_index&.[](node) || scope
118
+ case node
119
+ when Prism::CallNode then optimistic_call?(node, scope, context)
120
+ when Prism::LocalVariableReadNode then !scope.optimistic_local(node.name).nil?
121
+ when Prism::InstanceVariableReadNode then !scope.optimistic_ivar(node.name).nil?
122
+ when Prism::AndNode then [node.left, node.right].any? { optimistic_value?(it, scope, context) }
123
+ when Prism::OrNode then optimistic_value?(node.right, scope, context)
124
+ else optimistic_write?(node, scope, context) || optimistic_branch?(node, scope, context)
125
+ end
126
+ end
127
+
128
+ def optimistic_call?(node, scope, context)
129
+ if scope.optimistic_origins[node]
130
+ return false if guarded_lookup?(node, context) || non_empty_end?(node, scope)
131
+
132
+ return true
133
+ end
134
+ node.safe_navigation? && optimistic_value?(node.receiver, scope, context)
135
+ end
136
+
137
+ # `return h[k] if h[k]`, `h[k] ? h[k] : d`: the same lookup, read again under a predicate that just found it
138
+ # truthy. Only a lookup whose receiver and arguments are plain reads or literals counts, so the second read
139
+ # sees what the first did.
140
+ GUARDABLE_LOOKUPS = %i[[] first last dig].freeze
141
+ private_constant :GUARDABLE_LOOKUPS
142
+
143
+ def guarded_lookup?(node, context)
144
+ return false if context.nil? || !GUARDABLE_LOOKUPS.include?(node.name)
145
+ return false unless [node.receiver, *node.arguments&.arguments].all? { stable_operand?(it) }
146
+
147
+ context.guards.fetch(node, EMPTY_GUARDS).include?(node.slice)
148
+ end
149
+
150
+ EMPTY_GUARDS = [].freeze
151
+ private_constant :EMPTY_GUARDS
152
+
153
+ def stable_operand?(node)
154
+ case node
155
+ when Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode, Prism::SymbolNode, Prism::StringNode,
156
+ Prism::IntegerNode, Prism::SelfNode, Prism::ConstantReadNode then true
157
+ else false
158
+ end
159
+ end
160
+
161
+ # `a.first` / `a.last` on a receiver the engine has narrowed to a non-empty array (`a.empty? ? d : a.first`)
162
+ # cannot miss.
163
+ def non_empty_end?(node, scope)
164
+ return false unless %i[first last].include?(node.name) && node.arguments.nil? && node.receiver
165
+
166
+ receiver = scope.type_of(node.receiver)
167
+ receiver.is_a?(Type::Difference) && receiver.removed == Type::Combinator.tuple_of
168
+ rescue StandardError
169
+ false
170
+ end
171
+
172
+ def optimistic_write?(node, scope, context)
173
+ case node
174
+ when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
175
+ Prism::LocalVariableOrWriteNode, Prism::InstanceVariableOrWriteNode
176
+ optimistic_value?(node.value, scope, context)
177
+ when Prism::LocalVariableAndWriteNode
178
+ !scope.optimistic_local(node.name).nil? || optimistic_value?(node.value, scope, context)
179
+ when Prism::InstanceVariableAndWriteNode
180
+ !scope.optimistic_ivar(node.name).nil? || optimistic_value?(node.value, scope, context)
181
+ else false
182
+ end
183
+ end
184
+
185
+ # The value-carrying children of a statement list or a conditional: any of them can be the value returned.
186
+ def optimistic_branch?(node, scope, context)
187
+ value_branches(node).any? { |branch| !branch.nil? && optimistic_value?(branch, scope, context) }
188
+ end
189
+
190
+ # The lookup a truthy arm (the body of an `if` or ternary, the `else` of an `unless`) reads FIRST, mapped to
191
+ # the source of the predicate that just tested it, for {#guarded_lookup?}. Only the first expression, or the
192
+ # value of a first `return`, counts: anything run between the test and the re-read (`h.delete(k)`, a
193
+ # reassignment, a block, `yield`, another call that mutates the receiver) can make the re-read miss.
194
+ def truthy_guards(body)
195
+ guards = {}.compare_by_identity
196
+ collect_truthy_guards(body, guards)
197
+ guards
198
+ end
199
+
200
+ def collect_truthy_guards(node, guards)
201
+ return unless node.is_a?(Prism::Node)
202
+
203
+ predicate, arm = truthy_arm(node)
204
+ first = first_value(arm)
205
+ guards[first] = [predicate.slice] if first
206
+ node.rigor_each_child { collect_truthy_guards(it, guards) }
207
+ end
208
+
209
+ def first_value(arm)
210
+ return nil unless arm.is_a?(Prism::StatementsNode)
211
+
212
+ first = arm.body.first
213
+ args = first.is_a?(Prism::ReturnNode) ? first.arguments&.arguments : nil
214
+ args&.size == 1 ? args.first : first
215
+ end
216
+
217
+ def truthy_arm(node)
218
+ case node
219
+ when Prism::IfNode then [node.predicate, node.statements]
220
+ when Prism::UnlessNode then [node.predicate, node.else_clause&.statements]
221
+ end
222
+ end
223
+
224
+ def value_branches(node)
225
+ case node
226
+ when Prism::ParenthesesNode then [node.body && body_last_expression(node.body)]
227
+ when Prism::StatementsNode, Prism::BeginNode then [body_last_expression(node)]
228
+ when Prism::IfNode then [node.statements, node.subsequent]
229
+ when Prism::UnlessNode then [node.statements, node.else_clause]
230
+ when Prism::ElseNode, Prism::WhenNode, Prism::InNode then [node.statements]
231
+ when Prism::CaseNode, Prism::CaseMatchNode then [*node.conditions, node.else_clause]
232
+ else []
233
+ end
234
+ end
235
+
45
236
  def body_last_expression(body)
46
237
  case body
47
238
  when Prism::StatementsNode then body.body.last
@@ -50,27 +241,27 @@ module Rigor
50
241
  end
51
242
  end
52
243
 
53
- def union_with_explicit_returns(body, last_type, scope_index)
244
+ def union_with_explicit_returns(body, last_type, scope_index, lookup_context: nil)
54
245
  return_types = []
55
- collect_return_types(body, scope_index, return_types)
246
+ collect_return_types(body, scope_index, return_types, lookup_context: lookup_context)
56
247
  return last_type if return_types.empty?
57
248
 
58
249
  Type::Combinator.union(last_type, *return_types)
59
250
  end
60
251
 
61
- def collect_return_types(node, scope_index, out)
252
+ def collect_return_types(node, scope_index, out, lookup_context: nil)
62
253
  return unless node.is_a?(Prism::Node)
63
254
  return if ReturnBarrier.node?(node)
64
255
 
65
- type_return_node(node, scope_index, out) if node.is_a?(Prism::ReturnNode)
256
+ type_return_node(node, scope_index, out, lookup_context: lookup_context) if node.is_a?(Prism::ReturnNode)
66
257
  # A barrier call's receiver and arguments still run in the method; only its block is cut off.
67
258
  barrier_block = node.block if node.is_a?(Prism::CallNode) && ReturnBarrier.block_call?(node)
68
259
  node.rigor_each_child do |c|
69
- collect_return_types(c, scope_index, out) unless c.equal?(barrier_block)
260
+ collect_return_types(c, scope_index, out, lookup_context: lookup_context) unless c.equal?(barrier_block)
70
261
  end
71
262
  end
72
263
 
73
- def type_return_node(return_node, scope_index, out)
264
+ def type_return_node(return_node, scope_index, out, lookup_context: nil)
74
265
  args = return_node.arguments&.arguments || []
75
266
  if args.empty?
76
267
  out << Type::Combinator.constant_of(nil)
@@ -79,12 +270,13 @@ module Rigor
79
270
 
80
271
  scope = scope_index[return_node] || scope_index[args.first]
81
272
  return if scope.nil?
82
- # `return a, b` packs into a Tuple at runtime; the MVP only handles the single-value form. Multi-arg
83
- # returns contribute no type to keep the implementation focused.
273
+ # `return a, b` packs into a Tuple at runtime; only the single-value form is typed.
84
274
  return unless args.size == 1
85
275
 
86
276
  type = safe_type_of(scope, args.first)
87
- out << type unless type.nil?
277
+ return if type.nil?
278
+
279
+ out << (lookup_context ? with_lookup_nil(type, args.first, scope, lookup_context) : type)
88
280
  end
89
281
 
90
282
  def safe_type_of(scope, node)
@@ -25,8 +25,10 @@ module Rigor
25
25
  # truth-tested: {UNKNOWN} and {ABSENT} are both truthy, so any other use folds a decline into a firing arm.
26
26
  # `spec/rigor/inference/definer_resolution_case_in_spec.rb` fails on a call site of any other shape.
27
27
  #
28
- # It migrates one question at a time: `SourceArity` (C1b) and the relationship lints (C1c) read the instance
29
- # side; no production reader reads the singleton side yet (C2-a landed it for C2's typing sites).
28
+ # It migrates one question at a time: `SourceArity` (C1b, C2-c) asks for the receiver's own side, instance or
29
+ # singleton; the override lints (C1c) and the C2 typing sites (`ExpressionTyper#resolve_typing_def_with_owner`,
30
+ # `StatementEvaluator#own_definer_node`) read the instance side; `RefinedDispatch::OwnMethod.certain_owner?` and
31
+ # `CheckRules#reached_definer_visibility` pass the side they hold.
30
32
  module DefinerResolution
31
33
  Known = Data.define(:answer, :owner)
32
34
  UnknownResult = Data.define
@@ -221,11 +223,15 @@ module Rigor
221
223
  # alone where a later `attr_*` replaced the `def`: the attr writes the name's visibility slot, so the reader's
222
224
  # visibility is certain. A `define_method` or alias that replaced it contests the visibility slot as well, which
223
225
  # this question does read.
226
+ #
227
+ # Issue #2134 — the contest is a read of the slot, so a question that reads it files `visibility:<owner>#<name>`:
228
+ # an answer block may answer from RBS without reading the slot itself (`nearest_ancestor_method_def`).
224
229
  def possible?(scope, hit, method_name, reads_visibility)
225
230
  discovery = scope.discovery
226
231
  owner = hit.owner
227
232
  kind = hit.side == :singleton ? :singleton : :instance
228
233
  nodes = kind == :singleton ? :discovered_singleton_def_nodes : :discovered_def_nodes
234
+ Analysis::DependencyRecorder.read_visibility(owner, method_name) if reads_visibility
229
235
  discovery.possible_method?(owner, method_name, kind) ||
230
236
  (!(reads_visibility == :visibility && kind == :instance) &&
231
237
  discovery.contested?(nodes, [owner, method_name.to_sym])) ||
@@ -277,7 +283,7 @@ module Rigor
277
283
 
278
284
  def side_visibility(scope, owner, method_name, kind)
279
285
  if kind == :singleton
280
- scope.discovered_method_visibilities.dig(owner.to_s, visibility_slot(method_name, :singleton)) ||
286
+ scope.discovered_method_visibility(owner, method_name, singleton: true) ||
281
287
  self_extended_copy_visibility(scope, owner, method_name)
282
288
  else
283
289
  scope.discovered_method_visibility(owner, method_name)
@@ -301,11 +307,21 @@ module Rigor
301
307
 
302
308
  # Whether any file's deferred ranges carry a `module_function` row (`:both` or `:singleton`) for the name on
303
309
  # `owner` (`ModuleFunctionState`).
310
+ #
311
+ # Issue #2161 — the answer reads every file's rows, so it records `visibility:<owner>#<name>` whichever way it
312
+ # answers: `ScopeIndexer.visibility_entries` carries each file's rows of this shape, so a file that starts or
313
+ # stops writing one fires the key. A swap from `private :x` to `module_function :x` in a reopening leaves the
314
+ # file's visibility table and declared classes as they were, and fires nothing else. The caller's own
315
+ # visibility reads record the same key today; this one keeps the reader's edge where it reads.
316
+ #
317
+ # Issue #2209 — a row named {ModuleFunctionState::ANY_ROW_NAME} (a `module_function` spelling whose names the
318
+ # prescan cannot read) stands for every name of its owner, so it counts too, and its key is read as well
319
+ # ({ModuleFunctionState.row_names}).
304
320
  def module_function_row?(scope, owner, method_name)
305
- name = method_name.to_sym
321
+ names = ModuleFunctionState.row_names(method_name).each { Analysis::DependencyRecorder.read_visibility(owner, it) }
306
322
  scope.discovered_deferred_ranges.each_value.any? do |rows|
307
323
  rows.any? do |(_start, _finish, row_name, kind, row_owner)|
308
- row_name == name && row_owner == owner.to_s && kind != :instance
324
+ names.include?(row_name) && row_owner == owner.to_s && kind != :instance
309
325
  end
310
326
  end
311
327
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../run_scoped_slots"
4
+
5
+ module Rigor
6
+ module Inference
7
+ # The `method_name (Symbol) → [owner names]` inverted index the override gate reads
8
+ # (`ExpressionTyper#method_definers_index`), over one instance or singleton def table.
9
+ #
10
+ # Issue #2300 — a file's def table is its own `def`s merged over the cross-file seed
11
+ # (`ScopeIndexer#merge_def_node_tables`, `#merge_singleton_def_nodes`, then the #526 extend fold), a new Hash per
12
+ # file. Inverting it whole scanned every project `def` once per analysed file. {.record_layers} remembers the
13
+ # seed a final merged table was built over, and {.for} then inverts the seed ONCE per seed table and, per file,
14
+ # only the owners whose inner table is not the seed's own object ({Layered}).
15
+ #
16
+ # The two answers hold the same owners: an owner the merge left alone shares the seed's inner table, so its
17
+ # names are the seed's, and every other owner is read from the merged table itself. That holds only while the
18
+ # seed cannot change under the cached index, so a seed whose tables are not all frozen, or a merged table
19
+ # missing one of the seed's owners, is inverted whole, as is a table {.record_layers} was not told about. Only
20
+ # the order of a name's owners can differ, and the gate asks `any?` of the list.
21
+ module DefinersIndex
22
+ # These thread slots are bounded, like `ExpressionTyper#method_definers_index`'s two ways. Each is a fixed
23
+ # number of entries, replaced rather than accumulated: two `[merged, seed]` pairs (the last file's two def
24
+ # tables) and two seed indexes. The run that filled them releases them when it ends ({RunScopedSlots}, #2399),
25
+ # so a finished run keeps none of its seed tables or their indexes alive.
26
+ LAYERS_KEY = RunScopedSlots.register(:__rigor_definers_index_layers__)
27
+ SEED_INDEX_KEY = RunScopedSlots.register(:__rigor_definers_index_seed__)
28
+ SEED_INDEX_ALT_KEY = RunScopedSlots.register(:__rigor_definers_index_seed_alt__)
29
+ private_constant :LAYERS_KEY, :SEED_INDEX_KEY, :SEED_INDEX_ALT_KEY
30
+
31
+ # A name's owners: the seed's, less the owners the file replaced, plus the replaced owners' own.
32
+ class Layered
33
+ def initialize(seed_index, replaced, replaced_index)
34
+ @seed_index = seed_index
35
+ @replaced = replaced
36
+ @replaced_index = replaced_index
37
+ @answers = {}
38
+ end
39
+
40
+ def [](method_name)
41
+ return @answers[method_name] if @answers.key?(method_name)
42
+
43
+ from_seed = @seed_index[method_name]
44
+ from_seed = from_seed.reject { |owner| @replaced.key?(owner) } if from_seed && !@replaced.empty?
45
+ from_file = @replaced_index[method_name]
46
+ owners = from_file.nil? ? from_seed : (from_seed || []) + from_file
47
+ @answers[method_name] = owners.nil? || owners.empty? ? nil : owners
48
+ end
49
+ end
50
+
51
+ module_function
52
+
53
+ # Records that the final `merged` table was built over `seed`. The last two are kept (a file merges an
54
+ # instance and a singleton table), replaced rather than accumulated.
55
+ def record_layers(merged, seed)
56
+ previous = Thread.current[LAYERS_KEY]
57
+ Thread.current[LAYERS_KEY] = [[merged, seed], previous&.first].compact
58
+ end
59
+
60
+ # The definers index over `table`.
61
+ def for(table)
62
+ seed = Thread.current[LAYERS_KEY]&.find { |entry| entry[0].equal?(table) }&.last
63
+ return build(table) if seed.nil? || !deeply_frozen?(seed)
64
+
65
+ replaced = {}
66
+ kept = 0
67
+ table.each do |owner, methods|
68
+ if methods.equal?(seed[owner])
69
+ kept += 1
70
+ else
71
+ replaced[owner] = methods
72
+ end
73
+ end
74
+ return build(table) unless kept + replaced.count { |owner, _| seed.key?(owner) } == seed.size
75
+
76
+ Layered.new(seed_index(seed), replaced, build(replaced))
77
+ end
78
+
79
+ def deeply_frozen?(seed)
80
+ seed.frozen? && seed.each_value.all?(&:frozen?)
81
+ end
82
+
83
+ # The seed's index, built once per seed table. Two ways, an instance and a singleton seed table.
84
+ def seed_index(seed)
85
+ [SEED_INDEX_KEY, SEED_INDEX_ALT_KEY].each do |key|
86
+ slot = Thread.current[key]
87
+ return slot[1] if slot && slot[0].equal?(seed)
88
+ end
89
+ index = build(seed)
90
+ Thread.current[SEED_INDEX_ALT_KEY] = Thread.current[SEED_INDEX_KEY]
91
+ Thread.current[SEED_INDEX_KEY] = [seed, index]
92
+ index
93
+ end
94
+
95
+ def build(table)
96
+ index = {}
97
+ table.each do |class_name, methods|
98
+ next if class_name == ScopeIndexer::TOP_LEVEL_DEF_KEY
99
+
100
+ methods.each_key { |method_name| (index[method_name] ||= []) << class_name }
101
+ end
102
+ index
103
+ end
104
+ end
105
+ end
106
+ end