rigortype 0.3.7 → 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 (450) 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 +96 -15
  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 +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  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 +69 -13
  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 +100 -29
  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 +316 -40
  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 +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  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 +166 -30
  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 +70 -0
  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_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -214,7 +214,6 @@ module Rigor
214
214
  # survives the call.
215
215
  CONSTANT_SELF_RETURNERS = %i[freeze itself dup clone].to_set.freeze
216
216
 
217
- # @return [Rigor::Type::Constant, Rigor::Type::Union, Rigor::Type::IntegerRange, nil]
218
217
  def try_dispatch(context)
219
218
  receiver = context.receiver
220
219
  method_name = context.method_name
@@ -234,15 +233,8 @@ module Rigor
234
233
  first_arg = args.first
235
234
  return nil unless first_arg.is_a?(Type::Constant) && first_arg.value.is_a?(Symbol)
236
235
  end
237
- self_return = try_fold_self_return(receiver, method_name, args)
238
- return self_return if self_return
239
-
240
- # v0.0.7 — `String#%` against a `Tuple` / `HashShape` argument runs Ruby's format-string engine
241
- # when both sides are statically constant. The standard `numeric_set_of` path bails on Tuple /
242
- # HashShape arguments because they are not scalar-Constant carriers, so the special-case sits
243
- # ahead of the numeric path.
244
- format_lift = try_fold_string_format(receiver, method_name, args)
245
- return format_lift if format_lift
236
+ ahead = try_fold_ahead_of_numeric_path(receiver, method_name, args)
237
+ return ahead if ahead
246
238
 
247
239
  receiver_set = numeric_set_of(receiver)
248
240
  return nil unless receiver_set
@@ -253,6 +245,20 @@ module Rigor
253
245
  dispatch_by_arity(receiver_set, method_name, arg_sets)
254
246
  end
255
247
 
248
+ # The folds that must answer before `numeric_set_of` gets a say, because the receiver or an
249
+ # argument is a carrier it declines:
250
+ #
251
+ # - the self-returners, whose receiver may be any `Constant`;
252
+ # - v0.0.7's `String#%` against a `Tuple` / `HashShape` argument, neither a scalar-Constant
253
+ # carrier, which runs Ruby's own format-string engine;
254
+ # - #861's `clamp` on a plain `Integer` / `Float` receiver, a `Nominal` that `numeric_set_of`
255
+ # declines outright.
256
+ def try_fold_ahead_of_numeric_path(receiver, method_name, args)
257
+ try_fold_self_return(receiver, method_name, args) ||
258
+ try_fold_string_format(receiver, method_name, args) ||
259
+ try_fold_unbounded_clamp(receiver, method_name, args)
260
+ end
261
+
256
262
  # `freeze` / `itself` / `dup` / `clone` on a value-pinned receiver return the receiver carrier.
257
263
  #
258
264
  # This sits AHEAD of the catalogue path and deliberately bypasses its purity gate, because the gate
@@ -344,11 +350,13 @@ module Rigor
344
350
  return type.members.map(&:value) if type.members.all?(Type::Constant)
345
351
 
346
352
  # A union that mixes `Constant<Integer>` and `IntegerRange` members (e.g. an accumulator's
347
- # running fixpoint assumption `1 | int<1, 6>`) folds as the bounding interval. The
353
+ # running fixpoint assumption `1 | Integer[1..6]`) folds as the bounding interval. The
348
354
  # range-arithmetic path (`try_fold_binary_range`) then keeps the result an `IntegerRange`
349
355
  # instead of bailing to Dynamic.
350
356
  union_integer_bounds(type)
351
- when Type::IntegerRange then type
357
+ # ADR-109 — a bounded Float folds through its own arms (`try_fold_unary_float_range`,
358
+ # `try_fold_ternary_float_range`, `try_fold_clamp_range`); every other arm declines it.
359
+ when Type::IntegerRange, Type::FloatRange then type
352
360
  end
353
361
  end
354
362
 
@@ -390,11 +398,15 @@ module Rigor
390
398
  case set
391
399
  when Array then try_fold_unary_set(set, method_name)
392
400
  when Type::IntegerRange then try_fold_unary_range(set, method_name)
401
+ when Type::FloatRange then try_fold_unary_float_range(set, method_name)
393
402
  end
394
403
  end
395
404
 
396
405
  def try_fold_binary(left, method_name, right)
397
406
  return try_fold_divmod(left, right) if method_name == :divmod
407
+ return try_fold_clamp_range(left, right) if method_name == :clamp && bounded_range?(left)
408
+ # ADR-109 — no Float-range arithmetic yet; a bounded Float on either side declines here.
409
+ return nil if left.is_a?(Type::FloatRange) || right.is_a?(Type::FloatRange)
398
410
 
399
411
  if left.is_a?(Type::IntegerRange) || right.is_a?(Type::IntegerRange)
400
412
  try_fold_binary_range(left, method_name, right)
@@ -403,6 +415,133 @@ module Rigor
403
415
  end
404
416
  end
405
417
 
418
+ def bounded_range?(type)
419
+ type.is_a?(Type::IntegerRange) || type.is_a?(Type::FloatRange)
420
+ end
421
+
422
+ # #834 — `clamp(range)` on a bounded receiver, the one-argument twin of `range_clamp` /
423
+ # `float_range_clamp`. An exclusive end raises at run time (`cannot clamp with an exclusive
424
+ # range`), so it declines rather than folding a call that does not return; a missing end keeps
425
+ # the receiver's own bound on that side. The bracket's endpoints must be literals of the
426
+ # receiver's class.
427
+ def try_fold_clamp_range(receiver, right)
428
+ bracket = clamp_bracket(right)
429
+ return nil if bracket.nil?
430
+
431
+ lower, upper = bracket
432
+ case receiver
433
+ when Type::IntegerRange
434
+ return nil unless clamp_endpoints_are?(Integer, lower, upper)
435
+
436
+ range_clamp(receiver, lower || receiver.lower, upper || receiver.upper)
437
+ when Type::FloatRange
438
+ return nil unless clamp_endpoints_are?(Numeric, lower, upper)
439
+
440
+ float_range_clamp(receiver, lower, upper)
441
+ end
442
+ end
443
+
444
+ # `[lower, upper]` of a single literal, closed, non-reversed `Range` argument (either side may be
445
+ # `nil` for an open range); `nil` for any other shape.
446
+ def clamp_bracket(right)
447
+ return nil unless right.is_a?(Array) && right.size == 1 && right.first.is_a?(Range)
448
+
449
+ bracket = right.first
450
+ return nil if bracket.exclude_end? && !bracket.end.nil?
451
+
452
+ lower = bracket.begin
453
+ upper = bracket.end
454
+ return nil if lower && upper && lower > upper
455
+
456
+ [lower, upper]
457
+ end
458
+
459
+ def clamp_endpoints_are?(klass, lower, upper)
460
+ (lower.nil? || lower.is_a?(klass)) && (upper.nil? || upper.is_a?(klass))
461
+ end
462
+
463
+ # #861 — `clamp` on a receiver that carries no bracket of its own. `Comparable#clamp` returns a
464
+ # value inside the bracket whatever the receiver's bounds are, so a plain `Integer` / `Float`
465
+ # receiver takes the bracket entire: `i.clamp(1..9)` and `i.clamp(1, 9)` are `Integer[1..9]`
466
+ # where the RBS tier's `self | A` could only say `Integer` (resp. `1 | 9 | Integer`).
467
+ #
468
+ # Endpoints must be literals of the receiver's own class. A mixed bracket is `Comparable`'s
469
+ # business rather than the fold's: `1.clamp(0.5, 2.5)` returns the receiver OR a bound, so the
470
+ # run-time class is a union across `Integer` and `Float`, and it declines here.
471
+ #
472
+ # `Float` covers `NaN` while a `FloatRange` never does — sound in this direction, because
473
+ # `clamp` on a NaN receiver raises (`comparison of Float with Float failed`) and the call that
474
+ # returns is always inside the bracket.
475
+ #
476
+ # The two run-time raisers `clamp_bracket` already declines — an exclusive end (`cannot clamp
477
+ # with an exclusive range`) and a reversed bracket — keep declining here.
478
+ def try_fold_unbounded_clamp(receiver, method_name, args)
479
+ return nil unless method_name == :clamp
480
+
481
+ klass = unbounded_clamp_receiver_class(receiver)
482
+ return nil unless klass
483
+
484
+ case args.size
485
+ when 1 then fold_unbounded_clamp_range(klass, args.first)
486
+ when 2 then fold_unbounded_clamp_pair(klass, args)
487
+ end
488
+ end
489
+
490
+ # `Integer` / `Float` for a bare nominal receiver of that class; `nil` for everything else,
491
+ # the bounded carriers included — those keep their own intersecting folds.
492
+ def unbounded_clamp_receiver_class(receiver)
493
+ return nil unless receiver.is_a?(Type::Nominal) && receiver.type_args.empty?
494
+
495
+ case receiver.class_name
496
+ when "Integer" then Integer
497
+ when "Float" then Float
498
+ end
499
+ end
500
+
501
+ def fold_unbounded_clamp_range(klass, arg)
502
+ bracket = clamp_bracket(numeric_set_of(arg))
503
+ return nil if bracket.nil?
504
+
505
+ lower, upper = bracket
506
+ return nil unless clamp_endpoints_are?(klass, lower, upper)
507
+ return nil if clamp_bound_nan?(lower) || clamp_bound_nan?(upper)
508
+
509
+ unbounded_clamp_result(klass, lower, upper)
510
+ end
511
+
512
+ def fold_unbounded_clamp_pair(klass, args)
513
+ values = args.map { |arg| single_clamp_bound(klass, numeric_set_of(arg)) }
514
+ lower, upper = values
515
+ return nil if lower.nil? || upper.nil?
516
+ return nil if lower > upper
517
+
518
+ unbounded_clamp_result(klass, lower, upper)
519
+ end
520
+
521
+ # The two-argument form takes no `nil` bound: `clamp(nil, 9)` is `Comparable`'s own
522
+ # one-sided form, which the catalogue does not route here.
523
+ def single_clamp_bound(klass, values)
524
+ return nil unless values.is_a?(Array) && values.size == 1
525
+
526
+ value = values.first
527
+ return nil unless value.is_a?(klass)
528
+
529
+ clamp_bound_nan?(value) ? nil : value
530
+ end
531
+
532
+ # A NaN bound orders against nothing, so `clamp` raises for it — no bracket to fold to.
533
+ def clamp_bound_nan?(bound)
534
+ bound.is_a?(Float) && bound.nan?
535
+ end
536
+
537
+ def unbounded_clamp_result(klass, lower, upper)
538
+ if klass == Integer
539
+ build_integer_range(lower || -Float::INFINITY, upper || Float::INFINITY)
540
+ else
541
+ Type::Combinator.float_range(lower || -Float::INFINITY, upper || Float::INFINITY)
542
+ end
543
+ end
544
+
406
545
  # `Integer#divmod` and `Float#divmod` return a 2-element array `[quotient, remainder]`. We project
407
546
  # that into `Tuple[Constant[q], Constant[r]]` so downstream rules see the precise element types.
408
547
  # Union/range receivers are widened per-position: each tuple slot carries the union of quotients
@@ -870,16 +1009,70 @@ module Rigor
870
1009
  # could not reach.
871
1010
  #
872
1011
  # v0.0.6 — IntegerRange-shaped receivers participate in `Comparable#between?` and
873
- # `Comparable#clamp` folds. `int<a,b>.between?(min, max)` decides three-valued via the receiver's
874
- # bounds against scalar args; `int<a,b>.clamp` narrows the receiver's bounds against the bracket.
1012
+ # `Comparable#clamp` folds. `Integer[a..b].between?(min, max)` decides three-valued via the receiver's
1013
+ # bounds against scalar args; `Integer[a..b].clamp` narrows the receiver's bounds against the bracket.
875
1014
  # Other ternary methods over IntegerRange operands still decline.
876
1015
  def try_fold_ternary(receiver_set, method_name, arg_sets)
877
1016
  return try_fold_ternary_range(receiver_set, method_name, arg_sets) if receiver_set.is_a?(Type::IntegerRange)
878
- return nil if arg_sets.any?(Type::IntegerRange)
1017
+ if receiver_set.is_a?(Type::FloatRange)
1018
+ return try_fold_ternary_float_range(receiver_set, method_name, arg_sets)
1019
+ end
1020
+ return nil if arg_sets.any? { |s| bounded_range?(s) }
879
1021
 
880
1022
  try_fold_ternary_set(receiver_set, method_name, arg_sets)
881
1023
  end
882
1024
 
1025
+ # ADR-109 — receiver `FloatRange` + two scalar numeric args: `between?` decides three-valued
1026
+ # over the bracket, `clamp` narrows to the bracket (NaN never inhabits the receiver, so the
1027
+ # comparison that would raise on NaN cannot).
1028
+ def try_fold_ternary_float_range(range, method_name, arg_sets)
1029
+ return nil unless arg_sets.all?(Array)
1030
+
1031
+ min_arg = single_real_arg(arg_sets[0])
1032
+ max_arg = single_real_arg(arg_sets[1])
1033
+ return nil if min_arg.nil? || max_arg.nil?
1034
+ return nil if min_arg > max_arg
1035
+
1036
+ case method_name
1037
+ when :between? then float_range_between(range, min_arg, max_arg)
1038
+ when :clamp then float_range_clamp(range, min_arg, max_arg)
1039
+ end
1040
+ end
1041
+
1042
+ def single_real_arg(values)
1043
+ return nil unless values.is_a?(Array) && values.size == 1
1044
+
1045
+ v = values.first
1046
+ return nil unless v.is_a?(Integer) || (v.is_a?(Float) && !v.nan?)
1047
+
1048
+ v
1049
+ end
1050
+
1051
+ def float_range_between(range, min_arg, max_arg)
1052
+ return Type::Combinator.constant_of(false) if range.canonical_max < min_arg || range.min > max_arg
1053
+ return Type::Combinator.constant_of(true) if range.min >= min_arg && range.canonical_max <= max_arg
1054
+
1055
+ bool_union
1056
+ end
1057
+
1058
+ # `Float[a..b].clamp(lo, hi)` (either bound may be `nil` for the one-argument `clamp(lo..)` /
1059
+ # `clamp(..hi)` forms). Keeps the receiver's own end, exclusivity included, where it is the
1060
+ # tighter one. When the bracket lies wholly outside the receiver every value snaps to one bound
1061
+ # and the result is a point Rigor would have to invent, so it declines like the Integer twin.
1062
+ def float_range_clamp(range, min_arg, max_arg)
1063
+ new_min = min_arg.nil? ? range.min : [range.min, min_arg.to_f].max
1064
+ if max_arg.nil? || max_arg.to_f >= range.canonical_max
1065
+ new_max = range.max
1066
+ exclusive = range.exclude_end?
1067
+ else
1068
+ new_max = max_arg.to_f
1069
+ exclusive = false
1070
+ end
1071
+ return nil if new_min > (exclusive ? new_max.prev_float : new_max)
1072
+
1073
+ Type::Combinator.float_range(new_min, new_max, exclude_end: exclusive)
1074
+ end
1075
+
883
1076
  # Receiver IntegerRange + two scalar `Constant<Integer>` args — the only IntegerRange-aware ternary
884
1077
  # fold today. `between?` returns Trinary truthiness over the bracket; `clamp` returns the
885
1078
  # intersected IntegerRange (or a collapsed Constant if the result pins a single point).
@@ -904,7 +1097,7 @@ module Rigor
904
1097
  v.is_a?(Integer) ? v : nil
905
1098
  end
906
1099
 
907
- # `int<a,b>.between?(min, max)`:
1100
+ # `Integer[a..b].between?(min, max)`:
908
1101
  # - Constant[true] when [a,b] ⊆ [min,max] (and finite).
909
1102
  # - Constant[false] when [a,b] ∩ [min,max] is empty.
910
1103
  # - bool union otherwise.
@@ -916,7 +1109,7 @@ module Rigor
916
1109
  bool_union
917
1110
  end
918
1111
 
919
- # `int<a,b>.clamp(min, max)`:
1112
+ # `Integer[a..b].clamp(min, max)`:
920
1113
  # - new_lower = max(a, min), new_upper = min(b, max).
921
1114
  # - When new_lower > new_upper the bracket excluded the range entirely; the call still returns one
922
1115
  # of the bracket bounds at runtime, but Rigor is strictly less precise here than Ruby — decline
@@ -1112,8 +1305,8 @@ module Rigor
1112
1305
  end
1113
1306
 
1114
1307
  # Range % Range. Only the `(any range) % (positive constant n)` and `(any range) % (negative
1115
- # constant n)` cases are folded precisely — the former narrows to `int<0, n-1>`, the latter to
1116
- # `int<n+1, 0>`. Other shapes fall back to nil.
1308
+ # constant n)` cases are folded precisely — the former narrows to `Integer[0..n-1]`, the latter to
1309
+ # `Integer[n+1..0]`. Other shapes fall back to nil.
1117
1310
  def range_modulo(_left, right)
1118
1311
  return nil unless right.finite? && right.min == right.max
1119
1312
 
@@ -1220,6 +1413,99 @@ module Rigor
1220
1413
  }.freeze
1221
1414
  private_constant :UNARY_RANGE_DIRECT
1222
1415
 
1416
+ # ADR-109 — unary folds over a bounded Float. No bounded range contains NaN, so the predicates
1417
+ # that exist to detect it are decided outright, and the monotone conversions map the two bounds.
1418
+ # An exclusive end is dropped where negation would turn it into an exclusive begin, a shape
1419
+ # Ruby's literal cannot spell: the result is the closed envelope, a superset.
1420
+ FLOAT_RANGE_UNARY = {
1421
+ abs: :float_range_abs,
1422
+ magnitude: :float_range_abs,
1423
+ "-@": :float_range_negate,
1424
+ "+@": :float_range_identity,
1425
+ to_f: :float_range_identity,
1426
+ nan?: :float_range_never_nan,
1427
+ finite?: :float_range_finite,
1428
+ zero?: :float_range_zero,
1429
+ positive?: :float_range_positive,
1430
+ negative?: :float_range_negative,
1431
+ floor: :float_range_to_integer,
1432
+ ceil: :float_range_to_integer,
1433
+ round: :float_range_to_integer,
1434
+ truncate: :float_range_to_integer,
1435
+ to_i: :float_range_to_integer
1436
+ }.freeze
1437
+ private_constant :FLOAT_RANGE_UNARY
1438
+
1439
+ def try_fold_unary_float_range(range, method_name)
1440
+ handler = FLOAT_RANGE_UNARY[method_name]
1441
+ return nil if handler.nil?
1442
+
1443
+ send(handler, range, method_name)
1444
+ end
1445
+
1446
+ def float_range_abs(range, _method_name)
1447
+ lower = range.min
1448
+ upper = range.canonical_max
1449
+ return range if lower >= 0.0
1450
+ return Type::Combinator.float_range(-upper, -lower) if upper <= 0.0
1451
+
1452
+ Type::Combinator.float_range(0.0, [-lower, upper].max)
1453
+ end
1454
+
1455
+ def float_range_negate(range, _method_name)
1456
+ Type::Combinator.float_range(-range.canonical_max, -range.min)
1457
+ end
1458
+
1459
+ def float_range_identity(range, _method_name)
1460
+ range
1461
+ end
1462
+
1463
+ def float_range_never_nan(_range, _method_name)
1464
+ Type::Combinator.constant_of(false)
1465
+ end
1466
+
1467
+ def float_range_finite(range, _method_name)
1468
+ lower = range.min
1469
+ upper = range.canonical_max
1470
+ return Type::Combinator.constant_of(true) if lower.finite? && upper.finite?
1471
+ return Type::Combinator.constant_of(false) if lower == upper
1472
+
1473
+ bool_union
1474
+ end
1475
+
1476
+ def float_range_zero(range, _method_name)
1477
+ return Type::Combinator.constant_of(false) unless range.covers?(0.0)
1478
+ return Type::Combinator.constant_of(true) if range.min.zero? && range.canonical_max.zero?
1479
+
1480
+ bool_union
1481
+ end
1482
+
1483
+ # `Float#positive?` is `self > 0`, `negative?` is `self < 0`.
1484
+ def float_range_positive(range, _method_name)
1485
+ return Type::Combinator.constant_of(true) if range.min > 0.0
1486
+ return Type::Combinator.constant_of(false) if range.canonical_max <= 0.0
1487
+
1488
+ bool_union
1489
+ end
1490
+
1491
+ def float_range_negative(range, _method_name)
1492
+ return Type::Combinator.constant_of(true) if range.canonical_max < 0.0
1493
+ return Type::Combinator.constant_of(false) if range.min >= 0.0
1494
+
1495
+ bool_union
1496
+ end
1497
+
1498
+ # `floor` / `ceil` / `round` / `truncate` / `to_i` are monotone, so the image of the range is the
1499
+ # integer range between the images of its bounds. An infinite bound raises `FloatDomainError` at
1500
+ # run time for the value that reaches it, so the fold declines and the RBS `Integer` stands.
1501
+ def float_range_to_integer(range, method_name)
1502
+ lower = range.min
1503
+ upper = range.canonical_max
1504
+ return nil if lower.infinite? || upper.infinite?
1505
+
1506
+ build_integer_range(lower.public_send(method_name), upper.public_send(method_name))
1507
+ end
1508
+
1223
1509
  def try_fold_unary_range(range, method_name)
1224
1510
  return range_unary_predicate(range, method_name) if RANGE_UNARY_PREDICATES.include?(method_name)
1225
1511
  return range_unary_shift(range, method_name) if RANGE_UNARY_SHIFTS.include?(method_name)
@@ -31,7 +31,7 @@ module Rigor
31
31
  # and the reader-redefinition guard are shared with {StructFolding}.
32
32
  extend MemberShapeProjection
33
33
 
34
- # @return [Rigor::Type, nil] the folded result, or nil to defer.
34
+ # @return the folded result, or nil to defer.
35
35
  def try_dispatch(context)
36
36
  receiver = context.receiver
37
37
 
@@ -73,7 +73,7 @@ module Rigor
73
73
 
74
74
  module_function
75
75
 
76
- # @return [Rigor::Type, nil] folded result, or nil to defer
76
+ # @return folded result, or nil to defer
77
77
  # to the next dispatcher tier.
78
78
  def try_dispatch(context)
79
79
  receiver = context.receiver
@@ -16,7 +16,7 @@ module Rigor
16
16
  # Each rule mirrors Ruby's actual iteration semantics:
17
17
  #
18
18
  # - `n.times { |i| … }` yields `i ∈ [0, n-1]` when `n > 0`, nothing otherwise. The block-param type is
19
- # therefore `int<0, n-1>` for a `Constant<Integer>` receiver, `int<0, upper-1>` for a finite
19
+ # therefore `Integer[0..n-1]` for a `Constant<Integer>` receiver, `Integer[0..upper-1]` for a finite
20
20
  # `IntegerRange`, and `non_negative_int` for any unbounded-above shape.
21
21
  # - `a.upto(b) { |i| … }` yields `i ∈ [a, b]` when `a <= b`. Lower bound from the receiver, upper
22
22
  # bound from the argument.
@@ -25,7 +25,7 @@ module Rigor
25
25
  module IteratorDispatch
26
26
  module_function
27
27
 
28
- # @return [Array<Rigor::Type>, nil] block-param types, or
28
+ # @return block-param types, or
29
29
  # nil to fall through to the next tier.
30
30
  def block_param_types(context)
31
31
  receiver = context.receiver
@@ -41,7 +41,7 @@ module Rigor
41
41
 
42
42
  module_function
43
43
 
44
- # @return [Rigor::Type, nil] folded result, or nil to defer.
44
+ # @return folded result, or nil to defer.
45
45
  def try_dispatch(context)
46
46
  receiver = context.receiver
47
47
  method_name = context.method_name
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../type"
4
4
  require_relative "constant_folding"
5
+ require_relative "random_folding"
5
6
 
6
7
  module Rigor
7
8
  module Inference
@@ -43,7 +44,7 @@ module Rigor
43
44
  # every entry is spelling-parity checked. Public (not private_constant) and Ractor-shareable
44
45
  # because the gate and the spec both read it.
45
46
  INTRINSIC_NAMES = Ractor.make_shareable(
46
- Set[:Array, :Integer, :Float, :Rational, :Complex, :p, :pp, :String, :Hash, :format, :sprintf]
47
+ Set[:Array, :Integer, :Float, :Rational, :Complex, :p, :pp, :String, :Hash, :format, :sprintf, :rand]
47
48
  )
48
49
 
49
50
  # `Kernel#Rational` / `Kernel#Complex` constructor folds. When every argument is a `Type::Constant`
@@ -101,6 +102,11 @@ module Rigor
101
102
  return try_string(context) if method_name == :String
102
103
  return try_hash(context) if method_name == :Hash
103
104
  return try_format(context) if FORMAT_METHODS.include?(method_name)
105
+ # ADR-109 / #833 — `Kernel#rand(a..b)` lands in the literal range, a bounded carrier of the
106
+ # range's own class (never a constant: the generator advances). The other argument shapes
107
+ # stay unfolded on purpose; {RandomFolding} owns the `Random.rand` spelling and the reading.
108
+ # A splat still declines: the reading needs exactly one literal-Range argument.
109
+ return RandomFolding.interval_for_args(args) if method_name == :rand
104
110
 
105
111
  nil
106
112
  end
@@ -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