rigortype 0.4.5 → 0.4.7

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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -2,12 +2,14 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "../analysis/dependency_recorder"
5
6
  require_relative "../source/node_children"
6
7
  require_relative "../source/parameter_envelope"
7
8
  require_relative "../type"
8
9
  require_relative "guard_rebinding"
9
10
  require_relative "project_method_ownership"
10
11
  require_relative "refined_dispatch/own_method"
12
+ require_relative "../run_scoped_slots"
11
13
 
12
14
  module Rigor
13
15
  module Inference
@@ -84,7 +86,9 @@ module Rigor
84
86
  :to_r, :to_c, :to_proc, :to_path, :to_sym, :to_io, :coerce, :exception, :divmod, :each, :respond_to?,
85
87
  :respond_to_missing?, :method_missing, :initialize_copy, :marshal_dump, :pretty_print
86
88
  ].freeze
87
- private_constant :UNPROVABLE_NODES, :LITERAL_CLASSES, :SELF_SAFE_NAMES, :PROTOCOL_NAMES
89
+ # Issue #2203 — the classes and module every project class's instances reach past its own chain.
90
+ IMPLICIT_ROOTS = %w[Object Kernel BasicObject].freeze
91
+ private_constant :UNPROVABLE_NODES, :LITERAL_CLASSES, :SELF_SAFE_NAMES, :PROTOCOL_NAMES, :IMPLICIT_ROOTS
88
92
 
89
93
  # Raised inside a proof to abandon it.
90
94
  class Unprovable < StandardError
@@ -112,6 +116,32 @@ module Rigor
112
116
  nil
113
117
  end
114
118
 
119
+ DEFINITION_COUNTS_KEY = RunScopedSlots.register(:__rigor_self_call_ivar_writes_definition_counts__)
120
+ private_constant :DEFINITION_COUNTS_KEY
121
+
122
+ # `{[owner, name] => the number of instance `def` rows of name the project records for owner}`, over every
123
+ # file of `deferred_ranges` (a `discovered_deferred_ranges` table). Built once per table and kept in ONE
124
+ # thread slot keyed by the table's identity (#2300): the table is never written in place, a file whose rows
125
+ # equal the seed's keeps the seed's table, and a scan of every file's rows per query was the cost once the
126
+ # table held the whole project. The slot holds one table and its counts, the next table replaces them, and the
127
+ # run that filled it releases it when it ends ({RunScopedSlots}, #2399).
128
+ def definition_counts(deferred_ranges)
129
+ slot = Thread.current[DEFINITION_COUNTS_KEY]
130
+ return slot[1] if slot && slot[0].equal?(deferred_ranges)
131
+
132
+ counts = {}
133
+ deferred_ranges.each_value do |rows|
134
+ rows.each do |row|
135
+ next unless row[3] == :instance
136
+
137
+ key = [row[4], row[2]]
138
+ counts[key] = counts.fetch(key, 0) + 1
139
+ end
140
+ end
141
+ Thread.current[DEFINITION_COUNTS_KEY] = [deferred_ranges, counts]
142
+ counts
143
+ end
144
+
115
145
  # One proof's state: the instance variables read code writes, and the `def` bodies already read.
116
146
  class Proof
117
147
  attr_reader :written
@@ -127,7 +157,7 @@ module Rigor
127
157
  # Reads the method an implicit-self or `self.` call to `call_node`'s name runs.
128
158
  def self_call(call_node)
129
159
  name = call_node.name
130
- unprovable! if GuardRebinding.code_running_name?(name) || refined?(name)
160
+ unprovable! if GuardRebinding.code_running_name?(name) || refined?(name, on_self: true)
131
161
 
132
162
  if ProjectMethodOwnership.defines?(@class_name, name, :instance, @scope)
133
163
  read_def(proven_def(name))
@@ -184,10 +214,66 @@ module Rigor
184
214
 
185
215
  private
186
216
 
187
- # Whether a `refine` block anywhere in the project defines `name`, on any class: a `using` in effect where the
188
- # read code sits may route the call to it (`refine Kernel do def warn …`), whatever the receiver.
189
- def refined?(name)
190
- @scope.discovered_refinements.any? { |_refined, methods| methods.key?(name) }
217
+ # Whether a `refine` block in the project defines `name` where the call may reach it: a `using` in effect
218
+ # where the read code sits may route the call to it (`refine Kernel do def warn …`).
219
+ #
220
+ # Issue #2163 — the answer reads every file's refinements whole, and a refined class off the chain (`Kernel`,
221
+ # `IO`, `String`) is no `mark:` the chain read files, so it records `refinement:<name>` and `refinement:*`
222
+ # whichever way it answers, as `InEffectRefinements` consumers do: a file that starts or stops refining the
223
+ # name re-checks the reader.
224
+ #
225
+ # Issue #2203 — for a call on `self` (`on_self:`) whose reach {#self_reach} knows, a row of the name counts only
226
+ # on a class key in that reach, on the wildcard class key, or on a key the walk could not name
227
+ # (`InEffectRefinements.class_wildcard_key?`); a names-wildcard row (a computed `define_method`,
228
+ # `import_methods`) counts as the name, but only on a class key in the reach. A refinement of `String` leaves
229
+ # a `Holder` alone: dropping the proof is no safer, since the intervening-call reset restores the class-ivar
230
+ # seed and a class guard's narrowing then reports. Where the reach is unknown, and for a call on another
231
+ # receiver, a row of the name counts on any class and a names-wildcard row never does, as before the issue.
232
+ def refined?(name, on_self: false)
233
+ wildcard = Scope::DiscoveryIndex::REFINEMENT_WILDCARD
234
+ if Analysis::DependencyRecorder.active?
235
+ Analysis::DependencyRecorder.read_name(:refinement, name)
236
+ Analysis::DependencyRecorder.read_name(:refinement, wildcard)
237
+ end
238
+ reach = on_self ? self_reach : nil
239
+ @scope.discovered_refinements.any? do |refined, methods|
240
+ next methods.key?(name) if reach.nil?
241
+
242
+ if reach.include?(refined)
243
+ methods.key?(name) || methods.key?(wildcard)
244
+ else
245
+ methods.key?(name) && InEffectRefinements.class_wildcard_key?(@scope, refined)
246
+ end
247
+ end
248
+ end
249
+
250
+ # Issue #2203 — the classes and modules on the instance chain of `self`'s class and of `Object`, `Kernel` and
251
+ # `BasicObject` (which holds the modules the project mixes into them), with `Object`'s RBS ancestors
252
+ # (`PP::ObjectMixin`); nil when a chain is cut, mixes in a module the tables cannot name, ends in a superclass
253
+ # they do not know (`class Holder < Struct.new(:a)`), or holds an external entry. A project subclass's chain
254
+ # needs no read: {#no_protocol_override} gives the proof up on a refined class or module anywhere on it. The
255
+ # implicit roots' chains are recorded, so a file that mixes a module into `Object` re-checks the reader;
256
+ # `self`'s own chain is recorded by {#no_protocol_override}'s read.
257
+ def self_reach
258
+ return @self_reach if defined?(@self_reach)
259
+
260
+ @self_reach = read_self_reach
261
+ end
262
+
263
+ def read_self_reach
264
+ names = Set.new
265
+ [@class_name.to_s, *IMPLICIT_ROOTS].each do |class_name|
266
+ chain = Scope::ResolutionChain.for(@scope, class_name, :instance, :methods)
267
+ chain.record(@scope) if Analysis::DependencyRecorder.active?
268
+ return nil if chain.truncated? || chain.wildcard_mixin? || chain.opaque_tail?(@scope)
269
+ return nil if chain.entries.any?(&:external?)
270
+
271
+ chain.entries.each { |entry| names << entry.name }
272
+ end
273
+ loader = @scope.environment&.rbs_loader
274
+ return nil if loader.nil?
275
+
276
+ names.merge(loader.ancestor_names_for("Object"))
191
277
  end
192
278
 
193
279
  def call_receiver(node) = node.is_a?(Prism::CallNode) ? node.receiver : nil
@@ -240,12 +326,11 @@ module Rigor
240
326
  # one file, with one `def` of the name there (a reopening or a `class_eval` body in the same file is a second
241
327
  # one), and no `define_method`, `attr_*` or alias of it, nor a rewrite no literal names (`T.define_method`,
242
328
  # `T.send(:define_method, …)`, `T.class_eval` with a computed name), nor a definition the walk could not prove
243
- # runs (a possible or contested one, as `DefinerResolution` reads them). The class edge files every declaring
244
- # file.
329
+ # runs (a possible or contested one, as `DefinerResolution` reads them). `Scope#parameter_envelopes_of` files
330
+ # the class edge to every declaring file and, issue #2137, the `mark:` edge for a file that only marks it.
245
331
  def single_definition(owner, name)
246
- Scope::ResolutionChain.record_class(@scope, owner) if Analysis::DependencyRecorder.active?
332
+ bucket = @scope.parameter_envelopes_of(owner)
247
333
  unprovable! if possible_definition?(owner, name)
248
- bucket = @scope.discovered_parameter_envelopes[owner] || {}
249
334
  unprovable! if bucket[[:instance, name]] == Source::ParameterEnvelope::OPAQUE
250
335
  unprovable! if (@scope.discovered_class_sources[owner]&.size || 0) > 1
251
336
  unprovable! if definition_count(owner, name) > 1
@@ -259,11 +344,7 @@ module Rigor
259
344
  end
260
345
 
261
346
  def definition_count(owner, name)
262
- count = 0
263
- @scope.discovered_deferred_ranges.each_value do |rows|
264
- count += rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
265
- end
266
- count
347
+ SelfCallIvarWrites.definition_counts(@scope.discovered_deferred_ranges).fetch([owner, name], 0)
267
348
  end
268
349
 
269
350
  # Whether `chain`'s own order stands with no second world: no order mark and no fork. `ResolutionChain#settle`
@@ -279,9 +360,12 @@ module Rigor
279
360
  end
280
361
 
281
362
  # Whether the project rewrites `class_name`'s method table beyond the names it spells, or refines it.
363
+ #
364
+ # Issue #2137 — read through `Scope#parameter_envelopes_of`, which files the `mark:` edge: a `refine` of the
365
+ # class from a file whose declared classes do not change fires no class key, and adding or removing it must
366
+ # re-check the reader.
282
367
  def rewritten?(class_name)
283
- bucket = @scope.discovered_parameter_envelopes[class_name]
284
- !bucket.nil? && Scope::DiscoveryIndex.rewritten_surface?(bucket)
368
+ Scope::DiscoveryIndex.rewritten_surface?(@scope.parameter_envelopes_of(class_name))
285
369
  end
286
370
 
287
371
  # The first project class or module on `class_name`'s settled instance chain that defines `name`, stopping
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../type"
4
+ require_relative "content_join"
5
+ require_relative "mutation_rejoin"
6
+ require_relative "rewrite_mutation"
7
+
8
+ module Rigor
9
+ module Inference
10
+ # The Set half of {MutationWidening}'s seams (issue #2294), beside {StringMutation}.
11
+ #
12
+ # `f = Set["a"]; f.clear; f << 1; f.map { |v| v.fdiv(2) }` reported `undefined method 'fdiv' for String`
13
+ # on code that prints `[0.5]`: the mutation seams answered `Tuple`, `HashShape`, `Constant[String]` and the
14
+ # re-openable `Array` / `Hash` nominals, so a `Set` kept the elements it was built with through every
15
+ # `<<`, `add`, `merge` and `replace`.
16
+ #
17
+ # A Set has no slots and no arity, so the only thing a mutation falsifies is the element set. Both carriers
18
+ # this module answers end at the typed Set #2290 builds for a non-constant literal — `Set[E | Dynamic[top]]`:
19
+ #
20
+ # - a constant-folded `Constant[Set]` widens to that carrier, its members' value pins dropped
21
+ # ({MethodDispatcher.array_new_element}) and joined with the `Dynamic[top]` floor;
22
+ # - a typed `Set` whose element already carries a gradual arm re-grows the way
23
+ # {MutationRejoin.regrowable_carrier?} lets an `Array` / `Hash` re-grow;
24
+ # - a PRECISE `Set[String]` (an RBS return such as `Array#to_set`) re-grows too (issue #2361), as a precise
25
+ # `Array[String]` does ({MutationRejoin.widen_precise_array}): `f = ARGV.to_set; f.clear; f << 1` is
26
+ # `Set[Dynamic[top] | String]`.
27
+ #
28
+ # A gradual seed admits every added member as itself: the seed-admissibility gate has nothing to contradict on
29
+ # it. A precise seed admits only a member its element accepts ({MutationRejoin.precise_admitted}), any other
30
+ # taking the floor, and takes no {RewriteMutation} arm: `replace` joins its elements onto the seed, and `map!`
31
+ # leaves it as it was. Every join carries the `Dynamic[top]` floor, as the Array re-join's does. An adder whose
32
+ # arguments the block-capture seam did not type joins only that floor: the store happened, and a Set has no
33
+ # block content join to add it afterwards. An adder called with no arguments changes nothing.
34
+ module SetMutation
35
+ # Receiver-mutating Set methods. The non-bang siblings (`map` vs `map!`, `select` vs `select!`) and the
36
+ # algebra (`|`, `+`, `-`, `&`, `^`) return a new Set and stay precise.
37
+ MUTATORS = %i[
38
+ << add add? merge replace
39
+ map! collect! flatten!
40
+ clear delete delete? delete_if keep_if select! filter! reject! subtract
41
+ ].to_set.freeze
42
+
43
+ # The {MUTATORS} that put elements in. `<<` / `add` / `add?` store their argument; `merge` stores every
44
+ # argument's elements; `replace` its argument's elements, after {RewriteMutation} has dropped the old ones.
45
+ CONTENT_ADDERS = %i[<< add add? merge replace].to_set.freeze
46
+
47
+ module_function
48
+
49
+ def constant?(type)
50
+ type.is_a?(Type::Constant) && type.value.is_a?(::Set)
51
+ end
52
+
53
+ # True when `type` is a `Set` carrier of either kind, which {#widen} answers for alone.
54
+ def set?(type)
55
+ constant?(type) || (type.is_a?(Type::Nominal) && type.class_name == "Set")
56
+ end
57
+
58
+ # True when `type` is a `Set` the seams may grow: a constant-folded one, or a typed one carrying a gradual
59
+ # element arm.
60
+ def carrier?(type)
61
+ constant?(type) || (type.is_a?(Type::Nominal) && regrowable?(type))
62
+ end
63
+
64
+ # True when `type`, or a member of it, is a {#carrier?}: the only types a Set-only adder's floor can change.
65
+ def carrier_in?(type)
66
+ type.is_a?(Type::Union) ? type.members.any? { |member| carrier?(member) } : carrier?(type)
67
+ end
68
+
69
+ # Every typed `Set[E]`, a precise one included since issue #2361.
70
+ def regrowable?(nominal)
71
+ nominal.class_name == "Set" && nominal.type_args.size == 1
72
+ end
73
+
74
+ # The carrier `type` holds after `method_name` with argument types `arg_types`, or `nil` when the call is not
75
+ # a Set mutator or `type` is not a carrier this seam grows. `nil` also answers a re-join that changed nothing,
76
+ # so a remover on an already-widened Set does not rebind it. That is a guard, not a behaviour: a Set has no
77
+ # `receiver[key] ||=` slot narrowings for a rebind to drop, so the identical carrier would read the same either
78
+ # way, and declining only saves rebuilding the scope.
79
+ #
80
+ # Issue #2555 L3 — `synthesised` says the binding carries the `:synthesised` construction fact: the engine built
81
+ # the Set itself (`s = Set[1]`, `Set.new(...)`), so no declaration made a claim about its elements, and the
82
+ # carrier stays re-growable for as long as the binding keeps the fact. Such a Set joins without the floor
83
+ # ({#floored}'s and {#join}'s) and without the precise seed's admissibility gate: `s = Set[1]; s << 2` reads
84
+ # `Set[Integer]`, and a read of its member reports what an Integer does not answer.
85
+ def widen(type, method_name, arg_types, unread_arguments: false, synthesised: false)
86
+ return nil unless MUTATORS.include?(method_name)
87
+
88
+ if constant?(type)
89
+ members = type.value.map { |v| Type::Combinator.constant_of(v) }
90
+ seed = synthesised && !members.empty? ? exact(members) : floored(members)
91
+ join(seed, method_name, arg_types, unread_arguments, synthesised:)
92
+ elsif type.is_a?(Type::Nominal) && regrowable?(type)
93
+ joined = join(type, method_name, arg_types, unread_arguments, synthesised:)
94
+ joined == type ? nil : joined
95
+ end
96
+ end
97
+
98
+ # Every join carries the `Dynamic[top]` floor, a precise seed's included (issue #2361): a later remover or
99
+ # rewriter (`subtract`, `delete`, `map!`) can take the stored members out again, and nothing narrows the
100
+ # carrier back, so a closed `Set[Integer | String]` would reject a declared `Set[String]` the program meets.
101
+ # A synthesised carrier (issue #2555) takes the rewrite arm a gradual one takes, admits every added member, and
102
+ # joins no floor: an untyped store joins `Dynamic[top]` as the one unknown member it is.
103
+ def join(carrier, method_name, arg_types, unread_arguments, synthesised: false)
104
+ seed = carrier.type_args.first
105
+ # A precise seed takes no rewrite arm, as a precise `Array` does not ({MutationRejoin.widen_precise_array}).
106
+ carrier = RewriteMutation.arm(carrier, method_name) if synthesised || MutationRejoin.gradual_parameter?(seed)
107
+ return carrier unless CONTENT_ADDERS.include?(method_name)
108
+
109
+ stored = added_elements(method_name, arg_types)
110
+ if stored.empty?
111
+ # An adder called with no arguments (`s.merge`) is a no-op; one whose arguments went untyped is a store.
112
+ return carrier unless unread_arguments
113
+
114
+ added = []
115
+ else
116
+ return carrier if !MutationRejoin.gradual_parameter?(seed) && MutationRejoin.already_held?(seed, stored)
117
+
118
+ added = admitted(seed, stored.map { |t| widened_member(t) }, synthesised:)
119
+ end
120
+ floor = synthesised && !added.empty? ? [] : [Type::Combinator.untyped]
121
+ element = Type::Combinator.union(carrier.type_args.first, *added, *floor)
122
+ Type::Combinator.nominal_of("Set", type_args: [element])
123
+ end
124
+
125
+ # `added` as {MutationRejoin.precise_admitted} admits it into a precise seed; a gradual or synthesised seed
126
+ # admits all.
127
+ def admitted(seed, added, synthesised: false)
128
+ return added if synthesised || MutationRejoin.gradual_parameter?(seed)
129
+
130
+ MutationRejoin.precise_admitted(seed, added)
131
+ end
132
+
133
+ # The element types one content-adder call puts in, from the call's argument types.
134
+ def added_elements(method_name, arg_types)
135
+ return [] if arg_types.empty?
136
+
137
+ case method_name
138
+ when :merge, :replace then arg_types.flat_map { |t| element_types(t) }
139
+ else [arg_types.first || Type::Combinator.untyped]
140
+ end
141
+ end
142
+
143
+ # `Set[E]` over `members`, value pins dropped: a constant Set a synthesised binding holds (issue #2555).
144
+ def exact(members)
145
+ Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(*members.map { |m| widened_member(m) })])
146
+ end
147
+
148
+ # `Set[E | Dynamic[top]]` over `members`, the #2290 typed-Set floor. An empty seed (`Set.new`) is
149
+ # `Set[Dynamic[top]]`.
150
+ def floored(members)
151
+ widened = members.map { |m| widened_member(m) }
152
+ Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.union(*widened, Type::Combinator.untyped)])
153
+ end
154
+
155
+ # The element types an enumerable argument (`merge`'s, `replace`'s, the algebra's `_Each[A]`) yields: a
156
+ # `Tuple`'s elements, an `Array` / `Set` / `Range` nominal's element parameter, a constant Set's or Range's
157
+ # members. Anything else yields elements this seam cannot name, which are unknown rather than none.
158
+ def element_types(type)
159
+ case type
160
+ when Type::Union then type.members.flat_map { |m| element_types(m) }
161
+ when Type::Tuple then type.elements
162
+ when Type::Nominal
163
+ if %w[Array Set Range].include?(type.class_name) && type.type_args.size == 1
164
+ type.type_args
165
+ else
166
+ [Type::Combinator.untyped]
167
+ end
168
+ when Type::Constant then constant_elements(type)
169
+ else [Type::Combinator.untyped]
170
+ end
171
+ end
172
+
173
+ def constant_elements(constant)
174
+ value = constant.value
175
+ if value.is_a?(::Set) then value.map { |v| Type::Combinator.constant_of(v) }
176
+ elsif value.is_a?(::Range) && value.begin.is_a?(Integer)
177
+ [Type::Combinator.nominal_of("Integer")]
178
+ else [Type::Combinator.untyped]
179
+ end
180
+ end
181
+
182
+ # A stored member's value pin and literal shape are dropped, as an `Array.new` fill's are: the set holds the
183
+ # object, and the program keeps a reference to it.
184
+ def widened_member(type)
185
+ MethodDispatcher.array_new_element(type)
186
+ end
187
+ end
188
+ end
189
+ end
@@ -105,8 +105,7 @@ module Rigor
105
105
  node = scope.singleton_def_for(owner, method_name)
106
106
  return nil if node.nil?
107
107
 
108
- nesting = scope.discovery.discovered_def_nestings[node] || DefNodeResolver.rehydrated_nesting(node)
109
- nesting&.first
108
+ scope.discovery.recorded_def_nesting(node)&.first
110
109
  end
111
110
 
112
111
  # Issue #1722 — the hook-capable test for one chain entry, asked by `call.undefined-method` of a project module