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
@@ -20,6 +20,7 @@ module Rigor
20
20
  class MethodCatalog
21
21
  FOLDABLE_PURITIES = Set["leaf", "trivial", "leaf_when_numeric"].freeze
22
22
  EMPTY_CATALOG = { "classes" => {} }.freeze
23
+ Ractor.make_shareable(EMPTY_CATALOG)
23
24
 
24
25
  # Selectors that are classified `:leaf` by the C-body analysis (they read no global mutable state
25
26
  # in the C sense) but whose result is NOT reproducible across Ruby processes, so they must never
@@ -37,7 +38,7 @@ module Rigor
37
38
 
38
39
  # Shared root for the offline-generated catalogues. Resolving it here keeps the repo-relative
39
40
  # `../../../../` hop in one place instead of copying it into every per-topic loader.
40
- DATA_ROOT = File.expand_path("../../../../data/builtins/ruby_core", __dir__)
41
+ DATA_ROOT = File.expand_path("../../../../data/builtins/ruby_core", __dir__).freeze
41
42
  private_constant :DATA_ROOT
42
43
 
43
44
  # Build a catalog for a named topic, resolving its YAML path under {DATA_ROOT}. Equivalent to
@@ -18,7 +18,7 @@ module Rigor
18
18
  :replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
19
19
  :prepend, :force_encoding, :encode, :scrub, :unicode_normalize, :"[]=",
20
20
  :upto, :each_byte, :each_char, :each_codepoint,
21
- :each_grapheme_cluster, :each_line, :bytesplice,
21
+ :each_grapheme_cluster, :each_line, :bytesplice, :append_as_bytes,
22
22
  # `crypt` is not a mutator but is blocked from folding for the same "do not bake a non-pure
23
23
  # result into a Constant" reason: `rb_str_crypt` delegates to the platform `crypt(3)`, whose
24
24
  # output (algorithm and digest) varies by libc / OS, so `"x".crypt("ab")` is not deterministic
@@ -4,6 +4,12 @@ require "prism"
4
4
 
5
5
  require_relative "../source/node_walker"
6
6
  require_relative "block_parameter_binder"
7
+ require_relative "element_read_widening"
8
+ require_relative "index_write_widening"
9
+ require_relative "mutation_widening"
10
+ require_relative "optimistic_origin"
11
+ require_relative "receiver_alias"
12
+ require_relative "unknown_store_widening"
7
13
 
8
14
  module Rigor
9
15
  module Inference
@@ -16,11 +22,21 @@ module Rigor
16
22
  # A write counts across every local-write form — plain `=` (`LocalVariableWriteNode`), the operator /
17
23
  # `||=` / `&&=` compound forms, and a multi-assign target (`x, y = …` → `LocalVariableTargetNode` under
18
24
  # a `MultiWriteNode`) — at ANY depth: a block is a closure, so a write inside a nested block binds the
19
- # same outer variable. Block-introduced names (parameters, numbered parameters, `;`-locals) and names
20
- # not bound in the outer scope are excluded; a write to either is not a captured rebind of an outer
21
- # variable.
22
- module CapturedLocals
23
- LOCAL_WRITE_NODES = [
25
+ # same outer variable. Block-introduced names (parameters, numbered parameters, `;`-locals), names
26
+ # not bound in the outer scope, a write a nested block's own parameter or block-local shadows, and a
27
+ # write inside a nested `def` or class body ({.outer_local?}) are excluded; a write to any of them is
28
+ # not a captured rebind of an outer variable.
29
+ #
30
+ # All three also ask for the instance variables the body rebinds (`ivars: true`). The per-element fold asks
31
+ # for every other binding that outlives an iteration as well (`non_locals: true`): the class variables and
32
+ # globals the body rebinds, and the instance variable behind an attribute setter it calls on `self`. Names
33
+ # keep their sigil, so a map over every kind never collides, and {.bound_type} / {.bind} reach each name
34
+ # through its own kind of binding.
35
+ #
36
+ # {.content_mutations} is the sibling set on the same terms: the captured outer locals the body mutates
37
+ # IN PLACE rather than rebinds, which the rebind set cannot see and the per-element fold needs as well.
38
+ module CapturedLocals # rubocop:disable Metrics/ModuleLength
39
+ LOCAL_WRITE_NODES = Set[
24
40
  Prism::LocalVariableWriteNode,
25
41
  Prism::LocalVariableOperatorWriteNode,
26
42
  Prism::LocalVariableOrWriteNode,
@@ -28,25 +44,380 @@ module Rigor
28
44
  Prism::LocalVariableTargetNode
29
45
  ].freeze
30
46
 
47
+ # The write forms of the bindings other than locals that outlive an iteration: instance variables, class
48
+ # variables and globals.
49
+ NON_LOCAL_WRITE_NODES = Set[
50
+ Prism::InstanceVariableWriteNode,
51
+ Prism::InstanceVariableOperatorWriteNode,
52
+ Prism::InstanceVariableOrWriteNode,
53
+ Prism::InstanceVariableAndWriteNode,
54
+ Prism::InstanceVariableTargetNode,
55
+ Prism::ClassVariableWriteNode,
56
+ Prism::ClassVariableOperatorWriteNode,
57
+ Prism::ClassVariableOrWriteNode,
58
+ Prism::ClassVariableAndWriteNode,
59
+ Prism::ClassVariableTargetNode,
60
+ Prism::GlobalVariableWriteNode,
61
+ Prism::GlobalVariableOperatorWriteNode,
62
+ Prism::GlobalVariableOrWriteNode,
63
+ Prism::GlobalVariableAndWriteNode,
64
+ Prism::GlobalVariableTargetNode
65
+ ].freeze
66
+
67
+ # The call forms that invoke an attribute setter: `self.w = v`, the compound `self.w += v` / `||=` / `&&=`,
68
+ # and a multi-assign target `self.w, x = …`.
69
+ SETTER_CALL_NODES = Set[
70
+ Prism::CallNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode, Prism::CallAndWriteNode,
71
+ Prism::CallTargetNode
72
+ ].freeze
73
+ private_constant :SETTER_CALL_NODES
74
+
75
+ # An attribute writer's name: an identifier followed by `=`. `==`, `!=`, `<=`, `>=`, `===` and `[]=` are
76
+ # not setters.
77
+ SETTER_NAME = /\A[A-Za-z_]\w*=\z/
78
+ private_constant :SETTER_NAME
79
+
80
+ # The binding the per-element fold lays under a block's parameters: each name's type across iterations, and
81
+ # the optimistic nil-freeness mark an iteration's own rebind gave it, which {.bind} adds to the one the
82
+ # call site already holds — as `Scope#join` unions the marks of the scopes it joins. `repeated` is the
83
+ # `RepeatedOrWrites::Marks` of the index `||=` sites whose slot an earlier run may have filled, marked on the
84
+ # same scope: those of the per-element fold's `position`, or every site when no position is given.
85
+ Bindings = Data.define(:types, :marks, :repeated) do
86
+ def names = types.keys
87
+
88
+ def lay(scope, position: nil)
89
+ bound = types.reduce(scope) do |acc, (name, type)|
90
+ CapturedLocals.bind(acc, name, type, optimistic: marks[name])
91
+ end
92
+ bound.with_repeated_or_writes(repeated.at(position))
93
+ end
94
+ end
95
+
96
+ # The empty answers, shared: every block-bearing call's return pass asks both questions, and nearly every
97
+ # body answers nothing to either.
98
+ NO_NAMES = [].freeze
99
+ NO_SITES = {}.freeze
100
+ private_constant :NO_NAMES, :NO_SITES
101
+
31
102
  module_function
32
103
 
33
- # @param block_node [Prism::BlockNode]
34
- # @param base_scope [Rigor::Scope] the call-site scope the block closes over.
35
- # @return [Array<Symbol>] the captured names the body writes, each once, in first-write order.
36
- def writes(block_node, base_scope)
104
+ # @param base_scope — the call-site scope the block closes over.
105
+ # @param ivars — also collect the instance variables the body rebinds. An ivar is not captured — the
106
+ # block shares the caller's `self` — but it outlives an iteration, and the call, exactly as a captured
107
+ # local does. It counts on the same terms as a local (every write form, any depth, bound in
108
+ # `base_scope`), except that one still on its class-wide binding does not: ADR-58's declaration seed is
109
+ # the union of every write in the class, this body's included, so nothing the body stores can move it,
110
+ # and rebinding it would only drop the declaration mark that keeps its nil from being diagnostic fuel.
111
+ # A nested block that rebinds `self` (`o.instance_eval`) writes another object's ivar, and a nested
112
+ # `def` runs only when called; both still count, because an `instance_eval` without a receiver, or a
113
+ # call to that `def` inside the body, does write this one.
114
+ # @param non_locals — the per-element fold's superset of `ivars`: the class variables and globals the body
115
+ # rebinds count too, on the same terms, and so does an attribute setter called on `self` (`self.w = v`)
116
+ # as a rebind of the instance variable it is named after, `@w`: the `attr_writer` / `attr_accessor`
117
+ # convention stores there, and no write node shows it. A hand-written setter storing elsewhere is not
118
+ # seen.
119
+ # @return the captured names the body writes, each once, in first-write order.
120
+ def writes(block_node, base_scope, ivars: false, non_locals: false)
37
121
  body = block_node.body
38
- return [] if body.nil?
122
+ return NO_NAMES if body.nil?
123
+
124
+ introduced = nil
125
+ names = nil
126
+ outliving = non_locals || ivars
127
+ Source::NodeWalker.each_with_ancestors(body) do |descendant, ancestors|
128
+ name =
129
+ if LOCAL_WRITE_NODES.include?(descendant.class)
130
+ captured_local_write(descendant, ancestors, base_scope) { introduced ||= introduced_locals(block_node) }
131
+ elsif outliving
132
+ rebindable_outliving_write(descendant, base_scope, non_locals)
133
+ end
134
+ (names ||= []) << name if name
135
+ end
136
+ names ? names.uniq : NO_NAMES
137
+ end
138
+
139
+ def rebindable_outliving_write(node, base_scope, non_locals)
140
+ name = outliving_write_name(node, non_locals)
141
+ name if name && rebindable_non_local?(base_scope, name)
142
+ end
143
+
144
+ # The outer local a local-write node rebinds, or nil when the call site does not bind it, the write resolves
145
+ # inside a nested block or `def` ({.outer_local?}), or the block introduces it (the block yields the
146
+ # introduced set, computed only when needed).
147
+ def captured_local_write(node, ancestors, base_scope)
148
+ name = node.name
149
+ return nil unless base_scope.locals.key?(name) && outer_local?(node, ancestors)
150
+
151
+ yield.include?(name) ? nil : name
152
+ end
153
+
154
+ # The non-local `node` rebinds under `non_locals:` — or, without it, the instance variable only.
155
+ def outliving_write_name(node, non_locals)
156
+ name = non_local_write_name(node)
157
+ non_locals || (name && ivar_name?(name) && NON_LOCAL_WRITE_NODES.include?(node.class)) ? name : nil
158
+ end
159
+
160
+ # The instance, class or global variable `node` rebinds, or nil when it rebinds none of them.
161
+ def non_local_write_name(node)
162
+ return node.name if NON_LOCAL_WRITE_NODES.include?(node.class)
163
+
164
+ setter_ivar_name(node)
165
+ end
166
+
167
+ # `@w` for an attribute setter called on `self` (`self.w = …` and its compound and target forms), else nil.
168
+ def setter_ivar_name(node)
169
+ return nil unless SETTER_CALL_NODES.include?(node.class) && node.receiver.is_a?(Prism::SelfNode)
170
+
171
+ setter = node.respond_to?(:write_name) ? node.write_name : node.name
172
+ return nil unless SETTER_NAME.match?(setter)
173
+
174
+ :"@#{setter.to_s.delete_suffix('=')}"
175
+ end
176
+
177
+ def rebindable_non_local?(scope, name)
178
+ variable_kind(name) == :ivar ? rebindable_ivar?(scope, name) : !bound_type(scope, name).nil?
179
+ end
180
+
181
+ def rebindable_ivar?(scope, name)
182
+ !scope.ivar(name).nil? && !scope.declaration_sourced?(:ivar, name)
183
+ end
184
+
185
+ # The binding `scope` holds for a name from {.writes}.
186
+ def bound_type(scope, name)
187
+ case variable_kind(name)
188
+ when :ivar then scope.ivar(name)
189
+ when :cvar then scope.cvar(name)
190
+ when :global then scope.global(name)
191
+ else scope.local(name)
192
+ end
193
+ end
194
+
195
+ # `scope` with a name from {.writes} bound to `type`, keeping the name's optimistic nil-freeness mark
196
+ # (issue #286). `Scope#with_local` / `#with_ivar` drop it as a fresh write should, but here `type`
197
+ # stands for the binding across iterations, and a value that was nil-free only optimistically still is:
198
+ # without the mark `x.nil?` folds to `false` where the runtime answers `true`. `optimistic` is a mark the
199
+ # binding carries besides the one `scope` already holds — one an iteration's own rebind made. Class
200
+ # variables and globals carry no mark. {OptimisticOrigin.carry_local_mark} owns the re-marking, and with
201
+ # it the miss answer the mark records (issue #1302).
202
+ def bind(scope, name, type, optimistic: nil)
203
+ case variable_kind(name)
204
+ when :ivar then OptimisticOrigin.carry_ivar_mark(scope, scope.with_ivar(name, type), name, optimistic)
205
+ when :cvar then scope.with_cvar(name, type)
206
+ when :global then scope.with_global(name, type)
207
+ else OptimisticOrigin.carry_local_mark(scope, scope.with_local(name, type), name, optimistic)
208
+ end
209
+ end
210
+
211
+ # The optimistic nil-freeness mark `scope` holds for a name from {.writes}, or nil.
212
+ def optimistic_mark(scope, name)
213
+ case variable_kind(name)
214
+ when :ivar then scope.optimistic_ivar(name)
215
+ when :local then scope.optimistic_local(name)
216
+ end
217
+ end
218
+
219
+ def ivar_name?(name) = variable_kind(name) == :ivar
220
+
221
+ # Ruby spells a global with a leading `$`, a class variable with `@@`, an instance variable with a single
222
+ # `@`, and a local with none of them.
223
+ def variable_kind(name)
224
+ if name.start_with?("$") then :global
225
+ elsif name.start_with?("@@") then :cvar
226
+ elsif name.start_with?("@") then :ivar
227
+ else :local
228
+ end
229
+ end
230
+
231
+ # The nodes that change a receiver's CONTENT without rebinding it: a call to a name the straight-line
232
+ # widening responds to ({MutationWidening::SHAPE_MUTATORS}), and the index writes that store through
233
+ # `[]=` without being a `[]=` call ({IndexWriteWidening::CONTENT_WRITE_NODE_CLASSES}, the one list the
234
+ # block-return threading gate and the captured-local write-back read too).
235
+ INDEX_STORE_NODES = IndexWriteWidening::CONTENT_WRITE_NODE_CLASSES
236
+ private_constant :INDEX_STORE_NODES
237
+
238
+ # The captured outer locals the body mutates in place, each mapped to its mutation sites (the nodes
239
+ # above) in source order. A site counts through every variable its receiver can evaluate to
240
+ # ({ReceiverAlias.candidates}), at any nesting depth as long as a local's read does not resolve inside a nested
241
+ # block ({.outer_local?}), and a local is excluded on exactly the terms {.writes} excludes it.
242
+ #
243
+ # Under `non_locals: true` the instance variables, class variables and globals the body mutates in place
244
+ # count too, on the terms {.writes} takes a rebound one ({.rebindable_non_local?}). Since the block-return
245
+ # threading gate threads an index write, `@cache[:first] ||= e; @cache[:first] == 2` would otherwise type
246
+ # every position from the empty entry hash, store THAT position's `e`, and fold `find` to `2` where Ruby,
247
+ # keeping the first iteration's `1`, answers `nil`; `$seen << x; n == 1` after `n = $seen.size` folded
248
+ # `find` to `nil` the same way. A class variable or global counts only as the receiver itself (parentheses
249
+ # aside), not through a branch that selects it.
250
+ #
251
+ # A local also counts through the two routes straight-line code widens it by without reading it as the
252
+ # receiver. A mutator on an element read rooted at it (`a[0] << e`, the local {ElementReadWidening}
253
+ # widens) is a site of that local. A self-call whose callee content-mutates the parameter a local is passed
254
+ # to (`add_to(a, e)`, the local `StatementEvaluator#content_mutated_arguments` reports) is a
255
+ # {UnknownStoreWidening::CalleeStore} site of it. Both were invisible here, so every position of the fold
256
+ # read the local at its entry contents: `a = [[1]]; [1, 2].map { |e| v = a[0].size; a[0] << e; v }`
257
+ # folded to `[1, 1]` where Ruby answers `[1, 2]`.
258
+ #
259
+ # @param base_scope — the call-site scope the block closes over.
260
+ # @param non_locals — also collect the instance variables, class variables and globals the body mutates
261
+ # in place.
262
+ # @return `{ name => [site, ...] }`, empty for the overwhelmingly common body that mutates nothing
263
+ # captured.
264
+ def content_mutations(block_node, base_scope, non_locals: false)
265
+ body = block_node.body
266
+ return NO_SITES if body.nil?
267
+
268
+ introduced = nil
269
+ body_mutation_sites(body, base_scope, non_locals) { introduced ||= introduced_locals(block_node) }
270
+ end
271
+
272
+ # Issue #1412 — false when `node` certainly holds nothing {.writes} (with `ivars: true`) or
273
+ # {.content_mutations} could collect against `base_scope`: no write to a local it binds, no instance, class or
274
+ # global variable write, no mutator or index store, and no self-call passing a local. It over-approximates
275
+ # (a write a block parameter shadows still answers true) and allocates nothing, so a caller can skip both
276
+ # scans, and whatever it would compute only to use their answers, for the common body that touches no capture.
277
+ def may_touch_capture?(node, base_scope)
278
+ return true if capture_touch?(node, base_scope)
279
+
280
+ node.rigor_each_child { |child| return true if may_touch_capture?(child, base_scope) }
281
+ false
282
+ end
283
+
284
+ def capture_touch?(node, base_scope)
285
+ klass = node.class
286
+ return base_scope.locals.key?(node.name) if LOCAL_WRITE_NODES.include?(klass)
287
+ return true if NON_LOCAL_WRITE_NODES.include?(klass) || INDEX_STORE_NODES.include?(klass)
288
+ return false unless klass == Prism::CallNode
289
+
290
+ receiver = node.receiver
291
+ return true if receiver && MutationWidening::SHAPE_MUTATORS.include?(node.name)
292
+
293
+ callee_call?(node)
294
+ end
295
+
296
+ # Issue #1412 — {.content_mutations} for a `while` / `until` / `for` body: the locals bound in `base_scope`
297
+ # (the scope the loop body first enters from) that the body mutates in place. A loop body opens no scope and
298
+ # introduces no name, so every such local counts on the same depth terms a block's capture does — a read
299
+ # that resolves inside a nested block or lambda, or inside a nested `def` or class body, is not the loop's.
300
+ #
301
+ # @return `{ name => [site, ...] }`, empty for the common body that mutates nothing.
302
+ def loop_content_mutations(statements, base_scope)
303
+ return NO_SITES if statements.nil?
304
+
305
+ body_mutation_sites(statements, base_scope, false) { NO_INTRODUCED }
306
+ end
307
+
308
+ NO_INTRODUCED = Set.new.freeze
309
+ private_constant :NO_INTRODUCED
310
+
311
+ # The mutation sites under `body` whose reads name a variable {.captured_target?} accepts, keyed by name.
312
+ # The block yields the names the enclosing construct introduces, asked only when a local site is found.
313
+ def body_mutation_sites(body, base_scope, non_locals, &)
314
+ sites = nil
315
+ evaluator = nil
316
+ Source::NodeWalker.each_with_ancestors(body) do |descendant, ancestors|
317
+ # A callee is resolved in the call-site scope, as the straight-line callee floor resolves it.
318
+ site = mutation_site(descendant) { evaluator ||= StatementEvaluator.new(scope: base_scope) }
319
+ next if site.nil?
320
+
321
+ site_reads(site).each do |read|
322
+ next unless captured_target?(read, ancestors, base_scope, non_locals, &)
323
+
324
+ ((sites ||= {})[read.name] ||= []) << site
325
+ end
326
+ end
327
+ sites || NO_SITES
328
+ end
329
+
330
+ # The mutation site `node` is — the node itself, or a {UnknownStoreWidening::CalleeStore} for a self-call
331
+ # whose callee content-mutates a local it is passed — or nil. A self-call is resolved by the
332
+ # `StatementEvaluator` the block yields, which the caller builds only when one is needed. A mutator name
333
+ # called on `self` (`self.store(h, k)`, `self.push(a, x)`) names no variable as its receiver, so it is asked
334
+ # as a self-call, as the straight-line callee floor asks it.
335
+ def mutation_site(node)
336
+ receiver = mutated_receiver(node)
337
+ return node if receiver && !receiver.is_a?(Prism::SelfNode)
338
+ return nil unless callee_call?(node)
339
+
340
+ arguments = yield.content_mutated_arguments(node)
341
+ arguments.empty? ? nil : UnknownStoreWidening::CalleeStore.new(node, arguments)
342
+ end
343
+
344
+ # The variable reads a mutation site reaches: a callee store's arguments, the local an element read is
345
+ # rooted at, or else {ReceiverAlias.mutated_reads} of the receiver.
346
+ def site_reads(site)
347
+ return site.arguments if site.is_a?(UnknownStoreWidening::CalleeStore)
348
+
349
+ receiver = mutated_receiver(site)
350
+ path = ElementReadWidening.element_read_path(receiver)
351
+ path ? [path.first] : ReceiverAlias.mutated_reads(receiver)
352
+ end
353
+
354
+ # A call to a method on `self` (implicit or explicit) that passes a local as an argument — the only kind
355
+ # the callee floor can report a site for, so the only kind worth resolving.
356
+ def callee_call?(node)
357
+ return false unless node.is_a?(Prism::CallNode)
358
+ return false unless node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
359
+
360
+ arguments = node.arguments&.arguments
361
+ !arguments.nil? && arguments.any?(Prism::LocalVariableReadNode)
362
+ end
363
+
364
+ # True when `read` names a variable {.content_mutations} collects: a {.content_target?} which, when it is a
365
+ # local, does not resolve inside a nested block and is not one the block introduces (the block yields that set,
366
+ # computed only when needed). An `it` read is never one: it is the parameter of the innermost block around
367
+ # it, so the body's own `it` is introduced and a nested block's `it` is that block's.
368
+ def captured_target?(read, ancestors, base_scope, non_locals)
369
+ return false if read.is_a?(Prism::ItLocalVariableReadNode)
370
+ return false unless content_target?(read, base_scope, non_locals)
371
+ return true unless read.is_a?(Prism::LocalVariableReadNode)
372
+
373
+ outer_local?(read, ancestors) && !yield.include?(read.name)
374
+ end
375
+
376
+ # A local bound at the call site (the block's own names are excluded by the caller), or — under
377
+ # `non_locals:` — an instance variable, class variable or global {.rebindable_non_local?} accepts.
378
+ def content_target?(read, base_scope, non_locals)
379
+ return base_scope.locals.key?(read.name) if read.is_a?(Prism::LocalVariableReadNode)
380
+
381
+ non_locals && rebindable_non_local?(base_scope, read.name)
382
+ end
383
+
384
+ # False when a local read or write resolves inside a scope nested between the body and `node`. Prism resolves a
385
+ # name in a nested block's own scope first, so `[[9]].each { |a| a << x }` mutates, and `[[9]].each { |a| a =
386
+ # x }` rebinds, that block's parameter, not the outer local that happens to share its name: its `depth` stops
387
+ # short of the body's own scope. A `def`, `class`, `module` or `class << self` body opens a scope of its own
388
+ # that sees no outer local at all, so `def helper; z = 5; end` writes the method's `z` — but only its body,
389
+ # and a `def`'s parameters, are that scope: a `def (o = x).m` receiver, a `class Foo < (s = x)` superclass or
390
+ # a `class << (t = x)` target runs in the enclosing one and is left to the depth test. {.writes} and
391
+ # {.content_mutations} both ask it, so the two sets cannot disagree about which `a` a site names. A name
392
+ # resolving in the body's own scope is left to the block-introduced exclusion: Prism puts an outer local there
393
+ # only when the parse never saw it declared, which a synthetic call-site scope can still bind.
394
+ def outer_local?(node, ancestors)
395
+ nesting = 0
396
+ ancestors.each_with_index do |ancestor, index|
397
+ return false if hard_scope_entered?(ancestor, ancestors[index + 1] || node)
398
+
399
+ nesting += 1 if NESTED_SCOPE_NODES.include?(ancestor.class)
400
+ end
401
+ node.depth >= nesting
402
+ end
403
+
404
+ # True when `child`, the next node on the path, is inside the scope `ancestor` opens.
405
+ def hard_scope_entered?(ancestor, child)
406
+ case ancestor
407
+ when Prism::DefNode then child.equal?(ancestor.body) || child.equal?(ancestor.parameters)
408
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode then child.equal?(ancestor.body)
409
+ else false
410
+ end
411
+ end
39
412
 
40
- introduced = introduced_locals(block_node)
41
- outer_writes = []
42
- Source::NodeWalker.each(body) do |descendant|
43
- next unless LOCAL_WRITE_NODES.any? { |klass| descendant.is_a?(klass) }
44
- next if introduced.include?(descendant.name)
45
- next unless base_scope.locals.key?(descendant.name)
413
+ NESTED_SCOPE_NODES = Set[Prism::BlockNode, Prism::LambdaNode].freeze
414
+ private_constant :NESTED_SCOPE_NODES
46
415
 
47
- outer_writes << descendant.name
416
+ def mutated_receiver(node)
417
+ case node
418
+ when Prism::CallNode then node.receiver if MutationWidening::SHAPE_MUTATORS.include?(node.name)
419
+ when *INDEX_STORE_NODES then node.receiver
48
420
  end
49
- outer_writes.uniq
50
421
  end
51
422
 
52
423
  # Names the block itself introduces: parameters (numbered parameters included, via