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
@@ -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
 
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+ require_relative "../../rbs_extended"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module MethodDispatcher
9
+ # Issue #1350 — how `OverloadSelector` reads a `Dynamic[T]` argument. The internal spec keeps a `Dynamic` whose
10
+ # static facet is not `top` out of the imprecise arguments because its facet discriminates, but acceptance
11
+ # short-circuits on the wrapper and takes it against any parameter, so the receiver-affinity pre-sort's first arm
12
+ # won: `Money#add(Integer(v))` typed `Money` and `.even?` reported `call.undefined-method`.
13
+ #
14
+ # Selection therefore reads a narrow facet's members ({.select}), leaving out `nil`: a facet's `nil` is usually
15
+ # the join of an overload that returns it for some untyped input (`Integer(v)`'s `exception:` form), and chosen
16
+ # by it `Integer | nil` fell through to the only arm that takes `nil` and `Complex(Integer(v), 1)` turned
17
+ # `Complex?`. One member left stands in for the argument, pass 0 included, and the return stays unwrapped; two
18
+ # are selected one by one, and the distinct picks come back together for the dispatch layer's join, wrapped in
19
+ # `Dynamic` when their returns differ (#521).
20
+ #
21
+ # Only sealed members are read ({.sealed?}): a literal, or a plain `Integer`, `Float`, `Rational`, `Complex` or
22
+ # `Symbol`, core classes whose `new` and `allocate` are undefined. Such a member's runtime value is of exactly its
23
+ # class, where any other member's may be of a subclass and take an arm the member itself skips: `Numeric` in
24
+ # `2 ** v`'s `Complex | Numeric` skipped a project `(real) -> Float`, whose `real` alias names `Integer`, for a
25
+ # catch-all `(untyped) -> nil`, and `.floor` on the call reported `call.undefined-method`. Scanning the parameters
26
+ # for a subclass of the member caught a plain class name and missed aliases, `instance`, type variables,
27
+ # intersections, singletons and modules.
28
+ #
29
+ # Even a sealed member is read only where acceptance can rule it out ({.provable?}). Acceptance reads class
30
+ # relations from the analyzer's own process, so it answers `no` for `Integer` against a `(Printable)` that project
31
+ # RBS or source includes into `Integer` (#1352). A plain argument shares that, but the wrapper hid it from a
32
+ # `Dynamic` one: with `(Printable) -> String | (Integer) -> Integer`, reading the member typed
33
+ # `fmt(Integer(v)).upcase` as `Integer` and fired `call.undefined-method` where master read `String`. So every
34
+ # overload's positional parameters must be spelled as a class RBS declares, `untyped`, `top` or `nil`, through `?`
35
+ # and `|`, which leaves acceptance only a sealed class's fixed ancestry to read. A literal or `bool` parameter
36
+ # asks for a value, which a class member cannot prove it holds: `Symbol` skipped `(:json) -> String` for
37
+ # `(untyped) -> nil`, and a `TrueClass` member was refused by `bool` itself. Anything else, a module, a stubbed
38
+ # name, an alias, an interface, a type variable, `instance`, `self`, an intersection, a singleton or an untyped
39
+ # `(?)`, keeps the wrapper too. The list names what is provable rather than what is not, so a form it has not met
40
+ # falls back to master's reading.
41
+ #
42
+ # A wider facet keeps the wrapper and the receiver's arm, as before. It is usually itself a #521 join
43
+ # (`Dynamic[BigDecimal | Complex | Float | Integer | Rational]` from `n * untyped`), and read member by member it
44
+ # joined every arm again: 256 call sites across the survey corpus lost a precise type to `Dynamic`, and a
45
+ # recursion's fuel-exhausted `n * of(n - 1)` stopped reading `Integer`.
46
+ module FacetDistribution
47
+ # The most facet members, `nil` aside, that selection reads one by one.
48
+ MEMBER_LIMIT = 2
49
+ # Member-wise argument lists beyond this keep their wrappers.
50
+ CAP = 8
51
+ # Core classes whose `new` and `allocate` are undefined, so plain Ruby cannot make an instance of a subclass
52
+ # (`Integer`, `Float` and `Symbol` have no allocator at all; a `Rational` or `Complex` subclass instance takes
53
+ # `Marshal.load` or a rebound `Class#allocate`). `NilClass` leaves with the facet's `nil`, and `true` and
54
+ # `false` are read only as literals.
55
+ SEALED_CLASSES = %w[Integer Float Rational Complex Symbol].freeze
56
+
57
+ module_function
58
+
59
+ # Whether any argument is a `Dynamic` whose facet selection reads; the cheap guard of the hot path.
60
+ def faceted?(arg_types) = arg_types.any? { |arg| arg.is_a?(Type::Dynamic) && facet_members(arg) }
61
+
62
+ # Selects through the block, called as `yield(arg_types, member)`. It yields once per member-wise list, one
63
+ # member standing in for each faceted argument, with `member` true so the block answers only a genuine match.
64
+ # It yields once with the arguments as given instead when the overloads are not {.provable?}, when an argument
65
+ # has two members and the caller is the singular `select` (`member_wise` false, whose one answer a member order
66
+ # would decide), when there are more than {CAP} lists, or when any list matches nothing.
67
+ def select(arg_types, definition, member_wise:, environment:, &)
68
+ choices = arg_types.map { |arg| facet_members(arg) || [arg] }
69
+ return yield(arg_types, false) unless member_wise || choices.all? { |members| members.size == 1 }
70
+ return yield(arg_types, false) unless provable?(definition, environment)
71
+
72
+ picks = picks_by_member(choices, &)
73
+ picks.empty? ? yield(arg_types, false) : picks
74
+ end
75
+
76
+ # The distinct overloads the member-wise lists pick; none past {CAP} or when any list picks nothing.
77
+ def picks_by_member(choices)
78
+ return [] if choices.reduce(1) { |count, members| count * members.size } > CAP
79
+
80
+ lists = choices.first.product(*choices.drop(1)).map { |combination| yield(combination, true) }
81
+ lists.any?(&:empty?) ? [] : lists.flatten(1).uniq(&:object_id)
82
+ end
83
+
84
+ # Whether every overload's positional parameters, rest and trailing included, are in a provable form
85
+ # ({.provable_param?}), where acceptance's answer for a sealed member rests only on the member class's fixed
86
+ # ancestry; a `rigor:v1:param:` override proves nothing either.
87
+ def provable?(definition, environment)
88
+ loader = environment&.rbs_loader
89
+ return false if loader.nil?
90
+ return false unless RbsExtended.param_type_override_map(definition, environment: environment).empty?
91
+
92
+ definition.method_types.all? do |method_type|
93
+ fun = method_type.type
94
+ next false unless fun.respond_to?(:required_positionals)
95
+
96
+ params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
97
+ params += [fun.rest_positionals] if fun.rest_positionals
98
+ params.all? { |param| provable_param?(param.type, loader) }
99
+ end
100
+ end
101
+
102
+ def provable_param?(rbs_type, loader)
103
+ case rbs_type
104
+ when RBS::Types::ClassInstance then declared_class?(rbs_type.name.to_s.delete_prefix("::"), loader)
105
+ when RBS::Types::Optional then provable_param?(rbs_type.type, loader)
106
+ when RBS::Types::Union then rbs_type.types.all? { |member| provable_param?(member, loader) }
107
+ when RBS::Types::Bases::Any, RBS::Types::Bases::Top, RBS::Types::Bases::Nil then true
108
+ else false
109
+ end
110
+ end
111
+
112
+ # A class RBS declares, not a module and not a name Rigor stubbed because no RBS declares it: a class cannot be
113
+ # included, and a sealed member's class has a fixed ancestry, so acceptance's answer against it holds.
114
+ def declared_class?(name, loader)
115
+ loader.class_known?(name) && !loader.rbs_module?(name) && !loader.synthesized_type_names.include?(name)
116
+ end
117
+
118
+ # A `Dynamic` argument's facet members other than `nil`, or nil when selection keeps the wrapper: not a
119
+ # `Dynamic`, the untyped carrier, a facet that is only `nil`, one wider than {MEMBER_LIMIT}, or one with a
120
+ # member that is not sealed, an untyped member included.
121
+ def facet_members(arg)
122
+ return nil unless arg.is_a?(Type::Dynamic) && !arg.static_facet.is_a?(Type::Top)
123
+
124
+ facet = arg.static_facet
125
+ members = (facet.is_a?(Type::Union) ? facet.members : [facet]).reject { |member| nil_value?(member) }
126
+ members if members.size.between?(1, MEMBER_LIMIT) && members.all? { |member| sealed?(member) }
127
+ end
128
+
129
+ # A member whose runtime value is of exactly its class: a `Constant`, which is a value of that exact class, or a
130
+ # plain instance of {SEALED_CLASSES}.
131
+ def sealed?(member)
132
+ case member
133
+ when Type::Constant then true
134
+ when Type::Nominal then member.type_args.empty? && SEALED_CLASSES.include?(member.class_name)
135
+ else false
136
+ end
137
+ end
138
+
139
+ def nil_value?(type)
140
+ return type.value.nil? if type.is_a?(Type::Constant)
141
+
142
+ type.is_a?(Type::Nominal) && type.class_name == "NilClass"
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -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