rigortype 0.2.7 → 0.2.9

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 (433) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/core_overlay/csv.rbs +28 -0
  4. data/data/core_overlay/psych.rbs +22 -0
  5. data/docs/handbook/01-getting-started.md +9 -1
  6. data/docs/handbook/02-everyday-types.md +4 -1
  7. data/docs/handbook/08-understanding-errors.md +3 -3
  8. data/docs/manual/01-installation.md +1 -0
  9. data/docs/manual/02-cli-reference.md +16 -7
  10. data/docs/manual/04-diagnostics.md +7 -4
  11. data/docs/manual/06-baseline.md +5 -2
  12. data/docs/manual/07-plugins.md +1 -1
  13. data/docs/manual/14-rails-quickstart.md +4 -2
  14. data/docs/manual/15-type-protection-coverage.md +27 -4
  15. data/docs/manual/plugins/rigor-actionpack.md +1 -1
  16. data/docs/manual/plugins/rigor-activerecord.md +12 -5
  17. data/docs/manual/plugins/rigor-rails-routes.md +11 -0
  18. data/lib/rigor/analysis/baseline.rb +56 -96
  19. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  20. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  21. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  22. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  23. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  24. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  25. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  26. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  27. data/lib/rigor/analysis/check_rules.rb +174 -274
  28. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  29. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  30. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  31. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  32. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  33. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  34. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  35. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  36. data/lib/rigor/analysis/diagnostic.rb +48 -75
  37. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  38. data/lib/rigor/analysis/fact_store.rb +12 -22
  39. data/lib/rigor/analysis/incremental.rb +47 -63
  40. data/lib/rigor/analysis/incremental_session.rb +74 -100
  41. data/lib/rigor/analysis/project_scan.rb +12 -23
  42. data/lib/rigor/analysis/result.rb +3 -5
  43. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  44. data/lib/rigor/analysis/run_stats.rb +38 -61
  45. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  48. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  49. data/lib/rigor/analysis/runner.rb +220 -333
  50. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  51. data/lib/rigor/analysis/worker_session.rb +64 -102
  52. data/lib/rigor/ast/type_node.rb +6 -8
  53. data/lib/rigor/ast.rb +6 -10
  54. data/lib/rigor/bleeding_edge.rb +27 -35
  55. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  56. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  57. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  58. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  59. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  60. data/lib/rigor/cache/descriptor.rb +58 -94
  61. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  62. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  63. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  64. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  65. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  66. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  67. data/lib/rigor/cache/rbs_environment.rb +11 -19
  68. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  69. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  70. data/lib/rigor/cache/store.rb +248 -186
  71. data/lib/rigor/cli/annotate_command.rb +51 -88
  72. data/lib/rigor/cli/baseline_command.rb +10 -17
  73. data/lib/rigor/cli/check_command.rb +121 -199
  74. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  75. data/lib/rigor/cli/ci_detector.rb +9 -14
  76. data/lib/rigor/cli/command.rb +8 -14
  77. data/lib/rigor/cli/coverage_command.rb +62 -48
  78. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  79. data/lib/rigor/cli/coverage_report.rb +0 -2
  80. data/lib/rigor/cli/coverage_scan.rb +7 -11
  81. data/lib/rigor/cli/diagnostic_formats.rb +46 -62
  82. data/lib/rigor/cli/diff_command.rb +10 -20
  83. data/lib/rigor/cli/docs_command.rb +26 -40
  84. data/lib/rigor/cli/doctor_command.rb +8 -9
  85. data/lib/rigor/cli/explain_command.rb +4 -7
  86. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  87. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  88. data/lib/rigor/cli/lsp_command.rb +11 -19
  89. data/lib/rigor/cli/mcp_command.rb +4 -6
  90. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  91. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  92. data/lib/rigor/cli/options.rb +11 -19
  93. data/lib/rigor/cli/plugin_command.rb +18 -30
  94. data/lib/rigor/cli/plugins_command.rb +29 -51
  95. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  96. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  97. data/lib/rigor/cli/protection_fork_scan.rb +55 -0
  98. data/lib/rigor/cli/protection_renderer.rb +5 -7
  99. data/lib/rigor/cli/protection_report.rb +34 -18
  100. data/lib/rigor/cli/renderable.rb +4 -6
  101. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  102. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  103. data/lib/rigor/cli/skill_command.rb +30 -47
  104. data/lib/rigor/cli/skill_describe.rb +40 -64
  105. data/lib/rigor/cli/trace_command.rb +9 -16
  106. data/lib/rigor/cli/trace_renderer.rb +35 -51
  107. data/lib/rigor/cli/triage_command.rb +6 -10
  108. data/lib/rigor/cli/triage_renderer.rb +1 -2
  109. data/lib/rigor/cli/type_of_command.rb +14 -23
  110. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  111. data/lib/rigor/cli/type_scan_command.rb +6 -10
  112. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  113. data/lib/rigor/cli/type_scan_report.rb +2 -3
  114. data/lib/rigor/cli/upgrade_command.rb +2 -3
  115. data/lib/rigor/cli.rb +16 -28
  116. data/lib/rigor/config_audit.rb +35 -44
  117. data/lib/rigor/configuration/dependencies.rb +41 -70
  118. data/lib/rigor/configuration/severity_profile.rb +28 -42
  119. data/lib/rigor/configuration.rb +150 -241
  120. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  121. data/lib/rigor/environment/class_registry.rb +19 -26
  122. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  123. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  124. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  125. data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
  126. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  127. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  128. data/lib/rigor/environment/rbs_loader.rb +420 -518
  129. data/lib/rigor/environment/reflection.rb +28 -53
  130. data/lib/rigor/environment/reporters.rb +13 -24
  131. data/lib/rigor/environment.rb +225 -304
  132. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  133. data/lib/rigor/flow_contribution/element.rb +7 -12
  134. data/lib/rigor/flow_contribution/fact.rb +33 -57
  135. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  136. data/lib/rigor/flow_contribution/merger.rb +32 -47
  137. data/lib/rigor/flow_contribution.rb +37 -55
  138. data/lib/rigor/inference/acceptance.rb +133 -219
  139. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  140. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  141. data/lib/rigor/inference/budget_trace.rb +43 -63
  142. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  143. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  144. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  145. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  146. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  147. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  148. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  149. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  150. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  151. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  152. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  153. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  154. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  155. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  156. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  157. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  158. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  159. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  160. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  161. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  162. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  163. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  164. data/lib/rigor/inference/def_return_typer.rb +8 -14
  165. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  166. data/lib/rigor/inference/expression_typer.rb +794 -1154
  167. data/lib/rigor/inference/fallback.rb +8 -12
  168. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  169. data/lib/rigor/inference/flow_tracer.rb +25 -36
  170. data/lib/rigor/inference/fork_map.rb +87 -0
  171. data/lib/rigor/inference/hkt_body.rb +45 -68
  172. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  173. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  174. data/lib/rigor/inference/hkt_registry.rb +50 -76
  175. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  176. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  177. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  178. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  179. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  180. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  181. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  182. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  183. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  184. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  185. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  186. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  187. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  188. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  189. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  190. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  191. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  192. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  193. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  194. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  195. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  196. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  197. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  198. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  199. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  200. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  201. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  202. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  203. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  204. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  205. data/lib/rigor/inference/mutation_widening.rb +115 -158
  206. data/lib/rigor/inference/narrowing.rb +627 -727
  207. data/lib/rigor/inference/origin_lookup.rb +38 -0
  208. data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
  209. data/lib/rigor/inference/precision_scanner.rb +26 -35
  210. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  211. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  212. data/lib/rigor/inference/protection_scanner.rb +16 -18
  213. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  214. data/lib/rigor/inference/scope_indexer.rb +473 -821
  215. data/lib/rigor/inference/statement_evaluator.rb +754 -1114
  216. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  217. data/lib/rigor/inference/synthetic_method.rb +9 -16
  218. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  219. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  220. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  221. data/lib/rigor/language_server/buffer_table.rb +11 -18
  222. data/lib/rigor/language_server/completion_provider.rb +69 -116
  223. data/lib/rigor/language_server/debouncer.rb +16 -25
  224. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  225. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  226. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  227. data/lib/rigor/language_server/hover_provider.rb +8 -11
  228. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  229. data/lib/rigor/language_server/loop.rb +14 -19
  230. data/lib/rigor/language_server/project_context.rb +46 -74
  231. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  232. data/lib/rigor/language_server/server.rb +55 -83
  233. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  234. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  235. data/lib/rigor/language_server/uri.rb +8 -13
  236. data/lib/rigor/language_server.rb +4 -6
  237. data/lib/rigor/mcp/loop.rb +2 -3
  238. data/lib/rigor/mcp/server.rb +4 -7
  239. data/lib/rigor/mcp.rb +3 -6
  240. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  241. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  242. data/lib/rigor/plugin/base.rb +335 -518
  243. data/lib/rigor/plugin/blueprint.rb +14 -23
  244. data/lib/rigor/plugin/box.rb +18 -29
  245. data/lib/rigor/plugin/fact_store.rb +16 -26
  246. data/lib/rigor/plugin/inflector.rb +37 -53
  247. data/lib/rigor/plugin/io_boundary.rb +33 -56
  248. data/lib/rigor/plugin/isolation.rb +42 -55
  249. data/lib/rigor/plugin/load_error.rb +10 -15
  250. data/lib/rigor/plugin/loader.rb +30 -49
  251. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  252. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  253. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  254. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  255. data/lib/rigor/plugin/macro.rb +10 -15
  256. data/lib/rigor/plugin/manifest.rb +85 -144
  257. data/lib/rigor/plugin/node_context.rb +14 -22
  258. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  259. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  260. data/lib/rigor/plugin/registry.rb +132 -205
  261. data/lib/rigor/plugin/services.rb +21 -33
  262. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  263. data/lib/rigor/plugin/trust_policy.rb +24 -38
  264. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  265. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  266. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  267. data/lib/rigor/protection/mutator.rb +50 -70
  268. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  269. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  270. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  271. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  272. data/lib/rigor/rbs_extended.rb +107 -197
  273. data/lib/rigor/reflection.rb +68 -86
  274. data/lib/rigor/scope/discovery_index.rb +14 -19
  275. data/lib/rigor/scope.rb +255 -310
  276. data/lib/rigor/sig_gen/classification.rb +6 -10
  277. data/lib/rigor/sig_gen/generator.rb +197 -323
  278. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  279. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  280. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  281. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  282. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  283. data/lib/rigor/sig_gen/renderer.rb +7 -13
  284. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  285. data/lib/rigor/sig_gen/write_result.rb +8 -16
  286. data/lib/rigor/sig_gen/writer.rb +95 -174
  287. data/lib/rigor/sig_gen.rb +3 -6
  288. data/lib/rigor/signature_path_audit.rb +24 -30
  289. data/lib/rigor/source/constant_path.rb +10 -14
  290. data/lib/rigor/source/literals.rb +31 -45
  291. data/lib/rigor/source/node_locator.rb +9 -11
  292. data/lib/rigor/source/node_walker.rb +9 -13
  293. data/lib/rigor/source.rb +3 -4
  294. data/lib/rigor/testing.rb +16 -20
  295. data/lib/rigor/triage/catalogue.rb +38 -62
  296. data/lib/rigor/triage.rb +31 -52
  297. data/lib/rigor/trinary.rb +9 -13
  298. data/lib/rigor/type/acceptance_router.rb +4 -6
  299. data/lib/rigor/type/accepts_result.rb +10 -14
  300. data/lib/rigor/type/app.rb +19 -27
  301. data/lib/rigor/type/bot.rb +4 -6
  302. data/lib/rigor/type/bound_method.rb +10 -15
  303. data/lib/rigor/type/combinator.rb +165 -257
  304. data/lib/rigor/type/constant.rb +23 -34
  305. data/lib/rigor/type/data_class.rb +10 -15
  306. data/lib/rigor/type/data_instance.rb +14 -20
  307. data/lib/rigor/type/difference.rb +21 -32
  308. data/lib/rigor/type/dynamic.rb +3 -5
  309. data/lib/rigor/type/hash_shape.rb +32 -18
  310. data/lib/rigor/type/integer_range.rb +11 -16
  311. data/lib/rigor/type/intersection.rb +27 -42
  312. data/lib/rigor/type/nominal.rb +10 -15
  313. data/lib/rigor/type/plain_lattice.rb +9 -13
  314. data/lib/rigor/type/refined.rb +67 -114
  315. data/lib/rigor/type/singleton.rb +4 -6
  316. data/lib/rigor/type/struct_class.rb +11 -16
  317. data/lib/rigor/type/struct_instance.rb +15 -21
  318. data/lib/rigor/type/tuple.rb +14 -19
  319. data/lib/rigor/type/union.rb +30 -42
  320. data/lib/rigor/type_node/generic.rb +14 -26
  321. data/lib/rigor/type_node/identifier.rb +12 -19
  322. data/lib/rigor/type_node.rb +3 -12
  323. data/lib/rigor/value_semantics.rb +16 -21
  324. data/lib/rigor/version.rb +1 -1
  325. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
  337. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  340. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
  348. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  350. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  351. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  353. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  354. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  355. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  356. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
  357. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  358. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  359. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  361. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  363. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  365. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  366. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  367. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  368. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  369. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  372. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  373. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  376. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  377. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  378. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  379. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  380. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  381. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  385. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  386. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  389. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  390. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
  398. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
  399. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  400. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  406. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  408. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  409. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  411. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  416. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  417. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  426. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  427. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  428. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  429. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  430. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  431. data/sig/rigor/environment.rbs +1 -0
  432. data/sig/rigor/scope.rbs +6 -0
  433. metadata +9 -1
@@ -8,6 +8,7 @@ require_relative "environment/reflection"
8
8
  require_relative "environment/reporters"
9
9
  require_relative "environment/hkt_registry_holder"
10
10
  require_relative "environment/constant_type_cache_holder"
11
+ require_relative "environment/missing_gem_constant_index"
11
12
  require_relative "environment/bundle_sig_discovery"
12
13
  require_relative "environment/lockfile_resolver"
13
14
  require_relative "environment/rbs_collection_discovery"
@@ -21,36 +22,28 @@ require_relative "type_node/name_scope"
21
22
  require_relative "type_node/resolver_chain"
22
23
 
23
24
  module Rigor
24
- # The engine's view of the type universe outside the current scope.
25
- # Slice 1 exposed only the class registry; Slice 4 added the RBS loader,
26
- # which threads through ExpressionTyper and MethodDispatcher to type
27
- # constant references and method calls that the literal-typer and
28
- # constant-folding tiers cannot answer.
25
+ # The engine's view of the type universe outside the current scope. Slice 1 exposed only the class
26
+ # registry; Slice 4 added the RBS loader, which threads through ExpressionTyper and MethodDispatcher to
27
+ # type constant references and method calls that the literal-typer and constant-folding tiers cannot
28
+ # answer.
29
29
  #
30
30
  # See docs/internal-spec/inference-engine.md for the binding contract.
31
31
  class Environment # rubocop:disable Metrics/ClassLength
32
32
  DEFAULT_PROJECT_SIG_DIR = "sig"
33
33
  private_constant :DEFAULT_PROJECT_SIG_DIR
34
34
 
35
- # Slice A stdlib expansion. Stdlib libraries that
36
- # `Environment.for_project` loads on top of RBS core unless
37
- # the caller passes an explicit `libraries:` array. Each
38
- # entry MUST be a stdlib library name accepted by
39
- # `RBS::EnvironmentLoader#has_library?`; unknown libraries
40
- # MUST fail-soft (`RbsLoader#build_env` already filters
41
- # through `has_library?`). The default set covers the common
42
- # stdlib surface a Ruby program is likely to import
43
- # (`pathname`, `optparse`, `json`, `yaml`, `fileutils`,
44
- # `tempfile`, `uri`, `logger`, `date`) plus the analyzer-
45
- # adjacent gems shipping their own RBS in this bundle
46
- # (`prism`, `rbs`). On hosts where one of these libraries is
47
- # not installed, the loader silently drops it.
35
+ # Slice A stdlib expansion. Stdlib libraries that `Environment.for_project` loads on top of RBS core
36
+ # unless the caller passes an explicit `libraries:` array. Each entry MUST be a stdlib library name
37
+ # accepted by `RBS::EnvironmentLoader#has_library?`; unknown libraries MUST fail-soft
38
+ # (`RbsLoader#build_env` already filters through `has_library?`). The default set covers the common
39
+ # stdlib surface a Ruby program is likely to import (`pathname`, `optparse`, `json`, `yaml`, `fileutils`,
40
+ # `tempfile`, `uri`, `logger`, `date`) plus the analyzer-adjacent gems shipping their own RBS in this
41
+ # bundle (`prism`, `rbs`). On hosts where one of these libraries is not installed, the loader silently
42
+ # drops it.
48
43
  #
49
- # Callers MAY add to the default by passing
50
- # `libraries: %w[csv ...]`; the explicit list is appended to
51
- # `DEFAULT_LIBRARIES` and de-duplicated. Callers that need
52
- # a strictly RBS-core view MUST construct an `RbsLoader`
53
- # directly instead of going through `for_project`.
44
+ # Callers MAY add to the default by passing `libraries: %w[csv ...]`; the explicit list is appended to
45
+ # `DEFAULT_LIBRARIES` and de-duplicated. Callers that need a strictly RBS-core view MUST construct an
46
+ # `RbsLoader` directly instead of going through `for_project`.
54
47
  DEFAULT_LIBRARIES = %w[
55
48
  pathname optparse json yaml fileutils tempfile tmpdir
56
49
  stringio forwardable digest securerandom
@@ -65,11 +58,10 @@ module Rigor
65
58
  prism rbs
66
59
  ].freeze
67
60
 
68
- # ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id
69
- # that ships the SAME core-ext RBS. When that plugin is loaded the
70
- # auto-overlay for the gem stands down, so the two never both
71
- # declare the methods (which would raise a duplicate-declaration
72
- # error). Keyed on the gem name `RbsCoverageReport` reports.
61
+ # ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id that ships the SAME core-ext RBS. When
62
+ # that plugin is loaded the auto-overlay for the gem stands down, so the two never both declare the
63
+ # methods (which would raise a duplicate-declaration error). Keyed on the gem name `RbsCoverageReport`
64
+ # reports.
73
65
  GEM_OVERLAY_PLUGIN_IDS = { "activesupport" => "activesupport-core-ext" }.freeze
74
66
 
75
67
  attr_reader :class_registry, :rbs_loader, :plugin_registry, :dependency_source_index,
@@ -77,39 +69,30 @@ module Rigor
77
69
  :synthetic_method_index, :project_patched_methods
78
70
 
79
71
  # @param class_registry [Rigor::Environment::ClassRegistry]
80
- # @param rbs_loader [Rigor::Environment::RbsLoader, nil] when nil the
81
- # environment is "RBS-blind"; useful in tests that want to assert
82
- # how the engine behaves without RBS data. The default Environment
83
- # wires the shared core loader, which is itself lazy: requesting an
84
- # environment instance does NOT load RBS until a method or class
85
- # query actually consults the loader.
86
- # @param plugin_registry [Rigor::Plugin::Registry, nil] v0.1.1
87
- # Track 2 slice 7. The per-run plugin registry the
88
- # inference engine consults at call sites for plugin
89
- # `dynamic_return` rules. When nil (the
90
- # default), no plugin-level return-type contribution
91
- # participates — useful for tests, the `Environment.default`
92
- # facade, and analyses that don't load plugins.
93
- # @param dependency_source_index [Rigor::Analysis::DependencySourceInference::Index, nil]
94
- # ADR-10 slice 2b-ii. The per-run index of opt-in gem
95
- # sources the dispatcher consults BELOW RBS dispatch.
96
- # When nil (the default), no dep-source contribution
97
- # participates and the dispatcher tier is a no-op.
72
+ # @param rbs_loader [Rigor::Environment::RbsLoader, nil] when nil the environment is "RBS-blind"; useful
73
+ # in tests that want to assert how the engine behaves without RBS data. The default Environment wires
74
+ # the shared core loader, which is itself lazy: requesting an environment instance does NOT load RBS
75
+ # until a method or class query actually consults the loader.
76
+ # @param plugin_registry [Rigor::Plugin::Registry, nil] v0.1.1 Track 2 slice 7. The per-run plugin
77
+ # registry the inference engine consults at call sites for plugin `dynamic_return` rules. When nil
78
+ # (the default), no plugin-level return-type contribution participates — useful for tests, the
79
+ # `Environment.default` facade, and analyses that don't load plugins.
80
+ # @param dependency_source_index [Rigor::Analysis::DependencySourceInference::Index, nil] ADR-10 slice
81
+ # 2b-ii. The per-run index of opt-in gem sources the dispatcher consults BELOW RBS dispatch. When nil
82
+ # (the default), no dep-source contribution participates and the dispatcher tier is a no-op.
98
83
  def initialize(class_registry: ClassRegistry.default, rbs_loader: nil, # rubocop:disable Metrics/ParameterLists
99
84
  plugin_registry: nil, dependency_source_index: nil,
100
85
  rbs_extended_reporter: nil, boundary_cross_reporter: nil,
101
86
  source_rbs_synthesis_reporter: nil,
102
87
  synthetic_method_index: nil, project_patched_methods: nil,
103
- hkt_registry: nil)
88
+ hkt_registry: nil, missing_rbs_gems: [], missing_rbs_bundle_path: nil)
104
89
  @class_registry = class_registry
105
90
  @rbs_loader = rbs_loader
106
91
  @plugin_registry = plugin_registry
107
92
  @dependency_source_index = dependency_source_index
108
- # ADR-pending — reporters live in a mutable container so
109
- # long-lived integrations (LSP `ProjectContext`) can swap
110
- # them per `Runner.run` without rebuilding the env. The
111
- # existing `#rbs_extended_reporter` / `#boundary_cross_reporter`
112
- # accessors below preserve the public lookup shape.
93
+ # ADR-pending — reporters live in a mutable container so long-lived integrations (LSP `ProjectContext`)
94
+ # can swap them per `Runner.run` without rebuilding the env. The existing `#rbs_extended_reporter` /
95
+ # `#boundary_cross_reporter` accessors below preserve the public lookup shape.
113
96
  @reporters = Reporters.new(
114
97
  rbs_extended: rbs_extended_reporter,
115
98
  boundary_cross: boundary_cross_reporter,
@@ -117,34 +100,30 @@ module Rigor
117
100
  )
118
101
  @synthetic_method_index = synthetic_method_index || Inference::SyntheticMethodIndex::EMPTY
119
102
  @project_patched_methods = project_patched_methods || Inference::ProjectPatchedMethods::EMPTY
120
- # ADR-20 slice 2c + 2e — the per-env HKT registry
121
- # consulted by the reducer when resolving `Type::App`
122
- # carriers. Defaults to {Inference::HktRegistry::EMPTY};
123
- # the {.default} / {.for_project} class methods seed it
124
- # with the bundled builtins (`json::value`, …) plus any
125
- # `%a{rigor:v1:hkt_register / hkt_define}` annotations
126
- # the RBS loader exposes. The hkt_registry getter
127
- # (defined below) MEMOIZES the result of merging the
128
- # base with the RBS scan so the scan is paid at most
129
- # once per Environment lifetime — and only when first
130
- # consulted, leaving fast paths like `rigor check
131
- # --cache-stats --no-stats` from doing the RBS env
132
- # build at all.
103
+ # ADR-20 slice 2c + 2e — the per-env HKT registry consulted by the reducer when resolving `Type::App`
104
+ # carriers. Defaults to {Inference::HktRegistry::EMPTY}; the {.default} / {.for_project} class methods
105
+ # seed it with the bundled builtins (`json::value`, …) plus any `%a{rigor:v1:hkt_register /
106
+ # hkt_define}` annotations the RBS loader exposes. The hkt_registry getter (defined below) MEMOIZES
107
+ # the result of merging the base with the RBS scan so the scan is paid at most once per Environment
108
+ # lifetime and only when first consulted, leaving fast paths like `rigor check --cache-stats
109
+ # --no-stats` from doing the RBS env build at all.
133
110
  @hkt_registry_base = hkt_registry || Inference::HktRegistry::EMPTY
134
111
  @hkt_registry_holder = HktRegistryHolder.new
135
112
  @constant_type_cache = ConstantTypeCacheHolder.new
113
+ # ADR-82 WD9 — `[gem_name, version]` pairs for the locked gems with no resolvable RBS. The
114
+ # root-constant ownership index over them is built lazily (first unresolved constant read) so runs
115
+ # whose constants all resolve never pay the entry-file scan.
116
+ @missing_rbs_gems = missing_rbs_gems.freeze
117
+ @missing_rbs_bundle_path = missing_rbs_bundle_path
118
+ @missing_rbs_gem_constants_holder = HktRegistryHolder.new
136
119
  @name_scope = build_name_scope
137
120
  freeze
138
121
  end
139
122
 
140
- # ADR-20 slices 2e + 6 — lazy HKT registry getter.
141
- # Merge order on first call: builtins (base) plugin
142
- # manifest aggregation RBS env scan. Last-write-wins on
143
- # URI collisions so user-authored `.rbs` overlays beat
144
- # plugin entries, which beat the bundled JSON_VALUE.
145
- # Memoised; single-threaded use only (under the Ractor
146
- # pool path each worker has its own Environment so
147
- # cross-worker mutation is impossible; the LSP
123
+ # ADR-20 slices 2e + 6 — lazy HKT registry getter. Merge order on first call: builtins (base) ← plugin
124
+ # manifest aggregation ← RBS env scan. Last-write-wins on URI collisions so user-authored `.rbs` overlays
125
+ # beat plugin entries, which beat the bundled JSON_VALUE. Memoised; single-threaded use only (under the
126
+ # Ractor pool path each worker has its own Environment so cross-worker mutation is impossible; the LSP
148
127
  # single-publish-at-a-time invariant serialises here).
149
128
  def hkt_registry
150
129
  @hkt_registry_holder.fetch do
@@ -161,10 +140,24 @@ module Rigor
161
140
  end
162
141
  end
163
142
 
164
- # Backwards-compatible reporter accessors every existing
165
- # consumer (rbs_extended, method_dispatcher) calls these. The
166
- # frozen `@reporters` container is mutable for slot reassignment
167
- # via {#attach_reporters!} below.
143
+ # ADR-82 WD9the gem name owning `root_constant_name`, when that gem is locked in the project's
144
+ # Gemfile.lock, ships no resolvable RBS, and its entry file declares the constant at top level. Nil for
145
+ # everything else the caller then keeps the generic provenance cause. The index is built once, lazily,
146
+ # from RubyGems metadata + a Prism parse of each gem's entry file; no gem code runs (see
147
+ # {MissingGemConstantIndex}). Under the fork pool a worker that never meets an unresolved constant never
148
+ # builds it.
149
+ def missing_rbs_gem_owner(root_constant_name)
150
+ return nil if @missing_rbs_gems.empty?
151
+
152
+ index = @missing_rbs_gem_constants_holder.fetch do
153
+ MissingGemConstantIndex.build(@missing_rbs_gems, bundle_path: @missing_rbs_bundle_path)
154
+ end
155
+ index[root_constant_name.to_s]
156
+ end
157
+
158
+ # Backwards-compatible reporter accessors — every existing consumer (rbs_extended, method_dispatcher)
159
+ # calls these. The frozen `@reporters` container is mutable for slot reassignment via
160
+ # {#attach_reporters!} below.
168
161
  def rbs_extended_reporter
169
162
  @reporters.rbs_extended
170
163
  end
@@ -173,29 +166,22 @@ module Rigor
173
166
  @reporters.boundary_cross
174
167
  end
175
168
 
176
- # ADR-32 WD6 — the per-run accumulator for synthesizer
177
- # failure events. `Environment.for_project` records
178
- # `[:error, message]` returns from a plugin's
179
- # `source_rbs_synthesizer` here so the Runner can emit
180
- # `source-rbs-synthesis-failed` `:info` diagnostics after
181
- # analysis completes. Nil when no plugin contributes a
182
- # synthesizer.
169
+ # ADR-32 WD6 — the per-run accumulator for synthesizer failure events. `Environment.for_project` records
170
+ # `[:error, message]` returns from a plugin's `source_rbs_synthesizer` here so the Runner can emit
171
+ # `source-rbs-synthesis-failed` `:info` diagnostics after analysis completes. Nil when no plugin
172
+ # contributes a synthesizer.
183
173
  def source_rbs_synthesis_reporter
184
174
  @reporters.source_rbs_synthesis
185
175
  end
186
176
 
187
- # Replaces the env's per-run reporter slots. Intended for
188
- # long-lived integrations (LSP `ProjectContext`) that share one
189
- # Environment instance across many `Runner.run` calls: each call
190
- # attaches its own fresh reporter pair so per-call diagnostic
191
- # events stay scoped to that call rather than accumulating
192
- # across publishes.
177
+ # Replaces the env's per-run reporter slots. Intended for long-lived integrations (LSP `ProjectContext`)
178
+ # that share one Environment instance across many `Runner.run` calls: each call attaches its own fresh
179
+ # reporter pair so per-call diagnostic events stay scoped to that call rather than accumulating across
180
+ # publishes.
193
181
  #
194
- # Single-threaded use only. Concurrent publishes against one
195
- # Environment must serialise the LSP `Server` debouncer +
196
- # synchronized writer already enforces this for the editor
197
- # path. The Ractor pool path builds a per-worker Environment
198
- # and does not reach this surface.
182
+ # Single-threaded use only. Concurrent publishes against one Environment must serialise — the LSP
183
+ # `Server` debouncer + synchronized writer already enforces this for the editor path. The Ractor pool
184
+ # path builds a per-worker Environment and does not reach this surface.
199
185
  def attach_reporters!(rbs_extended_reporter:, boundary_cross_reporter:)
200
186
  @reporters.rbs_extended = rbs_extended_reporter
201
187
  @reporters.boundary_cross = boundary_cross_reporter
@@ -210,26 +196,20 @@ module Rigor
210
196
  ).freeze
211
197
  end
212
198
 
213
- # Builds an Environment that consults the project's local
214
- # signatures and any opt-in stdlib libraries on top of RBS core.
199
+ # Builds an Environment that consults the project's local signatures and any opt-in stdlib libraries on
200
+ # top of RBS core.
215
201
  #
216
- # @param root [String, Pathname] project root used to auto-detect
217
- # the default signature path. Defaults to the current working
218
- # directory.
219
- # @param libraries [Array<String, Symbol>] additional stdlib
220
- # libraries to load on top of {DEFAULT_LIBRARIES}. The
221
- # final list is the union of the two, de-duplicated while
222
- # preserving order. Pass an empty array (the default) to
223
- # load only the defaults.
224
- # @param signature_paths [Array<String, Pathname>, nil] explicit
225
- # list of `sig/`-style directories. When `nil` (the default),
226
- # the canonical project layout `<root>/sig` is used if it
227
- # exists, otherwise no signature path is loaded.
228
- # @param cache_store [Rigor::Cache::Store, nil] persistent cache
229
- # threaded into the underlying {Environment::RbsLoader} so
230
- # constant lookups (and, in later v0.0.9 slices, other
231
- # reflection artefacts) consult the cache. Pass `nil` (the
232
- # default) to skip caching for this environment.
202
+ # @param root [String, Pathname] project root used to auto-detect the default signature path. Defaults
203
+ # to the current working directory.
204
+ # @param libraries [Array<String, Symbol>] additional stdlib libraries to load on top of
205
+ # {DEFAULT_LIBRARIES}. The final list is the union of the two, de-duplicated while preserving order.
206
+ # Pass an empty array (the default) to load only the defaults.
207
+ # @param signature_paths [Array<String, Pathname>, nil] explicit list of `sig/`-style directories. When
208
+ # `nil` (the default), the canonical project layout `<root>/sig` is used if it exists, otherwise no
209
+ # signature path is loaded.
210
+ # @param cache_store [Rigor::Cache::Store, nil] persistent cache threaded into the underlying
211
+ # {Environment::RbsLoader} so constant lookups (and, in later v0.0.9 slices, other reflection
212
+ # artefacts) consult the cache. Pass `nil` (the default) to skip caching for this environment.
233
213
  # @return [Rigor::Environment]
234
214
  # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
235
215
  def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil,
@@ -242,21 +222,15 @@ module Rigor
242
222
  synthetic_method_index: nil, project_patched_methods: nil,
243
223
  source_files: [])
244
224
  resolved_paths = signature_paths || default_signature_paths(root)
245
- # O4 MVP — append per-gem `sig/` directories discovered
246
- # under the target project's bundler install root. Empty
247
- # array when neither an explicit path nor auto-detection
248
- # finds a bundle. Order: user `signature_paths:` win first
249
- # (semantic precedence inside `RbsLoader.build_env_for`);
250
- # gem-shipped sigs append last so user overrides stay
251
- # authoritative.
225
+ # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
226
+ # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
227
+ # `signature_paths:` win first (semantic precedence inside `RbsLoader.build_env_for`); gem-shipped
228
+ # sigs append last so user overrides stay authoritative.
252
229
  #
253
- # O4 Layer 3 — when a Gemfile.lock is available (explicit
254
- # `bundler_lockfile:` or auto-detected next to the project
255
- # root), use the locked gem set to filter the discovered
256
- # `sig/` directories. Stale gems in the bundle install
257
- # tree (out-of-band installs, version drift after a
258
- # `bundle update`) are silently dropped so only gems the
259
- # project actually declares contribute RBS.
230
+ # O4 Layer 3 — when a Gemfile.lock is available (explicit `bundler_lockfile:` or auto-detected next
231
+ # to the project root), use the locked gem set to filter the discovered `sig/` directories. Stale
232
+ # gems in the bundle install tree (out-of-band installs, version drift after a `bundle update`) are
233
+ # silently dropped so only gems the project actually declares contribute RBS.
260
234
  locked = LockfileResolver.locked_gems(
261
235
  lockfile_path: bundler_lockfile,
262
236
  project_root: root,
@@ -268,19 +242,19 @@ module Rigor
268
242
  auto_detect: bundler_auto_detect,
269
243
  locked_gems: locked.empty? ? nil : locked
270
244
  ).map(&:to_s)
271
- # O4 Layer 3 slice 2 when `rbs collection install`
272
- # has been run for the target project, parse the
273
- # resulting `rbs_collection.lock.yaml` and feed each
274
- # gem's `<collection_path>/<name>/<version>/` directory
275
- # into `signature_paths:`. Stdlib-typed entries are
276
- # skipped (already covered by `DEFAULT_LIBRARIES`), as
277
- # are gems whose RBS rigor already loads from another
278
- # source stdlib-extracted default gems (`cgi`,
279
- # `logger`, …, shipped by the collection under a `git`
280
- # source) and the `data/vendored_gem_sigs/` bundle
281
- # (`redis`, `nokogiri`, `pg`, ). `skip_gem_names:`
282
- # passes both sets so the collection copy doesn't
283
- # double-declare against rigor's bundled RBS (the
245
+ # ADR-82 WD9 the resolved bundle root, so the missing-gem constant index reads each RBS-less gem's
246
+ # entry file from the TARGET's bundle (not rigor's own — see `MissingGemConstantIndex`). Resolved
247
+ # once here; passed through to the lazy index build.
248
+ bundle_root = BundleSigDiscovery.resolve_bundle_path(
249
+ bundle_path: bundler_bundle_path, project_root: root, auto_detect: bundler_auto_detect
250
+ )&.to_s
251
+ # O4 Layer 3 slice 2 when `rbs collection install` has been run for the target project, parse the
252
+ # resulting `rbs_collection.lock.yaml` and feed each gem's `<collection_path>/<name>/<version>/`
253
+ # directory into `signature_paths:`. Stdlib-typed entries are skipped (already covered by
254
+ # `DEFAULT_LIBRARIES`), as are gems whose RBS rigor already loads from another source —
255
+ # stdlib-extracted default gems (`cgi`, `logger`, …, shipped by the collection under a `git` source)
256
+ # and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
257
+ # both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
284
258
  # `RBS::DuplicatedDeclarationError` hazard).
285
259
  merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s)).uniq
286
260
  skip_gem_names = merged_libraries + RbsLoader.vendored_gem_names
@@ -290,48 +264,33 @@ module Rigor
290
264
  auto_detect: rbs_collection_auto_detect,
291
265
  skip_gem_names: skip_gem_names
292
266
  ).map(&:to_s)
293
- # ADR-25 — RBS signature directories contributed by loaded
294
- # plugins via their manifest `signature_paths:`. Resolved
295
- # to absolute dirs by `Plugin::Base#signature_paths`;
296
- # additive, ranked below the user's explicit
297
- # `signature_paths:` and above the opportunistic bundle /
298
- # collection discovery. A duplicate-declaration conflict
299
- # degrades through the same O7 failure-memo path.
267
+ # ADR-25 — RBS signature directories contributed by loaded plugins via their manifest
268
+ # `signature_paths:`. Resolved to absolute dirs by `Plugin::Base#signature_paths`; additive, ranked
269
+ # below the user's explicit `signature_paths:` and above the opportunistic bundle / collection
270
+ # discovery. A duplicate-declaration conflict degrades through the same O7 failure-memo path.
300
271
  plugin_sig_paths = plugin_registry ? plugin_registry.signature_paths.map(&:to_s) : []
301
- # ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each
302
- # locked gem that ships no RBS through any resolution path
303
- # (`:missing`) and has a Rigor-bundled overlay, load that
304
- # overlay so its core-class extensions resolve (e.g.
305
- # `Integer#minutes` on a Rails project) turning a systematic
306
- # false `call.undefined-method` into a no-op while a project
307
- # WITHOUT the gem still sees the genuine diagnostic. Appended
308
- # last so any RBS the project already supplies wins, and skipped
309
- # for a gem whose opt-in plugin twin is loaded (no duplicate
310
- # declaration). The paths ride in `loader_signature_paths`, so
311
- # the env cache descriptor digests them for free.
312
- overlay_paths = gem_overlay_paths(
313
- locked: locked, default_libraries: merged_libraries,
314
- bundle_sig_paths: gem_sig_paths, rbs_collection_paths: collection_paths,
315
- plugin_registry: plugin_registry
272
+ # ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem that ships no RBS through any
273
+ # resolution path (`:missing`) and has a Rigor-bundled overlay, load that overlay so its core-class
274
+ # extensions resolve (e.g. `Integer#minutes` on a Rails project) turning a systematic false
275
+ # `call.undefined-method` into a no-op while a project WITHOUT the gem still sees the genuine
276
+ # diagnostic. Appended last so any RBS the project already supplies wins, and skipped for a gem whose
277
+ # opt-in plugin twin is loaded (no duplicate declaration). The paths ride in
278
+ # `loader_signature_paths`, so the env cache descriptor digests them for free.
279
+ missing_gems, overlay_paths = missing_gems_and_overlay_paths(
280
+ locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
281
+ rbs_collection_paths: collection_paths, plugin_registry: plugin_registry
316
282
  )
317
283
  loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
318
284
  collection_paths + overlay_paths
319
- # ADR-32 WD4 + WD5 — invoke each loaded plugin's
320
- # `source_rbs_synthesizer` once per project source file
321
- # and collect non-nil `[filename, rbs_source]` pairs.
322
- # The synthesizer-emitting plugin (currently only
323
- # `rigor-rbs-inline`) is responsible for its own
324
- # fail-soft on parse errors per WD6; this loop only
325
- # filters `nil` returns.
285
+ # ADR-32 WD4 + WD5 — invoke each loaded plugin's `source_rbs_synthesizer` once per project source
286
+ # file and collect non-nil `[filename, rbs_source]` pairs. The synthesizer-emitting plugin (currently
287
+ # only `rigor-rbs-inline`) is responsible for its own fail-soft on parse errors per WD6; this loop
288
+ # only filters `nil` returns.
326
289
  #
327
- # When a `cache_store` is supplied, each synthesizer
328
- # invocation is memoised per
329
- # `(file path + content SHA, plugin id + version + config_hash)`
330
- # WD5's cache key so a second run with unchanged
331
- # source skips the rbs-inline parse cost. The empty
332
- # string is the sentinel for "no contribution" so the
333
- # Store (which treats `nil` as cache miss) can persist
334
- # the no-contribution decision too.
290
+ # When a `cache_store` is supplied, each synthesizer invocation is memoised per `(file path + content
291
+ # SHA, plugin id + version + config_hash)` — WD5's cache key — so a second run with unchanged source
292
+ # skips the rbs-inline parse cost. The empty string is the sentinel for "no contribution" so the
293
+ # Store (which treats `nil` as cache miss) can persist the no-contribution decision too.
335
294
  virtual_rbs = collect_virtual_rbs(plugin_registry, source_files, cache_store,
336
295
  source_rbs_synthesis_reporter)
337
296
  loader = RbsLoader.new(
@@ -340,15 +299,11 @@ module Rigor
340
299
  cache_store: cache_store,
341
300
  virtual_rbs: virtual_rbs
342
301
  )
343
- # ADR-20 slice 2c + 2e — seed hkt_registry with the
344
- # bundled builtins. The Environment's `#hkt_registry`
345
- # getter then LAZILY merges in the RBS env scan on
346
- # first call so fast paths that don't consult HKT
347
- # (e.g. `rigor check --cache-stats --no-stats`) don't
348
- # pay the eager env-build cost up front. URI
349
- # collisions let the user-authored overlay win over
350
- # the bundled builtin (last-write-wins per ADR-20
351
- # OQ3 tentative).
302
+ # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
303
+ # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
304
+ # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
305
+ # up front. URI collisions let the user-authored overlay win over the bundled builtin
306
+ # (last-write-wins per ADR-20 OQ3 tentative).
352
307
  new(
353
308
  rbs_loader: loader,
354
309
  plugin_registry: plugin_registry,
@@ -358,7 +313,9 @@ module Rigor
358
313
  source_rbs_synthesis_reporter: source_rbs_synthesis_reporter,
359
314
  synthetic_method_index: synthetic_method_index,
360
315
  project_patched_methods: project_patched_methods,
361
- hkt_registry: Builtins::HktBuiltins.registry
316
+ hkt_registry: Builtins::HktBuiltins.registry,
317
+ missing_rbs_gems: missing_gems,
318
+ missing_rbs_bundle_path: bundle_root
362
319
  )
363
320
  end
364
321
  # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
@@ -370,51 +327,48 @@ module Rigor
370
327
  sig.directory? ? [sig] : []
371
328
  end
372
329
 
373
- # ADR-72 resolve the bundled RBS overlay directories to load for
374
- # this project. A gem is eligible when it is locked in the
375
- # Gemfile.lock, classified `:missing` by {RbsCoverageReport}
376
- # (no RBS through default-library / vendored / bundle-`sig/` /
377
- # rbs-collection), and its conflicting opt-in plugin (if any) is
378
- # not loaded. Returns `[Pathname]`, deterministically ordered, or
379
- # `[]` when no lockfile / no eligible gem.
380
- def gem_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
381
- rbs_collection_paths:, plugin_registry:)
382
- return [] if locked.empty?
383
-
384
- missing = RbsCoverageReport.classify(
330
+ # The `:missing` classification (locked gems with no RBS through any resolution path), computed once
331
+ # and consumed twice: as `[gem_name, version]` pairs for the ADR-82 WD9 constant-ownership index, and
332
+ # as the eligible-gem set for the ADR-72 overlay resolution. Returns `[pairs, overlay_paths]`.
333
+ def missing_gems_and_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
334
+ rbs_collection_paths:, plugin_registry:)
335
+ return [[], []] if locked.empty?
336
+
337
+ rows = RbsCoverageReport.classify(
385
338
  locked_gems: locked, default_libraries: default_libraries,
386
339
  bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
387
- ).select { |row| row.source == :missing }.map(&:gem_name)
340
+ ).select { |row| row.source == :missing }
341
+ overlays = gem_overlay_paths(missing_gem_names: rows.map(&:gem_name), plugin_registry: plugin_registry)
342
+ [rows.map { |row| [row.gem_name, row.version] }, overlays]
343
+ end
344
+
345
+ # ADR-72 — resolve the bundled RBS overlay directories to load for this project. A gem is eligible when
346
+ # it is classified `:missing` (see {missing_rbs_gem_rows}) and its conflicting opt-in plugin (if any)
347
+ # is not loaded. Returns `[Pathname]`, deterministically ordered, or `[]` when no eligible gem.
348
+ def gem_overlay_paths(missing_gem_names:, plugin_registry:)
349
+ return [] if missing_gem_names.empty?
388
350
 
389
351
  loaded_ids = plugin_registry ? plugin_registry.ids.to_set : Set.new
390
- eligible = missing.reject do |gem_name|
352
+ eligible = missing_gem_names.reject do |gem_name|
391
353
  plugin_id = GEM_OVERLAY_PLUGIN_IDS[gem_name]
392
354
  plugin_id && loaded_ids.include?(plugin_id)
393
355
  end.sort
394
356
  RbsLoader.gem_overlay_sig_paths(eligible)
395
357
  end
396
358
 
397
- # ADR-32 WD4 + WD5 — for each project source file, invoke
398
- # every plugin-registered synthesizer once and collect
399
- # non-nil returns. The returned array is `[[virtual_filename,
400
- # rbs_source], ...]`; the loader threads it through to
401
- # `RbsLoader.new(virtual_rbs: ...)`.
359
+ # ADR-32 WD4 + WD5 — for each project source file, invoke every plugin-registered synthesizer once and
360
+ # collect non-nil returns. The returned array is `[[virtual_filename, rbs_source], ...]`; the loader
361
+ # threads it through to `RbsLoader.new(virtual_rbs: ...)`.
402
362
  #
403
- # `virtual_filename` is the source file path prefixed with
404
- # the plugin id so RBS parse errors point back to the
405
- # contributing plugin in their diagnostic location string.
363
+ # `virtual_filename` is the source file path prefixed with the plugin id so RBS parse errors point back
364
+ # to the contributing plugin in their diagnostic location string.
406
365
  #
407
- # When no plugin declares a synthesizer (the common case),
408
- # the registry's `source_rbs_synthesizers` is empty and
409
- # this method short-circuits to `[]` without walking the
410
- # file list.
366
+ # When no plugin declares a synthesizer (the common case), the registry's `source_rbs_synthesizers` is
367
+ # empty and this method short-circuits to `[]` without walking the file list.
411
368
  #
412
- # WD5 — when `cache_store` is supplied, each (file, plugin)
413
- # synthesizer call is memoised through `Cache::Store`. The
414
- # cache key composes the file's content SHA with the
415
- # plugin's `PluginEntry` (id + version + config_hash) so a
416
- # config change or content change invalidates the entry
417
- # automatically.
369
+ # WD5 — when `cache_store` is supplied, each (file, plugin) synthesizer call is memoised through
370
+ # `Cache::Store`. The cache key composes the file's content SHA with the plugin's `PluginEntry` (id +
371
+ # version + config_hash) so a config change or content change invalidates the entry automatically.
418
372
  def collect_virtual_rbs(plugin_registry, source_files, cache_store, reporter)
419
373
  return [] if plugin_registry.nil?
420
374
 
@@ -436,14 +390,10 @@ module Rigor
436
390
  result
437
391
  end
438
392
 
439
- # ADR-32 WD5 — cache wrapper around a single (plugin,
440
- # file) invocation. The cache stores the empty string
441
- # `""` as the "no contribution" sentinel because
442
- # `Cache::Store` treats `nil` as a cache miss. Error
443
- # tuples are stored as the canonical
444
- # `[:error, message_string]` Array so the same wrapper
445
- # short-circuits subsequent runs against unchanged broken
446
- # input.
393
+ # ADR-32 WD5 — cache wrapper around a single (plugin, file) invocation. The cache stores the empty
394
+ # string `""` as the "no contribution" sentinel because `Cache::Store` treats `nil` as a cache miss.
395
+ # Error tuples are stored as the canonical `[:error, message_string]` Array so the same wrapper
396
+ # short-circuits subsequent runs against unchanged broken input.
447
397
  def synthesizer_output_for(plugin, callable, path, cache_store)
448
398
  return invoke_synthesizer_safely(callable, path) if cache_store.nil?
449
399
  return invoke_synthesizer_safely(callable, path) unless File.file?(path)
@@ -456,17 +406,14 @@ module Rigor
456
406
  ) { invoke_synthesizer_safely(callable, path) || "" }
457
407
  end
458
408
 
459
- # ADR-32 WD6 — route a synthesizer return value through
460
- # the per-run failure reporter. The synthesizer's contract
461
- # (declared in `plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb`)
462
- # admits three return shapes:
409
+ # ADR-32 WD6 — route a synthesizer return value through the per-run failure reporter. The
410
+ # synthesizer's contract (declared in
411
+ # `plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb`) admits three return shapes:
463
412
  # - `String` (non-empty) → successful RBS source
464
413
  # - `nil` / `""` → no contribution
465
414
  # - `[:error, message]` → parse failed
466
- # The error tuple is converted into a reporter entry +
467
- # treated as "no contribution" so the analysis pipeline
468
- # continues. Reporter is `nil` for callers that don't care
469
- # (legacy Environment.new, tests).
415
+ # The error tuple is converted into a reporter entry + treated as "no contribution" so the analysis
416
+ # pipeline continues. Reporter is `nil` for callers that don't care (legacy Environment.new, tests).
470
417
  def interpret_synthesizer_outcome(outcome, plugin, path, reporter)
471
418
  return outcome unless outcome.is_a?(Array) && outcome[0] == :error
472
419
 
@@ -495,8 +442,7 @@ module Rigor
495
442
  def synthesizer_input_digest(path)
496
443
  Digest::SHA256.hexdigest(File.binread(path))
497
444
  rescue ::SystemCallError
498
- # Unreadable file → key on the path alone; the
499
- # synthesizer's File.file?/File.read will see the same
445
+ # Unreadable file → key on the path alone; the synthesizer's File.file?/File.read will see the same
500
446
  # failure and return nil.
501
447
  Digest::SHA256.hexdigest(path.to_s)
502
448
  end
@@ -504,23 +450,19 @@ module Rigor
504
450
  def invoke_synthesizer_safely(callable, path)
505
451
  callable.call(path.to_s)
506
452
  rescue StandardError
507
- # WD6 fail-soft — a synthesizer that raises does NOT crash
508
- # analysis. Unlike the `[:error, msg]` return path (which
509
- # the runner surfaces as `source-rbs-synthesis-failed`),
510
- # an unhandled raise is swallowed silently; the
511
- # unexamined-raise channel is deliberately silent per WD6.
453
+ # WD6 fail-soft — a synthesizer that raises does NOT crash analysis. Unlike the `[:error, msg]`
454
+ # return path (which the runner surfaces as `source-rbs-synthesis-failed`), an unhandled raise is
455
+ # swallowed silently; the unexamined-raise channel is deliberately silent per WD6.
512
456
  nil
513
457
  end
514
458
  end
515
459
 
516
- # Resolves a constant name to a Rigor::Type::Nominal (the *instance*
517
- # type carrier). Consults the static class registry first (cheap,
518
- # hardcoded), then falls back to the RBS loader. Returns nil when
519
- # the name is unknown to both.
460
+ # Resolves a constant name to a Rigor::Type::Nominal (the *instance* type carrier). Consults the static
461
+ # class registry first (cheap, hardcoded), then falls back to the RBS loader. Returns nil when the name
462
+ # is unknown to both.
520
463
  #
521
- # NOTE: This is the construction helper for "an instance of class
522
- # `Foo`". For "the class object `Foo` itself" (the value of the
523
- # constant), use {#singleton_for_name} instead.
464
+ # NOTE: This is the construction helper for "an instance of class `Foo`". For "the class object `Foo`
465
+ # itself" (the value of the constant), use {#singleton_for_name} instead.
524
466
  def nominal_for_name(name)
525
467
  registered = class_registry.nominal_for_name(name)
526
468
  return registered if registered
@@ -528,36 +470,29 @@ module Rigor
528
470
  class_known_in_rbs?(name) ? Type::Combinator.nominal_of(name.to_s) : nil
529
471
  end
530
472
 
531
- # Resolves a constant name to a Rigor::Type::Singleton (the *class
532
- # object* carrier). The expression `Foo` evaluates to the class
533
- # object, whose RBS type is `singleton(Foo)` -- this method is the
534
- # corresponding Rigor construction helper.
473
+ # Resolves a constant name to a Rigor::Type::Singleton (the *class object* carrier). The expression `Foo`
474
+ # evaluates to the class object, whose RBS type is `singleton(Foo)` -- this method is the corresponding
475
+ # Rigor construction helper.
535
476
  #
536
- # The lookup uses the same registry/RBS chain as {#nominal_for_name}
537
- # so a class is either known to both queries or to neither.
477
+ # The lookup uses the same registry/RBS chain as {#nominal_for_name} so a class is either known to both
478
+ # queries or to neither.
538
479
  def singleton_for_name(name)
539
480
  return nil unless class_known?(name)
540
481
 
541
482
  Type::Combinator.singleton_of(name.to_s)
542
483
  end
543
484
 
544
- # Slice A constant-value lookup. Returns the translated
545
- # `Rigor::Type` for an RBS-declared **non-class** constant
546
- # (`Rigor::Analysis::FactStore::BUCKETS: Array[Symbol]`,
547
- # `Rigor::Configuration::DEFAULT_PATH: String`, ...) or `nil`
548
- # when no RBS constant declaration covers `name`. This is the
549
- # value-bearing counterpart of {#singleton_for_name}, which
550
- # only resolves names that name a class or module. Callers
551
- # that need to type a `Prism::ConstantReadNode`/
552
- # `Prism::ConstantPathNode` MUST consult {#singleton_for_name}
553
- # first and fall through to this query when the constant is
554
- # not a class.
485
+ # Slice A constant-value lookup. Returns the translated `Rigor::Type` for an RBS-declared **non-class**
486
+ # constant (`Rigor::Analysis::FactStore::BUCKETS: Array[Symbol]`, `Rigor::Configuration::DEFAULT_PATH:
487
+ # String`, ...) or `nil` when no RBS constant declaration covers `name`. This is the value-bearing
488
+ # counterpart of {#singleton_for_name}, which only resolves names that name a class or module. Callers
489
+ # that need to type a `Prism::ConstantReadNode`/ `Prism::ConstantPathNode` MUST consult
490
+ # {#singleton_for_name} first and fall through to this query when the constant is not a class.
555
491
  def constant_for_name(name)
556
492
  return nil if rbs_loader.nil?
557
493
 
558
- # Pure function of `name` for this Environment's lifetime — the
559
- # refinement table and the RBS constant table are both fixed so
560
- # memoise across the lexical-candidate ladder's heavy name reuse.
494
+ # Pure function of `name` for this Environment's lifetime — the refinement table and the RBS constant
495
+ # table are both fixed so memoise across the lexical-candidate ladder's heavy name reuse.
561
496
  key = name.to_s
562
497
  @constant_type_cache.fetch(key) do
563
498
  Builtins::PredefinedConstantRefinements.lookup(key) ||
@@ -565,50 +500,39 @@ module Rigor
565
500
  end
566
501
  end
567
502
 
568
- # Returns true when the constant name is known to either the static
569
- # registry or the RBS loader. Useful for callers that only need a
570
- # presence check without materialising a type carrier.
503
+ # Returns true when the constant name is known to either the static registry or the RBS loader. Useful
504
+ # for callers that only need a presence check without materialising a type carrier.
571
505
  def class_known?(name)
572
506
  return true if class_registry.nominal_for_name(name)
573
507
  return true if class_known_in_rbs?(name)
574
508
 
575
- # ADR-36 nested-class emission — a variant subclass the
576
- # substrate synthesised (e.g. `Shape::Circle` from a
577
- # `variants do variant Circle, Float end` block) is a real
578
- # class for resolution purposes even though no RBS / source
579
- # declares it.
509
+ # ADR-36 nested-class emission — a variant subclass the substrate synthesised (e.g. `Shape::Circle`
510
+ # from a `variants do variant Circle, Float end` block) is a real class for resolution purposes even
511
+ # though no RBS / source declares it.
580
512
  @synthetic_method_index&.knows_class?(name) || false
581
513
  end
582
514
 
583
- # ADR-15 Phase 2b — returns the loader's read-only,
584
- # `Ractor.shareable?` query surface as a frozen
585
- # {Environment::Reflection}. Built lazily on first
586
- # access; subsequent calls return the same instance.
587
- # Returns `nil` when the environment carries no RBS
588
- # loader (test-only `Environment.new` without
515
+ # ADR-15 Phase 2b — returns the loader's read-only, `Ractor.shareable?` query surface as a frozen
516
+ # {Environment::Reflection}. Built lazily on first access; subsequent calls return the same instance.
517
+ # Returns `nil` when the environment carries no RBS loader (test-only `Environment.new` without
589
518
  # `rbs_loader:`).
590
519
  def reflection
591
520
  @rbs_loader&.reflection
592
521
  end
593
522
 
594
- # Returns true when the RBS environment carries the named
595
- # declaration as a Module (not a Class). Used by the
596
- # `user_class_fallback_receiver` tier to detect a module-mixin
597
- # receiver (e.g. `PP::ObjectMixin`) so the dispatcher can route
598
- # unresolved method calls through the `Nominal[Object]`
599
- # fallback — every concrete includer of M honours Kernel /
600
- # Object instance methods through its own ancestor chain.
523
+ # Returns true when the RBS environment carries the named declaration as a Module (not a Class). Used by
524
+ # the `user_class_fallback_receiver` tier to detect a module-mixin receiver (e.g. `PP::ObjectMixin`) so
525
+ # the dispatcher can route unresolved method calls through the `Nominal[Object]` fallback every
526
+ # concrete includer of M honours Kernel / Object instance methods through its own ancestor chain.
601
527
  def rbs_module?(name)
602
528
  return false unless rbs_loader
603
529
 
604
530
  rbs_loader.rbs_module?(name)
605
531
  end
606
532
 
607
- # Compares two class/module names using analyzer-owned class data.
608
- # Returns `:equal`, `:subclass`, `:superclass`, `:disjoint`, or
609
- # `:unknown`. The static registry handles built-ins cheaply; the RBS
610
- # loader handles project/stdlib classes without relying on host Ruby
611
- # constants being loaded.
533
+ # Compares two class/module names using analyzer-owned class data. Returns `:equal`, `:subclass`,
534
+ # `:superclass`, `:disjoint`, or `:unknown`. The static registry handles built-ins cheaply; the RBS
535
+ # loader handles project/stdlib classes without relying on host Ruby constants being loaded.
612
536
  def class_ordering(lhs, rhs)
613
537
  lhs = normalize_class_name(lhs)
614
538
  rhs = normalize_class_name(rhs)
@@ -634,14 +558,11 @@ module Rigor
634
558
  name.to_s.delete_prefix("::")
635
559
  end
636
560
 
637
- # ADR-13 slice 3b — composes the per-run plugin-supplied
638
- # {Rigor::TypeNode::ResolverChain} into a single
639
- # {Rigor::TypeNode::NameScope} that the RBS::Extended
640
- # directive parser threads down to the
641
- # {Rigor::Builtins::ImportedRefinements::Resolver}. Returns
642
- # `nil` when no plugin contributes a type-node resolver so
643
- # the parser short-circuits the chain consultation and
644
- # behaves bit-for-bit like the v0.1.0 → v0.1.3 default.
561
+ # ADR-13 slice 3b — composes the per-run plugin-supplied {Rigor::TypeNode::ResolverChain} into a single
562
+ # {Rigor::TypeNode::NameScope} that the RBS::Extended directive parser threads down to the
563
+ # {Rigor::Builtins::ImportedRefinements::Resolver}. Returns `nil` when no plugin contributes a type-node
564
+ # resolver so the parser short-circuits the chain consultation and behaves bit-for-bit like the v0.1.0 →
565
+ # v0.1.3 default.
645
566
  def build_name_scope
646
567
  return nil if @plugin_registry.nil? || @plugin_registry.empty?
647
568