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
@@ -20,7 +20,6 @@ module Rigor
20
20
  #
21
21
  # Centralising both here keeps the receiver-shape and constant-unwrap knowledge in one place: if
22
22
  # `Type::Singleton` or `Type::Constant` ever changes shape, there is a single edit rather than nine.
23
- # The per-tier fold bodies stay where they are — only the shared gate moves.
24
23
  module SingletonFolding
25
24
  module_function
26
25
 
@@ -20,8 +20,9 @@ module Rigor
20
20
  # The rule is exact rather than heuristic — the receiver must be a `Singleton[C]` whose `C` the project
21
21
  # pre-pass recorded as including `Singleton` — so a class that does not include the mixin is untouched,
22
22
  # and a project that defines its own `Singleton` module is the one case that would misfire. Guarded by
23
- # {project_singleton_shadow?}: a project-declared `Singleton` class or module wins and the tier declines,
24
- # because then the name means something else entirely.
23
+ # {own_singleton_include?}: when the included name resolves to a project-declared class or module (a
24
+ # top-level `Singleton`, or `M::Singleton` inside `module M`), the tier declines, because then the name
25
+ # means something else entirely.
25
26
  module SingletonMixinDispatch
26
27
  MIXIN_NAMES = Ractor.make_shareable(Set["Singleton", "::Singleton"])
27
28
  private_constant :MIXIN_NAMES
@@ -31,30 +32,62 @@ module Rigor
31
32
 
32
33
  module_function
33
34
 
34
- # @return `Nominal[C]` for `C.instance`, or nil to decline.
35
+ # @return the instance type of `C` for `C.instance`, or nil to decline.
35
36
  def try_dispatch(context)
36
- return nil unless context.method_name == SELECTOR
37
- return nil unless context.args.empty?
38
-
39
37
  receiver = context.receiver
40
- return nil unless receiver.is_a?(Type::Singleton)
41
-
42
- scope = context.scope
43
- return nil if scope.nil?
44
- return nil unless includes_singleton_mixin?(scope, receiver.class_name)
45
- return nil if project_singleton_shadow?(scope)
38
+ return nil unless answers?(context.scope, receiver, context.method_name, context.args.empty?)
46
39
 
47
40
  Type::Combinator.nominal_of(receiver.class_name)
48
41
  end
49
42
 
43
+ # Issue #2438 — the one predicate behind {try_dispatch} and `CheckRules#undefined_method_diagnostic`, so the
44
+ # typer and the `call.undefined-method` rule cannot disagree about `C.instance`: a call this tier types is
45
+ # never reported as undefined, whatever a project sidecar of `C` lists.
46
+ def answers?(scope, receiver, method_name, no_arguments)
47
+ return false unless method_name == SELECTOR && no_arguments
48
+ return false unless receiver.is_a?(Type::Singleton)
49
+ return false if scope.nil?
50
+
51
+ includes_singleton_mixin?(scope, receiver.class_name)
52
+ end
53
+
54
+ # True when `class_name`'s instance ancestry carries the stdlib `Singleton` as an `include` written in the
55
+ # class itself or in a project class up its superclass chain (Ruby's `Singleton.included` also installs an
56
+ # `inherited` hook, so `class D < C` answers `instance` with its own instance). Read off
57
+ # `Scope::ResolutionChain` (the `:constants` flavor, so an empty project `module Singleton` is a project
58
+ # entry), where the stdlib module is an external entry. Issue #2438 — what is not the mixin:
59
+ #
60
+ # - `prepend Singleton`, which runs `prepend_features` instead of the `included` hook and so defines no
61
+ # `instance`; a prepended entry sits ahead of its owner on the chain;
62
+ # - an `include Singleton` in a MODULE the class includes: `Singleton.included` raises `TypeError` for a
63
+ # module, so that code never runs;
64
+ # - a top-level project `Singleton`, which both `Singleton` and `::Singleton` name.
65
+ #
66
+ # A NESTED project `N::Singleton` the name resolved to is read as the stdlib mixin: the discovery tables
67
+ # drop the leading `::` of a mixin argument, so `include ::Singleton` and `include Singleton` inside
68
+ # `module N` record the same name, and the stdlib reading is the one that types the rooted spelling and
69
+ # never reports a correct call.
50
70
  def includes_singleton_mixin?(scope, class_name)
51
- scope.includes_of(class_name).any? { |name| MIXIN_NAMES.include?(name.to_s) }
71
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s.delete_prefix("::"), :instance, :constants)
72
+ chain.record(scope)
73
+ classes = chain.level_classes.compact
74
+ chain.entries.each_with_index.any? { |entry, index| singleton_include?(chain, classes, entry, index) }
75
+ end
76
+
77
+ def singleton_include?(chain, classes, entry, index)
78
+ if entry.external?
79
+ !entry.superclass_edge && MIXIN_NAMES.include?(entry.raw.to_s) && classes.include?(entry.owner) &&
80
+ index > chain.index_of(entry.owner)
81
+ else
82
+ entry.name.end_with?("::Singleton") && included_by_level_class?(chain, index)
83
+ end
52
84
  end
53
85
 
54
- # True when the project declares its OWN `Singleton` class / module, in which case `include Singleton`
55
- # names that one and the stdlib rule does not apply.
56
- def project_singleton_shadow?(scope)
57
- MIXIN_NAMES.any? { |name| scope.discovery.discovered_classes.key?(name.to_s.delete_prefix("::")) }
86
+ # The entry sits after its level's class, so it was included into that level rather than prepended.
87
+ def included_by_level_class?(chain, index)
88
+ level = chain.level_starts.rindex { |start| start <= index }
89
+ klass = level && chain.level_classes[level]
90
+ !klass.nil? && index > chain.index_of(klass)
58
91
  end
59
92
  end
60
93
  end
@@ -14,8 +14,8 @@ module Rigor
14
14
  #
15
15
  # A `Struct` instance is mutable (`s.x = v`, `s[:x] = v`, escape), so a member map bound to a
16
16
  # variable can be invalidated by a later write. A member read folds off a **fresh** instance (the
17
- # transient receiver of a `.new(...).x` / `.with(...).x` chain, unmutatable between materialisation
18
- # and the read — slices 1+2), off a **fold-safe stored local** the {Inference::StructFoldSafety} scan
17
+ # transient receiver of a `.new(...).x` chain, unmutatable between materialisation and the read —
18
+ # slices 1+2), off a **fold-safe stored local** the {Inference::StructFoldSafety} scan
19
19
  # proved is never mutated / aliased / escaped (slice 3), or off a **setter-mutated fold-safe local**
20
20
  # whose bindings {#apply_setter_writeback} keeps current (slice 4). Any other stored receiver degrades
21
21
  # to `Dynamic[top]` rather than fold a possibly-stale value.
@@ -255,8 +255,8 @@ module Rigor
255
255
  end
256
256
 
257
257
  # A member read is foldable when the receiver is either FRESH (the transient result of a
258
- # `.new(...)`/`.with(...)` chain, which cannot have been mutated between materialisation and this
259
- # read) or a FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
258
+ # `.new(...)` chain, which cannot have been mutated between materialisation and this read) or a
259
+ # FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
260
260
  # never mutated / aliased / escaped in its scope).
261
261
  def foldable_receiver?(context)
262
262
  fresh_receiver?(context) || fold_safe_local_receiver?(context) ||
@@ -308,7 +308,7 @@ module Rigor
308
308
  end
309
309
 
310
310
  # A fresh receiver is the result of a chained call that MATERIALISED the struct — `Point.new(1, 2).x`,
311
- # `Point[1, 2].x`, `inst.with(x: 9).y`. Not merely a chained call: slices 1-2 read "chained" as
311
+ # `Point[1, 2].x`. Not merely a chained call: slices 1-2 read "chained" as
312
312
  # "cannot have been mutated between materialisation and the read", and a method that hands back its
313
313
  # own receiver falsifies that (issue #595):
314
314
  #
@@ -31,12 +31,10 @@ module Rigor
31
31
  # nodes; the bound exists so a pathological body cannot turn a per-call-site gate into a walk.
32
32
  FACTORY_BODY_SCAN_BUDGET = 200
33
33
 
34
- # Issue #595 / #525 — the ONE materialisation test. Both places that must answer "was this receiver
35
- # expression's struct newly built?" go through it: this module's direct member-read gate above, and
36
- # `ExpressionTyper`'s caller-side `:self`-grant arm. They had forked answers once (the grant arm was
37
- # tightened for #591 while this one still trusted any chained call), so both consumers must share this
38
- # one answer. A `.with` call is never a materialisation: the composed `x.dup_self.with(indent: 9)`
39
- # shape once slipped through as one, and `Struct#with` no longer exists to fold (#1335).
34
+ # Issue #595 / #525 — the ONE materialisation test, shared by {StructFolding#fresh_receiver?} and
35
+ # `ExpressionTyper`'s `:self`-grant arm so the two gates cannot fork. A `.with` call is never a
36
+ # materialisation: the composed `x.dup_self.with(indent: 9)` shape once slipped through as one, and
37
+ # `Struct#with` no longer exists (#1335).
40
38
  #
41
39
  # @param node — the receiver EXPRESSION.
42
40
  # @param scope — the scope the factory and ancestor lookups resolve against.
@@ -55,7 +55,7 @@ module Rigor
55
55
 
56
56
  # A form long enough to suggest generated or accumulated data rather than a literal a reader wrote;
57
57
  # past it the folded `Constant[String]` stops being useful in a diagnostic and only costs memory.
58
- # Mirrors the byte ceiling `ShapeDispatch`'s own `tuple_join` fold applies for the same reason.
58
+ # Counts pairs where `ShapeDispatch`'s `tuple_join` fold caps bytes, for the same reason.
59
59
  FORM_PAIR_LIMIT = 64
60
60
  private_constant :FORM_PAIR_LIMIT
61
61
 
@@ -41,6 +41,7 @@ require_relative "method_dispatcher/universal_object_dispatch"
41
41
  require_relative "method_dispatcher/singleton_mixin_dispatch"
42
42
  require_relative "method_dispatcher/method_folding"
43
43
  require_relative "method_dispatcher/optional_count"
44
+ require_relative "../run_scoped_slots"
44
45
 
45
46
  module Rigor
46
47
  module Inference
@@ -204,8 +205,8 @@ module Rigor
204
205
  # user-authored RBS overrides substrate synthesis) and ABOVE the dependency-source
205
206
  # inference tier so a plugin's declared emit table beats the generic gem-source fallback
206
207
  # for the same class. Slice 6a-TierB (origin_module dispatch) lands precise return types
207
- # for Tier B emissions; Tier C emissions still return `Dynamic[T]` at this tier (slice 6b
208
- # is the Tier C promotion via ADR-13's resolver chain).
208
+ # for Tier B emissions; Tier C emissions return their manifest `return_type` when the environment
209
+ # names that class (slice 6b), and `Dynamic[T]` otherwise.
209
210
  synthetic_result = try_synthetic_method(
210
211
  receiver_type, method_name, arg_types, block_type, environment
211
212
  )
@@ -535,10 +536,9 @@ module Rigor
535
536
  # The question is asked HERE, of the same two tiers `resolve` consults and in the same order, rather
536
537
  # than reconstructed in the veto: a name this reports as answered is a name the dispatch really would
537
538
  # resolve, and no plugin knowledge moves into the engine's veto. The tiers are the gated
538
- # `dynamic_return` walk and the ADR-16 synthetic-method index (Tier C `heredoc_templates` emissions
539
- # today; the Tier B `Plugin::Macro::TraitRegistry` explosions share that table but cannot populate it
540
- # until #476 threads a real environment into the pre-pass that builds it, so no trait member reaches
541
- # this predicate in production yet — it will when #476 lands, with no change here).
539
+ # `dynamic_return` walk and the ADR-16 synthetic-method index (Tier C `heredoc_templates` emissions and
540
+ # the Tier B `Plugin::Macro::TraitRegistry` explosions, which share that table; since #476 the pre-pass
541
+ # explodes a trait module whenever the project's RBS declares it).
542
542
  #
543
543
  # It is a predicate, not a type: the veto's job is to stop the top-level bind, after which the call
544
544
  # falls through to the ordinary dispatch chain and the tier that answered here answers there too. The
@@ -916,8 +916,7 @@ module Rigor
916
916
  result || EMPTY_CONTRIBUTIONS
917
917
  end
918
918
 
919
- # Runs the precision tiers (constant fold, shape dispatch, file-path fold, block fold) in
920
- # order and returns the first non-nil answer. Each tier owns its own receiver/argument
919
+ # Runs the precision tiers in order and returns the first non-nil answer. Each tier owns its own receiver/argument
921
920
  # shape checks; a tier that does not recognise the receiver returns nil so the next tier
922
921
  # can try. The RBS tier sits below this chain and is invoked by the outer `dispatch`
923
922
  # method.
@@ -926,7 +925,7 @@ module Rigor
926
925
  # (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
927
926
  # exact-value folds win first, the stdlib singleton folders sit in the middle (each
928
927
  # gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
929
- # BlockFolding runs last because its rules only apply to block-taking calls — the cheaper
928
+ # BlockFolding runs near the end because its rules only apply to block-taking calls — the cheaper
930
929
  # arity folds above it filter the common cases first. Adding a precise tier is a one-line
931
930
  # append here rather than another link in a hand-written `||` ladder.
932
931
  PRECISE_TIERS_HEAD = Ractor.make_shareable([
@@ -1031,6 +1030,8 @@ module Rigor
1031
1030
 
1032
1031
  fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
1033
1032
  return nil if fallback_receiver.nil?
1033
+ return nil if declared_by_external_ancestor?(receiver_type, environment, context)
1034
+ return nil if project_singleton_definer?(receiver_type, context)
1034
1035
 
1035
1036
  # Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
1036
1037
  # self` and other `self`-returning methods route through Object's RBS while still
@@ -1055,6 +1056,92 @@ module Rigor
1055
1056
  )
1056
1057
  end
1057
1058
 
1059
+ # Issue #2196 — a project class whose ancestry reaches a core or stdlib class before `Object` (`class FL <
1060
+ # Array`) answers a name that class declares from that class, not from `Object` / `Kernel`: Ruby dispatches
1061
+ # `select { … }` on an `FL` to `Array#select`, never to the private `Kernel#select` (`-> Array[String]`). The
1062
+ # ancestor tiers decline such a name where they cannot type it (`RbsDispatch#returns_the_walked_ancestry?`),
1063
+ # and the `Object` fallback then fabricated `Kernel#select`'s answer, so `fields.detect { |f| f.errors }` read
1064
+ # `f` as `String`. The fallback declines the name instead, which leaves the call `Dynamic[top]`.
1065
+ # {ExternalAncestorResolution.resolve} answers nil for a name only `Object`, `Kernel` or `BasicObject` declares,
1066
+ # so `puts` and `raise` keep resolving.
1067
+ def declared_by_external_ancestor?(receiver_type, environment, context)
1068
+ return false unless receiver_type.is_a?(Type::Nominal)
1069
+
1070
+ scope = context.scope
1071
+ return false if scope.nil?
1072
+
1073
+ memo = external_ancestor_memo(scope, environment)
1074
+ key = [receiver_type.class_name, context.method_name]
1075
+ return memo[key] if memo.key?(key)
1076
+
1077
+ memo[key] = !ExternalAncestorResolution.resolve(receiver_type.class_name, context.method_name, :instance,
1078
+ scope: scope, environment: environment,
1079
+ record_dependencies: false).nil?
1080
+ end
1081
+
1082
+ # Issue #2284 — a class object whose singleton chain holds a project definer of the name (its own `def
1083
+ # self.name`, one a project superclass wrote, an `extend`ed module's `def name`) answers from that definer,
1084
+ # not from `Module#name` / `Class#…` on the `Singleton[Object]` fallback: Ruby's singleton ancestry puts
1085
+ # every project entry ahead of `Class`. The fallback declines so the typer's singleton-body inference
1086
+ # answers the definer's return, or `Dynamic` where the slot is contested and the def may not be the one
1087
+ # that runs.
1088
+ #
1089
+ # The chain walk files the class edge of every entry it passes, so it runs only for a name some project file
1090
+ # defines at all ({BlockCallTiming.project_defines_anywhere?}, which files the `defines:<name>` edge either way,
1091
+ # #1459). Without it every class-body `private` / `attr_reader` call on a project class would depend on its
1092
+ # whole ancestry. The by-name answer is memoised per discovery index; the edge is filed on every call.
1093
+ def project_singleton_definer?(receiver_type, context)
1094
+ return false unless receiver_type.is_a?(Type::Singleton)
1095
+
1096
+ scope = context.scope
1097
+ return false if scope.nil?
1098
+ return false unless project_defines_name?(scope, context.method_name)
1099
+
1100
+ !scope.singleton_def_through_ancestors(receiver_type.class_name, context.method_name).last.nil?
1101
+ end
1102
+
1103
+ def project_defines_name?(scope, method_name)
1104
+ memo = defines_name_memo(scope)
1105
+ if memo.key?(method_name)
1106
+ Analysis::DependencyRecorder.read_missing(:defines, method_name) if Analysis::DependencyRecorder.active?
1107
+ return memo[method_name]
1108
+ end
1109
+
1110
+ memo[method_name] = BlockCallTiming.project_defines_anywhere?(method_name, scope)
1111
+ end
1112
+
1113
+ DEFINES_NAME_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_defines_name__)
1114
+ private_constant :DEFINES_NAME_MEMO_KEY
1115
+
1116
+ # One slot per discovery index and environment, as {#external_ancestor_memo} keeps its own: the answer reads
1117
+ # only the frozen discovery tables and the environment's `pre_eval:` patches.
1118
+ def defines_name_memo(scope)
1119
+ discovery = scope.discovery
1120
+ environment = scope.environment
1121
+ slot = Thread.current[DEFINES_NAME_MEMO_KEY]
1122
+ unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1123
+ slot = [discovery, environment, {}]
1124
+ Thread.current[DEFINES_NAME_MEMO_KEY] = slot
1125
+ end
1126
+ slot[2]
1127
+ end
1128
+
1129
+ EXTERNAL_ANCESTOR_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_external_ancestor__)
1130
+ private_constant :EXTERNAL_ANCESTOR_MEMO_KEY
1131
+
1132
+ # One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
1133
+ # reads only the frozen discovery tables and the RBS environment, and its walk is withheld from dependency
1134
+ # recording, so it may be served again for the same pair.
1135
+ def external_ancestor_memo(scope, environment)
1136
+ discovery = scope.discovery
1137
+ slot = Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY]
1138
+ unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1139
+ slot = [discovery, environment, {}]
1140
+ Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY] = slot
1141
+ end
1142
+ slot[2]
1143
+ end
1144
+
1058
1145
  # True when the call node carries an explicit receiver that is not the literal `self`.
1059
1146
  # Such a call cannot legally dispatch to a private method, so the user-class fallback must
1060
1147
  # skip private signatures rather than return a confidently-wrong type. Returns false for
@@ -1099,12 +1186,14 @@ module Rigor
1099
1186
  #
1100
1187
  # `RbsDispatch` already distributes over a `Union` (and erases a `Difference` to its base, #533),
1101
1188
  # but it does so INSIDE one tier, so a member that needs a LOWER tier — a user class with no RBS,
1102
- # a discovered `def`, an opt-in gem — makes the whole union decline. That is the corpus shape:
1103
- # rigor-actionpack types `params[:k]` as `ActionController::Parameters | nil`, the `Parameters` arm
1104
- # resolves only through the user-class fallback, and so `params[:k].present?` / `#==` / `#blank?` /
1105
- # `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the named-pair
1106
- # census drains 137 sites on redmine and 56 on mastodon, and `rigor check` reports the same
1107
- # diagnostics on both arms across eight survey targets.
1189
+ # a discovered `def`, an opt-in gem — makes the whole union decline. That was the corpus shape before
1190
+ # #2430: rigor-actionpack typed `params[:k]` as the bare `ActionController::Parameters | nil` union, the
1191
+ # `Parameters` arm resolved only through the user-class fallback, and so `params[:k].present?` / `#==`
1192
+ # / `#blank?` / `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the
1193
+ # named-pair census drained 137 sites on redmine and 56 on mastodon, and `rigor check` reported the
1194
+ # same diagnostics on both arms across eight survey targets. The plugin now answers a `Dynamic`
1195
+ # carrier, which this projection does not reach, so those off-chain calls read `Dynamic[top]` until
1196
+ # the carrier is projected here too (#2448).
1108
1197
  #
1109
1198
  # **Why the bottom and not next to `RbsDispatch`.** Sitting here, the tier can only ever replace a
1110
1199
  # `Dynamic[top]` the typer was about to produce — the same ADR-5 argument {UniversalObjectDispatch}
@@ -1257,7 +1346,7 @@ module Rigor
1257
1346
  range_lift = range_new_lift(receiver_type.class_name, arg_types)
1258
1347
  return range_lift if range_lift
1259
1348
 
1260
- set_lift = set_new_lift(receiver_type.class_name, arg_types)
1349
+ set_lift = set_new_lift(receiver_type.class_name, arg_types, context)
1261
1350
  return set_lift if set_lift
1262
1351
 
1263
1352
  hash_lift = hash_new_lift(receiver_type.class_name, arg_types, context&.call_node)
@@ -1307,8 +1396,8 @@ module Rigor
1307
1396
  # class is representable as the parent's singleton type — its singleton-method surface is
1308
1397
  # the parent's (plus whatever the block defines, which we do not statically track here),
1309
1398
  # so `Singleton[Parent]` lets downstream `klass.some_class_method` resolve. No parent →
1310
- # `singleton(Object)`. Anything else (dynamic parent, more than one positional, …) falls
1311
- # back to `Nominal[Class]` via the surrounding `meta_new` tail.
1399
+ # `singleton(Object)` (or the block's anonymous class, #319 below). Anything else (dynamic parent, more
1400
+ # than one positional, …) falls back to `Nominal[Class]` via the surrounding `meta_new` tail.
1312
1401
  # #319 — a `Class.new do ... end` with no parent is NOT `Object`: the block body is a class body, so the
1313
1402
  # class owns whatever that body defines. Answering `Singleton[Object]` handed the call site `Object`'s
1314
1403
  # zero-arity `new` and `Object`'s method surface, so an `initialize(bucket)` written right there produced
@@ -1613,19 +1702,10 @@ module Rigor
1613
1702
  # are given or the single argument is a `Tuple` whose every element is a `Constant[T]`.
1614
1703
  # Mirrors `SetFolding#fold_new` but lives here so the `:new` path in
1615
1704
  # `try_meta_introspection` can reach it before the RBS tier answers `Nominal[Set]`.
1616
- def set_new_lift(class_name, arg_types)
1705
+ def set_new_lift(class_name, arg_types, context = nil)
1617
1706
  return nil unless class_name == "Set"
1618
- return Type::Combinator.constant_of(::Set.new) if arg_types.empty?
1619
- return nil if arg_types.size > 1
1620
-
1621
- arg = arg_types.first
1622
- return nil unless arg.is_a?(Type::Tuple)
1623
- return nil unless arg.elements.all?(Type::Constant)
1624
1707
 
1625
- values = arg.elements.map(&:value)
1626
- Type::Combinator.constant_of(::Set.new(values))
1627
- rescue StandardError
1628
- nil
1708
+ SetFolding.fold_new(arg_types, context&.call_node, context&.block_type)
1629
1709
  end
1630
1710
 
1631
1711
  # `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to `Constant[Regexp]` when
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "../../source/literals"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module ModuleFunctionState
9
+ # Issue #2209 — the `module_function` spellings other than the receiverless call that the deferred-ranges
10
+ # prescan ({ModuleFunctionState.prescan_deferred}) reads: `self.module_function(...)`, and
11
+ # `send(:module_function, ...)` or `__send__(:module_function, ...)` on no receiver or `self`, which Ruby runs
12
+ # as the receiverless call. `public_send` raises on a private method such as `module_function`, so it is not
13
+ # one.
14
+ #
15
+ # A literal name gives the `:singleton` row and a `def` argument the `:both` or `:singleton` row a receiverless
16
+ # call gives. The bare form, and an argument that is neither (`*names`, a local), gives an {ANY_ROW_NAME} row
17
+ # over the call: the prescan reads neither the toggle these spellings set nor such a name, so every singleton
18
+ # copy of the owner is uncertain (`DefinerResolution.module_function_row?`).
19
+ module Spellings
20
+ SEND_CALLS = %i[send __send__].freeze
21
+ private_constant :SEND_CALLS
22
+
23
+ module_function
24
+
25
+ # The arguments the spelling `node` passes `module_function`, or nil for any other call.
26
+ def arguments(node)
27
+ arguments = node.arguments&.arguments || []
28
+ return arguments if node.name == :module_function && node.receiver.is_a?(Prism::SelfNode)
29
+ return nil unless SEND_CALLS.include?(node.name)
30
+ return nil unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
31
+ return nil unless Source::Literals.symbol_or_string(arguments.first) == :module_function
32
+
33
+ arguments.drop(1)
34
+ end
35
+
36
+ # Appends the rows of the spelling `node` with `arguments` ({.arguments}) to `ranges`, and prescans the rest
37
+ # as {ModuleFunctionState.prescan_deferred} prescans a receiverless call's.
38
+ def prescan(node, arguments, qualified_prefix, in_singleton_class, offsets, ranges)
39
+ owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
40
+ call_span = [node.location.start_offset, node.location.end_offset]
41
+ any = arguments.empty?
42
+ arguments.each do |arg|
43
+ if arg.is_a?(Prism::DefNode)
44
+ kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
45
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
46
+ elsif (name = Source::Literals.symbol_or_string(arg))
47
+ ranges << [*call_span, name, :singleton, owner]
48
+ else
49
+ any = true
50
+ nested_prescan(arg, qualified_prefix, in_singleton_class, offsets, ranges)
51
+ end
52
+ end
53
+ ranges << [*call_span, ANY_ROW_NAME, :singleton, owner] if any
54
+ nested_prescan(node.block, qualified_prefix, in_singleton_class, offsets, ranges) if node.block
55
+ end
56
+
57
+ def nested_prescan(node, qualified_prefix, in_singleton_class, offsets, ranges)
58
+ ModuleFunctionState.prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges,
59
+ nested: true)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -76,6 +76,17 @@ module Rigor
76
76
  (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
77
77
  end
78
78
 
79
+ # The `def` nodes a receiverless `public`, `private` or `protected` call takes as arguments (`private def x`,
80
+ # #2254). Ruby evaluates each one before the call, so it runs whenever the call's statement does. A named call
81
+ # with a symbol takes no def, and a call with a receiver is not read. A `module_function def` is not read here:
82
+ # its singleton copy is not modelled, so that gap stays as it was.
83
+ def visibility_call_defs(node)
84
+ return [] unless node.is_a?(Prism::CallNode) && node.receiver.nil?
85
+ return [] unless %i[public private protected].include?(node.name)
86
+
87
+ node.arguments&.arguments&.grep(Prism::DefNode) || []
88
+ end
89
+
79
90
  # The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
80
91
  # is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
81
92
  # and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
@@ -318,10 +329,20 @@ module Rigor
318
329
  end
319
330
  end
320
331
 
332
+ # Issue #2209 — the row name of a deferred-ranges row that stands for every name of its owner
333
+ # ({Spellings}).
334
+ ANY_ROW_NAME = :*
335
+
336
+ # Issue #2209 — the deferred-ranges row names that make `method_name` a module function on their owner: its
337
+ # own and {ANY_ROW_NAME}.
338
+ def row_names(method_name) = [method_name.to_sym, ANY_ROW_NAME]
339
+
321
340
  # The prescan's arms for a call; true when the arm took the call and its subtree.
322
341
  def prescanned_call?(node, qualified_prefix, in_singleton_class, offsets, ranges, nested)
323
342
  if call?(node)
324
343
  prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
344
+ elsif (arguments = Spellings.arguments(node))
345
+ Spellings.prescan(node, arguments, qualified_prefix, in_singleton_class, offsets, ranges)
325
346
  elsif visibility_reset?(node)
326
347
  offsets << [node.location.start_offset, nested ? :maybe_off : :off]
327
348
  elsif ScopeIndexer.receiver_eval_call?(node) || (node.block && ScopeIndexer.meta_new_constant_rvalue?(node))
@@ -332,7 +353,6 @@ module Rigor
332
353
  true
333
354
  end
334
355
 
335
- # The prescan's call arm.
336
356
  def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
337
357
  if bare?(node)
338
358
  offsets << [node.location.start_offset, :on]
@@ -374,3 +394,5 @@ module Rigor
374
394
  end
375
395
  end
376
396
  end
397
+
398
+ require_relative "module_function_state/spellings"
@@ -12,7 +12,7 @@ module Rigor
12
12
  #
13
13
  # `Rigor::Inference::MultiTargetBinder` decomposes a tuple- or array-shaped right-hand side type
14
14
  # against a Prism multi-target tree and produces a `name -> Rigor::Type` binding map. The
15
- # binder is shared between four surfaces:
15
+ # binder is shared between five surfaces:
16
16
  #
17
17
  # 1. `Rigor::Inference::StatementEvaluator#eval_multi_write` for the statement-level `a, b =
18
18
  # rhs` form (`Prism::MultiWriteNode`).