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
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../analysis/dependency_recorder"
4
+ require_relative "../../typing_state"
5
+ require_relative "../../memo_audit"
6
+ require_relative "../../type"
7
+ require_relative "../census_foreign_block"
8
+ require_relative "../ivar_writer_census"
9
+
10
+ module Rigor
11
+ module Inference
12
+ module ScopeIndexer
13
+ # Issues #2079 and #2170 — the instance-side ivar writes the class-ivar census of one file does not walk, joined
14
+ # into the ivars that census already holds for a class `K`:
15
+ #
16
+ # - the writes another file's reopening of `K` makes;
17
+ # - the writes of every project module and superclass on `K`'s instance resolution chain (`include`,
18
+ # `prepend`, transitively, and `<`; for a module `K`, the modules it includes or prepends), from this file's
19
+ # own census of that ancestor where it declares one (`writes`, taken before the read-before-write `nil` is
20
+ # added, so an ancestor's `initialize` exempts nothing and its read-before-write adds nothing, and only for an
21
+ # ivar an instance body of the ancestor writes in this file) and from every other file's entries for it.
22
+ #
23
+ # Another file's write comes from its `[:own_write_type, owner, ivar, value, path]` census entry
24
+ # ({IvarWriterCensus}): a literal joins as its `Constant`, anything else as `Dynamic[top]`. An ivar the census
25
+ # has no entry for gains none, so a read of an ivar only another file writes still reads `Dynamic[top]`. The
26
+ # join only widens, and every joined type is in the seed, so a `nil` it brings is declaration-sourced (ADR-58
27
+ # WD1). A file the scope names no path for (a census built outside the project pre-pass, `rigor sig-gen`'s
28
+ # unseeded scopes) joins nothing.
29
+ #
30
+ # ADR-46: each ivar read files `own_write_type:<owner>#<ivar>` for `K` and each chain owner, which the
31
+ # incremental session fires when a file's entries for the pair appear, vanish or change value; and the chain
32
+ # files its class edges ({Scope::ResolutionChain#record}), so an `include` added or removed re-checks the reader.
33
+ module IvarContributions
34
+ # ADR-129 WD1 — a run-scoped Memo: the table is a pure function of the census it is keyed on by identity.
35
+ TABLE = TypingState.memo(
36
+ :ivar_contributions_table,
37
+ run_scoped: true, audit: "ivar contributions table",
38
+ reason: "the contribution table of one census; keyed on the census's identity"
39
+ )
40
+ EMPTY = {}.freeze
41
+ NONE = [].freeze
42
+ BINDINGS = :bindings
43
+ CANDIDATE_KEYS = :candidate_keys
44
+ ANCESTOR_ROOTS = CensusForeignBlock::ANCESTOR_ROOTS.to_a.freeze
45
+ # The one implicit root a class whose superclass chain stops at `BasicObject` keeps.
46
+ BASIC_ROOTS = %w[BasicObject].freeze
47
+ SEPARATOR = CensusForeignBlock::CANDIDATE_SEPARATOR
48
+ OBJECT_MARK = CensusForeignBlock::OBJECT_MARK
49
+ private_constant :TABLE, :EMPTY, :NONE, :BINDINGS, :CANDIDATE_KEYS, :ANCESTOR_ROOTS, :BASIC_ROOTS, :SEPARATOR,
50
+ :OBJECT_MARK
51
+
52
+ module_function
53
+
54
+ def join!(accumulator, writes, scope)
55
+ path = scope.source_path
56
+ return if path.nil?
57
+
58
+ path = canonical(path)
59
+
60
+ table = table(scope.discovered_global_write_census)
61
+ resolution = nil
62
+ accumulator.each do |class_name, entries|
63
+ next unless class_name.is_a?(String) && !entries.empty?
64
+
65
+ resolution ||= memoized_resolution(scope, table)
66
+ owners = chain_owners(scope, class_name, resolution)
67
+ any = any_ivar_types(owners, table)
68
+ entries.each_key do |ivar|
69
+ joined = contributed(owners, class_name, ivar, table, writes, path)
70
+ joined = joined ? Type::Combinator.union(joined, any) : any if any
71
+ entries[ivar] = Type::Combinator.union(entries[ivar], joined) if joined
72
+ end
73
+ end
74
+ end
75
+
76
+ # `K` and the project classes and modules on its instance chain, in chain order, with their class edges filed.
77
+ # A module's chain holds the modules it includes or prepends (#2487): each is on the chain of every class that
78
+ # includes the module, so its writes land on every object a read in the module's own methods sees. `extend`
79
+ # is no instance chain entry, for a module as for a class.
80
+ #
81
+ # Issue #2983 — the chain is read under the `:constants` flavor, so a project class or module with no method of
82
+ # its own (`class Anc; end`, whose writer a `class_eval` installs) is an owner too. {ANCESTOR_ROOTS}
83
+ # (`Object`, `BasicObject`, `Kernel`) follow, as every instance has them, except that a class whose superclass
84
+ # chain stops at `BasicObject` keeps `BasicObject` alone; then each owner's alias and instance constants
85
+ # (`[:binds_class, …]` entries), and every candidate key that resolves to an owner ({.resolution}), under which
86
+ # a `class_eval` rooted at that constant files its code.
87
+ def chain_owners(scope, class_name, resolution)
88
+ ancestors, reaches_object = ancestor_names(scope, class_name)
89
+ names = [class_name, *ancestors, *(reaches_object ? ANCESTOR_ROOTS : BASIC_ROOTS)].uniq
90
+ aliases, bound = resolution
91
+ bound_names = names.flat_map do |name|
92
+ segment = name.split("::").last
93
+ Analysis::DependencyRecorder.read_name(:binds_class, segment) if Analysis::DependencyRecorder.active?
94
+ bound[name] || NONE
95
+ end
96
+ (names + bound_names).uniq.flat_map { |owner| [owner, *(aliases[owner] || NONE)] }.uniq
97
+ end
98
+
99
+ # The project ancestors on `class_name`'s instance chain, and whether the chain reaches `Object`: false when a
100
+ # superclass edge names `BasicObject`, which has neither `Object` nor `Kernel` among its ancestors.
101
+ def ancestor_names(scope, class_name)
102
+ chain = Scope::ResolutionChain.for(scope, class_name, :instance, :constants)
103
+ # A guard: the class body's own `include` / `<` resolution already files these edges; kept so the read
104
+ # states its own dependency.
105
+ chain.record(scope) if Analysis::DependencyRecorder.active?
106
+ names = chain.entries.filter_map do |entry|
107
+ # An external entry has no name, so `filter_map` drops it. The side check is a guard: an instance chain
108
+ # holds only instance-side entries.
109
+ entry.name if entry.side == :instance && entry.name != class_name
110
+ end
111
+ [names, chain.entries.none? { |entry| basic_object_edge?(entry) }]
112
+ rescue StandardError
113
+ [NONE, true]
114
+ end
115
+
116
+ def basic_object_edge?(entry)
117
+ entry.external? && entry.superclass_edge &&
118
+ (entry.raw.to_s.delete_prefix("::") == "BasicObject" || entry.candidates.include?("BasicObject"))
119
+ end
120
+
121
+ # Review round 1 of #3001 — the candidate keys ({CensusForeignBlock.candidate_key}) resolved against the
122
+ # project: each to its first candidate the project declares as a class or module, or binds as a constant, else
123
+ # to its last (top-level) spelling, as Ruby's lexical lookup finds the nearest. Answers `[aliases, bound]`:
124
+ # `{ resolved owner => [candidate key, ...] }` over the census's owners, and `{ resolved class => [bound
125
+ # constant, ...] }` over its `[:binds_class, …]` entries.
126
+ #
127
+ # Review round 2: a candidate a signature declares (`sig/`, a gem's RBS) counts as existing too, so a nearer
128
+ # `N::Anc` only RBS knows wins over a top-level project `Anc` and credits no project class; and a key carrying
129
+ # {OBJECT_MARK} joins only when it resolves to an instance binding.
130
+ def resolution(scope, table)
131
+ bindings = table[BINDINGS] || NONE
132
+ constants = bindings.to_set { |_, name, _| name }
133
+ instances = bindings.filter_map { |_, name, kind| name if kind == :instance }.to_set
134
+ bound = {}
135
+ bindings.each { |key, name, _| (bound[resolve(scope, key, constants)] ||= []) << name }
136
+ aliases = {}
137
+ (table[CANDIDATE_KEYS] || NONE).each do |key|
138
+ resolved = resolve(scope, key.delete_prefix(OBJECT_MARK), constants)
139
+ next if key.start_with?(OBJECT_MARK) && (!instances.include?(resolved) || module?(scope, resolved))
140
+
141
+ (aliases[resolved] ||= []) << key
142
+ end
143
+ [aliases, bound]
144
+ end
145
+
146
+ def resolve(scope, key, constants)
147
+ candidates = key.split(SEPARATOR)
148
+ candidates.find do |name|
149
+ constants.include?(name) || scope.discovered_classes.key?(name) ||
150
+ scope.discovered_superclasses.key?(name) || scope.discovered_includes.key?(name) || declared?(scope, name)
151
+ end || candidates.last
152
+ end
153
+
154
+ # Review round 3 of #3001 — {.resolution} for `table`, computed once per run: it reads only the census and the
155
+ # project-wide declaration tables and signatures, which no file changes within a run. The value rides the
156
+ # {TABLE} slot beside the table it was computed from, keyed on the discovery index it read.
157
+ def memoized_resolution(scope, table)
158
+ slot = TypingState.current[TABLE]
159
+ return resolution(scope, table) unless slot && slot[1].equal?(table)
160
+
161
+ discovery = scope.discovery
162
+ cached = slot[2]
163
+ return cached[1] if cached && cached[0].equal?(discovery)
164
+
165
+ value = resolution(scope, table)
166
+ slot[2] = [discovery, value]
167
+ value
168
+ end
169
+
170
+ # Review round 3 of #3001 — whether `name` is a class or module the project or a signature declares, whose
171
+ # `.class` / `.singleton_class` no instance of it runs, whatever a constructor binding says. A guard beside
172
+ # {CensusForeignBlock.binding_entry}'s meta-class check, which records no such binding to begin with.
173
+ def module?(scope, name)
174
+ scope.discovered_classes.key?(name) || scope.discovered_superclasses.key?(name) ||
175
+ scope.discovered_includes.key?(name) || scope.environment&.rbs_loader&.class_known?(name) || false
176
+ rescue StandardError
177
+ true
178
+ end
179
+
180
+ # Whether a loaded signature declares `name` as a class, module or constant.
181
+ def declared?(scope, name)
182
+ loader = scope.environment&.rbs_loader
183
+ return false if loader.nil?
184
+
185
+ loader.class_known?(name) || !loader.constant_declaration(name).nil?
186
+ rescue StandardError
187
+ false
188
+ end
189
+
190
+ # Issue #2983 — what the owners' string `class_eval`s contribute to every ivar (`[:own_write_type, owner, :*,
191
+ # :dynamic]`), from every file, this one included: the per-file census reads no String either.
192
+ def any_ivar_types(owners, table)
193
+ types = []
194
+ owners.each do |owner|
195
+ # A guard: a String `class_eval` also gives its module the discovery index's dynamic mark, whose edge
196
+ # re-checks the reader when one comes or goes; kept so the read states its own dependency.
197
+ if Analysis::DependencyRecorder.active?
198
+ Analysis::DependencyRecorder.read_name(:own_write_type, "#{owner}##{IvarWriterCensus::ANY_IVAR}")
199
+ end
200
+ add_entry_types(types, table.dig(owner, IvarWriterCensus::ANY_IVAR) || EMPTY, nil)
201
+ end
202
+ types.empty? ? nil : Type::Combinator.union(*types)
203
+ end
204
+
205
+ # The union of what the owners contribute to `ivar`, or nil for nothing.
206
+ def contributed(owners, class_name, ivar, table, writes, path)
207
+ types = []
208
+ owners.each do |owner|
209
+ if Analysis::DependencyRecorder.active?
210
+ Analysis::DependencyRecorder.read_name(:own_write_type, "#{owner}##{ivar}")
211
+ end
212
+ by_path = table.dig(owner, ivar) || EMPTY
213
+ # This file's census of an ancestor counts only an ivar an instance body of it writes here. `writes` leaves
214
+ # out an ivar a `def self.` of the ancestor also writes (the census files that write under it, #1958, and it
215
+ # runs on the module object); this file's census entries for it join instead, as in another file (#2931).
216
+ own = writes.dig(owner, ivar) if owner != class_name && by_path.key?(path)
217
+ types << own if own
218
+ add_entry_types(types, by_path, own || owner == class_name ? path : nil)
219
+ end
220
+ types.empty? ? nil : Type::Combinator.union(*types)
221
+ end
222
+
223
+ # Each census entry's value type, except those of `skipped_path` (the file whose census already holds them).
224
+ def add_entry_types(types, by_path, skipped_path)
225
+ by_path.each do |value_path, values|
226
+ next if value_path == skipped_path
227
+
228
+ values.each { |value| types << value_type(value) }
229
+ end
230
+ end
231
+
232
+ # A String literal joins as `String`, not its `Constant`: a write elsewhere cannot see an in-place mutation of
233
+ # the String (`@s << "abc"`) in this file, in another or in an ancestor, so the value would be pinned too
234
+ # narrowly. Every other literal kind is immutable and joins as its `Constant`.
235
+ def value_type(value)
236
+ return Type::Combinator.untyped unless value.is_a?(Array)
237
+
238
+ literal = value[1]
239
+ literal.is_a?(String) ? Type::Combinator.nominal_of("String") : Type::Combinator.constant_of(literal)
240
+ end
241
+
242
+ # `path` resolved through symlinks, so a file the pre-pass tagged under one spelling (`/tmp/…`) is the one
243
+ # analysed under another (`/private/tmp/…`); the expanded spelling when it cannot be resolved.
244
+ def canonical(path)
245
+ File.realpath(path)
246
+ rescue SystemCallError
247
+ File.expand_path(path)
248
+ end
249
+
250
+ # `{ owner => { ivar => { path => [value, ...] } } }` over `census`'s path-tagged `:own_write_type` entries,
251
+ # built once per census and kept in one run-scoped slot keyed by its identity.
252
+ def table(census)
253
+ return build_table(census) if MemoAudit.enabled? && MemoAudit.suspended?
254
+
255
+ state = TypingState.current
256
+ slot = state[TABLE]
257
+ if slot && slot[0].equal?(census)
258
+ return MemoAudit.check("ivar contributions table", slot[1], census.size) { build_table(census) }
259
+ end
260
+
261
+ table = MemoAudit.key_audit("ivar contributions table") { build_table(census) }
262
+ state[TABLE] = [census, table]
263
+ table
264
+ end
265
+
266
+ # Issue #2983 — `table[BINDINGS]` lists `[class candidate key, constant name, kind]` over the census's
267
+ # `[:binds_class, …]` entries, and `table[CANDIDATE_KEYS]` the owners that are candidate keys; no class is named
268
+ # by a Symbol.
269
+ def build_table(census)
270
+ table = {}
271
+ canonical_paths = {}
272
+ census.each do |entry|
273
+ next (table[BINDINGS] ||= []) << entry.drop(1) if entry[0] == :binds_class
274
+ next unless entry.size == 5 && entry[0] == :own_write_type
275
+
276
+ by_path = (owner_slot(table, entry[1])[entry[2]] ||= {})
277
+ (by_path[canonical_paths[entry[4]] ||= canonical(entry[4])] ||= []) << entry[3]
278
+ end
279
+ table
280
+ end
281
+
282
+ def owner_slot(table, owner)
283
+ table[owner] ||= begin
284
+ (table[CANDIDATE_KEYS] ||= []) << owner if owner.include?(SEPARATOR) || owner.start_with?(OBJECT_MARK)
285
+ {}
286
+ end
287
+ end
288
+
289
+ private_class_method :chain_owners, :ancestor_names, :basic_object_edge?, :resolution, :resolve, :declared?,
290
+ :memoized_resolution, :module?, :any_ivar_types, :contributed, :add_entry_types,
291
+ :value_type, :canonical, :table, :build_table, :owner_slot
292
+ end
293
+ end
294
+ end
295
+ end
@@ -25,6 +25,18 @@ module Rigor
25
25
  # leaves the edges the tables did record looking positioned. So it taints the whole owner side with the
26
26
  # {WILDCARD} name instead: an owner side that lists {WILDCARD} has no known order at all.
27
27
  #
28
+ # Issue #2991 — {WILDCARD} says only that the ORDER is unknown: `include M; prepend M`, an edge recorded where its
29
+ # position is not a fact, and an opaque eval whose block mixes nothing into its own `self` all set it while every
30
+ # module is recorded. Where the walk recorded no module under any name, the instance side also lists
31
+ # {UNNAMED}, always beside {WILDCARD}, because its SET of modules is unknown too. That covers an instance-side
32
+ # mixin call it could not record whose argument is not a constant (`include helper`, `include(*MODS)`; a
33
+ # `Module.new { … }` argument keeps its #2975 reading), a name beside one it cannot name (`include M, h`), and
34
+ # an opaque eval whose block mixes into its own `self`, and a hook's `base.include M`. `self.include M` and a
35
+ # receiverless `send(:include, M)` are recorded as a bare `include M` is (#2995). Issue #2994 — the singleton side
36
+ # lists {UNNAMED} the same way for an `extend` (or a `class << self` `include` / `prepend`) it could not record
37
+ # whose arguments include no constant (`extend(*EMODS)`, `extend helper`), and for a name beside one it cannot
38
+ # name (`extend M, h`).
39
+ #
28
40
  # The value is `{owner => {include: [names], extend: [names]}}`. `:include` is the instance side
29
41
  # (`include` and `prepend`), `:extend` the singleton side (`extend`, and `include` / `prepend` inside
30
42
  # `class << self`), so an unknown `extend` never taints a class's instance ancestry.
@@ -33,6 +45,11 @@ module Rigor
33
45
  class MixinAccumulator < Hash
34
46
  # The name an owner side lists when a mixin call the walk could not record was written on it.
35
47
  WILDCARD = "*"
48
+ # The name an owner side also lists when such a call mixes in a module the walk cannot name (#2991).
49
+ UNNAMED = "?"
50
+ UNNAMED_LIST = [WILDCARD, UNNAMED].freeze
51
+ WILDCARD_LIST = [WILDCARD].freeze
52
+ private_constant :UNNAMED_LIST, :WILDCARD_LIST
36
53
 
37
54
  # What {#unpositioned} answers when nothing was noted: one shared frozen table, so the common file
38
55
  # allocates none.
@@ -47,7 +64,11 @@ module Rigor
47
64
  @unpositioned = nil
48
65
  @hook_params = nil
49
66
  @body_blocks = nil
67
+ @program_body = nil
50
68
  @program = nil
69
+ @class_bodies = nil
70
+ @class_body_calls = nil
71
+ @class_bodies_read = 0
51
72
  end
52
73
 
53
74
  # Marks the file's own top-level statements: a receiverless `include` among them is `main.include`,
@@ -55,11 +76,38 @@ module Rigor
55
76
  def program_body(body)
56
77
  return unless body.is_a?(Prism::StatementsNode)
57
78
 
58
- @program = Set.new.compare_by_identity.merge(body.body)
79
+ @program_body = body
59
80
  end
60
81
 
82
+ # Whether `node` is a call whose `self` is `main`'s as far as the walk can tell: a top-level statement, or
83
+ # a call reached from one through control flow alone (issue #3007), so a receiverless `include` there is
84
+ # `main.include`. The set is built on the first question, which only a top-level mixin call asks.
61
85
  def program_statement?(node)
62
- !@program.nil? && @program.include?(node)
86
+ return false if @program_body.nil?
87
+
88
+ @program ||= MainSelfCalls.collect(@program_body)
89
+ @program.include?(node)
90
+ end
91
+
92
+ # Issue #3006 — registers a class or module body (or its `class << self` body), whose calls
93
+ # {#class_body_scope} answers for.
94
+ def class_body(body)
95
+ (@class_bodies ||= []) << body if body
96
+ end
97
+
98
+ # The node whose locals a call written in a registered class body reads — the body itself, or the `def self.`
99
+ # it sits in — or nil when the call is not one whose `self` is that body's class as far as {MainSelfCalls}
100
+ # can tell (an instance `def`, a block that may rebind `self`, a nested declaration). Built on the first
101
+ # question, which only a computed-name `send` asks, and extended with the bodies registered since.
102
+ def class_body_scope(node)
103
+ return nil if @class_bodies.nil?
104
+
105
+ @class_body_calls ||= {}.compare_by_identity
106
+ while @class_bodies_read < @class_bodies.size
107
+ @class_body_calls.merge!(MainSelfCalls.collect(@class_bodies[@class_bodies_read], instance_defs: false))
108
+ @class_bodies_read += 1
109
+ end
110
+ @class_body_calls[node]
63
111
  end
64
112
 
65
113
  # Marks the statements of a body whose own position is a fact — the calls and declarations in them
@@ -110,17 +158,18 @@ module Rigor
110
158
  end
111
159
 
112
160
  # Notes the edges `node` recorded for `owner` on `side`. `complete` is false when an argument named
113
- # nothing the walk could record, which leaves the recorded names' neighbours unknown.
114
- def note(node, owner, side, targets, complete:)
115
- return taint(owner, side) unless complete
116
- return if node.receiver.nil? && direct?(node)
161
+ # nothing the walk could record, which leaves the recorded names' neighbours unknown. `positioned` says a
162
+ # receiver-form call runs where it is written, as a receiverless one does (`singleton_class.prepend P`, #2998).
163
+ def note(node, owner, side, targets, complete:, positioned: false)
164
+ return taint(owner, side, unnamed: true) unless complete
165
+ return if (positioned || node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)) && direct?(node)
117
166
 
118
167
  add(owner, side, targets)
119
168
  end
120
169
 
121
- # Names `owner`'s `side` as having no known order.
122
- def taint(owner, side)
123
- add(owner, side, [WILDCARD])
170
+ # Names `owner`'s `side` as having no known order; `unnamed` also as holding a module no name spells.
171
+ def taint(owner, side, unnamed: false)
172
+ add(owner, side, unnamed ? UNNAMED_LIST : WILDCARD_LIST)
124
173
  end
125
174
 
126
175
  # `{owner => {side => [names]}}`, frozen, each list de-duplicated.
@@ -138,6 +187,84 @@ module Rigor
138
187
  ((@unpositioned ||= {})[owner] ||= {})[side] = ((@unpositioned[owner][side] || []) + names)
139
188
  end
140
189
  end
190
+
191
+ # Issue #3007 — the calls whose `self` is `main` as far as the mixin walk can tell: the file's top-level
192
+ # statements, and the calls reached from them through control flow (`if`, `unless`, `case`, `begin`, `rescue`, a
193
+ # loop, `&&` / `||`) or a value position (an assignment's value, a call's receiver or argument, an array literal),
194
+ # or through the body of a top-level `def` or `def self.`, whose `include` the walk reads as ADR-119 WD7 reads one
195
+ # in a method body: a member of the set with no position. A block is entered only when it is a core iterator's
196
+ # that keeps `self` ({SELF_KEEPING_ITERATORS}) on a literal receiver, or a receiverless `loop` / `tap` / `then`.
197
+ # Any other block or lambda is not: its `self` may not be `main` (a `describe do … end` or `class_exec` block
198
+ # rebinds it), and neither is a declaration, `class << self` or a `def` on another receiver, whose `self` is not
199
+ # `main`. Recording more here can only silence: these edges feed no typing (`Inference::ObjectMixins`).
200
+ module MainSelfCalls
201
+ CONTROL_FLOW = [
202
+ Prism::StatementsNode, Prism::BeginNode, Prism::RescueNode, Prism::ElseNode, Prism::EnsureNode,
203
+ Prism::IfNode, Prism::UnlessNode, Prism::CaseNode, Prism::CaseMatchNode, Prism::WhenNode, Prism::InNode,
204
+ Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::ParenthesesNode, Prism::AndNode, Prism::OrNode,
205
+ Prism::RescueModifierNode,
206
+ Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::GlobalVariableWriteNode,
207
+ Prism::ConstantWriteNode, Prism::MultiWriteNode, Prism::ArgumentsNode, Prism::ArrayNode
208
+ ].freeze
209
+ # Core iterators whose block runs with the caller's `self`, entered on a literal receiver.
210
+ SELF_KEEPING_ITERATORS = %i[each each_with_index each_with_object map times upto downto tap then loop].freeze
211
+ # The iterators entered without a receiver: `Kernel#loop`, and `tap` / `then` on `main` itself.
212
+ RECEIVERLESS_ITERATORS = %i[tap then loop].freeze
213
+ LITERAL_RECEIVERS = [Prism::ArrayNode, Prism::RangeNode, Prism::IntegerNode].freeze
214
+ private_constant :CONTROL_FLOW, :SELF_KEEPING_ITERATORS, :RECEIVERLESS_ITERATORS, :LITERAL_RECEIVERS
215
+
216
+ module_function
217
+
218
+ # `{call => scope}`: each call and the node whose locals it reads, `body` or the `def` it sits in. Issue #3006 —
219
+ # a class body's calls are collected the same way with `instance_defs: false`: an instance `def`'s `self` is
220
+ # an instance, not the class, while a `def self.`'s is the class, and every block is entered.
221
+ def collect(body, instance_defs: true)
222
+ calls = {}.compare_by_identity
223
+ visit(body, calls, body, instance_defs)
224
+ calls
225
+ end
226
+
227
+ def visit(node, calls, scope, instance_defs)
228
+ case node
229
+ when Prism::CallNode
230
+ calls[node] = scope
231
+ visit(node.receiver, calls, scope, instance_defs)
232
+ visit(node.arguments, calls, scope, instance_defs)
233
+ entered = instance_defs ? self_keeping_block?(node) : any_block?(node)
234
+ visit(node.block.body, calls, scope, instance_defs) if entered
235
+ when Prism::DefNode
236
+ visit(node.body, calls, node, instance_defs) if (instance_defs && node.receiver.nil?) ||
237
+ node.receiver.is_a?(Prism::SelfNode)
238
+ when *CONTROL_FLOW
239
+ node.compact_child_nodes.each { |child| visit(child, calls, scope, instance_defs) }
240
+ end
241
+ end
242
+ private_class_method :visit
243
+
244
+ # Issue #3006 — a class body's calls include those in any block written there (`KINDS.each { … }`, a concern's
245
+ # `included do … end`): a computed `send` in one may mix into the class, or into what the block's `self` is.
246
+ def any_block?(call)
247
+ call.block.is_a?(Prism::BlockNode)
248
+ end
249
+ private_class_method :any_block?
250
+
251
+ def self_keeping_block?(call)
252
+ return false unless call.block.is_a?(Prism::BlockNode) && SELF_KEEPING_ITERATORS.include?(call.name)
253
+ return RECEIVERLESS_ITERATORS.include?(call.name) if call.receiver.nil?
254
+
255
+ LITERAL_RECEIVERS.any? { |kind| literal(call.receiver).is_a?(kind) }
256
+ end
257
+ private_class_method :self_keeping_block?
258
+
259
+ # `(1..2)` is a range literal in parentheses.
260
+ def literal(receiver)
261
+ return receiver unless receiver.is_a?(Prism::ParenthesesNode) && receiver.body.is_a?(Prism::StatementsNode)
262
+
263
+ body = receiver.body.body
264
+ body.size == 1 ? body.first : receiver
265
+ end
266
+ private_class_method :literal
267
+ end
141
268
  end
142
269
  end
143
270
  end