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
@@ -22,10 +22,10 @@ module Rigor
22
22
  module ForkMap
23
23
  module_function
24
24
 
25
- # @param items [Array] work items (file paths, or `[path, ast]` pairs), in caller order.
26
- # @param workers [Integer] resolved worker count (≤1, empty items, or no `fork` → sequential).
27
- # @yield [Array] a contiguous slice of `items`; must return a **marshalable** object.
28
- # @return [Array] the per-slice block results, in original slice order.
25
+ # @param items work items (file paths, or `[path, ast]` pairs), in caller order.
26
+ # @param workers resolved worker count (≤1, empty items, or no `fork` → sequential).
27
+ # @yield a contiguous slice of `items`; must return a **marshalable** object.
28
+ # @return the per-slice block results, in original slice order.
29
29
  def call(items:, workers:, &block)
30
30
  worker_count = [workers, items.size].min
31
31
  return [block.call(items)] unless parallel?(worker_count) && !items.empty?
@@ -55,8 +55,6 @@ module Rigor
55
55
  # The directory check is the belt to {.run_worker}'s braces: the offer is only worth taking while the
56
56
  # parent still has the directory open, and answering `nil` once it is gone degrades to `Dir.tmpdir`
57
57
  # instead of raising `Errno::ENOENT` somewhere far from here.
58
- #
59
- # @return [String, nil]
60
58
  def child_scratch_dir
61
59
  return nil unless @child_scratch_dir && File.directory?(@child_scratch_dir)
62
60
 
@@ -120,7 +118,7 @@ module Rigor
120
118
  end
121
119
  end
122
120
 
123
- # @return [Hash{value: Object}, nil] the child's payload wrapped so a legitimately-nil block result is
121
+ # @return the child's payload wrapped so a legitimately-nil block result is
124
122
  # distinguishable from an abnormal exit. `Marshal.load` is safe: the blob was written by our own
125
123
  # forked child to a temp file we created.
126
124
  def worker_payload(status, out_path)
@@ -41,10 +41,9 @@ module Rigor
41
41
 
42
42
  # Reduce `app` against the registry.
43
43
  #
44
- # @param app [Rigor::Type::App]
45
- # @param fuel [Integer] reduction-step budget (default 64 per ADR-20 WD3). Each visited
44
+ # @param fuel — reduction-step budget (default 64 per ADR-20 WD3). Each visited
46
45
  # body node costs one unit. On exhaustion the reduction returns `app.bound`.
47
- # @return [Rigor::Type] the reduced type, or `app.bound` when reduction is impossible (URI
46
+ # @return the reduced type, or `app.bound` when reduction is impossible (URI
48
47
  # not defined, arity mismatch, body_tree absent, fuel exhausted).
49
48
  def reduce(app, fuel: DEFAULT_FUEL)
50
49
  raise ArgumentError, "expected a Rigor::Type::App, got #{app.class}" unless app.is_a?(Type::App)
@@ -99,8 +99,6 @@ module Rigor
99
99
 
100
100
  attr_reader :registrations, :definitions
101
101
 
102
- # @param registrations [Array<Registration>]
103
- # @param definitions [Array<Definition>]
104
102
  def initialize(registrations: [], definitions: [])
105
103
  @registrations = registrations.to_h { |r| [r.uri, r] }.freeze
106
104
  @definitions = definitions.to_h { |d| [d.uri, d] }.freeze
@@ -123,7 +121,7 @@ module Rigor
123
121
  @definitions[uri]
124
122
  end
125
123
 
126
- # @return [HktRegistry] a new registry whose entries are the union of this registry's and
124
+ # @return a new registry whose entries are the union of this registry's and
127
125
  # `other`'s. On URI collisions `other`'s entries win (last-write-wins; OQ3 tentative).
128
126
  def merge(other)
129
127
  raise ArgumentError, "merge target must be an HktRegistry, got #{other.class}" unless other.is_a?(HktRegistry)
@@ -154,13 +152,12 @@ module Rigor
154
152
  # diagnostic; the other annotations still apply). That claim was false until issue #785: the
155
153
  # production reporter has no `#record`, so every declined directive was dropped silently.
156
154
  #
157
- # @param rbs_loader [Rigor::Environment::RbsLoader]
158
- # @param base [HktRegistry] starting registry (typically the bundled
155
+ # @param base — starting registry (typically the bundled
159
156
  # `Rigor::Builtins::HktBuiltins.registry`).
160
- # @param name_scope [Rigor::Environment::NameScope, nil] threaded through to the bound
157
+ # @param name_scope threaded through to the bound
161
158
  # resolver for class-name lookups; safe to omit during scanning since hkt bounds are
162
159
  # typically `untyped` or stdlib classes.
163
- # @param reporter [Rigor::RbsExtended::Reporter, #record, nil] same fail-soft reporter contract
160
+ # @param reporter same fail-soft reporter contract
164
161
  # the other RBS-extended parsers use; a collecting double that responds only to `#record` /
165
162
  # `#<<` still works (see {Rigor::RbsExtended::HktDirectives.record_hkt_error}).
166
163
 
@@ -27,7 +27,6 @@ module Rigor
27
27
 
28
28
  module_function
29
29
 
30
- # @param node [Prism::Node]
31
30
  def index_write?(node)
32
31
  NODE_CLASSES.any? { |klass| node.is_a?(klass) }
33
32
  end
@@ -36,10 +35,7 @@ module Rigor
36
35
  # the widening joins the stored value into the carrier's content evidence exactly as a real `[]=` does
37
36
  # (issue #560). Empty means "no evidence" and widens without joining.
38
37
  #
39
- # @param node [Prism::Node] one of {NODE_CLASSES}
40
- # @param current_scope [Rigor::Scope]
41
- # @param arg_types [Array<Rigor::Type::Base>]
42
- # @return [Rigor::Scope]
38
+ # @param node one of {NODE_CLASSES}
43
39
  def widen(node:, current_scope:, arg_types: MutationWidening::NO_ARG_TYPES)
44
40
  MutationWidening.widen_receiver_aliases(node.receiver, MUTATOR, current_scope, arg_types: arg_types)
45
41
  end
@@ -28,10 +28,7 @@ module Rigor
28
28
  module MacroBlockSelfType
29
29
  module_function
30
30
 
31
- # @param scope [Rigor::Scope]
32
- # @param call_node [Prism::CallNode]
33
- # @param receiver_type [Rigor::Type, nil]
34
- # @return [Rigor::Type, nil] the narrowed self-type, or
31
+ # @return the narrowed self-type, or
35
32
  # `nil` when no registered entry matches the call shape.
36
33
  def narrow_self_type_for(scope:, call_node:, receiver_type:)
37
34
  return nil if receiver_type.nil?
@@ -46,13 +46,8 @@ module Rigor
46
46
  # a finitely-sized receiver it is `Constant[size]`.
47
47
  COUNT_METHOD = :count
48
48
 
49
- # @param receiver [Rigor::Type, nil]
50
- # @param method_name [Symbol]
51
- # @param args [Array<Rigor::Type>]
52
- # @param block_type [Rigor::Type, nil] inferred return type of
53
- # the call's block. `nil` means "no block at the call site"
54
- # and disqualifies every rule here.
55
- # @return [Rigor::Type, nil]
49
+ # `context.block_type` is the inferred return type of the call's block; `nil` means "no block at the
50
+ # call site" and disqualifies every rule here.
56
51
  def try_dispatch(context)
57
52
  receiver = context.receiver
58
53
  method_name = context.method_name
@@ -135,7 +130,6 @@ module Rigor
135
130
  end
136
131
  end
137
132
 
138
- # @return [:always_true, :always_false, :bool, nil]
139
133
  # rubocop:disable-next Metrics/CyclomaticComplexity
140
134
  def predicate_decision(method_name, truthiness, emptiness)
141
135
  case method_name
@@ -167,7 +161,6 @@ module Rigor
167
161
  )
168
162
  end
169
163
 
170
- # @return [:empty, :non_empty, :unknown]
171
164
  def receiver_emptiness(receiver)
172
165
  case receiver
173
166
  when Type::Tuple
@@ -55,7 +55,7 @@ module Rigor
55
55
 
56
56
  module_function
57
57
 
58
- # @return [Rigor::Type, nil] folded result, or nil to defer.
58
+ # @return folded result, or nil to defer.
59
59
  def try_dispatch(context)
60
60
  receiver = context.receiver
61
61
  method_name = context.method_name
@@ -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