rigortype 0.3.9 → 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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 857b76366640c2aa188649c7fe6af483d599bc05e5f185c4d8285b093c092fdd
4
- data.tar.gz: 8e281f8668221704dd74c0b8d942830f6e58b252135db54142a70543bcfdf839
3
+ metadata.gz: 96e337b134c8bcb33bf20707e88ffbe16ff98b94513d483043e18432d9bf6328
4
+ data.tar.gz: e46a5af02a699ef2d0ad41e7b8b94e2d5eac02d1085d30fc03831df208ef930e
5
5
  SHA512:
6
- metadata.gz: aaea4bc6f1073c80307375365f111ba58c198cef0852a63abe5476c1ffa937754b0e45a506b69a052e685724d24fa44689f4f8bfb375f902b7e53521eea7ae4d
7
- data.tar.gz: 846d488914e0046afce19772fa8295ab99b068acf8e94a04d57590f9feb7718ebca29858ca78f47435a1542e7c1c8cbf000ca983619b3ebdb684650f8c8fc7aa
6
+ metadata.gz: cc7a7cadf53373e66de5ff268b2a44452d42972cbd9c1683825e2809480573bf195d9c26252d0522ddac2ae3f39f9dc665768d61cbb7c6e63ee6d60eaa17b87a
7
+ data.tar.gz: 05b82b8662a6e5b0283a04eca851d0bb6aa1031f55fd1e99525c5b884e433a30b89d519486f6fceebca95e47a306f721766adecc13d4c68134c833e9e525304a
data/README.md CHANGED
@@ -231,8 +231,8 @@ rigor docs --list # list every bundled page
231
231
 
232
232
  ## Status
233
233
 
234
- Current release: **`v0.3.9`** (2026-09-12) — on the
235
- `0.3.x` line, continuing the evaluation era opened by `v0.2.0`, the first
234
+ Current release: **`v0.4.0`** (2026-09-27) — opening the
235
+ `0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
236
236
  publicly-announced (general / evaluation) release. The line publishes
237
237
  an enumerated [compatibility surface](docs/compatibility.md) as a
238
238
  minor-non-break trial, rehearsing the contract that hard-freezes at
@@ -0,0 +1,51 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- Enumerable#detect with an ifnone argument ---
4
+ #
5
+ # CRuby binds `detect` and `find` to one C function (`enum.c`:
6
+ # `rb_define_method(rb_mEnumerable, "detect", enum_find, -1)`), so
7
+ # `detect(ifnone)` answers `ifnone.call` when no element matches, exactly as
8
+ # `find(ifnone)` does. Upstream `ruby/rbs` declares the two apart, and only
9
+ # `find` carries the fallback arm:
10
+ #
11
+ # def find: () { (Elem) -> boolish } -> Elem?
12
+ # | () -> ::Enumerator[Elem, Elem?]
13
+ # | [T] (_NotFound[T] ifnone) { (Elem) -> boolish } -> (Elem | T)
14
+ # | [T] (_NotFound[T] ifnone) -> ::Enumerator[Elem, Elem | T]
15
+ # def detect: (?Proc ifnone) { (Elem) -> boolish } -> Elem?
16
+ # | (?Proc ifnone) -> ::Enumerator[Elem, Elem?]
17
+ #
18
+ # so `r = { a: 1 }.detect(-> { 0 }) { |k, v| v > 5 }` typed `[:a, 1]?`
19
+ # although it is `0` at runtime, and `r + 1` reported
20
+ # `call.possible-nil-receiver` on correct code. No core or stdlib class
21
+ # declares a `detect` of its own except `Array`, which rbs 4.1 and later
22
+ # alias to `Array#find`; `Hash`, `Range`, `Set`, `Struct`, `Enumerator`,
23
+ # `Enumerator::Lazy` (whose lazy method table leaves `detect` eager), `IO`,
24
+ # `Dir`, `ENV` and every other includer inherit this one, as does `Array`
25
+ # before rbs 4.1.
26
+ #
27
+ # Upstream's `?Proc` parameter also rejected a valid fallback that is not a
28
+ # `Proc` — `nil` (no fallback) or any object answering `call` — with
29
+ # `call.argument-type-mismatch`. Such an argument reaches the `_NotFound[T]`
30
+ # arms below, so `detect` checks its argument as `find` does.
31
+ #
32
+ # The overloads below are prepended to upstream's through `| ...` (rbs
33
+ # unshifts a continuation's overloads, so they are tried first). The last two
34
+ # are `find`'s ifnone arms. The first two restate them for a `Proc` argument:
35
+ # `OverloadSelector`'s strict pass skips an interface-typed parameter such as
36
+ # `_NotFound[T]`, so without them upstream's strictly typed `(?Proc ifnone)`
37
+ # arm would win a lambda and answer `Elem?` again. A block-only
38
+ # `detect { ... }` passes no argument, skips all four on arity, and keeps
39
+ # upstream's `Elem?`.
40
+ #
41
+ # The continuation's base is in rbs core on every supported release, so the
42
+ # file loads unconditionally. Once an upstream release aligns `detect` with
43
+ # `find`, gate this file off that release through
44
+ # `RbsLoader::RBS_LINE_CORE_OVERLAYS`, as `hash_rbs3.rbs` is.
45
+ module Enumerable[unchecked out Elem]
46
+ def detect: [T] (^() -> T ifnone) { (Elem) -> boolish } -> (Elem | T)
47
+ | [T] (^() -> T ifnone) -> ::Enumerator[Elem, Elem | T]
48
+ | [T] (_NotFound[T] ifnone) { (Elem) -> boolish } -> (Elem | T)
49
+ | [T] (_NotFound[T] ifnone) -> ::Enumerator[Elem, Elem | T]
50
+ | ...
51
+ end
@@ -0,0 +1,84 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- Enumerator::Lazy's redefined enumerable methods ---
4
+ #
5
+ # Issue #1121. `Enumerator::Lazy` is not merely an `Enumerator`: CRuby
6
+ # redefines most of `Enumerable` on it so that the call CHAINS another lazy
7
+ # enumerator instead of forcing the source (`enumerator.c`'s lazy method
8
+ # table: `collect` / `map`, `find_all` / `select` / `filter`, `reject`,
9
+ # `grep`, `grep_v`, `flat_map` / `collect_concat`, `take`, `take_while`,
10
+ # `drop`, `drop_while`, `uniq`, `zip`, `filter_map`). Upstream `ruby/rbs`
11
+ # declares none of them on `Enumerator::Lazy` — its whole body is `force`
12
+ # plus `compact` and `eager` — so dispatch walks up to `Enumerable` and
13
+ # adopts THAT class's eager return type:
14
+ #
15
+ # [1, 2, 3].lazy.map { |x| x * 2 } # Enumerable#map -> Array[U], not Lazy
16
+ #
17
+ # The receiver class is lost, and with it every terminal method only a lazy
18
+ # enumerator answers: `…map { … }.force` / `.eager` report
19
+ # `call.undefined-method` on correct code, on 124 `.lazy` sites in one
20
+ # survey corpus (gitlab services and graphql, rails actiontext, sidekiq,
21
+ # rubocop).
22
+ #
23
+ # The entries below mirror each upstream `Enumerable` declaration the lazy
24
+ # method replaces, with the return rewritten to `Enumerator::Lazy`. Two
25
+ # deliberate exclusions:
26
+ #
27
+ # * The single type argument spelling (`Enumerator::Lazy[U]`, not
28
+ # `[U, Return]`) mirrors upstream's own `Enumerable#lazy:
29
+ # () -> Enumerator::Lazy[Elem]` — the trailing `Return` parameter is
30
+ # defaulted (`void`), and Rigor binds the supplied prefix (see
31
+ # `docs/internal-spec/inference-engine.md` § generic dispatch).
32
+ # * The no-block arms of the block-taking methods are NOT carried over.
33
+ # CRuby RAISES for a blockless `lazy.map` / `select` / `reject` /
34
+ # `find_all` / `take_while` ("tried to call lazy map without a block"), so
35
+ # the inherited `Enumerable` arm is a wrong answer for a lazy receiver
36
+ # rather than a harmless fallback.
37
+ #
38
+ # `zip` is left to `Enumerable` for now: its lazy form yields
39
+ # `[elem, other_elem]` pairs through `_Each`, and the pair shape upstream
40
+ # declares for the eager form is not what the lazy chain hands downstream.
41
+ # `compact`, `eager` and `force` are upstream already and MUST NOT be
42
+ # duplicated here — a second declaration of a method `rbs` core already
43
+ # declares in this class is a `DuplicatedMethodDefinitionError`, which
44
+ # degrades the whole class to `Dynamic[top]`. (`eager` in particular is
45
+ # declared by rbs 4.x only; a `| ...` overload continuation that would
46
+ # declare it on the 3.10 line as well raises `InvalidOverloadMethodError`
47
+ # there, for the same whole-class cost, so `.eager` stays the 3.10 line's
48
+ # own gap.)
49
+ class Enumerator::Lazy[out E, out R = void]
50
+ def collect: [U] () { (E) -> U } -> Enumerator::Lazy[U]
51
+
52
+ def map: [U] () { (E) -> U } -> Enumerator::Lazy[U]
53
+
54
+ def find_all: () { (E) -> boolish } -> Enumerator::Lazy[E]
55
+
56
+ def select: () { (E) -> boolish } -> Enumerator::Lazy[E]
57
+
58
+ def filter: () { (E) -> boolish } -> Enumerator::Lazy[E]
59
+
60
+ def reject: () { (E) -> boolish } -> Enumerator::Lazy[E]
61
+
62
+ def filter_map: [U] () { (E) -> (nil | false | U) } -> Enumerator::Lazy[U]
63
+
64
+ def flat_map: [U] () { (E) -> (Array[U] | U) } -> Enumerator::Lazy[U]
65
+
66
+ def collect_concat: [U] () { (E) -> (Array[U] | U) } -> Enumerator::Lazy[U]
67
+
68
+ def take: (Integer n) -> Enumerator::Lazy[E]
69
+
70
+ def take_while: () { (E) -> boolish } -> Enumerator::Lazy[E]
71
+
72
+ def drop: (Integer n) -> Enumerator::Lazy[E]
73
+
74
+ def drop_while: () { (E) -> boolish } -> Enumerator::Lazy[E]
75
+
76
+ def uniq: () -> Enumerator::Lazy[E]
77
+ | () { (E item) -> untyped } -> Enumerator::Lazy[E]
78
+
79
+ def grep: (untyped pattern) -> Enumerator::Lazy[E]
80
+ | [U] (untyped pattern) { (E) -> U } -> Enumerator::Lazy[U]
81
+
82
+ def grep_v: (untyped pattern) -> Enumerator::Lazy[E]
83
+ | [U] (untyped pattern) { (E) -> U } -> Enumerator::Lazy[U]
84
+ end
@@ -0,0 +1,41 @@
1
+ # Rigor core overlay — supplemental core signatures, rbs 3.x line only.
2
+ #
3
+ # --- Hash#transform_keys / #transform_keys! with a replacements hash ---
4
+ #
5
+ # Ruby 3.0 added the replacements-hash argument
6
+ # (`h.transform_keys({ a: :z }) { |k| k.to_s }`), and rbs 4.0 declares it.
7
+ # Every rbs 3.x release declares only the blockless `Enumerator` form and
8
+ # the block form, both with no positional parameter:
9
+ #
10
+ # def transform_keys: () -> Enumerator[K, Hash[untyped, V]]
11
+ # | [A] () { (K) -> A } -> Hash[A, V]
12
+ # def transform_keys!: () -> Enumerator[K, self]
13
+ # | () { (K) -> K } -> self
14
+ #
15
+ # so under rbs 3.x a correct replacements call reports `call.wrong-arity`
16
+ # ("given 1, expected 0"), and `transform_keys!` answers `Enumerator`
17
+ # instead of the receiver.
18
+ #
19
+ # The overloads below are rbs 4.2's, prepended to 3.x's own through
20
+ # `| ...`. `RbsLoader::RBS_LINE_CORE_OVERLAYS` loads this file only on
21
+ # the rbs 3.x line: on 4.x the continuation would add a second copy of
22
+ # the overloads upstream already declares.
23
+ #
24
+ # Two spellings differ from rbs 4.2 so that every name resolves on every
25
+ # rbs 3.x release the gemspec admits:
26
+ #
27
+ # * `hash[K, V]` is written out as `Hash[K, V] | _ToHash[K, V]`: the
28
+ # `hash` alias arrived in rbs 3.3, `_ToHash` is in 3.0.
29
+ # * `hash[_Key, K2]`'s key is `untyped`: `Hash::_Key` arrived in rbs 3.4.
30
+ # It constrains only `hash` / `eql?`, which every object answers.
31
+ #
32
+ # The non-bang return type is answered ahead of either declaration by
33
+ # `MethodDispatcher::HashTransformKeysFolding`.
34
+ class Hash[unchecked out K, unchecked out V]
35
+ def transform_keys: [K2] (Hash[untyped, K2] | _ToHash[untyped, K2] replacements) -> Hash[K | K2, V]
36
+ | [K2] (Hash[untyped, K2] | _ToHash[untyped, K2] replacements) { (K old_key) -> K2 } -> Hash[K2, V]
37
+ | ...
38
+
39
+ def transform_keys!: (Hash[K, K] | _ToHash[K, K] replacements) ?{ (K key) -> K } -> self
40
+ | ...
41
+ end
@@ -0,0 +1,40 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- Process.last_status ---
4
+ #
5
+ # CRuby's `process.c` defines `Process.last_status` as a singleton method
6
+ # answering the calling thread's last child status — the object `$?` reads:
7
+ #
8
+ # rb_define_singleton_method(rb_mProcess, "last_status", proc_s_last_status, 0);
9
+ #
10
+ # It is `nil` until the thread has waited on a child process. Upstream
11
+ # `ruby/rbs` names `::last_status` in `Process`'s doc comment but has never
12
+ # declared it (no commit to `core/process.rbs` through 4.2 adds a `def`), so
13
+ #
14
+ # system("true")
15
+ # status = Process.last_status
16
+ #
17
+ # reported `call.undefined-method` on correct code. The return mirrors rbs
18
+ # core's own `$?: Process::Status?`.
19
+ #
20
+ # Declared on a module `Process` extends, not with `def self.last_status`.
21
+ # A second direct `def` of the method is a `DuplicatedMethodDefinitionError`
22
+ # that takes `Process`'s whole singleton surface down to `Dynamic[top]`, and
23
+ # this method is one a project is likely to have declared already: a
24
+ # `sig/` written for Steep, which reads the same rbs gap, or a later rbs
25
+ # release that fills it. A direct `def` from either source overrides a
26
+ # method `Process` gets through `extend`, on rbs 3.x and 4.x alike, so the
27
+ # overlay stands down instead of colliding.
28
+ #
29
+ # Unconditional: `Process` and `Process::Status` are rbs core on every
30
+ # supported release. `MethodDispatcher::ProcessFolding` refines the answer
31
+ # past a subprocess call, where `$?` is known to be set.
32
+ module RigorCoreOverlay
33
+ module ProcessLastStatus
34
+ def last_status: () -> Process::Status?
35
+ end
36
+ end
37
+
38
+ module Process
39
+ extend RigorCoreOverlay::ProcessLastStatus
40
+ end
@@ -0,0 +1,33 @@
1
+ # Rigor core overlay — supplemental core signatures.
2
+ #
3
+ # --- StringIO includes Enumerable[String] ---
4
+ #
5
+ # CRuby's `ext/stringio/stringio.c` makes `StringIO` Enumerable:
6
+ #
7
+ # rb_include_module(StringIO, rb_mEnumerable);
8
+ #
9
+ # and `StringIO#each` yields the stream's lines, so a `StringIO` is an
10
+ # `Enumerable[String]` exactly as rbs core declares `IO` to be. Upstream
11
+ # `ruby/rbs` declares `class StringIO` with no `include` on every release the
12
+ # gemspec supports, so `StringIO.new(s).detect { ... }`, `.map`,
13
+ # `.each_with_index` and the rest of Enumerable reported
14
+ # `call.undefined-method` on correct code, and a block-taking `.select` fell
15
+ # through to `Kernel#select` (the IO multiplexer) and reported
16
+ # `call.wrong-arity`.
17
+ #
18
+ # The mixin sits behind the class in the ancestry, so every method StringIO
19
+ # declares itself (`each`, `each_line`, `each_char`, `readlines`, ...) keeps
20
+ # upstream's signature. A duplicate include, should upstream add one, builds
21
+ # on both rbs lines.
22
+ #
23
+ # Unconditional — NOT in `RbsLoader::LIBRARY_SUPPLEMENT_CORE_OVERLAYS`,
24
+ # because every environment Rigor builds already declares `StringIO`: in rbs
25
+ # `core/string_io.rbs` before 3.7, and from 3.7 on because
26
+ # `RBS::EnvironmentLoader#load` adds the `stringio` library itself whenever
27
+ # it loads core ("For migrating stringio to stdlib"). Gating on `stringio`
28
+ # would drop the mixin on rbs < 3.7, which has no such library, and from any
29
+ # environment that does not name it. The premise is pinned in
30
+ # `spec/rigor/environment/bundled_rbs_definition_build_spec.rb`.
31
+ class StringIO
32
+ include Enumerable[String]
33
+ end
@@ -173,7 +173,7 @@ classes:
173
173
  instance_variable_set:
174
174
  effects: []
175
175
  mutates: receiver
176
- why: "writes another object's state; the label follows the receiver's ownership (mutate.self on self, mutate.instance on a parameter, taint otherwise)"
176
+ why: "writes another object's state; the label follows the receiver's ownership (effect-summaries.md § Ownership)"
177
177
  instance_variable_get: { effects: [], why: "reads an ivar — object state, not global state" }
178
178
  hash: { effects: [], why: "per-process SipHash salt makes it irreproducible (`NON_REPRODUCIBLE_SELECTORS`), but irreproducibility is a fold hazard, not an observable effect" }
179
179
  object_id: { effects: [], why: "per-process identity — a fold hazard (`NON_REPRODUCIBLE_SELECTORS`), not an effect" }
@@ -235,12 +235,12 @@ classes:
235
235
  Hash:
236
236
  posture: value
237
237
  mutators: hash
238
- why: "a value class; mutation is ownership-classified from `MutationWidening::HASH_MUTATORS`"
238
+ why: "a value class; mutation is ownership-classified from `MutationClassifier::HASH_MUTATORS`, the union of the widening's `HASH_MUTATORS` and `HashLookupMutation::MUTATORS` plus `rehash`"
239
239
 
240
240
  String:
241
241
  posture: value
242
242
  mutators: string
243
- why: "a value class; mutation is ownership-classified from `MutationClassifier::STRING_MUTATORS`"
243
+ why: "a value class; mutation is ownership-classified from `StringMutation::MUTATORS`"
244
244
 
245
245
  Symbol: { posture: value, why: "immutable value class" }
246
246
  Integer: { posture: value, why: "immutable value class" }
@@ -68,6 +68,8 @@ 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?
71
73
 
72
74
  # Issue #673 — `core_ext/object/to_query`, `core_ext/object/duplicable` and
73
75
  # `core_ext/object/instance_variables` all hang off `Object`, so every
@@ -93,6 +95,50 @@ class Object
93
95
  # `instance_variable_names`.
94
96
  def instance_values: () -> Hash[String, untyped]
95
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
96
142
  end
97
143
 
98
144
  # `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
@@ -149,6 +195,10 @@ class String
149
195
  def titlecase: () -> String
150
196
  def dasherize: () -> String
151
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
152
202
 
153
203
  # `core_ext/string/filters`
154
204
  def squish: () -> String
@@ -182,6 +232,12 @@ class String
182
232
  def to_datetime: () -> DateTime
183
233
  def to_hours: () -> Float
184
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
+
185
241
  # `core_ext/string/access`
186
242
  def at: (Integer | Range[Integer] | Regexp position) -> String?
187
243
  def from: (Integer position) -> String
@@ -194,6 +250,7 @@ class String
194
250
 
195
251
  # `core_ext/string/multibyte`
196
252
  def mb_chars: () -> untyped
253
+ def is_utf8?: () -> bool
197
254
 
198
255
  # `core_ext/string/inquiry`
199
256
  def inquiry: () -> untyped # ActiveSupport::StringInquirer
@@ -202,6 +259,17 @@ class String
202
259
  def exclude?: (String) -> bool
203
260
  end
204
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
+
205
273
  # ---------------------------------------------------------------
206
274
  # Integer — `core_ext/integer/*`
207
275
  # ---------------------------------------------------------------
@@ -762,6 +830,8 @@ class Array[unchecked out Elem]
762
830
  def fourth: () -> Elem?
763
831
  def fifth: () -> Elem?
764
832
  def forty_two: () -> Elem?
833
+ def second_to_last: () -> Elem?
834
+ def third_to_last: () -> Elem?
765
835
 
766
836
  # `core_ext/array/grouping`
767
837
  def in_groups_of: (Integer number, ?untyped fill_with) -> Array[Array[Elem]]
@@ -783,6 +853,9 @@ class Array[unchecked out Elem]
783
853
  # `core_ext/array/extract`
784
854
  def extract!: () { (Elem) -> bool } -> Array[Elem]
785
855
 
856
+ # `core_ext/array/extract_options`
857
+ def extract_options!: () -> Hash[untyped, untyped]
858
+
786
859
  # `core_ext/object/blank` / `core_ext/enumerable` — both
787
860
  # ActiveSupport additions, shipped on Array specifically.
788
861
  def compact_blank: () -> Array[Elem]
@@ -810,6 +883,13 @@ module Enumerable[unchecked out Elem]
810
883
  def minimum: (Symbol | String) -> untyped
811
884
  def sole: () -> Elem
812
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]
813
893
  end
814
894
 
815
895
  # ---------------------------------------------------------------
@@ -831,6 +911,9 @@ class Hash[unchecked out K, unchecked out V]
831
911
  def deep_transform_keys!: () { (K) -> K } -> self
832
912
  def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
833
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
834
917
 
835
918
  # `core_ext/hash/deep_dup`
836
919
  def deep_dup: () -> Hash[K, V]
@@ -840,6 +923,9 @@ class Hash[unchecked out K, unchecked out V]
840
923
  | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
841
924
  def deep_merge!: (Hash[K, V]) -> self
842
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
843
929
 
844
930
  # `core_ext/hash/except` — `Hash#except` is in core RBS as of
845
931
  # Ruby 3.0+; `except!` is ActiveSupport-only. ActiveSupport
@@ -853,8 +939,10 @@ class Hash[unchecked out K, unchecked out V]
853
939
  def to_param: (?String namespace) -> String
854
940
  def to_xml: (**untyped) -> String
855
941
 
856
- # `core_ext/hash/indifferent_access`
942
+ # `core_ext/hash/indifferent_access` — `nested_under_indifferent_access`
943
+ # is an `alias` of `with_indifferent_access`.
857
944
  def with_indifferent_access: () -> untyped
945
+ def nested_under_indifferent_access: () -> untyped
858
946
 
859
947
  # `core_ext/hash/conversions`
860
948
  def self.from_xml: (String, ?Symbol disallowed_types) -> Hash[String, untyped]
@@ -864,14 +952,49 @@ class Hash[unchecked out K, unchecked out V]
864
952
  def compact_blank: () -> Hash[K, V]
865
953
  def compact_blank!: () -> self
866
954
 
867
- # `core_ext/hash/reverse_merge`
868
- 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]
869
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
870
969
 
871
970
  # `core_ext/hash/slice` — `Hash#except` is in core RBS (Ruby 3.0+);
872
971
  # `Hash#slice` is in core RBS (Ruby 2.5+); the bang variants are
873
- # 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.
874
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
875
998
  end
876
999
 
877
1000
  # ---------------------------------------------------------------
@@ -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:
@@ -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
 
@@ -343,12 +430,10 @@ not chase mutation).
343
430
  A few forms you might expect that Rigor does **not** narrow
344
431
  today:
345
432
 
346
- - `respond_to?(:method_name)` records only a non-nil
347
- narrowing — a truthy check with a statically-known symbol
348
- removes `nil` from the receiver (since `nil` does not
349
- respond to most methods) — but it does **not** yet expose a
350
- structural "this object responds to that method" capability
351
- 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.
352
437
  - `frozen?` and other mutation guards — Rigor does not track
353
438
  mutability as a narrowing fact yet.
354
439
  - Open-ended class-comparison via `===` against arbitrary