rigortype 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -8,8 +8,12 @@ require_relative "../rbs_type_translator"
8
8
  require_relative "../external_ancestor_resolution"
9
9
  require_relative "../void_origin"
10
10
  require_relative "../optimistic_origin"
11
+ require_relative "argument_distribution"
12
+ require_relative "interface_shape"
13
+ require_relative "class_keyed_block_params"
11
14
  require_relative "overload_selector"
12
15
  require_relative "self_substitute"
16
+ require_relative "../../typing_state"
13
17
 
14
18
  module Rigor
15
19
  module Inference
@@ -33,8 +37,8 @@ module Rigor
33
37
  # type-parameter names (`Array` -> `[:Elem]`, `Hash` -> `[:K, :V]`, ...) to build a substitution map;
34
38
  # that map is then threaded through {RbsTypeTranslator} so a return type like `::Array[Elem]`
35
39
  # resolves to `Nominal["Array", [Integer]]` rather than degrading the variable to `Dynamic[Top]`.
36
- # When arities mismatch (raw receiver, partial generics) the map is left empty and free variables
37
- # degrade as before.
40
+ # A raw receiver, or one with more arguments than the class declares, leaves the map empty and free
41
+ # variables degrade as before; fewer arguments bind the declared prefix (#1121).
38
42
  #
39
43
  # Slice 5 phase 1 projects shape-carrying receivers onto their underlying nominal so the existing
40
44
  # dispatch + substitution machinery works without duplication: `Tuple[Integer, String]` dispatches as
@@ -44,13 +48,14 @@ module Rigor
44
48
  #
45
49
  # Remaining limitations:
46
50
  #
47
- # * Keyword arguments reach `args` only as one trailing hash entry and are not matched against keyword
48
- # parameters, so overloads with required keywords are skipped.
49
- # * Method-level type parameters bind only from two positions: the block return type (Slice 6 phase C)
50
- # and a positional parameter whose declared type is EXACTLY a type variable (issue #303 —
51
+ # * Keyword arguments reach `args` as one trailing hash entry; the selector matches it against keyword
52
+ # parameters when the call's last argument is a keyword hash (#1727).
53
+ # * Method-level type parameters bind from the block return type (Slice 6 phase C) and from a
54
+ # positional parameter whose declared type is EXACTLY a type variable (issue #303 —
51
55
  # `def foo[T]: (T) -> T` binds `T` from the first argument, and carries it into a generic return
52
- # such as `-> Array[T]`). A variable reachable only through a container position (`Array[T] arg`),
53
- # a rest positional (`*T`), or a keyword parameter is still unbound and degrades to `Dynamic[Top]`.
56
+ # such as `-> Array[T]`), plus a `Range[A]` element (#834) and a `singleton(T)` class object (#2828).
57
+ # A variable reachable only through other container positions (`Array[T] arg`), a rest positional
58
+ # (`*T`), or a keyword parameter is still unbound and degrades to `Dynamic[Top]`.
54
59
  # A block-return variable that a parameter also names takes the value class the argument and the
55
60
  # block share once the call passes an argument, or stays unbound when they share none (see
56
61
  # {compose_block_type_vars}).
@@ -69,6 +74,23 @@ module Rigor
69
74
  # objects answer to methods their RBS omits (`ActionController::Base`, `Hash`, …).
70
75
  ALLOWED_RBS_COMPLETE_ANCESTORS = ["Rigor::Plugin::Base"].freeze
71
76
 
77
+ # #2681 — the owners whose members every class answers to. A name only they declare is not part of an
78
+ # allow-listed ancestor's contract, so a `def` of it never walks for the declaration it overrides.
79
+ OBJECT_SURFACE_OWNERS = %w[BasicObject Object Kernel Module Class].freeze
80
+
81
+ # Per-thread memo, keyed by environment: the Set of the names an allow-listed ancestor's RBS declarations
82
+ # declare outside {OBJECT_SURFACE_OWNERS}. It keeps {.def_declaration} off the ancestor walk for any other
83
+ # `def`.
84
+ #
85
+ # ADR-129 WD1 — a run-scoped Memo: the names are a pure function of the environment the entry is keyed on.
86
+ RBS_COMPLETE_MEMBER_NAMES = TypingState.memo(
87
+ :rbs_complete_member_names,
88
+ run_scoped: true, audit: "rbs complete member names",
89
+ reason: "a WeakKeyMap keyed on the environment; the names are read from its loader " \
90
+ "and plugin registry alone"
91
+ )
92
+ private_constant :RBS_COMPLETE_MEMBER_NAMES
93
+
72
94
  # Issue #1094 — the methods through which a value can answer Ruby's implicit `to_ary` conversion.
73
95
  # Multiple assignment and block auto-splat convert through `rb_check_array_type`, which calls
74
96
  # `to_ary` when defined and otherwise asks `respond_to_missing?`, dispatching to `method_missing`
@@ -216,6 +238,17 @@ module Rigor
216
238
  )
217
239
  end
218
240
 
241
+ # ADR-130 WD5 — interface dispatch switched off. Inside the block a `Rigor::Plugin` interface value keeps its
242
+ # carrier, but no call on it resolves through the interface's members: the receiver names no class, as
243
+ # `Dynamic[top]` names none. `rigor sig-gen` analyses here, so it writes no RBS from an interface a plugin
244
+ # author did not write against, and the behavioural parity gate's `:dispatch_off` mode runs here
245
+ # (`spec/integration/interface_parity_spec.rb`). The switch is a per-thread `TypingState` frame
246
+ # ({InterfaceShape.without_dispatch}), so a run under it is sequential: a pooled run raises (`PoolCoordinator`).
247
+ # No CLI flag or configuration key sets it.
248
+ def without_interface_dispatch(&) = InterfaceShape.without_dispatch(&)
249
+
250
+ def interface_dispatch? = InterfaceShape.dispatch?
251
+
219
252
  # Slice 6 (Phase C sub-phase 1) probe: returns the positional block-parameter types declared by
220
253
  # the receiving method's selected RBS overload, translated into `Rigor::Type`. Used by the
221
254
  # StatementEvaluator to bind block parameter names before evaluating the block body.
@@ -248,10 +281,89 @@ module Rigor
248
281
  )
249
282
  end
250
283
 
284
+ # The RBS declaration a `def method_name` on `class_name` (`kind` side) binds its parameters from
285
+ # (`MethodParameterBinder`): the class's own, or nil.
286
+ #
287
+ # #2681 — when `class_name` is a Ruby-source class with no RBS, the declaration the `def` overrides on an
288
+ # ADR-43 allow-listed ancestor, as an empty RBS declaration of the class would inherit it. Nil whenever the
289
+ # call bridge would decline for a reason other than `class_name`'s own definition (no scope, no allow-listed
290
+ # ancestor, a nearer definer between them), and for a declaration one of {OBJECT_SURFACE_OWNERS} makes:
291
+ # `Kernel#load` is every class's, not that ancestor's contract. `initialize` is never bridged: a constructor
292
+ # does not override its superclass's contract, and a subclass routinely takes different arguments.
293
+ def def_declaration(environment, class_name, kind, method_name, scope)
294
+ direct = lookup_method_on(environment, class_name, kind, method_name)
295
+ return direct if direct
296
+ return nil if environment.nil? || scope.nil? || method_name == :initialize
297
+ return nil unless rbs_complete_member_name?(environment, method_name)
298
+
299
+ ancestor = allowed_rbs_complete_ancestor(environment, class_name, kind, method_name, scope,
300
+ overriding: true)
301
+ overridden = ancestor && lookup_method_on(environment, ancestor, kind, method_name)
302
+ overridden unless overridden.nil? || object_surface?(overridden)
303
+ end
304
+
305
+ def object_surface?(method_definition)
306
+ OBJECT_SURFACE_OWNERS.include?(method_definition.defined_in.to_s.delete_prefix("::"))
307
+ end
308
+
309
+ # A class object carries no type arguments, so a singleton declaration binds none from the receiver.
310
+ NO_RECEIVER_TYPE_VARS = {}.freeze
311
+ private_constant :NO_RECEIVER_TYPE_VARS
312
+
313
+ # Issue #2884 — the return of a call on `Singleton[class_name]` through `method_definition`, a singleton
314
+ # declaration {DeclaredConstructor} found on `class_name` or an ancestor, so `instance` and `self` read
315
+ # `class_name` whichever class wrote it. Nil when the declaration states no return
316
+ # (`%a{rigor:v1:inferred-return}`).
317
+ def declared_singleton_return(context, method_definition, class_name)
318
+ return nil if RbsExtended.inferred_return?(method_definition)
319
+
320
+ translate_return_type(
321
+ method_definition,
322
+ class_name: class_name, kind: :singleton, method_name: context.method_name, args: context.args || [],
323
+ type_vars: NO_RECEIVER_TYPE_VARS, block_type: context.block_type, environment: context.environment,
324
+ scope: context.scope, call_node: context.call_node
325
+ )
326
+ end
327
+
251
328
  # rubocop:disable-next Metrics/ClassLength
252
329
  class << self
253
330
  private
254
331
 
332
+ # Whether some allow-listed ancestor's RBS declarations declare `method_name` outside
333
+ # {OBJECT_SURFACE_OWNERS}: the one-probe gate that leaves a `def` no ancestor declares, the common case
334
+ # in a project with no plugin class, on the cost it had before #2681. Read from the declarations, so a
335
+ # gem signature that fails to build reports nothing more than it did.
336
+ def rbs_complete_member_name?(environment, method_name)
337
+ rbs_complete_member_names(environment).include?(method_name)
338
+ end
339
+
340
+ def rbs_complete_member_names(environment)
341
+ return collect_rbs_complete_member_names(environment) if MemoAudit.enabled? && MemoAudit.suspended?
342
+
343
+ table = TypingState.current.weak_key_map(RBS_COMPLETE_MEMBER_NAMES)
344
+ names = table[environment]
345
+ if names.nil?
346
+ return table[environment] = MemoAudit.key_audit("rbs complete member names") do
347
+ collect_rbs_complete_member_names(environment)
348
+ end
349
+ end
350
+
351
+ MemoAudit.check("rbs complete member names", names, :environment) do
352
+ collect_rbs_complete_member_names(environment)
353
+ end
354
+ end
355
+
356
+ def collect_rbs_complete_member_names(environment)
357
+ loader = environment.rbs_loader
358
+ # An RBS-blind environment declares nothing, so no override takes an ancestor's parameters.
359
+ return Set.new.freeze if loader.nil?
360
+
361
+ ancestors = ALLOWED_RBS_COMPLETE_ANCESTORS + (environment.plugin_registry&.rbs_complete_ancestors || [])
362
+ ancestors.each_with_object(Set.new) do |ancestor, names|
363
+ names.merge(loader.declared_method_names(ancestor, skip: OBJECT_SURFACE_OWNERS))
364
+ end.freeze
365
+ end
366
+
255
367
  def dispatch_for(receiver:, method_name:, args:, environment:, block_type:, self_type_override: nil, # rubocop:disable Metrics/ParameterLists
256
368
  public_only: false, scope: nil, call_node: nil)
257
369
  args ||= []
@@ -281,14 +393,17 @@ module Rigor
281
393
  # Issue #1794 — a sequence's inherited Enumerable surface reads its Rigor-private element argument, with
282
394
  # `self` still the sequence.
283
395
  element = ArithmeticSequenceElements.element_receiver(receiver, method_name, environment)
284
- self_type_override ||= receiver if element
396
+ self_type_override = sequence_self(element, receiver, self_type_override)
285
397
  receiver = element || receiver
286
398
  descriptor = receiver_descriptor(receiver)
287
399
  return nil unless descriptor
288
400
 
289
401
  class_name, kind, receiver_args = descriptor
290
- method_definition = lookup_method(environment, class_name, kind, method_name, scope,
291
- call_node: call_node)
402
+ if kind == :interface
403
+ return dispatch_interface(receiver, class_name, receiver_args, method_name, args, environment, block_type,
404
+ scope: scope, call_node: call_node)
405
+ end
406
+ method_definition = lookup_method(environment, class_name, kind, method_name, scope, call_node: call_node)
292
407
  return nil unless method_definition
293
408
  return nil if public_only && method_private?(method_definition)
294
409
  # Issue #823 — a declaration that states the member's presence and parameters but not its
@@ -304,17 +419,11 @@ module Rigor
304
419
  type_vars = build_type_vars(environment, class_name, receiver_args)
305
420
  translate_return_type(
306
421
  method_definition,
307
- class_name: class_name,
308
- kind: kind,
309
- method_name: method_name,
310
- args: args,
311
- type_vars: type_vars,
312
- block_type: block_type,
313
- environment: environment,
422
+ class_name: class_name, kind: kind, method_name: method_name, args: args, type_vars: type_vars,
423
+ block_type: block_type, environment: environment,
314
424
  self_type_override: self_type_override ||
315
425
  SelfSubstitute.for(receiver, receiver_args, method_name, args, block_type),
316
- scope: scope,
317
- call_node: call_node
426
+ scope: scope, call_node: call_node
318
427
  )
319
428
  rescue StandardError
320
429
  # Defensive: if RBS' definition builder raises on a broken hierarchy (e.g., partially loaded
@@ -322,6 +431,55 @@ module Rigor
322
431
  nil
323
432
  end
324
433
 
434
+ # The `self` a call on a sequence's element keeps: the sequence (#1794), unless the caller already set one.
435
+ # A method rather than a two-value return, which would allocate an Array on every dispatch.
436
+ def sequence_self(element, receiver, self_type_override)
437
+ element ? (self_type_override || receiver) : self_type_override
438
+ end
439
+
440
+ # ADR-130 WD3 — a call on a `Rigor::Plugin` interface value (`Dynamic[Interface]`). A member the interface
441
+ # does not declare declines, so the call falls through the later tiers as one on `Dynamic[top]` does:
442
+ # the value's class is wider than the interface, and reporting such a call is #2686's opt-in rule. A
443
+ # declared member answers through the overloads that take the call's shape ({InterfaceShape}), with
444
+ # `self` the receiver and `instance` untyped. A block-pass that may be nil may run the call block-less, so
445
+ # it answers only when both shapes are taken and their returns agree.
446
+ # rubocop:disable-next Metrics/ParameterLists
447
+ def dispatch_interface(receiver, name, receiver_args, method_name, args, environment, block_type, scope:,
448
+ call_node:)
449
+ definition = interface_method(environment, name, method_name)
450
+ return nil if definition.nil?
451
+
452
+ type_vars = interface_type_vars(environment, name, receiver_args)
453
+ shape = lambda do |shape_block|
454
+ accepted = InterfaceShape.accepting(definition, args, block: !shape_block.nil?, call_node: call_node)
455
+ accepted && translate_return_type(
456
+ accepted, class_name: name, kind: :interface, method_name: method_name, args: args,
457
+ type_vars: type_vars, block_type: shape_block, environment: environment,
458
+ self_type_override: receiver, scope: scope, call_node: call_node
459
+ )
460
+ end
461
+ return shape.call(block_type) unless InterfaceShape.nilable_block_pass?(call_node, scope)
462
+
463
+ block_less = shape.call(nil)
464
+ block_less if block_less && block_less == shape.call(block_type || Type::Combinator.untyped)
465
+ end
466
+
467
+ # The interface's declaration of `method_name`, or nil when it declares none.
468
+ def interface_method(environment, name, method_name)
469
+ environment.rbs_loader.interface_definition(name)&.methods&.[](method_name.to_sym)
470
+ end
471
+
472
+ # The interface's own type parameters bound to the value's type arguments, as {#build_type_vars} binds a
473
+ # class's: a parameter left unbound (a defaulted one) reads `Dynamic[top]`.
474
+ def interface_type_vars(environment, name, receiver_args)
475
+ return NO_TYPE_VARS if receiver_args.empty?
476
+
477
+ param_names = environment.rbs_loader.interface_definition(name)&.type_params || []
478
+ return NO_TYPE_VARS if param_names.empty? || receiver_args.size > param_names.size
479
+
480
+ param_names.first(receiver_args.size).zip(receiver_args).to_h
481
+ end
482
+
325
483
  # Issue #661 — `Kernel#<=>: (untyped other) -> 0?` is the IDENTITY comparison: `0` when the two
326
484
  # are the same object, `nil` otherwise, and correct as written for a bare `Object`. Reached by
327
485
  # inheritance it stops being a statement about the call. `1.day <=> 2.days` typed as `0?`, and a
@@ -405,10 +563,18 @@ module Rigor
405
563
  # (ConstantFolding, ShapeDispatch#dispatch_integer_range) do not own.
406
564
  ["Integer", :instance, []]
407
565
  when Type::Dynamic
408
- receiver_descriptor(receiver.static_facet)
566
+ facet_descriptor(receiver.static_facet)
409
567
  end
410
568
  end
411
569
 
570
+ # A `Dynamic`'s facet names what the facet names, and a `Rigor::Plugin` interface facet names the interface
571
+ # (ADR-130 WD3), unless dispatch is switched off, where it names nothing, as `top` does.
572
+ def facet_descriptor(facet)
573
+ return receiver_descriptor(facet) unless facet.is_a?(Type::Interface)
574
+
575
+ [facet.name, :interface, facet.type_args] if interface_dispatch?
576
+ end
577
+
412
578
  # ADR-48 — project a `Data`/`Struct` member carrier to its tagging class (or the `Data`/`Struct`
413
579
  # supertype) so non-member calls (`inspect`, `==`, `frozen?`, ...) resolve through RBS rather
414
580
  # than mis-firing undefined-method. Precise member reads were already folded by DataFolding /
@@ -512,7 +678,10 @@ module Rigor
512
678
  # ADR-43 WD4 — the allow-list's manifest-declared half: a loaded plugin may name its own
513
679
  # contract classes in `rbs_complete_ancestors:` (e.g. rigor-graphql's `GraphQL::Schema::Object`),
514
680
  # extending the engine's hard-coded seed without editing this constant.
515
- def allowed_rbs_complete_ancestor(environment, class_name, kind, method_name, scope)
681
+ #
682
+ # #2681 — `overriding: true` asks for the declaration a `def` on `class_name` overrides (its parameter
683
+ # binding), so `class_name`'s own definition of the name is set aside; every nearer ancestor still shadows.
684
+ def allowed_rbs_complete_ancestor(environment, class_name, kind, method_name, scope, overriding: false)
516
685
  return nil if scope.nil?
517
686
  return nil if Rigor::Reflection.rbs_class_known?(class_name, environment: environment)
518
687
 
@@ -521,15 +690,22 @@ module Rigor
521
690
  # this guard the bridge would resolve e.g. `field` on `GraphQL::Schema::Object` while
522
691
  # the runtime actually calls a user `def self.field` on an intermediate `BaseObject` —
523
692
  # a wrong return type and a false `undefined-method`/arity reading downstream.
524
- return nil if scope.discovered_method?(class_name, method_name, kind)
693
+ return nil if !overriding && scope.discovered_method?(class_name, method_name, kind)
525
694
 
526
695
  registry = environment&.plugin_registry
527
696
  each_source_ancestor_candidate(scope, class_name) do |candidate|
528
- return nil if scope.discovered_method?(candidate, method_name, kind)
529
- next unless ALLOWED_RBS_COMPLETE_ANCESTORS.include?(candidate) ||
530
- registry&.rbs_complete_ancestor?(candidate)
531
-
532
- return shadowed_in_ruby_order?(scope, class_name, kind, method_name, candidate) ? nil : candidate
697
+ # #2681 — the engine seed's own source (`lib/rigor/plugin/base.rb`, which this repository's plugin
698
+ # check discovers) is the implementation its RBS describes, not a nearer definer. A project `def` on
699
+ # a manifest-declared ancestor is a reopen (`class Grape::API; def self.desc`), which Ruby calls
700
+ # instead of the declaration, so it still shadows.
701
+ seed = ALLOWED_RBS_COMPLETE_ANCESTORS.include?(candidate)
702
+ return nil if !seed && scope.discovered_method?(candidate, method_name, kind)
703
+ next unless seed || registry&.rbs_complete_ancestor?(candidate)
704
+
705
+ return nil if shadowed_in_ruby_order?(scope, class_name, kind, method_name, candidate,
706
+ overriding: overriding)
707
+
708
+ return candidate
533
709
  end
534
710
  nil
535
711
  end
@@ -543,7 +719,8 @@ module Rigor
543
719
  # passed over, as the breadth-first walk always did. An unsettled chain withholds too: a mixin edge the
544
720
  # tables cannot place (a receiver-form `Demo.include(M)` after the body, an edge in a conditional, a block or
545
721
  # a hook) may put a definer ahead of the ancestor.
546
- def shadowed_in_ruby_order?(scope, class_name, kind, method_name, ancestor)
722
+ # With `overriding`, `class_name`'s own entry is the overriding `def` itself and does not shadow.
723
+ def shadowed_in_ruby_order?(scope, class_name, kind, method_name, ancestor, overriding: false)
547
724
  chain = Scope::ResolutionChain.for(scope, class_name.to_s, kind, :methods)
548
725
  return true if chain.unsettled?
549
726
 
@@ -554,6 +731,8 @@ module Rigor
554
731
  return true if stop.nil?
555
732
 
556
733
  entries.first(stop).any? do |entry|
734
+ next false if overriding && !entry.external? && entry.name == class_name.to_s
735
+
557
736
  if entry.external?
558
737
  rbs_declares_ahead?(scope, entry, method_name)
559
738
  else
@@ -616,12 +795,22 @@ module Rigor
616
795
  return nil if scope.nil? || kind != :instance
617
796
  return nil if environment.nil?
618
797
 
798
+ if MemoAudit.enabled? && MemoAudit.suspended?
799
+ return compute_core_stdlib_ancestor_method(environment, class_name, method_name, scope)
800
+ end
801
+
619
802
  memo = core_stdlib_memo(environment, scope)
620
803
  key = [class_name.to_s, method_name.to_sym]
621
- return memo[key] if memo.key?(key)
804
+ if memo.key?(key)
805
+ return MemoAudit.check("core stdlib ancestor dispatch", memo[key], key[0], key[1]) do
806
+ compute_core_stdlib_ancestor_method(environment, class_name, method_name, scope)
807
+ end
808
+ end
622
809
 
623
810
  store_unless_recorded(memo, key) do
624
- compute_core_stdlib_ancestor_method(environment, class_name, method_name, scope)
811
+ MemoAudit.key_audit("core stdlib ancestor dispatch") do
812
+ compute_core_stdlib_ancestor_method(environment, class_name, method_name, scope)
813
+ end
625
814
  end
626
815
  end
627
816
 
@@ -648,12 +837,23 @@ module Rigor
648
837
  return nil if scope.nil? || kind != :instance
649
838
  return nil if environment.nil?
650
839
 
840
+ if MemoAudit.enabled? && MemoAudit.suspended?
841
+ return compute_included_module_method(environment, class_name, method_name,
842
+ scope)
843
+ end
844
+
651
845
  memo = mixin_ancestor_memo(environment, scope)
652
846
  key = [class_name.to_s, method_name.to_sym]
653
- return memo[key] if memo.key?(key)
847
+ if memo.key?(key)
848
+ return MemoAudit.check("mixin ancestor dispatch", memo[key], key[0], key[1]) do
849
+ compute_included_module_method(environment, class_name, method_name, scope)
850
+ end
851
+ end
654
852
 
655
853
  store_unless_recorded(memo, key) do
656
- compute_included_module_method(environment, class_name, method_name, scope)
854
+ MemoAudit.key_audit("mixin ancestor dispatch") do
855
+ compute_included_module_method(environment, class_name, method_name, scope)
856
+ end
657
857
  end
658
858
  end
659
859
 
@@ -681,11 +881,18 @@ module Rigor
681
881
 
682
882
  # The same one-slot memo shape as {#core_stdlib_memo}; see there for why it is one slot keyed
683
883
  # on the discovery index's identity, and what a recording run may store in it.
684
- MIXIN_ANCESTOR_MEMO_KEY = :__rigor_mixin_ancestor_dispatch__
685
- private_constant :MIXIN_ANCESTOR_MEMO_KEY
884
+ #
885
+ # ADR-129 WD1 — a run-scoped Memo, keyed as {#ancestor_memo_slot} says.
886
+ MIXIN_ANCESTOR_MEMO = TypingState.memo(
887
+ :mixin_ancestor_dispatch,
888
+ run_scoped: true, audit: "mixin ancestor dispatch",
889
+ reason: "one slot keyed on the identities of the discovery index and environment, and " \
890
+ "on whether the thread is recording; the answers read nothing else"
891
+ )
892
+ private_constant :MIXIN_ANCESTOR_MEMO
686
893
 
687
894
  def mixin_ancestor_memo(environment, scope)
688
- ancestor_memo_slot(MIXIN_ANCESTOR_MEMO_KEY, environment, scope)
895
+ ancestor_memo_slot(MIXIN_ANCESTOR_MEMO, environment, scope)
689
896
  end
690
897
 
691
898
  # The declines are conjunctive, so their ORDER is free — and it is chosen so the two that walk
@@ -855,23 +1062,31 @@ module Rigor
855
1062
  # the measurement. A `Scope` hands each analysed file its own discovery index, so an
856
1063
  # identity-keyed store would pin every file's index, and every RBS definition resolved
857
1064
  # against it, for the length of the run.
858
- CORE_STDLIB_ANCESTOR_MEMO_KEY = :__rigor_core_stdlib_ancestor_dispatch__
859
- private_constant :CORE_STDLIB_ANCESTOR_MEMO_KEY
1065
+ #
1066
+ # ADR-129 WD1 — a run-scoped Memo, keyed as {#ancestor_memo_slot} says.
1067
+ CORE_STDLIB_ANCESTOR_MEMO = TypingState.memo(
1068
+ :core_stdlib_ancestor_dispatch,
1069
+ run_scoped: true, audit: "core stdlib ancestor dispatch",
1070
+ reason: "one slot keyed on the identities of the discovery index and environment, and " \
1071
+ "on whether the thread is recording; the answers read nothing else"
1072
+ )
1073
+ private_constant :CORE_STDLIB_ANCESTOR_MEMO
860
1074
 
861
1075
  def core_stdlib_memo(environment, scope)
862
- ancestor_memo_slot(CORE_STDLIB_ANCESTOR_MEMO_KEY, environment, scope)
1076
+ ancestor_memo_slot(CORE_STDLIB_ANCESTOR_MEMO, environment, scope)
863
1077
  end
864
1078
 
865
1079
  # The slot behind both memos. Whether this thread is recording is part of its key, so a slot
866
1080
  # filled with every answer is never read by a recording caller, which may only reuse the
867
1081
  # answers {#store_unless_recorded} lets through.
868
- def ancestor_memo_slot(memo_key, environment, scope)
1082
+ def ancestor_memo_slot(memo_slot, environment, scope)
869
1083
  discovery = scope.discovery
870
1084
  recording = Rigor::Analysis::DependencyRecorder.recording?
871
- slot = Thread.current[memo_key]
1085
+ state = TypingState.current
1086
+ slot = state[memo_slot]
872
1087
  unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment) && slot[2] == recording
873
1088
  slot = [discovery, environment, recording, {}]
874
- Thread.current[memo_key] = slot
1089
+ state[memo_slot] = slot
875
1090
  end
876
1091
  slot[3]
877
1092
  end
@@ -1086,7 +1301,42 @@ module Rigor
1086
1301
  block_required: !block_type.nil?,
1087
1302
  environment: environment
1088
1303
  )
1089
- return nil if candidates.empty?
1304
+ if candidates.empty?
1305
+ return unselected_return(method_definition, self_type, instance_type, type_vars, args, block_type,
1306
+ scope, call_node, call_site, environment, keywords_last)
1307
+ end
1308
+
1309
+ selected_return(candidates, method_definition, self_type, instance_type, type_vars, args, block_type, scope,
1310
+ call_node, call_site, environment, keywords_last)
1311
+ end
1312
+
1313
+ # Issue #2429 — a block call no overload matches, whose block overloads the arity fallback cannot decide
1314
+ # between, selects nothing. An argument union may still select per value (#2379): `S.h(flag ? "s" : 1) { }`
1315
+ # takes the `String` arm for `"s"` and the `Integer` arm for `1`. Otherwise the tier declines (nil), so a
1316
+ # later tier, such as the project's own definition of the method, answers.
1317
+ # rubocop:disable Metrics/ParameterLists
1318
+ def unselected_return(method_definition, self_type, instance_type, type_vars, args, block_type, scope,
1319
+ call_node, call_site, environment, keywords_last)
1320
+ # rubocop:enable Metrics/ParameterLists
1321
+ return nil if keywords_last
1322
+
1323
+ distributed = distributed_argument_return(method_definition, nil, self_type, instance_type, type_vars, args,
1324
+ block_type, scope, call_node, call_site, environment)
1325
+ distributed.equal?(NOT_DISTRIBUTED) ? nil : distributed
1326
+ end
1327
+
1328
+ # The return of the overloads the call selected: per argument value where a union or supertype argument
1329
+ # selects more than `candidates` ({#distributed_argument_return}), else their join.
1330
+ # rubocop:disable Metrics/ParameterLists
1331
+ def selected_return(candidates, method_definition, self_type, instance_type, type_vars, args, block_type,
1332
+ scope, call_node, call_site, environment, keywords_last)
1333
+ # rubocop:enable Metrics/ParameterLists
1334
+ if candidates.size == 1 && !keywords_last
1335
+ distributed = distributed_argument_return(method_definition, candidates.first, self_type, instance_type,
1336
+ type_vars, args, block_type, scope, call_node, call_site,
1337
+ environment)
1338
+ return distributed unless distributed.equal?(NOT_DISTRIBUTED)
1339
+ end
1090
1340
 
1091
1341
  record_dispatch_provenance(method_definition, candidates.first, scope, call_node, call_site)
1092
1342
  join_candidate_returns(
@@ -1169,6 +1419,75 @@ module Rigor
1169
1419
  end
1170
1420
  end
1171
1421
 
1422
+ # Issue #2379 — a positional argument selects its overload per runtime value, as the receiver does
1423
+ # (`dispatch` distributes a union receiver). Selecting once for the whole argument took the first overload
1424
+ # it `maybe`-matched: `Integer#+` with an `Integer | Float` argument read `(Integer) -> Integer`, and with a
1425
+ # `Numeric` one as well. So a precise union argument selects per member ({ArgumentDistribution.lists}; a
1426
+ # member no overload takes keeps the first-overload fallback, as a non-matching argument does), and a
1427
+ # nominal argument wider than the selected overload's parameter takes every strictly typed overload it
1428
+ # overlaps ({OverloadSelector.contested_matches}). The returns join as a plain union, and a return that does
1429
+ # not translate declines the call (nil), as the #521 join does. Union arguments that combine past the list
1430
+ # limit answer `Dynamic[top]`: neither the first overload nor any narrower reading is sound there. When
1431
+ # every list selects `chosen` alone the call answers {NOT_DISTRIBUTED} and reads as before, so a precise
1432
+ # argument keeps its answer; so does a call holding an imprecise argument (the #521 join owns it) or a splat.
1433
+ # rubocop:disable Metrics/ParameterLists
1434
+ def distributed_argument_return(method_definition, chosen, self_type, instance_type, type_vars, args,
1435
+ block_type, scope, call_node, call_site, environment)
1436
+ # rubocop:enable Metrics/ParameterLists
1437
+ selector = { self_type: self_type.is_a?(Type::Dynamic) ? self_type.static_facet : self_type,
1438
+ instance_type: instance_type, type_vars: type_vars, block_required: !block_type.nil?,
1439
+ environment: environment }
1440
+ return NOT_DISTRIBUTED unless distributable_arguments?(method_definition, args, call_node)
1441
+
1442
+ lists = ArgumentDistribution.lists(args)
1443
+ return Type::Combinator.untyped if lists.equal?(ArgumentDistribution::OVER_LIMIT)
1444
+
1445
+ per_list = argument_distribution_matches(method_definition, chosen, args, lists, selector)
1446
+ return NOT_DISTRIBUTED if per_list.nil?
1447
+
1448
+ overloads = per_list.flat_map(&:last).uniq
1449
+ if overloads.size == 1
1450
+ record_dispatch_provenance(method_definition, overloads.first, scope, call_node, call_site)
1451
+ end
1452
+ returns = per_list.flat_map do |arg_types, matches|
1453
+ matches.map do |method_type|
1454
+ join_candidate_returns(
1455
+ [method_type],
1456
+ method_definition: method_definition,
1457
+ self_type: self_type, instance_type: instance_type, type_vars: type_vars,
1458
+ args: arg_types, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
1459
+ alias_expander: environment.rbs_loader
1460
+ )
1461
+ end
1462
+ end
1463
+ returns.any?(&:nil?) ? nil : Type::Combinator.union(*returns.uniq)
1464
+ end
1465
+
1466
+ # Each argument list (`lists`, from {ArgumentDistribution.lists}, or the call's own) with the overloads it
1467
+ # selects, or nil when every list selects `chosen` alone. `selector` holds {OverloadSelector}'s keyword
1468
+ # inputs.
1469
+ def argument_distribution_matches(method_definition, chosen, args, lists, selector)
1470
+ return nil if lists.nil? && args.none?(Type::Nominal)
1471
+
1472
+ per_list = (lists || [args]).map do |arg_types|
1473
+ picked = chosen if arg_types.equal?(args)
1474
+ picked ||= OverloadSelector.select(method_definition, arg_types:, **selector)
1475
+ # #2429's block fallback may answer no overload; the call then reads as it did undistributed.
1476
+ return nil unless picked
1477
+
1478
+ contested = OverloadSelector.contested_matches(method_definition, picked, arg_types:, **selector)
1479
+ [arg_types, contested || [picked]]
1480
+ end
1481
+ per_list unless per_list.all? { |_, matches| matches.size == 1 && matches.first.equal?(chosen) }
1482
+ end
1483
+
1484
+ # Only a call with overloads to choose between and precise arguments it spells out one by one may select
1485
+ # differently per argument value: an imprecise argument is the #521 join's, a splat hides the positions.
1486
+ def distributable_arguments?(method_definition, args, call_node)
1487
+ method_definition.method_types.size > 1 && !args.empty? &&
1488
+ !ImpreciseArgument.any_in?(args, false) && !splat_indices(call_node, args)
1489
+ end
1490
+
1172
1491
  def join_distributed_returns(returns, keywords)
1173
1492
  return nil if returns.any?(&:nil?)
1174
1493
 
@@ -1412,11 +1731,24 @@ module Rigor
1412
1731
  # to {#shared_value_class} where both sides have one, and to `Dynamic[top]` otherwise. The key
1413
1732
  # stays in the map either way so {#compose_arg_type_vars} does not bind the variable from the
1414
1733
  # argument alone.
1734
+ #
1735
+ # A block return spelled as the variable beside falsey arms (`(nil | false | U)`, `U?`) solves the
1736
+ # variable from the block's type less those arms ({#strip_falsey_arms}): `filter_map` keeps only
1737
+ # the block's truthy values, so a block answering `String | nil` binds `U` to `String`. The strip
1738
+ # trusts the declared method to discard those values, so a receiver class whose Ruby source
1739
+ # defines the method itself leaves the variable unbound: `def filter_map(&b) = map(&b).compact`
1740
+ # under an `include Enumerable[Integer]` declaration keeps `false`, and `Array[true]` would fold
1741
+ # `b.last == false` always-falsey on correct code.
1415
1742
  def compose_block_type_vars(method_type, type_vars, block_type, args, scope:, call_node:, call_site:)
1416
1743
  return type_vars if block_type.nil?
1417
1744
 
1418
1745
  block_var_name = method_type_block_return_variable(method_type)
1419
1746
  return type_vars if block_var_name.nil?
1747
+
1748
+ arms = block_return_falsey_arms(method_type)
1749
+ return type_vars if arms.any? && project_redefined?(scope, call_site)
1750
+
1751
+ block_type = strip_falsey_arms(block_type, arms)
1420
1752
  return type_vars.merge(block_var_name => block_type) unless
1421
1753
  argument_reaches_variable?(method_type, block_var_name, args)
1422
1754
 
@@ -1564,7 +1896,8 @@ module Rigor
1564
1896
  #
1565
1897
  # * only a positional parameter (required or optional) whose declared type is EXACTLY
1566
1898
  # `RBS::Types::Variable`, plus the one container shape issue #834 admits (see
1567
- # {#range_element_binding}) — no general container walk, so `(Array[T]) -> T` still degrades;
1899
+ # {#range_element_binding}) and the class-object shape issue #2828 admits ({#singleton_param_binding}) —
1900
+ # no general container walk, so `(Array[T]) -> T` still degrades;
1568
1901
  # * only names the SELECTED overload declares in its own `type_params` (a class-level variable
1569
1902
  # keeps its receiver-derived binding);
1570
1903
  # * an existing key wins, so the receiver and the block return type both outrank an argument;
@@ -1573,7 +1906,7 @@ module Rigor
1573
1906
  #
1574
1907
  # See {#arg_binding_permitted?} for why the contribution is gated on the call site.
1575
1908
  def compose_arg_type_vars(method_type, type_vars, args, scope:, call_node:, call_site:)
1576
- bindings = arg_type_var_bindings(method_type, type_vars, args)
1909
+ bindings = arg_type_var_bindings(method_type, type_vars, args, scope&.environment, call_node)
1577
1910
  return type_vars if bindings.empty?
1578
1911
  return type_vars unless arg_binding_permitted?(scope, call_node, call_site)
1579
1912
 
@@ -1582,7 +1915,7 @@ module Rigor
1582
1915
 
1583
1916
  # The candidate bindings, computed before the guard so a non-generic overload (the overwhelming
1584
1917
  # majority) never pays for the `scope` probes.
1585
- def arg_type_var_bindings(method_type, type_vars, args)
1918
+ def arg_type_var_bindings(method_type, type_vars, args, environment = nil, call_node = nil)
1586
1919
  declared = declared_type_param_names(method_type)
1587
1920
  return EMPTY_TYPE_VARS if declared.empty? || args.empty?
1588
1921
 
@@ -1594,6 +1927,11 @@ module Rigor
1594
1927
  next if arg.nil?
1595
1928
 
1596
1929
  name, bound = param_binding(param, arg, declared, type_vars)
1930
+ if name.nil?
1931
+ name, bound = singleton_param_binding(method_type, param, arg, declared, type_vars,
1932
+ environment)
1933
+ next if name && class_factory_argument?(call_node)
1934
+ end
1597
1935
  next if name.nil?
1598
1936
 
1599
1937
  bound = Type::Combinator.widen_value_pinned(bound) if upper_bounded?(method_type, name)
@@ -1629,6 +1967,80 @@ module Rigor
1629
1967
  range_element_binding(param, arg, declared, type_vars)
1630
1968
  end
1631
1969
 
1970
+ # Issue #2828 — a positional parameter spelled `singleton(T)` for a method-level variable `T` binds `T`
1971
+ # to the instance type of the class object the call passes: `Resolv::DNS#getresources: [T < Resource]
1972
+ # (dns_name, singleton(T)) -> Array[T]` answers `Array[Resolv::DNS::Resource::IN::MX]` for an
1973
+ # `IN::MX` argument. A union of class objects binds the union of their instances, as a bare `T` binds
1974
+ # its argument union. Every other argument — `Dynamic`, an instance, a union with a non-class member —
1975
+ # binds nothing, and neither does a class the declared upper bound does not provably admit: an
1976
+ # interface, alias or other non-class bound, or a class the environment cannot order below the bound.
1977
+ def singleton_param_binding(method_type, param, arg, declared, type_vars, environment)
1978
+ name = singleton_variable_name(param, declared, type_vars)
1979
+ return NO_BINDING if name.nil?
1980
+
1981
+ members = arg.is_a?(Type::Union) ? arg.members : [arg]
1982
+ return NO_BINDING unless members.all?(Type::Singleton)
1983
+
1984
+ within = members.all? { |member| within_upper_bound?(method_type, name, member, environment) }
1985
+ return NO_BINDING unless within
1986
+
1987
+ instances = members.map { |member| Type::Combinator.nominal_of(member.class_name) }
1988
+ [name, instances.size == 1 ? instances.first : Type::Combinator.union(*instances)]
1989
+ end
1990
+
1991
+ # RBS parses `singleton(T)` as a class singleton whose type name is the bare, unqualified `T`; the
1992
+ # parameter names a variable when that name is one the overload declares and has not yet bound.
1993
+ def singleton_variable_name(param, declared, type_vars)
1994
+ declared_type = param.type
1995
+ return nil unless declared_type.is_a?(RBS::Types::ClassSingleton)
1996
+
1997
+ type_name = declared_type.name
1998
+ return nil unless type_name.namespace.empty?
1999
+
2000
+ name = type_name.name
2001
+ return nil unless declared.include?(name)
2002
+ return nil if type_vars.key?(name)
2003
+
2004
+ name
2005
+ end
2006
+
2007
+ # Whether the call passes, as any positional argument, a class built by `Class.new`, `Struct.new` or
2008
+ # `Data.define` with a block. The engine types `Class.new(Widget) { def extra = 1 }` as `singleton(Widget)`,
2009
+ # so binding `T` from it would read the instance as a plain `Widget` and report `extra` as undefined. The
2010
+ # argument node is visible here; a local the class was assigned to is not, and still binds.
2011
+ def class_factory_argument?(call_node)
2012
+ arguments = call_node.respond_to?(:arguments) ? call_node.arguments&.arguments : nil
2013
+ return false if arguments.nil?
2014
+
2015
+ arguments.any? { |argument| class_factory_call?(argument) }
2016
+ end
2017
+
2018
+ CLASS_FACTORIES = { new: %w[Class Struct].freeze, define: %w[Data].freeze }.freeze
2019
+ private_constant :CLASS_FACTORIES
2020
+
2021
+ def class_factory_call?(node)
2022
+ return false unless node.is_a?(Prism::CallNode) && node.block.is_a?(Prism::BlockNode)
2023
+
2024
+ receivers = CLASS_FACTORIES[node.name]
2025
+ return false if receivers.nil?
2026
+
2027
+ receiver = node.receiver
2028
+ name = case receiver
2029
+ when Prism::ConstantReadNode then receiver.name.to_s
2030
+ when Prism::ConstantPathNode then receiver.parent.nil? ? receiver.name.to_s : nil
2031
+ end
2032
+ receivers.include?(name)
2033
+ end
2034
+
2035
+ def within_upper_bound?(method_type, name, singleton, environment)
2036
+ type_param = method_type.type_params.find { |tp| tp.name == name }
2037
+ bound = type_param.respond_to?(:upper_bound_type) ? type_param.upper_bound_type : type_param.upper_bound
2038
+ return true if bound.nil?
2039
+ return false unless bound.is_a?(RBS::Types::ClassInstance) && environment
2040
+
2041
+ %i[equal subclass].include?(environment.class_ordering(singleton.class_name, bound.name.to_s))
2042
+ end
2043
+
1632
2044
  def declared_type_param_names(method_type)
1633
2045
  params = method_type.respond_to?(:type_params) ? method_type.type_params : nil
1634
2046
  return EMPTY_TYPE_PARAM_NAMES if params.nil? || params.empty?
@@ -1704,7 +2116,7 @@ module Rigor
1704
2116
  # dress up an absence of evidence as an inference. The variable stays unbound and degrades as it
1705
2117
  # did before, which is the same value anyway.
1706
2118
  def no_static_evidence?(arg)
1707
- arg.is_a?(Type::Dynamic) && arg.static_facet.is_a?(Type::Top)
2119
+ Type::Combinator.untyped?(arg)
1708
2120
  end
1709
2121
 
1710
2122
  # Issue #303 — the FP guard on the argument-position binding.
@@ -1751,27 +2163,89 @@ module Rigor
1751
2163
  end
1752
2164
 
1753
2165
  def block_return_variable(method_type)
2166
+ return_type = block_return_type(method_type)
2167
+ return return_type if return_type.is_a?(RBS::Types::Variable)
2168
+
2169
+ falsey_armed_variable(return_type)&.first
2170
+ end
2171
+
2172
+ def block_return_type(method_type)
1754
2173
  block = method_type.respond_to?(:block) ? method_type.block : nil
1755
2174
  return nil if block.nil?
1756
2175
 
1757
2176
  fun = block.type
1758
- return nil unless fun.respond_to?(:return_type)
2177
+ fun.respond_to?(:return_type) ? fun.return_type : nil
2178
+ end
1759
2179
 
1760
- return_type = fun.return_type
1761
- return_type.is_a?(RBS::Types::Variable) ? return_type : nil
2180
+ # The falsey arms ({#falsey_armed_variable}) the block's declared return spells beside its variable;
2181
+ # empty for a bare variable.
2182
+ def block_return_falsey_arms(method_type)
2183
+ falsey_armed_variable(block_return_type(method_type))&.last || EMPTY_FALSEY_ARMS
2184
+ end
2185
+
2186
+ EMPTY_FALSEY_ARMS = [].freeze
2187
+ private_constant :EMPTY_FALSEY_ARMS
2188
+
2189
+ # A block return that is one type variable beside `nil` and/or `false` and nothing else —
2190
+ # `Enumerable#filter_map`'s `(nil | false | U)`, or `U?` — as `[variable, arms]`, the arms a subset
2191
+ # of `[:nil, :false]`. Anything else, `bool` among them, is nil: a block return must only lose
2192
+ # values the method itself discards, and a `false`-only arm has no carrier that removes `false`
2193
+ # alone from a `Dynamic` block type, so it declines too.
2194
+ def falsey_armed_variable(return_type)
2195
+ members =
2196
+ case return_type
2197
+ when RBS::Types::Optional then [return_type.type, RBS::Types::Bases::Nil.new(location: nil)]
2198
+ when RBS::Types::Union then return_type.types
2199
+ else return nil
2200
+ end
2201
+ variables, arms = members.partition { |member| member.is_a?(RBS::Types::Variable) }
2202
+ return nil unless variables.size == 1
2203
+
2204
+ arm_names = arms.map { |arm| falsey_arm_name(arm) }
2205
+ return nil if arm_names.include?(nil) || !arm_names.include?(:nil)
2206
+
2207
+ [variables.first, arm_names.uniq]
2208
+ end
2209
+
2210
+ def falsey_arm_name(arm)
2211
+ case arm
2212
+ when RBS::Types::Bases::Nil then :nil
2213
+ when RBS::Types::Literal then arm.literal == false ? :false : nil # rubocop:disable Lint/BooleanSymbol
2214
+ end
2215
+ end
2216
+
2217
+ # The block's type less the falsey values the declared return spells beside the variable: its
2218
+ # truthy fragment for `nil | false`, its non-nil fragment for `nil` alone. A `Dynamic` block type
2219
+ # passes through unchanged, so an untyped block still binds `Dynamic[top]`. A block that answers
2220
+ # only the stripped values binds `bot`, so `filter_map { nil }` is `Array[bot]`: an Array that
2221
+ # holds no element.
2222
+ # Whether the receiver class's Ruby source defines the dispatched method, the probe
2223
+ # {#arg_binding_permitted?} also reads. Without a scope there is no discovery to consult.
2224
+ def project_redefined?(scope, call_site)
2225
+ return false if scope.nil?
2226
+
2227
+ class_name, method_name, kind = call_site
2228
+ return false if method_name.nil?
2229
+
2230
+ scope.discovered_method?(class_name, method_name, kind)
2231
+ end
2232
+
2233
+ def strip_falsey_arms(block_type, arms)
2234
+ return block_type if arms.empty?
2235
+ return Narrowing.narrow_truthy(block_type) if arms.include?(:false) # rubocop:disable Lint/BooleanSymbol
2236
+
2237
+ Narrowing.narrow_non_nil(block_type)
1762
2238
  end
1763
2239
 
1764
2240
  # ----- block parameter probe (Phase C sub-phase 1) -----
1765
2241
 
1766
2242
  def probe_block_param_types(receiver:, method_name:, args:, environment:, scope: nil, call_node: nil)
1767
2243
  args ||= []
1768
- keywords_last = ArgumentNodes.keyword_hash_last?(call_node, args)
1769
- splat = splat_indices(call_node, args)
1770
2244
  case receiver
1771
2245
  when Type::Union
1772
- probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
2246
+ probe_block_param_types_union(receiver, method_name, args, environment, scope, call_node)
1773
2247
  else
1774
- probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
2248
+ probe_block_param_types_one(receiver, method_name, args, environment, scope, call_node)
1775
2249
  end
1776
2250
  end
1777
2251
 
@@ -1779,9 +2253,9 @@ module Rigor
1779
2253
  # member resolves the same arity and types (otherwise the call sites would have to thread
1780
2254
  # per-member binders, which the slice does not support yet). Mismatches degrade to the empty
1781
2255
  # array so the binder defaults all params to Dynamic[Top].
1782
- def probe_block_param_types_union(receiver, method_name, args, environment, scope, keywords_last, splat)
2256
+ def probe_block_param_types_union(receiver, method_name, args, environment, scope, call_node)
1783
2257
  results = receiver.members.map do |member|
1784
- probe_block_param_types_one(member, method_name, args, environment, scope, keywords_last, splat)
2258
+ probe_block_param_types_one(member, method_name, args, environment, scope, call_node)
1785
2259
  end
1786
2260
  return [] if results.empty?
1787
2261
  return [] unless results.all? { |r| r == results.first }
@@ -1789,44 +2263,52 @@ module Rigor
1789
2263
  results.first
1790
2264
  end
1791
2265
 
1792
- def probe_block_param_types_one(receiver, method_name, args, environment, scope, keywords_last, splat)
2266
+ def probe_block_param_types_one(receiver, method_name, args, environment, scope, call_node)
1793
2267
  receiver = ArithmeticSequenceElements.element_receiver(receiver, method_name, environment, block: true) ||
1794
2268
  receiver
1795
2269
  descriptor = receiver_descriptor(receiver)
1796
2270
  return [] unless descriptor
1797
2271
 
1798
2272
  class_name, kind, receiver_args = descriptor
1799
- method_definition = lookup_method(environment, class_name, kind, method_name, scope)
2273
+ method_definition = probe_definition(environment, class_name, kind, method_name, args, scope, call_node)
1800
2274
  return [] unless method_definition
1801
2275
 
1802
- type_vars = build_type_vars(environment, class_name, receiver_args)
1803
- extract_block_param_types(
2276
+ type_vars =
2277
+ if kind == :interface
2278
+ interface_type_vars(environment, class_name, receiver_args)
2279
+ else
2280
+ build_type_vars(environment, class_name, receiver_args)
2281
+ end
2282
+ params = extract_block_param_types(
1804
2283
  method_definition,
1805
- class_name: class_name,
1806
- kind: kind,
1807
- args: args,
1808
- type_vars: type_vars,
1809
- environment: environment,
1810
- receiver: receiver,
1811
- receiver_args: receiver_args,
1812
- method_name: method_name,
1813
- keywords_last: keywords_last, positional_splat: splat
2284
+ class_name: class_name, kind: kind, args: args, type_vars: type_vars, environment: environment,
2285
+ receiver: receiver, receiver_args: receiver_args, method_name: method_name,
2286
+ keywords_last: ArgumentNodes.keyword_hash_last?(call_node, args),
2287
+ positional_splat: splat_indices(call_node, args), scope: scope, call_node: call_node
1814
2288
  )
2289
+ ClassKeyedBlockParams.rebind(method_definition, method_name, args, params)
1815
2290
  rescue StandardError
1816
2291
  []
1817
2292
  end
1818
2293
 
2294
+ # The declaration the block-parameter probe reads. On an interface value a block literal binds its parameters
2295
+ # through the overloads that take the call's shape, as the call's return does (ADR-130 WD3,
2296
+ # {#dispatch_interface}).
2297
+ def probe_definition(environment, class_name, kind, method_name, args, scope, call_node)
2298
+ return lookup_method(environment, class_name, kind, method_name, scope) unless kind == :interface
2299
+
2300
+ declared = interface_method(environment, class_name, method_name)
2301
+ declared && InterfaceShape.accepting(declared, args, block: true, call_node: call_node)
2302
+ end
2303
+
1819
2304
  # rubocop:disable Metrics/ParameterLists
1820
2305
  def extract_block_param_types(method_definition, class_name:, kind:, args:, type_vars:,
1821
2306
  environment: nil, receiver: nil, receiver_args: [],
1822
- method_name: nil, keywords_last: false, positional_splat: nil)
2307
+ method_name: nil, keywords_last: false, positional_splat: nil,
2308
+ scope: nil, call_node: nil)
1823
2309
  # rubocop:enable Metrics/ParameterLists
1824
2310
  instance_type = Type::Combinator.nominal_of(class_name)
1825
- self_type =
1826
- case kind
1827
- when :singleton then Type::Combinator.singleton_of(class_name)
1828
- else instance_type
1829
- end
2311
+ self_type = block_self_type(kind, class_name, instance_type, receiver)
1830
2312
 
1831
2313
  # Issue #1130 — a block parameter that receives `self` (`Object#tap` yields it) must see the
1832
2314
  # receiver's type arguments through the SAME {SelfSubstitute} verdict the return path applies
@@ -1862,13 +2344,59 @@ module Rigor
1862
2344
  instance_type: instance_type,
1863
2345
  type_vars: type_vars,
1864
2346
  block_required: true,
1865
- environment: environment
2347
+ environment: environment,
2348
+ # A call no overload matches binds no block parameter from the return path's block fallback (#2429).
2349
+ block_fallback: false
1866
2350
  )
1867
2351
  return [] unless method_type
1868
2352
 
2353
+ type_vars = compose_singleton_arg_type_vars(method_type, type_vars, args, environment,
2354
+ scope: scope, call_node: call_node,
2355
+ call_site: [class_name, method_name, kind])
1869
2356
  block_params_of(method_type, self_type, instance_type, type_vars, environment) || []
1870
2357
  end
1871
2358
 
2359
+ # Issue #2828 — the block-parameter twin of {#compose_arg_type_vars}, for the `singleton(T)` shape alone
2360
+ # ({#singleton_param_binding}): `Resolv::DNS#each_resource: [T < Resource] (dns_name, singleton(T))
2361
+ # { (T) -> void } -> void` yields the typeclass's instances. A bare `T` parameter stays out of the block
2362
+ # path: it binds the argument's own (possibly value-pinned) type, and `each_with_object([])`'s memo
2363
+ # would read as the empty tuple the block then appends to.
2364
+ def compose_singleton_arg_type_vars(method_type, type_vars, args, environment, scope:, call_node:, call_site:)
2365
+ declared = declared_type_param_names(method_type)
2366
+ return type_vars if declared.empty? || args.empty?
2367
+
2368
+ fun = method_type.type
2369
+ return type_vars unless fun.respond_to?(:required_positionals)
2370
+
2371
+ bindings = singleton_bindings(method_type, fun, args, declared, type_vars, environment)
2372
+ return type_vars if bindings.empty? || class_factory_argument?(call_node)
2373
+ return type_vars unless arg_binding_permitted?(scope, call_node, call_site)
2374
+
2375
+ type_vars.merge(bindings)
2376
+ end
2377
+
2378
+ def singleton_bindings(method_type, fun, args, declared, type_vars, environment)
2379
+ positionals = fun.required_positionals + fun.optional_positionals
2380
+ positionals.zip(args).each_with_object({}) do |(param, arg), bindings|
2381
+ next if arg.nil?
2382
+
2383
+ name, bound = singleton_param_binding(method_type, param, arg, declared, type_vars, environment)
2384
+ next if name.nil?
2385
+
2386
+ bindings[name] = bindings.key?(name) ? Type::Combinator.union(bindings[name], bound) : bound
2387
+ end
2388
+ end
2389
+
2390
+ # What RBS `self` reads in a block parameter of the member: the class's instance or singleton, and inside an
2391
+ # interface member the receiver (ADR-130 WD1).
2392
+ def block_self_type(kind, class_name, instance_type, receiver)
2393
+ case kind
2394
+ when :singleton then Type::Combinator.singleton_of(class_name)
2395
+ when :interface then receiver
2396
+ else instance_type
2397
+ end
2398
+ end
2399
+
1872
2400
  # The block parameters every overload in `matches` declares alike, or no information (`[]`).
1873
2401
  def agreed_block_params(matches, self_type, instance_type, type_vars, environment)
1874
2402
  answers = matches.uniq.map do |method_type|
@@ -1940,7 +2468,7 @@ module Rigor
1940
2468
  # rubocop:enable Metrics/ParameterLists
1941
2469
  unless FacetDistribution.faceted?(arg_types)
1942
2470
  return OverloadSelector.select_declared(method_definition, arg_types, selector_self, instance_type,
1943
- type_vars, block_required, environment, true, true, splats)
2471
+ type_vars, block_required, environment, true, true, splats, false)
1944
2472
  end
1945
2473
 
1946
2474
  facet_matches(method_definition, arg_types, selector_self, instance_type, type_vars, block_required,
@@ -1960,7 +2488,7 @@ module Rigor
1960
2488
  # The fallback list (`member` false) answers every gradual match rather than the strict pass's pick.
1961
2489
  OverloadSelector.select_declared(method_definition, list, selector_self, instance_type, type_vars,
1962
2490
  block_required, environment, member || :gradual, keywords_last,
1963
- splats)
2491
+ splats, false)
1964
2492
  end
1965
2493
  end
1966
2494