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
@@ -16,30 +16,21 @@ module Rigor
16
16
  module SigGen
17
17
  # Core generator for `rigor sig-gen` (ADR-14 slice 1 — MVP).
18
18
  #
19
- # Walks every `.rb` file under the input paths, builds a
20
- # per-node scope index via {Rigor::Inference::ScopeIndexer},
21
- # finds every `Prism::DefNode` whose enclosing class is
22
- # nameable, types the body's last expression to derive an
23
- # inferred return, looks up the project's existing RBS
24
- # declaration (if any), and emits one {MethodCandidate} per
25
- # def.
19
+ # Walks every `.rb` file under the input paths, builds a per-node scope index via
20
+ # {Rigor::Inference::ScopeIndexer}, finds every `Prism::DefNode` whose enclosing class is nameable, types the
21
+ # body's last expression to derive an inferred return, looks up the project's existing RBS declaration (if
22
+ # any), and emits one {MethodCandidate} per def.
26
23
  #
27
24
  # The MVP keeps the scope deliberately narrow:
28
- # - Only instance methods inside a `class` / `module` body
29
- # are considered. Top-level / DSL-block / singleton defs
30
- # are skipped (`sig.skipped.complex-shape`).
31
- # - Parameter signatures are hard-coded to `untyped` per
32
- # ADR-14 § "Robustness principle compliance" clause 2;
25
+ # - Only instance methods inside a `class` / `module` body are considered. Top-level / DSL-block / singleton
26
+ # defs are skipped (`sig.skipped.complex-shape`).
27
+ # - Parameter signatures are hard-coded to `untyped` per ADR-14 § "Robustness principle compliance" clause 2;
33
28
  # `--params=observed` arrives in slice 3.
34
- # - Optional / rest / keyword / block params disqualify the
35
- # def (`sig.skipped.complex-shape`).
36
- # - A `Dynamic[top]` inferred return becomes
37
- # `sig.skipped.untyped-return` emitting `untyped` would
38
- # obscure rather than help.
39
- # - Tighter-return detection compares the RBS-erased
40
- # spellings only when the existing declared return
41
- # strictly accepts the inferred one (acceptance check
42
- # under the engine's current `:gradual` mode; ADR-14
29
+ # - Optional / rest / keyword / block params disqualify the def (`sig.skipped.complex-shape`).
30
+ # - A `Dynamic[top]` inferred return becomes `sig.skipped.untyped-return` — emitting `untyped` would obscure
31
+ # rather than help.
32
+ # - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
33
+ # strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
43
34
  # reserves the eventual `:strict` mode).
44
35
  class Generator # rubocop:disable Metrics/ClassLength
45
36
  # @param configuration [Rigor::Configuration]
@@ -54,24 +45,18 @@ module Rigor
54
45
  @paths = paths
55
46
  @observations = normalize_observations(observations)
56
47
  @include_private = include_private
57
- # Per-file scratch state. `analyse_file` resets each
58
- # one to a fresh container for every file walked so
59
- # candidates from one file don't leak into another;
60
- # initialising empty here gives downstream consumers
61
- # (`build_candidate`, `method_def_prefix`) a never-nil
62
- # invariant without per-call-site defensive guards.
48
+ # Per-file scratch state. `analyse_file` resets each one to a fresh container for every file walked so
49
+ # candidates from one file don't leak into another; initialising empty here gives downstream consumers
50
+ # (`build_candidate`, `method_def_prefix`) a never-nil invariant without per-call-site defensive guards.
63
51
  @namespace_kinds = {}
64
52
  @module_function_methods = Set.new
65
53
  @class_shells = Set.new
66
54
  @class_superclasses = {}
67
55
  end
68
56
 
69
- # Lifts legacy plain-`Array[Type]` observation entries
70
- # into {ObservedCall} carriers. Specs from the slice-3
71
- # generation predate the carrier and pass observations
72
- # as `{ [class, method] => [[type1, type2], ...] }`;
73
- # the wrapper keeps those passing while internal code
74
- # always sees the new shape.
57
+ # Lifts legacy plain-`Array[Type]` observation entries into {ObservedCall} carriers. Specs from the
58
+ # slice-3 generation predate the carrier and pass observations as `{ [class, method] => [[type1, type2],
59
+ # ...] }`; the wrapper keeps those passing while internal code always sees the new shape.
75
60
  def normalize_observations(map)
76
61
  return map if map.empty?
77
62
 
@@ -120,11 +105,9 @@ module Rigor
120
105
  @class_superclasses = {}
121
106
  defs = collect_method_definitions(parse_result.value)
122
107
  candidates_from_defs = defs.filter_map do |def_node, class_name, kind|
123
- # An analyzer bug typing one def's body must cost only that
124
- # def's candidate, never the whole `rigor sig-gen` run. The
125
- # `check` path recovers each *file* this way
126
- # (worker_session.rb); sig-gen recovers per-def so the rest of
127
- # the file's candidates still emit.
108
+ # An analyzer bug typing one def's body must cost only that def's candidate, never the whole
109
+ # `rigor sig-gen` run. The `check` path recovers each *file* this way (worker_session.rb); sig-gen
110
+ # recovers per-def so the rest of the file's candidates still emit.
128
111
 
129
112
  classify_def(path, def_node, class_name, kind, scope_index)
130
113
  rescue StandardError
@@ -134,29 +117,20 @@ module Rigor
134
117
  candidates_from_defs + collect_attr_candidates(parse_result.value, path, scope_index, obs_ivar_map)
135
118
  end
136
119
 
137
- # Walks the AST collecting `(def_node, class_name, kind)`
138
- # tuples for every `def` Rigor can re-type. Slice 1
139
- # covered instance `def foo` methods inside a nameable
140
- # `class` / `module` body. Slice 4 extends this to
141
- # singleton-side methods via `def self.foo` and
142
- # `class << self; def foo; end`; top-level / DSL-block
143
- # defs still degrade silently (no nameable receiver).
120
+ # Walks the AST collecting `(def_node, class_name, kind)` tuples for every `def` Rigor can re-type. Slice 1
121
+ # covered instance `def foo` methods inside a nameable `class` / `module` body. Slice 4 extends this to
122
+ # singleton-side methods via `def self.foo` and `class << self; def foo; end`; top-level / DSL-block defs
123
+ # still degrade silently (no nameable receiver).
144
124
  #
145
- # ADR-14 gap-#3 follow-up tracks two extra pieces during
146
- # the same walk so the Writer can emit kind-correct RBS
147
- # without guessing:
125
+ # ADR-14 gap-#3 follow-up tracks two extra pieces during the same walk so the Writer can emit kind-correct
126
+ # RBS without guessing:
148
127
  #
149
- # - `@namespace_kinds[qualified_name]` records whether
150
- # each segment came from `class Foo` (`:class`) or
151
- # `module Foo` (`:module`). Used by the writer's
152
- # `wrap_in_modules` step to emit the right keyword for
128
+ # - `@namespace_kinds[qualified_name]` records whether each segment came from `class Foo` (`:class`) or
129
+ # `module Foo` (`:module`). Used by the writer's `wrap_in_modules` step to emit the right keyword for
153
130
  # each intermediate segment AND the leaf.
154
- # - `@module_function_methods` records `(class_name,
155
- # method_name)` pairs where a `module_function` (no
156
- # args) call preceded the `def` inside a module body.
157
- # The renderer emits `def self?.name` for these, the
158
- # RBS spelling that matches the dual instance +
159
- # singleton dispatch the runtime produces.
131
+ # - `@module_function_methods` records `(class_name, method_name)` pairs where a `module_function` (no
132
+ # args) call preceded the `def` inside a module body. The renderer emits `def self?.name` for these, the
133
+ # RBS spelling that matches the dual instance + singleton dispatch the runtime produces.
160
134
  def collect_method_definitions(root)
161
135
  out = []
162
136
  walk_defs(root, [], false, false, out)
@@ -179,8 +153,7 @@ module Rigor
179
153
  return
180
154
  when Prism::ConstantWriteNode
181
155
  register_data_struct_shell(node, prefix)
182
- # fall through to recurse into the RHS so a trailing
183
- # `do ... end` block carrying defs is still walked.
156
+ # fall through to recurse into the RHS so a trailing `do ... end` block carrying defs is still walked.
184
157
  when Prism::StatementsNode
185
158
  walk_statements(node, prefix, in_singleton_class, module_function_active, out)
186
159
  return
@@ -202,20 +175,15 @@ module Rigor
202
175
  true
203
176
  end
204
177
 
205
- # ADR-14: a generated subclass declaration MUST carry its
206
- # superclass, or the sidecar `sig/` misrepresents the class
207
- # (inherited members vanish receiver dispatch degrades to
208
- # `Dynamic`) and, worse, a nested reference to an inherited
209
- # type re-declares the class as a bare namespace on the RBS
210
- # side and can collapse the whole env (the 2026-07-04 redmine
211
- # `GitAdapter < AbstractAdapter` crash). Only a plain constant
212
- # superclass is emittable: `class X < Foo` / `class X <
213
- # Foo::Bar` yields the source token verbatim (RBS resolves it
214
- # relative to the emitted namespace, matching Ruby's lexical
215
- # scope). A computed superclass (`Struct.new`, `Data.define`,
216
- # `Class.new`, any `CallNode`) is left unrecorded — those flow
217
- # through the {#register_data_struct_shell} shell path or are
218
- # simply un-representable, and guessing would misfold.
178
+ # ADR-14: a generated subclass declaration MUST carry its superclass, or the sidecar `sig/` misrepresents
179
+ # the class (inherited members vanish → receiver dispatch degrades to `Dynamic`) and, worse, a nested
180
+ # reference to an inherited type re-declares the class as a bare namespace on the RBS side and can
181
+ # collapse the whole env (the 2026-07-04 redmine `GitAdapter < AbstractAdapter` crash). Only a plain
182
+ # constant superclass is emittable: `class X < Foo` / `class X < Foo::Bar` yields the source token verbatim
183
+ # (RBS resolves it relative to the emitted namespace, matching Ruby's lexical scope). A computed
184
+ # superclass (`Struct.new`, `Data.define`, `Class.new`, any `CallNode`) is left unrecorded — those flow
185
+ # through the {#register_data_struct_shell} shell path or are simply un-representable, and guessing would
186
+ # misfold.
219
187
  def record_superclass(node, full)
220
188
  return unless node.is_a?(Prism::ClassNode)
221
189
 
@@ -223,24 +191,16 @@ module Rigor
223
191
  @class_superclasses[full] = superclass if superclass
224
192
  end
225
193
 
226
- # ADR-14 gap-#3 (e): recognises
227
- # `Const = Data.define(...)` and
228
- # `Const = Struct.new(...)` as class declarations.
229
- # The runtime side stamps a brand-new anonymous class
230
- # at the RHS and binds it to `Const`, so the generated
231
- # RBS needs an explicit `class Const` declaration even
232
- # though no `class Const ... end` block appears in
233
- # source. Without it, references to `Const` in return
234
- # types fail to resolve under Steep (the canonical case
235
- # is `GemResolver::Resolved | GemResolver::Unresolvable`
236
- # where `Unresolvable = Data.define(:gem_name, :reason)`).
194
+ # ADR-14 gap-#3 (e): recognises `Const = Data.define(...)` and `Const = Struct.new(...)` as class
195
+ # declarations. The runtime side stamps a brand-new anonymous class at the RHS and binds it to `Const`, so
196
+ # the generated RBS needs an explicit `class Const` declaration even though no `class Const ... end` block
197
+ # appears in source. Without it, references to `Const` in return types fail to resolve under Steep (the
198
+ # canonical case is `GemResolver::Resolved | GemResolver::Unresolvable` where
199
+ # `Unresolvable = Data.define(:gem_name, :reason)`).
237
200
  #
238
- # The walker records the fully-qualified constant name
239
- # in `@class_shells` (carried through to every
240
- # candidate so the writer's tree-builder picks it up)
241
- # AND in `@namespace_kinds` so the leaf's `class`
242
- # keyword wins over the intermediate-segment `module`
243
- # default.
201
+ # The walker records the fully-qualified constant name in `@class_shells` (carried through to every
202
+ # candidate so the writer's tree-builder picks it up) AND in `@namespace_kinds` so the leaf's `class`
203
+ # keyword wins over the intermediate-segment `module` default.
244
204
  def register_data_struct_shell(node, prefix)
245
205
  return unless data_or_struct_call?(node.value)
246
206
 
@@ -264,16 +224,11 @@ module Rigor
264
224
  DATA_STRUCT_SHELL_HEADS[receiver.name.to_s] == value.name
265
225
  end
266
226
 
267
- # Module / class bodies are walked through the
268
- # `walk_statements` path so `module_function` (no-args)
269
- # encountered as one statement applies to every
270
- # subsequent sibling def in the same body. The
271
- # directive is module-scoped semantically classes
272
- # inherit `module_function` via `Module`'s ancestor
273
- # chain but don't honour it the same way at runtime, so
274
- # tracking is only meaningful inside `ModuleNode`
275
- # bodies. Generator emits `def self?.name` for the
276
- # marked defs.
227
+ # Module / class bodies are walked through the `walk_statements` path so `module_function` (no-args)
228
+ # encountered as one statement applies to every subsequent sibling def in the same body. The directive is
229
+ # module-scoped semantically classes inherit `module_function` via `Module`'s ancestor chain but don't
230
+ # honour it the same way at runtime, so tracking is only meaningful inside `ModuleNode` bodies. Generator
231
+ # emits `def self?.name` for the marked defs.
277
232
  def walk_namespace_body(namespace_node, prefix, out)
278
233
  return if namespace_node.body.nil?
279
234
 
@@ -306,12 +261,9 @@ module Rigor
306
261
  out << [node, class_name, kind]
307
262
  end
308
263
 
309
- # Wraps `MethodCandidate.new` so every candidate carries
310
- # the per-file `@namespace_kinds` map AND the
311
- # `@class_shells` set the Writer's nested-syntax
312
- # emission consults both to pick `module` vs `class`
313
- # for each segment and to emit empty
314
- # `Const = Data.define(...)` declarations.
264
+ # Wraps `MethodCandidate.new` so every candidate carries the per-file `@namespace_kinds` map AND the
265
+ # `@class_shells` set — the Writer's nested-syntax emission consults both to pick `module` vs `class` for
266
+ # each segment and to emit empty `Const = Data.define(...)` declarations.
315
267
  def build_candidate(**)
316
268
  MethodCandidate.new(
317
269
  namespace_kinds: @namespace_kinds,
@@ -321,10 +273,8 @@ module Rigor
321
273
  )
322
274
  end
323
275
 
324
- # Returns "def self." (kind: :singleton),
325
- # "def self?." (instance method declared inside a
326
- # `module_function` region — both instance + singleton
327
- # dispatch at runtime), or "def " (plain instance).
276
+ # Returns "def self." (kind: :singleton), "def self?." (instance method declared inside a
277
+ # `module_function` region — both instance + singleton dispatch at runtime), or "def " (plain instance).
328
278
  def method_def_prefix(class_name, method_name, kind)
329
279
  return "def self." if kind == :singleton
330
280
  return "def self?." if @module_function_methods.include?([class_name, method_name])
@@ -332,16 +282,11 @@ module Rigor
332
282
  "def "
333
283
  end
334
284
 
335
- # Slice-4 follow-up surfaced by the Rigor self-dogfood:
336
- # most `lib/rigor/cli/*` files have a small public
337
- # surface (`run`) and many private helpers. Emitting the
338
- # private helpers into a `sig/` file is noise private
339
- # methods are implementation details, not part of the
340
- # type contract downstream consumers (Steep, IDE, gem
341
- # users) read. The default now skips private and
342
- # protected methods; the `:include_private` flag
343
- # restores the slice-4 behaviour for callers that want
344
- # every method.
285
+ # Slice-4 follow-up surfaced by the Rigor self-dogfood: most `lib/rigor/cli/*` files have a small public
286
+ # surface (`run`) and many private helpers. Emitting the private helpers into a `sig/` file is noise —
287
+ # private methods are implementation details, not part of the type contract downstream consumers (Steep,
288
+ # IDE, gem users) read. The default now skips private and protected methods; the `:include_private` flag
289
+ # restores the slice-4 behaviour for callers that want every method.
345
290
  def visibility_excludes?(def_node, class_name, kind, scope_index)
346
291
  return false if kind == :singleton
347
292
  return false if @include_private
@@ -353,34 +298,25 @@ module Rigor
353
298
  %i[private protected].include?(visibility)
354
299
  end
355
300
 
356
- # Ruby's `initialize` return value is never meaningful;
357
- # the conventional RBS spelling is `() -> void`. The
358
- # body-typing path types the last expression (often an
359
- # ivar assignment whose rvalue happens to be `[]` /
301
+ # Ruby's `initialize` return value is never meaningful; the conventional RBS spelling is `() -> void`. The
302
+ # body-typing path types the last expression (often an ivar assignment whose rvalue happens to be `[]` /
360
303
  # `{}`), which produces nonsense return types.
361
304
  #
362
- # Skipping `initialize` entirely is correct ONLY for
363
- # default constructors — the `Object#initialize: () -> void`
364
- # RBS fallback then covers the lookup. When the class
365
- # has a non-trivial `initialize(argv:, ...)` (i.e. any
366
- # parameter), partial-class sigs trip Steep's
367
- # method-parameter-mismatch check: Steep sees the
368
- # runtime `def initialize(...)` and compares against
369
- # the inherited `Object#initialize: () -> void`. The
370
- # mismatch surfaces a `Ruby::MethodParameterMismatch`
371
- # warning even when `rigor check` itself is clean.
305
+ # Skipping `initialize` entirely is correct ONLY for default constructors — the
306
+ # `Object#initialize: () -> void` RBS fallback then covers the lookup. When the class has a non-trivial
307
+ # `initialize(argv:, ...)` (i.e. any parameter), partial-class sigs trip Steep's method-parameter-mismatch
308
+ # check: Steep sees the runtime `def initialize(...)` and compares against the inherited
309
+ # `Object#initialize: () -> void`. The mismatch surfaces a `Ruby::MethodParameterMismatch` warning even
310
+ # when `rigor check` itself is clean.
372
311
  #
373
- # Returning `nil` here causes `classify_def` to skip
374
- # emission; returning `:emit_stub` causes
375
- # `initialize_stub_candidate` to emit a permissive
376
- # `(<param shape>) -> void` stub matching the
377
- # runtime parameter list.
312
+ # Returning `nil` here causes `classify_def` to skip emission; returning `:emit_stub` causes
313
+ # `initialize_stub_candidate` to emit a permissive `(<param shape>) -> void` stub matching the runtime
314
+ # parameter list.
378
315
  def initialize_excludes?(def_node, kind)
379
316
  return false unless kind == :instance
380
317
  return false unless def_node.name == :initialize
381
318
 
382
- # Default constructor with no params — skip; the
383
- # Object#initialize RBS fallback covers it.
319
+ # Default constructor with no params — skip; the Object#initialize RBS fallback covers it.
384
320
  params = def_node.parameters
385
321
  params.nil? || trivial_initialize_params?(params)
386
322
  end
@@ -397,21 +333,18 @@ module Rigor
397
333
  kind == :instance && def_node.name == :initialize && !trivial_initialize_params?(def_node.parameters)
398
334
  end
399
335
 
400
- # Emits `def initialize: (<shape>) -> void`. The return
401
- # is always `void` because Ruby's `initialize` return
402
- # value is never meaningful. The parameter list mirrors
403
- # the runtime shape (required / optional / rest /
404
- # keyword / keyword-rest / block).
336
+ # Emits `def initialize: (<shape>) -> void`. The return is always `void` because Ruby's `initialize`
337
+ # return value is never meaningful. The parameter list mirrors the runtime shape (required / optional /
338
+ # rest / keyword / keyword-rest / block).
405
339
  #
406
- # When `--params=observed` populates `@observations` for
407
- # `[class_name, :initialize]` (via the
408
- # `ObservationCollector`'s `.new` → `:initialize`
409
- # routing), positional and keyword arg types come from
410
- # the per-position / per-keyword union of observed
411
- # types; otherwise every position keeps `untyped` per
412
- # ADR-5 clause 2.
340
+ # When `--params=observed` populates `@observations` for `[class_name, :initialize]` (via the
341
+ # `ObservationCollector`'s `.new` → `:initialize` routing), positional and keyword arg types come from the
342
+ # per-position / per-keyword union of observed types; otherwise every position keeps `untyped` per ADR-5
343
+ # clause 2.
413
344
  def initialize_stub_candidate(path, def_node, class_name)
414
- rbs = "def initialize: (#{render_initialize_param_list(def_node.parameters, class_name)}) -> void"
345
+ params = def_node.parameters
346
+ rbs = "def initialize: (#{render_initialize_param_list(params, class_name)})" \
347
+ "#{block_signature_suffix(params)} -> void"
415
348
  build_candidate(
416
349
  path: path, class_name: class_name, method_name: :initialize,
417
350
  kind: :instance, classification: Classification::NEW_METHOD,
@@ -438,14 +371,25 @@ module Rigor
438
371
  parts << "*untyped" if params.rest
439
372
  params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
440
373
  parts << "**untyped" if params.keyword_rest
441
- parts << "?{ (?) -> void }" if params.block
442
374
  parts.join(", ")
443
375
  end
444
376
 
445
- # Picks observations under `[class_name, :initialize]`
446
- # whose positional arity matches the def's accepted
447
- # range (required..required+optional). Looser arities
448
- # don't get used because they describe a different
377
+ # The RBS block suffix for a `def` that takes a `&block` parameter, e.g. ` ?{ (*untyped) -> untyped }`.
378
+ # A block belongs AFTER the parameter parens in RBS (`(params) ?{ block } -> ret`), not inside them —
379
+ # emitting it as a comma-joined member produced `(**untyped, ?{ (?) -> void })`, which RBS rejects
380
+ # (`optional keyword argument type is expected`) and which then collapsed the whole env build. sig-gen
381
+ # never observes the block's own signature, so it is rendered maximally lenient (ADR-5): an optional
382
+ # block (`?{`, since a `&block` need not be passed) taking `*untyped` and returning `untyped`. Returns
383
+ # "" when the method takes no block.
384
+ def block_signature_suffix(params)
385
+ return "" unless params.is_a?(Prism::ParametersNode)
386
+ return "" if params.block.nil?
387
+
388
+ " ?{ (*untyped) -> untyped }"
389
+ end
390
+
391
+ # Picks observations under `[class_name, :initialize]` whose positional arity matches the def's accepted
392
+ # range (required..required+optional). Looser arities don't get used because they describe a different
449
393
  # overload the stub cannot express.
450
394
  def initialize_observations(class_name, params)
451
395
  return [] if @observations.empty?
@@ -503,11 +447,9 @@ module Rigor
503
447
  end
504
448
  end
505
449
 
506
- # Required positionals only; the MVP's body-typing path
507
- # gives well-defined returns for that shape. Optional /
508
- # rest / keyword / block parameters route through the
509
- # `sig.skipped.complex-shape` reason until slices 3+
510
- # widen the param policy.
450
+ # Required positionals only; the MVP's body-typing path gives well-defined returns for that shape.
451
+ # Optional / rest / keyword / block parameters route through the `sig.skipped.complex-shape` reason until
452
+ # slices 3+ widen the param policy.
511
453
  def simple_parameter_shape?(params)
512
454
  return true if params.nil?
513
455
  return false unless params.is_a?(Prism::ParametersNode)
@@ -519,27 +461,18 @@ module Rigor
519
461
  params.block.nil?
520
462
  end
521
463
 
522
- # Mirrors the `def.return-type-mismatch` rule's body-type
523
- # extraction: type the implicit-return expression under
524
- # the scope the indexer associated with the body. The
525
- # parameter bindings (typed `untyped` per the indexer's
526
- # default) come from `with_local` inside
527
- # `StatementEvaluator`; the result is the carrier the
464
+ # Mirrors the `def.return-type-mismatch` rule's body-type extraction: type the implicit-return expression
465
+ # under the scope the indexer associated with the body. The parameter bindings (typed `untyped` per the
466
+ # indexer's default) come from `with_local` inside `StatementEvaluator`; the result is the carrier the
528
467
  # body proves *given an untyped argument tuple*.
529
468
  #
530
- # Post-dogfood enhancement: walk the body's AST for
531
- # explicit `return X` statements and union their value
532
- # types with the implicit-return expression's type. The
533
- # earlier MVP only typed the implicit-return path, which
534
- # routinely produced single-branch artefacts like
535
- # `parse_options: () -> nil` (the actual runtime return
536
- # is `options | nil`) or `find: () -> V` (actually
537
- # `V | nil` via `return nil unless ...`). The walk
538
- # excludes nested `DefNode` / lambda / block scopes
539
- # whose returns belong to different methods.
540
- # Delegates to {Rigor::Inference::DefReturnTyper} — the same
541
- # body-typing + explicit-return-union the `rigor annotate`
542
- # def-line annotator uses.
469
+ # Post-dogfood enhancement: walk the body's AST for explicit `return X` statements and union their value
470
+ # types with the implicit-return expression's type. The earlier MVP only typed the implicit-return path,
471
+ # which routinely produced single-branch artefacts like `parse_options: () -> nil` (the actual runtime
472
+ # return is `options | nil`) or `find: () -> V` (actually `V | nil` via `return nil unless ...`). The walk
473
+ # excludes nested `DefNode` / lambda / block scopes whose returns belong to different methods. Delegates
474
+ # to {Rigor::Inference::DefReturnTyper} the same body-typing + explicit-return-union the `rigor
475
+ # annotate` def-line annotator uses.
543
476
  def infer_return_type(def_node, scope_index)
544
477
  Inference::DefReturnTyper.call(def_node, scope_index)
545
478
  end
@@ -548,13 +481,10 @@ module Rigor
548
481
  return true if type.is_a?(Type::Dynamic)
549
482
  return true if type.respond_to?(:top?) && type.top?.yes?
550
483
 
551
- # Post-dogfood: when explicit-return union absorbs
552
- # Dynamic and the carrier ends up as a Union containing
553
- # `Dynamic[top]`, the Bug-1 erasure rule renders it as
554
- # `untyped`. Emitting `def m: () -> untyped` is the
555
- # `sig.skipped.untyped-return` case — obscures rather
556
- # than helps — so the skip check considers the erased
557
- # form too.
484
+ # Post-dogfood: when explicit-return union absorbs Dynamic and the carrier ends up as a Union
485
+ # containing `Dynamic[top]`, the Bug-1 erasure rule renders it as `untyped`. Emitting
486
+ # `def m: () -> untyped` is the `sig.skipped.untyped-return` case obscures rather than helps — so the
487
+ # skip check considers the erased form too.
558
488
  type.respond_to?(:erase_to_rbs) && type.erase_to_rbs == "untyped"
559
489
  end
560
490
 
@@ -621,26 +551,17 @@ module Rigor
621
551
  nil
622
552
  end
623
553
 
624
- # ADR-14 § "What 'more precise' means". The MVP uses the
625
- # engine's gradual-mode acceptance `:strict` is
626
- # reserved by `Inference::Acceptance` and lands in a
627
- # follow-up. The "different spelling" guard ensures we
554
+ # ADR-14 § "What 'more precise' means". The MVP uses the engine's gradual-mode acceptance — `:strict` is
555
+ # reserved by `Inference::Acceptance` and lands in a follow-up. The "different spelling" guard ensures we
628
556
  # never classify a same-string round-trip as tighter.
629
557
  #
630
- # The `loses_declared_union_member?` guard added after
631
- # the Rigor self-dogfood pass refuses to classify as
632
- # tighter-return when the declared form is a top-level
633
- # Union and the inferred form collapses one or more of
634
- # its declared members. The body-typing path in slice 1
635
- # only inspects the implicit-return expression, so
636
- # methods with `return nil unless ...` / boolean
637
- # `false | true` shapes / `Float | Integer` numeric
638
- # alternates routinely look "tighter" while actually
639
- # dropping reachable branches. Treating those as
640
- # equivalent matches the project rule that an
641
- # inferred tightening contradicting an existing RBS
642
- # member set is suspected incomplete inference until
643
- # proven otherwise.
558
+ # The `loses_declared_union_member?` guard added after the Rigor self-dogfood pass refuses to classify as
559
+ # tighter-return when the declared form is a top-level Union and the inferred form collapses one or more
560
+ # of its declared members. The body-typing path in slice 1 only inspects the implicit-return expression,
561
+ # so methods with `return nil unless ...` / boolean `false | true` shapes / `Float | Integer` numeric
562
+ # alternates routinely look "tighter" while actually dropping reachable branches. Treating those as
563
+ # equivalent matches the project rule that an inferred tightening contradicting an existing RBS member set
564
+ # is suspected incomplete inference until proven otherwise.
644
565
  def tighter?(declared, inferred)
645
566
  return false if inferred.is_a?(Type::Dynamic)
646
567
  return false if loses_declared_lenience?(declared, inferred)
@@ -652,24 +573,16 @@ module Rigor
652
573
  !backward.yes?
653
574
  end
654
575
 
655
- # Composite guard: refuse to classify as tighter-return
656
- # when the declared RBS expresses lenience that the
657
- # inferred form removes. Three cases all signal
658
- # incomplete inference rather than precision gain:
576
+ # Composite guard: refuse to classify as tighter-return when the declared RBS expresses lenience that the
577
+ # inferred form removes. Three cases all signal incomplete inference rather than precision gain:
659
578
  #
660
- # 1. Top-level union losing one or more declared
661
- # members. `return nil unless ...` paths, two-valued
579
+ # 1. Top-level union losing one or more declared members. `return nil unless ...` paths, two-valued
662
580
  # booleans, `Float | Integer` numeric alternates.
663
- # 2. Generic collection narrowed to a fixed shape.
664
- # `Array[T]` `Tuple[T, ...]`, `Hash[K, V]`
665
- # HashShape — the body's last expression was a
666
- # literal whose specific shape is not the method's
581
+ # 2. Generic collection narrowed to a fixed shape. `Array[T]` → `Tuple[T, ...]`, `Hash[K, V]` →
582
+ # HashShape the body's last expression was a literal whose specific shape is not the method's
667
583
  # contract.
668
- # 3. `untyped` type-arg replaced by a concrete form.
669
- # Declared `Hash[String, untyped]` carries the
670
- # author's intentional value-type lenience; the
671
- # inference's narrower Union should not override
672
- # it.
584
+ # 3. `untyped` type-arg replaced by a concrete form. Declared `Hash[String, untyped]` carries the author's
585
+ # intentional value-type lenience; the inference's narrower Union should not override it.
673
586
  def loses_declared_lenience?(declared, inferred)
674
587
  loses_declared_union_member?(declared, inferred) ||
675
588
  narrows_collection_to_shape?(declared, inferred) ||
@@ -704,19 +617,13 @@ module Rigor
704
617
  inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
705
618
  end
706
619
 
707
- # Heuristic added after the third-round self-dogfood:
708
- # `FallbackTracer#size` body is `@events.size`, where
709
- # `@events` is initialised to `[]` and never assigned
710
- # again at the class-ivar pre-pass level. The
711
- # `Type::Tuple[]` (size 0) folds `.size` to
712
- # `Constant<0>` the carrier knows the empty-tuple
713
- # cardinality exactly. But the runtime contract is
714
- # `Integer` because callers add events through other
715
- # methods. The signal is "the body's last expression
716
- # is NOT a directly-authored literal but the inferred
717
- # type IS a Constant"; in that case the precision
718
- # came from inference over an internal computation,
719
- # not the author's contract, so refuse to tighten.
620
+ # Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
621
+ # `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
622
+ # `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` the carrier knows the empty-tuple cardinality
623
+ # exactly. But the runtime contract is `Integer` because callers add events through other methods. The
624
+ # signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
625
+ # Constant"; in that case the precision came from inference over an internal computation, not the
626
+ # author's contract, so refuse to tighten.
720
627
  def computed_literal_tightening?(inferred, def_node)
721
628
  return false unless inferred.is_a?(Type::Constant)
722
629
 
@@ -774,22 +681,17 @@ module Rigor
774
681
  "#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
775
682
  end
776
683
 
777
- # Routes the inferred carrier through {TypeElaborator}
778
- # so bare generic nominals (`Array` / `Hash` / `Set`
779
- # / `Range` / `Enumerable`) get their `untyped` type
780
- # parameters filled in before erasing to RBS. The
781
- # elaborator consults the class's RBS-declared
782
- # type-parameter list via `Reflection.class_type_param_names`.
684
+ # Routes the inferred carrier through {TypeElaborator} so bare generic nominals (`Array` / `Hash` / `Set`
685
+ # / `Range` / `Enumerable`) get their `untyped` type parameters filled in before erasing to RBS. The
686
+ # elaborator consults the class's RBS-declared type-parameter list via
687
+ # `Reflection.class_type_param_names`.
783
688
  def elaborated_rbs(type)
784
689
  TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs
785
690
  end
786
691
 
787
- # RBS / Steep require return-position unions to be
788
- # parenthesised when they appear bare at the top
789
- # level of a method type `def m: () -> 0 | 1` fails
790
- # the parser because the trailing `| 1` isn't a valid
791
- # method-type start. Wrap when the erased form is a
792
- # top-level union; single types and already-bracketed
692
+ # RBS / Steep require return-position unions to be parenthesised when they appear bare at the top level of
693
+ # a method type `def m: () -> 0 | 1` fails the parser because the trailing `| 1` isn't a valid
694
+ # method-type start. Wrap when the erased form is a top-level union; single types and already-bracketed
793
695
  # forms (e.g. `Array[A | B]`) parse without wrapping.
794
696
  def paren_wrap_union(rendered)
795
697
  top_level_union?(rendered) ? "(#{rendered})" : rendered
@@ -815,14 +717,11 @@ module Rigor
815
717
  params.is_a?(Prism::ParametersNode) ? params.requireds.size : 0
816
718
  end
817
719
 
818
- # Per ADR-5 clause 2 the default is `untyped` for every
819
- # position. Observed-policy callers (`--params=observed`)
820
- # pass an `observations:` map at construction time; the
821
- # generator unions per-position arg types whose tuple
822
- # arity matches the def's required-positional count.
823
- # Observations from arities other than the def's count
824
- # are discarded — they describe a different overload
825
- # the MVP does not emit.
720
+ # Per ADR-5 clause 2 the default is `untyped` for every position. Observed-policy callers
721
+ # (`--params=observed`) pass an `observations:` map at construction time; the generator unions
722
+ # per-position arg types whose tuple arity matches the def's required-positional count. Observations from
723
+ # arities other than the def's count are discarded — they describe a different overload the MVP does not
724
+ # emit.
826
725
  def render_param_list(class_name, method_name, arity)
827
726
  tuples = matching_observations(class_name, method_name, arity)
828
727
  return Array.new(arity, "untyped").join(", ") if tuples.empty?
@@ -841,25 +740,18 @@ module Rigor
841
740
  return "untyped" if types.empty?
842
741
  return elaborated_rbs(types.first) if types.size == 1
843
742
 
844
- # `Type::Combinator.union` dedupes by structural type
845
- # equality. The carrier-level `erase_to_rbs` now
846
- # absorbs `untyped` members and dedupes the post-erase
847
- # strings (`String | String` → `String` for distinct
848
- # `Constant<"Alice">` / `Constant<"Bob">` envelopes),
849
- # so the sig-gen layer only needs to elaborate bare
850
- # generics before erasing.
743
+ # `Type::Combinator.union` dedupes by structural type equality. The carrier-level `erase_to_rbs` now
744
+ # absorbs `untyped` members and dedupes the post-erase strings (`String | String` → `String` for
745
+ # distinct `Constant<"Alice">` / `Constant<"Bob">` envelopes), so the sig-gen layer only needs to
746
+ # elaborate bare generics before erasing.
851
747
  elaborated_rbs(Type::Combinator.union(*types))
852
748
  end
853
749
 
854
- # ADR-14 slice 4 — `attr_reader` / `attr_writer` /
855
- # `attr_accessor` recognition. Each Symbol-named entry in
856
- # the call's argument list yields one or two
857
- # {MethodCandidate}s whose inferred return type is the
858
- # corresponding instance-variable's accumulated type from
859
- # `Scope#class_ivars_for(class_name)`. `attr_reader` adds
860
- # one reader candidate; `attr_writer` adds one
861
- # `name=`-method writer candidate; `attr_accessor` adds
862
- # both.
750
+ # ADR-14 slice 4 — `attr_reader` / `attr_writer` / `attr_accessor` recognition. Each Symbol-named entry in
751
+ # the call's argument list yields one or two {MethodCandidate}s whose inferred return type is the
752
+ # corresponding instance-variable's accumulated type from `Scope#class_ivars_for(class_name)`.
753
+ # `attr_reader` adds one reader candidate; `attr_writer` adds one `name=`-method writer candidate;
754
+ # `attr_accessor` adds both.
863
755
  ATTR_METHOD_NAMES = %i[attr_reader attr_writer attr_accessor].freeze
864
756
  private_constant :ATTR_METHOD_NAMES
865
757
 
@@ -870,11 +762,9 @@ module Rigor
870
762
  }.freeze
871
763
  private_constant :ATTR_KINDS
872
764
 
873
- # Per-file context the attr_* walker threads through its
874
- # recursive descent. Keeps parameter lists in check.
875
- # `obs_ivar_map` carries the observation-derived fallback types
876
- # built by {#build_observed_ivar_map}; it is empty when sig-gen
877
- # is invoked without `--params=observed`.
765
+ # Per-file context the attr_* walker threads through its recursive descent. Keeps parameter lists in
766
+ # check. `obs_ivar_map` carries the observation-derived fallback types built by
767
+ # {#build_observed_ivar_map}; it is empty when sig-gen is invoked without `--params=observed`.
878
768
  AttrWalkContext = Struct.new(:path, :scope_index, :obs_ivar_map, :out, keyword_init: true)
879
769
  private_constant :AttrWalkContext
880
770
 
@@ -899,8 +789,7 @@ module Rigor
899
789
  walk_attr_calls(node.body, prefix, true, ctx) if node.body
900
790
  return
901
791
  when Prism::DefNode
902
- # Skip method bodies — attr_* there would refer to
903
- # whatever the method is doing dynamically, not a
792
+ # Skip method bodies — attr_* there would refer to whatever the method is doing dynamically, not a
904
793
  # class-level declaration.
905
794
  return
906
795
  when Prism::CallNode
@@ -930,21 +819,15 @@ module Rigor
930
819
  Source::Literals.symbol_arguments(call_node)
931
820
  end
932
821
 
933
- # Returns a closure that looks up `:@<attr_name>` in the
934
- # class-ivar accumulator carried by the first scope the
935
- # indexer associated with this file. The accumulator is
936
- # populated by `ScopeIndexer#build_class_ivar_index`
937
- # before any statement evaluation runs, so the lookup
938
- # works even when attr_* declarations come before the
939
- # corresponding ivar writes lexically.
822
+ # Returns a closure that looks up `:@<attr_name>` in the class-ivar accumulator carried by the first scope
823
+ # the indexer associated with this file. The accumulator is populated by
824
+ # `ScopeIndexer#build_class_ivar_index` before any statement evaluation runs, so the lookup works even
825
+ # when attr_* declarations come before the corresponding ivar writes lexically.
940
826
  #
941
- # When `obs_ivar_map` is non-empty (i.e. `--params=observed`
942
- # was used), it acts as a fallback: if the ivar pre-pass
943
- # resolved the type to `nil` or `Dynamic[top]` typically
944
- # because `@ivar = param` inside `initialize` typed the param
945
- # as `untyped` — the observation-derived type is substituted.
946
- # This lets `attr_reader :name` emit a concrete type when
947
- # `ClassName.new("alice")` call sites are visible to the
827
+ # When `obs_ivar_map` is non-empty (i.e. `--params=observed` was used), it acts as a fallback: if the ivar
828
+ # pre-pass resolved the type to `nil` or `Dynamic[top]` typically because `@ivar = param` inside
829
+ # `initialize` typed the param as `untyped` the observation-derived type is substituted. This lets
830
+ # `attr_reader :name` emit a concrete type when `ClassName.new("alice")` call sites are visible to the
948
831
  # observation scan.
949
832
  def ivar_type_lookup(scope_index, class_name, obs_ivar_map = {})
950
833
  any_scope = scope_index.each_value.first
@@ -958,13 +841,10 @@ module Rigor
958
841
  end
959
842
  end
960
843
 
961
- # Build a { class_name => { attr_name_sym => Type } } map that
962
- # records observation-derived types for ivars assigned directly
963
- # from `def initialize` parameters. Only populated when
964
- # `@observations` is non-empty (i.e. `--params=observed` was
965
- # supplied). Matches the pattern `@ivar_name = param_name` where
966
- # `param_name` is a required / optional positional or keyword
967
- # parameter of `initialize`.
844
+ # Build a { class_name => { attr_name_sym => Type } } map that records observation-derived types for
845
+ # ivars assigned directly from `def initialize` parameters. Only populated when `@observations` is
846
+ # non-empty (i.e. `--params=observed` was supplied). Matches the pattern `@ivar_name = param_name` where
847
+ # `param_name` is a required / optional positional or keyword parameter of `initialize`.
968
848
  def build_observed_ivar_map(root)
969
849
  return {} if @observations.empty?
970
850
 
@@ -995,9 +875,8 @@ module Rigor
995
875
  node.compact_child_nodes.each { |c| collect_init_ivar_obs(c, prefix, result) }
996
876
  end
997
877
 
998
- # Derive { attr_name_sym => Type } for a single `def initialize`
999
- # by matching `@ivar = param_name` assignments against the
1000
- # available `[class_name, :initialize]` observations.
878
+ # Derive { attr_name_sym => Type } for a single `def initialize` by matching `@ivar = param_name`
879
+ # assignments against the available `[class_name, :initialize]` observations.
1001
880
  def ivar_obs_from_initialize(class_name, def_node)
1002
881
  obs_list = @observations[[class_name, :initialize]]
1003
882
  return {} if obs_list.nil? || obs_list.empty?
@@ -1011,8 +890,8 @@ module Rigor
1011
890
  build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
1012
891
  end
1013
892
 
1014
- # Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }`
1015
- # by looking up each param's observation types and unioning them.
893
+ # Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }` by looking up each param's observation
894
+ # types and unioning them.
1016
895
  def build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
1017
896
  ivar_to_param.filter_map do |ivar_name, param_name|
1018
897
  types = collect_param_obs_types(obs_list, param_name, param_index[param_name])
@@ -1023,8 +902,8 @@ module Rigor
1023
902
  end.to_h
1024
903
  end
1025
904
 
1026
- # Collect observed argument types for a single parameter across all
1027
- # call-site observations. Returns an array of Type objects (may be empty).
905
+ # Collect observed argument types for a single parameter across all call-site observations. Returns an
906
+ # array of Type objects (may be empty).
1028
907
  def collect_param_obs_types(obs_list, param_name, param_info)
1029
908
  case param_info[:kind]
1030
909
  when :positional then obs_list.filter_map { |obs| obs.positional[param_info[:index]] }
@@ -1033,9 +912,8 @@ module Rigor
1033
912
  end
1034
913
  end
1035
914
 
1036
- # Map param_name_sym → { kind: :positional, index: N } or
1037
- # { kind: :keyword } for required / optional positionals and
1038
- # required / optional keywords of a ParametersNode.
915
+ # Map param_name_sym → { kind: :positional, index: N } or { kind: :keyword } for required / optional
916
+ # positionals and required / optional keywords of a ParametersNode.
1039
917
  def build_init_param_index(parameters)
1040
918
  index = {}
1041
919
  offset = 0
@@ -1056,10 +934,9 @@ module Rigor
1056
934
  index
1057
935
  end
1058
936
 
1059
- # Walk a def body for direct `@ivar = local_var` assignments
1060
- # where `local_var` is one of the listed parameter names.
1061
- # Records ivar_name (Symbol with `@` prefix) → param_name.
1062
- # Does not recurse into nested defs / classes / modules.
937
+ # Walk a def body for direct `@ivar = local_var` assignments where `local_var` is one of the listed
938
+ # parameter names. Records ivar_name (Symbol with `@` prefix) → param_name. Does not recurse into nested
939
+ # defs / classes / modules.
1063
940
  def scan_ivar_param_assignments(node, param_names, result)
1064
941
  return unless node.is_a?(Prism::Node)
1065
942
 
@@ -1143,14 +1020,11 @@ module Rigor
1143
1020
  )
1144
1021
  end
1145
1022
 
1146
- # Slice 4 emits attr_* in the long-form `def` spelling so
1147
- # the existing writer's `MethodDefinition`-based merge
1148
- # path applies without extra wiring. Users who prefer the
1149
- # idiomatic `attr_reader name: Type` short form can
1150
- # normalise post-emit; the writer-side member detection
1151
- # (slice 2) treats existing `attr_*` declarations as
1152
- # user-authored so a paired source-side `attr_reader`
1153
- # never produces a duplicate `def` insertion.
1023
+ # Slice 4 emits attr_* in the long-form `def` spelling so the existing writer's `MethodDefinition`-based
1024
+ # merge path applies without extra wiring. Users who prefer the idiomatic `attr_reader name: Type` short
1025
+ # form can normalise post-emit; the writer-side member detection (slice 2) treats existing `attr_*`
1026
+ # declarations as user-authored so a paired source-side `attr_reader` never produces a duplicate `def`
1027
+ # insertion.
1154
1028
  def render_attr_rbs_line(method_name, variant, ivar_type)
1155
1029
  erased = elaborated_rbs(ivar_type)
1156
1030
  wrapped = paren_wrap_union(erased)