rigortype 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type"
4
+ require_relative "external_ancestor_resolution"
5
+
6
+ module Rigor
7
+ module Inference
8
+ # Issue #1234 — does the PROJECT define the method a receiver answers a name with? The captured-binding
9
+ # pass reads a catalogued iterator name on an unclassified receiver as repetition
10
+ # ({ClosureEscapeAnalyzer.repeats_by_name?}), and that reading is wrong for a method the project wrote
11
+ # under the same name: `class Vault; def select(key) = yield(key.to_s); end` runs its block once. The
12
+ # ancestry step of {ClosureEscapeAnalyzer.classify} asks the same question, of the same call, to rule the
13
+ # project out as the method's owner.
14
+ #
15
+ # "Defines" is a `def`, `define_method` or `attr_*` anywhere in a project class's ancestry, on the
16
+ # instance side or on the singleton side for a class-object receiver, or a signature whose declaring
17
+ # owner is a project class or module.
18
+ module ProjectMethodOwnership
19
+ NO_TARGETS = [].freeze
20
+ private_constant :NO_TARGETS
21
+
22
+ MEMO_KEY = :__rigor_project_method_ownership_memo__
23
+ private_constant :MEMO_KEY
24
+
25
+ module_function
26
+
27
+ # The `[class_name, kind]` pairs the receiver's method is looked up on — `[]` for a receiver Rigor
28
+ # cannot see at all — or nil when the carrier names a class this cannot resolve, or is one it does not
29
+ # recognise, so a caller that cannot tell whether the project owns the method can decline. The audit,
30
+ # carrier by carrier:
31
+ #
32
+ # - `Dynamic`, `Top` — unseen: `[]`.
33
+ # - `Nominal`, `StructInstance`, `DataInstance` — their `class_name`, instance side.
34
+ # - `Singleton`, `StructClass`, `DataClass` — their `class_name`, singleton side.
35
+ # - `Tuple` → `Array`, `HashShape` → `Hash`, `IntegerRange` → `Integer`, `FloatRange` → `Float`,
36
+ # `Constant` → its value's class, `BoundMethod` → `Method`: instance side of that core class, which a
37
+ # project reopening may still define the method on.
38
+ # - `Union`, `Intersection` — every member's targets; a member it cannot resolve resolves the whole.
39
+ # - `Refined`, `Difference` — their `base`. `App` — its erasure `bound`.
40
+ # - A struct / data carrier with no `class_name` (an anonymous `Struct.new` value), `Bot`, `Maybe`,
41
+ # `Result`, and any carrier added later — nil.
42
+ def targets(receiver_type)
43
+ case receiver_type
44
+ when Type::Dynamic, Type::Top then NO_TARGETS
45
+ when Type::Nominal, Type::StructInstance, Type::DataInstance
46
+ class_target(receiver_type.class_name, :instance)
47
+ when Type::Singleton, Type::StructClass, Type::DataClass
48
+ class_target(receiver_type.class_name, :singleton)
49
+ when Type::Union, Type::Intersection then member_targets(receiver_type.members)
50
+ when Type::Refined, Type::Difference then targets(receiver_type.base)
51
+ when Type::App then targets(receiver_type.bound)
52
+ else core_targets(receiver_type)
53
+ end
54
+ end
55
+
56
+ # Whether the project's source, or a signature whose declaring owner is a project class or module,
57
+ # defines `method_sym` on `class_name` or an ancestor the project declares. Memoised ({.memo}): the
58
+ # ancestry step and the name reading ask it of the same call, and every block call on the class asks
59
+ # it again.
60
+ def defines?(class_name, method_sym, kind, scope)
61
+ return false if scope.nil?
62
+
63
+ by_method = (memo(scope)[kind][class_name] ||= {})
64
+ return by_method[method_sym] if by_method.key?(method_sym)
65
+
66
+ by_method[method_sym] = compute_defines?(class_name, method_sym, kind, scope)
67
+ end
68
+
69
+ # One slot per thread, keyed on the identity of the scope's frozen discovery index and its environment —
70
+ # the only inputs {.defines?} and the ancestry step read — and replaced, not accumulated, when either
71
+ # changes: the shape of `ExpressionTyper#class_graph_buckets`, whose rationale applies unchanged. A
72
+ # `Scope` merges each file's discovery with the project pre-pass, so the slot turns over per file, and
73
+ # an ADR-46 dependency the first lookup recorded belongs to the file every later hit serves. Pool
74
+ # workers are separate processes, and a Ractor has its own `Thread.current`. The `:ancestry` bucket
75
+ # belongs to {ClosureEscapeAnalyzer}'s ancestry step.
76
+ def memo(scope)
77
+ discovery = scope.discovery
78
+ environment = scope.environment
79
+ slot = Thread.current[MEMO_KEY]
80
+ unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
81
+ slot = [discovery, environment, { instance: {}, singleton: {}, ancestry: {} }]
82
+ Thread.current[MEMO_KEY] = slot
83
+ end
84
+ slot[2]
85
+ end
86
+
87
+ class << self
88
+ private
89
+
90
+ def class_target(class_name, kind)
91
+ class_name.nil? ? nil : [[class_name.to_s, kind]]
92
+ end
93
+
94
+ def member_targets(members)
95
+ members.each_with_object([]) do |member, out|
96
+ resolved = targets(member)
97
+ return nil if resolved.nil?
98
+
99
+ out.concat(resolved)
100
+ end
101
+ end
102
+
103
+ def core_targets(receiver_type)
104
+ case receiver_type
105
+ when Type::Tuple then class_target("Array", :instance)
106
+ when Type::HashShape then class_target("Hash", :instance)
107
+ when Type::IntegerRange then class_target("Integer", :instance)
108
+ when Type::FloatRange then class_target("Float", :instance)
109
+ when Type::Constant then class_target(receiver_type.value.class.name, :instance)
110
+ when Type::BoundMethod then class_target("Method", :instance)
111
+ end
112
+ end
113
+
114
+ def compute_defines?(class_name, method_sym, kind, scope)
115
+ return true if source_defines?(class_name, method_sym, kind, scope)
116
+
117
+ # `ExternalAncestorResolution.method_definition` owns the rescue for a malformed signature.
118
+ definition = ExternalAncestorResolution.method_definition(class_name, method_sym, kind, scope: scope)
119
+ owner = definition.respond_to?(:defined_in) ? definition.defined_in : nil
120
+ !owner.nil? && scope.known_user_class?(owner.to_s.delete_prefix("::"))
121
+ end
122
+
123
+ def source_defines?(class_name, method_sym, kind, scope)
124
+ return true if scope.discovered_method_through_ancestors?(class_name, method_sym, kind)
125
+
126
+ found, = if kind == :singleton
127
+ scope.singleton_def_through_ancestors(class_name, method_sym)
128
+ else
129
+ scope.user_def_through_ancestors(class_name, method_sym)
130
+ end
131
+ !found.nil?
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "global_write_census"
4
+
3
5
  module Rigor
4
6
  module Inference
5
7
  # ADR-17 § "Inference contract" — project-wide patched-method registry populated by the pre-eval
@@ -26,20 +28,23 @@ module Rigor
26
28
  end
27
29
  end
28
30
 
29
- attr_reader :by_key
31
+ attr_reader :by_key, :write_census
30
32
 
31
- # @param entries [Array<Entry>] flat list of declarations observed during the pre-pass.
33
+ # @param entries — flat list of declarations observed during the pre-pass.
32
34
  # First-write-wins on `(class_name, method_name, kind)` duplicates so the
33
35
  # `pre-eval.duplicate-declaration` diagnostic emission stays decoupled from registry behaviour.
34
- def initialize(entries: [])
36
+ # @param write_census — issue #1367: the `pre_eval:` files' {GlobalWriteCensus}, which the `global.*` write
37
+ # rules join with the project's, since a patch file is loaded ahead of the code it patches.
38
+ def initialize(entries: [], write_census: GlobalWriteCensus::EMPTY)
35
39
  @by_key = entries.each_with_object({}) do |entry, acc|
36
40
  key = [entry.class_name, entry.method_name, entry.kind]
37
41
  acc[key] ||= entry
38
42
  end.freeze
43
+ @write_census = write_census.frozen? ? write_census : write_census.dup.freeze
39
44
  freeze
40
45
  end
41
46
 
42
- # @return [Entry, nil] the recorded entry for the given `(class_name, method_name, kind)` triple,
47
+ # @return the recorded entry for the given `(class_name, method_name, kind)` triple,
43
48
  # or `nil` when no pre-eval file declared it.
44
49
  def lookup(class_name:, method_name:, kind:)
45
50
  @by_key[[class_name, method_name, kind]]
@@ -31,20 +31,21 @@ module Rigor
31
31
 
32
32
  module_function
33
33
 
34
- # @param paths [Array<String>] absolute paths to the pre-eval files. The runner has already
34
+ # @param paths — absolute paths to the pre-eval files. The runner has already
35
35
  # validated that each path exists (slice-1 `pre-eval.file-not-found` `:error` covers missing
36
36
  # entries); the scanner does NOT re-check existence.
37
- # @param buffer [Rigor::Analysis::BufferBinding, nil] editor-mode buffer binding. When set, the
37
+ # @param buffer — editor-mode buffer binding. When set, the
38
38
  # scanner reads the buffer's physical bytes if a pre-eval entry matches the logical path, so
39
39
  # users editing a monkey-patch file see the in-flight version in their analysis.
40
- # @return [Result] the populated registry plus any per-file warnings.
40
+ # @return the populated registry plus any per-file warnings.
41
41
  def scan(paths, buffer: nil)
42
42
  entries = []
43
43
  diagnostics = []
44
- paths.each { |path| scan_file(path, entries, diagnostics, buffer) }
44
+ census = Set.new
45
+ paths.each { |path| scan_file(path, entries, diagnostics, buffer, census) }
45
46
  diagnostics.concat(duplicate_declaration_diagnostics(entries))
46
47
  Result.new(
47
- registry: ProjectPatchedMethods.new(entries: entries),
48
+ registry: ProjectPatchedMethods.new(entries: entries, write_census: census.freeze),
48
49
  diagnostics: diagnostics
49
50
  )
50
51
  end
@@ -77,7 +78,7 @@ module Rigor
77
78
  end
78
79
  private_class_method :duplicate_declaration_diagnostics
79
80
 
80
- def scan_file(path, entries, diagnostics, buffer = nil)
81
+ def scan_file(path, entries, diagnostics, buffer = nil, census = Set.new)
81
82
  physical = buffer ? buffer.resolve(path) : path
82
83
  parse_result =
83
84
  if physical == path
@@ -91,6 +92,9 @@ module Rigor
91
92
  end
92
93
 
93
94
  walk_node(parse_result.value, [], false, path, entries)
95
+ # Issue #1367 — the file's `global.*` write facts: a patch file may alias a special or give a class the
96
+ # method a setter asks for, as any project file may.
97
+ census.merge(GlobalWriteCensus.scan(parse_result.value))
94
98
  rescue StandardError => e
95
99
  diagnostics << build_diagnostic(
96
100
  path: path, line: 1, column: 1,
@@ -37,8 +37,7 @@ module Rigor
37
37
  @scope = scope || Scope.empty
38
38
  end
39
39
 
40
- # @param root [Prism::Node] the parsed AST
41
- # @return [FileResult]
40
+ # @param root — the parsed AST
42
41
  def scan(root)
43
42
  index = ScopeIndexer.index(root, default_scope: @scope)
44
43
  protected_count = 0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ # Reads the endpoints out of a `Constant<Range>`.
8
+ #
9
+ # A `Constant` normally carries a value and no generic instantiation, which is why acceptance
10
+ # ignores a nominal's `type_args` against one. A Range value is the exception: the engine only
11
+ # builds `Constant<Range>` when both endpoints are static (`ExpressionTyper#type_of_range`), so
12
+ # the element the range would yield IS known, and a `Range[T]` parameter can be judged against it.
13
+ #
14
+ # Two consumers share the reading so they cannot drift: `Acceptance` refutes a `Range[T]`
15
+ # parameter whose `T` rejects an endpoint (issue #833), and `RbsDispatch` binds a method-level
16
+ # `Range[A]` parameter's `A` from the endpoints (issue #834).
17
+ module RangeConstant
18
+ module_function
19
+
20
+ # The non-`nil` endpoints of the range `type` pins, or nil when there is nothing to read from.
21
+ #
22
+ # nil — meaning "no opinion, keep the answer you already had" — covers a carrier that is not a
23
+ # `Constant<Range>`, a range with no endpoint at all (`nil..nil` constrains nothing), and a
24
+ # range whose endpoints are not literals. The literal set is the one
25
+ # `ExpressionTyper#static_range_endpoint` pins; a value outside it reached the carrier by some
26
+ # other route and the caller has no lifting for it.
27
+ def literal_endpoints(type)
28
+ return nil unless type.is_a?(Type::Constant)
29
+
30
+ value = type.value
31
+ return nil unless value.is_a?(::Range)
32
+
33
+ endpoints = [value.begin, value.end].compact
34
+ return nil if endpoints.empty?
35
+ return nil unless endpoints.all? { |endpoint| literal_endpoint?(endpoint) }
36
+
37
+ endpoints
38
+ end
39
+
40
+ # The endpoints lifted to their own classes and unioned — the element type the range would
41
+ # yield. Lifting rather than pinning the values mirrors
42
+ # `ExpressionTyper#nominal_range_for_endpoints`: `1..9` yields Integers, not the two values 1
43
+ # and 9, so binding a `Range[A]` parameter's `A` to `Integer` states what the runtime can
44
+ # honour. Nil when {#literal_endpoints} has nothing to read.
45
+ def element_type(type)
46
+ endpoints = literal_endpoints(type)
47
+ return nil if endpoints.nil?
48
+
49
+ Type::Combinator.union(*endpoints.map { |endpoint| Type::Combinator.nominal_of(endpoint.class.name) })
50
+ end
51
+
52
+ def literal_endpoint?(value)
53
+ value.is_a?(::Integer) || value.is_a?(::Float) || value.is_a?(::String)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -100,17 +100,31 @@ module Rigor
100
100
  ALIAS_EXPANSION_LIMIT = 8
101
101
  private_constant :ALIAS_EXPANSION_LIMIT
102
102
 
103
+ # #775 — `TypeName#relative!` allocates a TypeName and a Namespace and `#to_s` joins the path: four
104
+ # objects per class-instance translation, ~380k of them on the lib self-check, for the same few
105
+ # hundred names. rbs compares a TypeName by namespace + name, so a value-keyed memo answers every
106
+ # repeat with one frozen String.
107
+ @relative_names = {}
108
+
109
+ # #775 — the translated form of a CLOSED alias expansion, keyed by the expansion. Closed means no
110
+ # `self`, `instance` or type variable anywhere inside, so the caller's context cannot change the
111
+ # answer; at expansion depth 0 the budget cannot either. rbs compares types by value, so the memo
112
+ # is per distinct expansion, not per loader (a re-parsed identical alias hits), and the weak keys
113
+ # let an entry go with the loader whose memo held the expansion. `Rigor::Type::t` on the self-check
114
+ # is a 21-member union that was re-translated ~13k times, each time re-normalising the union from
115
+ # scratch; every repeat now shares one Union.
116
+ @closed_alias_translations = ObjectSpace::WeakKeyMap.new
117
+
103
118
  class << self
104
- # @param rbs_type [RBS::Types::Bases::Base, RBS::Types::ClassInstance, ...]
105
- # @param self_type [Rigor::Type, nil] substitute for `Bases::Self`.
106
- # @param instance_type [Rigor::Type, nil] substitute for `Bases::Instance`. Defaults to `nil`,
119
+ # @param self_type — substitute for `Bases::Self`.
120
+ # @param instance_type — substitute for `Bases::Instance`. Defaults to `nil`,
107
121
  # which degrades to Dynamic[Top].
108
- # @param type_vars [Hash{Symbol => Rigor::Type}] substitution map for `Bases::Variable`. Keys
122
+ # @param type_vars — substitution map for `Bases::Variable`. Keys
109
123
  # are the RBS variable names (e.g., `:Elem`); values are Rigor types that replace the
110
124
  # variable. Variables that are not bound in the map degrade to Dynamic[Top].
111
- # @param alias_expander [#expand_type_alias, nil] resolves `RBS::Types::Alias` one level out —
112
- # in practice the environment's `RbsLoader`. When nil, aliases degrade to Dynamic[Top].
113
- # @return [Rigor::Type]
125
+ # @param alias_expander — anything answering `expand_type_alias`, which resolves `RBS::Types::Alias`
126
+ # one level out — in practice the environment's `RbsLoader`. When nil, aliases degrade to
127
+ # Dynamic[Top].
114
128
  def translate(rbs_type, self_type: nil, instance_type: nil, type_vars: EMPTY_TYPE_VARS,
115
129
  alias_expander: nil)
116
130
  translate_in(rbs_type, Context.new(self_type, instance_type, type_vars, alias_expander, 0))
@@ -180,11 +194,17 @@ module Rigor
180
194
  # `Nominal["Array", [Nominal["Integer"]]]`. Variables inside the args participate in
181
195
  # substitution through the same `type_vars:` map.
182
196
  def translate_class_instance(rbs_type, context)
183
- name = rbs_type.name.relative!.to_s
197
+ name = relative_name(rbs_type.name)
198
+ return Type::Combinator.nominal_of(name) if rbs_type.args.empty?
199
+
184
200
  translated_args = rbs_type.args.map { |arg| translate_in(arg, context) }
185
201
  Type::Combinator.nominal_of(name, type_args: translated_args)
186
202
  end
187
203
 
204
+ def relative_name(type_name)
205
+ @relative_names[type_name] ||= type_name.relative!.to_s.freeze
206
+ end
207
+
188
208
  # Preserves tuple precision through the boundary. Each positional element type is translated
189
209
  # recursively under the caller's substitution context, and the resulting list is wrapped in a
190
210
  # `Rigor::Type::Tuple`.
@@ -218,8 +238,7 @@ module Rigor
218
238
  # `singleton(Foo)` is the type of the constant `Foo` itself (the class object). With the
219
239
  # dedicated Singleton type, we map directly to `Singleton[Foo]`.
220
240
  def translate_class_singleton(rbs_type, _context)
221
- name = rbs_type.name.relative!.to_s
222
- Type::Combinator.singleton_of(name)
241
+ Type::Combinator.singleton_of(relative_name(rbs_type.name))
223
242
  end
224
243
 
225
244
  # #529 — sees through a type alias instead of reading `untyped`. `expand_type_alias` resolves
@@ -232,8 +251,27 @@ module Rigor
232
251
 
233
252
  expanded = expander.expand_type_alias(rbs_type)
234
253
  return Type::Combinator.untyped if expanded.nil?
254
+ # A nested expansion is translated under its parent's budget, and `rigor trace` records every
255
+ # union merge as it happens, so neither consults the memo.
256
+ return translate_in(expanded, context.deeper) unless context.alias_depth.zero? && !FlowTracer.active?
257
+
258
+ cached = @closed_alias_translations[expanded]
259
+ return cached if cached
260
+
261
+ translated = translate_in(expanded, context.deeper)
262
+ @closed_alias_translations[expanded] = translated if closed_type?(expanded)
263
+ translated
264
+ end
265
+
266
+ # True when nothing in `rbs_type`'s tree reads the translation context: no `self`, no `instance`,
267
+ # no type variable. Every rbs type answers `each_type` (the leaves through `EmptyEachType`).
268
+ def closed_type?(rbs_type)
269
+ case rbs_type
270
+ when RBS::Types::Bases::Self, RBS::Types::Bases::Instance, RBS::Types::Variable then return false
271
+ end
235
272
 
236
- translate_in(expanded, context.deeper)
273
+ rbs_type.each_type { |inner| return false unless closed_type?(inner) }
274
+ true
237
275
  end
238
276
 
239
277
  # #529 — `A & B` reads as its first member that carries static evidence. Every value of the
@@ -24,21 +24,53 @@ module Rigor
24
24
  # object no binding can be attributed to and yields `[]`, which is what every receiver form
25
25
  # outside the single-read case already contributed. The walk is depth-capped so a pathological
26
26
  # nest cannot make receiver classification unbounded.
27
+ #
28
+ # The `it` parameter (Ruby 3.4) is a local like any other, but Prism reads it through
29
+ # `Prism::ItLocalVariableReadNode`, which carries no `name`; {.read_name} answers `:it` for it, the
30
+ # binding `BlockParameterBinder` installs. A consumer names a read through {.read_name}, never `#name`.
31
+ #
32
+ # A local or instance-variable write evaluates to the variable it writes, so `(buf ||= []) << x`
33
+ # mutates `buf` and yields a read of it ({.read_of}). Issue #1223 made that binding visible: the
34
+ # statement evaluator threads a write in a receiver, and without the alias `buf` kept the `[]`
35
+ # the `||=` stored.
27
36
  module ReceiverAlias
28
37
  # Deep enough for any hand-written selection; a nest beyond it degrades to "names no binding".
29
38
  WALK_DEPTH_CAP = 6
30
39
 
40
+ NON_ALIASED_READS = [Prism::ClassVariableReadNode, Prism::GlobalVariableReadNode].freeze
41
+ private_constant :NON_ALIASED_READS
42
+
43
+ LOCAL_WRITE_NODES = Set[
44
+ Prism::LocalVariableWriteNode, Prism::LocalVariableOrWriteNode,
45
+ Prism::LocalVariableAndWriteNode, Prism::LocalVariableOperatorWriteNode
46
+ ].freeze
47
+ INSTANCE_WRITE_NODES = Set[
48
+ Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode,
49
+ Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode
50
+ ].freeze
51
+ CLASS_VARIABLE_WRITE_NODES = Set[
52
+ Prism::ClassVariableWriteNode, Prism::ClassVariableOrWriteNode,
53
+ Prism::ClassVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode
54
+ ].freeze
55
+ GLOBAL_WRITE_NODES = Set[
56
+ Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode,
57
+ Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode
58
+ ].freeze
59
+ private_constant :LOCAL_WRITE_NODES, :INSTANCE_WRITE_NODES, :CLASS_VARIABLE_WRITE_NODES, :GLOBAL_WRITE_NODES
60
+
31
61
  module_function
32
62
 
33
- # @param node [Prism::Node, nil] the receiver expression.
34
- # @param depth [Integer] recursion depth, internal.
35
- # @return [Array<Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode>] every variable
63
+ # @param node — the receiver expression.
64
+ # @param depth — recursion depth, internal.
65
+ # @return every variable
36
66
  # read the expression can evaluate to; empty when it can evaluate to none.
37
67
  def candidates(node, depth = 0)
38
68
  return [] if node.nil? || depth > WALK_DEPTH_CAP
69
+ # Tested before the `case`: a `when *SET` arm would copy the set into an Array on every call.
70
+ return [read_of(node)] if variable_write?(node)
39
71
 
40
72
  case node
41
- when Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode then [node]
73
+ when Prism::LocalVariableReadNode, Prism::ItLocalVariableReadNode, Prism::InstanceVariableReadNode then [node]
42
74
  when Prism::ParenthesesNode then candidates(node.body, depth + 1)
43
75
  when Prism::StatementsNode then candidates(node.body.last, depth + 1)
44
76
  when Prism::ElseNode then candidates(node.statements, depth + 1)
@@ -52,6 +84,63 @@ module Rigor
52
84
  def branches(first, second, depth)
53
85
  candidates(first, depth + 1) + candidates(second, depth + 1)
54
86
  end
87
+
88
+ # The variable reads an in-place mutation of `receiver` changes: {.candidates}' locals and instance
89
+ # variables, or the class variable or global the receiver reads or writes directly, parenthesised or not
90
+ # (`(@@c ||= []) << x` mutates `@@c`, as `(buf ||= []) << x` mutates `buf`). A class variable or global counts
91
+ # only as the receiver itself, not through a branch that selects it. This is the
92
+ # one answer the straight-line widening ({MutationWidening.widen_receiver_aliases}), the block-return
93
+ # threading gate that predicts it (`ExpressionTyper#prefix_statement_jump_free?`) and the per-element fold's
94
+ # content-mutation scan ({CapturedLocals.content_mutations}) all read, so "the scan says the body changed
95
+ # it" and "evaluating the body changes it" cannot disagree about which kinds of variable count.
96
+ def mutated_reads(receiver)
97
+ direct = receiver
98
+ direct = direct.body.body.last while direct.is_a?(Prism::ParenthesesNode) && direct.body.is_a?(Prism::StatementsNode)
99
+ return [direct] if NON_ALIASED_READS.include?(direct.class)
100
+ return [read_of(direct)] if non_aliased_write?(direct)
101
+
102
+ candidates(receiver)
103
+ end
104
+
105
+ # True when `node` is a class-variable or global write, which {.mutated_reads} answers only as the receiver
106
+ # itself, as it answers a class-variable or global read.
107
+ def non_aliased_write?(node)
108
+ CLASS_VARIABLE_WRITE_NODES.include?(node.class) || GLOBAL_WRITE_NODES.include?(node.class)
109
+ end
110
+
111
+ # The binding a read from {.candidates} or {.mutated_reads} names.
112
+ def read_name(read) = read.is_a?(Prism::ItLocalVariableReadNode) ? :it : read.name
113
+
114
+ # True when `read` is a local the innermost block around it owns: one Prism resolves at `depth == 0`, or an
115
+ # `it` read, which is always that block's own parameter and never a capture of an enclosing scope.
116
+ def block_local?(read)
117
+ read.is_a?(Prism::ItLocalVariableReadNode) || (read.is_a?(Prism::LocalVariableReadNode) && read.depth.zero?)
118
+ end
119
+
120
+ # True when `node` is a local or instance-variable write, whose value is the variable it leaves.
121
+ def variable_write?(node)
122
+ LOCAL_WRITE_NODES.include?(node.class) || INSTANCE_WRITE_NODES.include?(node.class)
123
+ end
124
+
125
+ # A read of the variable `write` writes, at the write's name, for the analysers that key a receiver on a
126
+ # read node — a local, instance-variable, class-variable or global write. `Prism` 1.x constructs every node
127
+ # from `source, node_id, location, flags` and its fields.
128
+ def read_of(write)
129
+ source = write.send(:source)
130
+ if LOCAL_WRITE_NODES.include?(write.class)
131
+ Prism::LocalVariableReadNode.new(source, write.node_id, write.name_loc, 0, write.name, write.depth)
132
+ else
133
+ named_read_class(write.class).new(source, write.node_id, write.name_loc, 0, write.name)
134
+ end
135
+ end
136
+
137
+ # The read node class for an instance-variable, class-variable or global write class.
138
+ def named_read_class(write_class)
139
+ if INSTANCE_WRITE_NODES.include?(write_class) then Prism::InstanceVariableReadNode
140
+ elsif CLASS_VARIABLE_WRITE_NODES.include?(write_class) then Prism::ClassVariableReadNode
141
+ else Prism::GlobalVariableReadNode
142
+ end
143
+ end
55
144
  end
56
145
  end
57
146
  end