rigortype 0.3.9 → 0.4.0

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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -3,6 +3,9 @@
3
3
  require_relative "../../type"
4
4
  require_relative "../acceptance"
5
5
  require_relative "../rbs_type_translator"
6
+ require_relative "alias_strict_nominals"
7
+ require_relative "facet_distribution"
8
+ require_relative "proven_overload"
6
9
  require_relative "receiver_affinity"
7
10
 
8
11
  module Rigor
@@ -14,6 +17,9 @@ module Rigor
14
17
  # 1. Filter overloads by positional arity (required, optional and rest_positionals are honored;
15
18
  # required_keywords disqualify the overload because we do not yet thread keyword args through
16
19
  # `call_arg_types`).
20
+ # 1a. **Pass 0 — proven, in declared order (#1344).** With only plain-value arguments, the first
21
+ # overload they do not rule out wins outright when each of its params names its argument's own
22
+ # class and accepts it with a `yes`; every later pass reads the receiver-affinity order instead.
17
23
  # 2. **Pass 1 — strict matches first.** Among the arity-matching overloads, prefer the first one
18
24
  # whose every (param, arg) pair returns a `yes` or `maybe` answer AND whose param types do NOT
19
25
  # translate through `RBS::Types::Alias` / `Interface` / `Intersection`. The translator demotes
@@ -38,31 +44,7 @@ module Rigor
38
44
  module OverloadSelector
39
45
  module_function
40
46
 
41
- # Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
42
- # Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
43
- # the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
44
- # The translator collapses both aliases to `Dynamic[Top]` (interfaces are not structurally matched
45
- # yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
46
- # whose strict arm matches.
47
- #
48
- # Symbol keys are the alias names as they appear under `RBS::Types::Alias#name.to_s` (the `name` is
49
- # a `TypeName` whose `to_s` includes the `::` prefix). Values are an Array of class names whose
50
- # Nominal[..] form is the alias's strict-arm matcher.
51
- ALIAS_STRICT_NOMINALS = {
52
- "::int" => ["Integer"],
53
- "::string" => ["String"],
54
- "::interned" => %w[Symbol String],
55
- "::io" => ["IO"],
56
- "::encoding" => %w[Encoding String],
57
- "::path" => ["String"],
58
- "::boolean" => %w[TrueClass FalseClass],
59
- # `range[T] = Range[T] | _Range[T]`. Generic, unlike the others, but the strict arm is still a
60
- # single nominal and the args are irrelevant to this pass. rbs 4.1 rewrote `Array#[]`'s slicing
61
- # overload from `(::Range[::Integer?])` to `(range[int])`; without the entry both it and the
62
- # `(int) -> E` overload look alias-typed, so `a[1..2]` resolved to the element type.
63
- "::range" => ["Range"]
64
- }.freeze
65
- private_constant :ALIAS_STRICT_NOMINALS
47
+ # `ALIAS_STRICT_NOMINALS`, the alias-strict pass's table, lives in `alias_strict_nominals.rb`.
66
48
 
67
49
  # @param arg_types — caller-provided types in positional order. Empty when
68
50
  # there are no arguments.
@@ -79,22 +61,40 @@ module Rigor
79
61
  # declaration.
80
62
  # @return the chosen overload, or nil when the definition has no method
81
63
  # types at all.
82
- def select(method_definition, **) = select_candidates(method_definition, **).first
64
+ def select(method_definition, **) = select_candidates(method_definition, member_wise: false, **).first
83
65
 
84
- # Issue #521 — like {.select}, but when a `Dynamic[Top]` argument reaches the gradual pass it
85
- # returns EVERY gradually-matching overload instead of pinning the first. An untyped argument
86
- # accepts every param indiscriminately, so "first gradual match" is decided by overload-list
66
+ # Issue #521 — like {.select}, but when an imprecise argument (`Dynamic[Top]`, or since #1021 a
67
+ # union with a `Dynamic[Top]` member) reaches the gradual pass it returns EVERY gradually-matching
68
+ # overload instead of pinning the first. An untyped argument accepts every param
69
+ # indiscriminately, so "first gradual match" is decided by overload-list
87
70
  # position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
88
71
  # and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
89
- # returns, which contains the truth whichever overload the runtime takes. Every other path (strict,
90
- # alias-resolved, typed-gradual, arity fallback) still yields exactly one candidate, so `select`
91
- # keeps its historical single answer.
72
+ # returns, which contains the truth whichever overload the runtime takes. The member-wise reading of a
73
+ # two-member `Dynamic` facet (#1350, `FacetDistribution`) returns one candidate per member the same way.
74
+ # Every other path (strict, alias-resolved, typed-gradual, arity fallback) yields exactly one candidate,
75
+ # and `select` (`member_wise: false`) keeps its historical single answer.
92
76
  #
93
77
  # @return matching overloads; empty when the definition declares none.
94
- def select_candidates(method_definition, arg_types:, self_type:, instance_type:, type_vars: {},
95
- block_required: false, environment: nil)
96
- overloads = method_definition.method_types
97
- return [] if overloads.empty?
78
+ # rubocop:disable-next Metrics/ParameterLists -- the selection keywords plus the member-wise switch.
79
+ def select_candidates(definition, arg_types:, self_type:, instance_type:, type_vars: {},
80
+ block_required: false, environment: nil, member_wise: true)
81
+ unless FacetDistribution.faceted?(arg_types)
82
+ return select_declared(definition, arg_types, self_type, instance_type, type_vars, block_required,
83
+ environment, false)
84
+ end
85
+
86
+ FacetDistribution.select(arg_types, definition, member_wise:, environment:) do |args, member|
87
+ select_declared(definition, args, self_type, instance_type, type_vars, block_required, environment, member)
88
+ end
89
+ end
90
+
91
+ # The selection proper, positional so the hot path forwards no keyword hash. A member-wise call (`member`, see
92
+ # `FacetDistribution.select`) answers only a genuine match, never the first-overload fallback.
93
+ # rubocop:disable-next Metrics/ParameterLists -- the selection inputs plus the member-wise flag.
94
+ def select_declared(method_definition, arg_types, self_type, instance_type, type_vars, block_required,
95
+ environment, member)
96
+ declared = method_definition.method_types
97
+ return [] if declared.empty?
98
98
 
99
99
  # `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
100
100
  # parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
@@ -104,17 +104,18 @@ module Rigor
104
104
  # Pre-sort: demote overloads whose param class is a disjoint sibling of the receiver class (e.g.
105
105
  # `Integer#+(BigDecimal) -> BigDecimal` from the `bigdecimal` RBS reopen). Honors the coerce
106
106
  # convention so `5 + ?` for unknown `?` resolves to the receiver-class-preserving arm rather than
107
- # an arbitrary sibling-class arm that only wins by overload-list position.
108
- overloads = ReceiverAffinity.reorder(overloads, self_type: self_type, environment: environment)
107
+ # an arbitrary sibling-class arm that only wins by overload-list position. The proven pass keeps
108
+ # the declared order (see `run_selection_passes`).
109
+ overloads = ReceiverAffinity.reorder(declared, self_type: self_type, environment: environment)
109
110
 
110
111
  # One keyword bundle for the pass pipeline (see `run_selection_passes`); a block retry rebuilds it
111
112
  # with the block flag cleared. Built once and passed positionally -- the previous lambda plus a
112
113
  # `**shared` splat per pass allocated three objects per selection (#775).
113
114
  shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
114
115
  type_vars: type_vars, block_required: block_required, param_overrides: param_overrides,
115
- alias_expander: environment&.rbs_loader }
116
+ alias_expander: environment&.rbs_loader, environment: environment }
116
117
 
117
- matches = run_selection_passes(overloads, shared)
118
+ matches = run_selection_passes(declared, overloads, shared)
118
119
  return matches unless matches.empty?
119
120
 
120
121
  # A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
@@ -123,9 +124,10 @@ module Rigor
123
124
  # `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
124
125
  # `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
125
126
  if block_required
126
- matches = run_selection_passes(overloads, shared.merge(block_required: false))
127
+ matches = run_selection_passes(declared, overloads, shared.merge(block_required: false))
127
128
  return matches unless matches.empty?
128
129
  end
130
+ return [] if member
129
131
 
130
132
  # No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
131
133
  # `overloads.first`. Methods like `Array#filter` / `Enumerable#map` declare the block-bearing
@@ -150,18 +152,30 @@ module Rigor
150
152
  class << self
151
153
  private
152
154
 
153
- # Three-pass overload search:
154
- # - Pass 1 (strict): skipped when any arg is `Dynamic[Top]`, because gradual acceptance against
155
- # an untyped arg accepts every param indiscriminately and would let pass 1 lock in an arbitrary
156
- # strict overload (e.g. `Regexp#=~(nil) -> nil` over the `(::interned?) -> Integer?` overload).
155
+ # Overload search after pass 0 (`find_proven_overload`):
156
+ # - Pass 1 (strict): skipped when any arg is imprecise (`imprecise_arg?`), because gradual
157
+ # acceptance against an untyped arg accepts every param indiscriminately and would let pass 1
158
+ # lock in an arbitrary strict overload (e.g. `Regexp#=~(nil) -> nil` over the
159
+ # `(::interned?) -> Integer?` overload).
157
160
  # - Pass 1.5 (alias-resolved): consults each `RBS::Types::Alias`'s strict arm so e.g.
158
161
  # `Array#*(int)` wins over the `Array#*(string) -> String` overload for Integer args.
159
162
  # - Pass 2 (gradual): the original gradual matcher so overloads that legitimately rely on
160
163
  # duck-typed params still resolve when nothing stricter applies.
161
164
  # `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
162
165
  # because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
163
- def run_selection_passes(overloads, shared)
164
- strict = find_matching_overload(overloads, shared, strict: true)
166
+ #
167
+ # Pass 0 (#1344) comes first and reads the overloads in declared order; every later pass reads them
168
+ # in receiver-affinity order (`ReceiverAffinity`). RBS resolves two arms that both take an argument by
169
+ # declaration order: `Rational#+` declares `(Float) -> Float` before `(Numeric) -> Rational`, and the
170
+ # affinity order, which moves the `(Numeric)` arm first, typed `Rational(1, 2) + 0.5` as `Rational`
171
+ # where Ruby answers `1.0`. The affinity order stays for every call pass 0 declines: read in declared
172
+ # order, the strict pass let the `bigdecimal` reopen's `(BigDecimal) -> BigDecimal` arm, which it
173
+ # matches on a `maybe`, take `Integer#+` of a `bot`, a `Dynamic[Integer | Float | …]` or an
174
+ # unloadable class (612 call sites across the survey corpus).
175
+ def run_selection_passes(declared, overloads, shared)
176
+ # Unreordered, pass 0 can only pick what the strict pass picks, so it is skipped (#1344's allocations).
177
+ proven = find_proven_overload(declared, shared) unless overloads.equal?(declared)
178
+ strict = proven || find_matching_overload(overloads, shared, strict: true)
165
179
  return strict unless strict.empty?
166
180
 
167
181
  alias_hit = find_matching_overload_via_aliases(
@@ -170,15 +184,33 @@ module Rigor
170
184
  return [alias_hit] if alias_hit
171
185
 
172
186
  # Pass 2, array-valued. With every argument carrying real type information the first gradual
173
- # match keeps its historical single-winner contract. With a `Dynamic[Top]` argument in play the
187
+ # match keeps its historical single-winner contract. With an imprecise argument in play the
174
188
  # matches are indistinguishable by types — position alone would pick — so ALL of them come back
175
189
  # and the dispatch layer unions their returns (#521).
176
190
  matches = find_matching_overload(overloads, shared, strict: false)
177
- return matches.first(1) unless shared[:arg_types].any? { |t| untyped_arg?(t) }
191
+ return matches.first(1) unless shared[:arg_types].any? { |t| imprecise_arg?(t) }
178
192
 
179
193
  matches
180
194
  end
181
195
 
196
+ # Pass 0. With every argument a plain value (a `Constant`, or a `Nominal` with no type arguments), the
197
+ # first overload in declared order that the arguments do not rule out is the one RBS would take, and
198
+ # the pass takes it only when that is certain: every parameter is a lone class naming its argument's
199
+ # own class, and accepts it with a `yes`. Any doubt declines to the affinity-ordered passes. A `maybe`
200
+ # is doubt — the analyzer process cannot load a project class, so a declared `(Base)` only maybe
201
+ # takes a `Sub`, and taking a later `(top)` arm instead typed the call wrong. So is a union or
202
+ # supertype parameter, which takes the argument through a declaration upstream RBS can get wrong:
203
+ # `Rational#divmod`'s `(Integer | Float | Rational) -> [Integer, Rational]` for a Float remainder.
204
+ def find_proven_overload(declared, shared)
205
+ args = shared[:arg_types]
206
+ return nil unless ProvenOverload.applies?(args)
207
+
208
+ index = declared.index { |mt| engages_block_shape?(mt, shared[:block_required]) && matches?(mt, shared) }
209
+ first = index && declared[index]
210
+ proven = first && strictly_typed_params?(first, args.size) && matches?(first, shared, strict: :proven)
211
+ [first] if proven && !ProvenOverload.contested?(declared, index, args, shared[:environment])
212
+ end
213
+
182
214
  # The shared "no overload matched" answer; every consumer only reads the list.
183
215
  NO_MATCH = [].freeze
184
216
  private_constant :NO_MATCH
@@ -187,7 +219,7 @@ module Rigor
187
219
  # type_vars, block_required, param_overrides, alias_expander).
188
220
  def find_matching_overload(overloads, shared, strict:)
189
221
  arg_types = shared[:arg_types]
190
- return NO_MATCH if strict && arg_types.any? { |t| untyped_arg?(t) }
222
+ return NO_MATCH if strict && arg_types.any? { |t| imprecise_arg?(t) }
191
223
 
192
224
  block_required = shared[:block_required]
193
225
  # Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
@@ -196,7 +228,7 @@ module Rigor
196
228
  found = overloads.find do |method_type|
197
229
  engages_block_shape?(method_type, block_required) &&
198
230
  strictly_typed_params?(method_type, arg_types.size) &&
199
- matches?(method_type, shared)
231
+ matches?(method_type, shared, strict: true)
200
232
  end
201
233
  return found ? [found] : NO_MATCH
202
234
  end
@@ -218,8 +250,17 @@ module Rigor
218
250
  # Treats the literal `untyped` carrier (`Dynamic[Top]`) as too imprecise to drive a strict-pass
219
251
  # match. Other `Dynamic`-wrapped types with a concrete static facet carry enough information to
220
252
  # pick a sensible overload.
221
- def untyped_arg?(type)
222
- type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
253
+ def untyped_arg?(type) = type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
254
+
255
+ # Issue #1021 — the strict and alias passes must not discriminate on an argument whose type
256
+ # cannot rule an overload out: the bare untyped carrier, or a union with an untyped member. That
257
+ # member may reach any overload at runtime, so a strict match is decided by the union's other
258
+ # members alone — `Dynamic[top] | nil` pinned `Regexp#match?(nil) -> false` and typed a live
259
+ # predicate as the literal `false`. The gradual pass still accepts against the whole union, so
260
+ # `Dynamic[top] | nil` keeps both `match?` overloads and the #521 join answers `Dynamic[bool]`.
261
+ # A `Dynamic` with a concrete static facet stays out: its facet discriminates.
262
+ def imprecise_arg?(type)
263
+ untyped_arg?(type) || (type.is_a?(Type::Union) && type.members.any? { |member| untyped_arg?(member) })
223
264
  end
224
265
 
225
266
  # Pass 1.5: for arity-compatible overloads whose every positional param is either a strict
@@ -232,7 +273,7 @@ module Rigor
232
273
  # Issue #521 — an untyped argument "maybe"-accepts EVERY alias's strict arm, so it cannot
233
274
  # discriminate between overloads here any more than in the strict pass; without this guard a
234
275
  # Dynamic arg pinned `Array#*(string) -> String` purely by declaration order.
235
- return nil if arg_types.any? { |t| untyped_arg?(t) }
276
+ return nil if arg_types.any? { |t| imprecise_arg?(t) }
236
277
 
237
278
  overloads.find do |method_type|
238
279
  next false unless engages_block_shape?(method_type, block_required)
@@ -327,7 +368,8 @@ module Rigor
327
368
  end
328
369
 
329
370
  # `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
330
- def matches?(method_type, shared)
371
+ # `strict:` is `false` (gradual), `true` (strict pass) or `:proven` (pass 0; see `find_proven_overload`).
372
+ def matches?(method_type, shared, strict: false)
331
373
  return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
332
374
 
333
375
  arg_types = shared[:arg_types]
@@ -335,17 +377,7 @@ module Rigor
335
377
  return false unless arity_compatible?(fun, arg_types.size)
336
378
 
337
379
  params = positional_params_for(fun, arg_types.size)
338
- each_param_accepts?(params, arg_types) do |param, arg|
339
- accepts_param?(
340
- param,
341
- arg,
342
- self_type: shared[:self_type],
343
- instance_type: shared[:instance_type],
344
- type_vars: shared[:type_vars],
345
- param_overrides: shared[:param_overrides],
346
- alias_expander: shared[:alias_expander]
347
- )
348
- end
380
+ each_param_accepts?(params, arg_types) { |param, arg| accepts_param?(param, arg, shared, strict) }
349
381
  end
350
382
 
351
383
  # Slice 4 phase 2c does not pass keyword arguments through the call site (caller passes only
@@ -401,14 +433,14 @@ module Rigor
401
433
  head
402
434
  end
403
435
 
404
- def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:,
405
- alias_expander: nil)
406
- param_type = param_overrides[param.name] || RbsTypeTranslator.translate(
436
+ # `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
437
+ def accepts_param?(param, arg, shared, strict)
438
+ param_type = shared[:param_overrides][param.name] || RbsTypeTranslator.translate(
407
439
  param.type,
408
- self_type: self_type,
409
- instance_type: instance_type,
410
- type_vars: type_vars,
411
- alias_expander: alias_expander
440
+ self_type: shared[:self_type],
441
+ instance_type: shared[:instance_type],
442
+ type_vars: shared[:type_vars],
443
+ alias_expander: shared[:alias_expander]
412
444
  )
413
445
  # An `untyped` arg gradually accepts against every param, so a value-pinning param would be
414
446
  # "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
@@ -418,6 +450,14 @@ module Rigor
418
450
  return false if untyped_arg?(arg) && value_pinning?(param_type)
419
451
 
420
452
  result = param_type.accepts(arg, mode: :gradual)
453
+ return result.yes? && ProvenOverload.names_arg_class?(param_type, arg) if strict == :proven
454
+
455
+ # A record's `maybe` for a `Hash` with a gradual arm or an open shape is no evidence for the overload:
456
+ # with `({ a: Integer }) -> Integer | (Hash[Symbol, untyped]) -> String`, `{ **o, b: 2 }` has a key the
457
+ # closed record forbids, yet the first overload won by list position. The gradual pass still takes it.
458
+ # A `maybe` that survives with those records answering `no` has another source, and still counts.
459
+ return false if strict && result.maybe? && Acceptance.maybe_rests_on_unread_hash?(param_type, arg)
460
+
421
461
  result.yes? || result.maybe?
422
462
  end
423
463
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+ require_relative "singleton_folding"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module MethodDispatcher
9
+ # `Process` class-method calls that read flow state.
10
+ #
11
+ # `Process.last_status` answers the thread's last child status, the slot `$?` reads (both are
12
+ # `rb_last_status_get` in CRuby's `process.c`). `data/core_overlay/process.rbs` declares it
13
+ # `() -> Process::Status?`, which is right where nothing is known. But past a statement that certainly
14
+ # ran a subprocess the flow engine has already bound `$?` to a non-nil `Process::Status`
15
+ # ({Inference::LastStatus}); answering the declared return there would put back the possible nil
16
+ # receiver the binding exists to remove as soon as the code reaches the status through the method
17
+ # rather than the global (`system("make"); Process.last_status.success?`). The same reasoning, for
18
+ # `Regexp.last_match` and `$~`, is {RegexpFolding.fold_last_match}.
19
+ #
20
+ # A bound `$?` is answered as it stands, so the method reads what the global reads in every body the
21
+ # binding rules reach, `Dynamic[top]` in a `define_method` body included. The consult never invents a
22
+ # binding: where `$?` is unbound (rescue clauses, closures, thread roots, joins with a path that ran no
23
+ # subprocess) it declines to the declaration. It also declines an argument-bearing call, whose arity
24
+ # the RBS tier reports.
25
+ #
26
+ # A project that defines `Process.last_status` itself answers `Dynamic[top]`: its method may return
27
+ # anything, and declining would hand the call to the overlay's declaration, which outranks a
28
+ # discovered method. A definition the discovery pass does not record — `def Process.last_status` outside
29
+ # `module Process`, `define_singleton_method`, `alias_method`, a prepended module — keeps the binding's answer.
30
+ module ProcessFolding
31
+ module_function
32
+
33
+ # @return the answer, or nil to defer.
34
+ def try_dispatch(context)
35
+ return nil unless SingletonFolding.receiver?(context.receiver, "Process")
36
+ return nil unless context.method_name == :last_status && context.args.empty?
37
+
38
+ scope = context.scope
39
+ return nil if scope.nil?
40
+ return Type::Combinator.untyped if project_defines_last_status?(scope)
41
+
42
+ scope.global(:$?)
43
+ end
44
+
45
+ def project_defines_last_status?(scope)
46
+ return true if scope.discovered_method?("Process", :last_status, :singleton)
47
+ return true if scope.discovered_singleton_def_nodes["Process"]&.key?(:last_status)
48
+
49
+ patched = scope.environment&.project_patched_methods
50
+ !patched.nil? && !patched.lookup(class_name: "Process", method_name: :last_status, kind: :singleton).nil?
51
+ end
52
+ private_class_method :project_defines_last_status?
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # The value tests of `OverloadSelector`'s pass 0 (#1344), which reads the overloads in declared order and
9
+ # takes the first one plain-value arguments do not rule out, when that one names each argument's own class.
10
+ #
11
+ # Acceptance reads class relations from the analyzer's own process, which cannot load a project class or
12
+ # module, so it can rule an arm out wrongly: `(Printable)` against `1`, where project RBS includes
13
+ # `Printable` into `Integer`, answers `no`, and taking the `(Integer)` arm declared after it typed the call
14
+ # as that arm's return and fired `call.undefined-method` on correct code. {.contested?} therefore trusts
15
+ # an earlier arm to be out only when it can prove it: every positional parameter, rest included, is a plain
16
+ # class (through `?` and `|`), and both orderings — `Environment#class_ordering`, which asks the host class
17
+ # registry first, and the RBS loader's own — call it disjoint from every argument's class. A type alias,
18
+ # interface or variable proves nothing, and the host registry alone calls `Integer` and `Enumerable`
19
+ # disjoint where project RBS includes one into the other. Nor does a module, or a name Rigor stubbed because
20
+ # no RBS declares it: the source can include a module into the argument's class where no RBS says so, and
21
+ # neither ordering sees a source-only `include` (#1351). A class cannot be included, so a declared class
22
+ # stays provable.
23
+ module ProvenOverload
24
+ module_function
25
+
26
+ # Every argument is a plain value: a `Constant`, or a `Nominal` with no type arguments.
27
+ def applies?(arg_types)
28
+ !arg_types.empty? && arg_types.all? do |arg|
29
+ arg.is_a?(Type::Constant) || (arg.is_a?(Type::Nominal) && arg.type_args.empty?)
30
+ end
31
+ end
32
+
33
+ def names_arg_class?(param, arg) = param.is_a?(Type::Nominal) && param.class_name == class_name_of(arg)
34
+
35
+ def class_name_of(arg) = arg.is_a?(Type::Constant) ? arg.value.class.name : arg.class_name
36
+
37
+ # Whether an overload declared before `declared[index]` may take the arguments after all.
38
+ def contested?(declared, index, arg_types, environment)
39
+ loader = environment&.rbs_loader
40
+ return true if loader.nil?
41
+
42
+ arg_names = arg_types.map { |arg| class_name_of(arg) }
43
+ (0...index).any? { |position| !proven_out?(declared[position], arg_names, environment, loader) }
44
+ end
45
+
46
+ def proven_out?(method_type, arg_names, environment, loader)
47
+ fun = method_type.type
48
+ return false unless fun.respond_to?(:required_positionals)
49
+
50
+ params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
51
+ params += [fun.rest_positionals] if fun.rest_positionals
52
+ params.all? { |param| disjoint_param?(param.type, arg_names, environment, loader) }
53
+ end
54
+
55
+ def disjoint_param?(rbs_type, arg_names, environment, loader)
56
+ case rbs_type
57
+ when RBS::Types::ClassInstance
58
+ name = rbs_type.name.to_s.delete_prefix("::")
59
+ return false if loader.rbs_module?(name) || loader.synthesized_type_names.include?(name)
60
+
61
+ arg_names.all? do |arg|
62
+ environment.class_ordering(arg, name) == :disjoint && loader.class_ordering(arg, name) == :disjoint
63
+ end
64
+ when RBS::Types::Optional then disjoint_param?(rbs_type.type, arg_names, environment, loader)
65
+ when RBS::Types::Union then rbs_type.types.all? { |member| disjoint_param?(member, arg_names, environment, loader) }
66
+ else false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end