rigortype 0.3.8 → 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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -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,64 +44,57 @@ 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
66
-
67
- # @param method_definition [RBS::Definition::Method]
68
- # @param arg_types [Array<Rigor::Type>] caller-provided types in positional order. Empty when
47
+ # `ALIAS_STRICT_NOMINALS`, the alias-strict pass's table, lives in `alias_strict_nominals.rb`.
48
+
49
+ # @param arg_types — caller-provided types in positional order. Empty when
69
50
  # there are no arguments.
70
- # @param self_type [Rigor::Type] substitute for `Bases::Self`.
71
- # @param instance_type [Rigor::Type] substitute for `Bases::Instance`.
72
- # @param type_vars [Hash{Symbol => Rigor::Type}] substitution map for class-level type variables
51
+ # @param self_type — substitute for `Bases::Self`.
52
+ # @param instance_type — substitute for `Bases::Instance`.
53
+ # @param type_vars — substitution map for class-level type variables
73
54
  # (Slice 4 phase 2d). The selector threads it through to {RbsTypeTranslator} so parameter types
74
55
  # like `::Array[Elem]` substitute Elem before the accepts check, instead of degrading the param
75
56
  # to `Array[Dynamic[Top]]`.
76
- # @param block_required [Boolean] when `true`, only overloads that declare a block clause are
57
+ # @param block_required — when `true`, only overloads that declare a block clause are
77
58
  # considered (Slice 6 phase C sub-phase 1). The fallback also prefers a block-bearing overload
78
59
  # over `method_types.first`. When `false` (the Slice 4 phase 2c default) the selector behaves
79
60
  # exactly as before: `find` over arity-compatible overloads, falling back to the first
80
61
  # declaration.
81
- # @return [RBS::MethodType, nil] the chosen overload, or nil when the definition has no method
62
+ # @return the chosen overload, or nil when the definition has no method
82
63
  # types at all.
83
- def select(method_definition, **) = select_candidates(method_definition, **).first
64
+ def select(method_definition, **) = select_candidates(method_definition, member_wise: false, **).first
84
65
 
85
- # Issue #521 — like {.select}, but when a `Dynamic[Top]` argument reaches the gradual pass it
86
- # returns EVERY gradually-matching overload instead of pinning the first. An untyped argument
87
- # 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
88
70
  # position, not by types — `[true] * n` with an untyped `n` pinned `Array#*(string) -> String`
89
71
  # and answered a wrong precise type the runtime can contradict. The caller unions the candidates'
90
- # returns, which contains the truth whichever overload the runtime takes. Every other path (strict,
91
- # alias-resolved, typed-gradual, arity fallback) still yields exactly one candidate, so `select`
92
- # 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.
93
76
  #
94
- # @return [Array<RBS::MethodType>] matching overloads; empty when the definition declares none.
95
- def select_candidates(method_definition, arg_types:, self_type:, instance_type:, type_vars: {},
96
- block_required: false, environment: nil)
97
- overloads = method_definition.method_types
98
- return [] if overloads.empty?
77
+ # @return matching overloads; empty when the definition declares none.
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?
99
98
 
100
99
  # `rigor:v1:param: <name> <refinement>` annotations on this method override the RBS-declared
101
100
  # parameter type at the matching name. The map is consumed inside `accepts_param?` so overload
@@ -105,20 +104,18 @@ module Rigor
105
104
  # Pre-sort: demote overloads whose param class is a disjoint sibling of the receiver class (e.g.
106
105
  # `Integer#+(BigDecimal) -> BigDecimal` from the `bigdecimal` RBS reopen). Honors the coerce
107
106
  # convention so `5 + ?` for unknown `?` resolves to the receiver-class-preserving arm rather than
108
- # an arbitrary sibling-class arm that only wins by overload-list position.
109
- overloads = ReceiverAffinity.reorder(overloads, self_type: self_type, environment: environment)
110
-
111
- alias_expander = environment&.rbs_loader
112
- passes = lambda do |require_block|
113
- run_selection_passes(
114
- overloads,
115
- { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
116
- type_vars: type_vars, block_required: require_block, param_overrides: param_overrides,
117
- alias_expander: alias_expander }
118
- )
119
- end
120
-
121
- matches = passes.call(block_required)
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)
110
+
111
+ # One keyword bundle for the pass pipeline (see `run_selection_passes`); a block retry rebuilds it
112
+ # with the block flag cleared. Built once and passed positionally -- the previous lambda plus a
113
+ # `**shared` splat per pass allocated three objects per selection (#775).
114
+ shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
115
+ type_vars: type_vars, block_required: block_required, param_overrides: param_overrides,
116
+ alias_expander: environment&.rbs_loader, environment: environment }
117
+
118
+ matches = run_selection_passes(declared, overloads, shared)
122
119
  return matches unless matches.empty?
123
120
 
124
121
  # A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
@@ -127,9 +124,10 @@ module Rigor
127
124
  # `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
128
125
  # `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
129
126
  if block_required
130
- matches = passes.call(false)
127
+ matches = run_selection_passes(declared, overloads, shared.merge(block_required: false))
131
128
  return matches unless matches.empty?
132
129
  end
130
+ return [] if member
133
131
 
134
132
  # No (usable) block at the call site: prefer an overload that does not REQUIRE a block over
135
133
  # `overloads.first`. Methods like `Array#filter` / `Enumerable#map` declare the block-bearing
@@ -154,18 +152,30 @@ module Rigor
154
152
  class << self
155
153
  private
156
154
 
157
- # Three-pass overload search:
158
- # - Pass 1 (strict): skipped when any arg is `Dynamic[Top]`, because gradual acceptance against
159
- # an untyped arg accepts every param indiscriminately and would let pass 1 lock in an arbitrary
160
- # 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).
161
160
  # - Pass 1.5 (alias-resolved): consults each `RBS::Types::Alias`'s strict arm so e.g.
162
161
  # `Array#*(int)` wins over the `Array#*(string) -> String` overload for Integer args.
163
162
  # - Pass 2 (gradual): the original gradual matcher so overloads that legitimately rely on
164
163
  # duck-typed params still resolve when nothing stricter applies.
165
164
  # `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
166
165
  # because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
167
- def run_selection_passes(overloads, shared)
168
- 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)
169
179
  return strict unless strict.empty?
170
180
 
171
181
  alias_hit = find_matching_overload_via_aliases(
@@ -174,36 +184,58 @@ module Rigor
174
184
  return [alias_hit] if alias_hit
175
185
 
176
186
  # Pass 2, array-valued. With every argument carrying real type information the first gradual
177
- # 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
178
188
  # matches are indistinguishable by types — position alone would pick — so ALL of them come back
179
189
  # and the dispatch layer unions their returns (#521).
180
- matches = find_matching_overload(overloads, **shared, strict: false)
181
- return matches.first(1) unless shared[:arg_types].any? { |t| untyped_arg?(t) }
190
+ matches = find_matching_overload(overloads, shared, strict: false)
191
+ return matches.first(1) unless shared[:arg_types].any? { |t| imprecise_arg?(t) }
182
192
 
183
193
  matches
184
194
  end
185
195
 
186
- # rubocop:disable-next Metrics/ParameterLists
187
- def find_matching_overload(overloads, arg_types:, self_type:, instance_type:, type_vars:, block_required:,
188
- param_overrides:, strict:, alias_expander: nil)
189
- return [] if strict && arg_types.any? { |t| untyped_arg?(t) }
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
+
214
+ # The shared "no overload matched" answer; every consumer only reads the list.
215
+ NO_MATCH = [].freeze
216
+ private_constant :NO_MATCH
190
217
 
191
- predicate = lambda do |method_type|
192
- next false unless engages_block_shape?(method_type, block_required)
193
- next false if strict && !strictly_typed_params?(method_type, arg_types.size)
194
-
195
- matches?(
196
- method_type, arg_types,
197
- self_type: self_type, instance_type: instance_type,
198
- type_vars: type_vars, param_overrides: param_overrides,
199
- alias_expander: alias_expander
200
- )
201
- end
218
+ # `shared` is the keyword bundle `select_candidates` assembled (arg_types, self_type, instance_type,
219
+ # type_vars, block_required, param_overrides, alias_expander).
220
+ def find_matching_overload(overloads, shared, strict:)
221
+ arg_types = shared[:arg_types]
222
+ return NO_MATCH if strict && arg_types.any? { |t| imprecise_arg?(t) }
223
+
224
+ block_required = shared[:block_required]
202
225
  # Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
203
226
  # pass needs the full candidate list for the #521 union.
204
- return [overloads.find(&predicate)].compact if strict
227
+ if strict
228
+ found = overloads.find do |method_type|
229
+ engages_block_shape?(method_type, block_required) &&
230
+ strictly_typed_params?(method_type, arg_types.size) &&
231
+ matches?(method_type, shared, strict: true)
232
+ end
233
+ return found ? [found] : NO_MATCH
234
+ end
205
235
 
206
- overloads.select(&predicate)
236
+ overloads.select do |method_type|
237
+ engages_block_shape?(method_type, block_required) && matches?(method_type, shared)
238
+ end
207
239
  end
208
240
 
209
241
  # Whether the overload's block clause is compatible with the call site's block shape: a
@@ -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)
@@ -243,10 +284,19 @@ module Rigor
243
284
  params = positional_params_for(fun, arg_types.size)
244
285
  next false unless params.size == arg_types.size
245
286
 
246
- params.zip(arg_types).all? { |param, arg| alias_param_accepts?(param.type, arg) }
287
+ each_param_accepts?(params, arg_types) { |param, arg| alias_param_accepts?(param.type, arg) }
247
288
  end
248
289
  end
249
290
 
291
+ # `params.zip(arg_types).all? { |param, arg| ... }` without the pair arrays: a formal beyond the
292
+ # actuals meets `nil`, exactly as `zip` pads. Selection zips once per overload per pass, so the
293
+ # pairs were a top allocation site of dispatch (#775). The counter is a captured local, so the
294
+ # walk allocates nothing (a Range or an Enumerator would be one object per overload per pass).
295
+ def each_param_accepts?(params, arg_types)
296
+ index = -1
297
+ params.all? { |param| yield(param, arg_types[index += 1]) }
298
+ end
299
+
250
300
  # Checks the param's RBS type against an arg using alias-strict-arm matching. Optional / Union
251
301
  # wrappers are flattened; alias resolution is one level deep (the canonical core aliases all have
252
302
  # non-alias strict arms).
@@ -317,25 +367,17 @@ module Rigor
317
367
  end
318
368
  end
319
369
 
320
- def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:,
321
- alias_expander: nil)
370
+ # `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
371
+ # `strict:` is `false` (gradual), `true` (strict pass) or `:proven` (pass 0; see `find_proven_overload`).
372
+ def matches?(method_type, shared, strict: false)
322
373
  return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
323
374
 
375
+ arg_types = shared[:arg_types]
324
376
  fun = method_type.type
325
377
  return false unless arity_compatible?(fun, arg_types.size)
326
378
 
327
379
  params = positional_params_for(fun, arg_types.size)
328
- params.zip(arg_types).all? do |param, arg|
329
- accepts_param?(
330
- param,
331
- arg,
332
- self_type: self_type,
333
- instance_type: instance_type,
334
- type_vars: type_vars,
335
- param_overrides: param_overrides,
336
- alias_expander: alias_expander
337
- )
338
- end
380
+ each_param_accepts?(params, arg_types) { |param, arg| accepts_param?(param, arg, shared, strict) }
339
381
  end
340
382
 
341
383
  # Slice 4 phase 2c does not pass keyword arguments through the call site (caller passes only
@@ -376,8 +418,12 @@ module Rigor
376
418
  rest = fun.rest_positionals
377
419
  trailing = fun.trailing_positionals
378
420
 
421
+ optional_needed = [actual_count - required.size - trailing.size, 0].max
422
+ # Nothing to append: the declaration's own list is the answer, read-only (the common
423
+ # required-positionals-only overload -- no copy, #775).
424
+ return required if optional_needed.zero? && trailing.empty? && (rest.nil? || actual_count <= required.size)
425
+
379
426
  head = required.dup
380
- optional_needed = [actual_count - head.size - trailing.size, 0].max
381
427
  head.concat(optional.first(optional_needed))
382
428
 
383
429
  absorbed_by_rest = actual_count - head.size - trailing.size
@@ -387,14 +433,14 @@ module Rigor
387
433
  head
388
434
  end
389
435
 
390
- def accepts_param?(param, arg, self_type:, instance_type:, type_vars:, param_overrides:,
391
- alias_expander: nil)
392
- 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(
393
439
  param.type,
394
- self_type: self_type,
395
- instance_type: instance_type,
396
- type_vars: type_vars,
397
- 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]
398
444
  )
399
445
  # An `untyped` arg gradually accepts against every param, so a value-pinning param would be
400
446
  # "matched" with zero evidence and its value-precise return (`(nil) -> []`) would beat broader
@@ -404,6 +450,14 @@ module Rigor
404
450
  return false if untyped_arg?(arg) && value_pinning?(param_type)
405
451
 
406
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
+
407
461
  result.yes? || result.maybe?
408
462
  end
409
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
@@ -0,0 +1,82 @@
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
+ # ADR-109 — the interval a random draw over a literal range lands in. `Random.rand` and
10
+ # `Kernel#rand` advance a generator, so no call folds to a constant ({Builtins::RANDOM_CATALOG}
11
+ # says why); but a draw over `a..b` returns a value the range covers, and that is a bounded
12
+ # carrier of the range's own class, keeping its written end:
13
+ #
14
+ # rand(1..6) -> Integer[1..6]
15
+ # rand(1...6) -> Integer[1..5]
16
+ # rand(0.0...1.0) -> Float[0.0...1.0]
17
+ # Random.rand(1.0..2.0) -> Float[1.0..2.0]
18
+ #
19
+ # An empty range (`rand(5..1)` returns nil, `Random.rand(5..1)` raises), an unbounded one
20
+ # (`Errno::EDOM`) and a non-literal argument decline to the RBS envelope. The other argument
21
+ # shapes (`rand`, `rand(n)`, `rand(1.5)`) are deliberately NOT folded: the corpus uses them as its
22
+ # "unknown Integer / Float" oracle, and their intervals belong to a change that replaces that
23
+ # oracle first. The `Kernel` spelling is folded by {KernelDispatch#try_rand} behind ADR-91's
24
+ # ownership gate; this module owns `Singleton[Random]` and the shared interval reading.
25
+ module RandomFolding
26
+ module_function
27
+
28
+ def try_dispatch(context)
29
+ return nil unless SingletonFolding.receiver?(context.receiver, "Random")
30
+ return nil unless context.method_name == :rand
31
+
32
+ interval_for_args(context.args)
33
+ end
34
+
35
+ # The bounded carrier a one-argument draw over a literal range lands in, or `nil` for any other
36
+ # argument shape.
37
+ def interval_for_args(args)
38
+ return nil unless args.size == 1
39
+
40
+ arg = args.first
41
+ return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Range)
42
+
43
+ range_interval(arg.value)
44
+ end
45
+
46
+ def range_interval(range)
47
+ lower = range.begin
48
+ upper = range.end
49
+ return nil unless real_endpoint?(lower) && real_endpoint?(upper)
50
+
51
+ if lower.is_a?(Integer) && upper.is_a?(Integer)
52
+ integer_range_interval(lower, upper, range.exclude_end?)
53
+ else
54
+ float_range_interval(lower.to_f, upper.to_f, range.exclude_end?)
55
+ end
56
+ end
57
+
58
+ # A finite Integer or Float; a missing end is `Errno::EDOM` at run time.
59
+ def real_endpoint?(value)
60
+ case value
61
+ when Integer then true
62
+ when Float then value.finite?
63
+ else false
64
+ end
65
+ end
66
+
67
+ def integer_range_interval(lower, upper, exclusive)
68
+ upper -= 1 if exclusive
69
+ return nil if lower > upper
70
+
71
+ Type::Combinator.integer_range(lower, upper)
72
+ end
73
+
74
+ def float_range_interval(lower, upper, exclusive)
75
+ return nil if lower > (exclusive ? upper.prev_float : upper)
76
+
77
+ Type::Combinator.float_range(lower, upper, exclude_end: exclusive)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end