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
@@ -68,6 +68,77 @@ class Object
68
68
 
69
69
  # `core_ext/object/inclusion` — `x.in?([a, b])` / `x.in?(1..10)`.
70
70
  def in?: (untyped) -> bool
71
+ # `in?(another_object) ? self : nil` — `params[:kind].presence_in(%w[a b])`.
72
+ def presence_in: (untyped) -> self?
73
+
74
+ # Issue #673 — `core_ext/object/to_query`, `core_ext/object/duplicable` and
75
+ # `core_ext/object/instance_variables` all hang off `Object`, so every
76
+ # RBS-known receiver reaches them; declaring them only on `Hash` (as
77
+ # `to_param` / `to_query` were) or only on `NilClass` (as `duplicable?` /
78
+ # `instance_values` were) left `"x".to_param`, `1.duplicable?` and friends a
79
+ # `call.undefined-method` on ordinary Rails code.
80
+ #
81
+ # `to_param` is `to_s`; `to_query(key)` interpolates two `CGI.escape` results.
82
+ # `key` is `untyped` because the body only calls `to_param` on it, and both
83
+ # `:name` and `"user[name]"` are ordinary call sites.
84
+ def to_param: () -> String
85
+ def to_query: (untyped key) -> String
86
+
87
+ # `bool` and not `true`: `Object#duplicable?` answers true, but the
88
+ # `Singleton` (and, on a Ruby where methods are not duplicable, `Method` /
89
+ # `UnboundMethod`) overrides answer false. `-> true` on the universal
90
+ # receiver would make every `if x.duplicable?` an always-truthy branch —
91
+ # a false positive on the only shape anybody writes.
92
+ def duplicable?: () -> bool
93
+
94
+ # Keys without the leading `@` for `instance_values`, with it for
95
+ # `instance_variable_names`.
96
+ def instance_values: () -> Hash[String, untyped]
97
+ def instance_variable_names: () -> Array[String]
98
+
99
+ # `core_ext/object/deep_dup` — `duplicable? ? dup : self`, plus `Array`
100
+ # (`map(&:deep_dup)`), `Hash` and `Module` overrides. `self` over-claims on
101
+ # an `Array` subclass, whose override answers a plain `Array`. Declared on
102
+ # `Object` and not only on `Hash`, where it sat alone: `[[1], [2]].deep_dup`
103
+ # is the common shape.
104
+ def deep_dup: () -> self
105
+
106
+ # `core_ext/object/with` (ActiveSupport 7.1+) — sets each keyword through
107
+ # its public writer, yields `self`, and restores the old values in an
108
+ # `ensure`; the answer is the block's. There is no blockless form: `yield` without a block raises.
109
+ # ActiveSupport `undef_method`s it on `NilClass`, `TrueClass`, `FalseClass`,
110
+ # `Integer`, `Float` and `Symbol`, which RBS cannot express, so `1.with(a: 1)`
111
+ # resolves here where Ruby raises — a missed error, not a false one.
112
+ # `Data#with` (core) is a subclass override and keeps its own `-> self`.
113
+ def with: [T] (**untyped attributes) { (self) -> T } -> T
114
+
115
+ # `core_ext/object/with_options` — without a block, the
116
+ # `ActiveSupport::OptionMerger` proxy (not declared here); with one, the
117
+ # block's value. A zero-arity block is `instance_eval`ed on the proxy, the
118
+ # `with_options dependent: :destroy do has_many … end` model-body shape.
119
+ def with_options: (untyped options) -> untyped
120
+ | [T] (untyped options) { (?) -> T } -> T
121
+
122
+ # `core_ext/string/output_safety` — `false` on `Object`, `true` on
123
+ # `Numeric`, the buffer's own state on `ActiveSupport::SafeBuffer`. `bool`
124
+ # for the reason `duplicable?` is: `-> false` on the universal receiver makes
125
+ # every `if x.html_safe?` an always-falsy branch on the subclasses that answer
126
+ # true.
127
+ def html_safe?: () -> bool
128
+ end
129
+
130
+ # ---------------------------------------------------------------
131
+ # Kernel — `core_ext/kernel/singleton_class`
132
+ # ---------------------------------------------------------------
133
+
134
+ # `singleton_class.class_eval(*args, &block)` on any object. Declared on
135
+ # `Kernel`, where ActiveSupport defines it; a `Module` receiver still answers
136
+ # core `Module#class_eval`, which is its own alias of `module_eval`. The two
137
+ # overloads mirror that `module_eval`: a string, or a block given the
138
+ # singleton class.
139
+ module Kernel
140
+ def class_eval: (string code, ?string? filename, ?int lineno) -> untyped
141
+ | [U] () { (untyped singleton_class) -> U } -> U
71
142
  end
72
143
 
73
144
  # `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
@@ -80,16 +151,24 @@ class NilClass
80
151
  def try!: (*untyped) -> nil
81
152
  def duplicable?: () -> true
82
153
  def instance_values: () -> Hash[String, untyped]
154
+ # `core_ext/object/to_query` overrides `to_param` on the three singleton
155
+ # values to return `self` rather than `Object#to_param`'s `to_s`, so the
156
+ # `Object` row above would carry a wrong return here. `NilClass#to_query`
157
+ # is its own method (`CGI.escape(key.to_param)`), still a String.
158
+ def to_param: () -> nil
159
+ def to_query: (untyped key) -> String
83
160
  end
84
161
 
85
162
  class TrueClass
86
163
  def blank?: () -> false
87
164
  def present?: () -> true
165
+ def to_param: () -> true
88
166
  end
89
167
 
90
168
  class FalseClass
91
169
  def blank?: () -> true
92
170
  def present?: () -> false
171
+ def to_param: () -> false
93
172
  end
94
173
 
95
174
  # ---------------------------------------------------------------
@@ -116,6 +195,10 @@ class String
116
195
  def titlecase: () -> String
117
196
  def dasherize: () -> String
118
197
  def upcase_first: () -> String
198
+ def downcase_first: () -> String # ActiveSupport 7.1+
199
+
200
+ # `core_ext/string/behavior` — the `acts_like?(:string)` duck-type hook.
201
+ def acts_like_string?: () -> true
119
202
 
120
203
  # `core_ext/string/filters`
121
204
  def squish: () -> String
@@ -149,6 +232,12 @@ class String
149
232
  def to_datetime: () -> DateTime
150
233
  def to_hours: () -> Float
151
234
 
235
+ # `core_ext/string/zones` — `Time.find_zone!(zone).parse(self)`, or
236
+ # `to_time` when no zone is set. `untyped` like the `Time` / `Date` rows:
237
+ # the parse answers an `ActiveSupport::TimeWithZone`, or `nil` on a string
238
+ # with no date parts.
239
+ def in_time_zone: (?untyped zone) -> untyped
240
+
152
241
  # `core_ext/string/access`
153
242
  def at: (Integer | Range[Integer] | Regexp position) -> String?
154
243
  def from: (Integer position) -> String
@@ -161,6 +250,7 @@ class String
161
250
 
162
251
  # `core_ext/string/multibyte`
163
252
  def mb_chars: () -> untyped
253
+ def is_utf8?: () -> bool
164
254
 
165
255
  # `core_ext/string/inquiry`
166
256
  def inquiry: () -> untyped # ActiveSupport::StringInquirer
@@ -169,6 +259,17 @@ class String
169
259
  def exclude?: (String) -> bool
170
260
  end
171
261
 
262
+ # ---------------------------------------------------------------
263
+ # Symbol — `core_ext/symbol/starts_ends_with`
264
+ # ---------------------------------------------------------------
265
+
266
+ # `alias_method`s of core `start_with?` / `end_with?`, declared with the core
267
+ # parameter lists.
268
+ class Symbol
269
+ def starts_with?: (*Regexp | string prefixes) -> bool
270
+ def ends_with?: (*string suffixes) -> bool
271
+ end
272
+
172
273
  # ---------------------------------------------------------------
173
274
  # Integer — `core_ext/integer/*`
174
275
  # ---------------------------------------------------------------
@@ -263,6 +364,16 @@ end
263
364
  # `spec/rigor/environment/activesupport_overlay_parity_spec.rb`); every method not listed here stays
264
365
  # undeclared and silently `Dynamic` rather than guessed.
265
366
  module ActiveSupport
367
+ # Issue #534 item 7 — the `ActiveSupport::Concern` twin of the plugin's declaration. The plugin file
368
+ # carries the reasoning; the short version is that naming the module makes `extend ActiveSupport::Concern`
369
+ # resolve, and the three members have to come with it so that `included do … end` does not become a false
370
+ # `call.undefined-method` on a now-RBS-known module.
371
+ module Concern
372
+ def included: (?untyped base) ?{ (?) -> untyped } -> untyped
373
+ def prepended: (?untyped base) ?{ (?) -> untyped } -> untyped
374
+ def class_methods: () { (?) -> untyped } -> untyped
375
+ end
376
+
266
377
  class Duration
267
378
  # The `ago` family (issue #659). `since(time = ::Time.current)` and `ago(time = ::Time.current)` are
268
379
  # the only two real methods; `from_now` / `after` alias the first and `until` / `before` the second,
@@ -272,23 +383,25 @@ module ActiveSupport
272
383
  # `GEM_OVERLAY_OPEN_RECEIVERS` cannot suppress since it protects a Duration receiver, not a `Time`
273
384
  # one. #658 declared that surface and #670 the `Date` / `DateTime` twins, so the chains resolve now.
274
385
  #
275
- # `Time` is the right class rather than a compromise: under a zone these answer an
276
- # `ActiveSupport::TimeWithZone`, and Rails overrides `TimeWithZone#is_a?` to answer true for
277
- # `::Time`. A `Time | ActiveSupport::TimeWithZone` union was measured and rejected — it types every
278
- # downstream call `Dynamic[top]`. The with-an-argument form stays `untyped` because the runtime
279
- # return depends on the argument's class AND on whether the duration carries a date-scale part
280
- # (`(1.month).ago(Date.today)` answers a `Date`; `(30.minutes).ago(Date.today)` a `TimeWithZone`).
281
- def ago: () -> Time
386
+ # Since #673 the no-argument form answers `ActiveSupport::TimeWithZone`, the class these really
387
+ # build off `::Time.current` under a zone, declared below as a `::Time` SUBCLASS. That keeps every
388
+ # `Time`-typed chain and parameter working while `1.hour.ago.time_zone` stops being a false
389
+ # `call.undefined-method`. A `Time | ActiveSupport::TimeWithZone` union was measured and rejected —
390
+ # it types every downstream call `Dynamic[top]`. The with-an-argument form stays `untyped` because
391
+ # the runtime return depends on the argument's class AND on whether the duration carries a
392
+ # date-scale part (`(1.month).ago(Date.today)` answers a `Date`; `(30.minutes).ago(Date.today)` a
393
+ # `TimeWithZone`).
394
+ def ago: () -> TimeWithZone
282
395
  | (untyped time) -> untyped
283
- def until: () -> Time
396
+ def until: () -> TimeWithZone
284
397
  | (untyped time) -> untyped
285
- def before: () -> Time
398
+ def before: () -> TimeWithZone
286
399
  | (untyped time) -> untyped
287
- def since: () -> Time
400
+ def since: () -> TimeWithZone
288
401
  | (untyped time) -> untyped
289
- def from_now: () -> Time
402
+ def from_now: () -> TimeWithZone
290
403
  | (untyped time) -> untyped
291
- def after: () -> Time
404
+ def after: () -> TimeWithZone
292
405
  | (untyped time) -> untyped
293
406
 
294
407
  # `to_i` / its `in_seconds` alias return `@value.to_i` — always Integer.
@@ -315,6 +428,54 @@ module ActiveSupport
315
428
  # Float` is what stays honest without costing the common `parts[:days] * 2` chain anything.
316
429
  def parts: () -> Hash[Symbol, Integer | Float]
317
430
  end
431
+
432
+ # ---------------------------------------------------------------
433
+ # ActiveSupport::TimeWithZone — issue #673
434
+ # ---------------------------------------------------------------
435
+ #
436
+ # `Time.current` and the `Duration#ago` family really answer a `TimeWithZone`
437
+ # under a zone, and this class was not modelled at all, so the four readers a
438
+ # TWZ has and a `Time` does not — `time_zone`, `time`, `period`,
439
+ # `comparable_time` — were `call.undefined-method` on every `Time.current`
440
+ # receiver. Declaring them on `Time` would have been wrong: a plain `Time`
441
+ # genuinely lacks them.
442
+ #
443
+ # `< ::Time` rather than a `Time | TimeWithZone` union at the producers. The
444
+ # union was measured during #632 and rejected — it types every downstream
445
+ # call `Dynamic[top]`. Inheritance is the cheaper shape AND the honest one
446
+ # here: Rails overrides `TimeWithZone#is_a?` to answer true for `::Time`, so
447
+ # `Time` is the class Rails itself tells callers a TWZ is, and TWZ's
448
+ # `method_missing` forwards every name it does not define to the wrapped
449
+ # `::Time` — which is exactly what inheriting `Time`'s surface models. A TWZ
450
+ # therefore also satisfies every `Time`-typed parameter, and a chain through
451
+ # `Time.current` keeps resolving instead of collapsing.
452
+ #
453
+ # The cost, taken deliberately: `Time.current` answers a plain `Time` when
454
+ # `Time.zone` is nil, so `Time.current.time_zone` in a zone-less
455
+ # activesupport-only project is a real `NoMethodError` this now types. Rails
456
+ # sets `config.time_zone` ("UTC") by default, so the shape is rare, and the
457
+ # false positive it replaces fired on correct Rails code — which AGENTS.md
458
+ # ranks above the worst-case static reading.
459
+ class TimeWithZone < ::Time
460
+ # `Time`'s own `initialize` takes the `(year, month, …)` parts; inheriting
461
+ # it would make `TimeWithZone.new(utc, zone)` — how Rails constructs one —
462
+ # a `call.argument-type-mismatch`. `untyped` because the zone and period
463
+ # carriers (`ActiveSupport::TimeZone`, `TZInfo::TimezonePeriod`) are not
464
+ # declared anywhere in this overlay.
465
+ def initialize: (untyped utc_time, untyped time_zone, ?untyped local_time, ?untyped period) -> void
466
+
467
+ # The `attr_reader`; an `ActiveSupport::TimeZone`, undeclared here.
468
+ def time_zone: () -> untyped
469
+
470
+ # The wall-clock `::Time` in the zone. `utc` / `getutc` / `getgm` /
471
+ # `gmtime` are `Time`'s own names and stay inherited; `comparable_time` is
472
+ # the alias of `utc` that `Time` does not carry.
473
+ def time: () -> ::Time
474
+ def comparable_time: () -> ::Time
475
+
476
+ # A `TZInfo::TimezonePeriod`, undeclared here.
477
+ def period: () -> untyped
478
+ end
318
479
  end
319
480
 
320
481
  # ---------------------------------------------------------------
@@ -322,7 +483,10 @@ end
322
483
  # ---------------------------------------------------------------
323
484
 
324
485
  class Time
325
- def self.current: () -> Time
486
+ # `Time.zone ? Time.zone.now : Time.now` — an `ActiveSupport::TimeWithZone` under a zone, which
487
+ # since #673 is declared as a `::Time` subclass so the four TWZ-only readers resolve without the
488
+ # rest of the chain losing its `Time` typing. See the class's own note for the zone-less cost.
489
+ def self.current: () -> ActiveSupport::TimeWithZone
326
490
  def self.zone: () -> untyped # ActiveSupport::TimeZone | nil
327
491
  def self.zone=: (String | Symbol | untyped) -> untyped
328
492
 
@@ -666,6 +830,8 @@ class Array[unchecked out Elem]
666
830
  def fourth: () -> Elem?
667
831
  def fifth: () -> Elem?
668
832
  def forty_two: () -> Elem?
833
+ def second_to_last: () -> Elem?
834
+ def third_to_last: () -> Elem?
669
835
 
670
836
  # `core_ext/array/grouping`
671
837
  def in_groups_of: (Integer number, ?untyped fill_with) -> Array[Array[Elem]]
@@ -687,6 +853,9 @@ class Array[unchecked out Elem]
687
853
  # `core_ext/array/extract`
688
854
  def extract!: () { (Elem) -> bool } -> Array[Elem]
689
855
 
856
+ # `core_ext/array/extract_options`
857
+ def extract_options!: () -> Hash[untyped, untyped]
858
+
690
859
  # `core_ext/object/blank` / `core_ext/enumerable` — both
691
860
  # ActiveSupport additions, shipped on Array specifically.
692
861
  def compact_blank: () -> Array[Elem]
@@ -714,6 +883,13 @@ module Enumerable[unchecked out Elem]
714
883
  def minimum: (Symbol | String) -> untyped
715
884
  def sole: () -> Elem
716
885
  def compact_blank: () -> Array[Elem]
886
+ def many?: () -> bool
887
+ | () { (Elem) -> boolish } -> bool
888
+ # `key` and `series` are untyped on purpose: the default `filter: true`
889
+ # path is `group_by(&key).values_at(*series)`, so a Proc key or a Set /
890
+ # Range series is correct Rails code. `filter:` is ActiveSupport 8.x;
891
+ # passing it on 7.x raises, which this unversioned row does not catch.
892
+ def in_order_of: (untyped key, untyped series, ?filter: boolish) -> Array[Elem]
717
893
  end
718
894
 
719
895
  # ---------------------------------------------------------------
@@ -735,6 +911,9 @@ class Hash[unchecked out K, unchecked out V]
735
911
  def deep_transform_keys!: () { (K) -> K } -> self
736
912
  def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
737
913
  def deep_transform_values!: () { (V) -> untyped } -> self
914
+ # `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
915
+ def to_options: () -> Hash[Symbol, V]
916
+ def to_options!: () -> self
738
917
 
739
918
  # `core_ext/hash/deep_dup`
740
919
  def deep_dup: () -> Hash[K, V]
@@ -744,6 +923,9 @@ class Hash[unchecked out K, unchecked out V]
744
923
  | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
745
924
  def deep_merge!: (Hash[K, V]) -> self
746
925
  | (Hash[K, V]) { (K, V, V) -> V } -> self
926
+ # `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
927
+ # recursing into a value. `:nodoc:`, but public.
928
+ def deep_merge?: (untyped other) -> bool
747
929
 
748
930
  # `core_ext/hash/except` — `Hash#except` is in core RBS as of
749
931
  # Ruby 3.0+; `except!` is ActiveSupport-only. ActiveSupport
@@ -757,8 +939,10 @@ class Hash[unchecked out K, unchecked out V]
757
939
  def to_param: (?String namespace) -> String
758
940
  def to_xml: (**untyped) -> String
759
941
 
760
- # `core_ext/hash/indifferent_access`
942
+ # `core_ext/hash/indifferent_access` — `nested_under_indifferent_access`
943
+ # is an `alias` of `with_indifferent_access`.
761
944
  def with_indifferent_access: () -> untyped
945
+ def nested_under_indifferent_access: () -> untyped
762
946
 
763
947
  # `core_ext/hash/conversions`
764
948
  def self.from_xml: (String, ?Symbol disallowed_types) -> Hash[String, untyped]
@@ -768,14 +952,49 @@ class Hash[unchecked out K, unchecked out V]
768
952
  def compact_blank: () -> Hash[K, V]
769
953
  def compact_blank!: () -> self
770
954
 
771
- # `core_ext/hash/reverse_merge`
772
- def reverse_merge: (Hash[K, V]) -> Hash[K, V]
955
+ # `core_ext/array/extract_options`
956
+ def extractable_options?: () -> bool
957
+
958
+ # `core_ext/hash/reverse_merge` — `other_hash.merge(self)`, so the result
959
+ # carries the argument's keys and values as well as the receiver's, the
960
+ # same shape as core `Hash#merge`. `-> Hash[K, V]` typed
961
+ # `opts.reverse_merge(size: 25, velocity: 10)` as `Hash[:size, 1]`.
962
+ def reverse_merge: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
773
963
  def reverse_merge!: (Hash[K, V]) -> self
964
+ # `alias_method`s: `with_defaults` of `reverse_merge`, `with_defaults!`
965
+ # and `reverse_update` of `reverse_merge!`.
966
+ def with_defaults: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
967
+ def with_defaults!: (Hash[K, V]) -> self
968
+ def reverse_update: (Hash[K, V]) -> self
774
969
 
775
970
  # `core_ext/hash/slice` — `Hash#except` is in core RBS (Ruby 3.0+);
776
971
  # `Hash#slice` is in core RBS (Ruby 2.5+); the bang variants are
777
- # ActiveSupport-only.
972
+ # ActiveSupport-only. `extract!` deletes the named keys and answers them
973
+ # as a new hash; a key the receiver lacks is skipped, not `nil`-filled.
778
974
  def slice!: (*K) -> Hash[K, V]
975
+ def extract!: (*K) -> Hash[K, V]
976
+ end
977
+
978
+ # ---------------------------------------------------------------
979
+ # Range — `core_ext/range/*`
980
+ # ---------------------------------------------------------------
981
+
982
+ # `[out E]` matches core `range.rbs`. A reopening whose parameters differ in
983
+ # count or variance (`class Range`, `class Range[E]`) raises
984
+ # `RBS::GenericParameterMismatchError` and collapses `Range`.
985
+ class Range[out E]
986
+ # `core_ext/range/overlap` — an `alias` of `overlap?`, which rbs declares
987
+ # since Ruby 3.3 (ActiveSupport defines the method itself only on an older
988
+ # Ruby). `overlap?` is therefore NOT redeclared here: a second declaration
989
+ # is a `DuplicatedMethodDefinitionError` that collapses the whole class.
990
+ def overlaps?: (Range[untyped]) -> bool
991
+
992
+ # `core_ext/range/conversions` — `ActiveSupport::RangeWithFormat`, prepended
993
+ # onto `Range`. An unknown format falls back to `to_s`; `:db` answers `nil`
994
+ # for a beginless-and-endless range, which the declared `String` does not
995
+ # model — `(nil..nil).to_fs(:db)` is not a shape correct code writes.
996
+ def to_fs: (?Symbol format) -> String
997
+ def to_formatted_s: (?Symbol format) -> String
779
998
  end
780
999
 
781
1000
  # ---------------------------------------------------------------
@@ -25,7 +25,7 @@ n = ARGV.size
25
25
  ```
26
26
 
27
27
  A vanilla checker says: `n: Integer`. Rigor says:
28
- `n: int<0, max>` (a non-negative integer — `Array#size` cannot
28
+ `n: Integer[0..]` (a non-negative integer — `Array#size` cannot
29
29
  return a negative count).
30
30
 
31
31
  The reason this matters: most diagnostics Rigor wants to fire
@@ -40,7 +40,7 @@ or narrow (`Constant<3>`, `non-empty-string`). The rest of
40
40
  this chapter is the carrier zoo.
41
41
 
42
42
  One note on notation before the zoo: angle brackets hold a
43
- concrete value or bound — `Constant<3>`, `int<0, max>` —
43
+ concrete value or bound — `Constant<3>`, `Integer[0..]` —
44
44
  while square brackets hold type parameters, exactly as in RBS
45
45
  — `Nominal[String]`, `Hash[K, V]`, `Dynamic[top]`. A type
46
46
  parameter names the type of a *part*: `Hash[K, V]` is a Hash
@@ -124,27 +124,30 @@ nominal carrier or `Dynamic[top]`.
124
124
 
125
125
  Some integer-valued expressions produce a known range without
126
126
  producing a single literal value. Rigor describes those with
127
- `Type::IntegerRange`, displayed as `int<min, max>`:
127
+ `Type::IntegerRange`, displayed as `Integer[…]` around the Ruby
128
+ range literal that covers the values, `Integer[1..10]`:
128
129
 
129
130
  ```ruby
130
- n = ARGV.size #=> dump_type: int<0, max>
131
- m = n + 1 #=> dump_type: int<1, max>
132
- double = n * 2 #=> dump_type: int<0, max>
131
+ n = ARGV.size #=> dump_type: Integer[0..]
132
+ m = n + 1 #=> dump_type: Integer[1..]
133
+ double = n * 2 #=> dump_type: Integer[0..]
133
134
  ```
134
135
 
135
- `max` here means "positive infinity" — the upper bound is
136
- unbounded; `min`, which appears in the table below, is its
137
- mirror, "negative infinity." Multiplication preserves the
138
- floor, so `n * 2` stays `int<0, max>`.
136
+ The literal means what it means in Ruby: `Integer[0..]` is
137
+ every integer `(0..).cover?` accepts, an endless range is
138
+ unbounded above, a beginless one (`Integer[..-1]`) is unbounded
139
+ below, and `Integer[1...10]` is written back as `Integer[1..9]`
140
+ because for integers the two are the same set. Multiplication
141
+ preserves the floor, so `n * 2` stays `Integer[0..]`.
139
142
 
140
143
  A handful of common ranges have shorter names:
141
144
 
142
145
  | Spelling | Meaning |
143
146
  | --- | --- |
144
- | `positive-int` | `int<1, max>` |
145
- | `non-negative-int` | `int<0, max>` |
146
- | `negative-int` | `int<min, -1>` |
147
- | `non-positive-int` | `int<min, 0>` |
147
+ | `positive-int` | `Integer[1..]` |
148
+ | `non-negative-int` | `Integer[0..]` |
149
+ | `negative-int` | `Integer[..-1]` |
150
+ | `non-positive-int` | `Integer[..0]` |
148
151
 
149
152
  `Array#size`, `Array#length`, `Hash#size`, `String#size`, …
150
153
  all carry `non-negative-int`. `Array#count` does too. Adding
@@ -77,6 +77,32 @@ unless x.is_a?(Integer)
77
77
  end
78
78
  ```
79
79
 
80
+ A guard can also name a class the variable's type says it cannot
81
+ be. Inside such a branch the variable reads `bot`, so no call on
82
+ it is checked. That matters when the type is one Rigor inferred
83
+ or read from a signature: `$stdout` is typed `IO`, and a test runs
84
+ the same code with a `StringIO`, which is not an `IO` subclass, so
85
+ the guarded call below is correct code and stays quiet. A guard
86
+ on `$stdout`, `STDOUT` or another global or constant narrows the
87
+ same way:
88
+
89
+ ```ruby
90
+ require "stringio"
91
+
92
+ def captured_output
93
+ out = STDOUT
94
+ if out.is_a?(StringIO)
95
+ assert_type("bot", out)
96
+ out.string
97
+ end
98
+ end
99
+ ```
100
+
101
+ The rest of the branch is still checked, and its `bot` variable
102
+ never reaches the code after it. A `case` drops such a branch
103
+ from its value (see below); keeping it is tracked in
104
+ [#1465](https://github.com/rigortype/rigor/issues/1465).
105
+
80
106
  ## Equality with literal values
81
107
 
82
108
  Rigor narrows `==` and `!=` against trusted literal values:
@@ -107,7 +133,7 @@ matched member:
107
133
  ```ruby
108
134
  case n
109
135
  when 0 then :zero # Constant<0>
110
- when 1..9 then :small # int<1, 9>
136
+ when 1..9 then :small # Integer[1..9]
111
137
  when 10 then :ten # Constant<10>
112
138
  else :large # everything else
113
139
  end
@@ -117,17 +143,78 @@ The result type unions the per-branch results. When the input
117
143
  is a finite literal union, Rigor proves the `else` branch is
118
144
  unreachable when every member is matched.
119
145
 
120
- The same narrowing also works in reverse: when a `when <Class>`
121
- clause is disjoint from the subject's type, or an earlier
122
- clause already covered it, the clause is dead — Rigor emits
146
+ The same narrowing also works in reverse: when an earlier clause
147
+ already covered the subject, or a `when <Class>` clause is
148
+ disjoint from the subject's type, the clause is dead — Rigor emits
123
149
  [`flow.unreachable-clause`](08-understanding-errors.md) so you
124
150
  can delete it. (It ships at `:info` under the default profile.)
125
151
 
152
+ A disjoint `when <Class>` is reported only when the subject's
153
+ type is what the code literally shows: a literal (`1`, `"s"`,
154
+ `:a`), an array or hash literal, a class object, or `nil` /
155
+ `true` / `false`. When the subject's type is one Rigor inferred
156
+ or read from a signature, the `when` is evidence that it can hold
157
+ something else, as in the `is_a?` example above, so the clause is
158
+ not reported. It is still read as `bot`, and the `case` value
159
+ drops it:
160
+
161
+ ```ruby
162
+ require "stringio"
163
+
164
+ io = STDOUT
165
+ assert_type(":io", (case io when StringIO then :string_io else :io end))
166
+
167
+ count = 3
168
+ assert_type(":number", (case count when String then :text else :number end))
169
+
170
+ case io
171
+ when StringIO then puts io.string # not reported
172
+ end
173
+
174
+ case count
175
+ when String then puts count # flow.unreachable-clause
176
+ end
177
+ ```
178
+
179
+ Both values leave the `when` branch out. Of the two `case`
180
+ statements after them, only the second reports
181
+ `flow.unreachable-clause`. The check reads a `case` the code runs
182
+ as a statement or assigns to a variable; one passed straight to a
183
+ method, like the two `assert_type` lines, is not checked.
184
+
126
185
  `case x; in pattern` (one-line pattern matching) narrows the
127
186
  same way for the patterns Rigor understands — class checks,
128
187
  literal equality, array / hash structural patterns. The
129
188
  clause-reachability check extends to bare-class `in` patterns
130
- (`in String` / `in MyClass => x`) too.
189
+ (`in String` / `in MyClass => x`) too, under the same literal
190
+ rule.
191
+
192
+ ## `respond_to?`
193
+
194
+ A truthy `respond_to?(:name)` with a literal symbol removes `nil`
195
+ from the receiver (unless `nil` itself responds to `name`) and the
196
+ members whose class Rigor knows lacks `name`. When no member could
197
+ respond, the receiver reads `Dynamic[top]` if its type is one
198
+ Rigor inferred or read from a signature, so the guarded call is
199
+ not an error. It reads `bot` if it is a literal, an array or hash
200
+ literal, a class object, or `nil` / `true` / `false`, so the
201
+ branch goes unchecked. For a literal value that is exact: it
202
+ never gains the method. A class object can gain one from a gem
203
+ Rigor has no signature for (`Time.respond_to?(:zone)` under
204
+ ActiveSupport), and its branch is then not checked either:
205
+
206
+ ```ruby
207
+ require "stringio"
208
+
209
+ value = [1, "one"].sample
210
+ assert_type('"one"', value) if value.respond_to?(:upcase)
211
+
212
+ io = STDOUT
213
+ assert_type("Dynamic[top]", io) if io.respond_to?(:string)
214
+
215
+ count = 3
216
+ assert_type("bot", count) if count.respond_to?(:upcase)
217
+ ```
131
218
 
132
219
  ## Boolean composition
133
220
 
@@ -153,7 +240,7 @@ end
153
240
  ```
154
241
 
155
242
  The `is_a?` narrowed `x` to `Integer`, then the integer
156
- comparison narrowed it further to `int<1, max>`.
243
+ comparison narrowed it further to `Integer[1..]`.
157
244
 
158
245
  ## Integer comparisons
159
246
 
@@ -165,7 +252,7 @@ integer ranges:
165
252
  def safe_index(arr, n)
166
253
  return :empty if arr.empty?
167
254
  return :out_of_range if n < 0 || n >= arr.size
168
- # n: int<0, arr.size - 1> (in practice: int<0, max>
255
+ # n: Integer[0..arr.size - 1] (in practice: Integer[0..]
169
256
  # tightened against `n >= arr.size`)
170
257
  arr.fetch(n)
171
258
  end
@@ -176,10 +263,43 @@ Range comparisons compose with literals:
176
263
  ```ruby
177
264
  n = some_input
178
265
  if n.between?(1, 9)
179
- # n: int<1, 9>
266
+ # n: Integer[1..9]
180
267
  end
181
268
  ```
182
269
 
270
+ ## Float comparisons
271
+
272
+ A `Float` narrows on the *true* edge only. `x > 0.0` is false for
273
+ `NaN`, so the values that pass are a range, `Float[0.0..]`, but the
274
+ values that fail are everything else *plus* `NaN`, which no range
275
+ spells, so the else branch keeps `Float`:
276
+
277
+ ```ruby
278
+ x = Float(input)
279
+ if x > 0.0
280
+ # x: Float[0.0..]
281
+ else
282
+ # x: Float
283
+ end
284
+ if x < 1.0
285
+ # x: Float[...1.0] (Ruby's exclusive end)
286
+ end
287
+ if x.between?(0.0, 1.0)
288
+ # x: Float[0.0..1.0]
289
+ end
290
+ ```
291
+
292
+ `x.nan?` narrows its *false* edge to `non-nan-float`, and
293
+ `x.finite?` its *true* edge to `finite-float`. The range is the
294
+ one Ruby's `Range#cover?` would accept, so `Float[0.0..]` contains
295
+ `Float::INFINITY` and never `Float::NAN`.
296
+
297
+ Some calls produce a bounded Float on their own: `rand(0.0...1.0)`
298
+ and `Random.rand(1.0..2.0)` are the range they name, `Math.sqrt(n)` of a `non-negative-int` is
299
+ `Float[0.0..]`, and `x.clamp(0.0, 1.0)` or `x.clamp(0.0..1.0)` on a
300
+ bounded `x` is the bracket. A bounded Float knows it is not `NaN`,
301
+ so `u.nan?` folds to `false` and `u.round` to an integer range.
302
+
183
303
  ## Predicate methods on refinements
184
304
 
185
305
  Rigor recognises a small set of "type-carrier predicate
@@ -310,12 +430,10 @@ not chase mutation).
310
430
  A few forms you might expect that Rigor does **not** narrow
311
431
  today:
312
432
 
313
- - `respond_to?(:method_name)` records only a non-nil
314
- narrowing — a truthy check with a statically-known symbol
315
- removes `nil` from the receiver (since `nil` does not
316
- respond to most methods) — but it does **not** yet expose a
317
- structural "this object responds to that method" capability
318
- fact you could dispatch against.
433
+ - `respond_to?(:method_name)` narrows by removing members (see
434
+ [`respond_to?`](#respond_to) below), but it does **not** yet
435
+ expose a structural "this object responds to that method"
436
+ capability fact you could dispatch against.
319
437
  - `frozen?` and other mutation guards — Rigor does not track
320
438
  mutability as a narrowing fact yet.
321
439
  - Open-ended class-comparison via `===` against arbitrary