rigortype 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # `Hash#transform_keys(mapping)` return type, with or without a block.
9
+ #
10
+ # CRuby's `transform_keys_hash_i` looks each old key up in the mapping with `rb_hash_lookup2`, which
11
+ # ignores a default. A hit takes the mapping's value as the new key and never reaches the block. A miss
12
+ # yields the old key to the block, or keeps it when no block is given. The new key type is therefore the
13
+ # mapping's value type joined with the block's return type, or with the receiver's key type when there is
14
+ # no block. The values are the receiver's, untouched.
15
+ #
16
+ # Neither RBS line says that. rbs 4.2 declares `[K2] (hash[_Key, K2] replacements) { (K old_key) -> K2 }
17
+ # -> Hash[K2, V]`, and {RbsDispatch} leaves `K2` untyped because the mapping and the block both decide it.
18
+ # The blockless `[K2] (hash[_Key, K2]) -> Hash[K | K2, V]` leaves `K2` unbound too. rbs 3.x declares no
19
+ # mapping overload at all; `data/core_overlay/hash_rbs3.rbs` supplies rbs 4.2's on that line, with the same gaps.
20
+ # This tier answers ahead of both, so the forms it accepts get one answer on either RBS line.
21
+ #
22
+ # A mapping the analysis cannot read contributes a `Dynamic[top]` key arm instead of nothing. That covers an
23
+ # untyped argument, a `to_hash`-convertible object, a Hash subclass, the unlisted entries of an open shape,
24
+ # and an empty closed shape, which is what a mapping filled through an alias the engine does not track still
25
+ # reads as. A block the call carries but the block pass could not type contributes `Dynamic[top]` too. A
26
+ # literal with a `**splat` entry needs no arm of its own: its type joins the splatted hash's pairs and a
27
+ # `Dynamic[top]` arm, which also keeps a copy the code writes into (`m = { **o }; m[:b] = :w`) growing. A
28
+ # non-empty shape filled through an alias still reads narrower than it is: that is a gap in the binding's
29
+ # own type.
30
+ #
31
+ # Declines, leaving the RBS answer, when:
32
+ #
33
+ # - the method is not `transform_keys`. `transform_keys!` returns `self` and stays with RBS.
34
+ # - the argument count is not one, or is unknown with no block. A `*splat`, a forwarded `...`, or a lone
35
+ # `**splat` may pass no argument, and with no block that call answers an `Enumerator`. With a block, the
36
+ # call is the block form or the mapping form, and a `Dynamic[top]` mapping arm covers both.
37
+ # - a receiver member is not a `Hash` carrier: a `HashShape` other than the empty closed one, a `Hash`
38
+ # nominal, or a difference over one (`non-empty-hash[K, V]`). A subclass may override the method
39
+ # (`ActiveSupport::HashWithIndifferentAccess` does), and an empty closed shape answers `{}` whatever the
40
+ # mapping says.
41
+ module HashTransformKeysFolding
42
+ module_function
43
+
44
+ def try_dispatch(context)
45
+ return nil unless context.method_name == :transform_keys
46
+
47
+ block = block_arm(context)
48
+ renamed = mapping_arm(context, block)
49
+ return nil if renamed.nil?
50
+
51
+ receiver = context.receiver
52
+ members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
53
+ answers = members.map { |member| answer_for(member, renamed, block) }
54
+ return nil if answers.any?(&:nil?)
55
+
56
+ Type::Combinator.union(*answers)
57
+ end
58
+
59
+ # The mapping form's block parameter: the receiver's key type, as rbs 4.2's `{ (K old_key) -> K2 }`
60
+ # declares it, and as `data/core_overlay/hash_rbs3.rbs` restates it on the rbs 3.x line. The RBS probe
61
+ # does not bind it for a union of receiver shapes. Reached through {IteratorDispatch.block_param_types};
62
+ # nil falls through to the RBS probe.
63
+ def block_param_types(context)
64
+ return nil unless context.args.size == 1
65
+
66
+ receiver = context.receiver
67
+ members = receiver.is_a?(Type::Union) ? receiver.members : [receiver]
68
+ keys = members.map { |member| receiver_key_value(member)&.first }
69
+ return nil if keys.any?(&:nil?)
70
+
71
+ [Type::Combinator.union(*keys)]
72
+ end
73
+
74
+ # `Hash[unmapped | renamed, V]` for one receiver member, or nil when the member is not a `Hash` carrier.
75
+ def answer_for(receiver, renamed, block)
76
+ key_value = receiver_key_value(receiver)
77
+ return nil if key_value.nil?
78
+
79
+ key, value = key_value
80
+ new_key = Type::Combinator.union(block || key, renamed)
81
+ Type::Combinator.nominal_of("Hash", type_args: [new_key, value])
82
+ end
83
+
84
+ # The receiver's `[K, V]`, projected the way {RbsDispatch} projects a shape, or nil when the receiver
85
+ # is not a `Hash` carrier. An open shape may hold entries it does not list, so its key and value take
86
+ # a `Dynamic[top]` arm.
87
+ def receiver_key_value(receiver)
88
+ case receiver
89
+ when Type::HashShape then shape_key_value(receiver)
90
+ when Type::Nominal then nominal_key_value(receiver)
91
+ when Type::Difference then receiver_key_value(receiver.base)
92
+ end
93
+ end
94
+
95
+ def shape_key_value(shape)
96
+ return nil if shape.pairs.empty? && shape.closed?
97
+
98
+ key = Type::Combinator.union(*shape.pairs.keys.map { |k| Type::Combinator.constant_of(k) })
99
+ value = Type::Combinator.union(*shape.pairs.values)
100
+ return [key, value] if shape.closed?
101
+
102
+ [Type::Combinator.union(key, unknown), Type::Combinator.union(value, unknown)]
103
+ end
104
+
105
+ def nominal_key_value(nominal)
106
+ return nil unless nominal.class_name == "Hash"
107
+ return [unknown, unknown] unless nominal.type_args.size == 2
108
+
109
+ nominal.type_args
110
+ end
111
+
112
+ # The new keys the mapping supplies, or nil when the call is not one this tier answers (see the module
113
+ # comment for the argument-count rule).
114
+ def mapping_arm(context, block)
115
+ arguments = argument_nodes(context.call_node)
116
+ return block.nil? ? nil : unknown if unresolved_argument_count?(arguments)
117
+ return nil unless context.args.size == 1
118
+
119
+ mapping_value_type(context.args.first)
120
+ end
121
+
122
+ # The mapping's value type. Anything whose value type cannot be read contributes `Dynamic[top]`,
123
+ # including a member that is not a Hash at all (`to_hash` may still convert it, and a `TypeError` for one
124
+ # that cannot is not this tier's to report).
125
+ def mapping_value_type(mapping)
126
+ case mapping
127
+ when Type::HashShape then shape_value_type(mapping)
128
+ when Type::Nominal then nominal_value_type(mapping)
129
+ when Type::Difference then mapping_value_type(mapping.base)
130
+ when Type::Union then Type::Combinator.union(*mapping.members.map { |member| mapping_value_type(member) })
131
+ else unknown
132
+ end
133
+ end
134
+
135
+ # An empty closed shape contributes `Dynamic[top]`, not `bot`: the engine records no aliasing, so
136
+ # `m = {}; m.tap { |x| x[:a] = :z }` still reads `{}` after the store, and a `bot` arm would drop `:z`.
137
+ # A non-empty shape filled through an alias has the same gap, which this rule does not close.
138
+ def shape_value_type(shape)
139
+ return unknown if shape.pairs.empty? && shape.closed?
140
+
141
+ value = Type::Combinator.union(*shape.pairs.values)
142
+ shape.closed? ? value : Type::Combinator.union(value, unknown)
143
+ end
144
+
145
+ def nominal_value_type(nominal)
146
+ return unknown unless nominal.class_name == "Hash" && nominal.type_args.size == 2
147
+
148
+ nominal.type_args[1]
149
+ end
150
+
151
+ # The block's return type; `Dynamic[top]` when the call carries a block the block pass left untyped (it
152
+ # rescues to nil); nil when there is no block, so the unmapped keys keep the receiver's key type. `&nil`
153
+ # passes no block, whatever type the block pass gave it.
154
+ def block_arm(context)
155
+ block_node = context.call_node.is_a?(Prism::CallNode) ? context.call_node.block : nil
156
+ return nil if block_node.is_a?(Prism::BlockArgumentNode) && block_node.expression.is_a?(Prism::NilNode)
157
+ return context.block_type if context.block_type
158
+
159
+ block_node ? unknown : nil
160
+ end
161
+
162
+ # The call's argument nodes, or an empty list for a nil or non-call node (an internal caller, whose
163
+ # argument types are authoritative).
164
+ def argument_nodes(call_node)
165
+ return EMPTY_ARGUMENTS unless call_node.is_a?(Prism::CallNode)
166
+
167
+ call_node.arguments&.arguments || EMPTY_ARGUMENTS
168
+ end
169
+
170
+ EMPTY_ARGUMENTS = [].freeze
171
+ private_constant :EMPTY_ARGUMENTS
172
+
173
+ # A `*splat`, a forwarded `...`, or keyword arguments made only of `**splat` entries: each may pass no
174
+ # argument at all (`transform_keys(**{})` passes none), so whether there is a mapping is not static.
175
+ def unresolved_argument_count?(arguments)
176
+ arguments.any? do |argument|
177
+ case argument
178
+ when Prism::SplatNode, Prism::ForwardingArgumentsNode then true
179
+ when Prism::KeywordHashNode then argument.elements.all?(Prism::AssocSplatNode)
180
+ else false
181
+ end
182
+ end
183
+ end
184
+
185
+ def unknown
186
+ Type::Combinator.untyped
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../type"
4
4
  require_relative "singleton_folding"
5
+ require_relative "hash_transform_keys_folding"
5
6
 
6
7
  module Rigor
7
8
  module Inference
@@ -16,16 +17,18 @@ module Rigor
16
17
  # Each rule mirrors Ruby's actual iteration semantics:
17
18
  #
18
19
  # - `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
20
+ # therefore `Integer[0..n-1]` for a `Constant<Integer>` receiver, `Integer[0..upper-1]` for a finite
20
21
  # `IntegerRange`, and `non_negative_int` for any unbounded-above shape.
21
22
  # - `a.upto(b) { |i| … }` yields `i ∈ [a, b]` when `a <= b`. Lower bound from the receiver, upper
22
23
  # bound from the argument.
23
24
  # - `a.downto(b) { |i| … }` yields the same domain `[b, a]`, just iterated in reverse. Lower bound
24
25
  # from the argument, upper bound from the receiver.
26
+ # - `h.transform_keys(mapping) { |k| … }` yields the receiver's keys, a rule the RBS probe does not state for
27
+ # a union of receiver shapes ({HashTransformKeysFolding.block_param_types}).
25
28
  module IteratorDispatch
26
29
  module_function
27
30
 
28
- # @return [Array<Rigor::Type>, nil] block-param types, or
31
+ # @return block-param types, or
29
32
  # nil to fall through to the next tier.
30
33
  def block_param_types(context)
31
34
  receiver = context.receiver
@@ -41,6 +44,7 @@ module Rigor
41
44
  when :group_by, :partition then single_element_block_params(receiver)
42
45
  when :each_slice, :each_cons then slice_block_params(receiver)
43
46
  when :new then class_new_block_params(receiver, args)
47
+ when :transform_keys then HashTransformKeysFolding.block_param_types(context)
44
48
  end
45
49
  end
46
50
 
@@ -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
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+ require_relative "regexp_folding"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module MethodDispatcher
9
+ # Issue #1381 — slice reads of the proven `$~`. On a proven-match edge the flow engine narrows `$~` to
10
+ # `MatchData` and each unconditional group's `$N` to `String` (see `Narrowing#regex_match_predicate_scopes`).
11
+ # The Integer overload of RBS `MatchData#[]` already reads `String`, but the Range and `(start, length)`
12
+ # overloads and `values_at` read `Array[String?]`, and destructuring that binds every slot `String?`.
13
+ # This tier answers those reads with a Tuple, one slot per group, using the rule
14
+ # {RegexpFolding.fold_last_match_group} applies to `Regexp.last_match(N)`: `String` when `$N` is bound,
15
+ # `String?` when it is not (an optional or alternation-reachable group is nil on a successful match).
16
+ # Index 0 is the whole match, always a `String`.
17
+ #
18
+ # Supported, all with compile-time constant Integer arguments:
19
+ #
20
+ # * `$~[a..b]` / `$~[a...b]`
21
+ # * `$~[start, length]`
22
+ # * `$~.values_at(i, j, ...)`
23
+ #
24
+ # The receiver must be the `$~` read itself or a no-argument `Regexp.last_match`: a local MatchData carries
25
+ # no group facts, and the scope's `$N` describe only the frame's current match.
26
+ #
27
+ # The scope does not record the regex's group count, and a slice past the last group is shorter than the
28
+ # range asked for (`$~[1..3]` on a one-group match is `["x"]`) while `values_at` pads with nil. The highest
29
+ # bound `$K` is a lower bound on the count — `Scope#join_bindings` keeps a key only when both sides bind
30
+ # it — so every index must be at most K. Declines (RBS answers) when:
31
+ #
32
+ # - `$~` is not proven non-nil, or no scope is threaded through,
33
+ # - no `$1`..`$9` narrows to `String` (every group optional, no group at all, `when /a/, /b/`),
34
+ # - any index is above K, negative, or not a constant Integer, or a Range bound is nil (endless /
35
+ # beginless),
36
+ # - the slice is empty.
37
+ #
38
+ # K is read from `$1`..`$9` only: `Scope#forget_match_globals` does not clear `$10` and above (#1384), so a
39
+ # binding there may belong to an earlier match.
40
+ module MatchDataFolding
41
+ MAX_GROUP_GLOBAL = 9
42
+ private_constant :MAX_GROUP_GLOBAL
43
+
44
+ module_function
45
+
46
+ # @return folded Tuple, or nil to defer.
47
+ def try_dispatch(context)
48
+ receiver = context.receiver
49
+ return nil unless receiver.is_a?(Type::Nominal) && receiver.class_name == "MatchData"
50
+
51
+ scope = context.scope
52
+ return nil if scope.nil?
53
+ return nil unless last_match_read?(context.call_node, scope)
54
+ return nil unless RegexpFolding.proven_match?(scope.global(:$~))
55
+
56
+ highest = highest_bound_group(scope)
57
+ return nil if highest.nil?
58
+
59
+ indices = slot_indices(context.method_name, context.args, highest)
60
+ return nil if indices.nil?
61
+
62
+ Type::Combinator.tuple_of(*indices.map { |index| slot_type(index, scope) })
63
+ end
64
+
65
+ # The receiver expression reads the frame's current match: `$~`, or `last_match` with no arguments and no block
66
+ # on an expression that types as the core `Regexp` class. A project class that happens to be named `Regexp`
67
+ # (`Foo::Regexp` read as `Regexp` inside `module Foo`) types as its own singleton and does not count.
68
+ def last_match_read?(call_node, scope)
69
+ return false unless call_node.is_a?(Prism::CallNode)
70
+
71
+ receiver = call_node.receiver
72
+ case receiver
73
+ when Prism::GlobalVariableReadNode
74
+ receiver.name == :$~
75
+ when Prism::CallNode
76
+ receiver.name == :last_match && receiver.arguments.nil? && receiver.block.nil? &&
77
+ regexp_class?(receiver.receiver, scope)
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ def regexp_class?(node, scope)
84
+ return false if node.nil?
85
+
86
+ type = scope.type_of(node)
87
+ type.is_a?(Type::Singleton) && type.class_name == "Regexp"
88
+ end
89
+
90
+ # The highest N in 1..9 whose `$N` is narrowed to `String`, or nil when none is.
91
+ def highest_bound_group(scope)
92
+ MAX_GROUP_GLOBAL.downto(1).find { |index| string_global?(scope.global(:"$#{index}")) }
93
+ end
94
+
95
+ def string_global?(type)
96
+ type.is_a?(Type::Nominal) && type.class_name == "String"
97
+ end
98
+
99
+ # The group indices the call reads, in result order, or nil when the call shape is not folded or any index
100
+ # falls outside `0..highest`.
101
+ def slot_indices(method_name, args, highest)
102
+ indices =
103
+ case method_name
104
+ when :[] then index_slice(args)
105
+ when :values_at then values_at_indices(args)
106
+ end
107
+ return nil if indices.nil? || indices.none?
108
+ return nil unless indices.all? { |index| index.between?(0, highest) }
109
+
110
+ indices.to_a
111
+ end
112
+
113
+ # `[range]` or `[start, length]`. A single Integer index is left to RBS, which already reads `String`.
114
+ def index_slice(args)
115
+ case args.size
116
+ when 1 then range_indices(args.first)
117
+ when 2 then start_length_indices(*args)
118
+ end
119
+ end
120
+
121
+ def range_indices(arg)
122
+ range = constant_value(arg)
123
+ return nil unless range.is_a?(Range)
124
+
125
+ first = range.begin
126
+ last = range.end
127
+ return nil unless first.is_a?(Integer) && last.is_a?(Integer)
128
+ return nil if first.negative? || last.negative?
129
+
130
+ range
131
+ end
132
+
133
+ def start_length_indices(start_arg, length_arg)
134
+ start = constant_value(start_arg)
135
+ length = constant_value(length_arg)
136
+ return nil unless start.is_a?(Integer) && length.is_a?(Integer)
137
+ return nil if start.negative? || length.negative?
138
+
139
+ start...(start + length)
140
+ end
141
+
142
+ def values_at_indices(args)
143
+ indices = args.map { |arg| constant_value(arg) }
144
+ indices.all?(Integer) ? indices : nil
145
+ end
146
+
147
+ def constant_value(type)
148
+ type.is_a?(Type::Constant) ? type.value : nil
149
+ end
150
+
151
+ def slot_type(index, scope)
152
+ return Type::Combinator.nominal_of("String") if index.zero?
153
+
154
+ RegexpFolding.fold_last_match_group(Type::Combinator.constant_of(index), scope)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ 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