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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  module Inference
5
7
  # The value the statement evaluator last gave a loop node, and the scope it evaluated the loop from, so
@@ -13,40 +15,90 @@ module Rigor
13
15
  # evaluation would. It is not compared with `Scope#==`, which ignores several fields (`match_frame`,
14
16
  # `declared_refinements`, `struct_fold_safe_locals`, the opaque-block flags, `lexical_nesting`, the origins tables,
15
17
  # `discovery`): across the cost spec's 10933 reads every match was an identity match, and `lib/` read no record at
16
- # all (issue #2262). It lives in a per-thread map whose keys are held weakly, so a record goes when its parse tree
17
- # does.
18
+ # all (issue #2262). It lives in a per-thread map keyed by the loop node. The map's keys are weak but its values
19
+ # are strong, and a recorded scope reaches its discovery, its environment and the parse tree that holds the key,
20
+ # so a record never goes on its own (issue #2412): the map is a run-scoped slot ({TypingState.release_run_scoped}),
21
+ # emptied when the run that filled it ends.
18
22
  module LoopValueRecord
19
- KEY = :rigor_loop_value_record
20
- private_constant :KEY
23
+ # ADR-129 WD1 — a Table, and a read-through cache of the computation's own value: a record read back answers
24
+ # what evaluating the loop again from the same scope would.
25
+ RECORD = TypingState.table(
26
+ :loop_value_record,
27
+ run_scoped: true, read_through: true, entries: TypingState::WeakKeyMapEntries,
28
+ reason: "keyed by loop node; a read-through cache of the loop's own evaluation"
29
+ )
30
+ private_constant :RECORD
21
31
 
22
- module_function
32
+ # ADR-129 WD3 — a Resource read only as a delta: {#loops_evaluated} feeds a before/after comparison taken within
33
+ # one conditional's evaluation, so with no loop evaluated in between the delta is zero.
34
+ LOOPS_EVALUATED = TypingState.resource(
35
+ :loops_evaluated,
36
+ read_exemption: :delta_internal, run_scoped: true,
37
+ reason: "a count of the loops this thread evaluated, incremented in place and read as a delta"
38
+ )
39
+ private_constant :LOOPS_EVALUATED
23
40
 
24
- COUNT_KEY = :rigor_loop_value_count
25
- private_constant :COUNT_KEY
41
+ # Issue #2767 — the ADR-55 fixpoint summaries are an ambient input of a loop's value: an ADR-55 fixpoint
42
+ # evaluates a recursive method's body several times from the same body scope object, each time under a wider
43
+ # assumption, so a value recorded in one iteration would answer an identity-matching read in the next one with
44
+ # the narrower iterate, and the fixpoint would converge on it (`[(while …; break(n.zero? ? 1 : q(n - 1)); end)]`
45
+ # typed `[bot]`). The generation moves whenever a summary's assumption is seeded or changed
46
+ # ({advance_generation}), and a record answers only a read in the generation it was written in. The advance in
47
+ # `fixpoint_step` is the one a witness needs (the spec above); the advances at the seed, the entangled degrade
48
+ # and the cap collapse are precautions, since each leaves no in-flight evaluation of that body scope behind.
49
+ #
50
+ # ADR-129 WD3 — a Resource whose value is fixed while the state id is: every advance is a {TypingState::State#rewrite}.
51
+ GENERATION = TypingState.resource(
52
+ :summary_generation,
53
+ read_exemption: :id_determined, run_scoped: true,
54
+ reason: "a count of the ADR-55 summary assumption changes on this thread; a record keys on it"
55
+ )
56
+ private_constant :GENERATION
57
+
58
+ module_function
26
59
 
27
60
  def record(node, scope, type)
28
- Thread.current[COUNT_KEY] = loops_evaluated + 1
61
+ TypingState.current.write(LOOPS_EVALUATED, loops_evaluated + 1)
29
62
  conditional(node, scope, type)
30
63
  end
31
64
 
65
+ # The current ADR-55 summary generation (#2767), which a record made now carries and a read must match.
66
+ def generation = TypingState.current[GENERATION] || 0
67
+
68
+ # A summary assumption was seeded or changed: no record written before answers a read from now on (#2767).
69
+ def advance_generation
70
+ state = TypingState.current
71
+ state.write(GENERATION, (state[GENERATION] || 0) + 1)
72
+ state.rewrite(GENERATION)
73
+ end
74
+
32
75
  # How many loops this thread has evaluated, so a conditional can tell whether its evaluation held one.
33
- def loops_evaluated = Thread.current[COUNT_KEY] || 0
76
+ def loops_evaluated = TypingState.current[LOOPS_EVALUATED] || 0
34
77
 
35
78
  # The same record for a conditional that evaluated a loop in a branch: `id(c ? (while …) : 0)` types the
36
79
  # conditional again from the scope it was evaluated from, and each typing evaluated the loop again from a
37
80
  # branch scope of its own, which multiplied the passes of every loop nested in it (issue #2262).
38
81
  def conditional(node, scope, type)
39
- (Thread.current[KEY] ||= ObjectSpace::WeakKeyMap.new)[node] = [scope, type]
82
+ record = [scope, type, generation]
83
+ state = TypingState.current
84
+ state.log_write(RECORD, node, record)
85
+ state.weak_key_map(RECORD)[node] = record
40
86
  type
41
87
  end
42
88
 
43
- # The recorded value of `node` evaluated from `scope` itself, or nil.
89
+ # Runs the evaluation a {fetch} that answered nil falls back to: the read-through Table's miss (ADR-129 WD1),
90
+ # which touches when it writes another Table while a capture is open.
91
+ def miss(&) = TypingState.current.memo_miss(RECORD, &)
92
+
93
+ # The recorded value of `node` evaluated from `scope` itself in the current summary generation, or nil.
44
94
  def fetch(node, scope)
45
- slot = Thread.current[KEY]&.[](node)
95
+ state = TypingState.current
96
+ slot = state[RECORD]&.[](node)
97
+ state.log_read(RECORD, node, slot || TypingState::ABSENT)
46
98
  return nil if slot.nil?
47
99
 
48
- recorded, type = slot
49
- recorded.equal?(scope) ? type : nil
100
+ recorded, type, recorded_generation = slot
101
+ recorded.equal?(scope) && recorded_generation == generation ? type : nil
50
102
  end
51
103
  end
52
104
  end
@@ -3,6 +3,7 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../../source/node_children"
6
+ require_relative "../../typing_state"
6
7
 
7
8
  module Rigor
8
9
  module Inference
@@ -15,6 +16,22 @@ module Rigor
15
16
  # `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
16
17
  # too, asked only while `$_` is narrowed.
17
18
  class Frame
19
+ # ADR-129 G4 — the frame's extensional caches. A scan is keyed on its node (and reading kind) and on the scope's
20
+ # local and instance-variable tables by identity, the inputs a lookup argument's answer reads; the rest of what
21
+ # it reads through the scope (constant resolution over the lexical nesting, discovery and environment) is fixed
22
+ # for the body the frame runs. The other three read the body and the parameters alone.
23
+ TypingState.memo_field(self, :@scans, :@kind_scans, :@self_call_fallback,
24
+ reason: "keyed on the node and the scope's locals and ivars tables by identity; the " \
25
+ "other scope inputs a scan reads are fixed for the frame's body")
26
+ TypingState.memo_field(self, :@forwarded_block, :@body_locals, :@parameter_names,
27
+ reason: "functions of the frame's body and parameters alone")
28
+
29
+ # The body the frame runs, which issue #2555's escape scan reads ({ConstructionEscape.escaped?}).
30
+ attr_reader :body
31
+ # The method's parameters (nil for a class, module or file body), which issue #2608's holder scan reads for a
32
+ # zero-argument `super` ({RemoverHolderScan}).
33
+ attr_reader :parameters
34
+
18
35
  # The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
19
36
  # parameters and targets.
20
37
  LOCAL_BINDINGS = Set[
@@ -37,6 +54,8 @@ module Rigor
37
54
  @scans = nil
38
55
  @last_line_closure = nil
39
56
  @last_line_fallback = nil
57
+ @body_locals = nil
58
+ @parameter_names = nil
40
59
  end
41
60
 
42
61
  def matching_closure?(scope = nil)
@@ -88,6 +107,17 @@ module Rigor
88
107
  @forwarded_block == name
89
108
  end
90
109
 
110
+ # Issue #2354 — whether `name` is a local of the frame's own body: the body binds it (a write, or a block's,
111
+ # lambda's, `rescue`'s or pattern's own parameter or target) and no parameter of the method names it. A
112
+ # method parameter is the caller's object wherever the body also writes the name (`h = h.dup if f`, a
113
+ # later `h = {}`), so it never counts. Kept per name for the rest of the body.
114
+ def body_local?(name)
115
+ bound = (@body_locals ||= {})
116
+ return bound[name] if bound.key?(name)
117
+
118
+ bound[name] = !parameter_names.include?(name) && binds_local?(@body, name)
119
+ end
120
+
91
121
  # Issue #1157 — whether a call's `&expr` block argument hands on the block THIS method was called with: the
92
122
  # anonymous `&` of a method that declares an anonymous `&` parameter, or {#forwarded_block?} for `&name`.
93
123
  # Anything else (`&:sym`, a proc made here, a rebound name) is a block of this frame's own making.
@@ -144,6 +174,33 @@ module Rigor
144
174
  !block.nil? && block.name.nil?
145
175
  end
146
176
 
177
+ # The names the method's own parameters bind: positional (destructured ones included), optional, rest,
178
+ # keyword, keyword-rest and block. Empty for a class, module or file body.
179
+ def parameter_names
180
+ @parameter_names ||= begin
181
+ parameters = @parameters
182
+ names = Set.new
183
+ if parameters.is_a?(Prism::ParametersNode)
184
+ [*parameters.requireds, *parameters.optionals, parameters.rest, *parameters.posts,
185
+ *parameters.keywords, parameters.keyword_rest, parameters.block].each do |parameter|
186
+ collect_parameter_names(parameter, names)
187
+ end
188
+ end
189
+ names.freeze
190
+ end
191
+ end
192
+
193
+ def collect_parameter_names(node, names)
194
+ case node
195
+ when Prism::MultiTargetNode
196
+ [*node.lefts, node.rest, *node.rights].each { |target| collect_parameter_names(target, names) }
197
+ when Prism::SplatNode then collect_parameter_names(node.expression, names)
198
+ else
199
+ name = node.respond_to?(:name) ? node.name : nil
200
+ names << name if name
201
+ end
202
+ end
203
+
147
204
  # The method's own `&block` parameter's name, or nil.
148
205
  def block_parameter_name
149
206
  parameters = @parameters
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MatchRebinding
8
+ # Issue #2443, #1371 — the match globals a block enters with when the call that owns it matches before every
9
+ # yield. `String#scan`, `#gsub`, `#gsub!`, `#sub` and `#sub!` set the caller's `$~` to the current match before
10
+ # each run of their block, with a Regexp pattern or a String one, so the block reads that match on every run
11
+ # whatever the enclosing frame held; the frame-wide entry ({MatchRebinding.block_entry}) would otherwise leave
12
+ # it the narrowing at the call site or nothing at all.
13
+ #
14
+ # A receiver that is a String, or a `Dynamic` the analyzer cannot type ({Narrowing.string_match_receiver?}),
15
+ # enters with the call's own match edge ({Narrowing.yielded_match_scope}), which forgets what the frame held.
16
+ # A `Dynamic` receiver may be another object whose block-taking `scan` runs no match, and then the block reads
17
+ # the caller's `$~`; the name is taken as String's there, the overwhelmingly common case. A receiver known to
18
+ # be anything else — a project class, `Pathname`, a union with either — keeps the frame-wide entry: a method
19
+ # defined in Ruby runs its matches in a frame of its own, so its block reads the caller's `$~` as the frame
20
+ # rules leave it. So does an implicit-self call.
21
+ #
22
+ # The block form of `grep` and `grep_v` runs `pattern === element` in the caller's frame before each yield,
23
+ # which a Regexp pattern answers by setting `$~`: `grep` yields after a match, `grep_v` after a failed one. With
24
+ # a regex literal for the pattern, a `grep` block enters with that match edge and a `grep_v` block with the
25
+ # globals unbound. `Enumerable` is mixed into classes of every kind, so any explicit receiver counts but one
26
+ # whose `grep` the project defines ({ProjectMethodOwnership}): that method matches, if at all, in a frame of
27
+ # its own, and its block keeps the frame-wide entry as a project `scan`'s does. Where a refinement that defines
28
+ # the name is in effect at the call (issue #3198), the method that runs is the refinement's or `Enumerable`'s,
29
+ # and this does not tell which, so the block enters with what `Enumerable`'s would bind read untyped. Any other
30
+ # pattern (`grep(Integer)`) keeps the frame-wide entry too, since its `===` may leave `$~` alone.
31
+ module YieldingMatch
32
+ NAMES = Set[:scan, :gsub, :gsub!, :sub, :sub!].freeze
33
+ GREP_NAMES = Set[:grep, :grep_v].freeze
34
+ private_constant :NAMES, :GREP_NAMES
35
+
36
+ module_function
37
+
38
+ # `scope` with the match globals `call_node`'s block enters with; `scope` itself for any other block.
39
+ # `operand_scope` types the receiver.
40
+ def entry(scope, block_node, call_node, operand_scope)
41
+ return scope unless own_block_call?(block_node, call_node)
42
+
43
+ name = call_node.name
44
+ if NAMES.include?(name)
45
+ string_entry(scope, call_node, operand_scope)
46
+ elsif GREP_NAMES.include?(name)
47
+ grep_entry(scope, call_node, operand_scope)
48
+ else
49
+ scope
50
+ end
51
+ end
52
+
53
+ def string_entry(scope, call_node, operand_scope)
54
+ return scope unless Narrowing.string_match_receiver?(operand_scope.type_of(call_node.receiver), operand_scope)
55
+
56
+ Narrowing.yielded_match_scope(scope, pattern_argument(call_node))
57
+ end
58
+
59
+ def grep_entry(scope, call_node, operand_scope)
60
+ pattern = pattern_argument(call_node)
61
+ return scope unless pattern.is_a?(Prism::RegularExpressionNode) ||
62
+ pattern.is_a?(Prism::InterpolatedRegularExpressionNode)
63
+ return scope if project_method?(call_node, operand_scope)
64
+
65
+ entry = call_node.name == :grep_v ? scope.forget_match_globals : Narrowing.yielded_match_scope(scope, pattern)
66
+ refined?(call_node, operand_scope) ? entry.untyped_match_globals : entry
67
+ end
68
+
69
+ # True when the receiver answers the call with a method the project defines, or is a carrier whose class
70
+ # cannot be resolved; an untyped receiver is not one.
71
+ def project_method?(call_node, operand_scope)
72
+ targets = ProjectMethodOwnership.targets(operand_scope.type_of(call_node.receiver))
73
+ return true if targets.nil?
74
+
75
+ targets.any? do |class_name, kind|
76
+ ProjectMethodOwnership.defines?(class_name, call_node.name, kind, operand_scope)
77
+ end
78
+ end
79
+
80
+ # Issue #3198 — true when a refinement in effect at the call defines its name on any class: a refined method
81
+ # is Ruby's, and matches, if at all, in a frame of its own. The refined class is not compared with the
82
+ # receiver, so the call may still be `Enumerable`'s.
83
+ def refined?(call_node, operand_scope)
84
+ SelfPreservingBlock.refined_in_effect?(call_node, call_node.name, operand_scope)
85
+ end
86
+
87
+ # The call's first argument, which each of these methods takes as its pattern; nil without one.
88
+ def pattern_argument(call_node)
89
+ call_node.arguments&.arguments&.first
90
+ end
91
+
92
+ def own_block_call?(block_node, call_node)
93
+ call_node.is_a?(Prism::CallNode) && !call_node.receiver.nil? &&
94
+ block_node.is_a?(Prism::BlockNode) && call_node.block.equal?(block_node)
95
+ end
96
+ private_class_method :string_entry, :grep_entry, :project_method?, :refined?, :pattern_argument,
97
+ :own_block_call?
98
+ end
99
+ end
100
+ end
101
+ end
@@ -7,11 +7,14 @@ require_relative "../type"
7
7
  require_relative "block_call_timing"
8
8
  require_relative "fresh_frame_blocks"
9
9
  require_relative "guard_rebinding"
10
+ require_relative "project_method_ownership"
11
+ require_relative "self_preserving_block"
10
12
  require_relative "stored_block_call"
11
13
  require_relative "match_rebinding/frame"
12
14
  require_relative "match_rebinding/operands"
13
15
  require_relative "match_rebinding/calls"
14
16
  require_relative "match_rebinding/self_calls"
17
+ require_relative "match_rebinding/yielding_match"
15
18
  require_relative "last_line"
16
19
 
17
20
  module Rigor
@@ -363,21 +366,41 @@ module Rigor
363
366
  # ractor's root block with the globals unbound, since it reads a slot of its own, and a `define_method` body
364
367
  # with a narrowed global untyped, since it reads the definer's slot whenever the method is called. `$_`, which
365
368
  # shares the slot, enters on the same terms ({LastLine.block_entry}, issue #1359). A closure's body, which runs
366
- # whenever it is called, enters with `$!`, `$@` and `$?` unbound ({FreshFrameBlocks.closure_entry}, issue #1360).
369
+ # whenever it is called, enters with `$!`, `$@` and `$?` unbound ({FreshFrameBlocks.closure_entry}, issue #1360),
370
+ # and with the match globals unbound whatever its body does ({FreshFrameBlocks.proc_body?}, issue #1371): the
371
+ # narrowing where a lambda or proc is written proves nothing about the `$~` it reads when it is called. The
372
+ # block of `Enumerator.new` or `Hash.new` on the core class runs later too, but often in the statement that
373
+ # makes it, so where none of the rules above forgets them it enters with a narrowed global untyped
374
+ # ({FreshFrameBlocks.deferred_constructor?}, issue #3195).
367
375
  #
368
376
  # Issue #1429 — the entry also restores a guard's narrowing of a global or constant where the body may run after
369
377
  # code that rebinds it ({GuardRebinding.block_entry}).
378
+ #
379
+ # Issue #2443, #1371 — the block of a call that matches before every yield (`scan`, `gsub`, `sub`, … on a String
380
+ # or untyped receiver, `grep` and `grep_v` with a regex literal) enters with that call's own match edge in place
381
+ # of the entry above ({YieldingMatch}); a match its body runs still rebinds them through the statement-level
382
+ # rules.
370
383
  def block_entry(scope, block_node, call_node = nil)
384
+ operand_scope = scope
371
385
  scope = GuardRebinding.block_entry(scope, block_node, call_node)
372
386
  return FreshFrameBlocks.entry(scope, call_node) if FreshFrameBlocks.fresh_entry?(call_node, scope)
373
387
 
374
388
  scope = FreshFrameBlocks.closure_entry(scope, block_node, call_node)
375
389
  scope = LastLine.block_entry(scope, block_node, call_node)
390
+ YieldingMatch.entry(frame_entry(scope, block_node, call_node), block_node, call_node, operand_scope)
391
+ end
392
+
393
+ def frame_entry(scope, block_node, call_node)
376
394
  return scope unless scope.match_globals_bound?
377
- return scope unless scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
378
- (call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
395
+ return scope.forget_match_globals if entry_forgets?(scope, block_node, call_node)
396
+
397
+ FreshFrameBlocks.deferred_constructor?(block_node, call_node, scope) ? scope.untyped_match_globals : scope
398
+ end
379
399
 
380
- scope.forget_match_globals
400
+ def entry_forgets?(scope, block_node, call_node)
401
+ FreshFrameBlocks.proc_body?(block_node, call_node, scope) ||
402
+ scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
403
+ (call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
381
404
  end
382
405
 
383
406
  def entry_may_match?(body, scope, call_node)
@@ -386,7 +409,7 @@ module Rigor
386
409
 
387
410
  body.is_a?(Prism::Node) && scan(body, scope, base: true)
388
411
  end
389
- private_class_method :entry_may_match?
412
+ private_class_method :frame_entry, :entry_forgets?, :entry_may_match?
390
413
  end
391
414
  end
392
415
  end
@@ -7,7 +7,7 @@ module Rigor
7
7
  # Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
8
8
  # Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
9
9
  # the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
10
- # The translator collapses both aliases to `Dynamic[Top]` (interfaces are not structurally matched
10
+ # The translator reads each alias's interface arm as `Dynamic[Top]` (interfaces are not structurally matched
11
11
  # yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
12
12
  # whose strict arm matches.
13
13
  #
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # The positional argument lists a call with a precise union argument stands for (#2379): one per combination
9
+ # of the union arguments' members, since the runtime selects an overload per value. `RbsDispatch` selects an
10
+ # overload for each list and joins their returns.
11
+ module ArgumentDistribution
12
+ # The most lists {.lists} spells out when more than one argument is a union. One union argument is linear in
13
+ # its members and is not capped.
14
+ LIMIT = 8
15
+ private_constant :LIMIT
16
+
17
+ # {.lists}' answer past {LIMIT}: the call's overloads cannot be read per value, and it answers `Dynamic[top]`.
18
+ OVER_LIMIT = :over_limit
19
+
20
+ module_function
21
+
22
+ # The lists, nil when no argument is a union, or {OVER_LIMIT}.
23
+ def lists(arg_types)
24
+ unions = arg_types.count { |arg| arg.is_a?(Type::Union) }
25
+ return nil if unions.zero?
26
+
27
+ if unions > 1
28
+ count = arg_types.reduce(1) { |product, arg| arg.is_a?(Type::Union) ? product * arg.members.size : product }
29
+ return OVER_LIMIT if count > LIMIT
30
+ end
31
+
32
+ arg_types.reduce([[]]) do |partials, arg|
33
+ members = arg.is_a?(Type::Union) ? arg.members : [arg]
34
+ partials.flat_map { |partial| members.map { |member| partial + [member] } }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../type"
4
4
  require_relative "iterator_dispatch"
5
+ require_relative "core_enumerable_receiver"
5
6
 
6
7
  module Rigor
7
8
  module Inference
@@ -37,12 +38,12 @@ module Rigor
37
38
  module BlockFolding
38
39
  module_function
39
40
 
40
- FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :take_while].freeze
41
+ FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :find_all, :take_while].freeze
41
42
  FILTER_KEEP_ON_FALSEY = Set[:reject, :drop_while].freeze
42
43
 
43
- # The filter methods `Hash` defines itself, returning a Hash. `take_while` / `drop_while` on a Hash
44
- # are Enumerable's and return an Array of `[key, value]` pairs; `Set` and `Range` define none of the
45
- # five, so all of theirs return an Array too.
44
+ # The filter methods `Hash` defines itself, returning a Hash. `find_all` / `take_while` /
45
+ # `drop_while` on a Hash are Enumerable's and return an Array of `[key, value]` pairs; `Set` and
46
+ # `Range` define none of the six, so all of theirs return an Array too.
46
47
  HASH_RETURNING_FILTERS = Set[:select, :filter, :reject].freeze
47
48
 
48
49
  # `filter_receiver_kind`'s answer for a nominal receiver; an unlisted class declines.
@@ -52,21 +53,22 @@ module Rigor
52
53
 
53
54
  PREDICATE_METHODS = Set[:all?, :any?, :none?].freeze
54
55
 
55
- # Methods whose answer is `nil` when the block always returns Ruby-falsey — `find` / `detect`
56
- # short-circuit to nil when nothing matches, `find_index` / `index` likewise. These methods only fold
57
- # on the falsey side for now; the truthy-block side requires per-position analysis (the index of the
58
- # first kept element, or the element itself, depend on the receiver's shape and on which positions
59
- # actually evaluate to truthy).
60
- FALSEY_BLOCK_NIL_METHODS = Set[:find, :detect, :find_index, :index].freeze
56
+ # Methods whose answer is `nil` when the block always returns Ruby-falsey — `find` / `detect` /
57
+ # `rfind` short-circuit to nil when nothing matches, `find_index` / `index` / `rindex` likewise.
58
+ # These methods only fold on the falsey side here; the truthy-block side requires per-position
59
+ # analysis.
60
+ FALSEY_BLOCK_NIL_METHODS = Set[:find, :detect, :rfind, :find_index, :index, :rindex].freeze
61
+ ARRAY_ONLY_FALSEY_NIL_METHODS = Set[:rfind, :index, :rindex].freeze
61
62
 
62
63
  # Block-taking `count` returns the number of elements for which the block is truthy. With a
63
64
  # Constant-falsey block the answer is unconditionally `Constant[0]`; with a Constant-truthy block on
64
65
  # a finitely-sized receiver it is `Constant[size]`.
65
66
  COUNT_METHOD = :count
66
67
 
67
- # `min_by` / `max_by` pick one element by the block's key, so the block's value decides WHICH element
68
- # but never WHETHER one is returned: only an empty receiver answers `nil`.
69
- EDGE_BY_METHODS = Set[:min_by, :max_by].freeze
68
+ # `min_by` / `max_by` / `minmax_by` pick edge elements by the block's key, so the block's value
69
+ # decides WHICH element(s) but never WHETHER one is returned: only an empty receiver answers `nil`
70
+ # (or `[nil, nil]` for `minmax_by`).
71
+ EDGE_BY_METHODS = Set[:min_by, :max_by, :minmax_by].freeze
70
72
 
71
73
  # `context.block_type` is the inferred return type of the call's block; `nil` means "no block at the
72
74
  # call site" and disqualifies every rule here.
@@ -76,32 +78,44 @@ module Rigor
76
78
  args = context.args
77
79
  block_type = context.block_type
78
80
  return nil if receiver.nil? || block_type.nil?
79
- return fold_edge_by(receiver, args) if EDGE_BY_METHODS.include?(method_name)
81
+ return fold_edge_by(receiver, method_name, args) if EDGE_BY_METHODS.include?(method_name)
80
82
 
83
+ # Issue #3242 — the predicate, `find`-family and `count` folds read no receiver shape, so a class that
84
+ # may define the method itself declines (the filter folds already do, through `filter_receiver_kind`).
81
85
  truthiness = constant_truthiness(block_type)
82
- return nil if truthiness.nil?
86
+ return nil if truthiness.nil? || CoreEnumerableReceiver.project_receiver?(receiver)
83
87
 
84
88
  if PREDICATE_METHODS.include?(method_name)
85
89
  fold_predicate(receiver, method_name, truthiness, args)
86
90
  elsif filter_method?(method_name)
87
91
  fold_filter(receiver, method_name, truthiness)
88
92
  elsif FALSEY_BLOCK_NIL_METHODS.include?(method_name)
89
- fold_falsey_nil_short_circuit(method_name, truthiness, args)
93
+ fold_falsey_nil_short_circuit(receiver, method_name, truthiness, args)
90
94
  elsif method_name == COUNT_METHOD
91
95
  fold_count(receiver, truthiness, args)
92
96
  end
93
97
  end
94
98
 
95
- # Issue #1333 — `min_by` / `max_by` with a block and no count on a non-empty Tuple or a non-empty
96
- # constant integer `Range` answer the receiver's element type: the RBS `Elem?` return adds `nil` for
97
- # the empty receiver, and `[1, 2].min_by { |s| rand(3) } + 1` then reported a nil receiver on correct
98
- # code. The block's value picks which element, and one `block_type` typed from the call's entry scope
99
- # cannot say which, so the answer is the whole element union. An empty receiver, one whose size is
100
- # not static (`Array[T]`), and the count form (`min_by(2) { … }`, an Array) decline to RBS.
101
- def fold_edge_by(receiver, args)
99
+ # Issue #1333 — `min_by` / `max_by` / `minmax_by` with a block and no count on a Tuple or constant
100
+ # integer `Range` answer the receiver's element type without `nil` when non-empty (or `[nil, nil]`
101
+ # for an empty `minmax_by`), avoiding false `nil` receivers on `min_by` / `minmax_by` calls.
102
+ def fold_edge_by(receiver, method_name, args)
102
103
  return nil unless args.empty?
103
- return nil unless receiver_emptiness(receiver) == :non_empty
104
104
 
105
+ emptiness = receiver_emptiness(receiver)
106
+ if method_name == :minmax_by && emptiness == :empty
107
+ nil_const = Type::Combinator.constant_of(nil)
108
+ return Type::Combinator.tuple_of(nil_const, nil_const)
109
+ end
110
+ return nil unless emptiness == :non_empty
111
+
112
+ element = edge_by_element_type(receiver)
113
+ return nil if element.nil?
114
+
115
+ method_name == :minmax_by ? Type::Combinator.tuple_of(element, element) : element
116
+ end
117
+
118
+ def edge_by_element_type(receiver)
105
119
  case receiver
106
120
  when Type::Tuple then Type::Combinator.union(*receiver.elements)
107
121
  when Type::Constant then IteratorDispatch.element_type_of(receiver) if receiver.value.is_a?(Range)
@@ -288,13 +302,13 @@ module Rigor
288
302
  end
289
303
  end
290
304
 
291
- # `find` / `detect` / `find_index` / `index` (block form) short-circuit to nil when the block is
292
- # provably falsey. `index` and `find_index` also accept a non-block argument form
293
- # (`arr.index(value)`); we decline whenever the call carries a positional argument so the RBS tier
294
- # still answers the value-search variant correctly.
295
- def fold_falsey_nil_short_circuit(_method_name, truthiness, args)
305
+ # `find` / `detect` / `find_index` (and Array-only `rfind` / `index` / `rindex`) short-circuit to
306
+ # nil when the block is provably falsey. Calls with a positional argument decline so the RBS tier
307
+ # answers value-search calls.
308
+ def fold_falsey_nil_short_circuit(receiver, method_name, truthiness, args)
296
309
  return nil unless args.empty?
297
310
  return nil unless truthiness == :falsey
311
+ return nil if ARRAY_ONLY_FALSEY_NIL_METHODS.include?(method_name) && filter_receiver_kind(receiver) != :array
298
312
 
299
313
  Type::Combinator.constant_of(nil)
300
314
  end
@@ -317,7 +331,7 @@ module Rigor
317
331
  end
318
332
 
319
333
  # Returns the receiver's known finite element count, or nil when the carrier does not pin a size.
320
- # Tuple and HashShape are pinned by construction; `Constant<…>` exposes the literal's `.size`. Other
334
+ # Tuple and HashShape are pinned by construction, and a `Constant` Range by its bounds. Other
321
335
  # shapes (Array[T], Range[T], Nominal) decline so the RBS tier widens.
322
336
  def finite_size(receiver)
323
337
  case receiver
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # Issue #2686 — `Rigor::Plugin::Base.node_rule(node_class) { |node, ...| }` yields instances of the class it is
9
+ # given, which RBS cannot say: `[N < ::Prism::Node] (singleton(N))` does not resolve (RBS has no
10
+ # `singleton(<type variable>)`). `sig/rigor/plugin/base.rbs` declares per-class overloads for the classes
11
+ # the bundled plugins register and a `Prism::Node` fallback. Read through the overloads alone, two calls bind
12
+ # `node` wrong:
13
+ #
14
+ # - a class with no overload of its own (`node_rule(Prism::IfNode)`) takes the fallback, so `node` is
15
+ # `Prism::Node` and `node.predicate` reported `call.undefined-method`;
16
+ # - a class argument the engine cannot read (`def self.reg(k) = node_rule(k) { ... }`) matches every overload
17
+ # gradually, and the block probe took the first, `Prism::CallNode`.
18
+ #
19
+ # This rebinds the block's first parameter from the class argument:
20
+ #
21
+ # - `singleton(X)` binds `X`, and a union of class objects the union of their instances;
22
+ # - any other argument (an untyped parameter, a `Class` value) binds `Dynamic[top]`, so the body is not
23
+ # checked against a class the call may not register.
24
+ #
25
+ # The rest of the block's parameters keep the overload's binding; every overload declares them alike.
26
+ #
27
+ # A per-class overload set generated from Prism's node classes was the alternative. It was declined: the
28
+ # gemspec admits `prism >= 1.0, < 2.0`, and a generated set names classes later minors added, which an older
29
+ # Prism's RBS does not declare, so loading Rigor's own `sig/` would fail there.
30
+ #
31
+ # Only the declaration Rigor owns is read, by the method's RBS owner, so a project's own `node_rule` is not.
32
+ module ClassKeyedBlockParams
33
+ OWNER = "::Rigor::Plugin::Base"
34
+ METHOD = :node_rule
35
+ private_constant :OWNER, :METHOD
36
+
37
+ module_function
38
+
39
+ # @param method_definition — the RBS declaration the probe read.
40
+ # @param params — the block parameters the overloads bound.
41
+ # @return the block parameters, the first rebound for `Base.node_rule`.
42
+ def rebind(method_definition, method_name, args, params)
43
+ return params unless method_name == METHOD
44
+ return params unless method_definition.defined_in.to_s == OWNER
45
+
46
+ [instance_of(args.first), *params.drop(1)]
47
+ end
48
+
49
+ # The instances the class argument names, or `Dynamic[top]`.
50
+ def instance_of(arg)
51
+ case arg
52
+ when Type::Singleton
53
+ Type::Combinator.nominal_of(arg.class_name)
54
+ when Type::Union
55
+ return Type::Combinator.untyped unless arg.members.all?(Type::Singleton)
56
+
57
+ Type::Combinator.union(*arg.members.map { |member| Type::Combinator.nominal_of(member.class_name) })
58
+ else
59
+ Type::Combinator.untyped
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -192,8 +192,8 @@ module Rigor
192
192
 
193
193
  # #2115 — the numeric twin of `STRING_FOLD_BYTE_LIMIT`. A folded Integer (and each Integer part of a
194
194
  # folded Rational / Complex) is at most this many bits, the largest bit length whose decimal
195
- # rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds, `2 ** 13_606`
196
- # (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
195
+ # rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
196
+ # `2 ** 13_606` (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
197
197
  # rendering it in decimal — which describing, sorting and printing the `Constant` does — takes
198
198
  # minutes. `**` / `pow` / `<<` / `>>` also check their operands before invoking, so the big value
199
199
  # is never computed.
@@ -380,7 +380,7 @@ module Rigor
380
380
  # Normalises an input type into one of:
381
381
  # - `Array<Object>` for a `Constant` (1-element) or `Union[Constant…]` (n-element) — concrete values
382
382
  # to enumerate.
383
- # - `Type::IntegerRange` — bounded interval.
383
+ # - `Type::IntegerRange` / `Type::FloatRange` — bounded interval.
384
384
  # - `nil` — the input shape is not foldable.
385
385
  def numeric_set_of(type)
386
386
  case type