rigortype 0.2.6 → 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 (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/docs/manual/02-cli-reference.md +2 -1
  4. data/docs/manual/04-diagnostics.md +7 -4
  5. data/docs/manual/06-baseline.md +5 -2
  6. data/docs/manual/08-skills.md +21 -0
  7. data/docs/manual/15-type-protection-coverage.md +6 -4
  8. data/lib/rigor/analysis/baseline.rb +56 -96
  9. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  10. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  11. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  12. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  13. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  14. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  15. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  16. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  17. data/lib/rigor/analysis/check_rules.rb +174 -274
  18. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  19. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  20. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  21. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  22. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  23. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  24. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  25. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  26. data/lib/rigor/analysis/diagnostic.rb +48 -75
  27. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  28. data/lib/rigor/analysis/fact_store.rb +12 -22
  29. data/lib/rigor/analysis/incremental.rb +47 -63
  30. data/lib/rigor/analysis/incremental_session.rb +74 -100
  31. data/lib/rigor/analysis/project_scan.rb +12 -23
  32. data/lib/rigor/analysis/result.rb +3 -5
  33. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  34. data/lib/rigor/analysis/run_stats.rb +38 -61
  35. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  36. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  37. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  38. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  39. data/lib/rigor/analysis/runner.rb +220 -333
  40. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  41. data/lib/rigor/analysis/worker_session.rb +64 -102
  42. data/lib/rigor/ast/type_node.rb +6 -8
  43. data/lib/rigor/ast.rb +6 -10
  44. data/lib/rigor/bleeding_edge.rb +27 -35
  45. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  46. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  47. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  48. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  49. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  50. data/lib/rigor/cache/descriptor.rb +58 -94
  51. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  52. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  53. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  54. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  55. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  56. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  57. data/lib/rigor/cache/rbs_environment.rb +11 -19
  58. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  59. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  60. data/lib/rigor/cache/store.rb +96 -151
  61. data/lib/rigor/cli/annotate_command.rb +51 -88
  62. data/lib/rigor/cli/baseline_command.rb +10 -17
  63. data/lib/rigor/cli/check_command.rb +121 -199
  64. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  65. data/lib/rigor/cli/ci_detector.rb +9 -14
  66. data/lib/rigor/cli/command.rb +8 -14
  67. data/lib/rigor/cli/coverage_command.rb +45 -20
  68. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  69. data/lib/rigor/cli/coverage_report.rb +0 -2
  70. data/lib/rigor/cli/coverage_scan.rb +7 -11
  71. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  72. data/lib/rigor/cli/diff_command.rb +10 -20
  73. data/lib/rigor/cli/docs_command.rb +26 -40
  74. data/lib/rigor/cli/doctor_command.rb +8 -9
  75. data/lib/rigor/cli/explain_command.rb +4 -7
  76. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  77. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  78. data/lib/rigor/cli/lsp_command.rb +11 -19
  79. data/lib/rigor/cli/mcp_command.rb +4 -6
  80. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  81. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  82. data/lib/rigor/cli/options.rb +11 -19
  83. data/lib/rigor/cli/plugin_command.rb +18 -30
  84. data/lib/rigor/cli/plugins_command.rb +29 -51
  85. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  86. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  87. data/lib/rigor/cli/protection_renderer.rb +5 -7
  88. data/lib/rigor/cli/protection_report.rb +27 -17
  89. data/lib/rigor/cli/renderable.rb +4 -6
  90. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  91. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  92. data/lib/rigor/cli/skill_command.rb +70 -36
  93. data/lib/rigor/cli/skill_describe.rb +40 -64
  94. data/lib/rigor/cli/trace_command.rb +9 -16
  95. data/lib/rigor/cli/trace_renderer.rb +35 -51
  96. data/lib/rigor/cli/triage_command.rb +6 -10
  97. data/lib/rigor/cli/triage_renderer.rb +1 -2
  98. data/lib/rigor/cli/type_of_command.rb +14 -23
  99. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_command.rb +6 -10
  101. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  102. data/lib/rigor/cli/type_scan_report.rb +2 -3
  103. data/lib/rigor/cli/upgrade_command.rb +2 -3
  104. data/lib/rigor/cli.rb +16 -28
  105. data/lib/rigor/config_audit.rb +35 -44
  106. data/lib/rigor/configuration/dependencies.rb +41 -70
  107. data/lib/rigor/configuration/severity_profile.rb +28 -42
  108. data/lib/rigor/configuration.rb +150 -241
  109. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  110. data/lib/rigor/environment/class_registry.rb +19 -26
  111. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  112. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  113. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  114. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  115. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  116. data/lib/rigor/environment/rbs_loader.rb +433 -503
  117. data/lib/rigor/environment/reflection.rb +28 -53
  118. data/lib/rigor/environment/reporters.rb +13 -24
  119. data/lib/rigor/environment.rb +176 -291
  120. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  121. data/lib/rigor/flow_contribution/element.rb +7 -12
  122. data/lib/rigor/flow_contribution/fact.rb +33 -57
  123. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  124. data/lib/rigor/flow_contribution/merger.rb +32 -47
  125. data/lib/rigor/flow_contribution.rb +37 -55
  126. data/lib/rigor/inference/acceptance.rb +133 -219
  127. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  128. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  129. data/lib/rigor/inference/budget_trace.rb +43 -63
  130. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  131. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  132. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  133. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  134. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  135. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  136. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  137. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  138. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  139. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  140. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  141. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  142. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  143. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  144. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  145. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  146. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  147. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  148. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  149. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  150. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  151. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  152. data/lib/rigor/inference/def_return_typer.rb +8 -14
  153. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  154. data/lib/rigor/inference/expression_typer.rb +774 -1152
  155. data/lib/rigor/inference/fallback.rb +8 -12
  156. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  157. data/lib/rigor/inference/flow_tracer.rb +25 -36
  158. data/lib/rigor/inference/hkt_body.rb +45 -68
  159. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  160. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  161. data/lib/rigor/inference/hkt_registry.rb +50 -76
  162. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  163. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  164. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  165. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  166. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  167. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  168. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  169. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  170. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  171. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  172. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  173. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  174. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  175. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  176. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  177. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  178. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  179. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  180. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  181. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  182. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  183. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  184. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  185. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  186. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  187. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  188. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  189. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  190. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  191. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  192. data/lib/rigor/inference/mutation_widening.rb +115 -158
  193. data/lib/rigor/inference/narrowing.rb +509 -727
  194. data/lib/rigor/inference/origin_lookup.rb +38 -0
  195. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  196. data/lib/rigor/inference/precision_scanner.rb +26 -35
  197. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  198. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  199. data/lib/rigor/inference/protection_scanner.rb +16 -18
  200. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  201. data/lib/rigor/inference/scope_indexer.rb +473 -817
  202. data/lib/rigor/inference/statement_evaluator.rb +706 -1115
  203. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  204. data/lib/rigor/inference/synthetic_method.rb +9 -16
  205. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  206. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  207. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  208. data/lib/rigor/language_server/buffer_table.rb +11 -18
  209. data/lib/rigor/language_server/completion_provider.rb +69 -116
  210. data/lib/rigor/language_server/debouncer.rb +16 -25
  211. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  212. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  213. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  214. data/lib/rigor/language_server/hover_provider.rb +8 -11
  215. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  216. data/lib/rigor/language_server/loop.rb +14 -19
  217. data/lib/rigor/language_server/project_context.rb +46 -74
  218. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  219. data/lib/rigor/language_server/server.rb +55 -83
  220. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  221. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  222. data/lib/rigor/language_server/uri.rb +8 -13
  223. data/lib/rigor/language_server.rb +4 -6
  224. data/lib/rigor/mcp/loop.rb +2 -3
  225. data/lib/rigor/mcp/server.rb +4 -7
  226. data/lib/rigor/mcp.rb +3 -6
  227. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  228. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  229. data/lib/rigor/plugin/base.rb +335 -518
  230. data/lib/rigor/plugin/blueprint.rb +14 -23
  231. data/lib/rigor/plugin/box.rb +18 -29
  232. data/lib/rigor/plugin/fact_store.rb +16 -26
  233. data/lib/rigor/plugin/inflector.rb +37 -53
  234. data/lib/rigor/plugin/io_boundary.rb +33 -56
  235. data/lib/rigor/plugin/isolation.rb +42 -55
  236. data/lib/rigor/plugin/load_error.rb +10 -15
  237. data/lib/rigor/plugin/loader.rb +30 -49
  238. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  239. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  240. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  241. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  242. data/lib/rigor/plugin/macro.rb +10 -15
  243. data/lib/rigor/plugin/manifest.rb +85 -144
  244. data/lib/rigor/plugin/node_context.rb +14 -22
  245. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  246. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  247. data/lib/rigor/plugin/registry.rb +132 -205
  248. data/lib/rigor/plugin/services.rb +21 -33
  249. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  250. data/lib/rigor/plugin/trust_policy.rb +24 -38
  251. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  252. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  253. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  254. data/lib/rigor/protection/mutator.rb +50 -70
  255. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  256. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  257. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  258. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  259. data/lib/rigor/rbs_extended.rb +107 -197
  260. data/lib/rigor/reflection.rb +68 -86
  261. data/lib/rigor/scope/discovery_index.rb +14 -19
  262. data/lib/rigor/scope.rb +255 -310
  263. data/lib/rigor/sig_gen/classification.rb +6 -10
  264. data/lib/rigor/sig_gen/generator.rb +208 -309
  265. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  266. data/lib/rigor/sig_gen/method_candidate.rb +15 -15
  267. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  268. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  269. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  270. data/lib/rigor/sig_gen/renderer.rb +7 -13
  271. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  272. data/lib/rigor/sig_gen/write_result.rb +8 -16
  273. data/lib/rigor/sig_gen/writer.rb +141 -173
  274. data/lib/rigor/sig_gen.rb +3 -6
  275. data/lib/rigor/signature_path_audit.rb +24 -30
  276. data/lib/rigor/source/constant_path.rb +10 -14
  277. data/lib/rigor/source/literals.rb +31 -45
  278. data/lib/rigor/source/node_locator.rb +9 -11
  279. data/lib/rigor/source/node_walker.rb +9 -13
  280. data/lib/rigor/source.rb +3 -4
  281. data/lib/rigor/testing.rb +16 -20
  282. data/lib/rigor/triage/catalogue.rb +38 -62
  283. data/lib/rigor/triage.rb +31 -52
  284. data/lib/rigor/trinary.rb +9 -13
  285. data/lib/rigor/type/acceptance_router.rb +4 -6
  286. data/lib/rigor/type/accepts_result.rb +10 -14
  287. data/lib/rigor/type/app.rb +19 -27
  288. data/lib/rigor/type/bot.rb +4 -6
  289. data/lib/rigor/type/bound_method.rb +10 -15
  290. data/lib/rigor/type/combinator.rb +165 -257
  291. data/lib/rigor/type/constant.rb +23 -34
  292. data/lib/rigor/type/data_class.rb +10 -15
  293. data/lib/rigor/type/data_instance.rb +14 -20
  294. data/lib/rigor/type/difference.rb +21 -32
  295. data/lib/rigor/type/dynamic.rb +3 -5
  296. data/lib/rigor/type/hash_shape.rb +32 -18
  297. data/lib/rigor/type/integer_range.rb +11 -16
  298. data/lib/rigor/type/intersection.rb +27 -42
  299. data/lib/rigor/type/nominal.rb +10 -15
  300. data/lib/rigor/type/plain_lattice.rb +9 -13
  301. data/lib/rigor/type/refined.rb +67 -114
  302. data/lib/rigor/type/singleton.rb +4 -6
  303. data/lib/rigor/type/struct_class.rb +11 -16
  304. data/lib/rigor/type/struct_instance.rb +15 -21
  305. data/lib/rigor/type/tuple.rb +14 -19
  306. data/lib/rigor/type/union.rb +30 -42
  307. data/lib/rigor/type_node/generic.rb +14 -26
  308. data/lib/rigor/type_node/identifier.rb +12 -19
  309. data/lib/rigor/type_node.rb +3 -12
  310. data/lib/rigor/value_semantics.rb +16 -21
  311. data/lib/rigor/version.rb +1 -1
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  314. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  315. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  318. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  319. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  322. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  323. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +107 -83
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  326. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  327. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  332. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  333. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -236
  334. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  337. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  338. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  339. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  340. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  341. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  342. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  343. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  344. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  345. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  346. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  347. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  348. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  349. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  350. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  351. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  352. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  353. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  354. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  357. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  358. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  359. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  360. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  361. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  362. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +20 -36
  363. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +23 -30
  364. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  365. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  366. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  369. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  370. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  371. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  374. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  384. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  389. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  390. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  392. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  393. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  394. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  395. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  398. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  399. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  400. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  401. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  409. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  410. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +182 -328
  411. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  412. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  413. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  414. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  415. data/sig/rigor/scope.rbs +6 -0
  416. data/skills/rigor-ask/SKILL.md +21 -1
  417. data/skills/rigor-baseline-reduce/SKILL.md +16 -0
  418. data/skills/rigor-ci-setup/SKILL.md +96 -249
  419. data/skills/rigor-doctor/SKILL.md +39 -49
  420. data/skills/rigor-doctor/references/01-checks.md +52 -0
  421. data/skills/rigor-editor-setup/SKILL.md +14 -0
  422. data/skills/rigor-mcp-setup/SKILL.md +14 -0
  423. data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
  424. data/skills/rigor-plugin-author/SKILL.md +16 -0
  425. data/skills/rigor-plugin-review/SKILL.md +174 -0
  426. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
  427. data/skills/rigor-plugin-tune/SKILL.md +21 -2
  428. data/skills/rigor-project-init/SKILL.md +16 -0
  429. data/skills/rigor-protection-uplift/SKILL.md +15 -0
  430. data/skills/rigor-rbs-setup/SKILL.md +15 -0
  431. data/skills/rigor-upgrade/SKILL.md +16 -0
  432. metadata +8 -4
@@ -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,23 +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
54
+ @class_superclasses = {}
66
55
  end
67
56
 
68
- # Lifts legacy plain-`Array[Type]` observation entries
69
- # into {ObservedCall} carriers. Specs from the slice-3
70
- # generation predate the carrier and pass observations
71
- # as `{ [class, method] => [[type1, type2], ...] }`;
72
- # the wrapper keeps those passing while internal code
73
- # 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.
74
60
  def normalize_observations(map)
75
61
  return map if map.empty?
76
62
 
@@ -116,13 +102,12 @@ module Rigor
116
102
  @namespace_kinds = {}
117
103
  @module_function_methods = Set.new
118
104
  @class_shells = Set.new
105
+ @class_superclasses = {}
119
106
  defs = collect_method_definitions(parse_result.value)
120
107
  candidates_from_defs = defs.filter_map do |def_node, class_name, kind|
121
- # An analyzer bug typing one def's body must cost only that
122
- # def's candidate, never the whole `rigor sig-gen` run. The
123
- # `check` path recovers each *file* this way
124
- # (worker_session.rb); sig-gen recovers per-def so the rest of
125
- # 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.
126
111
 
127
112
  classify_def(path, def_node, class_name, kind, scope_index)
128
113
  rescue StandardError
@@ -132,29 +117,20 @@ module Rigor
132
117
  candidates_from_defs + collect_attr_candidates(parse_result.value, path, scope_index, obs_ivar_map)
133
118
  end
134
119
 
135
- # Walks the AST collecting `(def_node, class_name, kind)`
136
- # tuples for every `def` Rigor can re-type. Slice 1
137
- # covered instance `def foo` methods inside a nameable
138
- # `class` / `module` body. Slice 4 extends this to
139
- # singleton-side methods via `def self.foo` and
140
- # `class << self; def foo; end`; top-level / DSL-block
141
- # 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).
142
124
  #
143
- # ADR-14 gap-#3 follow-up tracks two extra pieces during
144
- # the same walk so the Writer can emit kind-correct RBS
145
- # 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:
146
127
  #
147
- # - `@namespace_kinds[qualified_name]` records whether
148
- # each segment came from `class Foo` (`:class`) or
149
- # `module Foo` (`:module`). Used by the writer's
150
- # `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
151
130
  # each intermediate segment AND the leaf.
152
- # - `@module_function_methods` records `(class_name,
153
- # method_name)` pairs where a `module_function` (no
154
- # args) call preceded the `def` inside a module body.
155
- # The renderer emits `def self?.name` for these, the
156
- # RBS spelling that matches the dual instance +
157
- # 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.
158
134
  def collect_method_definitions(root)
159
135
  out = []
160
136
  walk_defs(root, [], false, false, out)
@@ -177,8 +153,7 @@ module Rigor
177
153
  return
178
154
  when Prism::ConstantWriteNode
179
155
  register_data_struct_shell(node, prefix)
180
- # fall through to recurse into the RHS so a trailing
181
- # `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.
182
157
  when Prism::StatementsNode
183
158
  walk_statements(node, prefix, in_singleton_class, module_function_active, out)
184
159
  return
@@ -195,28 +170,37 @@ module Rigor
195
170
 
196
171
  full = (prefix + [name]).join("::")
197
172
  @namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
173
+ record_superclass(node, full)
198
174
  walk_namespace_body(node, prefix + [name], out)
199
175
  true
200
176
  end
201
177
 
202
- # ADR-14 gap-#3 (e): recognises
203
- # `Const = Data.define(...)` and
204
- # `Const = Struct.new(...)` as class declarations.
205
- # The runtime side stamps a brand-new anonymous class
206
- # at the RHS and binds it to `Const`, so the generated
207
- # RBS needs an explicit `class Const` declaration even
208
- # though no `class Const ... end` block appears in
209
- # source. Without it, references to `Const` in return
210
- # types fail to resolve under Steep (the canonical case
211
- # is `GemResolver::Resolved | GemResolver::Unresolvable`
212
- # where `Unresolvable = Data.define(:gem_name, :reason)`).
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.
187
+ def record_superclass(node, full)
188
+ return unless node.is_a?(Prism::ClassNode)
189
+
190
+ superclass = qualified_constant_path(node.superclass)
191
+ @class_superclasses[full] = superclass if superclass
192
+ end
193
+
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)`).
213
200
  #
214
- # The walker records the fully-qualified constant name
215
- # in `@class_shells` (carried through to every
216
- # candidate so the writer's tree-builder picks it up)
217
- # AND in `@namespace_kinds` so the leaf's `class`
218
- # keyword wins over the intermediate-segment `module`
219
- # 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.
220
204
  def register_data_struct_shell(node, prefix)
221
205
  return unless data_or_struct_call?(node.value)
222
206
 
@@ -240,16 +224,11 @@ module Rigor
240
224
  DATA_STRUCT_SHELL_HEADS[receiver.name.to_s] == value.name
241
225
  end
242
226
 
243
- # Module / class bodies are walked through the
244
- # `walk_statements` path so `module_function` (no-args)
245
- # encountered as one statement applies to every
246
- # subsequent sibling def in the same body. The
247
- # directive is module-scoped semantically classes
248
- # inherit `module_function` via `Module`'s ancestor
249
- # chain but don't honour it the same way at runtime, so
250
- # tracking is only meaningful inside `ModuleNode`
251
- # bodies. Generator emits `def self?.name` for the
252
- # 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.
253
232
  def walk_namespace_body(namespace_node, prefix, out)
254
233
  return if namespace_node.body.nil?
255
234
 
@@ -282,24 +261,20 @@ module Rigor
282
261
  out << [node, class_name, kind]
283
262
  end
284
263
 
285
- # Wraps `MethodCandidate.new` so every candidate carries
286
- # the per-file `@namespace_kinds` map AND the
287
- # `@class_shells` set the Writer's nested-syntax
288
- # emission consults both to pick `module` vs `class`
289
- # for each segment and to emit empty
290
- # `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.
291
267
  def build_candidate(**)
292
268
  MethodCandidate.new(
293
269
  namespace_kinds: @namespace_kinds,
294
270
  class_shells: @class_shells.to_a,
271
+ class_superclasses: @class_superclasses,
295
272
  **
296
273
  )
297
274
  end
298
275
 
299
- # Returns "def self." (kind: :singleton),
300
- # "def self?." (instance method declared inside a
301
- # `module_function` region — both instance + singleton
302
- # 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).
303
278
  def method_def_prefix(class_name, method_name, kind)
304
279
  return "def self." if kind == :singleton
305
280
  return "def self?." if @module_function_methods.include?([class_name, method_name])
@@ -307,16 +282,11 @@ module Rigor
307
282
  "def "
308
283
  end
309
284
 
310
- # Slice-4 follow-up surfaced by the Rigor self-dogfood:
311
- # most `lib/rigor/cli/*` files have a small public
312
- # surface (`run`) and many private helpers. Emitting the
313
- # private helpers into a `sig/` file is noise private
314
- # methods are implementation details, not part of the
315
- # type contract downstream consumers (Steep, IDE, gem
316
- # users) read. The default now skips private and
317
- # protected methods; the `:include_private` flag
318
- # restores the slice-4 behaviour for callers that want
319
- # 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.
320
290
  def visibility_excludes?(def_node, class_name, kind, scope_index)
321
291
  return false if kind == :singleton
322
292
  return false if @include_private
@@ -328,34 +298,25 @@ module Rigor
328
298
  %i[private protected].include?(visibility)
329
299
  end
330
300
 
331
- # Ruby's `initialize` return value is never meaningful;
332
- # the conventional RBS spelling is `() -> void`. The
333
- # body-typing path types the last expression (often an
334
- # 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 `[]` /
335
303
  # `{}`), which produces nonsense return types.
336
304
  #
337
- # Skipping `initialize` entirely is correct ONLY for
338
- # default constructors — the `Object#initialize: () -> void`
339
- # RBS fallback then covers the lookup. When the class
340
- # has a non-trivial `initialize(argv:, ...)` (i.e. any
341
- # parameter), partial-class sigs trip Steep's
342
- # method-parameter-mismatch check: Steep sees the
343
- # runtime `def initialize(...)` and compares against
344
- # the inherited `Object#initialize: () -> void`. The
345
- # mismatch surfaces a `Ruby::MethodParameterMismatch`
346
- # 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.
347
311
  #
348
- # Returning `nil` here causes `classify_def` to skip
349
- # emission; returning `:emit_stub` causes
350
- # `initialize_stub_candidate` to emit a permissive
351
- # `(<param shape>) -> void` stub matching the
352
- # 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.
353
315
  def initialize_excludes?(def_node, kind)
354
316
  return false unless kind == :instance
355
317
  return false unless def_node.name == :initialize
356
318
 
357
- # Default constructor with no params — skip; the
358
- # Object#initialize RBS fallback covers it.
319
+ # Default constructor with no params — skip; the Object#initialize RBS fallback covers it.
359
320
  params = def_node.parameters
360
321
  params.nil? || trivial_initialize_params?(params)
361
322
  end
@@ -372,21 +333,18 @@ module Rigor
372
333
  kind == :instance && def_node.name == :initialize && !trivial_initialize_params?(def_node.parameters)
373
334
  end
374
335
 
375
- # Emits `def initialize: (<shape>) -> void`. The return
376
- # is always `void` because Ruby's `initialize` return
377
- # value is never meaningful. The parameter list mirrors
378
- # the runtime shape (required / optional / rest /
379
- # 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).
380
339
  #
381
- # When `--params=observed` populates `@observations` for
382
- # `[class_name, :initialize]` (via the
383
- # `ObservationCollector`'s `.new` → `:initialize`
384
- # routing), positional and keyword arg types come from
385
- # the per-position / per-keyword union of observed
386
- # types; otherwise every position keeps `untyped` per
387
- # 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.
388
344
  def initialize_stub_candidate(path, def_node, class_name)
389
- 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"
390
348
  build_candidate(
391
349
  path: path, class_name: class_name, method_name: :initialize,
392
350
  kind: :instance, classification: Classification::NEW_METHOD,
@@ -413,14 +371,25 @@ module Rigor
413
371
  parts << "*untyped" if params.rest
414
372
  params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
415
373
  parts << "**untyped" if params.keyword_rest
416
- parts << "?{ (?) -> void }" if params.block
417
374
  parts.join(", ")
418
375
  end
419
376
 
420
- # Picks observations under `[class_name, :initialize]`
421
- # whose positional arity matches the def's accepted
422
- # range (required..required+optional). Looser arities
423
- # 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
424
393
  # overload the stub cannot express.
425
394
  def initialize_observations(class_name, params)
426
395
  return [] if @observations.empty?
@@ -478,11 +447,9 @@ module Rigor
478
447
  end
479
448
  end
480
449
 
481
- # Required positionals only; the MVP's body-typing path
482
- # gives well-defined returns for that shape. Optional /
483
- # rest / keyword / block parameters route through the
484
- # `sig.skipped.complex-shape` reason until slices 3+
485
- # 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.
486
453
  def simple_parameter_shape?(params)
487
454
  return true if params.nil?
488
455
  return false unless params.is_a?(Prism::ParametersNode)
@@ -494,27 +461,18 @@ module Rigor
494
461
  params.block.nil?
495
462
  end
496
463
 
497
- # Mirrors the `def.return-type-mismatch` rule's body-type
498
- # extraction: type the implicit-return expression under
499
- # the scope the indexer associated with the body. The
500
- # parameter bindings (typed `untyped` per the indexer's
501
- # default) come from `with_local` inside
502
- # `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
503
467
  # body proves *given an untyped argument tuple*.
504
468
  #
505
- # Post-dogfood enhancement: walk the body's AST for
506
- # explicit `return X` statements and union their value
507
- # types with the implicit-return expression's type. The
508
- # earlier MVP only typed the implicit-return path, which
509
- # routinely produced single-branch artefacts like
510
- # `parse_options: () -> nil` (the actual runtime return
511
- # is `options | nil`) or `find: () -> V` (actually
512
- # `V | nil` via `return nil unless ...`). The walk
513
- # excludes nested `DefNode` / lambda / block scopes
514
- # whose returns belong to different methods.
515
- # Delegates to {Rigor::Inference::DefReturnTyper} — the same
516
- # body-typing + explicit-return-union the `rigor annotate`
517
- # 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.
518
476
  def infer_return_type(def_node, scope_index)
519
477
  Inference::DefReturnTyper.call(def_node, scope_index)
520
478
  end
@@ -523,13 +481,10 @@ module Rigor
523
481
  return true if type.is_a?(Type::Dynamic)
524
482
  return true if type.respond_to?(:top?) && type.top?.yes?
525
483
 
526
- # Post-dogfood: when explicit-return union absorbs
527
- # Dynamic and the carrier ends up as a Union containing
528
- # `Dynamic[top]`, the Bug-1 erasure rule renders it as
529
- # `untyped`. Emitting `def m: () -> untyped` is the
530
- # `sig.skipped.untyped-return` case — obscures rather
531
- # than helps — so the skip check considers the erased
532
- # 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.
533
488
  type.respond_to?(:erase_to_rbs) && type.erase_to_rbs == "untyped"
534
489
  end
535
490
 
@@ -596,26 +551,17 @@ module Rigor
596
551
  nil
597
552
  end
598
553
 
599
- # ADR-14 § "What 'more precise' means". The MVP uses the
600
- # engine's gradual-mode acceptance `:strict` is
601
- # reserved by `Inference::Acceptance` and lands in a
602
- # 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
603
556
  # never classify a same-string round-trip as tighter.
604
557
  #
605
- # The `loses_declared_union_member?` guard added after
606
- # the Rigor self-dogfood pass refuses to classify as
607
- # tighter-return when the declared form is a top-level
608
- # Union and the inferred form collapses one or more of
609
- # its declared members. The body-typing path in slice 1
610
- # only inspects the implicit-return expression, so
611
- # methods with `return nil unless ...` / boolean
612
- # `false | true` shapes / `Float | Integer` numeric
613
- # alternates routinely look "tighter" while actually
614
- # dropping reachable branches. Treating those as
615
- # equivalent matches the project rule that an
616
- # inferred tightening contradicting an existing RBS
617
- # member set is suspected incomplete inference until
618
- # 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.
619
565
  def tighter?(declared, inferred)
620
566
  return false if inferred.is_a?(Type::Dynamic)
621
567
  return false if loses_declared_lenience?(declared, inferred)
@@ -627,24 +573,16 @@ module Rigor
627
573
  !backward.yes?
628
574
  end
629
575
 
630
- # Composite guard: refuse to classify as tighter-return
631
- # when the declared RBS expresses lenience that the
632
- # inferred form removes. Three cases all signal
633
- # 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:
634
578
  #
635
- # 1. Top-level union losing one or more declared
636
- # members. `return nil unless ...` paths, two-valued
579
+ # 1. Top-level union losing one or more declared members. `return nil unless ...` paths, two-valued
637
580
  # booleans, `Float | Integer` numeric alternates.
638
- # 2. Generic collection narrowed to a fixed shape.
639
- # `Array[T]` `Tuple[T, ...]`, `Hash[K, V]`
640
- # HashShape — the body's last expression was a
641
- # 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
642
583
  # contract.
643
- # 3. `untyped` type-arg replaced by a concrete form.
644
- # Declared `Hash[String, untyped]` carries the
645
- # author's intentional value-type lenience; the
646
- # inference's narrower Union should not override
647
- # 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.
648
586
  def loses_declared_lenience?(declared, inferred)
649
587
  loses_declared_union_member?(declared, inferred) ||
650
588
  narrows_collection_to_shape?(declared, inferred) ||
@@ -679,19 +617,13 @@ module Rigor
679
617
  inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
680
618
  end
681
619
 
682
- # Heuristic added after the third-round self-dogfood:
683
- # `FallbackTracer#size` body is `@events.size`, where
684
- # `@events` is initialised to `[]` and never assigned
685
- # again at the class-ivar pre-pass level. The
686
- # `Type::Tuple[]` (size 0) folds `.size` to
687
- # `Constant<0>` the carrier knows the empty-tuple
688
- # cardinality exactly. But the runtime contract is
689
- # `Integer` because callers add events through other
690
- # methods. The signal is "the body's last expression
691
- # is NOT a directly-authored literal but the inferred
692
- # type IS a Constant"; in that case the precision
693
- # came from inference over an internal computation,
694
- # 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.
695
627
  def computed_literal_tightening?(inferred, def_node)
696
628
  return false unless inferred.is_a?(Type::Constant)
697
629
 
@@ -749,22 +681,17 @@ module Rigor
749
681
  "#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
750
682
  end
751
683
 
752
- # Routes the inferred carrier through {TypeElaborator}
753
- # so bare generic nominals (`Array` / `Hash` / `Set`
754
- # / `Range` / `Enumerable`) get their `untyped` type
755
- # parameters filled in before erasing to RBS. The
756
- # elaborator consults the class's RBS-declared
757
- # 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`.
758
688
  def elaborated_rbs(type)
759
689
  TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs
760
690
  end
761
691
 
762
- # RBS / Steep require return-position unions to be
763
- # parenthesised when they appear bare at the top
764
- # level of a method type `def m: () -> 0 | 1` fails
765
- # the parser because the trailing `| 1` isn't a valid
766
- # method-type start. Wrap when the erased form is a
767
- # 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
768
695
  # forms (e.g. `Array[A | B]`) parse without wrapping.
769
696
  def paren_wrap_union(rendered)
770
697
  top_level_union?(rendered) ? "(#{rendered})" : rendered
@@ -790,14 +717,11 @@ module Rigor
790
717
  params.is_a?(Prism::ParametersNode) ? params.requireds.size : 0
791
718
  end
792
719
 
793
- # Per ADR-5 clause 2 the default is `untyped` for every
794
- # position. Observed-policy callers (`--params=observed`)
795
- # pass an `observations:` map at construction time; the
796
- # generator unions per-position arg types whose tuple
797
- # arity matches the def's required-positional count.
798
- # Observations from arities other than the def's count
799
- # are discarded — they describe a different overload
800
- # 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.
801
725
  def render_param_list(class_name, method_name, arity)
802
726
  tuples = matching_observations(class_name, method_name, arity)
803
727
  return Array.new(arity, "untyped").join(", ") if tuples.empty?
@@ -816,25 +740,18 @@ module Rigor
816
740
  return "untyped" if types.empty?
817
741
  return elaborated_rbs(types.first) if types.size == 1
818
742
 
819
- # `Type::Combinator.union` dedupes by structural type
820
- # equality. The carrier-level `erase_to_rbs` now
821
- # absorbs `untyped` members and dedupes the post-erase
822
- # strings (`String | String` → `String` for distinct
823
- # `Constant<"Alice">` / `Constant<"Bob">` envelopes),
824
- # so the sig-gen layer only needs to elaborate bare
825
- # 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.
826
747
  elaborated_rbs(Type::Combinator.union(*types))
827
748
  end
828
749
 
829
- # ADR-14 slice 4 — `attr_reader` / `attr_writer` /
830
- # `attr_accessor` recognition. Each Symbol-named entry in
831
- # the call's argument list yields one or two
832
- # {MethodCandidate}s whose inferred return type is the
833
- # corresponding instance-variable's accumulated type from
834
- # `Scope#class_ivars_for(class_name)`. `attr_reader` adds
835
- # one reader candidate; `attr_writer` adds one
836
- # `name=`-method writer candidate; `attr_accessor` adds
837
- # 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.
838
755
  ATTR_METHOD_NAMES = %i[attr_reader attr_writer attr_accessor].freeze
839
756
  private_constant :ATTR_METHOD_NAMES
840
757
 
@@ -845,11 +762,9 @@ module Rigor
845
762
  }.freeze
846
763
  private_constant :ATTR_KINDS
847
764
 
848
- # Per-file context the attr_* walker threads through its
849
- # recursive descent. Keeps parameter lists in check.
850
- # `obs_ivar_map` carries the observation-derived fallback types
851
- # built by {#build_observed_ivar_map}; it is empty when sig-gen
852
- # 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`.
853
768
  AttrWalkContext = Struct.new(:path, :scope_index, :obs_ivar_map, :out, keyword_init: true)
854
769
  private_constant :AttrWalkContext
855
770
 
@@ -874,8 +789,7 @@ module Rigor
874
789
  walk_attr_calls(node.body, prefix, true, ctx) if node.body
875
790
  return
876
791
  when Prism::DefNode
877
- # Skip method bodies — attr_* there would refer to
878
- # 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
879
793
  # class-level declaration.
880
794
  return
881
795
  when Prism::CallNode
@@ -905,21 +819,15 @@ module Rigor
905
819
  Source::Literals.symbol_arguments(call_node)
906
820
  end
907
821
 
908
- # Returns a closure that looks up `:@<attr_name>` in the
909
- # class-ivar accumulator carried by the first scope the
910
- # indexer associated with this file. The accumulator is
911
- # populated by `ScopeIndexer#build_class_ivar_index`
912
- # before any statement evaluation runs, so the lookup
913
- # works even when attr_* declarations come before the
914
- # 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.
915
826
  #
916
- # When `obs_ivar_map` is non-empty (i.e. `--params=observed`
917
- # was used), it acts as a fallback: if the ivar pre-pass
918
- # resolved the type to `nil` or `Dynamic[top]` typically
919
- # because `@ivar = param` inside `initialize` typed the param
920
- # as `untyped` — the observation-derived type is substituted.
921
- # This lets `attr_reader :name` emit a concrete type when
922
- # `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
923
831
  # observation scan.
924
832
  def ivar_type_lookup(scope_index, class_name, obs_ivar_map = {})
925
833
  any_scope = scope_index.each_value.first
@@ -933,13 +841,10 @@ module Rigor
933
841
  end
934
842
  end
935
843
 
936
- # Build a { class_name => { attr_name_sym => Type } } map that
937
- # records observation-derived types for ivars assigned directly
938
- # from `def initialize` parameters. Only populated when
939
- # `@observations` is non-empty (i.e. `--params=observed` was
940
- # supplied). Matches the pattern `@ivar_name = param_name` where
941
- # `param_name` is a required / optional positional or keyword
942
- # 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`.
943
848
  def build_observed_ivar_map(root)
944
849
  return {} if @observations.empty?
945
850
 
@@ -970,9 +875,8 @@ module Rigor
970
875
  node.compact_child_nodes.each { |c| collect_init_ivar_obs(c, prefix, result) }
971
876
  end
972
877
 
973
- # Derive { attr_name_sym => Type } for a single `def initialize`
974
- # by matching `@ivar = param_name` assignments against the
975
- # 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.
976
880
  def ivar_obs_from_initialize(class_name, def_node)
977
881
  obs_list = @observations[[class_name, :initialize]]
978
882
  return {} if obs_list.nil? || obs_list.empty?
@@ -986,8 +890,8 @@ module Rigor
986
890
  build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
987
891
  end
988
892
 
989
- # Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }`
990
- # 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.
991
895
  def build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
992
896
  ivar_to_param.filter_map do |ivar_name, param_name|
993
897
  types = collect_param_obs_types(obs_list, param_name, param_index[param_name])
@@ -998,8 +902,8 @@ module Rigor
998
902
  end.to_h
999
903
  end
1000
904
 
1001
- # Collect observed argument types for a single parameter across all
1002
- # 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).
1003
907
  def collect_param_obs_types(obs_list, param_name, param_info)
1004
908
  case param_info[:kind]
1005
909
  when :positional then obs_list.filter_map { |obs| obs.positional[param_info[:index]] }
@@ -1008,9 +912,8 @@ module Rigor
1008
912
  end
1009
913
  end
1010
914
 
1011
- # Map param_name_sym → { kind: :positional, index: N } or
1012
- # { kind: :keyword } for required / optional positionals and
1013
- # 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.
1014
917
  def build_init_param_index(parameters)
1015
918
  index = {}
1016
919
  offset = 0
@@ -1031,10 +934,9 @@ module Rigor
1031
934
  index
1032
935
  end
1033
936
 
1034
- # Walk a def body for direct `@ivar = local_var` assignments
1035
- # where `local_var` is one of the listed parameter names.
1036
- # Records ivar_name (Symbol with `@` prefix) → param_name.
1037
- # 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.
1038
940
  def scan_ivar_param_assignments(node, param_names, result)
1039
941
  return unless node.is_a?(Prism::Node)
1040
942
 
@@ -1118,14 +1020,11 @@ module Rigor
1118
1020
  )
1119
1021
  end
1120
1022
 
1121
- # Slice 4 emits attr_* in the long-form `def` spelling so
1122
- # the existing writer's `MethodDefinition`-based merge
1123
- # path applies without extra wiring. Users who prefer the
1124
- # idiomatic `attr_reader name: Type` short form can
1125
- # normalise post-emit; the writer-side member detection
1126
- # (slice 2) treats existing `attr_*` declarations as
1127
- # user-authored so a paired source-side `attr_reader`
1128
- # 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.
1129
1028
  def render_attr_rbs_line(method_name, variant, ivar_type)
1130
1029
  erased = elaborated_rbs(ivar_type)
1131
1030
  wrapped = paren_wrap_union(erased)