rigortype 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../../source/node_children"
6
+
7
+ module Rigor
8
+ module Inference
9
+ module MatchRebinding
10
+ # The frame a body runs in, stamped on its entry scope ({Scope#with_match_frame}) and shared by every scope
11
+ # derived from it, blocks included, since they run in the same frame. Each answer is computed on the first
12
+ # ask — only code run while a match global is narrowed asks — and kept for the rest of the body: whether the
13
+ # body makes a matching closure, whether its implicit-self calls keep the reset ({#self_call_fallback?}), the
14
+ # method's forwarded `&block` name, and {MatchRebinding.may_match?} per node ({#memo}). The same slot holds
15
+ # `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
16
+ # too, asked only while `$_` is narrowed.
17
+ class Frame
18
+ # The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
19
+ # parameters and targets.
20
+ LOCAL_BINDINGS = Set[
21
+ Prism::LocalVariableWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
22
+ Prism::LocalVariableAndWriteNode, Prism::LocalVariableTargetNode, Prism::RequiredParameterNode,
23
+ Prism::OptionalParameterNode, Prism::RestParameterNode, Prism::RequiredKeywordParameterNode,
24
+ Prism::OptionalKeywordParameterNode, Prism::KeywordRestParameterNode, Prism::BlockParameterNode,
25
+ Prism::BlockLocalVariableNode
26
+ ].freeze
27
+ private_constant :LOCAL_BINDINGS
28
+
29
+ # @param parameters — a method's parameters, whose default expressions run in its frame; nil for a class,
30
+ # module or file body.
31
+ def initialize(body, parameters = nil)
32
+ @body = body
33
+ @parameters = parameters
34
+ @matching_closure = nil
35
+ @self_call_fallback = nil
36
+ @forwarded_block = nil
37
+ @scans = nil
38
+ @last_line_closure = nil
39
+ @last_line_fallback = nil
40
+ end
41
+
42
+ def matching_closure?(scope = nil)
43
+ if @matching_closure.nil?
44
+ @matching_closure = MatchRebinding.matching_closure?(@body, scope) ||
45
+ MatchRebinding.matching_closure?(@parameters, scope)
46
+ end
47
+ @matching_closure
48
+ end
49
+
50
+ # True when an implicit-self call in this frame forgets the match globals as it did before issue #1364,
51
+ # because the body or a parameter default hands the frame's slot to code the analyzer does not trace
52
+ # ({MatchRebinding.self_call_fallback?}). Kept as {#memo} keeps a scan: the broad reading resolves constants
53
+ # and the variables a lookup argument names through `scope`.
54
+ def self_call_fallback?(scope = nil)
55
+ return fallback_in_frame?(scope) if scope.nil?
56
+
57
+ kept = @self_call_fallback
58
+ return kept[2] if kept && kept[0].equal?(scope.locals) && kept[1].equal?(scope.ivars)
59
+
60
+ result = fallback_in_frame?(scope)
61
+ @self_call_fallback = [scope.locals, scope.ivars, result]
62
+ result
63
+ end
64
+
65
+ # {#matching_closure?} for `$_` ({LastLine.closure?}).
66
+ def last_line_closure?(scope = nil)
67
+ if @last_line_closure.nil?
68
+ @last_line_closure = LastLine.closure?(@body, scope) || LastLine.closure?(@parameters, scope)
69
+ end
70
+ @last_line_closure
71
+ end
72
+
73
+ # {#self_call_fallback?} for `$_` ({LastLine.fallback?}). Its reading is by name alone, so it is kept once.
74
+ def last_line_fallback?(scope = nil)
75
+ if @last_line_fallback.nil?
76
+ block_name = block_parameter_name
77
+ @last_line_fallback = LastLine.fallback?(@body, block_name, scope) ||
78
+ LastLine.fallback?(@parameters, block_name, scope)
79
+ end
80
+ @last_line_fallback
81
+ end
82
+
83
+ # True when `name` is the method's own `&block` parameter and the body never binds that name — no write, no
84
+ # block or lambda parameter shadowing it — so every read of it in the frame is the block the caller passed,
85
+ # which the caller made in its own frame.
86
+ def forwarded_block?(name)
87
+ @forwarded_block = forwarded_block_name || false if @forwarded_block.nil?
88
+ @forwarded_block == name
89
+ end
90
+
91
+ # The scan of `node` under `scope`, kept while `scope`'s local and instance-variable tables are the same
92
+ # objects: a lookup argument's answer reads them ({MatchRebinding.may_match?}), and a rebuild that leaves
93
+ # them alone passes the same tables on. `kind` keeps another reading of the same node apart (issue #1365:
94
+ # {MatchRebinding.operands_may_rebind?}, and the code an eval runs), which the statement pass and each
95
+ # typing pass over the call ask again.
96
+ def memo(node, scope, kind = nil)
97
+ scans = kind.nil? ? (@scans ||= {}.compare_by_identity) : kind_scans(kind)
98
+ locals = scope.locals
99
+ ivars = scope.ivars
100
+ kept = scans[node]
101
+ return kept[2] if kept && kept[0].equal?(locals) && kept[1].equal?(ivars)
102
+
103
+ result = yield
104
+ scans[node] = [locals, ivars, result]
105
+ result
106
+ end
107
+
108
+ private
109
+
110
+ def kind_scans(kind)
111
+ tables = (@kind_scans ||= {})
112
+ tables[kind] ||= {}.compare_by_identity
113
+ end
114
+
115
+ def fallback_in_frame?(scope)
116
+ block_name = block_parameter_name
117
+ MatchRebinding.self_call_fallback?(@body, block_name, scope) ||
118
+ MatchRebinding.self_call_fallback?(@parameters, block_name, scope)
119
+ end
120
+
121
+ def forwarded_block_name
122
+ name = block_parameter_name
123
+ return nil if name.nil? || binds_local?(@body, name)
124
+
125
+ name
126
+ end
127
+
128
+ # The method's own `&block` parameter's name, or nil.
129
+ def block_parameter_name
130
+ parameters = @parameters
131
+ block = parameters.is_a?(Prism::ParametersNode) ? parameters.block : nil
132
+ block&.name
133
+ end
134
+
135
+ # `node` is a body or one of its descendants; a method with an empty body passes nil.
136
+ def binds_local?(node, name)
137
+ return false if node.nil?
138
+ return true if LOCAL_BINDINGS.include?(node.class) && node.name == name
139
+ return false if OWN_FRAME_NODES.include?(node.class)
140
+
141
+ found = false
142
+ node.rigor_each_child { |child| found ||= binds_local?(child, name) }
143
+ found
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../../source/node_children"
6
+ require_relative "../../type"
7
+
8
+ module Rigor
9
+ module Inference
10
+ module MatchRebinding
11
+ # Whether an operand of a call, a `when` condition or a pattern is a Regexp — the one question that decides
12
+ # whether `===`, `case` and the lookup names run a match. Two readings, because the constructs differ in
13
+ # what they usually hold:
14
+ #
15
+ # - a `when` condition, a pattern value and a `===` receiver are patterns far more often than not, so
16
+ # anything but a value known not to be a Regexp counts ({.pattern_value?}, {.pattern_matches?});
17
+ # - a `[]` / `split` / `index` argument is a lookup key far more often than not, so only a value known to be
18
+ # a Regexp counts ({.regexp_argument?}).
19
+ #
20
+ # A constant resolves through the scope. One that does not resolve is read as a class, the usual CamelCase
21
+ # referent, so it does not count either way — except under the `broad` reading, where it may be a Regexp
22
+ # defined in another file (#1373) and counts ({MatchRebinding.broad_may_match?}).
23
+ #
24
+ # Outside a block, a statement's operands are read on two further terms (issue #1365), because the flow scope
25
+ # types them but its types can be stale (#1380): a call the name table forgot on before keeps the narrowing only
26
+ # on a non-Regexp literal ({.keep_literal?}), and a call in a position that never forgot forgets only on an
27
+ # operand known to be a Regexp ({.known_regexp_operand?}).
28
+ module Operands
29
+ REGEX_LITERALS = Set[Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode].freeze
30
+ # Values whose `===` cannot run a match. An interpolated String or Symbol is still a String or Symbol; the
31
+ # code it embeds is scanned like any other.
32
+ NON_REGEXP_LITERALS = Set[
33
+ Prism::SymbolNode, Prism::InterpolatedSymbolNode, Prism::StringNode, Prism::InterpolatedStringNode,
34
+ Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode,
35
+ Prism::NilNode, Prism::TrueNode, Prism::FalseNode
36
+ ].freeze
37
+ # The literals {.keep_literal?} accepts: each is never a Regexp, whatever a flow type says.
38
+ KEEP_LITERALS = Set[
39
+ Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode,
40
+ Prism::ImaginaryNode, Prism::NilNode, Prism::TrueNode, Prism::FalseNode
41
+ ].freeze
42
+ CONSTANT_NODES = Set[Prism::ConstantReadNode, Prism::ConstantPathNode].freeze
43
+ PINNED_NODES = Set[Prism::PinnedVariableNode, Prism::PinnedExpressionNode].freeze
44
+ REGEXP_CONSTRUCTORS = Set[:new, :union, :compile].freeze
45
+ # The classes a Regexp is an instance of, so a value of one of these types may be a Regexp.
46
+ REGEXP_ANCESTORS = Set["Regexp", "Object", "BasicObject", "Kernel"].freeze
47
+ # How a class relates to `Regexp` when every instance of it is one.
48
+ REGEXP_CLASS_ORDERINGS = Set[:subclass, :equal].freeze
49
+ private_constant :REGEX_LITERALS, :NON_REGEXP_LITERALS, :KEEP_LITERALS, :CONSTANT_NODES, :PINNED_NODES,
50
+ :REGEXP_CONSTRUCTORS, :REGEXP_ANCESTORS, :REGEXP_CLASS_ORDERINGS
51
+
52
+ module_function
53
+
54
+ # True when a statement's operand is never a Regexp by its syntax alone: a Symbol, a String without
55
+ # interpolation, a number, `nil`, `true`, `false`, or a range of those. A call the name table forgot on before
56
+ # issue #1365 (`row[:name]`, `csv.split(",")`, `list.index(3)`) keeps the narrowing only when every argument is
57
+ # one, because a flow type that says "String" can be stale while the value is a Regexp (#1380).
58
+ def keep_literal?(node)
59
+ return true if KEEP_LITERALS.include?(node.class)
60
+ return false unless node.is_a?(Prism::RangeNode)
61
+
62
+ [node.left, node.right].all? { |bound| bound.nil? || KEEP_LITERALS.include?(bound.class) }
63
+ end
64
+
65
+ # True when `node` is a constant naming a class or module (`String === s`), whose `===` is `Module#===`.
66
+ def class_constant?(node, scope)
67
+ CONSTANT_NODES.include?(node.class) && node_type(node, scope).is_a?(Type::Singleton)
68
+ end
69
+
70
+ # True when a statement's operand is known to be a Regexp: a regex literal, a splat whose elements are known
71
+ # to be ones, or a value whose type in the flow scope is one ({.regexp_type?}; `Regexp.new(src)` types as
72
+ # one). `Dynamic[top]`, `Object` and any other type are not: a position that never forgot before issue #1365
73
+ # forgets only on evidence, so an argument that is a Regexp without the analyzer knowing it is a gap there,
74
+ # as it was before.
75
+ def known_regexp_operand?(node, scope)
76
+ return true if REGEX_LITERALS.include?(node.class)
77
+ return splat_known_regexp?(node.expression, scope) if node.is_a?(Prism::SplatNode)
78
+ return false if NON_REGEXP_LITERALS.include?(node.class)
79
+
80
+ regexp_type?(node_type(node, scope), scope)
81
+ end
82
+
83
+ # `*args` passes each element; an anonymous `*` forwards elements nothing is known about.
84
+ def splat_known_regexp?(expression, scope)
85
+ return false if expression.nil?
86
+
87
+ type = node_type(expression, scope)
88
+ case type
89
+ when Type::Tuple then type.elements.any? { |element| regexp_type?(element, scope) }
90
+ when Type::Nominal
91
+ type.class_name == "Array" ? type.type_args.any? { |element| regexp_type?(element, scope) } : false
92
+ else false
93
+ end
94
+ end
95
+ private_class_method :splat_known_regexp?
96
+
97
+ # True when a value of `type` may be a Regexp on the analyzer's evidence: a Regexp constant, `Regexp` or a
98
+ # class the environment places below it, a union or intersection with such a member, a refinement or
99
+ # difference over one, or a `Dynamic` whose static facet is one.
100
+ def regexp_type?(type, scope)
101
+ case type
102
+ when Type::Constant then type.value.is_a?(Regexp)
103
+ when Type::Nominal then regexp_class?(type.class_name, scope)
104
+ when Type::Union, Type::Intersection then type.members.any? { |member| regexp_type?(member, scope) }
105
+ when Type::Dynamic then regexp_type?(type.static_facet, scope)
106
+ when Type::Difference, Type::Refined then regexp_type?(type.base, scope)
107
+ else false
108
+ end
109
+ end
110
+
111
+ def regexp_class?(class_name, scope)
112
+ return true if class_name == "Regexp"
113
+
114
+ environment = scope&.environment
115
+ !environment.nil? && REGEXP_CLASS_ORDERINGS.include?(environment.class_ordering(class_name, "Regexp"))
116
+ end
117
+ private_class_method :regexp_class?
118
+
119
+ def regexp_constructor?(node)
120
+ node.is_a?(Prism::CallNode) && REGEXP_CONSTRUCTORS.include?(node.name) &&
121
+ node.receiver.is_a?(Prism::ConstantReadNode) && node.receiver.name == :Regexp
122
+ end
123
+ private_class_method :regexp_constructor?
124
+
125
+ def node_type(node, scope)
126
+ scope&.type_of(node)
127
+ rescue StandardError
128
+ nil
129
+ end
130
+ private_class_method :node_type
131
+
132
+ # True when a `===` receiver or a `when` condition may be a Regexp: a regex literal, a constant bound to
133
+ # one, a splat of a constant holding one, or any other expression (`when re`), but not a literal that is
134
+ # not one. `broad` counts an unresolved constant too.
135
+ def pattern_value?(node, scope, broad: false)
136
+ klass = node.class
137
+ return true if REGEX_LITERALS.include?(klass)
138
+ return false if non_regexp_literal?(node)
139
+ return constant_may_be_regexp?(node_type(node, scope), broad) if CONSTANT_NODES.include?(klass)
140
+ return splat_regexp?(node, scope, broad) if node.is_a?(Prism::SplatNode)
141
+
142
+ true
143
+ end
144
+
145
+ # True when an `in` / `=>` pattern holds a value that may be a Regexp: a regex literal, a pinned variable
146
+ # or expression, or a constant bound to one. Its structure, captures and other literals run no match, and
147
+ # an `if` / `unless` guard is ordinary code, scanned with the rest of the body. `broad` counts an unresolved
148
+ # constant too.
149
+ def pattern_matches?(node, scope, broad: false)
150
+ return false if node.nil?
151
+
152
+ klass = node.class
153
+ return true if REGEX_LITERALS.include?(klass) || PINNED_NODES.include?(klass)
154
+ return constant_may_be_regexp?(node_type(node, scope), broad) if CONSTANT_NODES.include?(klass)
155
+ if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
156
+ return pattern_matches?(node.statements, scope, broad: broad)
157
+ end
158
+
159
+ found = false
160
+ node.rigor_each_child { |child| found ||= pattern_matches?(child, scope, broad: broad) }
161
+ found
162
+ end
163
+
164
+ # True when an argument is known to be a Regexp: a regex literal, a constant bound to one, a local or
165
+ # instance variable bound to one where the block is written, or `Regexp.new` / `.union` / `.compile`. A
166
+ # block parameter is bound only inside the block, so `row[f]` does not count.
167
+ def regexp_argument?(node, scope)
168
+ case node
169
+ when Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode then true
170
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then regexp_type?(node_type(node, scope), scope)
171
+ when Prism::LocalVariableReadNode then regexp_type?(scope&.local(node.name), scope)
172
+ when Prism::InstanceVariableReadNode then regexp_type?(scope&.ivar(node.name), scope)
173
+ when Prism::CallNode then regexp_constructor?(node)
174
+ else false
175
+ end
176
+ end
177
+
178
+ # `when *KEYS` runs `===` on each element of `KEYS`; a splat of anything but a constant counts.
179
+ def splat_regexp?(splat, scope, broad)
180
+ expression = splat.expression
181
+ return true unless CONSTANT_NODES.include?(expression.class)
182
+
183
+ type = node_type(expression, scope)
184
+ (broad && unresolved?(type)) || elements_regexp?(type)
185
+ end
186
+ private_class_method :splat_regexp?
187
+
188
+ def elements_regexp?(type)
189
+ case type
190
+ when Type::Tuple then type.elements.any? { |element| constant_regexp?(element) }
191
+ when Type::Nominal then type.type_args.any? { |argument| constant_regexp?(argument) }
192
+ when Type::Constant then type.value.is_a?(Array) && type.value.any?(Regexp)
193
+ when Type::Union then type.members.any? { |member| elements_regexp?(member) }
194
+ else false
195
+ end
196
+ end
197
+ private_class_method :elements_regexp?
198
+
199
+ def non_regexp_literal?(node)
200
+ return true if NON_REGEXP_LITERALS.include?(node.class)
201
+ return false unless node.is_a?(Prism::RangeNode)
202
+
203
+ [node.left, node.right].all? { |bound| bound.nil? || non_regexp_literal?(bound) }
204
+ end
205
+ private_class_method :non_regexp_literal?
206
+
207
+ def constant_may_be_regexp?(type, broad)
208
+ constant_regexp?(type) || (broad && unresolved?(type))
209
+ end
210
+ private_class_method :constant_may_be_regexp?
211
+
212
+ # A constant that does not resolve reads as `Dynamic`, or as nil without a scope.
213
+ def unresolved?(type)
214
+ case type
215
+ when nil, Type::Dynamic then true
216
+ when Type::Union then type.members.any? { |member| unresolved?(member) }
217
+ else false
218
+ end
219
+ end
220
+ private_class_method :unresolved?
221
+
222
+ # True when a constant's type may be a Regexp. A class or module, a Tuple or HashShape, any other value,
223
+ # and a constant that does not resolve do not.
224
+ def constant_regexp?(type)
225
+ case type
226
+ when Type::Constant then type.value.is_a?(Regexp)
227
+ when Type::Nominal then REGEXP_ANCESTORS.include?(type.class_name)
228
+ when Type::Union then type.members.any? { |member| constant_regexp?(member) }
229
+ else false
230
+ end
231
+ end
232
+ private_class_method :constant_regexp?
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MatchRebinding
8
+ # Method names that may run a regex match, read by name alone: a Symbol or String literal naming one counts
9
+ # ({SelfCalls.method_name_literal?}), since the method it is handed to may run it from C (`inject(:=~)`). A call
10
+ # itself is read with its arguments ({Calls}, issue #1365).
11
+ MATCH_CAPABLE_METHODS = %i[
12
+ =~ match match? gsub gsub! sub sub! scan split slice slice!
13
+ [] partition rpartition index rindex === grep grep_v
14
+ ].freeze
15
+
16
+ # The calls past that table that may rebind the frame they are made from, read by name alone (issue #1364), as
17
+ # the broad reading of a frame's blocks counts them ({MatchRebinding.broad_may_match?}), and as the calls an
18
+ # implicit-self call forgot on before issue #1365, which {Calls.base_named?} keeps forgetting on.
19
+ module SelfCalls
20
+ # Builtins that set their caller's `$~` and the table misses: `x !~ re`, `~re`, and `start_with?(re)`,
21
+ # `byteindex(re)`, `byterindex(re)`, `s[re] = v` — the ones a `String` or `Regexp` subclass inherits too.
22
+ SELF_MATCHING = Set[:!~, :~, :start_with?, :byteindex, :byterindex, :[]=].freeze
23
+ # Predicates that run `pattern === element` from C when given an argument, even when the class's `each` is
24
+ # written in Ruby.
25
+ PATTERN_PREDICATES = Set[:any?, :all?, :none?, :one?].freeze
26
+ # These run a String of code in the frame that calls them: `eval` always, the other three in their String
27
+ # form only, since their block form is a block ({MatchRebinding.block_may_match?}).
28
+ EVALS = Set[:eval].freeze
29
+ STRING_EVALS = Set[:instance_eval, :class_eval, :module_eval].freeze
30
+ # These run the method they name, which counts when it is one of {METHOD_NAMES} or not a literal.
31
+ SENDS = Set[:send, :__send__, :public_send].freeze
32
+ # Every name above and in the table, as Strings: a literal compares by its bytes, which an invalid one
33
+ # (`send("\xff")`) cannot turn into a Symbol.
34
+ METHOD_NAMES = (
35
+ MATCH_CAPABLE_METHODS.to_set | SELF_MATCHING | PATTERN_PREDICATES | EVALS | STRING_EVALS | SENDS
36
+ ).to_set(&:to_s).freeze
37
+ private_constant :SELF_MATCHING, :PATTERN_PREDICATES, :EVALS, :STRING_EVALS, :SENDS, :METHOD_NAMES
38
+
39
+ module_function
40
+
41
+ # True when `call_node`, on any receiver, is a call past the table that may rebind the frame it is made
42
+ # from: a {SELF_MATCHING} name; a {PATTERN_PREDICATES} name with an argument; an eval of a String
43
+ # ({EVALS}, or {STRING_EVALS} with an argument); or a {SENDS} call whose name is not a literal, or names a
44
+ # method that counts.
45
+ def named_match?(call_node)
46
+ name = call_node.name
47
+ arguments = call_node.arguments&.arguments
48
+ return true if SELF_MATCHING.include?(name) || EVALS.include?(name)
49
+ return !arguments.nil? if PATTERN_PREDICATES.include?(name) || STRING_EVALS.include?(name)
50
+ return sent_name_matches?(arguments&.first) if SENDS.include?(name)
51
+
52
+ false
53
+ end
54
+
55
+ # True when a {SENDS} call's name argument is not a Symbol or String literal, or names a method that counts.
56
+ def sent_name_matches?(name_node)
57
+ return method_name_literal?(name_node) if literal_name?(name_node)
58
+
59
+ true
60
+ end
61
+
62
+ # True when `node` is a Symbol or String literal naming the table's methods or the ones above:
63
+ # `inject(:=~)` runs `=~` from C in the frame that calls it.
64
+ def method_name_literal?(node)
65
+ literal_name?(node) && METHOD_NAMES.include?(node.unescaped)
66
+ end
67
+
68
+ # True for `send(:binding)`, or a {SENDS} call whose name is not a literal and so may be `:binding`.
69
+ def sends_binding?(call_node)
70
+ return false unless SENDS.include?(call_node.name)
71
+
72
+ name_node = call_node.arguments&.arguments&.first
73
+ !literal_name?(name_node) || name_node.unescaped == "binding"
74
+ end
75
+
76
+ def literal_name?(node)
77
+ node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
78
+ end
79
+ private_class_method :literal_name?
80
+ end
81
+ end
82
+ end
83
+ end