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
@@ -28,13 +28,12 @@ module Rigor
28
28
  # a flag threaded into `Scope`: an ADR-2 plugin-facing bypass of dependency recording would produce
29
29
  # a silently stale warm cache that no diagnostic diff can show.
30
30
  #
31
- # There is deliberately NO memo here. The dispatch consumer this module is being extracted FOR does
32
- # not exist yet, and a memo keyed on the tables this slice happens to name would be keyed on less
31
+ # There is deliberately NO memo here. A memo keyed on the tables the walk names would be keyed on less
33
32
  # than the walk reads — the ancestor chain resolves names through `known_user_class?`
34
33
  # (`discovered_def_nodes`, `discovered_methods`) and `ancestor_name_candidates`
35
34
  # (`discovered_header_nestings`). The chain itself is memoised per discovery index
36
- # ({Scope::ResolutionChain}); the slice that brings the hot path brings this module's memo, keyed on
37
- # what that consumer actually reads.
35
+ # ({Scope::ResolutionChain}); the dispatch callers in {MethodDispatcher::RbsDispatch} memoise their
36
+ # own answers, keyed on what they read.
38
37
  module ExternalAncestorResolution
39
38
  # The owner a name must PRECEDE for its declaration to win an MRO. See {declared_before_object?}.
40
39
  OBJECT_OWNER = "Object"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  module Inference
5
7
  # Thread-local event recorder behind `rigor trace`: while a block runs under {record}, the inference engine
@@ -13,8 +15,14 @@ module Rigor
13
15
  # hot paths (`ExpressionTyper#type_of`, `Scope#with_local`, `Type::Combinator.union`, `MethodDispatcher.dispatch`)
14
16
  # each guard their emit behind {active?}, so a normal (non-tracing) run pays one integer comparison.
15
17
  module FlowTracer
16
- KEY = :__rigor_flow_tracer__
17
- private_constant :KEY
18
+ # ADR-129 WD1 — the installed recorder is a Frame whose contents are a Resource: each emit into it touches. It is
19
+ # an output the engine never reads back, and the activation count below is a process switch, outside the typing
20
+ # state (WD5). G1′: a tracer switch, under which the `type_of` memo stays shut (WD4).
21
+ RECORDER = TypingState.frame(
22
+ :flow_tracer, contents: :resource, reason: "the recorder a trace installs; each emit records into it",
23
+ memo_shut_while_installed: true
24
+ )
25
+ private_constant :RECORDER
18
26
 
19
27
  # One animation-relevant moment.
20
28
  #
@@ -95,15 +103,16 @@ module Rigor
95
103
  # Activates recording on the current thread for the duration of the block and returns the frozen event list.
96
104
  # Nests safely; restores the previous recorder on exit.
97
105
  def record
98
- previous = Thread.current[KEY]
99
106
  recorder = Recorder.new
100
- Thread.current[KEY] = recorder
101
- @mutex.synchronize { @active_count += 1 }
102
- yield
103
- recorder.events.freeze
104
- ensure
105
- Thread.current[KEY] = previous
106
- @mutex.synchronize { @active_count -= 1 }
107
+ TypingState.current.with(RECORDER, recorder) do
108
+ @mutex.synchronize { @active_count += 1 }
109
+ begin
110
+ yield
111
+ recorder.events.freeze
112
+ ensure
113
+ @mutex.synchronize { @active_count -= 1 }
114
+ end
115
+ end
107
116
  end
108
117
 
109
118
  # Plain integer read (GVL-atomic) — the disabled fast path.
@@ -114,20 +123,28 @@ module Rigor
114
123
  # Brackets one expression-typing recursion. Falls through to the bare block when the current thread is not
115
124
  # recording (another thread may have flipped {active?}).
116
125
  def trace_node(node, &)
117
- recorder = Thread.current[KEY]
126
+ recorder = recording
118
127
  return yield unless recorder
119
128
 
120
129
  recorder.node(node, &)
121
130
  end
122
131
 
132
+ # The recorder installed on this thread, touched because the caller is about to record into it, or nil.
133
+ def recording
134
+ state = TypingState.current
135
+ recorder = state[RECORDER]
136
+ state.touch(RECORDER) if recorder
137
+ recorder
138
+ end
139
+
123
140
  # `Scope#with_local` — the moment a local enters the scope.
124
141
  def bind(name, type)
125
- Thread.current[KEY]&.emit(:bind, data: { name: name.to_s, type: describe(type) })
142
+ recording&.emit(:bind, data: { name: name.to_s, type: describe(type) })
126
143
  end
127
144
 
128
145
  # `Type::Combinator.union` — the moment branch types merge (including degenerate collapses like `1 | 1 → 1`).
129
146
  def union(members, result)
130
- Thread.current[KEY]&.emit(
147
+ recording&.emit(
131
148
  :union,
132
149
  data: { members: members.map { |m| describe(m) }.freeze, type: describe(result) }
133
150
  )
@@ -136,7 +153,7 @@ module Rigor
136
153
  # `MethodDispatcher.dispatch` — resolution or the fail-soft `nil` ("no rule matched"; the caller will widen
137
154
  # to `Dynamic[Top]`).
138
155
  def dispatch(receiver:, method_name:, args:, result:, location: nil)
139
- recorder = Thread.current[KEY]
156
+ recorder = recording
140
157
  return unless recorder
141
158
 
142
159
  recorder.emit(
@@ -3,6 +3,7 @@
3
3
  require "tmpdir"
4
4
 
5
5
  require_relative "../runtime/jit"
6
+ require_relative "../type_of_memo"
6
7
 
7
8
  module Rigor
8
9
  module Inference
@@ -95,6 +96,7 @@ module Rigor
95
96
  # what is left of the parent's window rather than a fresh one; {Runtime::Jit.rearm_after_fork}
96
97
  # carries the mechanism.
97
98
  Runtime::Jit.rearm_after_fork
99
+ TypeOfMemo.forked
98
100
  # Published before the block runs, so anything it lazily creates for this process alone lands in the
99
101
  # parent's tmpdir and dies with it. See {.child_scratch_dir}.
100
102
  @child_scratch_dir = File.dirname(out_path)
@@ -105,6 +107,7 @@ module Rigor
105
107
  ensure
106
108
  @child_scratch_dir = nil
107
109
  end
110
+ TypeOfMemo.write_report
108
111
  exit!(status)
109
112
  end
110
113
 
@@ -23,7 +23,8 @@ module Rigor
23
23
  # what it reads. Unlike a root block it shares that slot, so a match in it still rebinds the definer's `$~`.
24
24
  #
25
25
  # `Enumerator.new` is neither: its block runs on an internal fiber whose root is not the block, so it reads and
26
- # rebinds the creator's slot like any other block.
26
+ # rebinds the creator's slot. It reads that slot when the enumerator is read, though, not where the block is
27
+ # written ({.deferred_body?}, issue #3195).
27
28
  #
28
29
  # The receiver of a root call must be the literal constant, and must resolve to the core class where a scope is
29
30
  # at hand, so a project's own `Thread` (`module Pool; class Thread; …`) keeps the ordinary block rules.
@@ -115,6 +116,56 @@ module Rigor
115
116
  scope.forget_error_info.forget_last_status
116
117
  end
117
118
 
119
+ # The core constructors that keep their block and run it on a schedule of their own: an enumerator's generator
120
+ # runs when the enumerator is first read, and a hash's default proc on a missing key.
121
+ DEFERRED_CONSTRUCTORS = Set[:Enumerator, :Hash].freeze
122
+ private_constant :DEFERRED_CONSTRUCTORS
123
+
124
+ # Issue #1371, #3195 — true when `block_node` is a body that runs later than where it is written, on the slot of
125
+ # the frame that wrote it: a proc's ({.proc_body?}), or the block of `Enumerator.new` or `Hash.new` on the core
126
+ # class ({.deferred_constructor?}). Such a body runs whenever the proc is called, the enumerator is read or a
127
+ # key is missing, so the frame-local specials narrowed where it is written prove nothing about the slot it
128
+ # reads then. It enters with `$_` unbound ({LastLine.block_entry}), and with the match globals as
129
+ # {MatchRebinding.block_entry} gives each kind. It still shares the slot, so a match or a reader it runs
130
+ # rebinds the frame's as any block's does. The other calls that keep their block ({StoredBlockCall}) are a
131
+ # definer's or a root's ({.entry}).
132
+ def deferred_body?(block_node, call_node, scope)
133
+ proc_body?(block_node, call_node, scope) || deferred_constructor?(block_node, call_node, scope)
134
+ end
135
+
136
+ # Issue #1371 — true when `block_node` is the body of a proc made where it is written: a lambda literal, or the
137
+ # block of Kernel's `lambda` / `proc` or of `Proc.new` on the core class. It enters with the match globals
138
+ # unbound; a project's own `Proc` keeps the ordinary entry.
139
+ # Only a lambda literal the statement evaluator enters gets this entry: one written as an operand is typed as a
140
+ # value, and the rule walker reads its body in the enclosing scope.
141
+ def proc_body?(block_node, call_node, scope)
142
+ return true if block_node.is_a?(Prism::LambdaNode)
143
+ return false unless call_node.is_a?(Prism::CallNode) && call_node.block.equal?(block_node)
144
+
145
+ receiver = call_node.receiver
146
+ case call_node.name
147
+ when :lambda, :proc then StoredBlockCall.kernel_spelled?(receiver)
148
+ when :new then StoredBlockCall.root_constant_name(receiver) == :Proc && core_class?(receiver, :Proc, scope)
149
+ else false
150
+ end
151
+ end
152
+
153
+ # Issue #3195 — true when `block_node` is the block of `Enumerator.new` or `Hash.new` on the literal constant
154
+ # naming the core class. The block often runs right away, in the statement that makes it
155
+ # (`Enumerator.new { … }.to_a`, `Hash.new { … }[key]`), and reads the narrowing then; it may as well run after a
156
+ # later match. The analyzer does not follow which, so the body enters with a narrowed match global untyped, as
157
+ # a definer body does ({.entry}): neither narrowed nor flagged. A project's own `Enumerator` or `Hash` keeps
158
+ # the ordinary entry, and so does every other spelling of the core class: a subclass (`Class.new(Hash)`), an
159
+ # alias (`E = Enumerator`), `Object::Enumerator`, or a receiver that is not a constant (`klass.new { … }`).
160
+ def deferred_constructor?(block_node, call_node, scope)
161
+ return false unless call_node.is_a?(Prism::CallNode) && call_node.name == :new &&
162
+ call_node.block.equal?(block_node)
163
+
164
+ receiver = call_node.receiver
165
+ constant = StoredBlockCall.root_constant_name(receiver)
166
+ DEFERRED_CONSTRUCTORS.include?(constant) && core_class?(receiver, constant, scope)
167
+ end
168
+
118
169
  def core_class?(receiver, constant, scope)
119
170
  type = scope.type_of(receiver)
120
171
  type.is_a?(Type::Singleton) && type.class_name == constant.name
@@ -5,6 +5,7 @@ require "prism"
5
5
  require_relative "../source/constant_path"
6
6
  require_relative "../source/node_children"
7
7
  require_relative "ivar_writer_census"
8
+ require_relative "stored_block_call"
8
9
 
9
10
  module Rigor
10
11
  module Inference
@@ -28,7 +29,15 @@ module Rigor
28
29
  # - `[:main_mixin, name]` — a module a top-level `include`, `prepend` or `extend` mixes into `Object` (or
29
30
  # `main`), as written. A module RBS does not know may carry any method, so it reaches every class.
30
31
  # - the instance-variable writer entries of {IvarWriterCensus} (issue #1154): `[:ivar_set, name]`,
31
- # `[:ivar_set_class, class_name]` and `[:own_write, class_name, name, path]`.
32
+ # `[:ivar_set_class, class_name]`, `[:own_write, class_name, name, path]`, issue #2079's
33
+ # `[:own_write_type, class_name, name, value, path]` and issue #2983's `[:binds_class, class_key, constant]`.
34
+ # - `[:global_writer, name, method]` — issue #2314: a write of the global `name` inside a method body, keyed by
35
+ # the `def`'s name (or the literal name a `define_method` block defines), or by nil inside a lambda or a block
36
+ # its call keeps to run later ({StoredBlockCall}). A write in a block its call runs at once belongs to the
37
+ # enclosing body. A write at the top level or in a class body records nothing, and neither does a write of
38
+ # the frame-local `$_` or `$~`. {GlobalWriterReach} reads these.
39
+ # - `[:method_alias, new, old]` — issue #2314: `alias new old`, `alias_method :new, :old` or `def_delegator
40
+ # :@o, :old, :new`, whose new name runs a method named old. {GlobalWriterReach} follows it.
32
41
  #
33
42
  # The census is a `Set` of those frozen entries, merged by union across files, the project pre-pass and the
34
43
  # `pre_eval:` files. It is recorded inside descents that already visit every node ({Collector#visit}), never
@@ -78,7 +87,7 @@ module Rigor
78
87
  end
79
88
 
80
89
  # #1154 — the {IvarWriterCensus} entries of `census`, for the incremental diff and its readers.
81
- IVAR_KINDS = %i[ivar_set ivar_set_class own_write].to_set.freeze
90
+ IVAR_KINDS = %i[ivar_set ivar_set_class own_write own_write_type binds_class].to_set.freeze
82
91
 
83
92
  def ivar_set_entries(census) = census.select { |entry| IVAR_KINDS.include?(entry.first) }.to_set
84
93
 
@@ -87,6 +96,8 @@ module Rigor
87
96
  case entry.first
88
97
  when :ivar_set then "ivar_set:#{entry[1]}"
89
98
  when :ivar_set_class then "ivar_set_class:#{entry[1]}"
99
+ when :own_write_type then "own_write_type:#{entry[1]}##{entry[2]}"
100
+ when :binds_class then "binds_class:#{entry[1].split(CensusForeignBlock::CANDIDATE_SEPARATOR).last.split('::').last}"
90
101
  else "own_write:#{entry[1]}##{entry[2]}"
91
102
  end
92
103
  end
@@ -140,14 +151,17 @@ module Rigor
140
151
  @refine_ranges = []
141
152
  @ivar_writers = IvarWriterCensus.new(@census)
142
153
  @in_effect = in_effect
154
+ @bodies = []
143
155
  end
144
156
 
145
157
  # A node the collector cannot read must not fail the analysis of its file, nor of every file the pre-pass
146
158
  # feeds: the census then counts it as a definition of every name, which only declines a report.
147
159
  # `owner` is the qualified name of the class whose body the node sits in, for {IvarWriterCensus} (nil where
148
- # the host walk does not track it, or no class owns an instance there).
149
- def visit(node, top_level:, owner: nil)
150
- @ivar_writers.visit(node, owner)
160
+ # the host walk does not track it, or no class owns an instance there); `receiver_owner` the class an
161
+ # explicit-receiver writer call names (issue #2488).
162
+ def visit(node, top_level:, owner: nil, receiver_owner: nil)
163
+ @ivar_writers.visit(node, owner, receiver_owner)
164
+ visit_global_writer(node)
151
165
  case node
152
166
  when Prism::AliasGlobalVariableNode then visit_global_alias(node)
153
167
  when Prism::DefNode then record(node, node.name)
@@ -160,6 +174,68 @@ module Rigor
160
174
 
161
175
  private
162
176
 
177
+ # Issue #2314 — tracks the method bodies, lambdas and stored blocks of a pre-order descent as flat
178
+ # `start, end, label` triples (`@bodies`, innermost last), and records a global write inside one
179
+ # ({GlobalWriterReach}). A body ending before the write starts is closed. Only those nodes read their location.
180
+ def visit_global_writer(node)
181
+ case node
182
+ when Prism::DefNode then open_body(node, node.name)
183
+ when Prism::LambdaNode then open_body(node, nil)
184
+ when Prism::AliasMethodNode then record_method_alias(node.new_name, node.old_name)
185
+ when Prism::CallNode
186
+ open_stored_block(node)
187
+ visit_alias_call(node)
188
+ when Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
189
+ Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableTargetNode
190
+ record_global_writer(node)
191
+ end
192
+ end
193
+
194
+ # `$_` and `$~` are the frame's own: a method's write to one never reaches its caller.
195
+ FRAME_LOCAL_GLOBALS = %i[$_ $~].freeze
196
+ private_constant :FRAME_LOCAL_GLOBALS
197
+
198
+ def record_global_writer(node)
199
+ return if FRAME_LOCAL_GLOBALS.include?(node.name)
200
+
201
+ start = node.location.start_offset
202
+ bodies = @bodies
203
+ bodies.pop(3) while !bodies.empty? && bodies[-2] <= start
204
+ index = bodies.size - 3
205
+ index -= 3 while index >= 0 && !(bodies[index] <= start && start < bodies[index + 1])
206
+ @census << [:global_writer, node.name, bodies[index + 2]].freeze if index >= 0
207
+ end
208
+
209
+ def open_body(node, label)
210
+ location = node.location
211
+ @bodies.push(location.start_offset, location.end_offset, label)
212
+ end
213
+
214
+ # `alias_method :new, :old`, and `def_delegator :@o, :old, :new` (and its instance and single spellings), whose
215
+ # new name runs a method of the old name.
216
+ def visit_alias_call(node)
217
+ arguments = node.arguments&.arguments
218
+ case node.name
219
+ when :alias_method then record_method_alias(arguments[0], arguments[1]) if arguments&.size == 2
220
+ when :def_delegator, :def_instance_delegator, :def_single_delegator
221
+ record_method_alias(arguments[2], arguments[1]) if arguments&.size == 3
222
+ end
223
+ end
224
+
225
+ def record_method_alias(new_node, old_node)
226
+ new_name = literal_name(new_node)
227
+ old_name = literal_name(old_node)
228
+ @census << [:method_alias, new_name, old_name].freeze if new_name && old_name && new_name != old_name
229
+ end
230
+
231
+ def open_stored_block(node)
232
+ block = node.block
233
+ return unless block.is_a?(Prism::BlockNode) && StoredBlockCall.stores_block?(node)
234
+
235
+ definer = %i[define_method define_singleton_method].include?(node.name)
236
+ open_body(block, definer ? literal_name(node.arguments&.arguments&.first) : nil)
237
+ end
238
+
163
239
  def visit_global_alias(node)
164
240
  [node.new_name, node.old_name].each do |side|
165
241
  @census << GlobalWriteCensus.alias_entry(side.name) if side.is_a?(Prism::GlobalVariableReadNode)
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../analysis/dependency_recorder"
6
+ require_relative "../source/node_children"
7
+ require_relative "../type"
8
+ require_relative "guard_rebinding"
9
+ require_relative "project_method_ownership"
10
+
11
+ module Rigor
12
+ module Inference
13
+ # Issue #2314 — whether running a node may reach a project method that writes a given global. Both places that
14
+ # end a global's binding at a call ask it: the statement walk past a statement call
15
+ # (`StatementEvaluator#forget_reached_globals`), and a `case` whose global subject a `when` test may rebind
16
+ # (`StatementEvaluator#when_test_rebinds_subject?`, issue #2211), so the two read a global alike.
17
+ #
18
+ # The writers come from {GlobalWriteCensus}: a `[:global_writer, name, method]` entry for each write of `name`
19
+ # inside a method body (`method` is the `def`'s name, or the literal name a `define_method` block defines), and
20
+ # `[:global_writer, name, nil]` for one inside a lambda or a block a call keeps to run later
21
+ # ({StoredBlockCall}), which any call may run. A write at the top level or in a class body runs where it is
22
+ # written, so no call reaches it.
23
+ #
24
+ # {.may_write?} answers true when the global has a writer and:
25
+ #
26
+ # - the node may run code other than Ruby core and the standard library ({GuardRebinding.may_rebind?}, the
27
+ # question #2260 asked of a `when` test), and
28
+ # - some writer is anonymous, or the node's own call has a receiver no target resolves (a `Dynamic` value), or a
29
+ # search by method name reaches a writer ({Search}).
30
+ #
31
+ # The search reads method names, not receivers: from the node it follows every call, by name, into every project
32
+ # `def` of that name on any class, transitively, and reaches a writer when it meets a call named after one. A
33
+ # call naming its method by a computed value (`send(name)`, `eval(code)`) reaches. A name no project `def` defines
34
+ # follows `method_missing`, `new` follows `initialize`, and an alias name (`alias`, `alias_method`, a
35
+ # `def_delegator` alias) follows the name it stands for. A nested `def` is not read, since defining a method runs
36
+ # nothing. Past {Search::DEF_BUDGET} bodies the search answers that it reaches. A `define_method` method that calls
37
+ # a writer is not followed (only its own writes count), and a method core code calls back without the program
38
+ # spelling it (`puts obj` runs `obj.to_s`) is read as the core method alone, as {GuardRebinding} reads it.
39
+ module GlobalWriterReach
40
+ EMPTY = Set.new.freeze
41
+
42
+ module_function
43
+
44
+ # The globals `scope` binds that some project method may write, other than one already `Dynamic[top]`. Each
45
+ # bound global's census key is recorded whatever the answer, so a file that starts writing one re-checks.
46
+ def candidates(scope)
47
+ table = writer_table(scope)
48
+ globals = scope.globals
49
+ globals.each_key { |name| record_writers(name) } if Analysis::DependencyRecorder.active?
50
+ return EMPTY if table.empty? || globals.empty?
51
+
52
+ table.each_key.select do |name|
53
+ type = globals[name]
54
+ !type.nil? && !type.is_a?(Type::Dynamic)
55
+ end
56
+ end
57
+
58
+ # True when running `node`, typed in `scope`, may reach a project method that writes `name` (see the module
59
+ # comment).
60
+ def may_write?(node, name, scope)
61
+ writers = writers(scope, name)
62
+ return false if writers.empty?
63
+ return false unless GuardRebinding.may_rebind?(node, scope)
64
+ return true if writers.include?(nil) || unresolved_call?(node, scope)
65
+
66
+ Search.new(scope, writers).reaches?(node)
67
+ end
68
+
69
+ # The method names that write `name` (nil for an anonymous writer). Records the census key, so a file that
70
+ # starts or stops writing the global re-checks the reader. A guard on the `case` site: every `when` test built so
71
+ # far also records the key through a statement call's {.candidates} or a callee's method edge.
72
+ def writers(scope, name)
73
+ record_writers(name) if Analysis::DependencyRecorder.active?
74
+ writer_table(scope)[name] || EMPTY
75
+ end
76
+
77
+ def record_writers(name)
78
+ Analysis::DependencyRecorder.read_name(:global_census, "global_writer:#{name}")
79
+ end
80
+
81
+ # `{ global => Set[method name or nil] }` over the project's census and the `pre_eval:` files' own.
82
+ def writer_table(scope) = census_tables(scope).first
83
+
84
+ # `{ alias name => Set[the method names it stands for] }`: an `alias`, an `alias_method` or a `def_delegator`
85
+ # with a literal alias name, from the same census.
86
+ def alias_table(scope) = census_tables(scope).last
87
+
88
+ # The two tables above, kept for the census objects they were built from.
89
+ def census_tables(scope)
90
+ census = scope.discovered_global_write_census
91
+ pre_eval = scope.environment&.project_patched_methods&.write_census
92
+ cached = @census_tables
93
+ return cached[2] if cached && cached[0].equal?(census) && cached[1].equal?(pre_eval)
94
+
95
+ tables = build_tables([census, pre_eval])
96
+ @census_tables = [census, pre_eval, tables.freeze]
97
+ tables
98
+ end
99
+
100
+ TABLE_KINDS = { global_writer: 0, method_alias: 1 }.freeze
101
+ private_constant :TABLE_KINDS
102
+
103
+ def build_tables(censuses)
104
+ tables = [{}, {}]
105
+ censuses.each do |entries|
106
+ entries&.each do |kind, key, value|
107
+ index = TABLE_KINDS[kind]
108
+ (tables[index][key] ||= Set.new) << value if index
109
+ end
110
+ end
111
+ tables.each { |table| table.each_value(&:freeze).freeze }
112
+ end
113
+
114
+ # True when `node` is a call whose receiver no target resolves: the method it runs may be any.
115
+ def unresolved_call?(node, scope)
116
+ return false unless node.is_a?(Prism::CallNode)
117
+
118
+ receiver = node.receiver
119
+ return false if receiver.nil? || receiver.is_a?(Prism::SelfNode)
120
+
121
+ targets = ProjectMethodOwnership.targets(GuardRebinding::ScanScope.dispatch_type(receiver, scope))
122
+ targets.nil? || targets.empty?
123
+ rescue StandardError
124
+ true
125
+ end
126
+
127
+ # `{ method name => [[class name, kind], …] }` over the project's `def` tables, kept for the tables it was
128
+ # built from.
129
+ def def_index(scope)
130
+ instance = scope.discovered_def_nodes
131
+ singleton = scope.discovered_singleton_def_nodes
132
+ cached = @def_index
133
+ return cached[2] if cached && cached[0].equal?(instance) && cached[1].equal?(singleton)
134
+
135
+ index = {}
136
+ { instance: instance, singleton: singleton }.each do |kind, table|
137
+ table.each do |class_name, methods|
138
+ methods.each_key { |method_name| (index[method_name] ||= []) << [class_name, kind] }
139
+ end
140
+ end
141
+ @def_index = [instance, singleton, index.freeze]
142
+ index
143
+ end
144
+
145
+ # A breadth-first search by method name from a node to a writer's name.
146
+ class Search
147
+ # The most `def` bodies one search reads before it answers that it reaches.
148
+ DEF_BUDGET = 64
149
+ SEND_CALLS = %i[send __send__ public_send method public_method instance_method].to_set.freeze
150
+ CODE_CALLS = %i[eval instance_eval instance_exec class_eval class_exec module_eval module_exec].to_set.freeze
151
+
152
+ class Reached < StandardError
153
+ end
154
+
155
+ def initialize(scope, writers)
156
+ @scope = scope
157
+ @writers = writers
158
+ @aliases = GlobalWriterReach.alias_table(scope)
159
+ @recording = Analysis::DependencyRecorder.active?
160
+ @seen = Set.new
161
+ @queue = []
162
+ @read = 0
163
+ end
164
+
165
+ def reaches?(node)
166
+ scan(node)
167
+ until @queue.empty?
168
+ name = @queue.shift
169
+ definitions = definitions_of(name)
170
+ enqueue(:method_missing) if definitions.empty?
171
+ definitions.each { |definition| read_def(definition) }
172
+ end
173
+ false
174
+ rescue Reached
175
+ true
176
+ end
177
+
178
+ private
179
+
180
+ def read_def(definition)
181
+ @read += 1
182
+ reached! if @read > DEF_BUDGET
183
+ scan(definition.parameters)
184
+ scan(definition.body)
185
+ end
186
+
187
+ def definitions_of(name)
188
+ Analysis::DependencyRecorder.read_name(:defines, name) if Analysis::DependencyRecorder.active?
189
+ targets = GlobalWriterReach.def_index(@scope)[name] || []
190
+ targets.filter_map do |class_name, kind|
191
+ kind == :singleton ? @scope.singleton_def_for(class_name, name) : @scope.user_def_for(class_name, name)
192
+ end
193
+ end
194
+
195
+ def scan(node)
196
+ return unless node.is_a?(Prism::Node)
197
+ # A nested `def` defines a method; it runs nothing here, and its name is followed where it is called.
198
+ return if node.is_a?(Prism::DefNode)
199
+
200
+ case node
201
+ when Prism::CallNode then scan_call(node)
202
+ when Prism::BlockArgumentNode then scan_block_argument(node)
203
+ when Prism::ForNode then enqueue(:each)
204
+ end
205
+ implicit_names(node).each { |name| enqueue(name) }
206
+ node.rigor_each_child { |child| scan(child) }
207
+ end
208
+
209
+ def scan_call(node)
210
+ enqueue(node.name)
211
+ # `new` runs the class's `initialize`.
212
+ enqueue(:initialize) if node.name == :new
213
+ arguments = node.arguments&.arguments || []
214
+ if SEND_CALLS.include?(node.name)
215
+ name = literal_name(arguments.first)
216
+ name ? enqueue(name) : reached!
217
+ elsif CODE_CALLS.include?(node.name) && !(arguments.empty? && node.block.is_a?(Prism::BlockNode))
218
+ reached!
219
+ end
220
+ end
221
+
222
+ def scan_block_argument(node)
223
+ name = literal_name(node.expression)
224
+ enqueue(name) if name
225
+ end
226
+
227
+ # The methods an operator or compound write calls without spelling them.
228
+ def implicit_names(node)
229
+ names = []
230
+ names << node.binary_operator if node.respond_to?(:binary_operator)
231
+ names.push(node.read_name, node.write_name) if node.respond_to?(:read_name) && node.respond_to?(:write_name)
232
+ names
233
+ end
234
+
235
+ def enqueue(name)
236
+ return if name.nil? || @seen.include?(name)
237
+
238
+ reached! if @writers.include?(name)
239
+ @seen << name
240
+ @queue << name
241
+ # An alias runs the method it names.
242
+ Analysis::DependencyRecorder.read_name(:global_census, "method_alias:#{name}") if @recording
243
+ @aliases[name]&.each { |original| enqueue(original) }
244
+ end
245
+
246
+ def literal_name(node)
247
+ return unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
248
+
249
+ text = node.unescaped
250
+ text.to_sym if text.valid_encoding?
251
+ end
252
+
253
+ def reached!
254
+ raise Reached
255
+ end
256
+ end
257
+ private_constant :Search
258
+ end
259
+ end
260
+ end
@@ -41,8 +41,7 @@ module Rigor
41
41
 
42
42
  # Reference to a formal parameter the enclosing `Definition#params` declared. The reducer
43
43
  # substitutes this node with the matching positional arg from the `App` being reduced; an
44
- # unknown name raises during reduction (the parser, when it ships, MUST reject unknown names
45
- # earlier).
44
+ # unknown name raises during reduction.
46
45
  Param = Data.define(:name) do
47
46
  def initialize(name:)
48
47
  raise ArgumentError, "name must be a Symbol, got #{name.class}" unless name.is_a?(Symbol)
@@ -43,7 +43,7 @@ module Rigor
43
43
  # `Rigor::Type::*` carrier: `nil` / `true` / `false` produce `Constant` carriers; `bool`
44
44
  # produces the `Constant<true> | Constant<false>` union; `untyped` produces
45
45
  # `Combinator.untyped` (i.e. `Dynamic[Top]`). Nominal class names produce raw `Type::Nominal`
46
- # carriers (no `name_scope` resolution at this slice — the reducer trusts the name verbatim).
46
+ # carriers (no `name_scope` resolution; the reducer trusts the name verbatim).
47
47
  module HktBodyParser
48
48
  class ParseError < StandardError; end
49
49
 
@@ -151,10 +151,8 @@ module Rigor
151
151
  # test := type_expr ("<:" | "==") type_expr
152
152
  #
153
153
  # Parens delimit a conditional unambiguously — bare `(type_expr)` grouping is not
154
- # supported at this slice (no use case yet). Branches can be unions; test sides are
155
- # single arms (wrap in `App[my_union, ...]` if you need a union there). `in [opt1, opt2]`
156
- # membership tests are programmatically supported via `HktBody::TestMembership` but the
157
- # parser does not yet recognise the `in` keyword (no concrete demand yet).
154
+ # supported (no use case yet). Branches can be unions; test sides are single arms (wrap
155
+ # in `App[my_union, ...]` if you need a union there).
158
156
  def parse_conditional
159
157
  expect!(:lparen)
160
158
  test = parse_test
@@ -152,8 +152,8 @@ module Rigor
152
152
  def subtype_verdict(left, right)
153
153
  return :yes if left == right
154
154
 
155
- # Both Nominals with different class names AND neither carries Dynamic — `:no`
156
- # (statically disjoint). For any other shape pair, `:maybe`.
155
+ # Two Nominals of different class names are statically disjoint (`:no`); any other shape
156
+ # pair is `:maybe`.
157
157
  return :no if disjoint_nominals?(left, right)
158
158
  return :no if disjoint_constants?(left, right)
159
159
 
@@ -9,11 +9,9 @@ module Rigor
9
9
  # annotations in shipped `.rbs` files.
10
10
  #
11
11
  # The registry stores registration metadata (arity, variance, bound) and the definition body
12
- # as both a raw String and an evaluable `HktBody` node tree. `HktReducer` (Slice 2a) and
13
- # `HktBodyParser` (Slice 2b) are both shipped and reduce `Type::App` instances against the
14
- # definition. The carrier never needs to read from the registry because Slice 1's `Type::App`
15
- # carries its `bound` directly; the registry exists at this slice solely so the parser
16
- # round-trip and downstream slices have a stable target API.
12
+ # as both a raw String and an evaluable `HktBody` node tree. `HktReducer` and `HktBodyParser`
13
+ # reduce `Type::App` instances against the definition. The carrier never needs to read from the
14
+ # registry because `Type::App` carries its `bound` directly.
17
15
  #
18
16
  # The registry is immutable after construction. Callers that need to extend it (e.g. plugin
19
17
  # registrations layered on top of stdlib registrations) MUST build a new registry via `merge`