rigortype 0.2.7 → 0.2.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 (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -34,21 +34,18 @@ module Rigor
34
34
  module Inference
35
35
  # Coordinates method dispatch for the inference engine.
36
36
  #
37
- # Given `(receiver_type, method_name, arg_types, block_type, environment)`,
38
- # the dispatcher returns the inferred result type or `nil` when no
39
- # rule matches. `nil` is a deliberately blunt "I don't know" signal:
40
- # callers (today only `ExpressionTyper`) own the fail-soft fallback
41
- # and decide whether to record a `FallbackTracer` event.
37
+ # Given `(receiver_type, method_name, arg_types, block_type, environment)`, the dispatcher
38
+ # returns the inferred result type or `nil` when no rule matches. `nil` is a deliberately
39
+ # blunt "I don't know" signal: callers (today only `ExpressionTyper`) own the fail-soft
40
+ # fallback and decide whether to record a `FallbackTracer` event.
42
41
  #
43
- # Tier order is documented inline in `resolve`; the precise-tier
44
- # group is built from `PRECISE_TIERS_HEAD`, `STDLIB_SINGLETON_FOLDERS`,
45
- # and `PRECISE_TIERS_TAIL`. `ShapeDispatch` runs above {RbsDispatch}
46
- # so a precise per-position/per-key answer wins over the projected
42
+ # Tier order is documented inline in `resolve`; the precise-tier group is built from
43
+ # `PRECISE_TIERS_HEAD`, `STDLIB_SINGLETON_FOLDERS`, and `PRECISE_TIERS_TAIL`. `ShapeDispatch`
44
+ # runs above {RbsDispatch} so a precise per-position/per-key answer wins over the projected
47
45
  # `Array#[]`/`Hash#fetch` RBS answer.
48
46
  #
49
- # The `block_type:` and plugin contribution (`dynamic_return`) tiers
50
- # landed in Slice 6 phase C and v0.1.1 Track 2 respectively; all
51
- # call sites pass through `dispatch`/`resolve` unchanged.
47
+ # The `block_type:` and plugin contribution (`dynamic_return`) tiers landed in Slice 6 phase C
48
+ # and v0.1.1 Track 2 respectively; all call sites pass through `dispatch`/`resolve` unchanged.
52
49
  module MethodDispatcher # rubocop:disable Metrics/ModuleLength
53
50
  module_function
54
51
 
@@ -73,8 +70,8 @@ module Rigor
73
70
  block_type: block_type, environment: environment,
74
71
  call_node: call_node, scope: scope
75
72
  )
76
- # `rigor trace` — record the dispatch outcome (resolved type, or
77
- # the fail-soft `nil` the caller widens to `Dynamic[Top]`).
73
+ # `rigor trace` — record the dispatch outcome (resolved type, or the fail-soft `nil` the
74
+ # caller widens to `Dynamic[Top]`).
78
75
  if FlowTracer.active?
79
76
  FlowTracer.dispatch(
80
77
  receiver: receiver_type, method_name: method_name, args: arg_types,
@@ -89,11 +86,9 @@ module Rigor
89
86
  call_node: nil, scope: nil)
90
87
  return nil if receiver_type.nil?
91
88
 
92
- # Build the call context once and thread it — unchanged —
93
- # through every tier (`_DispatchTier#try_dispatch`). The
94
- # dispatcher's own private fallback tiers still read the
95
- # positional locals below; only the tier modules consume the
96
- # context object.
89
+ # Build the call context once and thread it — unchanged — through every tier
90
+ # (`_DispatchTier#try_dispatch`). The dispatcher's own private fallback tiers still read
91
+ # the positional locals below; only the tier modules consume the context object.
97
92
  context = CallContext.build(
98
93
  receiver: receiver_type, method_name: method_name, args: arg_types,
99
94
  block_type: block_type, environment: environment,
@@ -106,14 +101,12 @@ module Rigor
106
101
  precise = dispatch_precise_tiers(context)
107
102
  return precise if precise
108
103
 
109
- # v0.1.1 Track 2 slice 7 — plugin return-type contribution
110
- # tier. Sits ahead of `RbsDispatch` so a plugin that
111
- # understands a domain-specific dispatch (e.g. an
112
- # `ActiveRecord::Base.find` returning `Nominal[<resolved
113
- # model>]`) wins over the RBS-projected envelope. Only
114
- # consults the registry when both `call_node` and `scope`
115
- # are supplied — the dispatcher's own internal callers
116
- # (per-element block fold, etc.) skip this tier.
104
+ # v0.1.1 Track 2 slice 7 — plugin return-type contribution tier. Sits ahead of
105
+ # `RbsDispatch` so a plugin that understands a domain-specific dispatch (e.g. an
106
+ # `ActiveRecord::Base.find` returning `Nominal[<resolved model>]`) wins over the
107
+ # RBS-projected envelope. Only consults the registry when both `call_node` and `scope`
108
+ # are supplied the dispatcher's own internal callers (per-element block fold, etc.)
109
+ # skip this tier.
117
110
  plugin_result = try_plugin_contribution(call_node, scope, receiver_type)
118
111
  if plugin_result
119
112
  if plugin_result.is_a?(Type::Dynamic)
@@ -122,29 +115,22 @@ module Rigor
122
115
  return plugin_result
123
116
  end
124
117
 
125
- # ADR-20 slice 3 — Rigor-bundled HKT-builtin return-
126
- # type tier. Sits ABOVE `RbsDispatch.try_dispatch` so
127
- # the handful of stdlib methods whose upstream RBS
128
- # signature is `untyped` but whose runtime shape Rigor
129
- # models via a Lightweight HKT (`json::value`,
130
- # eventually `dry_monads::result`, …) get the reduced
131
- # type instead of `Dynamic[Top]`. The table that
132
- # populates this tier lives in
133
- # `Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES`;
134
- # plugin-supplied per-method overrides are out of
135
- # scope for slice 3 and continue to flow through the
118
+ # ADR-20 slice 3 — Rigor-bundled HKT-builtin return-type tier. Sits ABOVE
119
+ # `RbsDispatch.try_dispatch` so the handful of stdlib methods whose upstream RBS
120
+ # signature is `untyped` but whose runtime shape Rigor models via a Lightweight HKT
121
+ # (`json::value`, eventually `dry_monads::result`, …) get the reduced type instead of
122
+ # `Dynamic[Top]`. The table that populates this tier lives in
123
+ # `Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES`; plugin-supplied per-method
124
+ # overrides are out of scope for slice 3 and continue to flow through the
136
125
  # `try_plugin_contribution` tier above.
137
126
  hkt_builtin_result = try_hkt_builtin_return(receiver_type, method_name, arg_types, environment)
138
127
  return hkt_builtin_result if hkt_builtin_result
139
128
 
140
- # Rigor-bundled static refinement tier. Sits between HKT
141
- # and RBS so stdlib methods whose upstream RBS is broader
142
- # than the documented behaviour (e.g. `Kernel#__dir__`
143
- # declared `() -> String?` when the documented return is
144
- # `non-empty-string | nil`) get the tightened type
145
- # without modifying the vendored `ruby/rbs` submodule.
146
- # The override table lives in
147
- # `Rigor::Builtins::StaticReturnRefinements::OVERRIDES`.
129
+ # Rigor-bundled static refinement tier. Sits between HKT and RBS so stdlib methods whose
130
+ # upstream RBS is broader than the documented behaviour (e.g. `Kernel#__dir__` declared
131
+ # `() -> String?` when the documented return is `non-empty-string | nil`) get the
132
+ # tightened type without modifying the vendored `ruby/rbs` submodule. The override table
133
+ # lives in `Rigor::Builtins::StaticReturnRefinements::OVERRIDES`.
148
134
  static_refinement = try_static_refinement(receiver_type, method_name, arg_types)
149
135
  return static_refinement if static_refinement
150
136
 
@@ -157,121 +143,106 @@ module Rigor
157
143
  return rbs_result
158
144
  end
159
145
 
160
- # ADR-16 Tier B / Tier C — synthetic-method tier. Sits
161
- # BELOW RBS dispatch (per WD13: user-authored RBS overrides
162
- # substrate synthesis) and ABOVE the dependency-source
163
- # inference tier so a plugin's declared emit table beats
164
- # the generic gem-source fallback for the same class. Slice
165
- # 6a-TierB (origin_module dispatch) lands precise return
166
- # types for Tier B emissions; Tier C emissions still return
167
- # `Dynamic[T]` at this tier (slice 6b is the Tier C
168
- # promotion via ADR-13's resolver chain).
146
+ # ADR-16 Tier B / Tier C — synthetic-method tier. Sits BELOW RBS dispatch (per WD13:
147
+ # user-authored RBS overrides substrate synthesis) and ABOVE the dependency-source
148
+ # inference tier so a plugin's declared emit table beats the generic gem-source fallback
149
+ # for the same class. Slice 6a-TierB (origin_module dispatch) lands precise return types
150
+ # for Tier B emissions; Tier C emissions still return `Dynamic[T]` at this tier (slice 6b
151
+ # is the Tier C promotion via ADR-13's resolver chain).
169
152
  synthetic_result = try_synthetic_method(
170
153
  receiver_type, method_name, arg_types, block_type, environment
171
154
  )
172
155
  return synthetic_result if synthetic_result
173
156
 
174
- # ADR-17 slice 2 — project-side patched-method tier.
175
- # Sits BELOW the substrate / plugin tiers and ABOVE
176
- # dependency-source inference per ADR-17 § "Inference
177
- # contract". When the user's `pre_eval:` list named a
178
- # file that re-opens a class (e.g.,
179
- # `lib/core_ext/string_extensions.rb` declaring
180
- # `class String; def to_url; end; end`), the pre-pass
181
- # populated `ProjectPatchedMethods` with the `(class,
182
- # method, kind)` triple; this tier surfaces it as
183
- # `Dynamic[top]` so the patched call resolves
184
- # cross-file without `call.undefined-method`.
157
+ # ADR-17 slice 2 — project-side patched-method tier. Sits BELOW the substrate / plugin
158
+ # tiers and ABOVE dependency-source inference per ADR-17 § "Inference contract". When
159
+ # the user's `pre_eval:` list named a file that re-opens a class (e.g.,
160
+ # `lib/core_ext/string_extensions.rb` declaring `class String; def to_url; end; end`),
161
+ # the pre-pass populated `ProjectPatchedMethods` with the `(class, method, kind)` triple;
162
+ # this tier surfaces it as `Dynamic[top]` so the patched call resolves cross-file without
163
+ # `call.undefined-method`.
185
164
  patched_result = try_project_patched_method(receiver_type, method_name, environment)
186
165
  if patched_result
187
166
  scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
188
167
  return patched_result
189
168
  end
190
169
 
191
- # ADR-10 slice 2b-ii — dependency-source inference tier.
192
- # Sits BELOW RBS dispatch (RBS / RBS::Inline / generated
193
- # stubs / plugin contracts always win) and ABOVE the
194
- # user-class fallback so a method defined in an opt-in
195
- # gem stops emitting `call.undefined-method` even when
196
- # no signature contract resolves. Returns
197
- # `Dynamic[top]` — slice 2b-ii deliberately stops at the
198
- # dynamic-origin envelope; per-method return-type
199
- # precision is queued for a later slice.
170
+ # ADR-10 slice 2b-ii — dependency-source inference tier. Sits BELOW RBS dispatch (RBS /
171
+ # RBS::Inline / generated stubs / plugin contracts always win) and ABOVE the user-class
172
+ # fallback so a method defined in an opt-in gem stops emitting `call.undefined-method`
173
+ # even when no signature contract resolves. Returns `Dynamic[top]` — slice 2b-ii
174
+ # deliberately stops at the dynamic-origin envelope; per-method return-type precision is
175
+ # queued for a later slice.
200
176
  dep_source_result = try_dependency_source(receiver_type, method_name, environment)
201
177
  if dep_source_result
202
178
  scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
203
179
  return dep_source_result
204
180
  end
205
181
 
206
- # v0.1.3 — discovered-method dispatch tier. When the
207
- # receiver class has no RBS BUT scope_indexer recorded
208
- # `def method_name` for that class (or singleton), the
209
- # call dispatches to `Dynamic[top]` rather than falling
210
- # through to the user-class fallback. Sits below RBS /
211
- # dependency-source so authoritative signatures still win.
212
- # The scope-indexer-built table records every project-side
213
- # `def`, `define_method`, and `alias_method`; the
214
- # `discovered_method?` consult here closes the
215
- # fail-soft-event hot spot on implicit-self calls
216
- # (`sibling_private(...)`) inside `lib/rigor/`'s own
182
+ # v0.1.3 — discovered-method dispatch tier. When the receiver class has no RBS BUT
183
+ # scope_indexer recorded `def method_name` for that class (or singleton), the call
184
+ # dispatches to `Dynamic[top]` rather than falling through to the user-class fallback.
185
+ # Sits below RBS / dependency-source so authoritative signatures still win. The
186
+ # scope-indexer-built table records every project-side `def`, `define_method`, and
187
+ # `alias_method`; the `discovered_method?` consult here closes the fail-soft-event hot
188
+ # spot on implicit-self calls (`sibling_private(...)`) inside `lib/rigor/`'s own
217
189
  # internals (analyser private helpers don't have RBS).
218
190
  discovered_result = try_discovered_method(receiver_type, method_name, scope)
219
- return discovered_result if discovered_result
220
-
221
- # ADR-5 robustness synthesized-stub-type tier. When the
222
- # receiver is a type Rigor invented to make an otherwise-
223
- # unbuildable project signature resolve (a missing-namespace
224
- # module, or a stub for a referenced-but-undeclared type like
225
- # an unavailable `DRb::DRbServer`), the stub carries no methods,
226
- # so an unresolved call against it would otherwise mis-fire
227
- # `call.undefined-method`. Resolve it to `Dynamic[Top]` instead
228
- # — the same no-false-positive contract as the dependency-
229
- # source tier. Sits below every real resolution tier so a
230
- # genuine signature always wins.
191
+ if discovered_result
192
+ # ADR-82 WD2/WD3 — the call resolved to a discovered user method but its return could not be
193
+ # inferred (bare `def`, untyped-parameter chain). Record it as an inference gap, not the
194
+ # generic `unsupported_syntax`, so `coverage --protection` routes it to ADR-58/67.
195
+ if discovered_result.is_a?(Type::Dynamic)
196
+ scope&.record_dynamic_origin(call_node, DynamicOrigin::INFERRED_RETURN_UNTYPED)
197
+ end
198
+ return discovered_result
199
+ end
200
+
201
+ # ADR-5 robustness — synthesized-stub-type tier. When the receiver is a type Rigor
202
+ # invented to make an otherwise-unbuildable project signature resolve (a
203
+ # missing-namespace module, or a stub for a referenced-but-undeclared type like an
204
+ # unavailable `DRb::DRbServer`), the stub carries no methods, so an unresolved call
205
+ # against it would otherwise mis-fire `call.undefined-method`. Resolve it to
206
+ # `Dynamic[Top]` instead — the same no-false-positive contract as the dependency-source
207
+ # tier. Sits below every real resolution tier so a genuine signature always wins.
231
208
  stub_result = try_synthesized_stub_type(receiver_type, environment)
232
209
  return stub_result if stub_result
233
210
 
234
- # Slice 7 phase 10 — user-class ancestor fallback. When
235
- # the receiver is `Nominal[T]` or `Singleton[T]` for a
236
- # class not in the RBS environment (typically a
237
- # user-defined class), retry the dispatch against the
238
- # implicit ancestor: `Nominal[Object]` for instance
239
- # receivers and `Singleton[Object]` for singleton
240
- # receivers. This resolves Kernel intrinsics
241
- # (`require`, `raise`, `puts`, ...) and Module/Class
242
- # introspection (`attr_reader`, `private`, ...) on
243
- # user classes without requiring the user to author
211
+ # Slice 7 phase 10 — user-class ancestor fallback. When the receiver is `Nominal[T]` or
212
+ # `Singleton[T]` for a class not in the RBS environment (typically a user-defined class),
213
+ # retry the dispatch against the implicit ancestor: `Nominal[Object]` for instance
214
+ # receivers and `Singleton[Object]` for singleton receivers. This resolves Kernel
215
+ # intrinsics (`require`, `raise`, `puts`, ...) and Module/Class introspection
216
+ # (`attr_reader`, `private`, ...) on user classes without requiring the user to author
244
217
  # their own RBS.
245
- try_user_class_fallback(receiver_type, environment, call_node, context)
218
+ fallback_result = try_user_class_fallback(receiver_type, environment, call_node, context)
219
+ # ADR-82 WD2/WD3 — a user-class receiver whose call resolved to the lenient ancestor fallback
220
+ # with no inferable return. Same inference-gap provenance as the discovered-method tier, so a
221
+ # downstream dispatch on this value is labeled honestly rather than `unsupported_syntax`.
222
+ if fallback_result.is_a?(Type::Dynamic)
223
+ scope&.record_dynamic_origin(call_node, DynamicOrigin::INFERRED_RETURN_UNTYPED)
224
+ end
225
+ fallback_result
246
226
  end
247
227
 
248
- # v0.1.3 — discovered-method dispatch tier. `scope` carries
249
- # the `discovered_methods` table built once per program by
250
- # `ScopeIndexer` (a `Hash[String, Hash[Symbol, :instance |
251
- # :singleton]]`). When the receiver names a discovered
252
- # class AND the requested method is recorded for that
253
- # class's appropriate kind, return `Type::Combinator.untyped`
254
- # — the dispatcher cannot infer a more precise return type
255
- # from the bare `def` shape, but the call site stops being a
256
- # fail-soft hot spot.
228
+ # v0.1.3 — discovered-method dispatch tier. `scope` carries the `discovered_methods` table
229
+ # built once per program by `ScopeIndexer` (a `Hash[String, Hash[Symbol, :instance |
230
+ # :singleton]]`). When the receiver names a discovered class AND the requested method is
231
+ # recorded for that class's appropriate kind, return `Type::Combinator.untyped` — the
232
+ # dispatcher cannot infer a more precise return type from the bare `def` shape, but the
233
+ # call site stops being a fail-soft hot spot.
257
234
  #
258
- # Returns `nil` when scope / receiver class is unavailable,
259
- # when the method is not in the discovered table, OR when
260
- # `discovered_def_nodes` carries a re-typable body for the
261
- # method (so the downstream
262
- # `ExpressionTyper#try_user_method_inference` tier can
263
- # re-type the body for a precise return type rather than
264
- # collapsing to `Dynamic[top]` here).
235
+ # Returns `nil` when scope / receiver class is unavailable, when the method is not in the
236
+ # discovered table, OR when `discovered_def_nodes` carries a re-typable body for the method
237
+ # (so the downstream `ExpressionTyper#try_user_method_inference` tier can re-type the body
238
+ # for a precise return type rather than collapsing to `Dynamic[top]` here).
265
239
  #
266
- # The tier does NOT gate on `rbs_class_known?`. RBS dispatch
267
- # already had its turn upstream and returned `nil` (otherwise
268
- # we wouldn't be here). When RBS knows the class but the
269
- # particular method is missing from the sigcommon for
270
- # internal helpers and for auto-generated stubs that emit
271
- # `class X` without enumerating every method falling
272
- # through to the user-class fallback would mistakenly fire
273
- # `call.undefined-method`. Honoring the discovered table
274
- # here keeps the sibling-private call resolution working
240
+ # The tier does NOT gate on `rbs_class_known?`. RBS dispatch already had its turn upstream
241
+ # and returned `nil` (otherwise we wouldn't be here). When RBS knows the class but the
242
+ # particular method is missing from the sig common for internal helpers and for
243
+ # auto-generated stubs that emit `class X` without enumerating every method falling
244
+ # through to the user-class fallback would mistakenly fire `call.undefined-method`.
245
+ # Honoring the discovered table here keeps the sibling-private call resolution working
275
246
  # under partial RBS coverage.
276
247
  def try_discovered_method(receiver_type, method_name, scope)
277
248
  return nil if scope.nil?
@@ -279,23 +250,20 @@ module Rigor
279
250
  class_name, kind = discovered_method_lookup(receiver_type)
280
251
  return nil if class_name.nil?
281
252
  return nil unless scope.discovered_method?(class_name, method_name, kind)
282
- # Decline when a re-typable body is recorded for the method, so the
283
- # downstream `ExpressionTyper` inference tier can fold a precise
284
- # return instead of collapsing to `Dynamic[top]` here — instance
285
- # bodies via `user_def_for`, singleton bodies (`def self.x` /
286
- # `module_function`) via `singleton_def_for` (module-singleton
287
- # call resolution, ADR-57 follow-up).
253
+ # Decline when a re-typable body is recorded for the method, so the downstream
254
+ # `ExpressionTyper` inference tier can fold a precise return instead of collapsing to
255
+ # `Dynamic[top]` here — instance bodies via `user_def_for`, singleton bodies (`def
256
+ # self.x` / `module_function`) via `singleton_def_for` (module-singleton call
257
+ # resolution, ADR-57 follow-up).
288
258
  return nil if kind == :instance && scope.user_def_for(class_name, method_name)
289
259
  return nil if kind == :singleton && scope.singleton_def_for(class_name, method_name)
290
260
 
291
261
  Type::Combinator.untyped
292
262
  end
293
263
 
294
- # Resolves the `(class_name, kind)` pair scope_indexer keys
295
- # its `discovered_methods` table on. `Nominal[X]` looks up
296
- # instance methods on X; `Singleton[X]` looks up singleton
297
- # methods on X. Other carriers return `[nil, nil]` so the
298
- # tier declines.
264
+ # Resolves the `(class_name, kind)` pair scope_indexer keys its `discovered_methods` table
265
+ # on. `Nominal[X]` looks up instance methods on X; `Singleton[X]` looks up singleton
266
+ # methods on X. Other carriers return `[nil, nil]` so the tier declines.
299
267
  def discovered_method_lookup(receiver_type)
300
268
  case receiver_type
301
269
  when Type::Nominal then [receiver_type.class_name, :instance]
@@ -304,13 +272,11 @@ module Rigor
304
272
  end
305
273
  end
306
274
 
307
- # ADR-5 robustness — returns `Dynamic[Top]` when the receiver is
308
- # an instance or singleton of a type Rigor synthesized (a
309
- # missing-namespace module or a referenced-type stub). The stub
310
- # has no methods, so the call would otherwise reach the
311
- # user-class fallback and surface `call.undefined-method`; the
312
- # honest answer for a type Rigor invented is "unknown shape",
313
- # i.e. `Dynamic[Top]`. Returns nil (declines) for any real type.
275
+ # ADR-5 robustness — returns `Dynamic[Top]` when the receiver is an instance or singleton
276
+ # of a type Rigor synthesized (a missing-namespace module or a referenced-type stub). The
277
+ # stub has no methods, so the call would otherwise reach the user-class fallback and
278
+ # surface `call.undefined-method`; the honest answer for a type Rigor invented is "unknown
279
+ # shape", i.e. `Dynamic[Top]`. Returns nil (declines) for any real type.
314
280
  def try_synthesized_stub_type(receiver_type, environment)
315
281
  return nil if environment.nil?
316
282
 
@@ -329,13 +295,11 @@ module Rigor
329
295
  Type::Combinator.untyped
330
296
  end
331
297
 
332
- # ADR-20 slice 3 — looks up the receiver / method pair
333
- # in {Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES}
334
- # and returns the reduced HKT type. Only fires when the
335
- # receiver is a {Rigor::Type::Singleton} (the
336
- # `JSON.parse` shape) and the registry-backed reduction
337
- # succeeds; returns `nil` otherwise so the dispatcher
338
- # falls through to RBS.
298
+ # ADR-20 slice 3 — looks up the receiver / method pair in
299
+ # {Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES} and returns the reduced HKT type.
300
+ # Only fires when the receiver is a {Rigor::Type::Singleton} (the `JSON.parse` shape) and
301
+ # the registry-backed reduction succeeds; returns `nil` otherwise so the dispatcher falls
302
+ # through to RBS.
339
303
  def try_hkt_builtin_return(receiver_type, method_name, arg_types, environment)
340
304
  return nil if environment.nil?
341
305
  return nil unless receiver_type.is_a?(Type::Singleton)
@@ -349,23 +313,17 @@ module Rigor
349
313
  )
350
314
  end
351
315
 
352
- # Consults the Rigor-bundled static refinement table for a
353
- # (owner-class, method-name, kind) entry. Kernel methods
354
- # are mixed into every non-BasicObject class, so an
355
- # implicit-self `__dir__` call (receiver_type =
356
- # Nominal[ClassName]) is matched by looking up Kernel as
357
- # the owner. Explicit `Kernel.__dir__` (receiver_type =
358
- # Singleton[Kernel]) and instance-side calls
359
- # (receiver_type = Nominal[Klass]) share the `:both` row.
316
+ # Consults the Rigor-bundled static refinement table for a (owner-class, method-name,
317
+ # kind) entry. Kernel methods are mixed into every non-BasicObject class, so an
318
+ # implicit-self `__dir__` call (receiver_type = Nominal[ClassName]) is matched by looking
319
+ # up Kernel as the owner. Explicit `Kernel.__dir__` (receiver_type = Singleton[Kernel]) and
320
+ # instance-side calls (receiver_type = Nominal[Klass]) share the `:both` row.
360
321
  #
361
- # The receiver-side ancestor check is intentionally cheap:
362
- # any non-BasicObject Nominal / Singleton matches every
363
- # Kernel-owned override. BasicObject explicitly excludes
364
- # Kernel and is therefore rejected. The narrow risk of a
365
- # user-defined `def __dir__` shadowing Kernel's method
366
- # would also alter the runtime answer; users with that
367
- # configuration opt out via a `signature_paths` overlay
368
- # declaring their own return type.
322
+ # The receiver-side ancestor check is intentionally cheap: any non-BasicObject Nominal /
323
+ # Singleton matches every Kernel-owned override. BasicObject explicitly excludes Kernel and
324
+ # is therefore rejected. The narrow risk of a user-defined `def __dir__` shadowing Kernel's
325
+ # method would also alter the runtime answer; users with that configuration opt out via a
326
+ # `signature_paths` overlay declaring their own return type.
369
327
  def try_static_refinement(receiver_type, method_name, arg_types)
370
328
  candidates = Rigor::Builtins::StaticReturnRefinements.owners_for(method_name)
371
329
  return nil if candidates.empty?
@@ -382,11 +340,10 @@ module Rigor
382
340
  )
383
341
  end
384
342
 
385
- # Picks the most specific override owner the receiver
386
- # honours. For Kernel-owned overrides the receiver simply
387
- # needs to be a real-class Nominal / Singleton (i.e. not
388
- # BasicObject and not a Dynamic / Constant / shape carrier
389
- # — those carriers go through their own narrower tiers).
343
+ # Picks the most specific override owner the receiver honours. For Kernel-owned overrides
344
+ # the receiver simply needs to be a real-class Nominal / Singleton (i.e. not BasicObject
345
+ # and not a Dynamic / Constant / shape carrier — those carriers go through their own
346
+ # narrower tiers).
390
347
  def static_refinement_owner_for(receiver_type, candidates)
391
348
  receiver_class = static_refinement_class_for(receiver_type)
392
349
  return nil unless receiver_class
@@ -403,19 +360,14 @@ module Rigor
403
360
  end
404
361
  end
405
362
 
406
- # ADR-2 § "Flow Contribution Bundle" / v0.1.1 Track 2
407
- # slice 7; ADR-52 WD3 consults each loaded plugin's gated
408
- # `dynamic_return` rules, wraps the contributed types as
409
- # `FlowContribution` bundles, merges them through
410
- # `FlowContribution::Merger`, and returns the merged
411
- # `return_type` slot (or nil when no plugin contributed a
412
- # return type).
363
+ # ADR-2 § "Flow Contribution Bundle" / v0.1.1 Track 2 slice 7; ADR-52 WD3 — consults each
364
+ # loaded plugin's gated `dynamic_return` rules, wraps the contributed types as
365
+ # `FlowContribution` bundles, merges them through `FlowContribution::Merger`, and returns
366
+ # the merged `return_type` slot (or nil when no plugin contributed a return type).
413
367
  #
414
- # Plugins whose hook raises have their contribution
415
- # silently dropped for this call so the dispatch chain
416
- # keeps moving the run-level diagnostic envelope (per
417
- # ADR-2 § "Plugin Trust and I/O Policy") is owned by
418
- # `Analysis::Runner#plugin_emitted_diagnostics`.
368
+ # Plugins whose hook raises have their contribution silently dropped for this call so the
369
+ # dispatch chain keeps moving the run-level diagnostic envelope (per ADR-2 § "Plugin
370
+ # Trust and I/O Policy") is owned by `Analysis::Runner#plugin_emitted_diagnostics`.
419
371
  def try_plugin_contribution(call_node, scope, receiver_type)
420
372
  return nil if call_node.nil? || scope.nil?
421
373
 
@@ -428,27 +380,20 @@ module Rigor
428
380
  FlowContribution::Merger.merge(contributions).return_type
429
381
  end
430
382
 
431
- # ADR-16 synthetic-method tier. Slice 2b shipped the floor —
432
- # a match short-circuits at the right precedence (above
433
- # dep-source / discovered / user-class-fallback; below RBS)
434
- # and returns `Dynamic[T]`. Slice 6 (precision promotion):
435
- # - Tier B path (slice 6a, `provenance[:origin_module]`
436
- # recorded by the slice-3b scanner): redispatch on
437
- # `Nominal[origin_module]` via `RbsDispatch` so the
438
- # module's authored RBS return type wins. Devise's
439
- # `valid_password?` returns `bool`, not `Dynamic[T]`.
440
- # - Tier C path (slice 6b, plain `return_type:` string from
441
- # the manifest's emit table): look up
442
- # `environment.nominal_for_name(return_type)` so
443
- # `attribute :avatar, Types::String` emits a synthetic
444
- # reader returning `Nominal[ActiveStorage::Attached::One]`
445
- # (when the class is in RBS). Unparameterised class names
446
- # only — parameterised forms (`Array[String]`,
447
- # `Hash[K, V]`) and plugin-supplied utility-type names
448
- # (`Pick<T, K>`) require routing through the full ADR-13
449
- # `Plugin::TypeNodeResolver` chain, which slice 6 does
450
- # not yet wire in (the resolver chain is consulted only
451
- # for `%a{rigor:v1:…}` payloads as of ADR-13 slice 3).
383
+ # ADR-16 synthetic-method tier. Slice 2b shipped the floor — a match short-circuits at the
384
+ # right precedence (above dep-source / discovered / user-class-fallback; below RBS) and
385
+ # returns `Dynamic[T]`. Slice 6 (precision promotion):
386
+ # - Tier B path (slice 6a, `provenance[:origin_module]` recorded by the slice-3b scanner):
387
+ # redispatch on `Nominal[origin_module]` via `RbsDispatch` so the module's authored RBS
388
+ # return type wins. Devise's `valid_password?` returns `bool`, not `Dynamic[T]`.
389
+ # - Tier C path (slice 6b, plain `return_type:` string from the manifest's emit table):
390
+ # look up `environment.nominal_for_name(return_type)` so `attribute :avatar,
391
+ # Types::String` emits a synthetic reader returning
392
+ # `Nominal[ActiveStorage::Attached::One]` (when the class is in RBS). Unparameterised
393
+ # class names only — parameterised forms (`Array[String]`, `Hash[K, V]`) and
394
+ # plugin-supplied utility-type names (`Pick<T, K>`) require routing through the full
395
+ # ADR-13 `Plugin::TypeNodeResolver` chain, which slice 6 does not yet wire in (the
396
+ # resolver chain is consulted only for `%a{rigor:v1:…}` payloads as of ADR-13 slice 3).
452
397
  def try_synthetic_method(receiver_type, method_name, arg_types, block_type, environment)
453
398
  index = environment&.synthetic_method_index
454
399
  return nil if index.nil? || index.empty?
@@ -466,12 +411,10 @@ module Rigor
466
411
  promoted || Type::Combinator.untyped
467
412
  end
468
413
 
469
- # First non-nil promotion wins. Tier B (origin_module) and
470
- # Tier C (return_type nominal lookup) are tried in the
471
- # same registration-order pass per WD11 first-wins
472
- # the slice-3b scanner sets `origin_module` for Tier B
473
- # entries and leaves it absent for Tier C, so the two
474
- # paths self-route per match.
414
+ # First non-nil promotion wins. Tier B (origin_module) and Tier C (return_type nominal
415
+ # lookup) are tried in the same registration-order pass per WD11 first-wins — the slice-3b
416
+ # scanner sets `origin_module` for Tier B entries and leaves it absent for Tier C, so the
417
+ # two paths self-route per match.
475
418
  def promote_synthetic_match(matches, method_name, arg_types, block_type, environment)
476
419
  return nil if environment.nil?
477
420
 
@@ -484,11 +427,10 @@ module Rigor
484
427
  nil
485
428
  end
486
429
 
487
- # Slice 6a-TierB. For Tier B emissions (origin_module
488
- # recorded in provenance), redispatch the call on the
489
- # included module's `Nominal[...]` type via `RbsDispatch`.
490
- # Returns nil when the SyntheticMethod is not a Tier B
491
- # entry or when the origin_module is not in the RBS env.
430
+ # Slice 6a-TierB. For Tier B emissions (origin_module recorded in provenance), redispatch
431
+ # the call on the included module's `Nominal[...]` type via `RbsDispatch`. Returns nil when
432
+ # the SyntheticMethod is not a Tier B entry or when the origin_module is not in the RBS
433
+ # env.
492
434
  def promote_via_origin_module(synthetic, method_name, arg_types, block_type, environment)
493
435
  module_name = synthetic.provenance[:origin_module]
494
436
  return nil unless module_name
@@ -502,13 +444,11 @@ module Rigor
502
444
  )
503
445
  end
504
446
 
505
- # Slice 6b-TierC. For Tier C emissions, look up the
506
- # manifest-declared `return_type:` string via
507
- # `environment.nominal_for_name`. Skips the placeholder
508
- # `"untyped"` (Tier B's record-but-do-not-resolve marker
509
- # from the slice-3b scanner) and the `"void"` keyword
510
- # (RBS-style absent return). Falls back to nil when the
511
- # class is not in the env — caller then returns Dynamic[T].
447
+ # Slice 6b-TierC. For Tier C emissions, look up the manifest-declared `return_type:` string
448
+ # via `environment.nominal_for_name`. Skips the placeholder `"untyped"` (Tier B's
449
+ # record-but-do-not-resolve marker from the slice-3b scanner) and the `"void"` keyword
450
+ # (RBS-style absent return). Falls back to nil when the class is not in the env — caller
451
+ # then returns Dynamic[T].
512
452
  TIER_C_PLACEHOLDER_RETURNS = %w[untyped void].freeze
513
453
  private_constant :TIER_C_PLACEHOLDER_RETURNS
514
454
 
@@ -525,15 +465,12 @@ module Rigor
525
465
  end
526
466
  end
527
467
 
528
- # ADR-17 slice 2 — project-side patched-method tier.
529
- # Slice 3a uses the registry's heuristic-extracted
530
- # `return_type` (populated via the same
531
- # `Analysis::DependencySourceInference::ReturnTypeHeuristic`
532
- # the ADR-10 walker uses): a `def to_url; "hello"; end`
533
- # patched onto `String` now resolves `s.to_url` to
534
- # `Dynamic[Nominal[String]]` instead of the pre-3a
535
- # `Dynamic[Top]`. Falls back to `Dynamic[Top]` when the
536
- # heuristic declined (non-literal tail expression).
468
+ # ADR-17 slice 2 — project-side patched-method tier. Slice 3a uses the registry's
469
+ # heuristic-extracted `return_type` (populated via the same
470
+ # `Analysis::DependencySourceInference::ReturnTypeHeuristic` the ADR-10 walker uses): a
471
+ # `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
472
+ # `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
473
+ # `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
537
474
  def try_project_patched_method(receiver_type, method_name, environment)
538
475
  registry = environment&.project_patched_methods
539
476
  return nil if registry.nil? || registry.empty?
@@ -549,16 +486,12 @@ module Rigor
549
486
  Type::Combinator.dynamic(entry.return_type)
550
487
  end
551
488
 
552
- # ADR-10 slice 2b-ii. Consults the per-run
553
- # `Analysis::DependencySourceInference::Index` carried by
554
- # the environment for `(class_name, method_name)`
555
- # observations harvested from opt-in gems' `roots:`. On a
556
- # hit, returns `Combinator.untyped` so the call site
557
- # carries the `Dynamic[top]` provenance (per ADR-10's
558
- # "Inference contract": gem-source-inferred shapes never
559
- # publish as ground-truth `T`). Returns `nil` when the
560
- # environment carries no index, the index has no entry, or
561
- # the receiver has no nominal class to look up.
489
+ # ADR-10 slice 2b-ii. Consults the per-run `Analysis::DependencySourceInference::Index`
490
+ # carried by the environment for `(class_name, method_name)` observations harvested from
491
+ # opt-in gems' `roots:`. On a hit, returns `Combinator.untyped` so the call site carries
492
+ # the `Dynamic[top]` provenance (per ADR-10's "Inference contract": gem-source-inferred
493
+ # shapes never publish as ground-truth `T`). Returns `nil` when the environment carries no
494
+ # index, the index has no entry, or the receiver has no nominal class to look up.
562
495
  def try_dependency_source(receiver_type, method_name, environment)
563
496
  index = environment&.dependency_source_index
564
497
  return nil if index.nil? || index.empty?
@@ -566,53 +499,40 @@ module Rigor
566
499
  class_name = dep_source_class_name(receiver_type)
567
500
  return nil if class_name.nil?
568
501
 
569
- # ADR-10 5a — per-receiver plugin veto. When a
570
- # registered plugin declares `manifest(owns_receivers:
571
- # [<class>])` AND the call's receiver IS that class
572
- # (or a subclass), decline and let plugins handle the
573
- # call. Plugins that own a receiver are the
574
- # authoritative source for that type; gem-source
575
- # inference must not contribute behind their backs.
502
+ # ADR-10 5a — per-receiver plugin veto. When a registered plugin declares
503
+ # `manifest(owns_receivers: [<class>])` AND the call's receiver IS that class (or a
504
+ # subclass), decline and let plugins handle the call. Plugins that own a receiver are
505
+ # the authoritative source for that type; gem-source inference must not contribute
506
+ # behind their backs.
576
507
  return nil if plugin_owns_receiver?(class_name, environment)
577
508
 
578
509
  contribution = index.contribution_for(class_name: class_name, method_name: method_name)
579
510
  return dependency_source_return_type(contribution) if contribution
580
511
 
581
- # ADR-10 5b — β budget semantics. On a catalog miss,
582
- # if the receiver class belongs to a budget-exceeded
583
- # gem AND the user opted into `:dependency_silence`,
584
- # return `Dynamic[top]` rather than falling through to
585
- # the user-class fallback. The user-class fallback
586
- # would otherwise emit `call.undefined-method` for
587
- # methods Rigor's catalog couldn't reach because the
588
- # walker hit its cap.
512
+ # ADR-10 5b — β budget semantics. On a catalog miss, if the receiver class belongs to a
513
+ # budget-exceeded gem AND the user opted into `:dependency_silence`, return
514
+ # `Dynamic[top]` rather than falling through to the user-class fallback. The user-class
515
+ # fallback would otherwise emit `call.undefined-method` for methods Rigor's catalog
516
+ # couldn't reach because the walker hit its cap.
589
517
  budget_silence_result(class_name, index, environment)
590
518
  end
591
519
 
592
- # ADR-10 slice 5c — record a
593
- # `dynamic.dependency-source.boundary-cross` event when
594
- # RBS dispatch resolves a call AND the receiver class
595
- # belongs to a `mode: :full` opt-in gem whose Walker
596
- # also catalogued the same `(class_name, method_name)`.
597
- # The dispatcher still returns the RBS answer (per
598
- # ADR-10's tier order: authoritative-source wins), but
599
- # the reporter accumulates the crossing for end-of-run
600
- # audit diagnostics.
520
+ # ADR-10 slice 5c — record a `dynamic.dependency-source.boundary-cross` event when RBS
521
+ # dispatch resolves a call AND the receiver class belongs to a `mode: :full` opt-in gem
522
+ # whose Walker also catalogued the same `(class_name, method_name)`. The dispatcher still
523
+ # returns the RBS answer (per ADR-10's tier order: authoritative-source wins), but the
524
+ # reporter accumulates the crossing for end-of-run audit diagnostics.
601
525
  #
602
526
  # Five honest fall-throughs keep the gate narrow:
603
527
  #
604
- # - environment / index / reporter missing — slice 5c
605
- # needs all three.
606
- # - receiver has no nominal class name (Dynamic-only
607
- # carriers) nothing to look up.
608
- # - receiver class doesn't belong to a `mode: :full` gem
609
- # the user didn't opt this gem into the distinct
610
- # dispatch path.
611
- # - the gem-source catalog has no entry for the method —
612
- # only RBS knows about it; nothing to cross.
613
- # - the RBS-side result is itself `Dynamic[Top]` — the
614
- # "agreement" is trivially `untyped ≈ untyped`, no
615
- # meaningful divergence to flag.
528
+ # - environment / index / reporter missing — slice 5c needs all three.
529
+ # - receiver has no nominal class name (Dynamic-only carriers) — nothing to look up.
530
+ # - receiver class doesn't belong to a `mode: :full` gem — the user didn't opt this gem
531
+ # into the distinct dispatch path.
532
+ # - the gem-source catalog has no entry for the method — only RBS knows about it; nothing
533
+ # to cross.
534
+ # - the RBS-side result is itself `Dynamic[Top]` — the "agreement" is trivially `untyped ≈
535
+ # untyped`, no meaningful divergence to flag.
616
536
  def record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
617
537
  class_name = boundary_cross_class_name(receiver_type, environment, rbs_result)
618
538
  return if class_name.nil?
@@ -628,23 +548,20 @@ module Rigor
628
548
  )
629
549
  end
630
550
 
631
- # Maps a {DependencySourceInference::Walker::CatalogEntry}
632
- # to the Type the dispatcher returns at the call site.
633
- # When the heuristic recovered a static facet, wrap it in
634
- # `Dynamic[T]` per ADR-10's gem-boundary contract;
635
- # otherwise fall back to the pre-heuristic `Dynamic[top]`.
551
+ # Maps a {DependencySourceInference::Walker::CatalogEntry} to the Type the dispatcher
552
+ # returns at the call site. When the heuristic recovered a static facet, wrap it in
553
+ # `Dynamic[T]` per ADR-10's gem-boundary contract; otherwise fall back to the
554
+ # pre-heuristic `Dynamic[top]`.
636
555
  def dependency_source_return_type(contribution)
637
556
  return Type::Combinator.untyped if contribution.return_type.nil?
638
557
 
639
558
  Type::Combinator.dynamic(contribution.return_type)
640
559
  end
641
560
 
642
- # Composite preflight for {#record_boundary_cross_if_applicable}.
643
- # Returns the receiver class name only when every prerequisite
644
- # for emitting the diagnostic is satisfied (environment carries
645
- # an index + reporter, receiver is a nominal carrier, RBS-side
646
- # result is not the trivial `Dynamic[Top]` envelope). Returns
647
- # `nil` to short-circuit otherwise.
561
+ # Composite preflight for {#record_boundary_cross_if_applicable}. Returns the receiver
562
+ # class name only when every prerequisite for emitting the diagnostic is satisfied
563
+ # (environment carries an index + reporter, receiver is a nominal carrier, RBS-side result
564
+ # is not the trivial `Dynamic[Top]` envelope). Returns `nil` to short-circuit otherwise.
648
565
  def boundary_cross_class_name(receiver_type, environment, rbs_result)
649
566
  return nil if environment.nil?
650
567
  return nil if environment.dependency_source_index.nil?
@@ -675,9 +592,8 @@ module Rigor
675
592
  Type::Combinator.untyped
676
593
  end
677
594
 
678
- # ADR-52 WD1 — the per-dispatch plugins × owns_receivers ×
679
- # `class_ordering` walk moved into the compiled contribution
680
- # table: the union is built once per registry (almost always
595
+ # ADR-52 WD1 — the per-dispatch plugins × owns_receivers × `class_ordering` walk moved into
596
+ # the compiled contribution table: the union is built once per registry (almost always
681
597
  # empty → O(1) false) and per-class verdicts memoise per run.
682
598
  def plugin_owns_receiver?(class_name, environment)
683
599
  registry = environment&.plugin_registry
@@ -692,55 +608,48 @@ module Rigor
692
608
  end
693
609
  end
694
610
 
695
- # ADR-37 slice 2 / ADR-52 WD3 — gathers each plugin's return-type
696
- # contribution from the gated `dynamic_return` DSL, wrapped as a
697
- # return-only `FlowContribution` for the shared merger. (The legacy
698
- # ungated `flow_contribution_for` escape valve was deleted once its
611
+ # ADR-37 slice 2 / ADR-52 WD3 — gathers each plugin's return-type contribution from the
612
+ # gated `dynamic_return` DSL, wrapped as a return-only `FlowContribution` for the shared
613
+ # merger. (The legacy ungated `flow_contribution_for` escape valve was deleted once its
699
614
  # five users migrated.)
700
615
  EMPTY_CONTRIBUTIONS = [].freeze
701
616
  private_constant :EMPTY_CONTRIBUTIONS
702
617
 
703
- # Collects every plugin's flow / dynamic-return contribution for one
704
- # call site. Two prunings keep this off the hot path on plugin-heavy
705
- # projects (it was the #1 allocation site and a top CPU cost):
618
+ # Collects every plugin's flow / dynamic-return contribution for one call site. Two
619
+ # prunings keep this off the hot path on plugin-heavy projects (it was the #1 allocation
620
+ # site and a top CPU cost):
706
621
  #
707
- # 1. Only the plugins that *structurally* implement a per-call path
708
- # are visited — `registry.contribution_index.for_method_dispatch`
709
- # is the registry-ordered subset declaring a `dynamic_return`.
710
- # Iterating the subset in registry order, and gating each path by
711
- # membership, yields the exact same contributions in the same
712
- # order as visiting every plugin would (a skipped plugin's call
713
- # returns nil/[] anyway). The receiver-class ancestry match still
714
- # happens per dispatch inside `dynamic_return_type`.
715
- # 2. Contributions accumulate lazily — allocate only when one
716
- # actually appears, and share a frozen empty array otherwise. The
717
- # caller treats the result as read-only (`.empty?` / `Merger.merge`).
718
- # 3. ADR-52 WD1 method-name gates compiled at registry build. The
719
- # global gate makes the common "no plugin cares about this call"
720
- # case a single Set probe; the per-plugin gate skips a plugin
721
- # whose `dynamic_return` rules are all `methods:`-gated on other
722
- # names. A pruned consultation could only have returned nil, so
723
- # contribution order and content are unchanged.
622
+ # 1. Only the plugins that *structurally* implement a per-call path are visited —
623
+ # `registry.contribution_index.for_method_dispatch` is the registry-ordered subset
624
+ # declaring a `dynamic_return`. Iterating the subset in registry order, and gating each
625
+ # path by membership, yields the exact same contributions in the same order as visiting
626
+ # every plugin would (a skipped plugin's call returns nil/[] anyway). The
627
+ # receiver-class ancestry match still happens per dispatch inside `dynamic_return_type`.
628
+ # 2. Contributions accumulate lazily allocate only when one actually appears, and share a
629
+ # frozen empty array otherwise. The caller treats the result as read-only (`.empty?` /
630
+ # `Merger.merge`).
631
+ # 3. ADR-52 WD1 method-name gates compiled at registry build. The global gate makes the
632
+ # common "no plugin cares about this call" case a single Set probe; the per-plugin gate
633
+ # skips a plugin whose `dynamic_return` rules are all `methods:`-gated on other names. A
634
+ # pruned consultation could only have returned nil, so contribution order and content
635
+ # are unchanged.
724
636
  def collect_plugin_contributions(registry, call_node, scope, receiver_type)
725
637
  index = registry.contribution_index
726
638
  relevant = index.for_method_dispatch
727
639
  return EMPTY_CONTRIBUTIONS if relevant.empty?
728
640
 
729
- # `call_node` is not always a CallNode — the `&:symbol` block
730
- # path dispatches with the `Prism::BlockArgumentNode` itself
731
- # (`ExpressionTyper#symbol_block_return_type`). A bare `.name`
732
- # here raised, and the raise was silently absorbed by
733
- # `block_return_type_for`'s rescue, nil-ing the block type and
734
- # flipping `select(&:p)`-style calls onto their no-block
735
- # Enumerator overloads (caught by the GitLab corpus gate).
641
+ # `call_node` is not always a CallNode — the `&:symbol` block path dispatches with the
642
+ # `Prism::BlockArgumentNode` itself (`ExpressionTyper#symbol_block_return_type`). A bare
643
+ # `.name` here raised, and the raise was silently absorbed by `block_return_type_for`'s
644
+ # rescue, nil-ing the block type and flipping `select(&:p)`-style calls onto their
645
+ # no-block Enumerator overloads (caught by the GitLab corpus gate).
736
646
  name = call_node.respond_to?(:name) ? call_node.name : nil
737
647
  return EMPTY_CONTRIBUTIONS unless index.dispatch_candidate?(name)
738
648
 
739
649
  collect_gated_contributions(index, relevant, name, call_node, scope, receiver_type)
740
650
  end
741
651
 
742
- # The post-gate walk, in registry order — the same order the
743
- # ungated walk used.
652
+ # The post-gate walk, in registry order — the same order the ungated walk used.
744
653
  def collect_gated_contributions(index, relevant, name, call_node, scope, receiver_type)
745
654
  result = nil
746
655
  relevant.each do |plugin|
@@ -754,39 +663,31 @@ module Rigor
754
663
  result || EMPTY_CONTRIBUTIONS
755
664
  end
756
665
 
757
- # Runs the precision tiers (constant fold, shape dispatch,
758
- # file-path fold, block fold) in order and returns the first
759
- # non-nil answer. Each tier owns its own receiver/argument
760
- # shape checks; a tier that does not recognise the receiver
761
- # returns nil so the next tier can try. The RBS tier sits
762
- # below this chain and is invoked by the outer `dispatch`
666
+ # Runs the precision tiers (constant fold, shape dispatch, file-path fold, block fold) in
667
+ # order and returns the first non-nil answer. Each tier owns its own receiver/argument
668
+ # shape checks; a tier that does not recognise the receiver returns nil so the next tier
669
+ # can try. The RBS tier sits below this chain and is invoked by the outer `dispatch`
763
670
  # method.
764
671
  #
765
- # The precise-tier folders, consulted in order via the uniform
766
- # `_DispatchTier` interface (`try_dispatch(CallContext) -> Type?`).
767
- # Order is significant: ConstantFolding's exact-value folds win
768
- # first, the eight stdlib singleton folders sit in the middle (each
769
- # gates on a distinct `Singleton` receiver, so their relative order
770
- # is immaterial), and BlockFolding runs last because its rules only
771
- # apply to block-taking calls the cheaper arity folds above it
772
- # filter the common cases first. Adding a precise tier is a
773
- # one-line append here rather than another link in a hand-written
774
- # `||` ladder.
672
+ # The precise-tier folders, consulted in order via the uniform `_DispatchTier` interface
673
+ # (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
674
+ # exact-value folds win first, the eight stdlib singleton folders sit in the middle (each
675
+ # gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
676
+ # BlockFolding runs last because its rules only apply to block-taking calls — the cheaper
677
+ # arity folds above it filter the common cases first. Adding a precise tier is a one-line
678
+ # append here rather than another link in a hand-written `||` ladder.
775
679
  PRECISE_TIERS_HEAD = Ractor.make_shareable([
776
680
  ConstantFolding, LiteralStringFolding, ShapeDispatch
777
681
  ].freeze)
778
682
  private_constant :PRECISE_TIERS_HEAD
779
683
 
780
- # ADR-53 re-review follow-up (gate-by-held-key applied to the
781
- # built-in tiers): the eight stdlib singleton folders are mutually
782
- # exclusive each fires only on `Singleton[<its class>]`, the
783
- # first check in every `try_dispatch` so at most one can match a
784
- # given receiver and their relative trial order was never
785
- # observable. Compiling them into a class-name table turns eight
786
- # no-op trials per call into one Hash read, skipped entirely when
787
- # the receiver is not a `Singleton` (the overwhelmingly common
788
- # case). The table sits where the eight sat in the old flat list:
789
- # after ShapeDispatch, before KernelDispatch.
684
+ # ADR-53 re-review follow-up (gate-by-held-key applied to the built-in tiers): the eight
685
+ # stdlib singleton folders are mutually exclusive — each fires only on `Singleton[<its
686
+ # class>]`, the first check in every `try_dispatch` so at most one can match a given
687
+ # receiver and their relative trial order was never observable. Compiling them into a
688
+ # class-name table turns eight no-op trials per call into one Hash read, skipped entirely
689
+ # when the receiver is not a `Singleton` (the overwhelmingly common case). The table sits
690
+ # where the eight sat in the old flat list: after ShapeDispatch, before KernelDispatch.
790
691
  STDLIB_SINGLETON_FOLDERS = Ractor.make_shareable({
791
692
  "File" => FileFolding,
792
693
  "Shellwords" => ShellwordsFolding,
@@ -805,20 +706,18 @@ module Rigor
805
706
  private_constant :PRECISE_TIERS_TAIL
806
707
 
807
708
  def dispatch_precise_tiers(context)
808
- # ADR-48 — Data value folding runs ahead of meta-introspection:
809
- # `meta_new` intercepts every `Singleton[*].new` (returning
810
- # `Nominal`), which would mask a `Data` class's precise instance.
811
- # The tier only fires on Data receivers (a `Data.define`, a
812
- # `DataClass`/`DataInstance`, or a `Singleton` with a recorded
813
- # member layout), so it never shadows meta's Array/Set/Range lifts.
709
+ # ADR-48 — Data value folding runs ahead of meta-introspection: `meta_new` intercepts
710
+ # every `Singleton[*].new` (returning `Nominal`), which would mask a `Data` class's
711
+ # precise instance. The tier only fires on Data receivers (a `Data.define`, a
712
+ # `DataClass`/`DataInstance`, or a `Singleton` with a recorded member layout), so it
713
+ # never shadows meta's Array/Set/Range lifts.
814
714
  data_result = DataFolding.try_dispatch(context)
815
715
  return data_result if data_result
816
716
 
817
- # ADR-48 Struct follow-up — runs in the same band and for the same
818
- # reason as DataFolding: `meta_new` would otherwise intercept every
819
- # `Singleton[*].new` (the old `struct_new_lift` produced a bare
820
- # `Singleton[Struct]`), masking a Struct class's precise instance.
821
- # Only fires on Struct receivers, so it never shadows meta's lifts.
717
+ # ADR-48 Struct follow-up — runs in the same band and for the same reason as DataFolding:
718
+ # `meta_new` would otherwise intercept every `Singleton[*].new` (the old
719
+ # `struct_new_lift` produced a bare `Singleton[Struct]`), masking a Struct class's
720
+ # precise instance. Only fires on Struct receivers, so it never shadows meta's lifts.
822
721
  struct_result = StructFolding.try_dispatch(context)
823
722
  return struct_result if struct_result
824
723
 
@@ -849,24 +748,19 @@ module Rigor
849
748
  fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
850
749
  return nil if fallback_receiver.nil?
851
750
 
852
- # Preserve the ORIGINAL receiver type as the `self`
853
- # substitution so `Kernel#dup: () -> self` and other
854
- # `self`-returning methods route through Object's RBS
855
- # while still returning the caller's type rather than
856
- # `Object`. Without this, `base = self.dup` inside a
857
- # `Bundler::URI::Generic` instance method types `base`
858
- # as `Object` because `Bundler::URI::Generic` is not in
859
- # RBS and the fallback's `self` resolves to Object.
751
+ # Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
752
+ # self` and other `self`-returning methods route through Object's RBS while still
753
+ # returning the caller's type rather than `Object`. Without this, `base = self.dup`
754
+ # inside a `Bundler::URI::Generic` instance method types `base` as `Object` because
755
+ # `Bundler::URI::Generic` is not in RBS and the fallback's `self` resolves to Object.
860
756
  #
861
- # `public_only:` — when the call has an EXPLICIT, non-`self`
862
- # receiver (`Favourite.select(...)`), suppress the private
863
- # `Object`/`Kernel`/`Class` methods the fallback would
864
- # otherwise resolve. Ruby raises `NoMethodError` for a
865
- # private method called with an explicit receiver, so
866
- # resolving `Favourite.select` to the private `Kernel#select`
867
- # (`-> Array[String]`) is a confidently-wrong type. Implicit-
868
- # self / `self.`-receiver calls (`puts`, `raise`, `require`)
869
- # keep resolving — those are the fallback's intended targets.
757
+ # `public_only:` — when the call has an EXPLICIT, non-`self` receiver
758
+ # (`Favourite.select(...)`), suppress the private `Object`/`Kernel`/`Class` methods the
759
+ # fallback would otherwise resolve. Ruby raises `NoMethodError` for a private method
760
+ # called with an explicit receiver, so resolving `Favourite.select` to the private
761
+ # `Kernel#select` (`-> Array[String]`) is a confidently-wrong type. Implicit-self /
762
+ # `self.`-receiver calls (`puts`, `raise`, `require`) keep resolving — those are the
763
+ # fallback's intended targets.
870
764
  RbsDispatch.try_dispatch(
871
765
  context.with(
872
766
  receiver: fallback_receiver,
@@ -877,13 +771,11 @@ module Rigor
877
771
  )
878
772
  end
879
773
 
880
- # True when the call node carries an explicit receiver that is
881
- # not the literal `self`. Such a call cannot legally dispatch to
882
- # a private method, so the user-class fallback must skip private
883
- # signatures rather than return a confidently-wrong type. Returns
884
- # false for implicit-self calls and `self.`-receiver calls (both
885
- # may legally reach a private method in modern Ruby), and false
886
- # when no `call_node` is supplied (internal dispatcher callers).
774
+ # True when the call node carries an explicit receiver that is not the literal `self`.
775
+ # Such a call cannot legally dispatch to a private method, so the user-class fallback must
776
+ # skip private signatures rather than return a confidently-wrong type. Returns false for
777
+ # implicit-self calls and `self.`-receiver calls (both may legally reach a private method
778
+ # in modern Ruby), and false when no `call_node` is supplied (internal dispatcher callers).
887
779
  def explicit_non_self_receiver?(call_node)
888
780
  return false if call_node.nil?
889
781
  return false unless call_node.respond_to?(:receiver)
@@ -897,13 +789,11 @@ module Rigor
897
789
  def user_class_fallback_receiver(receiver_type, environment)
898
790
  case receiver_type
899
791
  when Type::Nominal
900
- # Modules: even when RBS knows the module, an instance
901
- # method on a mixin-only module (e.g. `PP::ObjectMixin`)
902
- # observes Kernel / Object methods through every concrete
903
- # includer's ancestor chain. Route through the
904
- # `Nominal[Object]` fallback so `self.inspect` /
905
- # `self.respond_to?` / `self.class` resolve cleanly when
906
- # the module itself does not declare them.
792
+ # Modules: even when RBS knows the module, an instance method on a mixin-only module
793
+ # (e.g. `PP::ObjectMixin`) observes Kernel / Object methods through every concrete
794
+ # includer's ancestor chain. Route through the `Nominal[Object]` fallback so
795
+ # `self.inspect` / `self.respond_to?` / `self.class` resolve cleanly when the module
796
+ # itself does not declare them.
907
797
  known = Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
908
798
  return environment.nominal_for_name("Object") if !known || environment.rbs_module?(receiver_type.class_name)
909
799
 
@@ -915,18 +805,13 @@ module Rigor
915
805
  end
916
806
  end
917
807
 
918
- # Slice 7 phase 8 — meta-introspection shortcuts. The
919
- # default `Object#class` RBS return type is `Class`, but
920
- # for a receiver of known nominal identity we can do
921
- # better: `instance_of(Foo).class` is `Singleton[Foo]`
922
- # (the class object itself), which downstream dispatch
923
- # uses to resolve `self.class.some_class_method`. The
924
- # same logic answers `Foo.class` as `Singleton[Class]`
925
- # (deliberate; calling `.class` on a class object yields
926
- # `Class`, the metaclass). We also special-case `is_a?`-
927
- # adjacent calls and the trivial `instance_of?(self)`
928
- # later as the rule catalogue grows; for now only `class`
929
- # is handled.
808
+ # Slice 7 phase 8 — meta-introspection shortcuts. The default `Object#class` RBS return
809
+ # type is `Class`, but for a receiver of known nominal identity we can do better:
810
+ # `instance_of(Foo).class` is `Singleton[Foo]` (the class object itself), which downstream
811
+ # dispatch uses to resolve `self.class.some_class_method`. The same logic answers
812
+ # `Foo.class` as `Singleton[Class]` (deliberate; calling `.class` on a class object yields
813
+ # `Class`, the metaclass). We also special-case `is_a?`-adjacent calls and the trivial
814
+ # `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.
930
815
  def try_meta_introspection(receiver_type, method_name, arg_types = [])
931
816
  case method_name
932
817
  when :class then meta_class(receiver_type)
@@ -941,18 +826,15 @@ module Rigor
941
826
  end
942
827
  end
943
828
 
944
- # `Singleton[Foo].new` returns `Nominal[Foo]` (a fresh
945
- # instance), regardless of whether Foo is in RBS. This
946
- # short-circuits the Class.new generic-`instance`
947
- # plumbing for user classes, so a discovered-class
948
- # `ScanAccumulator.new` types as `Nominal[ScanAccumulator]`
949
- # rather than `Class`.
829
+ # `Singleton[Foo].new` returns `Nominal[Foo]` (a fresh instance), regardless of whether Foo
830
+ # is in RBS. This short-circuits the Class.new generic-`instance` plumbing for user
831
+ # classes, so a discovered-class `ScanAccumulator.new` types as
832
+ # `Nominal[ScanAccumulator]` rather than `Class`.
950
833
  #
951
- # v0.0.7 — for the curated set of immutable scalar-shaped
952
- # classes that `Type::Constant::SCALAR_CLASSES` accepts
953
- # (today: `Pathname`), `.new(Constant<…>)` lifts to a
954
- # `Constant<…>` carrier so downstream method calls fold
955
- # through the standard catalog tier.
834
+ # v0.0.7 — for the curated set of immutable scalar-shaped classes that
835
+ # `Type::Constant::SCALAR_CLASSES` accepts (today: `Pathname`), `.new(Constant<…>)` lifts
836
+ # to a `Constant<…>` carrier so downstream method calls fold through the standard catalog
837
+ # tier.
956
838
  def meta_new(receiver_type, arg_types = [])
957
839
  return nil unless receiver_type.is_a?(Type::Singleton)
958
840
 
@@ -986,25 +868,21 @@ module Rigor
986
868
  Type::Combinator.nominal_of(receiver_type.class_name)
987
869
  end
988
870
 
989
- # `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass*
990
- # (a class object), not a Struct *instance* — so the chained
991
- # idiom `Struct.new(:a, :b).new(1, 2)` must resolve `.new` again
992
- # on a class-like carrier. The constant-bound form
993
- # (`S = Struct.new(:a); S.new(1)`) already records `Singleton[S]`
994
- # via `ScopeIndexer#record_meta_new_constant?`; this lift gives
995
- # the *chained* (anonymous) position the same class-like carrier
996
- # so the trailing `.new` dispatches instead of firing a spurious
997
- # `undefined method 'new' for Struct`.
871
+ # `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass* (a class object), not a
872
+ # Struct *instance* — so the chained idiom `Struct.new(:a, :b).new(1, 2)` must resolve
873
+ # `.new` again on a class-like carrier. The constant-bound form (`S = Struct.new(:a);
874
+ # S.new(1)`) already records `Singleton[S]` via `ScopeIndexer#record_meta_new_constant?`;
875
+ # this lift gives the *chained* (anonymous) position the same class-like carrier so the
876
+ # trailing `.new` dispatches instead of firing a spurious `undefined method 'new' for
877
+ # Struct`.
998
878
  #
999
- # The disambiguation mirrors `ScopeIndexer#struct_new_call?`: a
1000
- # call whose positionals are all `Constant<Symbol>` literals is a
1001
- # member-list class definition `Singleton[Struct]`. The
1002
- # following `AnonStruct.new(1, 2)` carries non-symbol args, so it
1003
- # falls through this gate to `Nominal[Struct]` (a fresh instance)
1004
- # via the `meta_new` tail. ADR-48 deferred full Struct *value*
1005
- # folding (member-reader precision) on mutability grounds; this is
1006
- # the narrower `.new`-dispatch-only fix and contributes no member
1007
- # layout, so `instance.a` stays at its RBS/Dynamic type.
879
+ # The disambiguation mirrors `ScopeIndexer#struct_new_call?`: a call whose positionals are
880
+ # all `Constant<Symbol>` literals is a member-list class definition → `Singleton[Struct]`.
881
+ # The following `AnonStruct.new(1, 2)` carries non-symbol args, so it falls through this
882
+ # gate to `Nominal[Struct]` (a fresh instance) via the `meta_new` tail. ADR-48 deferred full
883
+ # Struct *value* folding (member-reader precision) on mutability grounds; this is the
884
+ # narrower `.new`-dispatch-only fix and contributes no member layout, so `instance.a` stays
885
+ # at its RBS/Dynamic type.
1008
886
  def struct_new_lift(class_name, arg_types)
1009
887
  return nil unless class_name == "Struct"
1010
888
 
@@ -1015,15 +893,12 @@ module Rigor
1015
893
  Type::Combinator.singleton_of("Struct")
1016
894
  end
1017
895
 
1018
- # `Class.new` and `Class.new(Parent)` create a brand-new
1019
- # anonymous class. Statically that class is representable as
1020
- # the parent's singleton type its singleton-method surface
1021
- # is the parent's (plus whatever the block defines, which we
1022
- # do not statically track here), so `Singleton[Parent]` lets
1023
- # downstream `klass.some_class_method` resolve. No parent
1024
- # `singleton(Object)`. Anything else (dynamic parent, more
1025
- # than one positional, …) falls back to `Nominal[Class]` via
1026
- # the surrounding `meta_new` tail.
896
+ # `Class.new` and `Class.new(Parent)` create a brand-new anonymous class. Statically that
897
+ # class is representable as the parent's singleton type — its singleton-method surface is
898
+ # the parent's (plus whatever the block defines, which we do not statically track here),
899
+ # so `Singleton[Parent]` lets downstream `klass.some_class_method` resolve. No parent
900
+ # `singleton(Object)`. Anything else (dynamic parent, more than one positional, …) falls
901
+ # back to `Nominal[Class]` via the surrounding `meta_new` tail.
1027
902
  def class_new_lift(class_name, arg_types)
1028
903
  return nil unless class_name == "Class"
1029
904
  return Type::Combinator.singleton_of("Object") if arg_types.empty?
@@ -1035,20 +910,15 @@ module Rigor
1035
910
  nil
1036
911
  end
1037
912
 
1038
- # ADR-15 Phase 4b.x — `Ractor.make_shareable` on both the
1039
- # outer Hash and each lambda value. A plain `.freeze` leaves
1040
- # the Procs unshareable; reading `CONSTANT_CONSTRUCTORS[class]`
1041
- # from a worker Ractor would raise `Ractor::IsolationError`,
1042
- # which the `rescue StandardError` in
1043
- # `constant_constructor_lift` silently swallows `meta_new`
1044
- # then falls back to `Nominal[Pathname]` in pool mode while
1045
- # sequential builds the `Constant<Pathname>` lift. The
1046
- # divergence surfaces downstream as a spurious
1047
- # `call.argument-type-mismatch` (sequential's
1048
- # `argument_type_diagnostic` short-circuits on Constant<Pathname>
1049
- # because Pathname is not in its CONSTANT_CLASSES table; pool's
1050
- # Nominal[Pathname] doesn't short-circuit). Surfaced on GitLab
1051
- # FOSS via `lib/gitlab/mail_room.rb:17`.
913
+ # ADR-15 Phase 4b.x — `Ractor.make_shareable` on both the outer Hash and each lambda value.
914
+ # A plain `.freeze` leaves the Procs unshareable; reading `CONSTANT_CONSTRUCTORS[class]`
915
+ # from a worker Ractor would raise `Ractor::IsolationError`, which the `rescue
916
+ # StandardError` in `constant_constructor_lift` silently swallows `meta_new` then falls
917
+ # back to `Nominal[Pathname]` in pool mode while sequential builds the `Constant<Pathname>`
918
+ # lift. The divergence surfaces downstream as a spurious `call.argument-type-mismatch`
919
+ # (sequential's `argument_type_diagnostic` short-circuits on Constant<Pathname> because
920
+ # Pathname is not in its CONSTANT_CLASSES table; pool's Nominal[Pathname] doesn't
921
+ # short-circuit). Surfaced on GitLab FOSS via `lib/gitlab/mail_room.rb:17`.
1052
922
  CONSTANT_CONSTRUCTORS = Ractor.make_shareable({
1053
923
  "Pathname" => Ractor.make_shareable(lambda { |arg|
1054
924
  Pathname.new(arg)
@@ -1070,11 +940,10 @@ module Rigor
1070
940
  nil
1071
941
  end
1072
942
 
1073
- # `Array.new(n, value)` and `Array.new(n)` (no value, default
1074
- # `nil`) lift to a per-position `Tuple[…]` when `n` is a
1075
- # small `Constant<Integer>`. Cap at `ARRAY_NEW_TUPLE_LIMIT`
1076
- # (16) so a `Array.new(1_000_000)` does not balloon the
1077
- # carrier; oversize calls fall back to `Nominal[Array]`.
943
+ # `Array.new(n, value)` and `Array.new(n)` (no value, default `nil`) lift to a per-position
944
+ # `Tuple[…]` when `n` is a small `Constant<Integer>`. Cap at `ARRAY_NEW_TUPLE_LIMIT` (16)
945
+ # so a `Array.new(1_000_000)` does not balloon the carrier; oversize calls fall back to
946
+ # `Nominal[Array]`.
1078
947
  ARRAY_NEW_TUPLE_LIMIT = 16
1079
948
  private_constant :ARRAY_NEW_TUPLE_LIMIT
1080
949
 
@@ -1101,22 +970,18 @@ module Rigor
1101
970
  type
1102
971
  end
1103
972
 
1104
- # `Hash.new(default)` — lifts the default value's type into the
1105
- # Hash's value parameter so a subsequent `h[k]` read surfaces the
1106
- # default type rather than `Dynamic[top]`. The common counter
1107
- # idiom `h = Hash.new(0); h[k] += 1` then types the read as
1108
- # `Integer`. The key parameter is left `untyped` (the default
1109
- # carrier imposes no key constraint), so reads of any key resolve
1110
- # through the value parameter. A value-pinned `Constant` default
1111
- # (`0`) is widened to its nominal (`Integer`): the hash's values
1112
- # mutate over its lifetime, so pinning the parameter to the
1113
- # literal would be unsound for the aggregate.
973
+ # `Hash.new(default)` — lifts the default value's type into the Hash's value parameter so
974
+ # a subsequent `h[k]` read surfaces the default type rather than `Dynamic[top]`. The
975
+ # common counter idiom `h = Hash.new(0); h[k] += 1` then types the read as `Integer`. The
976
+ # key parameter is left `untyped` (the default carrier imposes no key constraint), so
977
+ # reads of any key resolve through the value parameter. A value-pinned `Constant` default
978
+ # (`0`) is widened to its nominal (`Integer`): the hash's values mutate over its lifetime,
979
+ # so pinning the parameter to the literal would be unsound for the aggregate.
1114
980
  #
1115
- # Only the single-argument default form folds. The zero-arg
1116
- # (`Hash.new`) and the block form (`Hash.new { |h, k| … }`) keep
1117
- # the bare `Nominal[Hash]` answer the block's value type is not
1118
- # available at this `:new` dispatch site, and conservatively
1119
- # leaving the read as today's behaviour is precision-additive.
981
+ # Only the single-argument default form folds. The zero-arg (`Hash.new`) and the block
982
+ # form (`Hash.new { |h, k| … }`) keep the bare `Nominal[Hash]` answer — the block's value
983
+ # type is not available at this `:new` dispatch site, and conservatively leaving the read
984
+ # as today's behaviour is precision-additive.
1120
985
  def hash_new_lift(class_name, arg_types)
1121
986
  return nil unless class_name == "Hash"
1122
987
  return nil unless arg_types.size == 1
@@ -1128,13 +993,11 @@ module Rigor
1128
993
  Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.untyped, value])
1129
994
  end
1130
995
 
1131
- # `Range.new(b, e)` / `Range.new(b, e, excl)` — folds to
1132
- # `Constant[Range]` when both endpoints are `Constant[Integer]`
1133
- # or both are `Constant[String]`, and the optional third argument
1134
- # is a `Constant[true/false]`. Nil endpoints (beginless /
1135
- # endless ranges) are not folded because the useful instance
1136
- # methods (`to_a`, `first`, `last`) are not defined for them;
1137
- # the RBS tier answers `Nominal[Range]` for those forms.
996
+ # `Range.new(b, e)` / `Range.new(b, e, excl)` — folds to `Constant[Range]` when both
997
+ # endpoints are `Constant[Integer]` or both are `Constant[String]`, and the optional third
998
+ # argument is a `Constant[true/false]`. Nil endpoints (beginless / endless ranges) are not
999
+ # folded because the useful instance methods (`to_a`, `first`, `last`) are not defined for
1000
+ # them; the RBS tier answers `Nominal[Range]` for those forms.
1138
1001
  def range_new_lift(class_name, arg_types)
1139
1002
  return nil unless class_name == "Range"
1140
1003
  return nil if arg_types.size < 2 || arg_types.size > 3
@@ -1159,9 +1022,9 @@ module Rigor
1159
1022
  nil
1160
1023
  end
1161
1024
 
1162
- # Resolves the optional `exclude_end` argument for `range_new_lift`.
1163
- # Returns `false` (no arg), `true`/`false` (Constant[bool] arg),
1164
- # or `nil` to signal "decline" (wrong type / wrong value class).
1025
+ # Resolves the optional `exclude_end` argument for `range_new_lift`. Returns `false` (no
1026
+ # arg), `true`/`false` (Constant[bool] arg), or `nil` to signal "decline" (wrong type /
1027
+ # wrong value class).
1165
1028
  def range_new_excl(excl_type)
1166
1029
  case excl_type
1167
1030
  when nil then false
@@ -1170,11 +1033,10 @@ module Rigor
1170
1033
  end
1171
1034
  end
1172
1035
 
1173
- # `Set.new` / `Set.new(tuple_of_constants)` — folds to `Constant[Set]`
1174
- # when zero arguments are given or the single argument is a `Tuple`
1175
- # whose every element is a `Constant[T]`. Mirrors `SetFolding#fold_new`
1176
- # but lives here so the `:new` path in `try_meta_introspection` can
1177
- # reach it before the RBS tier answers `Nominal[Set]`.
1036
+ # `Set.new` / `Set.new(tuple_of_constants)` — folds to `Constant[Set]` when zero arguments
1037
+ # are given or the single argument is a `Tuple` whose every element is a `Constant[T]`.
1038
+ # Mirrors `SetFolding#fold_new` but lives here so the `:new` path in
1039
+ # `try_meta_introspection` can reach it before the RBS tier answers `Nominal[Set]`.
1178
1040
  def set_new_lift(class_name, arg_types)
1179
1041
  return nil unless class_name == "Set"
1180
1042
  return Type::Combinator.constant_of(::Set.new) if arg_types.empty?
@@ -1190,10 +1052,9 @@ module Rigor
1190
1052
  nil
1191
1053
  end
1192
1054
 
1193
- # `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to
1194
- # `Constant[Regexp]` when the pattern is a `Constant[String]`.
1195
- # Mirrors `RegexpFolding#fold_new` but lives here so the `:new` path
1196
- # in `try_meta_introspection` can reach it.
1055
+ # `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to `Constant[Regexp]` when
1056
+ # the pattern is a `Constant[String]`. Mirrors `RegexpFolding#fold_new` but lives here so
1057
+ # the `:new` path in `try_meta_introspection` can reach it.
1197
1058
  def regexp_new_lift(class_name, arg_types)
1198
1059
  return nil unless class_name == "Regexp"
1199
1060
  return nil if arg_types.empty? || arg_types.size > 2
@@ -1215,15 +1076,12 @@ module Rigor
1215
1076
  nil
1216
1077
  end
1217
1078
 
1218
- # `Date.new(y, m, d)` / `DateTime.new(y, m, d, h, min, s, off)`
1219
- # folds to `Constant[Date]` / `Constant[DateTime]` when every
1220
- # argument is a `Constant` carrying an Integer (or, for the
1221
- # `DateTime` offset / `start` slots, a Rational or String).
1222
- # `Date.new` carries no timezone, and `DateTime.new`'s offset
1223
- # defaults to UTC, so the literal is machine-independent.
1224
- # `Date.today` / `Date.parse` are not constructors here — they
1225
- # are non-deterministic / string-dependent and stay
1226
- # `Nominal[Date]`.
1079
+ # `Date.new(y, m, d)` / `DateTime.new(y, m, d, h, min, s, off)` — folds to `Constant[Date]`
1080
+ # / `Constant[DateTime]` when every argument is a `Constant` carrying an Integer (or, for
1081
+ # the `DateTime` offset / `start` slots, a Rational or String). `Date.new` carries no
1082
+ # timezone, and `DateTime.new`'s offset defaults to UTC, so the literal is
1083
+ # machine-independent. `Date.today` / `Date.parse` are not constructors here — they are
1084
+ # non-deterministic / string-dependent and stay `Nominal[Date]`.
1227
1085
  DATE_NEW_CLASSES = %w[Date DateTime].freeze
1228
1086
  private_constant :DATE_NEW_CLASSES
1229
1087
 
@@ -1256,18 +1114,14 @@ module Rigor
1256
1114
  nil
1257
1115
  end
1258
1116
 
1259
- # Returns the positional block parameter types declared by the
1260
- # receiving method's selected RBS overload, translated into
1261
- # `Rigor::Type`. Used by the StatementEvaluator's CallNode
1262
- # handler to bind block parameter names before evaluating the
1263
- # block body.
1117
+ # Returns the positional block parameter types declared by the receiving method's selected
1118
+ # RBS overload, translated into `Rigor::Type`. Used by the StatementEvaluator's CallNode
1119
+ # handler to bind block parameter names before evaluating the block body.
1264
1120
  #
1265
- # The probe is best-effort: it returns an empty array whenever
1266
- # the receiver, environment, method definition, or selected
1267
- # overload does not provide statically declared block parameter
1268
- # types. Callers MUST treat the empty array as "no information";
1269
- # the binder falls back to `Dynamic[Top]` for every parameter
1270
- # slot in that case.
1121
+ # The probe is best-effort: it returns an empty array whenever the receiver, environment,
1122
+ # method definition, or selected overload does not provide statically declared block
1123
+ # parameter types. Callers MUST treat the empty array as "no information"; the binder
1124
+ # falls back to `Dynamic[Top]` for every parameter slot in that case.
1271
1125
  #
1272
1126
  # @param receiver_type [Rigor::Type, nil]
1273
1127
  # @param method_name [Symbol]