rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -44,11 +44,23 @@ module Rigor
44
44
  MATH_BINARY = Set[:atan2, :hypot, :ldexp].freeze
45
45
  MATH_TUPLE_UNARY = Set[:frexp, :lgamma].freeze
46
46
 
47
- private_constant :MATH_UNARY, :MATH_BINARY, :MATH_TUPLE_UNARY
47
+ # ADR-109 — the monotone non-decreasing functions, each with the inclusive lower edge of its
48
+ # domain (`nil` for every real). Over a bounded argument the image is the closed range between the
49
+ # images of the bounds: `Math.sqrt(Integer[0..])` is `Float[0.0..]`, `Math.exp(Float[..0.0])` is
50
+ # `Float[0.0..1.0]`. A bound below the domain edge raises at run time for the value that reaches
51
+ # it, so the fold declines. Not listed: the non-monotone functions (`sin`, `cos`, `tan`, `gamma`,
52
+ # `erfc` decreases, `acos` decreases) and the ones whose image needs a sign split.
53
+ MATH_MONOTONE_DOMAIN = {
54
+ sqrt: 0.0, cbrt: nil, exp: nil, expm1: nil,
55
+ log: 0.0, log2: 0.0, log10: 0.0, log1p: -1.0,
56
+ atan: nil, sinh: nil, tanh: nil, asinh: nil, erf: nil
57
+ }.freeze
58
+
59
+ private_constant :MATH_UNARY, :MATH_BINARY, :MATH_TUPLE_UNARY, :MATH_MONOTONE_DOMAIN
48
60
 
49
61
  module_function
50
62
 
51
- # @return [Rigor::Type, nil] folded result, or nil to defer.
63
+ # @return folded result, or nil to defer.
52
64
  def try_dispatch(context)
53
65
  receiver = context.receiver
54
66
  method_name = context.method_name
@@ -77,13 +89,38 @@ module Rigor
77
89
  return nil unless args.size == 1
78
90
 
79
91
  x = numeric_constant(args.first)
80
- return nil if x.nil?
92
+ return fold_monotone_range(method_name, args.first) if x.nil?
81
93
 
82
94
  fold_float_result(Math.public_send(method_name, x))
83
95
  rescue Math::DomainError, RangeError
84
96
  nil
85
97
  end
86
98
 
99
+ # The bounded-argument arm of {fold_unary} / {fold_log}: an `IntegerRange` or `FloatRange`
100
+ # argument to a monotone function folds to the range between the images of its bounds.
101
+ def fold_monotone_range(method_name, arg)
102
+ return nil unless MATH_MONOTONE_DOMAIN.key?(method_name)
103
+
104
+ lower, upper = range_bounds(arg)
105
+ return nil if lower.nil?
106
+
107
+ edge = MATH_MONOTONE_DOMAIN[method_name]
108
+ return nil if edge && lower < edge
109
+
110
+ Type::Combinator.float_range(Math.public_send(method_name, lower), Math.public_send(method_name, upper))
111
+ rescue Math::DomainError, RangeError, ArgumentError
112
+ nil
113
+ end
114
+
115
+ # `[lower, upper]` as doubles for a bounded numeric carrier, `nil` for anything else. An
116
+ # exclusive Float end reads as its canonical closed bound, one double below.
117
+ def range_bounds(arg)
118
+ case arg
119
+ when Type::FloatRange then [arg.min, arg.canonical_max]
120
+ when Type::IntegerRange then [arg.lower.to_f, arg.upper.to_f]
121
+ end
122
+ end
123
+
87
124
  def fold_binary(method_name, args)
88
125
  return nil unless args.size == 2
89
126
 
@@ -101,6 +138,7 @@ module Rigor
101
138
  return nil unless [1, 2].include?(args.size)
102
139
 
103
140
  values = args.map { |a| numeric_constant(a) }
141
+ return fold_monotone_range(:log, args.first) if args.size == 1 && values.first.nil?
104
142
  return nil if values.any?(&:nil?)
105
143
 
106
144
  fold_float_result(Math.log(*values))
@@ -29,11 +29,8 @@ module Rigor
29
29
  # `.method("name")` with a precisely-known Symbol / String argument. Declines on every other shape
30
30
  # so the RBS tier still answers `Method` for non-folding cases.
31
31
  #
32
- # @param receiver [Rigor::Type] caller's receiver
33
- # @param method_name [Symbol] the method being dispatched on `receiver` — only `:method` triggers
34
- # the fold.
35
- # @param args [Array<Rigor::Type>] caller's argument types in order. Only the single-argument case
36
- # matches; other arities decline.
32
+ # Reads `context.receiver`, `context.method_name` (only `:method` triggers the fold) and `context.args`
33
+ # (only the single-argument case matches; other arities decline).
37
34
  def try_dispatch(context)
38
35
  receiver = context.receiver
39
36
  method_name = context.method_name
@@ -64,21 +64,20 @@ module Rigor
64
64
  }.freeze
65
65
  private_constant :ALIAS_STRICT_NOMINALS
66
66
 
67
- # @param method_definition [RBS::Definition::Method]
68
- # @param arg_types [Array<Rigor::Type>] caller-provided types in positional order. Empty when
67
+ # @param arg_types — caller-provided types in positional order. Empty when
69
68
  # 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
69
+ # @param self_type substitute for `Bases::Self`.
70
+ # @param instance_type substitute for `Bases::Instance`.
71
+ # @param type_vars substitution map for class-level type variables
73
72
  # (Slice 4 phase 2d). The selector threads it through to {RbsTypeTranslator} so parameter types
74
73
  # like `::Array[Elem]` substitute Elem before the accepts check, instead of degrading the param
75
74
  # to `Array[Dynamic[Top]]`.
76
- # @param block_required [Boolean] when `true`, only overloads that declare a block clause are
75
+ # @param block_required when `true`, only overloads that declare a block clause are
77
76
  # considered (Slice 6 phase C sub-phase 1). The fallback also prefers a block-bearing overload
78
77
  # over `method_types.first`. When `false` (the Slice 4 phase 2c default) the selector behaves
79
78
  # exactly as before: `find` over arity-compatible overloads, falling back to the first
80
79
  # declaration.
81
- # @return [RBS::MethodType, nil] the chosen overload, or nil when the definition has no method
80
+ # @return the chosen overload, or nil when the definition has no method
82
81
  # types at all.
83
82
  def select(method_definition, **) = select_candidates(method_definition, **).first
84
83
 
@@ -91,7 +90,7 @@ module Rigor
91
90
  # alias-resolved, typed-gradual, arity fallback) still yields exactly one candidate, so `select`
92
91
  # keeps its historical single answer.
93
92
  #
94
- # @return [Array<RBS::MethodType>] matching overloads; empty when the definition declares none.
93
+ # @return matching overloads; empty when the definition declares none.
95
94
  def select_candidates(method_definition, arg_types:, self_type:, instance_type:, type_vars: {},
96
95
  block_required: false, environment: nil)
97
96
  overloads = method_definition.method_types
@@ -108,17 +107,14 @@ module Rigor
108
107
  # an arbitrary sibling-class arm that only wins by overload-list position.
109
108
  overloads = ReceiverAffinity.reorder(overloads, self_type: self_type, environment: environment)
110
109
 
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
110
+ # One keyword bundle for the pass pipeline (see `run_selection_passes`); a block retry rebuilds it
111
+ # with the block flag cleared. Built once and passed positionally -- the previous lambda plus a
112
+ # `**shared` splat per pass allocated three objects per selection (#775).
113
+ shared = { arg_types: arg_types, self_type: self_type, instance_type: instance_type,
114
+ type_vars: type_vars, block_required: block_required, param_overrides: param_overrides,
115
+ alias_expander: environment&.rbs_loader }
120
116
 
121
- matches = passes.call(block_required)
117
+ matches = run_selection_passes(overloads, shared)
122
118
  return matches unless matches.empty?
123
119
 
124
120
  # A block at the call site that no block-declaring overload matched: Ruby ignores a block handed
@@ -127,7 +123,7 @@ module Rigor
127
123
  # `define_command(:x) do … end` against `def define_command: (Symbol) -> Symbol`) degraded to
128
124
  # `Dynamic[Top]` — and on a self-send suppressed the whole method's return type.
129
125
  if block_required
130
- matches = passes.call(false)
126
+ matches = run_selection_passes(overloads, shared.merge(block_required: false))
131
127
  return matches unless matches.empty?
132
128
  end
133
129
 
@@ -165,7 +161,7 @@ module Rigor
165
161
  # `shared` is the caller-assembled keyword bundle for `find_matching_overload` — hash-shaped
166
162
  # because the pass pipeline forwards it twice and RuboCop's parameter-list budget is real.
167
163
  def run_selection_passes(overloads, shared)
168
- strict = find_matching_overload(overloads, **shared, strict: true)
164
+ strict = find_matching_overload(overloads, shared, strict: true)
169
165
  return strict unless strict.empty?
170
166
 
171
167
  alias_hit = find_matching_overload_via_aliases(
@@ -177,33 +173,37 @@ module Rigor
177
173
  # match keeps its historical single-winner contract. With a `Dynamic[Top]` argument in play the
178
174
  # matches are indistinguishable by types — position alone would pick — so ALL of them come back
179
175
  # and the dispatch layer unions their returns (#521).
180
- matches = find_matching_overload(overloads, **shared, strict: false)
176
+ matches = find_matching_overload(overloads, shared, strict: false)
181
177
  return matches.first(1) unless shared[:arg_types].any? { |t| untyped_arg?(t) }
182
178
 
183
179
  matches
184
180
  end
185
181
 
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) }
182
+ # The shared "no overload matched" answer; every consumer only reads the list.
183
+ NO_MATCH = [].freeze
184
+ private_constant :NO_MATCH
190
185
 
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)
186
+ # `shared` is the keyword bundle `select_candidates` assembled (arg_types, self_type, instance_type,
187
+ # type_vars, block_required, param_overrides, alias_expander).
188
+ def find_matching_overload(overloads, shared, strict:)
189
+ arg_types = shared[:arg_types]
190
+ return NO_MATCH if strict && arg_types.any? { |t| untyped_arg?(t) }
194
191
 
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
192
+ block_required = shared[:block_required]
202
193
  # Strict keeps its historical first-match short-circuit (a dispatch hot path); the gradual
203
194
  # pass needs the full candidate list for the #521 union.
204
- return [overloads.find(&predicate)].compact if strict
195
+ if strict
196
+ found = overloads.find do |method_type|
197
+ engages_block_shape?(method_type, block_required) &&
198
+ strictly_typed_params?(method_type, arg_types.size) &&
199
+ matches?(method_type, shared)
200
+ end
201
+ return found ? [found] : NO_MATCH
202
+ end
205
203
 
206
- overloads.select(&predicate)
204
+ overloads.select do |method_type|
205
+ engages_block_shape?(method_type, block_required) && matches?(method_type, shared)
206
+ end
207
207
  end
208
208
 
209
209
  # Whether the overload's block clause is compatible with the call site's block shape: a
@@ -243,10 +243,19 @@ module Rigor
243
243
  params = positional_params_for(fun, arg_types.size)
244
244
  next false unless params.size == arg_types.size
245
245
 
246
- params.zip(arg_types).all? { |param, arg| alias_param_accepts?(param.type, arg) }
246
+ each_param_accepts?(params, arg_types) { |param, arg| alias_param_accepts?(param.type, arg) }
247
247
  end
248
248
  end
249
249
 
250
+ # `params.zip(arg_types).all? { |param, arg| ... }` without the pair arrays: a formal beyond the
251
+ # actuals meets `nil`, exactly as `zip` pads. Selection zips once per overload per pass, so the
252
+ # pairs were a top allocation site of dispatch (#775). The counter is a captured local, so the
253
+ # walk allocates nothing (a Range or an Enumerator would be one object per overload per pass).
254
+ def each_param_accepts?(params, arg_types)
255
+ index = -1
256
+ params.all? { |param| yield(param, arg_types[index += 1]) }
257
+ end
258
+
250
259
  # Checks the param's RBS type against an arg using alias-strict-arm matching. Optional / Union
251
260
  # wrappers are flattened; alias resolution is one level deep (the canonical core aliases all have
252
261
  # non-alias strict arms).
@@ -317,23 +326,24 @@ module Rigor
317
326
  end
318
327
  end
319
328
 
320
- def matches?(method_type, arg_types, self_type:, instance_type:, type_vars:, param_overrides:,
321
- alias_expander: nil)
329
+ # `shared` is the keyword bundle `select_candidates` assembled (see `find_matching_overload`).
330
+ def matches?(method_type, shared)
322
331
  return false if method_type.respond_to?(:type_params) && rejects_keyword_required?(method_type)
323
332
 
333
+ arg_types = shared[:arg_types]
324
334
  fun = method_type.type
325
335
  return false unless arity_compatible?(fun, arg_types.size)
326
336
 
327
337
  params = positional_params_for(fun, arg_types.size)
328
- params.zip(arg_types).all? do |param, arg|
338
+ each_param_accepts?(params, arg_types) do |param, arg|
329
339
  accepts_param?(
330
340
  param,
331
341
  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
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]
337
347
  )
338
348
  end
339
349
  end
@@ -376,8 +386,12 @@ module Rigor
376
386
  rest = fun.rest_positionals
377
387
  trailing = fun.trailing_positionals
378
388
 
389
+ optional_needed = [actual_count - required.size - trailing.size, 0].max
390
+ # Nothing to append: the declaration's own list is the answer, read-only (the common
391
+ # required-positionals-only overload -- no copy, #775).
392
+ return required if optional_needed.zero? && trailing.empty? && (rest.nil? || actual_count <= required.size)
393
+
379
394
  head = required.dup
380
- optional_needed = [actual_count - head.size - trailing.size, 0].max
381
395
  head.concat(optional.first(optional_needed))
382
396
 
383
397
  absorbed_by_rest = actual_count - head.size - trailing.size
@@ -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
@@ -3,6 +3,7 @@
3
3
  require_relative "../../reflection"
4
4
  require_relative "../../type"
5
5
  require_relative "../../rbs_extended"
6
+ require_relative "../range_constant"
6
7
  require_relative "../rbs_type_translator"
7
8
  require_relative "../void_origin"
8
9
  require_relative "../optimistic_origin"
@@ -71,31 +72,38 @@ module Rigor
71
72
  private_constant :EMPTY_TYPE_VARS
72
73
  EMPTY_TYPE_PARAM_NAMES = [].freeze
73
74
  private_constant :EMPTY_TYPE_PARAM_NAMES
75
+ NO_BINDING = [nil, nil].freeze
76
+ private_constant :NO_BINDING
74
77
 
75
- # @param receiver [Rigor::Type]
76
- # @param method_name [Symbol]
77
- # @param args [Array<Rigor::Type>]
78
- # @param environment [Rigor::Environment]
79
- # @param block_type [Rigor::Type, nil] inferred block return type, propagated from
80
- # `MethodDispatcher.dispatch`. When non-nil, the selector prefers a block-bearing overload and
81
- # binds the method-level type parameter that the block's return type references to `block_type`
82
- # (Slice 6 phase C sub-phase 2).
83
- # @param self_type_override [Rigor::Type, nil] when set, the substitution for `Bases::Self` in the
84
- # method's return type. Used by `MethodDispatcher#try_user_class_fallback` to preserve the
85
- # ORIGINAL receiver as the substitute for `self` even though the dispatch is routed through
86
- # `Nominal[Object]` — so that `Bundler::URI::Generic.dup` (which resolves through the `Object`
87
- # fallback because `Bundler::URI::Generic` lacks RBS) returns `Bundler::URI::Generic` per
88
- # `Kernel#dup: () -> self` rather than `Object`. Defaults to nil (compute self from the resolved
89
- # class_name as before).
90
- # @param public_only [Boolean] when true, a method whose RBS accessibility is `:private` does not
91
- # resolve (the call yields `nil`, i.e. "no rule"). Set by the explicit-non-`self`-receiver
92
- # user-class fallback so a call like `Favourite.select(...)` does not adopt the private
93
- # `Kernel#select` signature.
94
- # @return [Rigor::Type, nil] inferred return type, or `nil` when no rule resolves (no class name,
78
+ # Both spellings a resolved `RBS::Types::ClassInstance#name` — or a `Nominal#class_name` built
79
+ # from one — may carry for `Range`; core signatures absolutise, but a plugin-contributed one
80
+ # need not.
81
+ RANGE_TYPE_NAMES = ["::Range", "Range"].freeze
82
+ private_constant :RANGE_TYPE_NAMES
83
+
84
+ # Four fields of `context` shape the answer beyond receiver, method name and arguments.
85
+ #
86
+ # `block_type` is the inferred block return type propagated from `MethodDispatcher.dispatch`; when
87
+ # non-nil, the selector prefers a block-bearing overload and binds the method-level type parameter
88
+ # that the block's return type references to it (Slice 6 phase C sub-phase 2).
89
+ #
90
+ # `self_type_override`, when set, is the substitution for `Bases::Self` in the method's return type.
91
+ # `MethodDispatcher#try_user_class_fallback` uses it to preserve the ORIGINAL receiver as the
92
+ # substitute for `self` even though the dispatch is routed through `Nominal[Object]` — so that
93
+ # `Bundler::URI::Generic.dup` (which resolves through the `Object` fallback because
94
+ # `Bundler::URI::Generic` lacks RBS) returns `Bundler::URI::Generic` per `Kernel#dup: () -> self`
95
+ # rather than `Object`. Nil computes self from the resolved class_name as before.
96
+ #
97
+ # `public_only`, when true, keeps a method whose RBS accessibility is `:private` from resolving (the
98
+ # call yields `nil`, i.e. "no rule"). Set by the explicit-non-`self`-receiver user-class fallback so
99
+ # a call like `Favourite.select(...)` does not adopt the private `Kernel#select` signature.
100
+ #
101
+ # `scope`, when supplied, enables ADR-43 RBS-complete-ancestor resolution against
102
+ # `ALLOWED_RBS_COMPLETE_ANCESTORS`; `nil` keeps inherited calls unresolved (`Dynamic[Top]`) — the
103
+ # FP-safe default for open hierarchies (`< ActionController::Base`, …).
104
+ #
105
+ # @return inferred return type, or `nil` when no rule resolves (no class name,
95
106
  # no method, dispatch on a Top/Dynamic[Top] receiver, etc.).
96
- # @param scope [Rigor::Scope, nil] when supplied, enables ADR-43 RBS-complete-ancestor resolution
97
- # against `ALLOWED_RBS_COMPLETE_ANCESTORS`. `nil` keeps inherited calls unresolved
98
- # (`Dynamic[Top]`) — the FP-safe default for open hierarchies (`< ActionController::Base`, …).
99
107
  def try_dispatch(context)
100
108
  environment = context.environment
101
109
  return nil if environment.nil?
@@ -130,7 +138,7 @@ module Rigor
130
138
  #
131
139
  # This deliberately does NOT differentiate "no overload had a block" from "the block is untyped";
132
140
  # the binder treats both the same way (every parameter defaults to `Dynamic[Top]`).
133
- # @return [Array<Rigor::Type>] positional block parameter types.
141
+ # @return positional block parameter types.
134
142
  def block_param_types(context)
135
143
  environment = context.environment
136
144
  return [] if environment.nil?
@@ -181,6 +189,15 @@ module Rigor
181
189
  method_definition = lookup_method(environment, class_name, kind, method_name, scope)
182
190
  return nil unless method_definition
183
191
  return nil if public_only && method_private?(method_definition)
192
+ # Issue #823 — a declaration that states the member's presence and parameters but not its
193
+ # return (`%a{rigor:v1:inferred-return}`, written by `rigor-rbs-inline` on every type slot it
194
+ # DEFAULTED). Declining here is the whole mechanism: this tier's product IS the return type, so
195
+ # withholding it routes the call down the same tiers an undeclared method takes and the body
196
+ # gets typed. Everything the declaration DOES say survives, because the rules that read it —
197
+ # `call.undefined-method`, `call.wrong-arity`, argument-type checking — look the method up in
198
+ # the environment themselves rather than reading this tier's answer.
199
+ return nil if RbsExtended.inferred_return?(method_definition)
200
+ return SPACESHIP_ENVELOPE if inherited_identity_spaceship?(method_definition, class_name, kind, method_name)
184
201
 
185
202
  type_vars = build_type_vars(environment, class_name, receiver_args)
186
203
  translate_return_type(
@@ -202,6 +219,46 @@ module Rigor
202
219
  nil
203
220
  end
204
221
 
222
+ # Issue #661 — `Kernel#<=>: (untyped other) -> 0?` is the IDENTITY comparison: `0` when the two
223
+ # are the same object, `nil` otherwise, and correct as written for a bare `Object`. Reached by
224
+ # inheritance it stops being a statement about the call. `1.day <=> 2.days` typed as `0?`, and a
225
+ # `Money` whose signature says `include Comparable` — whose whole contract is that the includer
226
+ # defines `<=>` — typed the same, because neither declares `<=>` of its own and every class
227
+ # inherits Kernel's.
228
+ #
229
+ # `0?` is a VALUE claim, so the cost is not confined to the expression: narrowing
230
+ # `n = a <=> b; n.negative? if n` reads the truthy arm as the literal `0`, folds `0.negative?`
231
+ # to false, and answers `bot` for a branch the runtime takes on every ordered pair. That is one
232
+ # `clause.unreachable` away from a false positive on correct code.
233
+ #
234
+ # Widened rather than declined: `Integer?` is the envelope Ruby's own `<=>` convention states and
235
+ # the one upstream rbs writes wherever a class DOES declare the operator (`Array`, `Module`,
236
+ # `Complex`). It is a supertype of `0?`, so this only ever removes a conclusion — and it removes
237
+ # exactly the conclusions that rested on the receiver not overriding an operator its signature
238
+ # was never required to mention (ADR-5: a partially-declared class is not a closed world).
239
+ #
240
+ # Bounded to the three classes that OWN the identity comparison, where the claim is the truth
241
+ # about the receiver rather than an artifact of inheritance, and to `<=>` alone — the general
242
+ # question of what an inherited Object/Kernel signature may assert about a subclass is much
243
+ # larger, and `to_s` / `hash` / `inspect` do not carry a value-precise return to lose.
244
+ SPACESHIP_IDENTITY_OWNERS = %w[Kernel Object BasicObject].to_set.freeze
245
+ private_constant :SPACESHIP_IDENTITY_OWNERS
246
+
247
+ SPACESHIP_ENVELOPE = Type::Combinator.union(
248
+ Type::Combinator.nominal_of("Integer"),
249
+ Type::Combinator.constant_of(nil)
250
+ ).freeze
251
+ private_constant :SPACESHIP_ENVELOPE
252
+
253
+ def inherited_identity_spaceship?(method_definition, class_name, kind, method_name)
254
+ return false unless method_name == :<=>
255
+ return false unless kind == :instance
256
+ return false if SPACESHIP_IDENTITY_OWNERS.include?(class_name.to_s.delete_prefix("::"))
257
+ return false unless method_definition.respond_to?(:defined_in)
258
+
259
+ SPACESHIP_IDENTITY_OWNERS.include?(method_definition.defined_in.to_s.delete_prefix("::"))
260
+ end
261
+
205
262
  # Maps a Rigor::Type receiver to a `[class_name, kind, type_args]` triple where `kind` is either
206
263
  # `:instance` or `:singleton` and `type_args` carries the receiver's generic instantiation (empty
207
264
  # for raw or singleton receivers, since `Singleton[Foo]` carries no generic args today). Returns
@@ -237,6 +294,13 @@ module Rigor
237
294
  # whole dispatch to Dynamic). The refinement-aware promotions (`String#upcase` →
238
295
  # `uppercase-string`, …) run in their own catalog tier ABOVE this one, so they still win.
239
296
  receiver_descriptor(receiver.base)
297
+ when Type::FloatRange
298
+ # ADR-109 WD4 — a bounded Float is a Float for every method the fold tiers do not own.
299
+ ["Float", :instance, []]
300
+ when Type::IntegerRange
301
+ # #842 — a bounded Integer is an Integer for every method the fold tiers
302
+ # (ConstantFolding, ShapeDispatch#dispatch_integer_range) do not own.
303
+ ["Integer", :instance, []]
240
304
  when Type::Dynamic
241
305
  receiver_descriptor(receiver.static_facet)
242
306
  end
@@ -331,15 +395,20 @@ module Rigor
331
395
  # disagree -- in both cases free variables in the method's return type degrade to `Dynamic[Top]`
332
396
  # per the translator's contract.
333
397
  def build_type_vars(environment, class_name, receiver_args)
334
- return {} if receiver_args.empty?
398
+ return NO_TYPE_VARS if receiver_args.empty?
335
399
 
336
400
  param_names = Rigor::Reflection.class_type_param_names(class_name, environment: environment)
337
- return {} if param_names.empty?
338
- return {} if param_names.size != receiver_args.size
401
+ return NO_TYPE_VARS if param_names.empty?
402
+ return NO_TYPE_VARS if param_names.size != receiver_args.size
339
403
 
340
404
  param_names.zip(receiver_args).to_h
341
405
  end
342
406
 
407
+ # The shared empty substitution map: most receivers carry no type arguments, and the translator
408
+ # only ever reads the map (#775).
409
+ NO_TYPE_VARS = {}.freeze
410
+ private_constant :NO_TYPE_VARS
411
+
343
412
  # rubocop:disable Metrics/ParameterLists
344
413
  def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:,
345
414
  method_name: nil, environment: nil, self_type_override: nil,
@@ -505,7 +574,8 @@ module Rigor
505
574
  # Envelope, deliberately the narrowest sound shape:
506
575
  #
507
576
  # * only a positional parameter (required or optional) whose declared type is EXACTLY
508
- # `RBS::Types::Variable` no container walk, so `(Array[T]) -> T` still degrades;
577
+ # `RBS::Types::Variable`, plus the one container shape issue #834 admits (see
578
+ # {#range_element_binding}) — no general container walk, so `(Array[T]) -> T` still degrades;
509
579
  # * only names the SELECTED overload declares in its own `type_params` (a class-level variable
510
580
  # keeps its receiver-derived binding);
511
581
  # * an existing key wins, so the receiver and the block return type both outrank an argument;
@@ -534,14 +604,27 @@ module Rigor
534
604
  positionals.zip(args).each_with_object({}) do |(param, arg), bindings|
535
605
  next if arg.nil?
536
606
 
537
- name = variable_param_name(param, declared, type_vars)
607
+ name, bound = param_binding(param, arg, declared, type_vars)
538
608
  next if name.nil?
539
- next if no_static_evidence?(arg)
540
609
 
541
- bindings[name] = bindings.key?(name) ? Type::Combinator.union(bindings[name], arg) : arg
610
+ bindings[name] = bindings.key?(name) ? Type::Combinator.union(bindings[name], bound) : bound
542
611
  end
543
612
  end
544
613
 
614
+ # The `(variable name, bound type)` a positional parameter contributes, or {NO_BINDING} when it
615
+ # contributes none. The bare-variable shape is tried first because it is the overwhelmingly
616
+ # common one; the `Range[A]` shape is reached only when the parameter is not a bare variable.
617
+ def param_binding(param, arg, declared, type_vars)
618
+ name = variable_param_name(param, declared, type_vars)
619
+ unless name.nil?
620
+ return NO_BINDING if no_static_evidence?(arg)
621
+
622
+ return [name, arg]
623
+ end
624
+
625
+ range_element_binding(param, arg, declared, type_vars)
626
+ end
627
+
545
628
  def declared_type_param_names(method_type)
546
629
  params = method_type.respond_to?(:type_params) ? method_type.type_params : nil
547
630
  return EMPTY_TYPE_PARAM_NAMES if params.nil? || params.empty?
@@ -562,6 +645,57 @@ module Rigor
562
645
  name
563
646
  end
564
647
 
648
+ # Issues #834 / #862 — the one container position the envelope admits: a `Range[A]` parameter
649
+ # against a Range argument that names its own element. `Comparable#clamp: [A] (Range[A]) ->
650
+ # (self | A)` otherwise leaves `A` unbound and `i.clamp(1..9)` answers `Dynamic[top] | Integer`,
651
+ # even though the argument names the element outright. It stays Range-only because the
652
+ # justification does not generalise: a Range is immutable and its element type is fixed at
653
+ # construction, whereas an `Array[T]` argument's carrier may have been widened long before the
654
+ # call reached here.
655
+ def range_element_binding(param, arg, declared, type_vars)
656
+ declared_type = param.type
657
+ return NO_BINDING unless declared_type.is_a?(RBS::Types::ClassInstance)
658
+ return NO_BINDING unless RANGE_TYPE_NAMES.include?(declared_type.name.to_s)
659
+ return NO_BINDING unless declared_type.args.size == 1
660
+
661
+ element = declared_type.args.first
662
+ return NO_BINDING unless element.is_a?(RBS::Types::Variable)
663
+ return NO_BINDING unless declared.include?(element.name)
664
+ return NO_BINDING if type_vars.key?(element.name)
665
+
666
+ bound = range_argument_element(arg)
667
+ bound.nil? ? NO_BINDING : [element.name, bound]
668
+ end
669
+
670
+ # The element a Range argument names, or nil when it names none.
671
+ #
672
+ # Two carriers qualify. A `Constant<Range>` contributes its endpoints lifted to their classes
673
+ # ({RangeConstant.element_type}), not the two values, so `clamp(1..9)` answers `Integer` rather
674
+ # than a `1 | 9` the runtime contradicts for every receiver already inside the bracket. A
675
+ # `Nominal[Range, [T]]` (issue #862 — `1..ARGV.size` has no literal endpoint to read, so
676
+ # `ExpressionTyper` hands back the nominal carrier) contributes `T` itself.
677
+ #
678
+ # A nominal carrier qualifies only when `T` is a Nominal or a union of Nominals. `untyped`, a
679
+ # `Dynamic`, or a still-unbound type variable would launder an unknown into the result — the
680
+ # caller's `self | A` would read as `self | unknown` while claiming to be inferred — so those
681
+ # keep degrading as an unbound variable does.
682
+ def range_argument_element(arg)
683
+ return RangeConstant.element_type(arg) unless arg.is_a?(Type::Nominal)
684
+ return nil unless RANGE_TYPE_NAMES.include?(arg.class_name)
685
+ return nil unless arg.type_args.size == 1
686
+
687
+ element = arg.type_args.first
688
+ nominal_only?(element) ? element : nil
689
+ end
690
+
691
+ def nominal_only?(type)
692
+ case type
693
+ when Type::Nominal then true
694
+ when Type::Union then type.members.all?(Type::Nominal)
695
+ else false
696
+ end
697
+ end
698
+
565
699
  # `Dynamic[top]` is the engine's "we could not tell" answer, so binding a variable to it would
566
700
  # dress up an absence of evidence as an inference. The variable stays unbound and degrades as it
567
701
  # did before, which is the same value anyway.