rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../typing_state"
5
6
  require_relative "project_patched_methods"
6
7
  require_relative "project_patched_definedness_guard"
7
8
  require_relative "project_patched_visibility"
@@ -45,6 +46,38 @@ module Rigor
45
46
  end
46
47
  private_constant :GuardedDef
47
48
 
49
+ # A `module_function` singleton copy a `def`-form produced (`module_function def x`, or a `def` under a
50
+ # bare `module_function`): `entry` is the copy to bind and `def_entry` the instance entry it was copied
51
+ # from, so {resolve_aliases} records which def the singleton binding answers — a later named
52
+ # `module_function :x` keeps the copy only while that def is proved unchanged (issue #3056).
53
+ SingletonCopy = Data.define(:entry, :def_entry) do
54
+ def source_path = entry.source_path
55
+ end
56
+ private_constant :SingletonCopy
57
+
58
+ # A statement the walk does not read that may rebind a method: any call other than a visibility call, a
59
+ # literal-named `alias_method`, or a receiverless `include` / `extend`, plus an `alias` with a computed
60
+ # name and an `undef`. `define_method(:x)`, `class_eval "def x"`, `send(:define_method, ...)` and
61
+ # `attr_reader :x` all make Ruby's next `module_function :x` copy a fresh public body, so after one
62
+ # {resolve_aliases} no longer assumes a copy is unchanged (issue #3056).
63
+ Unread = Data.define(:source_path)
64
+ private_constant :Unread
65
+
66
+ # The receiverless calls the walk reads as not rebinding a method of the body's own class.
67
+ MIXIN_CALLS = %i[include extend].freeze
68
+ private_constant :MIXIN_CALLS
69
+
70
+ # ADR-129 WD1 — the two per-file inputs of the walk, each a Frame {.walk_file} installs for the file's walk and
71
+ # restores on the way out: the `def`s on the undefined side of a definedness guard, and each `def`'s visibility.
72
+ # Both are frozen once built and read only by {.walk_node}'s callees.
73
+ #
74
+ # G1′: the walk is a Prism traversal that never types, so no `type_of` runs while either is installed.
75
+ GUARDED_DEFS = TypingState.frame(:patched_scanner_guarded_defs, reason: "installed per scanned file's walk",
76
+ typing_unreachable: true)
77
+ DEF_VISIBILITIES = TypingState.frame(:patched_scanner_def_visibilities, reason: "installed per scanned file",
78
+ typing_unreachable: true)
79
+ private_constant :GUARDED_DEFS, :DEF_VISIBILITIES
80
+
48
81
  module_function
49
82
 
50
83
  # @param paths — absolute paths to the pre-eval files. The runner has already
@@ -61,11 +94,13 @@ module Rigor
61
94
  paths.each { |path| scan_file(path, collected, diagnostics, buffer, census) }
62
95
  pending_mixins, collected = collected.partition { |item| item.is_a?(Mixins::Pending) }
63
96
  overrides = {}
64
- entries = resolve_aliases(collected, overrides)
97
+ singleton_copies = Set.new
98
+ entries = resolve_aliases(collected, overrides, singleton_copies)
65
99
  diagnostics.concat(duplicate_declaration_diagnostics(entries))
66
100
  Result.new(
67
101
  registry: ProjectPatchedMethods.new(entries: entries, write_census: census.freeze, overrides: overrides,
68
- mixins: Mixins.resolve(pending_mixins, entries)),
102
+ mixins: Mixins.resolve(pending_mixins, entries),
103
+ singleton_copies: singleton_copies),
69
104
  diagnostics: diagnostics
70
105
  )
71
106
  end
@@ -114,23 +149,37 @@ module Rigor
114
149
  # A {Visibility::Pending} (issue #1708) changes the visibility of the binding its name has when it runs, in the
115
150
  # file that bound it, so it is not a second declaration; a name no earlier patch binds on the class is one it
116
151
  # inherits, recorded in `overrides` ({Visibility.apply}). `module_function :x` also binds the public singleton
117
- # copy.
118
- def resolve_aliases(collected, overrides = {})
152
+ # copy: an entry {.applied} yields for a bound name, or a `[class_name, name]` pair in `singleton_copies`
153
+ # (issue #2108) for an inherited one, whose body the scan cannot name.
154
+ #
155
+ # `bound_defs` holds each key's def / alias-produced entry — the def identity a visibility change does not
156
+ # move — and `copy_sources` each singleton key's `module_function` copy source, the pair
157
+ # {Visibility.apply} reads to keep a repeated `module_function :x` from re-publicing a copy
158
+ # `private_class_method` made private (issue #3056).
159
+ def resolve_aliases(collected, overrides = {}, singleton_copies = Set.new)
119
160
  bound = {}
120
161
  homes = {}
162
+ bound_defs = {}
163
+ copy_sources = {}
121
164
  per_file = collected.group_by(&:source_path).values.map do |items|
122
165
  final = {}
123
166
  items.each do |item|
124
167
  if item.is_a?(Visibility::Pending)
125
- next Visibility.apply(item, bound, homes, overrides) { |copy| bind(copy, bound, homes, final) }
168
+ next Visibility.apply(item, bound, homes, overrides, singleton_copies, bound_defs, copy_sources) do |copy|
169
+ bind(copy, bound, homes, final)
170
+ end
126
171
  end
172
+ next if copy_provenance_item?(item, bound, homes, final, copy_sources)
127
173
 
128
174
  entry = item.is_a?(PendingAlias) ? alias_entry(item, bound) : item
129
175
  entry = entry.entry if entry.is_a?(GuardedDef)
130
176
  key = [entry.class_name, entry.method_name, entry.kind]
131
- next if item.is_a?(GuardedDef) && bound.key?(key)
177
+ # A guarded def dropped here may still run (`method_defined?` ignores a private method), so the
178
+ # copy it would rebind is no longer known to be unchanged.
179
+ next copy_sources.delete([key[0], key[1], :singleton]) if item.is_a?(GuardedDef) && bound.key?(key)
132
180
 
133
181
  bind(entry, bound, homes, final)
182
+ record_binding(key, entry, bound_defs, copy_sources)
134
183
  end
135
184
  final
136
185
  end
@@ -138,6 +187,33 @@ module Rigor
138
187
  end
139
188
  private_class_method :resolve_aliases
140
189
 
190
+ # The bookkeeping {resolve_aliases} keeps beside `bound`: `bound_defs` holds the def / alias
191
+ # entry `key` binds — the def identity a visibility change does not move, which
192
+ # {Visibility.apply}'s `module_function` re-copy check compares — and a singleton-side binding
193
+ # clears the copy provenance, since a `def self.x` between two `module_function :x` calls makes
194
+ # the next copy fresh (issue #3056).
195
+ def record_binding(key, entry, bound_defs, copy_sources)
196
+ bound_defs[key] = entry
197
+ copy_sources.delete([entry.class_name, entry.method_name, :singleton]) if entry.kind == :singleton
198
+ end
199
+ private_class_method :record_binding
200
+
201
+ # The two items that only move the `module_function` copy provenance {Visibility.apply}'s re-copy
202
+ # check compares (issue #3056), handled here and answered true: a {SingletonCopy} binds the copy and
203
+ # records which instance def entry it was made from, and an {Unread} statement forgets every record,
204
+ # since it may have rebound any name.
205
+ def copy_provenance_item?(item, bound, homes, final, copy_sources)
206
+ case item
207
+ when SingletonCopy
208
+ bind(item.entry, bound, homes, final)
209
+ copy_sources[[item.entry.class_name, item.entry.method_name, :singleton]] = item.def_entry
210
+ when Unread then copy_sources.clear
211
+ else return false
212
+ end
213
+ true
214
+ end
215
+ private_class_method :copy_provenance_item?
216
+
141
217
  def bind(entry, bound, homes, final)
142
218
  key = [entry.class_name, entry.method_name, entry.kind]
143
219
  bound[key] = entry
@@ -184,18 +260,16 @@ module Rigor
184
260
  "#{e.class}: #{e.message}. Pre-evaluation skipped for this file; " \
185
261
  "the rest of the run proceeds."
186
262
  )
187
- ensure
188
- Thread.current[:rigor_guarded_defs] = nil
189
- Thread.current[:rigor_def_visibilities] = nil
190
263
  end
191
264
  private_class_method :scan_file
192
265
 
193
266
  # Walks one parsed file with its definedness guards and `def` visibilities (issue #1708) in reach of
194
267
  # {record_def_node}.
195
268
  def walk_file(root, path, entries)
196
- Thread.current[:rigor_guarded_defs] = DefinednessGuard.defs(root)
197
- Thread.current[:rigor_def_visibilities] = Visibility.defs(root)
198
- walk_node(root, [], false, path, entries)
269
+ TypingState.current.with_pair(GUARDED_DEFS, DefinednessGuard.defs(root).freeze,
270
+ DEF_VISIBILITIES, Visibility.defs(root).freeze) do
271
+ walk_node(root, [], false, path, entries)
272
+ end
199
273
  end
200
274
  private_class_method :walk_file
201
275
 
@@ -231,17 +305,10 @@ module Rigor
231
305
  descend_singleton_class(node, qualified_prefix, source_path, entries)
232
306
  when Prism::DefNode
233
307
  record_def_node(node, qualified_prefix, in_singleton_class, source_path, entries)
234
- when Prism::AliasMethodNode
235
- record_alias(node, Source::AliasNames.keyword_names(node), qualified_prefix, in_singleton_class,
236
- source_path, entries)
308
+ when Prism::AliasMethodNode, Prism::UndefNode
309
+ record_keyword(node, qualified_prefix, in_singleton_class, source_path, entries)
237
310
  when Prism::CallNode
238
- record_alias(node, Source::AliasNames.alias_method_call_names(node), qualified_prefix,
239
- in_singleton_class, source_path, entries)
240
- if Thread.current[:rigor_def_visibilities]&.[](node).equal?(Visibility::STATEMENT)
241
- pending = Visibility.pending(node, qualified_prefix, in_singleton_class, source_path)
242
- entries << pending if pending
243
- entries.concat(Mixins.pending(node, qualified_prefix, in_singleton_class))
244
- end
311
+ record_call(node, qualified_prefix, in_singleton_class, source_path, entries)
245
312
  walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
246
313
  else
247
314
  walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
@@ -249,6 +316,34 @@ module Rigor
249
316
  end
250
317
  private_class_method :walk_node
251
318
 
319
+ # An `alias` with literal names binds as {record_alias} reads it; a computed-name `alias` or an `undef`
320
+ # is {Unread}.
321
+ def record_keyword(node, qualified_prefix, in_singleton_class, source_path, entries)
322
+ names = node.is_a?(Prism::AliasMethodNode) ? Source::AliasNames.keyword_names(node) : nil
323
+ entries << Unread.new(source_path: source_path) if names.nil?
324
+ record_alias(node, names, qualified_prefix, in_singleton_class, source_path, entries)
325
+ end
326
+ private_class_method :record_keyword
327
+
328
+ def record_call(node, qualified_prefix, in_singleton_class, source_path, entries)
329
+ names = Source::AliasNames.alias_method_call_names(node)
330
+ entries << Unread.new(source_path: source_path) if names.nil? && unread_call?(node)
331
+ record_alias(node, names, qualified_prefix, in_singleton_class, source_path, entries)
332
+ return unless TypingState.current[DEF_VISIBILITIES]&.[](node).equal?(Visibility::STATEMENT)
333
+
334
+ pending = Visibility.pending(node, qualified_prefix, in_singleton_class, source_path)
335
+ entries << pending if pending
336
+ entries.concat(Mixins.pending(node, qualified_prefix, in_singleton_class))
337
+ end
338
+ private_class_method :record_call
339
+
340
+ def unread_call?(node)
341
+ return false if Visibility.call(node)
342
+
343
+ !(MIXIN_CALLS.include?(node.name) && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)))
344
+ end
345
+ private_class_method :unread_call?
346
+
252
347
  def walk_children(node, qualified_prefix, in_singleton_class, source_path, entries)
253
348
  node.rigor_each_child do |child|
254
349
  walk_node(child, qualified_prefix, in_singleton_class, source_path, entries)
@@ -288,9 +383,9 @@ module Rigor
288
383
  source_path: source_path, source_line: line,
289
384
  return_type: return_type
290
385
  )
291
- entry, *copies = Visibility.applied(entry, Thread.current[:rigor_def_visibilities]&.[](node) || :public)
292
- entries << (Thread.current[:rigor_guarded_defs]&.key?(node) ? GuardedDef.new(entry: entry) : entry)
293
- entries.concat(copies)
386
+ entry, *copies = Visibility.applied(entry, TypingState.current[DEF_VISIBILITIES]&.[](node) || :public)
387
+ entries << (TypingState.current[GUARDED_DEFS]&.key?(node) ? GuardedDef.new(entry: entry) : entry)
388
+ entries.concat(copies.map { |copy| SingletonCopy.new(entry: copy, def_entry: entry) })
294
389
  end
295
390
  private_class_method :record_def_node
296
391
 
@@ -98,20 +98,56 @@ module Rigor
98
98
  # second declaration. A name the class does not bind is one it inherits (`public :kp` in `class Integer` over
99
99
  # `module Kernel; private def kp`), which Ruby copies onto the class with the new visibility: `overrides` gets
100
100
  # `key => visibility` for {ProjectPatchedMethods#lookup_through_ancestors} to apply to the body it finds farther
101
- # on. Yields each copy that binds (`module_function :x`'s singleton), at the call's line, for the caller to
101
+ # on. `module_function :x` also binds the public singleton copy: for a name the class binds, {.applied} yields
102
+ # it to bind here; for an inherited one (issue #2108) `singleton_copies` records `[class_name, name]`, which
103
+ # {ProjectPatchedMethods#singleton_copy_entry} resolves through the owner's instance ancestors at call time
104
+ # (an approximation: Ruby snapshots the body when module_function runs)
105
+ # — the copy's body is whichever method the name reaches there, which the scan cannot name.
106
+ # Yields each copy that binds (`module_function :x`'s singleton), at the call's line, for the caller to
102
107
  # bind.
103
- def apply(pending, bound, homes, overrides)
108
+ #
109
+ # `bound_defs` maps each bound key to the entry a `def` / `alias` last produced for it — the def
110
+ # identity a visibility change (`private :x`) does not move — and `copy_sources` maps a singleton key
111
+ # to the instance def entry its `module_function` copy was made from. Ruby re-copies the instance
112
+ # method onto the singleton only when that def changed since the last copy (issue #3056): a
113
+ # `private_class_method :x` between two `module_function :x` calls keeps the copy private, while a
114
+ # `def x` redefinition, or any statement the scan does not read, makes the next copy public again.
115
+ def apply(pending, bound, homes, overrides, singleton_copies, bound_defs, copy_sources)
104
116
  pending.names.each do |name|
105
117
  key = [pending.class_name, name, pending.kind]
106
118
  entry = bound[key]
107
- next overrides[key] = pending.visibility if entry.nil?
119
+ if entry.nil?
120
+ overrides[key] = pending.visibility
121
+ singleton_copies << [pending.class_name, name] if pending.visibility == :module_function
122
+ next
123
+ end
108
124
 
109
125
  changed, *copies = applied(entry, pending.visibility)
126
+ copies = [] if unchanged_module_function_copy?(pending, key, bound_defs, copy_sources)
110
127
  bound[key] = homes[key][key] = changed
111
128
  copies.each { |copy| yield copy.with(source_path: pending.source_path, source_line: pending.source_line) }
112
129
  end
113
130
  end
114
131
 
132
+ # Issue #3056 — true only when `pending` is a `module_function` named call whose singleton copy is
133
+ # proved to answer the same instance def: the copy's recorded source is still the def / alias
134
+ # binding `key` has. `resolve_aliases` drops that record whenever something may have rebound the
135
+ # name (a singleton-side `def`, a guarded def it dropped, or any statement the walk does not read),
136
+ # and then Ruby's re-copy is public, the reading that refuses no call Ruby runs. `bound_defs[key]` is
137
+ # the def entry `key` binds — a visibility change does not move it — and `copy_sources[skey]` the
138
+ # instance def entry the singleton's current copy was made from; either way the source is
139
+ # (re)recorded for the next call.
140
+ def unchanged_module_function_copy?(pending, key, bound_defs, copy_sources)
141
+ return false unless pending.visibility == :module_function
142
+
143
+ skey = [key[0], key[1], :singleton]
144
+ def_entry = bound_defs[key]
145
+ source = copy_sources[skey]
146
+ copy_sources[skey] = def_entry
147
+ !source.nil? && source.equal?(def_entry)
148
+ end
149
+ private_class_method :unchanged_module_function_copy?
150
+
115
151
  # `entry` as `visibility` leaves it, then any copy that binds: `module_function` makes an instance method
116
152
  # private and binds a public singleton copy, as Ruby does.
117
153
  def applied(entry, visibility)
@@ -3,6 +3,7 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../type"
6
+ require_relative "block_run_count"
6
7
 
7
8
  module Rigor
8
9
  module Inference
@@ -12,10 +13,12 @@ module Rigor
12
13
  # counted as running the block once, and a `return` in the block that only a later run reaches (`each { return
13
14
  # false if (seen += 1) > 3 }`) never joined the method's return type.
14
15
  #
15
- # Only the later-run `return` pass asks this (`StatementEvaluator#collect_repeated_block_returns`); the block's
16
- # entry scopes and the ADR-56 write-back keep {BlockRepetition}'s rule. That pass only widens the method's
17
- # return type, so the rule here errs toward "repeats". The callee repeats when its body, outside a nested `def`
18
- # (a `yield` in a class body is a syntax error inside a method):
16
+ # Only the later-run `return` pass asks {.may_run_block_repeatedly?}
17
+ # (`StatementEvaluator#collect_repeated_block_returns`); the block's entry scopes and the ADR-56 write-back keep
18
+ # {BlockRepetition}'s rule. That pass only widens the method's return type, so the rule here errs toward
19
+ # "repeats". The expression-position block-value pass asks {.block_value_may_repeat?} instead, whose rule
20
+ # ({BlockRunCount}) errs the other way. The callee repeats when its body, outside a nested `def` (a `yield` in a
21
+ # class body is a syntax error inside a method):
19
22
  #
20
23
  # - holds two or more sites that run the block (`yield`, or `call` / `yield` / `===` / `[]` on the `&blk`
21
24
  # parameter), or one inside a loop, a block or a lambda, which may run it many times; or
@@ -41,26 +44,17 @@ module Rigor
41
44
  false
42
45
  end
43
46
 
44
- # Issue #2301 — whether the callee hands the block it is called with on to another call (`g(x, &)`,
45
- # `g(x, &blk)`), outside a nested `def`. The block-return pass types such a forward's target `yield` as the
46
- # caller's block ({ExpressionTyper#forwarded_block_return_type}), and the target may run it any number of
47
- # times, so the caller's block is typed as one that may repeat. Answers false where the callee does not resolve.
48
- def hands_block_on?(call_node, receiver_type, scope)
47
+ # Issues #2301 and #2303 — whether the callee may run the block more than once on one path, for the
48
+ # expression-position block-value pass ({BlockRunCount}). Answers false where the callee does not resolve.
49
+ def block_value_may_repeat?(call_node, receiver_type, scope)
49
50
  def_node = callee_def(call_node, receiver_type, scope)
50
51
  return false unless def_node.is_a?(Prism::DefNode) && def_node.body
51
52
 
52
- hand_off_in?(def_node.body, def_node.parameters&.block&.name)
53
+ BlockRunCount.may_repeat?(def_node, scope)
53
54
  rescue StandardError
54
55
  false
55
56
  end
56
57
 
57
- def hand_off_in?(node, block_name)
58
- return false if node.is_a?(Prism::DefNode)
59
- return true if node.is_a?(Prism::BlockArgumentNode) && forwards_block?(node, block_name)
60
-
61
- node.compact_child_nodes.any? { |child| hand_off_in?(child, block_name) }
62
- end
63
-
64
58
  # The `def` an instance call on a project class, a singleton call on one, or an implicit-self call to a
65
59
  # top-level method resolves to.
66
60
  def callee_def(call_node, receiver_type, scope)
@@ -3,6 +3,8 @@
3
3
  require "rbs"
4
4
 
5
5
  require_relative "../type"
6
+ require_relative "../typing_state"
7
+ require_relative "../memo_audit"
6
8
 
7
9
  module Rigor
8
10
  module Inference
@@ -36,8 +38,9 @@ module Rigor
36
38
  # `untyped`. `Type::Intersection` stays out of this path: it is the same-base refinement composite
37
39
  # carrier, and the dispatch tiers do not accept it as a receiver.
38
40
  #
39
- # Interface types still degrade to `Dynamic[Top]`; they are bound to acceptance and dispatch rules
40
- # not yet implemented.
41
+ # Interface types degrade to `Dynamic[Top]`, except an interface in the `Rigor::Plugin` namespace, which
42
+ # translates to `Dynamic[Type::Interface]` (ADR-130 WD1/WD4): `untyped` to every rule, and resolvable only by a
43
+ # call of a member it declares (`RbsDispatch`).
41
44
  #
42
45
  # The optional `self_type:` and `instance_type:` arguments are the Rigor counterparts of RBS's
43
46
  # `self` and `instance` tokens:
@@ -78,7 +81,7 @@ module Rigor
78
81
  RBS::Types::Alias => :translate_alias,
79
82
  RBS::Types::Intersection => :translate_intersection,
80
83
  RBS::Types::Variable => :translate_variable,
81
- RBS::Types::Interface => :translate_untyped
84
+ RBS::Types::Interface => :translate_interface
82
85
  }.freeze
83
86
  private_constant :TRANSLATORS
84
87
 
@@ -106,14 +109,12 @@ module Rigor
106
109
  # repeat with one frozen String.
107
110
  @relative_names = {}
108
111
 
109
- # #775 — the translated form of a CLOSED alias expansion, keyed by the expansion. Closed means no
110
- # `self`, `instance` or type variable anywhere inside, so the caller's context cannot change the
111
- # answer; at expansion depth 0 the budget cannot either. rbs compares types by value, so the memo
112
- # is per distinct expansion, not per loader (a re-parsed identical alias hits), and the weak keys
113
- # let an entry go with the loader whose memo held the expansion. `Rigor::Type::t` on the self-check
114
- # is a 21-member union that was re-translated ~13k times, each time re-normalising the union from
115
- # scratch; every repeat now shares one Union.
116
- @closed_alias_translations = ObjectSpace::WeakKeyMap.new
112
+ # ADR-130 WD2 — the erasure baseline's flag, installed by {.with_interface_erasure}.
113
+ INTERFACE_ERASURE = TypingState.frame(
114
+ :interface_erasure, reason: "a flag installed around an erasure-baseline run (the parity harness)",
115
+ witness: "spec/fixtures/typing_state_witnesses/interface_erasure.rb"
116
+ )
117
+ private_constant :INTERFACE_ERASURE
117
118
 
118
119
  class << self
119
120
  # @param self_type — substitute for `Bases::Self`.
@@ -130,6 +131,20 @@ module Rigor
130
131
  translate_in(rbs_type, Context.new(self_type, instance_type, type_vars, alias_expander, 0))
131
132
  end
132
133
 
134
+ # ADR-130 WD2 — the erasure baseline. Inside the block every RBS interface translates to `untyped`, as it did
135
+ # before ADR-130, whatever carrier the default engine has; the behavioural parity gate
136
+ # (`spec/integration/interface_parity_spec.rb`) runs its baseline here, so the baseline never comes from the
137
+ # default engine. {translate_interface} reads {interface_erasure?}, and {translate_alias} bypasses the loader's
138
+ # closed-alias memo under it, so a translation made in one mode is never served in the other (ADR-130 WD4). The
139
+ # switch lives in the calling thread's (and fiber's) `TypingState`, so a run under it is sequential: a pooled
140
+ # run raises (`PoolCoordinator`).
141
+ def with_interface_erasure(&)
142
+ MemoAudit.check_run_level_install(:interface_erasure)
143
+ TypingState.current.with(INTERFACE_ERASURE, true, &)
144
+ end
145
+
146
+ def interface_erasure? = TypingState.current[INTERFACE_ERASURE] == true
147
+
133
148
  private
134
149
 
135
150
  def translate_in(rbs_type, context)
@@ -151,6 +166,28 @@ module Rigor
151
166
  Type::Combinator.untyped
152
167
  end
153
168
 
169
+ # ADR-130 WD4 — the namespace whose interfaces carry the facet: `::Rigor::Plugin::_Scope` and its siblings.
170
+ PLUGIN_INTERFACE_PATH = %i[Rigor Plugin].freeze
171
+ private_constant :PLUGIN_INTERFACE_PATH
172
+
173
+ # ADR-130 WD1/WD4 — a `Rigor::Plugin` interface is `Dynamic[Interface]`; every other interface, rbs core's
174
+ # conversion aliases (`int`, `string`, ...) included, is `untyped` as before. Which one is a pure function of
175
+ # the `TypeName` (its namespace path, read without allocating), so a closed alias translates alike whichever
176
+ # file or worker reaches it first. Under {with_interface_erasure} every interface is `untyped`.
177
+ def translate_interface(rbs_type, context)
178
+ name = rbs_type.name
179
+ return Type::Combinator.untyped unless name.namespace.path == PLUGIN_INTERFACE_PATH
180
+ return Type::Combinator.untyped if interface_erasure?
181
+
182
+ carrier =
183
+ if rbs_type.args.empty?
184
+ Type::Interface.new(relative_name(name))
185
+ else
186
+ Type::Interface.new(relative_name(name), rbs_type.args.map { |arg| translate_in(arg, context) })
187
+ end
188
+ Type::Combinator.dynamic(carrier)
189
+ end
190
+
154
191
  def translate_nil(_rbs_type, _context)
155
192
  Type::Combinator.constant_of(nil)
156
193
  end
@@ -251,18 +288,34 @@ module Rigor
251
288
 
252
289
  expanded = expander.expand_type_alias(rbs_type)
253
290
  return Type::Combinator.untyped if expanded.nil?
291
+
254
292
  # A nested expansion is translated under its parent's budget, and `rigor trace` records every
255
- # union merge as it happens, so neither consults the memo.
256
- return translate_in(expanded, context.deeper) unless context.alias_depth.zero? && !FlowTracer.active?
293
+ # union merge as it happens, so neither consults the memo. Nor does the erasure baseline (ADR-130 WD4): the
294
+ # memo holds the default engine's translations, where a `Rigor::Plugin` interface keeps its facet.
295
+ memo = closed_translations_of(expander)
296
+ unless memo && context.alias_depth.zero? && !FlowTracer.active? && !interface_erasure?
297
+ return translate_in(expanded, context.deeper)
298
+ end
257
299
 
258
- cached = @closed_alias_translations[expanded]
300
+ cached = memo[expanded]
259
301
  return cached if cached
260
302
 
261
303
  translated = translate_in(expanded, context.deeper)
262
- @closed_alias_translations[expanded] = translated if closed_type?(expanded)
304
+ memo[expanded] = translated if closed_type?(expanded)
263
305
  translated
264
306
  end
265
307
 
308
+ # #775 — the translated form of a CLOSED alias expansion, keyed by the expansion, lives with the expander.
309
+ # Closed means no `self`, `instance` or type variable anywhere inside, so the caller's context cannot change
310
+ # the answer; at expansion depth 0 the budget cannot either. But a nested alias inside the expansion is
311
+ # expanded through the expander (`type a = b | Integer` holds `b`), so the translation reads the expander's
312
+ # environment: a memo shared by every expander answered a second loader, whose `b` differs, with the first
313
+ # loader's translation (equal expansions are equal keys). The expander owns its memo, so it dies with it. An
314
+ # expander that offers none (anything answering only `expand_type_alias`) is translated afresh each time.
315
+ def closed_translations_of(expander)
316
+ expander.closed_alias_translations if expander.respond_to?(:closed_alias_translations)
317
+ end
318
+
266
319
  # True when nothing in `rbs_type`'s tree reads the translation context: no `self`, no `instance`,
267
320
  # no type variable. Every rbs type answers `each_type` (the leaves through `EmptyEachType`).
268
321
  def closed_type?(rbs_type)
@@ -85,6 +85,26 @@ module Rigor
85
85
  candidates(first, depth + 1) + candidates(second, depth + 1)
86
86
  end
87
87
 
88
+ # Calls whose result hands its caller the receiver's own element objects (issue #2287): `h.values` the hash's
89
+ # values, `a.dup` / `a.clone` a shallow copy sharing them, and the blockless enumerator spellings (`a.each`,
90
+ # `a.map`, `a.each_with_index`, `e.with_index`) the elements themselves.
91
+ ELEMENT_EXPOSING_CALLS = %i[values dup clone each map each_with_index with_index].to_set.freeze
92
+ private_constant :ELEMENT_EXPOSING_CALLS
93
+
94
+ # The variables whose elements a loop over `receiver` is handed: {.candidates} of `receiver` once every
95
+ # blockless {ELEMENT_EXPOSING_CALLS} call is peeled off, so `h.values.each { |v| v << 2 }` and
96
+ # `a.each_with_index.map { |x, i| x << i }` name `h` and `a`. Only a loop's element widening reads it; an
97
+ # in-place mutation OF such a result (`h.values << 1`, `a.dup << 1`) changes a new object, not the variable.
98
+ def element_sources(receiver)
99
+ depth = 0
100
+ while receiver.is_a?(Prism::CallNode) && receiver.block.nil? && ELEMENT_EXPOSING_CALLS.include?(receiver.name)
101
+ return [] if (depth += 1) > WALK_DEPTH_CAP
102
+
103
+ receiver = receiver.receiver
104
+ end
105
+ candidates(receiver)
106
+ end
107
+
88
108
  # The variable reads an in-place mutation of `receiver` changes: {.candidates}' locals and instance
89
109
  # variables, or the class variable or global the receiver reads or writes directly, parenthesised or not
90
110
  # (`(@@c ||= []) << x` mutates `@@c`, as `(buf ||= []) << x` mutates `buf`). A class variable or global counts
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Inference
7
+ # Issue #2488 — a writer a class gains from a call on an explicit receiver outside its body: `E.attr_writer :w`,
8
+ # `E.send(:attr_accessor, :y)`, `E.define_method(:set) { @v = … }` (and `define_method` through `send`). Ruby made
9
+ # `attr_*` and `define_method` public in 2.5, so each is the receiver class's own declaration, exactly as the same
10
+ # call in a `E.class_eval { … }` block is. Both ivar censuses read the calls through here: the per-file class-ivar
11
+ # walk ({ScopeIndexer}) and the cross-file {IvarWriterCensus}. The receiver's name is the caller's to resolve, the
12
+ # same way each resolves a `class_eval` receiver.
13
+ module ReceiverWriterCall
14
+ SEND_CALLS = %i[send __send__ public_send].to_set.freeze
15
+ WRITER_MACROS = %i[attr_writer attr_accessor].to_set.freeze
16
+ # The names a direct call spells; a `send` reaches any of them through its literal first argument.
17
+ CALLS = (WRITER_MACROS | SEND_CALLS | %i[define_method]).freeze
18
+ NO_ARGUMENTS = [].freeze
19
+
20
+ module_function
21
+
22
+ # The receiver of `node` when it is one of these calls on a receiver other than `self`, else nil.
23
+ def receiver(node)
24
+ return nil unless node.is_a?(Prism::CallNode) && CALLS.include?(node.name)
25
+
26
+ recv = node.receiver
27
+ recv unless recv.nil? || recv.is_a?(Prism::SelfNode)
28
+ end
29
+
30
+ # The ivar names an explicit-receiver `attr_writer` / `attr_accessor` declares (`[:@w]`), or an empty Array.
31
+ def writer_ivars(node)
32
+ name, arguments = called(node)
33
+ return NO_ARGUMENTS unless WRITER_MACROS.include?(name)
34
+
35
+ arguments.filter_map do |argument|
36
+ text = literal_name(argument)
37
+ :"@#{text}" if text
38
+ end
39
+ end
40
+
41
+ # The literal block an explicit-receiver `define_method` installs as an instance method, or nil.
42
+ def define_method_block(node)
43
+ name, = called(node)
44
+ block = node.block
45
+ block if name == :define_method && block.is_a?(Prism::BlockNode)
46
+ end
47
+
48
+ # The method `node` calls and its arguments, seen through a `send` whose first argument spells the name.
49
+ def called(node)
50
+ return [nil, NO_ARGUMENTS] if receiver(node).nil?
51
+
52
+ arguments = node.arguments&.arguments || NO_ARGUMENTS
53
+ return [node.name, arguments] unless SEND_CALLS.include?(node.name)
54
+
55
+ sent = literal_name(arguments.first)
56
+ sent ? [sent, arguments.drop(1)] : [nil, NO_ARGUMENTS]
57
+ end
58
+
59
+ def literal_name(argument)
60
+ return unless argument.is_a?(Prism::SymbolNode) || argument.is_a?(Prism::StringNode)
61
+
62
+ text = argument.unescaped
63
+ text.to_sym if text.valid_encoding?
64
+ end
65
+ end
66
+ end
67
+ end