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
@@ -54,6 +54,114 @@ module Rigor
54
54
  end
55
55
  end
56
56
 
57
+ # Issue #2681 — which declared type, if any, one RBS overload hands a `def`'s parameter slot when the def's shape
58
+ # need not match the declaration's (an override bound from its ancestor, or a source `def` whose RBS was written
59
+ # for another shape). A slot takes a type only where the two shapes line up:
60
+ #
61
+ # - a leading positional (required or optional) takes the declared leading positional at the same ABSOLUTE
62
+ # position, and a post positional the declared trailing one, provided both sides have as many post as
63
+ # trailing positionals: Ruby fills the leading parameters left to right on both sides, so the argument a call
64
+ # puts at a leading position reaches the slot at the same one;
65
+ # - `*rest` takes the declared rest only when the def has exactly the declared leading positionals;
66
+ # - a keyword takes the declared keyword of its name, and `**kw` the declared keyword rest only when the def
67
+ # names every declared keyword.
68
+ #
69
+ # Any other overlap is {MISALIGNED}, and leaves the slot `Dynamic[Top]`. {NOT_PASSED} is an overload that passes
70
+ # the slot nothing (`def first(n)` under `Array#first`'s `()`), which the binder skips.
71
+ module SlotAlignment
72
+ module_function
73
+
74
+ MISALIGNED = :misaligned
75
+ NOT_PASSED = :not_passed
76
+
77
+ # `accepts_keywords` is false for a def with no keyword parameter and no `**`, `**nil` or `...`: Ruby passes a
78
+ # call's keywords to such a def as a Hash in one more positional argument.
79
+ Shape = Data.define(:requireds, :leading, :posts, :keywords, :accepts_keywords)
80
+
81
+ def shape_of(params_node)
82
+ return Shape.new(0, 0, 0, [].freeze, false) if params_node.nil?
83
+
84
+ requireds = params_node.requireds.size
85
+ keywords = params_node.keywords.map(&:name).freeze
86
+ accepts_keywords = !keywords.empty? || !params_node.keyword_rest.nil?
87
+ Shape.new(requireds, requireds + params_node.optionals.size, params_node.posts.size, keywords,
88
+ accepts_keywords)
89
+ end
90
+
91
+ # The RBS type the overload `function` declares for `slot`, {MISALIGNED} or {NOT_PASSED}.
92
+ def declared(function, slot, shape)
93
+ return MISALIGNED if keyword_hash_reaches?(function, slot, shape)
94
+
95
+ case slot.kind
96
+ when :required_positional then leading(function, shape, slot.index)
97
+ when :optional_positional then leading(function, shape, shape.requireds + slot.index)
98
+ when :trailing_positional then trailing(function, shape, slot.index)
99
+ when :rest_positional then rest(function, shape)
100
+ when :required_keyword, :optional_keyword then keyword(function, slot.name)
101
+ when :rest_keyword then rest_keyword(function, shape)
102
+ else NOT_PASSED
103
+ end
104
+ end
105
+
106
+ # Whether the overload's keywords may reach `slot` as a positional Hash: the def accepts no keywords, so a
107
+ # call's keywords arrive as one more positional argument, which lands past the declared required leading
108
+ # positionals, in the `*rest`, or (shifting every post positional) in a post positional.
109
+ def keyword_hash_reaches?(function, slot, shape)
110
+ return false if shape.accepts_keywords
111
+ return false if function.required_keywords.empty? && function.optional_keywords.empty? &&
112
+ function.rest_keywords.nil?
113
+
114
+ case slot.kind
115
+ when :required_positional then slot.index >= function.required_positionals.size
116
+ when :optional_positional then shape.requireds + slot.index >= function.required_positionals.size
117
+ when :rest_positional, :trailing_positional then true
118
+ else false
119
+ end
120
+ end
121
+
122
+ def leading(function, shape, position)
123
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
124
+
125
+ required = function.required_positionals
126
+ return required[position].type if position < required.size
127
+
128
+ optional = function.optional_positionals[position - required.size]
129
+ return optional.type if optional
130
+
131
+ function.rest_positionals ? MISALIGNED : NOT_PASSED
132
+ end
133
+
134
+ def trailing(function, shape, index)
135
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
136
+
137
+ function.trailing_positionals[index].type
138
+ end
139
+
140
+ def rest(function, shape)
141
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
142
+
143
+ declared_leading = function.required_positionals.size + function.optional_positionals.size
144
+ return MISALIGNED if shape.leading < declared_leading
145
+ return NOT_PASSED if function.rest_positionals.nil?
146
+
147
+ shape.leading == declared_leading ? function.rest_positionals.type : MISALIGNED
148
+ end
149
+
150
+ def keyword(function, name)
151
+ declared = function.required_keywords[name] || function.optional_keywords[name]
152
+ return declared.type if declared
153
+
154
+ function.rest_keywords ? MISALIGNED : NOT_PASSED
155
+ end
156
+
157
+ def rest_keyword(function, shape)
158
+ declared = function.required_keywords.keys + function.optional_keywords.keys
159
+ return MISALIGNED unless declared.all? { |name| shape.keywords.include?(name) }
160
+
161
+ function.rest_keywords ? function.rest_keywords.type : NOT_PASSED
162
+ end
163
+ end
164
+
57
165
  class MethodParameterBinder
58
166
  # @param class_path — the qualified name of the class the method is defined in
59
167
  # (e.g., `"Foo::Bar"`), or `nil` for a top-level `def` outside any class. When `nil` (or
@@ -64,11 +172,15 @@ module Rigor
64
172
  # @param source_path — the project-relative path of the file the method is
65
173
  # defined in. Used to match ADR-28 path-scoped protocol contracts; `nil` (the default
66
174
  # for synthetic / probe scopes) disables the contract tier.
67
- def initialize(environment:, class_path:, singleton:, source_path: nil)
175
+ # @param scope — the scope the `def` is written in. When supplied, a `def` on a Ruby-source class with no RBS
176
+ # of its own binds the parameters of the declaration it overrides on an ADR-43 allow-listed ancestor (#2681);
177
+ # `nil` keeps that class's parameters `Dynamic[Top]`.
178
+ def initialize(environment:, class_path:, singleton:, source_path: nil, scope: nil)
68
179
  @environment = environment
69
180
  @class_path = class_path
70
181
  @singleton = singleton
71
182
  @source_path = source_path
183
+ @scope = scope
72
184
  end
73
185
 
74
186
  # @return ordered map from parameter name to bound type.
@@ -79,7 +191,9 @@ module Rigor
79
191
 
80
192
  rbs_method = lookup_rbs_method(def_node)
81
193
  if rbs_method
82
- apply_rbs_overloads(types, slots, rbs_method.method_types) unless rbs_method.method_types.empty?
194
+ unless rbs_method.method_types.empty?
195
+ apply_rbs_overloads(types, slots, rbs_method.method_types, SlotAlignment.shape_of(def_node.parameters))
196
+ end
83
197
  # `rigor:v1:param: <name> <refinement>` annotations tighten the bound type for matching
84
198
  # slots. Applied after the RBS-overload pass so the override is the authoritative
85
199
  # answer regardless of what the RBS signature declared.
@@ -186,15 +300,17 @@ module Rigor
186
300
  # `def self.foo` always means a singleton method on the immediate enclosing class. `def
187
301
  # foo` inside `class << self` is also a singleton method (the StatementEvaluator threads
188
302
  # the `singleton:` flag through this case).
189
- if def_node.receiver.is_a?(Prism::SelfNode) || @singleton
190
- Rigor::Reflection.singleton_method_definition(@class_path, method_name, environment: @environment)
191
- else
192
- Rigor::Reflection.instance_method_definition(@class_path, method_name, environment: @environment)
193
- end
303
+ # ADR-43 (#2681) — a source-only subclass of an allow-listed RBS-complete ancestor (`Rigor::Plugin::Base`)
304
+ # overriding one of its hooks binds the hook's declared parameters, as an empty RBS declaration of the
305
+ # subclass would through RBS's own inheritance.
306
+ kind = def_node.receiver.is_a?(Prism::SelfNode) || @singleton ? :singleton : :instance
307
+ MethodDispatcher::RbsDispatch.def_declaration(@environment, @class_path, kind, method_name, @scope)
194
308
  end
195
309
 
196
310
  # Bind each parameter slot to the union of the matching parameter types across every
197
- # overload that *has* that slot. Overloads that omit the slot (e.g., `Array#first` has
311
+ # overload that *has* that slot, where the def's parameter shape lines up with the overload's
312
+ # ({SlotAlignment}); one overload that does not line up leaves the slot `Dynamic[Top]`.
313
+ # Overloads that pass the slot no argument (e.g., `Array#first` has
198
314
  # both `()` and `(int)` overloads — only the second matches a `def first(n)` redefinition)
199
315
  # are silently skipped, so the binder defaults to the most informative type the RBS
200
316
  # signature provides without having to know which overload the runtime will pick.
@@ -203,14 +319,14 @@ module Rigor
203
319
  # accepts any argument in every slot, so no slot can be narrowed below `Dynamic[Top]`: binding a typed
204
320
  # sibling overload's parameter type would read a value that overload legitimately passes as a
205
321
  # contradiction inside the body. Every slot keeps its default; the declared return type still applies.
206
- def apply_rbs_overloads(types, slots, method_types)
322
+ def apply_rbs_overloads(types, slots, method_types, shape)
207
323
  return unless method_types.all? { |mt| mt.type.respond_to?(:required_positionals) }
208
324
 
209
325
  slots.each do |slot|
210
326
  next if slot.name.nil?
211
327
 
212
- translated = collect_translated_types(method_types, slot)
213
- next if translated.empty?
328
+ translated = collect_translated_types(method_types, slot, shape)
329
+ next if translated.nil? || translated.empty?
214
330
 
215
331
  types[slot.name] = build_slot_type(translated, slot.kind)
216
332
  end
@@ -272,10 +388,15 @@ module Rigor
272
388
  slots.find { |slot| POSITIONAL_KINDS.include?(slot.kind) && slot.index == index }
273
389
  end
274
390
 
275
- def collect_translated_types(method_types, slot)
276
- rbs_types = method_types.flat_map do |mt|
277
- t = rbs_type_for_slot(mt.type, slot)
278
- t ? [t] : []
391
+ # The translated types the overloads declare for `slot`, or nil when one of them does not line up with the
392
+ # def's parameter shape ({SlotAlignment}): that overload may hand the slot an argument of another type.
393
+ def collect_translated_types(method_types, slot, shape)
394
+ rbs_types = []
395
+ method_types.each do |mt|
396
+ declared = SlotAlignment.declared(mt.type, slot, shape)
397
+ return nil if declared.equal?(SlotAlignment::MISALIGNED)
398
+
399
+ rbs_types << declared unless declared.equal?(SlotAlignment::NOT_PASSED)
279
400
  end
280
401
  rbs_types.map { |t| translate_with_self(t) }.uniq
281
402
  end
@@ -285,47 +406,6 @@ module Rigor
285
406
  wrap_for_kind(bound, kind)
286
407
  end
287
408
 
288
- # Dispatch table from slot kind to a small lambda that pulls the matching RBS parameter
289
- # type out of an `RBS::Types::Function`. The hash keeps `rbs_type_for_slot` linear (one
290
- # lookup, one call) so the cyclomatic-complexity budget does not balloon as future slices
291
- # add more parameter kinds (e.g., `**Symbol kw` is a candidate for a stricter route in
292
- # Slice 5+).
293
- # Match keyword parameters by name across both required and optional keyword maps. RBS may
294
- # declare a keyword as optional (`?by:`) while the Ruby `def` lists it as required (or
295
- # vice versa); the binding is by-name regardless of which side defines it.
296
- KEYWORD_PROVIDER = Ractor.make_shareable(lambda do |fn, slot|
297
- fn.required_keywords[slot.name]&.type || fn.optional_keywords[slot.name]&.type
298
- end)
299
- private_constant :KEYWORD_PROVIDER
300
-
301
- RBS_TYPE_PROVIDERS = Ractor.make_shareable({
302
- required_positional: Ractor.make_shareable(lambda { |fn, slot|
303
- fn.required_positionals[slot.index]&.type
304
- }),
305
- optional_positional: Ractor.make_shareable(lambda { |fn, slot|
306
- fn.optional_positionals[slot.index]&.type
307
- }),
308
- rest_positional: Ractor.make_shareable(lambda { |fn, _slot|
309
- fn.rest_positionals&.type
310
- }),
311
- trailing_positional: Ractor.make_shareable(lambda { |fn, slot|
312
- fn.trailing_positionals[slot.index]&.type
313
- }),
314
- required_keyword: KEYWORD_PROVIDER,
315
- optional_keyword: KEYWORD_PROVIDER,
316
- rest_keyword: Ractor.make_shareable(lambda { |fn, _slot|
317
- fn.rest_keywords&.type
318
- })
319
- })
320
- private_constant :RBS_TYPE_PROVIDERS
321
-
322
- def rbs_type_for_slot(function, slot)
323
- provider = RBS_TYPE_PROVIDERS[slot.kind]
324
- return nil unless provider
325
-
326
- provider.call(function, slot)
327
- end
328
-
329
409
  # The variable bound to a `*rest` parameter is the *Array* of rest-positional arguments,
330
410
  # not a single element. Likewise `**kw` is bound to a `Hash[Symbol, V]`. Wrap the
331
411
  # translated element/value type accordingly so `rest` reads as `Array[Integer]` rather
@@ -41,8 +41,8 @@ module Rigor
41
41
  any = arguments.empty?
42
42
  arguments.each do |arg|
43
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]
44
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
45
+ *ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
46
46
  elsif (name = Source::Literals.symbol_or_string(arg))
47
47
  ranges << [*call_span, name, :singleton, owner]
48
48
  else
@@ -54,6 +54,17 @@ module Rigor
54
54
  module ModuleFunctionState
55
55
  module_function
56
56
 
57
+ # The `[kind, owner]` of a deferred-range row for a `def` passed to a `module_function` spelling: `:singleton`
58
+ # for a singleton def, `:both` for an instance one, and `[nil, nil]` for a def on another object's singleton
59
+ # (#3155), which claims no owner or side.
60
+ def def_row_side(def_node, qualified_prefix, in_singleton_class, owner)
61
+ case ScopeIndexer.def_side(def_node, qualified_prefix, in_singleton_class)
62
+ when :singleton then [:singleton, owner]
63
+ when :instance then [:both, owner]
64
+ else [nil, nil]
65
+ end
66
+ end
67
+
57
68
  # A receiverless `module_function` call, bare or named.
58
69
  def call?(node)
59
70
  node.name == :module_function && node.receiver.nil?
@@ -76,6 +87,17 @@ module Rigor
76
87
  (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
77
88
  end
78
89
 
90
+ # The `def` nodes a receiverless `public`, `private` or `protected` call takes as arguments (`private def x`,
91
+ # #2254). Ruby evaluates each one before the call, so it runs whenever the call's statement does. A named call
92
+ # with a symbol takes no def, and a call with a receiver is not read. A `module_function def` is not read here:
93
+ # its singleton copy is not modelled, so that gap stays as it was.
94
+ def visibility_call_defs(node)
95
+ return [] unless node.is_a?(Prism::CallNode) && node.receiver.nil?
96
+ return [] unless %i[public private protected].include?(node.name)
97
+
98
+ node.arguments&.arguments&.grep(Prism::DefNode) || []
99
+ end
100
+
79
101
  # The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
80
102
  # is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
81
103
  # and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
@@ -342,7 +364,6 @@ module Rigor
342
364
  true
343
365
  end
344
366
 
345
- # The prescan's call arm.
346
367
  def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
347
368
  if bare?(node)
348
369
  offsets << [node.location.start_offset, :on]
@@ -350,8 +371,8 @@ module Rigor
350
371
  owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
351
372
  node.arguments&.arguments&.each do |arg|
352
373
  if arg.is_a?(Prism::DefNode)
353
- kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
354
- ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
374
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
375
+ *ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
355
376
  elsif (name = symbol_argument_name(arg))
356
377
  ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
357
378
  else
@@ -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`).
@@ -302,7 +302,7 @@ module Rigor
302
302
  # and a per-slot `T | nil` fires `call.possible-nil-receiver` on the guarded read.
303
303
  def join_member_bindings(types, soften_slots)
304
304
  untyped = Type::Combinator.untyped
305
- return [untyped, false] if types.any? { |type| type == untyped }
305
+ return [untyped, false] if types.any? { |type| Type::Combinator.untyped?(type) }
306
306
 
307
307
  firm = types.reject { |type| nil_literal?(type) }
308
308
  return [Type::Combinator.union(*types), false] if !soften_slots || firm.empty? || firm.size == types.size
@@ -31,7 +31,12 @@ module Rigor
31
31
  # `nil` when the re-join changed nothing, so a non-adder (`pop`, `sort!`) on an already-widened
32
32
  # carrier does not rebind the binding at all: a rebind drops the per-slot narrowings `with_local`
33
33
  # keys to the old value, and paying that for an identical type is a straight loss.
34
- def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES)
34
+ def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES,
35
+ synthesised: false)
36
+ if synthesised && (synthesised_carrier?(nominal) || partially_gradual_hash?(nominal))
37
+ return widen_synthesised(nominal, method_name, values:, arg_types:)
38
+ end
39
+ return widen_precise_array(nominal, method_name, values:, arg_types:) if precise_array?(nominal)
35
40
  return nil unless regrowable_carrier?(nominal)
36
41
 
37
42
  pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
@@ -115,19 +120,250 @@ module Rigor
115
120
  # This is a superset of "mutation-widened": a DECLARED `Array[untyped]` or
116
121
  # `Array[Integer | untyped]` matches too. Deliberate — the soundness argument above is about
117
122
  # the carrier being open, not about who opened it, and a declared gradual arm is the author
118
- # saying the collection may hold anything. Distinguishing the two needs provenance travelling
119
- # WITH the value (a field on `Type::Nominal`, reopening structural equality, `Marshal`, and the
120
- # cache schema); nothing on this axis needs it yet.
123
+ # saying the collection may hold anything.
124
+ #
125
+ # Issue #2555 L3 — the provenance this boundary turns on travels beside the binding, not with the value: a
126
+ # local whose object the engine built carries the `:synthesised` construction fact ({Scope#construction_fact?}),
127
+ # and {#widen_synthesised} answers for it before this predicate is asked. A field on `Type::Nominal` was the
128
+ # other route; `Combinator.union`'s structural dedupe would keep whichever tagged twin it met first.
121
129
  def regrowable_carrier?(nominal)
122
130
  return false unless %w[Array Hash].include?(nominal.class_name)
123
131
 
124
132
  nominal.type_args.any? { |arg| gradual_parameter?(arg) }
125
133
  end
126
134
 
135
+ # Issue #2555 L3 — a PRECISE `Array` / `Hash` nominal (no parameter carries a `Dynamic` arm), the carrier
136
+ # {#widen_synthesised} re-grows when its binding carries the `:synthesised` fact. A carrier that already carries
137
+ # a gradual arm keeps the ordinary re-join, whose floor it holds already.
138
+ def synthesised_carrier?(nominal)
139
+ %w[Array Hash].include?(nominal.class_name) && !nominal.type_args.empty? &&
140
+ nominal.type_args.none? { |arg| gradual_parameter?(arg) }
141
+ end
142
+
143
+ # Issue #2555 L3 — a `Hash` with one gradual parameter and one precise one: `Hash.new(0)` is
144
+ # `Hash[Dynamic[top], Integer]`. On a synthesised binding the precise side is this engine's own fold of the
145
+ # default, not a declaration, so {#keep_precise_parameters} must not restore it over a store:
146
+ # `h = Hash.new(0); h[:y] = "s"` reads `Hash[Dynamic[top] | Symbol, Integer | String]`, not
147
+ # `Hash[Dynamic[top] | Symbol, Integer]` (which made `h[:y].upcase` an `undefined method` on Integer). The
148
+ # counter idiom keeps its `Integer`: `h[:x] += 1` stores an `Integer`.
149
+ def partially_gradual_hash?(nominal)
150
+ nominal.class_name == "Hash" && nominal.type_args.size == 2 &&
151
+ nominal.type_args.any? { |arg| gradual_parameter?(arg) } &&
152
+ nominal.type_args.any? { |arg| !gradual_parameter?(arg) }
153
+ end
154
+
155
+ # Issue #2555 L3 — the re-join for a precise carrier the engine built itself: a collection literal closed by a
156
+ # value-preserving mutator (`a = [1, 2]; a.sort!` is `Array[1 | 2]`, `g = { a: 1 }; g.delete(:b)` is
157
+ # `Hash[Symbol, 1]`), a constructor fold (`Array.new(n, 0)` is `Array[Integer]`), or a literal built as a
158
+ # nominal outright (`{ ENV["A"].to_s => 1 }`). Its element types are this engine's inference, not a claim a
159
+ # declaration made, so the #561 boundary ({#regrowable_carrier?}, {#precise_admitted}) does not apply:
160
+ #
161
+ # - an added member joins as itself (value pin and literal shape dropped, {MutationWidening.value_pin_widened}),
162
+ # with no seed-admissibility gate and no `Dynamic[top]` floor — `Array.new(n, 0) << "s"` reads
163
+ # `Array[Integer | String]`;
164
+ # - a rewriter takes {RewriteMutation.arm}'s gradual position, as a gradual carrier does —
165
+ # `g.transform_values!(&:to_s)` reads `Hash[Symbol, Dynamic[top]]`;
166
+ # - an adder whose arguments yield no evidence is one unknown store and joins `Dynamic[top]`; one called with no
167
+ # argument types at all (the block-capture seam, whose content join adds the stores afterwards) changes
168
+ # nothing.
169
+ #
170
+ # The floor the ordinary re-join adds exists because a carrier it closed could not re-grow; this one stays
171
+ # re-growable while the binding keeps the fact, so every later store made THROUGH THE BINDING is seen. A store
172
+ # through another holder is not, which is why a local that may escape anywhere in its body never carries the
173
+ # fact ({ConstructionEscape}). Nor is a remover taking a store out again, which is why a remover on a carrier a
174
+ # store has grown restores the floor ({MutationWidening.floor_grown_carrier}).
175
+ def widen_synthesised(nominal, method_name, values:, arg_types:)
176
+ pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
177
+ rejoined =
178
+ case nominal.class_name
179
+ when "Array"
180
+ return nil unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
181
+
182
+ synthesised_array_join(RewriteMutation.arm(pre_state, method_name), method_name, arg_types)
183
+ when "Hash"
184
+ return nil unless MutationWidening::HASH_MUTATORS.include?(method_name)
185
+
186
+ synthesised_hash_join(RewriteMutation.arm(pre_state, method_name), method_name, arg_types)
187
+ end
188
+ rejoined == nominal ? nil : rejoined
189
+ end
190
+
191
+ # Issue #2555 — the removers and filters of an Array, a Hash or a Set: each can take a stored member out again,
192
+ # which nothing narrows the carrier back from.
193
+ SYNTHESISED_REMOVERS = %i[
194
+ pop shift slice! delete delete? delete_at delete_if reject! select! filter! keep_if compact! clear subtract
195
+ ].to_set.freeze
196
+
197
+ # The adders whose store marks a synthesised binding `:grown`.
198
+ def content_adder?(method_name)
199
+ ContentJoin::CONTENT_ADDERS.include?(method_name) || SetMutation::CONTENT_ADDERS.include?(method_name)
200
+ end
201
+
202
+ # Whether a store turned `current` into `widened` by changing an Array / Hash / Set parameter that carried no
203
+ # `Dynamic` arm before it: a member joined where no floor covered it, which a later remover could leave stale.
204
+ # A store the parameter already held, or one that joined beside an earlier floor (`Hash.new(0)`'s key side),
205
+ # grows nothing.
206
+ def grew_unfloored?(current, widened)
207
+ return false if widened.nil?
208
+
209
+ before = ContentJoin.union_members(current)
210
+ ContentJoin.union_members(widened).any? do |member|
211
+ next false unless member.is_a?(Type::Nominal) && %w[Array Hash Set].include?(member.class_name)
212
+
213
+ prior = before.find { |b| same_carrier?(b, member) }
214
+ member.type_args.each_with_index.any? do |arg, i|
215
+ prior.nil? || (!gradual_parameter?(prior.type_args[i]) && prior.type_args[i] != arg)
216
+ end
217
+ end
218
+ end
219
+
220
+ def same_carrier?(type, nominal)
221
+ type.is_a?(Type::Nominal) && type.class_name == nominal.class_name &&
222
+ type.type_args.size == nominal.type_args.size
223
+ end
224
+
225
+ # Whether `method_name` on the synthesised local `name` takes the floor: a remover on a carrier a store grew.
226
+ def floors_grown_carrier?(scope, name, method_name)
227
+ SYNTHESISED_REMOVERS.include?(method_name) && scope.construction_fact?(name, :grown)
228
+ end
229
+
230
+ # `type` with every Array / Hash / Set parameter joined with `Dynamic[top]`, member-wise through a union: the
231
+ # reading the carrier would have had without the fact. With `drop_nil` (a remover that can take a `nil` out,
232
+ # {MutationWidening.removes_nil?}) the parameter's `nil` member goes too: on the path without the fact a stored
233
+ # `nil` the seed did not accept joined as the floor alone ({#precise_admitted}), and a `nil` kept beside the floor
234
+ # would still draw `call.possible-nil-receiver` after `parts << nil; parts.compact!`. Any other remover keeps the
235
+ # `nil`, which it may leave behind (`s << nil; s.delete(1)`).
236
+ def floor_parameters(type, drop_nil: false)
237
+ case type
238
+ when Type::Union then Type::Combinator.union(*type.members.map { |member| floor_parameters(member, drop_nil:) })
239
+ when Type::Nominal
240
+ return type unless %w[Array Hash Set].include?(type.class_name) && !type.type_args.empty?
241
+
242
+ Type::Combinator.nominal_of(type.class_name,
243
+ type_args: type.type_args.map { |arg| floored_parameter(arg, drop_nil) })
244
+ else type
245
+ end
246
+ end
247
+
248
+ def hash_carrier?(type)
249
+ ContentJoin.union_members(type).any? { |member| member.is_a?(Type::Nominal) && member.class_name == "Hash" }
250
+ end
251
+
252
+ def floored_parameter(arg, drop_nil)
253
+ kept = ContentJoin.union_members(arg)
254
+ kept = kept.reject { |member| member.is_a?(Type::Constant) && member.value.nil? } if drop_nil
255
+ Type::Combinator.union(*kept, Type::Combinator.untyped)
256
+ end
257
+
258
+ def synthesised_array_join(carrier, method_name, arg_types)
259
+ return carrier unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
260
+
261
+ added = MutationWidening.value_pin_widened(ContentJoin.array_added_elements(method_name, arg_types))
262
+ ContentJoin.join_array_content(carrier, added.empty? ? [Type::Combinator.untyped] : added)
263
+ end
264
+
265
+ def synthesised_hash_join(carrier, method_name, arg_types)
266
+ return carrier unless ContentJoin::HASH_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
267
+
268
+ # A `nil` key marks a splat, an unknown key ({MutationWidening.join_added_pairs}).
269
+ key, value = MutationWidening.value_pin_widened([arg_types.first || Type::Combinator.untyped, arg_types.last])
270
+ value ||= Type::Combinator.untyped
271
+ ContentJoin.join_hash_content(carrier, [[key, value]])
272
+ end
273
+
127
274
  def gradual_parameter?(type)
128
275
  ContentJoin.union_members(type).any?(Type::Dynamic)
129
276
  end
130
277
 
278
+ # A typed `Array[E]` whose `E` has no `Dynamic` arm — the carrier {#regrowable_carrier?} refuses.
279
+ def precise_array?(nominal)
280
+ nominal.class_name == "Array" && nominal.type_args.size == 1 && !gradual_parameter?(nominal.type_args.first)
281
+ end
282
+
283
+ # Issue #2361 — a PRECISE `Array[E]` re-grows under a store, on the literal arms' terms: `a = ARGV.dup;
284
+ # a.clear; a << 1; a.each { |v| v.fdiv(2) }` read `Array[String]` and reported `undefined method 'fdiv' for
285
+ # String` on code that prints `[0.5]`.
286
+ #
287
+ # Only a content adder ({ContentJoin::ARRAY_CONTENT_ADDERS}) re-grows it, and the join keeps the seed: `clear`
288
+ # empties the array but leaves `E` standing, a superset of what it holds. A remover leaves the carrier as it
289
+ # found it. An element rewriter (`map!`, `collect!`, `flatten!`) replaces `E` (issue #2427): with the element
290
+ # the non-bang counterpart answers when the call site could type it ({RewriteMutation.typed}, answered before
291
+ # this method), and otherwise with {RewriteMutation.arm}'s gradual element — `a.map!.with_index { … }` holds
292
+ # whatever the block returns, and keeping the stale `E` reported a missing method on correct code.
293
+ #
294
+ # Each added member the element accepts joins as itself; any other takes the `Dynamic[top]` floor
295
+ # ({#precise_admitted}), and the floor joins every re-grow as it does on the literal arms. `a << 1` therefore
296
+ # reads `Array[Dynamic[top] | String]` and `s = ints.map { "x" }; s << "y"` reads `Array["x" | Dynamic[top]]`.
297
+ # Nothing narrows a carrier back when a remover or rewriter takes the store out again (`a << 1; a.pop`,
298
+ # `a << nil; a.compact!`, `a.map!(&:to_s)`), so a joined `Integer` or `nil` made `t.take(a)` against a declared
299
+ # `Array[String]` report `call.argument-type-mismatch`, and a parameter returned as its declared
300
+ # `-> Array[String]` report `def.return-type-mismatch`, on correct Ruby. A union carrying the floor neither folds
301
+ # nor dispatches loudly, so the read stays silent. Once floored, the carrier is gradual and later stores re-join
302
+ # through {#widen_nominal}'s ordinary arm.
303
+ #
304
+ # A store the element already accepts changes nothing ({#already_held?}). An adder with no argument types at
305
+ # all is left alone, as {MutationWidening.join_added_elements} leaves it: the block-capture seams pass none,
306
+ # and their own content join adds the stores.
307
+ def widen_precise_array(nominal, method_name, values:, arg_types:)
308
+ return RewriteMutation.arm(nominal, method_name) if RewriteMutation::TYPED_COUNTERPARTS.key?(method_name)
309
+ return nil unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name) && !arg_types.empty?
310
+
311
+ stored = ContentJoin.array_added_elements(method_name, arg_types)
312
+ return nil if already_held?(nominal.type_args.first, stored)
313
+
314
+ added = MutationWidening.value_pin_widened(stored)
315
+ admitted = precise_admitted(nominal.type_args.first, added)
316
+ pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
317
+ rejoined = ContentJoin.join_array_content(pre_state, MutationWidening.gradual_floor(admitted))
318
+ rejoined == nominal ? nil : rejoined
319
+ end
320
+
321
+ # The seed-admissibility gate for a precise element: an added member joins as itself only when `element`
322
+ # already accepts it, and takes the `Dynamic[top]` floor otherwise. A precise element therefore never gains a
323
+ # member it rejects, which is what a declared boundary checks: nothing narrows the carrier back when a remover
324
+ # or rewriter takes the store out again, and a joined foreign arm stays rejected beside the floor — `Integer`
325
+ # under a declared `Array[String]`, `String` under `-> Array["x"]`, `Array[Integer]` under
326
+ # `Array[Array[String]]` each reported on correct Ruby. A class set ({ContentJoin.admissible_evidence}) is too
327
+ # coarse for that: it admits `Array[Integer]` beside `[String]` and `"y"` beside `"x"`.
328
+ def precise_admitted(element, added)
329
+ added.flat_map do |type|
330
+ ContentJoin.union_members(type).map do |member|
331
+ Acceptance.accepts(element, member).yes? ? member : Type::Combinator.untyped
332
+ end
333
+ end
334
+ end
335
+
336
+ # True when the precise element `element` already covers every type `stored` puts in, so the store changes
337
+ # nothing: `s = ints.map { "x" }; s << "x"` keeps `Array["x"]` and its `s.last == "x"` fold, which is correct,
338
+ # and a `[Integer, Integer]` pushed onto an `Array[[0 | Integer, Integer]]` keeps the Tuple element. Joining
339
+ # the value-pin-widened, shape-erased store instead would only lose precision.
340
+ #
341
+ # A stored type carrying `Dynamic` anywhere is never covered, though `Acceptance` passes it gradually: keeping
342
+ # the element would claim the unknown part is what the element says (`retry_binding_accepted?`'s rule).
343
+ def already_held?(element, stored)
344
+ return false if stored.empty?
345
+
346
+ held = ContentJoin.union_members(element)
347
+ stored.all? do |type|
348
+ next false if type.nil? || carries_dynamic?(type)
349
+
350
+ ContentJoin.union_members(type).all? { |member| held.include?(member) } ||
351
+ Acceptance.accepts(element, type).yes?
352
+ end
353
+ end
354
+
355
+ def carries_dynamic?(type)
356
+ case type
357
+ when Type::Dynamic then true
358
+ when Type::Union, Type::Intersection then type.members.any? { |member| carries_dynamic?(member) }
359
+ when Type::Nominal then type.type_args.any? { |arg| carries_dynamic?(arg) }
360
+ when Type::Tuple then type.elements.any? { |element| carries_dynamic?(element) }
361
+ when Type::HashShape then type.pairs.each_value.any? { |value| carries_dynamic?(value) }
362
+ when Type::Difference, Type::Refined then carries_dynamic?(type.base)
363
+ else false
364
+ end
365
+ end
366
+
131
367
  # The value-rewriting counterpart of {MutationWidening.widen_tuple}'s `values: :widen`. A mutator that lands a
132
368
  # new value in an EXISTING slot falsifies that slot's pinning on a re-opened carrier exactly as
133
369
  # it does on a literal one, so `d = [:multi]; d << x; d[0] = "s"` must not keep reading `:multi`