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
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../type"
4
+ require_relative "../reflection"
5
+ require_relative "external_ancestor_resolution"
6
+ require_relative "project_method_ownership"
4
7
 
5
8
  module Rigor
6
9
  module Inference
@@ -30,14 +33,19 @@ module Rigor
30
33
  # therefore covers ONLY the core-and-stdlib surface where immediate invocation is part of the documented
31
34
  # contract:
32
35
  #
33
- # - `Array`, `Hash`, `Range`, `Integer`, `Enumerator::Lazy` iteration methods (`each`, `map`, `select`,
34
- # `reject`, `flat_map`, `find`/`detect`, `any?`, `all?`, `none?`, `one?`, `count`, `inject`/`reduce`,
35
- # `each_with_index`, `each_with_object`, `min_by`, `max_by`, `sort_by`, `partition`, `group_by`,
36
- # `tally`, `sum`, `take_while`, `drop_while`, `chunk_while`, `slice_when`, `zip`, `collect`,
36
+ # - `Array`, `Hash`, `Range`, `Set`, `Enumerator`, `Enumerator::Lazy` iteration methods (`each`, `map`,
37
+ # `select`, `reject`, `flat_map`, `find`/`detect`, `any?`, `all?`, `none?`, `one?`, `count`,
38
+ # `inject`/`reduce`, `each_with_index`, `each_with_object`, `min_by`, `max_by`, `sort_by`, `partition`,
39
+ # `group_by`, `tally`, `sum`, `take_while`, `drop_while`, `chunk_while`, `slice_when`, `zip`, `collect`,
37
40
  # `collect_concat`, `filter`, `filter_map`).
38
41
  # - `Hash`-only iteration: `each_pair`, `each_key`, `each_value`, `transform_keys`, `transform_values`.
39
42
  # - `Integer#times`, `Integer#upto`, `Integer#downto`, `Range#each`, `Range#step`.
40
- # - `Object#tap`, `Object#then`, `Object#yield_self`.
43
+ # - `IO` / `File` / `StringIO` line iteration (`each`, `each_line`, `each_byte`, `each_char`,
44
+ # `each_codepoint`, the singleton `foreach`) and the same Enumerable methods minus
45
+ # {DEFERRED_ENUMERATOR_METHODS}.
46
+ # - `Object#tap`, `Object#then`, `Object#yield_self`. The stronger "yields exactly once, before returning"
47
+ # fact for these three lives in {BlockCallTiming}; that table is expected to move to the same
48
+ # `RBS::Extended` call-timing effect as this one.
41
49
  # - Tuple/HashShape carriers map to Array/Hash for catalogue lookup so a literal `[1, 2, 3].each { ... }`
42
50
  # is recognised.
43
51
  #
@@ -46,32 +54,80 @@ module Rigor
46
54
  # retained. False positives in this catalogue would silently weaken the soundness of fact retention in
47
55
  # later sub-phases.
48
56
  #
57
+ # Issue #1234 — a project class is outside the catalogue by name, yet `class Shelf; include Enumerable`
58
+ # answers `find` with `Enumerable#find` all the same. Given the `scope:` whose discovery tables know the
59
+ # project, a `Nominal` receiver of a project class classifies through its ancestry
60
+ # ({.ancestry_non_escaping?}): the method must be one the project does not define anywhere in that
61
+ # ancestry, and the first ancestor outside the project that declares it must be a catalogued class or
62
+ # `Enumerable` ({MIXIN_NON_ESCAPING}). An ancestor the RBS environment does not know could declare
63
+ # anything, so meeting one first declines.
64
+ #
49
65
  # The analyzer is a pure query. It MUST NOT mutate the receiver type or scope, MUST NOT raise on
50
66
  # unrecognised inputs, and MUST be deterministic for a given input.
51
67
  module ClosureEscapeAnalyzer
52
68
  module_function
53
69
 
54
- # @param receiver_type [Rigor::Type, nil]
55
- # @param method_name [Symbol]
56
- # @param environment [Rigor::Environment, nil] reserved for the future sub-phase that consults
70
+ # @param environment — reserved for the future sub-phase that consults
57
71
  # `RBS::Extended` call-timing effects; sub-phase 3a ignores it.
58
- # @return [Symbol] one of `:non_escaping`, `:escaping`, `:unknown`.
59
- def classify(receiver_type:, method_name:, environment: nil) # rubocop:disable Lint/UnusedMethodArgument
72
+ # @param scope — the project's discovery tables, for the ancestry step; without it a project class
73
+ # stays `:unknown`.
74
+ # @return one of `:non_escaping`, `:escaping`, `:unknown`.
75
+ def classify(receiver_type:, method_name:, environment: nil, scope: nil) # rubocop:disable Lint/UnusedMethodArgument
60
76
  return :unknown if receiver_type.nil?
61
77
 
78
+ method_sym = method_name.to_sym
62
79
  class_name = receiver_class_name(receiver_type)
63
- return :unknown if class_name.nil?
80
+ if class_name
81
+ return :non_escaping if non_escaping?(class_name, method_sym)
82
+ return :escaping if escaping?(class_name, method_sym)
83
+ end
84
+
85
+ instance_class = instance_carrier_class_name(receiver_type)
86
+ return :unknown if instance_class.nil?
87
+
88
+ ancestry_non_escaping?(instance_class, method_sym, scope) ? :non_escaping : :unknown
89
+ end
90
+
91
+ # Issue #1234 — whether some catalogue entry lists `method_name` as an iteration method: a name that runs
92
+ # its block once per element wherever the catalogue knows the receiver. `tap` / `then` / `yield_self` are
93
+ # left out, as they run the block exactly once. This is a fact about the NAME, for the one consumer that
94
+ # asks it of an `:unknown` receiver ({BlockRepetition.may_repeat?}); it proves nothing about escape.
95
+ def iterator_name?(method_name)
96
+ ITERATOR_NAMES.include?(method_name)
97
+ end
64
98
 
99
+ # Issue #1234 — whether the NAME of a catalogued iterator is the only thing Rigor knows about the method
100
+ # `receiver_type` answers `method_name` with, so a pass may read it as repetition
101
+ # ({BlockRepetition.may_repeat?}, for an `:unknown` receiver). It holds for a receiver Rigor cannot
102
+ # see at all (`Dynamic`, `Top`), and for a class it can see whose method the project does not define.
103
+ #
104
+ # A method the project defines under a catalogued name is the project's, not the iterator, so the name
105
+ # says nothing about how often it yields: `class Vault; def select(key) = yield(key.to_s); end` runs its
106
+ # block once. "Defines" is a `def`, `define_method` or `attr_*` anywhere in a project class's ancestry
107
+ # (instance side, or singleton side for a class-object receiver), or a signature whose declaring owner
108
+ # is a project class or module. A carrier this does not recognise, or one whose class it cannot name
109
+ # (an anonymous `Struct.new` value), answers false: when Rigor cannot tell whether the project owns the
110
+ # method, it does not assume repetition. {ProjectMethodOwnership.targets} carries the carrier audit.
111
+ def repeats_by_name?(receiver_type:, method_name:, scope:)
65
112
  method_sym = method_name.to_sym
66
- return :non_escaping if non_escaping?(class_name, method_sym)
67
- return :escaping if escaping?(class_name, method_sym)
113
+ return false unless ITERATOR_NAMES.include?(method_sym)
68
114
 
69
- :unknown
115
+ targets = ProjectMethodOwnership.targets(receiver_type)
116
+ return false if targets.nil?
117
+
118
+ targets.none? { |class_name, kind| ProjectMethodOwnership.defines?(class_name, method_sym, kind, scope) }
70
119
  end
71
120
 
72
121
  class << self
73
122
  private
74
123
 
124
+ # The instance-side class a `Nominal` or an ADR-48 member carrier names, for the ancestry step.
125
+ def instance_carrier_class_name(receiver_type)
126
+ case receiver_type
127
+ when Type::Nominal, Type::StructInstance, Type::DataInstance then receiver_type.class_name&.to_s
128
+ end
129
+ end
130
+
75
131
  # Resolve a single concrete class name for catalogue lookup. Returns `nil` when the receiver carrier
76
132
  # does not name a single class (e.g. `Top`, `Dynamic[Top]`, `Union[...]`, `Bot`). `Tuple` projects to
77
133
  # `Array`; `HashShape` to `Hash`; `Singleton[C]` to `C` (so `Integer.times` would resolve as a
@@ -117,6 +173,64 @@ module Rigor
117
173
  methods = ESCAPING[class_name]
118
174
  methods ? methods.include?(method_sym) : false
119
175
  end
176
+
177
+ # Issue #1234 — a project class answers `method_sym` through the catalogued ancestor Ruby dispatches it
178
+ # to. The cheap gates come first: the name must be a catalogued iteration method, and the receiver a
179
+ # class the project declares. A definition anywhere in the project ancestry — the class's own `def
180
+ # find`, a project module's, a reopened `Enumerable`'s — is not the catalogued method and declines.
181
+ # A class the project's `sig/` declares asks its RBS definition where the method comes from; one
182
+ # without RBS walks the ancestors outside the project in method-resolution order.
183
+ def ancestry_non_escaping?(class_name, method_sym, scope)
184
+ return false if scope.nil? || !ITERATOR_NAMES.include?(method_sym)
185
+ return false unless scope.known_user_class?(class_name)
186
+
187
+ by_method = (ProjectMethodOwnership.memo(scope)[:ancestry][class_name] ||= {})
188
+ return by_method[method_sym] if by_method.key?(method_sym)
189
+
190
+ by_method[method_sym] = compute_ancestry_non_escaping?(class_name, method_sym, scope)
191
+ end
192
+
193
+ def compute_ancestry_non_escaping?(class_name, method_sym, scope)
194
+ return false if ProjectMethodOwnership.defines?(class_name, method_sym, :instance, scope)
195
+
196
+ if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
197
+ return catalogued_declaration?(method_definition(class_name, method_sym, :instance, scope), method_sym)
198
+ end
199
+
200
+ external_ancestry_non_escaping?(class_name, method_sym, scope)
201
+ end
202
+
203
+ # The RBS definition, or nil — a malformed signature is a gap, and
204
+ # {ExternalAncestorResolution.method_definition} is the one place that rescues it.
205
+ def method_definition(class_name, method_sym, kind, scope)
206
+ ExternalAncestorResolution.method_definition(class_name, method_sym, kind, scope: scope)
207
+ end
208
+
209
+ # The first external ancestor that declares the method decides. One the environment does not know may
210
+ # declare it, so it declines rather than being skipped; one that does not declare it is skipped.
211
+ def external_ancestry_non_escaping?(class_name, method_sym, scope)
212
+ scope.external_ancestor_name_candidates(class_name).each do |candidates|
213
+ known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
214
+ return false if known.nil?
215
+ return true if catalogued_owner?(known, method_sym)
216
+
217
+ definition = method_definition(known, method_sym, :instance, scope)
218
+ return catalogued_declaration?(definition, method_sym) if definition
219
+ end
220
+ false
221
+ end
222
+
223
+ def catalogued_declaration?(definition, method_sym)
224
+ return false if definition.nil? || !definition.respond_to?(:defined_in)
225
+
226
+ owner = definition.defined_in
227
+ !owner.nil? && catalogued_owner?(owner.to_s.delete_prefix("::"), method_sym)
228
+ end
229
+
230
+ def catalogued_owner?(name, method_sym)
231
+ methods = NON_ESCAPING[name] || MIXIN_NON_ESCAPING[name]
232
+ methods ? methods.include?(method_sym) : false
233
+ end
120
234
  end
121
235
 
122
236
  OBJECT_NON_ESCAPING = %i[tap then yield_self].freeze
@@ -151,9 +265,24 @@ module Rigor
151
265
  # `File.foreach(path) { case … when … then flag = true when … then return true if flag end }` classifies
152
266
  # `:unknown` and misses the loop-body re-narrowing, so a local written in one `when` arm reads its
153
267
  # pre-loop value in a sibling arm and a guarding condition folds to a spurious constant.
268
+ #
269
+ # The three are also `Enumerable[String]` over lines (rbs core declares it for `IO`;
270
+ # `data/core_overlay/string_io.rbs` for `StringIO`). An eager Enumerable method runs its block from inside
271
+ # the call, through `each`, so `io.each_with_index { … }` / `io.detect { … }` carry the same contract and
272
+ # take `ENUMERABLE_NON_ESCAPING` — minus {DEFERRED_ENUMERATOR_METHODS}, whose block outlives the call. On
273
+ # the `singleton(File)` / `singleton(IO)` side those names only ever meet `IO.select`, which takes no
274
+ # block and so never retains one. The key is the exact class name: a subclass (`Tempfile`, a user
275
+ # `StringIO` subclass) stays `:unknown`.
154
276
  IO_ITERATION = %i[each_line each each_byte each_char each_codepoint].freeze
155
277
  IO_SINGLETON_ITERATION = %i[foreach].freeze
156
278
 
279
+ # Enumerable methods that return an Enumerator holding the block and run it only when that Enumerator is
280
+ # consumed, so the block may run after locals it reads or writes have changed. They are NOT
281
+ # non-escaping. `ENUMERABLE_NON_ESCAPING` still lists them for the collection entries — a defect tracked
282
+ # as #1311; the stream entries below leave them out rather than inherit it.
283
+ DEFERRED_ENUMERATOR_METHODS = %i[chunk chunk_while slice_when slice_before slice_after].freeze
284
+ STREAM_ENUMERABLE_NON_ESCAPING = (ENUMERABLE_NON_ESCAPING - DEFERRED_ENUMERATOR_METHODS).freeze
285
+
157
286
  NON_ESCAPING = {
158
287
  "Array" => (ENUMERABLE_NON_ESCAPING + ARRAY_EXTRA).freeze,
159
288
  "Hash" => (ENUMERABLE_NON_ESCAPING + HASH_EXTRA).freeze,
@@ -162,11 +291,24 @@ module Rigor
162
291
  "Integer" => INTEGER_EXTRA,
163
292
  "Enumerator" => ENUMERABLE_NON_ESCAPING,
164
293
  "Enumerator::Lazy" => ENUMERABLE_NON_ESCAPING,
165
- "IO" => (IO_ITERATION + IO_SINGLETON_ITERATION).freeze,
166
- "File" => (IO_ITERATION + IO_SINGLETON_ITERATION).freeze,
167
- "StringIO" => IO_ITERATION
294
+ "IO" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION | IO_SINGLETON_ITERATION).freeze,
295
+ "File" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION | IO_SINGLETON_ITERATION).freeze,
296
+ "StringIO" => (STREAM_ENUMERABLE_NON_ESCAPING | IO_ITERATION).freeze
297
+ }.freeze
298
+
299
+ # Issue #1234 — the catalogued modules a project class reaches only through its ancestry, read only by
300
+ # {.ancestry_non_escaping?} after the project has been ruled out as the method's owner. `Enumerable`'s
301
+ # methods run the block from inside the call, through the includer's `each` — minus
302
+ # {DEFERRED_ENUMERATOR_METHODS}, whose Enumerator outlives it, and minus `each` itself, which
303
+ # `Enumerable` does not declare: the includer supplies it, so nothing here speaks for it. For that
304
+ # reason a receiver typed as the bare module is not a key of {NON_ESCAPING} either.
305
+ MIXIN_NON_ESCAPING = {
306
+ "Enumerable" => (STREAM_ENUMERABLE_NON_ESCAPING - %i[each]).freeze
168
307
  }.freeze
169
308
 
309
+ # Every name a {NON_ESCAPING} entry lists as iteration, for {.iterator_name?} and the ancestry gate.
310
+ ITERATOR_NAMES = (NON_ESCAPING.values.flatten.to_set - OBJECT_NON_ESCAPING).freeze
311
+
170
312
  # Methods that are documented to **retain** the block past the call. The block is stored or scheduled,
171
313
  # so outer narrowing facts on writeable captured locals cannot survive.
172
314
  ESCAPING = {
@@ -35,23 +35,28 @@ module Rigor
35
35
  # argument and the value the last.
36
36
  HASH_CONTENT_ADDERS = %i[[]= store].to_set.freeze
37
37
 
38
- # String content-mutators that append to the buffer. String carries no element parameter, so
39
- # these contribute nothing to a join — they are listed so the orchestrator recognises them as
40
- # content mutators (the binding already widens to `String` via normal typing); the join
41
- # helpers below short-circuit on a non-collection pre-state.
42
- STRING_CONTENT_ADDERS = %i[<< concat prepend insert replace].to_set.freeze
38
+ # Every method name that adds to a collection's CONTENT — the evidence the joins read, and the
39
+ # gate the straight-line path types its arguments behind. A String carries no element
40
+ # parameter, so it has no adders of its own; `StatementEvaluator::CONTENT_MUTATORS` adds the
41
+ # String mutators to what its scans count.
42
+ CONTENT_ADDERS = (ARRAY_CONTENT_ADDERS | HASH_CONTENT_ADDERS).freeze
43
43
 
44
- # Every method name that mutates a collection's CONTENT — the union the orchestrators scan a
45
- # block body for, and the gate the straight-line path types its arguments behind.
46
- CONTENT_ADDERS = (ARRAY_CONTENT_ADDERS | HASH_CONTENT_ADDERS | STRING_CONTENT_ADDERS).freeze
44
+ # The probes {#could_be_range?} passes to `accepts` — interned because the questions are
45
+ # asked once per `[]=` index member.
46
+ RANGE_INDEX_PROBE = Type::Combinator.nominal_of("Range")
47
+ OBJECT_INDEX_PROBE = Type::Combinator.nominal_of("Object")
48
+ ARRAY_PROBE = Type::Combinator.nominal_of("Array")
49
+ private_constant :RANGE_INDEX_PROBE, :OBJECT_INDEX_PROBE, :ARRAY_PROBE
47
50
 
48
51
  module_function
49
52
 
50
53
  # The element types a single content-mutator call introduces into an Array, given the
51
54
  # per-argument types (already typed by the caller in the scope the arguments are evaluated
52
55
  # in). `concat`/`replace` take collection arguments, so their element evidence is the
53
- # arguments' OWN element types unioned; the rest append the argument values directly. Returns
54
- # `[]` when there is no element evidence (e.g. a `<<` with no resolvable arg).
56
+ # arguments' OWN element types unioned; the `[]=` splice forms (`arr[i, n] = other` /
57
+ # `arr[range] = other`) store their value's elements the same way and read it through the
58
+ # same unwrap (issue #1140). The rest append the argument values directly. Returns `[]` when
59
+ # there is no element evidence (e.g. a `<<` with no resolvable arg).
55
60
  def array_added_elements(method_name, arg_types)
56
61
  return [] if arg_types.empty?
57
62
 
@@ -62,21 +67,27 @@ module Rigor
62
67
  # `insert(index, *objs)` — first arg is the position.
63
68
  arg_types.drop(1)
64
69
  when :[]=
65
- # `arr[i] = v` / `arr[i, n] = v` — value is the last argument.
66
- #
67
- # The SPLICE forms (`arr[i, n] = other` / `arr[range] = other`) store `other`'s ELEMENTS,
68
- # not `other` itself, so reading the value as one element over-widens: `a[0, 2] = [1, 2]`
69
- # contributes `Array[Integer]` where `Integer` is the truth. Left alone deliberately —
70
- # the answer is a superset either way, so it can only cost precision, and splitting the
71
- # arities here would need the receiver's own element type to unwrap against. Revisit if a
72
- # corpus site ever reads an element back through a splice-built array.
73
- [arg_types.last]
70
+ # `arr[i] = v` stores `v` as ONE element — the index arguments precede the value.
71
+ # The splice forms store the value's ELEMENTS instead (issue #1140): `a[0, 2] = [1, 2]`
72
+ # puts `Integer`s in the receiver, not an `Array[Integer]`. An index the engine cannot
73
+ # classify (`a[x] = v` with `x` untyped, a `Range | Integer` union, or a splat that
74
+ # may vanish inside the brackets) may be either form, so both readings join — the
75
+ # union is a superset of the truth either way.
76
+ case index_store_form(arg_types)
77
+ when :splice then splice_stored_elements(arg_types.last)
78
+ when :either then [arg_types.last] + splice_stored_elements(arg_types.last)
79
+ else [arg_types.last]
80
+ end
74
81
  when :fill
75
82
  # `fill(value)` — only the no-block single-value form adds a concrete element; block /
76
83
  # range forms are conservatively ignored (the arity-forget already widened the binding).
77
84
  arg_types.size == 1 ? arg_types : []
78
- else # << push append prepend unshift
85
+ when :<<, :push, :append, :prepend, :unshift
79
86
  arg_types
87
+ else
88
+ # Not an Array adder — a String mutator a content scan counted (`x.sub!("a", "b")` on an
89
+ # `Array | String` capture): its arguments are not elements.
90
+ []
80
91
  end
81
92
  end
82
93
 
@@ -122,6 +133,23 @@ module Rigor
122
133
  with_residue(Type::Combinator.nominal_of("Hash", type_args: [key_t, value_t]), hash_residue(pre_state))
123
134
  end
124
135
 
136
+ # Builds the continuation of a pre-state that carries BOTH an Array and a Hash member, from the
137
+ # pairs and the elements the body stores. Each side is joined with its own class's evidence, and
138
+ # every member neither side absorbs survives beside the Hash carrier ({#hash_residue}).
139
+ #
140
+ # Joined as one carrier, the other class's member was residue and survived exactly as the seed
141
+ # had it: an unwidened literal `Tuple` / `HashShape`, so what the body did to it was gone — `x =
142
+ # gets ? [1] : { a: 1 }; [0].each { x.map!(&:to_s) if x.is_a?(Array); x[:k] = 2 if
143
+ # x.is_a?(Hash) }` read `[1]`, and `x.first.upcase` drew `undefined method` on correct code. The
144
+ # caller routes the evidence: a store may reach either member, and only the caller sees its
145
+ # index (`StatementEvaluator#mixed_content_evidence`).
146
+ def join_mixed_content(pre_state, added_pairs, added_elements)
147
+ members = pre_state.is_a?(Type::Union) ? pre_state.members : [pre_state]
148
+ arrays, rest = members.partition { |member| array_residue(member).empty? }
149
+ array = join_array_content(Type::Combinator.union(*arrays), added_elements)
150
+ Type::Combinator.union(array, join_hash_content(Type::Combinator.union(*rest), added_pairs))
151
+ end
152
+
125
153
  # ----------------------------------------------------------------
126
154
  # Union residue — the members the rederived carrier does NOT stand for (issue #631).
127
155
  #
@@ -277,15 +305,158 @@ module Rigor
277
305
  type.is_a?(Type::Union) ? type.members : [type]
278
306
  end
279
307
 
308
+ # How an `arr[...] = v` call's index arguments place the stored value: `:splice` — the
309
+ # value's ELEMENTS land in the receiver (`arr[i, n] = v` takes two index slots,
310
+ # `arr[range] = v` takes a Range); `:element` — the value itself is one stored element
311
+ # (`arr[i] = v`); `:either` — the store may be either form.
312
+ #
313
+ # A splat inside the brackets is marked `nil` by the caller — its expansion can change the
314
+ # store's arity (`a[0, *xs] = v` stores `v` itself when `xs` is empty and `v`'s elements
315
+ # otherwise). An ordinary argument counts as a provable index however untyped it is:
316
+ # `a[i, n] = v` is a splice at every binding of `i` and `n`. Two or more provable indices
317
+ # are therefore always a splice however a splat expands; one provable index beside a splat
318
+ # reduces to the Range question (extra expansion only reaches an arity error, never a
319
+ # scalar store); zero provable indices leaves the form to whatever the splat yields.
320
+ def index_store_form(arg_types)
321
+ leading = arg_types[0...-1]
322
+ definite = leading.compact
323
+ return :splice if definite.size >= 2
324
+ return :either if definite.empty?
325
+ return single_index_form(definite.first) if definite.size == leading.size
326
+
327
+ range_index?(definite.first) ? :splice : :either
328
+ end
329
+
330
+ # The one-index question: a Range carrier is a splice, a definite scalar an element store,
331
+ # and anything between — a union straddling both, a broad index that may still hold a
332
+ # Range at runtime — may be either.
333
+ def single_index_form(index_type)
334
+ members = union_members(index_type)
335
+ return :splice if members.all? { |m| definite_range?(m) }
336
+ return :element if members.none? { |m| could_be_range?(m) }
337
+
338
+ :either
339
+ end
340
+
341
+ # True when the index position PROVABLY holds a Range: a Range carrier {#range_index?}
342
+ # recognizes, or a member acceptance proves a subtype of `Range` — a loaded
343
+ # `class MyRange < Range` answers `yes` there where the name check cannot see it. An
344
+ # unresolvable subclass answers `maybe`, which correctly declines here and still reaches
345
+ # {#could_be_range?}'s `either` reading. A gradual member accepts in BOTH directions
346
+ # (`Range` accepts it optimistically), so it must be excluded before asking — an untyped
347
+ # index may hold a scalar just as well and stays `either`. The exclusion reads through a
348
+ # `Difference` / `Refined` to its base: acceptance projects the wrapper onto the base too,
349
+ # so a `non-nil` or refined `Dynamic` answers `yes` there exactly like the bare gradual
350
+ # while still being free to hold a scalar at runtime.
351
+ def definite_range?(member)
352
+ return true if range_index?(member)
353
+
354
+ case member
355
+ when Type::Dynamic, Type::Top then false
356
+ when Type::Difference, Type::Refined then definite_range?(member.base)
357
+ else member.respond_to?(:accepts) && RANGE_INDEX_PROBE.accepts(member).yes?
358
+ end
359
+ end
360
+
361
+ # True when the index position MAY hold a Range at runtime: a definite Range carrier
362
+ # (`Constant[0..1]` accepts only its own value, so acceptance alone cannot see it),
363
+ # `Dynamic`, a carrier the engine cannot answer for, and every type related to
364
+ # `Nominal[Range]` in EITHER direction — `Object`/`Enumerable`/`top` above it, a
365
+ # project `class MyRange < Range` below it (an unresolvable class name answers `maybe`,
366
+ # never `no`, so subclass-ness cannot be ruled out).
367
+ #
368
+ # `no` in both directions is a real disjointness only when the member's values are
369
+ # provably `Object` instances — a fixed-class value set that single inheritance keeps
370
+ # from ever being a Range (`Integer`, a non-Range `Constant`, a `Tuple`). A MODULE
371
+ # member fails that probe (`Comparable`'s ancestors exclude `Object` — a module has no
372
+ # superclass chain), and rightly so: `class MyRange < Range; include Comparable; end`
373
+ # satisfies the annotation and still splices at runtime.
374
+ def could_be_range?(member)
375
+ return true if range_index?(member)
376
+
377
+ case member
378
+ when Type::Intersection
379
+ # An intersected value sits in EVERY member's value set, so it can be a Range only
380
+ # when every member allows one — `String & Comparable` cannot be (a String never
381
+ # is), `Object & Comparable` can (`MyRange < Range; include Comparable` satisfies
382
+ # both). Asked member-wise because the `Object` probe below answers yes when ANY
383
+ # intersected member accepts it.
384
+ member.members.all? { |part| could_be_range?(part) }
385
+ when Type::Maybe then could_be_range?(member.value_type)
386
+ else
387
+ return true unless member.respond_to?(:accepts)
388
+ return true unless member.accepts(RANGE_INDEX_PROBE).no? &&
389
+ RANGE_INDEX_PROBE.accepts(member).no?
390
+
391
+ !OBJECT_INDEX_PROBE.accepts(member).yes?
392
+ end
393
+ end
394
+
395
+ # Element types a splice RHS adds to the receiver, read member-wise. Ruby splices an
396
+ # Array RHS (`a[0, 1] = [1, 2]` puts `Integer`s in — a bare `Nominal[Array]` splices
397
+ # elements the signature left untyped, which are unknown rather than none), and stores a
398
+ # NON-Array RHS as one element UNLESS the value coerces through `to_ary`, in which case
399
+ # the returned array's elements splice instead (`a[0, 1] = obj` where `obj.to_ary`
400
+ # returns `["x"]` stores `"x"`). `nil` is no exception — `a[i, n] = nil` stores a single
401
+ # nil.
402
+ #
403
+ # Every member that is not an Array carrier keeps a `Dynamic[top]` arm beside itself:
404
+ # `to_ary` is consulted for a NON-Array value, and nothing here can prove the member's
405
+ # class does not define one — a `Nominal` may be a subclass that declares it, and the
406
+ # project may reopen even a non-subclassable core class (ADR-17's patched-methods tier
407
+ # resolves exactly those). The two exact carriers are the ones `Array#[]=` splices
408
+ # without a coercion: a `Tuple` literal's elements, and a `Nominal[Array]`'s own type
409
+ # arguments — an Array object is spliced directly even when its class overrides
410
+ # `to_ary`.
411
+ def splice_stored_elements(value_type)
412
+ union_members(value_type).flat_map do |member|
413
+ base = member
414
+ base = base.base while base.is_a?(Type::Difference) || base.is_a?(Type::Refined)
415
+
416
+ case base
417
+ when Type::Tuple
418
+ base.elements
419
+ when Type::Nominal
420
+ if base.class_name == "Array"
421
+ # `Array#[]=` splices an Array RHS directly — `to_ary` is never consulted
422
+ # for an Array object, even a subclass overriding it — so the elements
423
+ # join exactly (a bare `Array` signature left them unknown, not absent).
424
+ base.type_args.empty? ? [Type::Combinator.untyped] : base.type_args
425
+ elsif ARRAY_PROBE.accepts(base).yes?
426
+ # A definite Array subclass is spliced the same way; its elements are
427
+ # unknown to this seam.
428
+ [Type::Combinator.untyped]
429
+ else
430
+ [member, Type::Combinator.untyped]
431
+ end
432
+ else
433
+ [member, Type::Combinator.untyped]
434
+ end
435
+ end
436
+ end
437
+
438
+ # True when an index position holds a Range carrier — a static `Constant<Range>` (`0..1`),
439
+ # a `Nominal[Range]` (`(0..n)`), or a refinement / difference over either. `IntegerRange`
440
+ # and `FloatRange` are scalar number refinements rather than Range objects and correctly
441
+ # decline, as does every ordinary scalar index.
442
+ def range_index?(type)
443
+ case type
444
+ when Type::Constant then type.value.is_a?(::Range)
445
+ when Type::Nominal then type.class_name == "Range"
446
+ when Type::Difference, Type::Refined then range_index?(type.base)
447
+ else false
448
+ end
449
+ end
450
+
280
451
  # Element types carried by a collection binding, regardless of which carrier holds them: a
281
452
  # `Tuple` lists them, a `Nominal[Array, [E]]` has one element param, a bare `Array` /
282
453
  # anything else yields none.
283
454
  #
284
455
  # A `Difference` reads through to its base: `non-empty-array[T]` holds `T`s, and the seams
285
456
  # that read a seed from BEFORE the arity-forget ran (see {#join_array_content}) meet the
286
- # refinement carrier itself where they used to meet the base the widening had left. Declining
287
- # it there would hand the continuation the widened base ALONE, with every appended arm missing
288
- # — a wrong type, not a wide one.
457
+ # refinement carrier itself where they used to meet the base the widening had left.
458
+ # Declining it there would hand the continuation the widened base ALONE, with every appended
459
+ # arm missing — a wrong type, not a wide one.
289
460
  def collection_element_types(type)
290
461
  case type
291
462
  when Type::Tuple
@@ -305,13 +476,15 @@ module Rigor
305
476
 
306
477
  # `[keys, values]` evidence from a Hash-ish pre-state binding — a `HashShape` (literal pairs)
307
478
  # or a `Nominal[Hash, [K, V]]`. A `Difference` (`non-empty-hash[K, V]`) reads through to its
308
- # base, as {#collection_element_types} does for the Array side.
479
+ # base, as {#collection_element_types} does for the Array side. An open shape adds a
480
+ # `Dynamic[top]` key and value: its unseen keys may hold anything, and the joined carrier's
481
+ # read of one must not answer a known value (`flags = { debug: true }; flags.default = false`
482
+ # then a store in a block read `flags[:verbose]` as `true`).
309
483
  def hash_shape_key_values(type)
310
484
  case type
311
485
  when Type::HashShape
312
- return [[], []] if type.pairs.empty?
313
-
314
- [[key_union_for(type.pairs.keys)], type.pairs.values]
486
+ keys, values = type.pairs.empty? ? [[], []] : [[key_union_for(type.pairs.keys)], type.pairs.values]
487
+ type.open? ? [keys + [Type::Combinator.untyped], values + [Type::Combinator.untyped]] : [keys, values]
315
488
  when Type::Nominal
316
489
  type.class_name == "Hash" && type.type_args.size == 2 ? [[type.type_args[0]], [type.type_args[1]]] : [[], []]
317
490
  when Type::Union
@@ -22,17 +22,17 @@ module Rigor
22
22
  # a tracer per visited node and discards the inferred type values.
23
23
  class CoverageScanner
24
24
  class Result < Data.define(:visits, :unrecognized, :events)
25
- # @return [Integer] sum of all visits across node classes.
25
+ # @return sum of all visits across node classes.
26
26
  def visited_count
27
27
  visits.values.sum
28
28
  end
29
29
 
30
- # @return [Integer] sum of directly-unrecognized counts across classes.
30
+ # @return sum of directly-unrecognized counts across classes.
31
31
  def unrecognized_count
32
32
  unrecognized.values.sum
33
33
  end
34
34
 
35
- # @return [Float] unrecognized_count / visited_count, or 0.0 when empty.
35
+ # @return unrecognized_count / visited_count, or 0.0 when empty.
36
36
  def unrecognized_ratio
37
37
  total = visited_count
38
38
  return 0.0 if total.zero?
@@ -41,13 +41,11 @@ module Rigor
41
41
  end
42
42
  end
43
43
 
44
- # @param scope [Rigor::Scope] base scope used for every type_of call. Defaults to `Scope.empty`.
44
+ # @param scope — base scope used for every type_of call. Defaults to `Scope.empty`.
45
45
  def initialize(scope: nil)
46
46
  @scope = scope || Scope.empty
47
47
  end
48
48
 
49
- # @param root [Prism::Node]
50
- # @return [Result]
51
49
  def scan(root)
52
50
  visits = Hash.new(0)
53
51
  unrecognized = Hash.new(0)
@@ -4,6 +4,7 @@ require "prism"
4
4
 
5
5
  require_relative "../type"
6
6
  require_relative "../source/node_children"
7
+ require_relative "return_barrier"
7
8
 
8
9
  module Rigor
9
10
  module Inference
@@ -13,15 +14,14 @@ module Rigor
13
14
  # The inferred type is the union of:
14
15
  #
15
16
  # - the body's last-statement type, and
16
- # - the type of every explicit `return value` reachable in the body. Nested `def` / lambda / block bodies
17
- # are return barriers — their `return`s do not bubble up to the enclosing method.
17
+ # - the type of every explicit `return value` that exits the method. A `return` inside an ordinary block does
18
+ # (control-flow-analysis.md § "Non-local exits"); one inside a {ReturnBarrier} — a nested `def`, a lambda,
19
+ # or a `define_method` body — does not. The barriers are the ones `StatementEvaluator`'s return sink stops
20
+ # at, so the proposal and `rigor check`'s view of callers agree (issue #1382).
18
21
  #
19
22
  # Extracted from `Rigor::SigGen::Generator#infer_return_type` so `LineTypeCollector` (`rigor annotate`'s
20
23
  # def-line annotator) and the sig-generator share one source of truth.
21
24
  module DefReturnTyper
22
- RETURN_BARRIER_NODES = [Prism::DefNode, Prism::LambdaNode, Prism::BlockNode].freeze
23
- private_constant :RETURN_BARRIER_NODES
24
-
25
25
  module_function
26
26
 
27
27
  def call(def_node, scope_index)
@@ -60,10 +60,14 @@ module Rigor
60
60
 
61
61
  def collect_return_types(node, scope_index, out)
62
62
  return unless node.is_a?(Prism::Node)
63
- return if RETURN_BARRIER_NODES.any? { |klass| node.is_a?(klass) }
63
+ return if ReturnBarrier.node?(node)
64
64
 
65
65
  type_return_node(node, scope_index, out) if node.is_a?(Prism::ReturnNode)
66
- node.rigor_each_child { |c| collect_return_types(c, scope_index, out) }
66
+ # A barrier call's receiver and arguments still run in the method; only its block is cut off.
67
+ barrier_block = node.block if node.is_a?(Prism::CallNode) && ReturnBarrier.block_call?(node)
68
+ node.rigor_each_child do |c|
69
+ collect_return_types(c, scope_index, out) unless c.equal?(barrier_block)
70
+ end
67
71
  end
68
72
 
69
73
  def type_return_node(return_node, scope_index, out)