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
@@ -14,46 +14,33 @@ require_relative "sorbet/sigil_detector"
14
14
 
15
15
  module Rigor
16
16
  module Plugin
17
- # rigor-sorbet — ingests Sorbet `sig { ... }` blocks as
18
- # method-signature contributions to Rigor's analyzer.
17
+ # rigor-sorbet — ingests Sorbet `sig { ... }` blocks as method-signature contributions to Rigor's
18
+ # analyzer.
19
19
  #
20
20
  # ADR-11 slice 1 — first deliverable. Recognises:
21
21
  #
22
- # - `sig { params(x: Integer).returns(String) }` above a
23
- # `def foo(x)` definition, contributing the parsed return
24
- # type at every call site.
25
- # - The `void` terminus and the `abstract` / `override` /
26
- # `overridable` / `final` modifiers (recorded on the
27
- # {MethodSignature} for slice ≥2).
28
- # - `class Foo` / `module Foo::Bar` / `class << self`
29
- # nesting; `def self.foo` is recognised as a singleton
30
- # method.
22
+ # - `sig { params(x: Integer).returns(String) }` above a `def foo(x)` definition, contributing the
23
+ # parsed return type at every call site.
24
+ # - The `void` terminus and the `abstract` / `override` / `overridable` / `final` modifiers (recorded
25
+ # on the {MethodSignature} for slice ≥2).
26
+ # - `class Foo` / `module Foo::Bar` / `class << self` nesting; `def self.foo` is recognised as a
27
+ # singleton method.
31
28
  #
32
- # The {TypeTranslator} table documents coverage. Most of
33
- # Sorbet's vocabulary translates; remaining gaps (`T.proc`,
34
- # `T::Struct` subclasses, `T.attached_class`, etc.) degrade
35
- # silently to `Dynamic[top]`.
29
+ # The {TypeTranslator} table documents coverage. Most of Sorbet's vocabulary translates; remaining
30
+ # gaps (`T.proc`, `T::Struct` subclasses, `T.attached_class`, etc.) degrade silently to `Dynamic[top]`.
36
31
  #
37
- # Architecture: per-run `Catalog` is built lazily on first
38
- # access by walking every configured `paths:` entry's `.rb`
39
- # files plus every `rbi_paths:` entry's `.rbi` files (slice
40
- # 4) via the plugin's `IoBoundary`. The catalog is frozen
41
- # after the first build and consulted by the `dynamic_return`
42
- # rule at every gated call site. RBI files
43
- # share the catalog with project-source sigsboth produce
44
- # `MethodSignature` entries keyed by
45
- # `(class_name, method_name, kind)`. When a key collides
46
- # across files, the last-walked sig wins (ordering is
47
- # platform-dependent: `Dir.glob` returns directory entries
48
- # in filesystem order). Sorbet's full shim-override
49
- # semantics — `sorbet/rbi/shims/` overriding
50
- # `sorbet/rbi/gems/` — lands in a later slice once the
51
- # catalog gains per-source provenance.
32
+ # Architecture: per-run `Catalog` is built lazily on first access by walking every configured `paths:`
33
+ # entry's `.rb` files plus every `rbi_paths:` entry's `.rbi` files (slice 4) via the plugin's
34
+ # `IoBoundary`. The catalog is frozen after the first build and consulted by the `dynamic_return` rule
35
+ # at every gated call site. RBI files share the catalog with project-source sigs — both produce
36
+ # `MethodSignature` entries keyed by `(class_name, method_name, kind)`. When a key collides across
37
+ # files, the last-walked sig wins (ordering is platform-dependent: `Dir.glob` returns directory
38
+ # entries in filesystem order). Sorbet's full shim-override semantics`sorbet/rbi/shims/` overriding
39
+ # `sorbet/rbi/gems/` lands in a later slice once the catalog gains per-source provenance.
52
40
  #
53
- # The plugin emits `plugin.sorbet.parse-error` warnings for
54
- # malformed sig blocks (no block / empty block / no
55
- # `returns` or `void` terminus / two consecutive sigs / sig
56
- # not followed by a def) but never aborts a run.
41
+ # The plugin emits `plugin.sorbet.parse-error` warnings for malformed sig blocks (no block / empty
42
+ # block / no `returns` or `void` terminus / two consecutive sigs / sig not followed by a def) but
43
+ # never aborts a run.
57
44
  #
58
45
  # ## Configuration
59
46
  #
@@ -63,27 +50,23 @@ module Rigor
63
50
  # paths: ["lib", "app"] # directories to scan for `.rb` sigs; defaults to `paths:`
64
51
  # rbi_paths: ["sorbet/rbi"] # directories to scan for `.rbi` files; default shown
65
52
  #
66
- # The `paths:` config key narrows the plugin's `.rb` walk;
67
- # omit it to inherit the project-wide `paths:` value. The
68
- # `rbi_paths:` key controls where Sorbet's RBI tree is read
69
- # from defaults to `sorbet/rbi/` per Tapioca's standard
70
- # layout (`gems/`, `annotations/`, `dsl/`, `shims/`). Set
71
- # to `[]` to opt out of RBI loading entirely.
53
+ # The `paths:` config key narrows the plugin's `.rb` walk; omit it to inherit the project-wide
54
+ # `paths:` value. The `rbi_paths:` key controls where Sorbet's RBI tree is read from — defaults to
55
+ # `sorbet/rbi/` per Tapioca's standard layout (`gems/`, `annotations/`, `dsl/`, `shims/`). Set to `[]`
56
+ # to opt out of RBI loading entirely.
72
57
  class Sorbet < Rigor::Plugin::Base
73
58
  manifest(
74
59
  id: "sorbet",
75
60
  version: "0.1.0",
76
61
  description: "Ingests Sorbet `sig` blocks as method-signature contributions.",
77
62
  config_schema: {
78
- # `paths` keeps a `fetch`-with-default in `init` because its
79
- # default is dynamic (the project's configured `paths`), not a
80
- # static literal the manifest can declare.
63
+ # `paths` keeps a `fetch`-with-default in `init` because its default is dynamic (the project's
64
+ # configured `paths`), not a static literal the manifest can declare.
81
65
  "paths" => :array,
82
- # Default RBI directory tree. Matches the layout `tapioca init`
83
- # generates — see Sorbet's `rbi.md`. Slice 4 walks every `.rbi`
84
- # file under these roots recursively; the four standard Tapioca
85
- # subdirectories (`gems` / `annotations` / `dsl` / `shims`) are
86
- # picked up as a side effect of recursing into the parent root.
66
+ # Default RBI directory tree. Matches the layout `tapioca init` generates — see Sorbet's
67
+ # `rbi.md`. Slice 4 walks every `.rbi` file under these roots recursively; the four standard
68
+ # Tapioca subdirectories (`gems` / `annotations` / `dsl` / `shims`) are picked up as a side
69
+ # effect of recursing into the parent root.
87
70
  "rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
88
71
  "enforce_sigil" => { kind: :boolean, default: true }
89
72
  }
@@ -93,98 +76,94 @@ module Rigor
93
76
  @services = services
94
77
  @configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
95
78
  @rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
96
- # Schema default `true` — only files marked `# typed: true` /
97
- # `:strict` / `:strong` contribute their sigs. Set to
98
- # `false` to record every file's sigs regardless of
99
- # sigil (current behaviour pre-this-config).
79
+ # Schema default `true` — only files marked `# typed: true` / `:strict` / `:strong` contribute
80
+ # their sigs. Set to `false` to record every file's sigs regardless of sigil (current behaviour
81
+ # pre-this-config).
100
82
  @enforce_sigil = config.fetch("enforce_sigil")
101
- # Per-call-site assertion gating: `@sigil_by_path` (built
102
- # during catalog harvest) is consulted so `T.let` /
103
- # `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only
104
- # fire in files Sorbet itself would enforce (`:true` /
105
- # `:strict` / `:strong`). With `enforce_sigil: false` the
106
- # gate is open everywhere. Missing-sigil paths (synthetic
107
- # fixtures, out-of-tree call sites) default to enforced —
108
- # failing-open suits spec ergonomics better than
109
- # failing-closed. See `assertion_enforced_here?`.
83
+ # Per-call-site assertion gating: `@sigil_by_path` (built during catalog harvest) is consulted so
84
+ # `T.let` / `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only fire in files Sorbet itself
85
+ # would enforce (`:true` / `:strict` / `:strong`). With `enforce_sigil: false` the gate is open
86
+ # everywhere. Missing-sigil paths (synthetic fixtures, out-of-tree call sites) default to enforced
87
+ # failing-open suits spec ergonomics better than failing-closed. See `assertion_enforced_here?`.
110
88
  @sigil_by_path = {}
111
89
  @catalog = nil
112
90
  @parse_errors_by_path = {}
113
91
  @catalog_built = false
114
- # ADR-11 slice 6 — Prism nodes for `T.absurd` calls
115
- # we observed in the `dynamic_return` rule to be
116
- # *reachable* (i.e., their discriminant didn't narrow
117
- # to `bot`). `diagnostics_for_file` walks the per-file
118
- # AST and surfaces these as `plugin.sorbet.absurd-reachable`
119
- # warnings. Hash is keyed on the Prism node's
120
- # `object_id` because the runner only parses each file
121
- # once per run, so identity is stable across the two
122
- # plugin hooks.
92
+ # ADR-11 slice 6 — Prism nodes for `T.absurd` calls we observed in the `dynamic_return` rule to be
93
+ # *reachable* (i.e., their discriminant didn't narrow to `bot`). `diagnostics_for_file` walks the
94
+ # per-file AST and surfaces these as `plugin.sorbet.absurd-reachable` warnings. Hash is keyed on
95
+ # the Prism node's `object_id` because the runner only parses each file once per run, so identity
96
+ # is stable across the two plugin hooks.
123
97
  @reachable_absurd_nodes = {}.compare_by_identity
124
- # ADR-11 light follow-up — `T.reveal_type` calls
125
- # observed in the `dynamic_return` rule, paired with the
126
- # display string for the inferred type at the call site.
127
- # Mirrors the absurd-node compare-by-identity hash;
128
- # `diagnostics_for_file` surfaces each entry as a
98
+ # ADR-11 light follow-up — `T.reveal_type` calls observed in the `dynamic_return` rule, paired
99
+ # with the display string for the inferred type at the call site. Mirrors the absurd-node
100
+ # compare-by-identity hash; `diagnostics_for_file` surfaces each entry as a
129
101
  # `plugin.sorbet.reveal-type` `:info` diagnostic.
130
102
  @reveal_type_calls = {}.compare_by_identity
131
- # T.bind / T.assert_type! priority slice 1 —
132
- # `T.assert_type!` calls observed in
133
- # the `dynamic_return` rule whose static subtype check
134
- # FAILED, paired with the inferred + asserted type
135
- # display strings. Same compare-by-identity discipline.
136
- # `diagnostics_for_file` walks the file AST for
137
- # `T.assert_type!` calls and surfaces matching entries
138
- # as `plugin.sorbet.assert-type-mismatch` `:error`
139
- # diagnostics.
103
+ # T.bind / T.assert_type! priority slice 1 — `T.assert_type!` calls observed in the
104
+ # `dynamic_return` rule whose static subtype check FAILED, paired with the inferred + asserted
105
+ # type display strings. Same compare-by-identity discipline. `diagnostics_for_file` walks the
106
+ # file AST for `T.assert_type!` calls and surfaces matching entries as
107
+ # `plugin.sorbet.assert-type-mismatch` `:error` diagnostics.
140
108
  @assert_type_mismatches = {}.compare_by_identity
141
109
  end
142
110
 
143
111
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
144
112
  ensure_catalog
145
- # The catalog records errors under the canonicalised
146
- # (realpath-resolved) form; the runner may pass the
147
- # symlink-bearing form here. Look up under both so the
148
- # match is symlink-agnostic.
113
+ # The catalog records errors under the canonicalised (realpath-resolved) form; the runner may pass
114
+ # the symlink-bearing form here. Look up under both so the match is symlink-agnostic.
149
115
  errors = @parse_errors_by_path[path] || @parse_errors_by_path[canonicalize(path)] || []
150
- diagnostics = errors.map { |error| parse_error_diagnostic(path, error) }
151
- diagnostics.concat(absurd_reachable_diagnostics(path, root))
152
- diagnostics.concat(reveal_type_diagnostics(path, root))
153
- diagnostics.concat(assert_type_mismatch_diagnostics(path, root))
154
- diagnostics
155
- end
156
-
157
- # ADR-52 slice 4 per-call return-type path via the
158
- # method-name-gated `dynamic_return` DSL. The recognised name
159
- # set is only known at run time (the catalog's `def` names come
160
- # from the lazy catalog build), so it is declared as a
161
- # callable: the engine `instance_exec`s it once per run on
162
- # first dispatch (always after `#prepare`) and memoises the
163
- # resolved Symbol Set. The gate is a safe over-approximation —
164
- # a project method merely *named* `cast` or `find` passes it
165
- # and is declined by the block's own `T.`-receiver / catalog
166
- # checks.
116
+ errors.map { |error| parse_error_diagnostic(path, error) }
117
+ end
118
+
119
+ # ADR-52 slice 4 — per-call return-type path via the method-name-gated `dynamic_return` DSL. The
120
+ # recognised name set is only known at run time (the catalog's `def` names come from the lazy
121
+ # catalog build), so it is declared as a callable: the engine `instance_exec`s it once per run on
122
+ # first dispatch (always after `#prepare`) and memoises the resolved Symbol Set. The gate is a safe
123
+ # over-approximation a project method merely *named* `cast` or `find` passes it and is declined by
124
+ # the block's own `T.`-receiver / catalog checks.
167
125
  dynamic_return methods: -> { recognised_method_names } do |call_node, scope|
168
126
  contribution_return_type(call_node, scope)
169
127
  end
170
128
 
171
- # ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact,
172
- # contributed via the method-gated `narrowing_facts` DSL. The
173
- # statement evaluator consults this path for narrowing facts.
174
- # The return-type half (`Constant[nil]`) flows through the
175
- # `dynamic_return` rule above; the block re-checks the `T.`
176
- # receiver via the recogniser, so an unrelated `bind` call
177
- # contributes nothing.
129
+ # ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact, contributed via the method-gated
130
+ # `narrowing_facts` DSL. The statement evaluator consults this path for narrowing facts. The
131
+ # return-type half (`Constant[nil]`) flows through the `dynamic_return` rule above; the block
132
+ # re-checks the `T.` receiver via the recogniser, so an unrelated `bind` call contributes nothing.
178
133
  narrowing_facts methods: [:bind] do |call_node, scope|
179
134
  bind_post_return_facts(call_node, scope)
180
135
  end
181
136
 
137
+ # ADR-37 — the three per-call diagnostics ride the engine-owned walk instead of three hand-rolled
138
+ # `walk_for_*` recursions. Each candidate `T.` call is *recorded by object identity* during the
139
+ # inference pass (the `dynamic_return` / `narrowing_facts` rules above call `record_*`), so by the
140
+ # time these node rules fire in the diagnostics phase the sets are populated; the membership
141
+ # `delete` both gates the emission and pops the entry so a re-run cannot double-fire. The recorded
142
+ # set is the gate — no per-node `AbsurdRecognizer` / name check is needed here.
143
+ node_rule Prism::CallNode do |node, _scope, path|
144
+ next [] unless @reachable_absurd_nodes.delete(node)
145
+
146
+ [absurd_diagnostic(path, node)]
147
+ end
148
+
149
+ node_rule Prism::CallNode do |node, _scope, path|
150
+ display = @reveal_type_calls.delete(node)
151
+ next [] if display.nil?
152
+
153
+ [reveal_type_diagnostic(path, node, display)]
154
+ end
155
+
156
+ node_rule Prism::CallNode do |node, _scope, path|
157
+ recorded = @assert_type_mismatches.delete(node)
158
+ next [] if recorded.nil?
159
+
160
+ [assert_type_mismatch_diagnostic(path, node, *recorded)]
161
+ end
162
+
182
163
  private
183
164
 
184
- # Run-time method-name gate for the `dynamic_return` rule
185
- # (ADR-52 slice 4): the static assertion vocabulary
186
- # (`T.let` / `T.cast` / …), `T.absurd`, and every method name
187
- # the catalog carries a sig for.
165
+ # Run-time method-name gate for the `dynamic_return` rule (ADR-52 slice 4): the static assertion
166
+ # vocabulary (`T.let` / `T.cast` / …), `T.absurd`, and every method name the catalog carries a sig for.
188
167
  def recognised_method_names
189
168
  ensure_catalog
190
169
  names = AssertionRecognizer::SORBET_ASSERTIONS.dup
@@ -193,54 +172,39 @@ module Rigor
193
172
  names
194
173
  end
195
174
 
196
- # Main contribution body for the `dynamic_return` rule.
197
- # Returns the bare `Rigor::Type` the contract expects.
198
- # Resolves the receiver in three passes:
175
+ # Main contribution body for the `dynamic_return` rule. Returns the bare `Rigor::Type` the contract
176
+ # expects. Resolves the receiver in three passes:
199
177
  #
200
- # 1. Constant receiver (`User.find(...)`) → singleton-side
201
- # catalog lookup.
202
- # 2. Nominal receiver-type (`user.name` where `user`'s
203
- # inferred type is `Nominal["User"]`) → instance-side
204
- # catalog lookup.
205
- # 3. Implicit-self (receiver-less inside a method body) →
206
- # current-class lookup via `implicit_self_lookup`.
178
+ # 1. Constant receiver (`User.find(...)`) → singleton-side catalog lookup.
179
+ # 2. Nominal receiver-type (`user.name` where `user`'s inferred type is `Nominal["User"]`) →
180
+ # instance-side catalog lookup.
181
+ # 3. Implicit-self (receiver-less inside a method body) → current-class lookup via
182
+ # `implicit_self_lookup`.
207
183
  def contribution_return_type(call_node, scope)
208
184
  return nil unless call_node.is_a?(Prism::CallNode)
209
185
 
210
- # ADR-11 slice 6 — `T.absurd(x)` exhaustiveness. Always
211
- # contributes a `bot` return (matches Sorbet's runtime
212
- # behaviour: `T.absurd` raises, so its value type is `bot`
213
- # and the engine's flow analysis treats code after it as
214
- # unreachable; the legacy contribution's `exceptional:
215
- # :raises` slot was never consumed on the dispatcher path —
216
- # only `return_type` survives the merge). When the
217
- # discriminant *isn't* narrowed to `bot` at this scope, also
218
- # records the call node so `diagnostics_for_file` can surface
219
- # a `plugin.sorbet.absurd-reachable` warning.
186
+ # ADR-11 slice 6 — `T.absurd(x)` exhaustiveness. Always contributes a `bot` return (matches
187
+ # Sorbet's runtime behaviour: `T.absurd` raises, so its value type is `bot` and the engine's flow
188
+ # analysis treats code after it as unreachable; the legacy contribution's `exceptional: :raises`
189
+ # slot was never consumed on the dispatcher path only `return_type` survives the merge). When
190
+ # the discriminant *isn't* narrowed to `bot` at this scope, also records the call node so
191
+ # `diagnostics_for_file` can surface a `plugin.sorbet.absurd-reachable` warning.
220
192
  if AbsurdRecognizer.absurd_call?(call_node)
221
193
  @reachable_absurd_nodes[call_node] = true unless AbsurdRecognizer.exhaustive?(call_node, scope)
222
194
  return Rigor::Type::Combinator.bot
223
195
  end
224
196
 
225
- # ADR-11 slice 2 — `T.let` / `T.cast` / `T.must` /
226
- # `T.unsafe` are checked first because they're cheaper
227
- # to recognise (no catalog walk required) and they
228
- # win over any cataloged signature: the user explicitly
229
- # asserted the type at the call site. The light
230
- # follow-up extends the recogniser to `T.must_because`
231
- # (alias of `T.must`) and `T.reveal_type` (passes the
232
- # type through; the human-facing diagnostic is recorded
233
- # here for `diagnostics_for_file` to emit).
197
+ # ADR-11 slice 2 — `T.let` / `T.cast` / `T.must` / `T.unsafe` are checked first because they're
198
+ # cheaper to recognise (no catalog walk required) and they win over any cataloged signature: the
199
+ # user explicitly asserted the type at the call site. The light follow-up extends the recogniser
200
+ # to `T.must_because` (alias of `T.must`) and `T.reveal_type` (passes the type through; the
201
+ # human-facing diagnostic is recorded here for `diagnostics_for_file` to emit).
234
202
  #
235
- # Per-call-site sigil gating: with `enforce_sigil: true`
236
- # (default), assertions only fire in files Sorbet itself
237
- # would enforce. Files at `# typed: false` (or
238
- # sigil-less, which Sorbet treats as `:false`) skip the
239
- # assertion path entirely so the dispatcher continues
240
- # through the next tier as if the wrapper weren't
241
- # there. The catalog tier already gates by sigil at
242
- # harvest time; this closes the matching gap for
243
- # caller-side recognition.
203
+ # Per-call-site sigil gating: with `enforce_sigil: true` (default), assertions only fire in files
204
+ # Sorbet itself would enforce. Files at `# typed: false` (or sigil-less, which Sorbet treats as
205
+ # `:false`) skip the assertion path entirely so the dispatcher continues through the next tier as
206
+ # if the wrapper weren't there. The catalog tier already gates by sigil at harvest time; this
207
+ # closes the matching gap for caller-side recognition.
244
208
  ensure_catalog
245
209
  if assertion_enforced_here?(scope)
246
210
  assertion = AssertionRecognizer.recognize(
@@ -258,10 +222,9 @@ module Rigor
258
222
  lookup_signature(call_node, scope)&.return_type
259
223
  end
260
224
 
261
- # The `narrowing_facts` body for `T.bind` — same sigil gate as
262
- # the return-type path, then the recogniser's
263
- # `post_return_facts` (the `Fact(target_kind: :self)` that
264
- # narrows `scope.self_type` for the rest of the block).
225
+ # The `narrowing_facts` body for `T.bind` — same sigil gate as the return-type path, then the
226
+ # recogniser's `post_return_facts` (the `Fact(target_kind: :self)` that narrows `scope.self_type`
227
+ # for the rest of the block).
265
228
  def bind_post_return_facts(call_node, scope)
266
229
  return nil unless call_node.is_a?(Prism::CallNode)
267
230
 
@@ -274,24 +237,16 @@ module Rigor
274
237
  contribution&.post_return_facts
275
238
  end
276
239
 
277
- # Per-call-site assertion gating (ADR-11). With
278
- # `enforce_sigil: false` the gate is fully open. With
279
- # `enforce_sigil: true` (default), the caller file's
280
- # sigil must reach `:true` / `:strict` / `:strong` for
281
- # assertions to fire. Three honest fallbacks:
240
+ # Per-call-site assertion gating (ADR-11). With `enforce_sigil: false` the gate is fully open. With
241
+ # `enforce_sigil: true` (default), the caller file's sigil must reach `:true` / `:strict` /
242
+ # `:strong` for assertions to fire. Three honest fallbacks:
282
243
  #
283
- # - `scope.source_path` is nil — synthetic call sites
284
- # (specs, virtual-node fixtures) have no file context.
285
- # Default to enforced so existing recogniser tests
286
- # keep working.
287
- # - the path is canonicalised to a form not in
288
- # `@sigil_by_path` the harvest never saw this file
289
- # (out-of-tree call site, or a path the
290
- # `configured_paths` config excluded). Sorbet itself
291
- # has no opinion on such files; default to enforced
292
- # so the recogniser still fires.
293
- # - the path IS in `@sigil_by_path` but at `:false` /
294
- # `:ignore` — gate closes.
244
+ # - `scope.source_path` is nil — synthetic call sites (specs, virtual-node fixtures) have no file
245
+ # context. Default to enforced so existing recogniser tests keep working.
246
+ # - the path is canonicalised to a form not in `@sigil_by_path` — the harvest never saw this file
247
+ # (out-of-tree call site, or a path the `configured_paths` config excluded). Sorbet itself has no
248
+ # opinion on such files; default to enforced so the recogniser still fires.
249
+ # - the path IS in `@sigil_by_path` but at `:false` / `:ignore` gate closes.
295
250
  def assertion_enforced_here?(scope)
296
251
  return true unless @enforce_sigil
297
252
 
@@ -311,8 +266,8 @@ module Rigor
311
266
  return nil if method_name.nil?
312
267
 
313
268
  if (singleton_target = constant_receiver_name(receiver))
314
- # `Post.find(...)` — direct singleton method, or
315
- # `extend M` lifting `M#find` to the extending class.
269
+ # `Post.find(...)` — direct singleton method, or `extend M` lifting `M#find` to the extending
270
+ # class.
316
271
  chain_lookup(singleton_target, method_name, anchor_kind: :singleton, mixin_kind: :extend)
317
272
  elsif receiver
318
273
  instance_chain_lookup(receiver, method_name, scope)
@@ -322,16 +277,13 @@ module Rigor
322
277
  end
323
278
 
324
279
  # ADR-11 slice 2 — implicit-self calls.
325
- # A receiver-less call inside a method body resolves against the
326
- # engine's own `scope.self_type`: `Nominal[Foo]` inside an
327
- # instance method (instance-side lookup), `Singleton[Foo]` inside
328
- # a `def self.x` body (singleton-side lookup, `extend` mixins).
329
- # Without this, an enforced sig on a sibling method was invisible
330
- # to in-class calls the engine's body-inference tiers then
331
- # re-typed the sibling's body, overriding an explicit
332
- # `T.untyped` opt-out (the dispatcher's plugin tier had already
333
- # run and declined). Anything else (toplevel / Dynamic / DSL
334
- # self) contributes nothing and the dispatcher continues.
280
+ # A receiver-less call inside a method body resolves against the engine's own `scope.self_type`:
281
+ # `Nominal[Foo]` inside an instance method (instance-side lookup), `Singleton[Foo]` inside a `def
282
+ # self.x` body (singleton-side lookup, `extend` mixins). Without this, an enforced sig on a sibling
283
+ # method was invisible to in-class calls the engine's body-inference tiers then re-typed the
284
+ # sibling's body, overriding an explicit `T.untyped` opt-out (the dispatcher's plugin tier had
285
+ # already run and declined). Anything else (toplevel / Dynamic / DSL self) contributes nothing and
286
+ # the dispatcher continues.
335
287
  def implicit_self_lookup(method_name, scope)
336
288
  self_type = scope&.self_type
337
289
  case self_type
@@ -352,9 +304,8 @@ module Rigor
352
304
 
353
305
  chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
354
306
  rescue StandardError
355
- # `scope.type_of` can raise on unrecognised synthetic
356
- # nodes; degrade to "no contribution" rather than
357
- # bubbling the failure into the dispatcher.
307
+ # `scope.type_of` can raise on unrecognised synthetic nodes; degrade to "no contribution" rather
308
+ # than bubbling the failure into the dispatcher.
358
309
  nil
359
310
  end
360
311
 
@@ -362,15 +313,13 @@ module Rigor
362
313
  #
363
314
  # For instance-side calls (`post.body`):
364
315
  # - `anchor_kind: :instance` (try `Post#body` first)
365
- # - `mixin_kind: :include` (then walk Post's `include`d
366
- # modules and try `Foo#body` on each)
316
+ # - `mixin_kind: :include` (then walk Post's `include`d modules and try `Foo#body` on each)
367
317
  #
368
318
  # For singleton-side calls (`Post.find`):
369
319
  # - `anchor_kind: :singleton` (try `Post.find` first)
370
- # - `mixin_kind: :extend` (then walk Post's `extend`ed
371
- # modules and try `Foo#find` *as :instance* `extend
372
- # Foo` lifts Foo's INSTANCE methods to the extending
373
- # class's SINGLETON methods, matching Ruby's MRO).
320
+ # - `mixin_kind: :extend` (then walk Post's `extend`ed modules and try `Foo#find` *as :instance* —
321
+ # `extend Foo` lifts Foo's INSTANCE methods to the extending class's SINGLETON methods, matching
322
+ # Ruby's MRO).
374
323
  def chain_lookup(class_name, method_name, anchor_kind:, mixin_kind:)
375
324
  each_class_form(class_name).each do |form|
376
325
  sig = @catalog.lookup(class_name: form, method_name: method_name, kind: anchor_kind)
@@ -388,8 +337,7 @@ module Rigor
388
337
  sig = @catalog.lookup(class_name: form, method_name: method_name, kind: :instance)
389
338
  return sig if sig
390
339
 
391
- # Transitive: an `include` inside the mixed-in
392
- # module is also inherited by the host class.
340
+ # Transitive: an `include` inside the mixed-in module is also inherited by the host class.
393
341
  mixin_modules_for(form, :include).each do |inner|
394
342
  queue << inner unless visited.include?(inner)
395
343
  end
@@ -399,20 +347,16 @@ module Rigor
399
347
  nil
400
348
  end
401
349
 
402
- # `Post` and `::Post` are routinely confused at the catalog
403
- # boundary (the walker records the lexical name; user code
404
- # often writes the rooted form). Try both at every lookup.
350
+ # `Post` and `::Post` are routinely confused at the catalog boundary (the walker records the lexical
351
+ # name; user code often writes the rooted form). Try both at every lookup.
405
352
  def each_class_form(class_name)
406
353
  [class_name, "::#{class_name}"]
407
354
  end
408
355
 
409
- # Resolution forms for a mixed-in module name. Tapioca's
410
- # generated DSL RBIs use the nested form
411
- # (`class Post; module GeneratedAttributeMethods; ...; end`);
412
- # hand-written shims often use the top-level form
413
- # (`module GeneratedAttributeMethods; ...; end` outside any
414
- # class); explicit rooting (`::GeneratedAttributeMethods`)
415
- # is occasionally seen. Try all three.
356
+ # Resolution forms for a mixed-in module name. Tapioca's generated DSL RBIs use the nested form
357
+ # (`class Post; module GeneratedAttributeMethods; ...; end`); hand-written shims often use the
358
+ # top-level form (`module GeneratedAttributeMethods; ...; end` outside any class); explicit rooting
359
+ # (`::GeneratedAttributeMethods`) is occasionally seen. Try all three.
416
360
  def forms_for_mixin(host_class, mixin_name)
417
361
  if mixin_name.start_with?("::")
418
362
  [mixin_name, mixin_name.delete_prefix("::")]
@@ -461,8 +405,7 @@ module Rigor
461
405
 
462
406
  # @param root [String] directory or single file.
463
407
  # @param catalog [Catalog]
464
- # @param extensions [Array<String>] file extensions to
465
- # accept (e.g. `[".rb"]` for project source,
408
+ # @param extensions [Array<String>] file extensions to accept (e.g. `[".rb"]` for project source,
466
409
  # `[".rbi"]` for Sorbet RBI tree).
467
410
  def harvest_path(root, catalog, extensions:)
468
411
  absolute = canonicalize(root)
@@ -473,18 +416,15 @@ module Rigor
473
416
  end
474
417
  end
475
418
  elsif File.file?(absolute) && extensions.any? { |ext| absolute.end_with?(ext) }
476
- # `paths:` may list individual files (the demos do
477
- # this); walk them directly rather than skipping.
419
+ # `paths:` may list individual files (the demos do this); walk them directly rather than skipping.
478
420
  harvest_file(absolute, catalog)
479
421
  end
480
422
  end
481
423
 
482
- # Canonicalises a path through `File.realpath` so it
483
- # matches the form `Plugin::TrustPolicy#allow_read?` sees
484
- # (the runner builds the policy's roots from `Dir.pwd`,
485
- # which has symlinks resolved on macOS — `/tmp` →
486
- # `/private/tmp` etc.). Falls back to `File.expand_path`
487
- # when realpath fails (e.g. the path no longer exists).
424
+ # Canonicalises a path through `File.realpath` so it matches the form `Plugin::TrustPolicy#allow_read?`
425
+ # sees (the runner builds the policy's roots from `Dir.pwd`, which has symlinks resolved on macOS —
426
+ # `/tmp` `/private/tmp` etc.). Falls back to `File.expand_path` when realpath fails (e.g. the path
427
+ # no longer exists).
488
428
  def canonicalize(path)
489
429
  expanded = File.expand_path(path)
490
430
  File.exist?(expanded) ? File.realpath(expanded) : expanded
@@ -496,31 +436,23 @@ module Rigor
496
436
  contents = io_boundary.read_file(path)
497
437
  return if contents.nil?
498
438
 
499
- # ADR-11 slice 5 — honour Sorbet's `# typed: ignore`
500
- # magic comment by skipping the file entirely.
439
+ # ADR-11 slice 5 — honour Sorbet's `# typed: ignore` magic comment by skipping the file entirely.
501
440
  level = SigilDetector.detect(contents)
502
- # Per-call-site assertion gating consults this map at
503
- # recognition time. Recorded BEFORE the ignored
504
- # short-circuit so a `# typed: ignore` file still
505
- # reports its level to the gate (the gate then chooses
506
- # to suppress assertions there too — `ignore` is
507
- # stricter than `false`).
441
+ # Per-call-site assertion gating consults this map at recognition time. Recorded BEFORE the
442
+ # ignored short-circuit so a `# typed: ignore` file still reports its level to the gate (the gate
443
+ # then chooses to suppress assertions there too — `ignore` is stricter than `false`).
508
444
  @sigil_by_path[path] = level
509
445
  return if SigilDetector.ignored?(level)
510
446
 
511
447
  result = Prism.parse(contents)
512
448
  return unless result.errors.empty?
513
449
 
514
- # `enforce_sigil` follow-up — when on (default), files
515
- # at `:false` (or sigil-less, which Sorbet treats as
516
- # `:false`) are STILL walked so parse-error diagnostics
517
- # surface, but sigs flow into a discardable catalog
518
- # rather than the per-run one. Sorbet itself doesn't
519
- # enforce types at `# typed: false`, and Rigor mirrors
520
- # that for sig contributions. Assertion recognisers
521
- # (`T.let` / `T.cast` / `T.must` / `T.bind` /
522
- # `T.assert_type!`) stay live regardless of sigil — the
523
- # user wrote those deliberately.
450
+ # `enforce_sigil` follow-up — when on (default), files at `:false` (or sigil-less, which Sorbet
451
+ # treats as `:false`) are STILL walked so parse-error diagnostics surface, but sigs flow into a
452
+ # discardable catalog rather than the per-run one. Sorbet itself doesn't enforce types at `#
453
+ # typed: false`, and Rigor mirrors that for sig contributions. Assertion recognisers (`T.let` /
454
+ # `T.cast` / `T.must` / `T.bind` / `T.assert_type!`) stay live regardless of sigil — the user
455
+ # wrote those deliberately.
524
456
  sig_catalog = if @enforce_sigil && !SigilDetector.enforced?(level)
525
457
  Catalog.new
526
458
  else
@@ -530,37 +462,13 @@ module Rigor
530
462
  errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
531
463
  @parse_errors_by_path[path] = errors unless errors.empty?
532
464
  rescue Plugin::AccessDeniedError, Errno::ENOENT
533
- # Skip files outside the trusted read scope or that
534
- # vanished between glob and read; the plugin produces
535
- # no output for them.
465
+ # Skip files outside the trusted read scope or that vanished between glob and read; the plugin
466
+ # produces no output for them.
536
467
  nil
537
468
  end
538
469
 
539
- # Walks the per-file AST looking for `T.absurd(x)` call
540
- # nodes and emits a `plugin.sorbet.absurd-reachable`
541
- # warning for any whose object identity matches
542
- # `@reachable_absurd_nodes` (populated during the engine's
543
- # earlier pass through the `dynamic_return` rule). Pops
544
- # matched entries so a duplicate run doesn't double-emit.
545
- def absurd_reachable_diagnostics(path, root)
546
- return [] if @reachable_absurd_nodes.empty?
547
-
548
- diagnostics = []
549
- walk_for_absurd(root) do |call_node|
550
- next unless @reachable_absurd_nodes.delete(call_node)
551
-
552
- diagnostics << absurd_diagnostic(path, call_node)
553
- end
554
- diagnostics
555
- end
556
-
557
- def walk_for_absurd(node, &)
558
- return unless node.is_a?(Prism::Node)
559
-
560
- yield node if node.is_a?(Prism::CallNode) && AbsurdRecognizer.absurd_call?(node)
561
- node.compact_child_nodes.each { |child| walk_for_absurd(child, &) }
562
- end
563
-
470
+ # Emits a `plugin.sorbet.absurd-reachable` warning for the `T.absurd(x)` call recorded in
471
+ # `@reachable_absurd_nodes` during inference; the node rule above does the identity match and pop.
564
472
  def absurd_diagnostic(path, call_node)
565
473
  Rigor::Analysis::Diagnostic.from_node(
566
474
  call_node,
@@ -573,47 +481,21 @@ module Rigor
573
481
  )
574
482
  end
575
483
 
576
- # ADR-11 light follow-up — `T.reveal_type(expr)` records
577
- # the inferred type at recogniser time so the per-file
578
- # diagnostic hook can surface the human-facing message.
579
- # The reveal call's contribution already preserved the
580
- # inferred type for downstream chaining; this hash carries
581
- # the *display* string that the diagnostic shows.
484
+ # ADR-11 light follow-up — `T.reveal_type(expr)` records the inferred type at recogniser time so the
485
+ # per-file diagnostic hook can surface the human-facing message. The reveal call's contribution
486
+ # already preserved the inferred type for downstream chaining; this hash carries the *display*
487
+ # string that the diagnostic shows.
582
488
  def record_reveal_type_call(call_node, return_type)
583
489
  @reveal_type_calls[call_node] = display_for_type(return_type)
584
490
  end
585
491
 
586
492
  def display_for_type(type)
587
- # `Type#describe` is the human-facing display contract
588
- # used by `rigor type-of`'s text renderer.
493
+ # `Type#describe` is the human-facing display contract used by `rigor type-of`'s text renderer.
589
494
  return "untyped" if type.nil?
590
495
 
591
496
  type.respond_to?(:describe) ? type.describe : type.inspect
592
497
  end
593
498
 
594
- def reveal_type_diagnostics(path, root)
595
- return [] if @reveal_type_calls.empty?
596
-
597
- diagnostics = []
598
- walk_for_reveal_type(root) do |call_node|
599
- display = @reveal_type_calls.delete(call_node)
600
- next if display.nil?
601
-
602
- diagnostics << reveal_type_diagnostic(path, call_node, display)
603
- end
604
- diagnostics
605
- end
606
-
607
- def walk_for_reveal_type(node, &)
608
- return unless node.is_a?(Prism::Node)
609
-
610
- if node.is_a?(Prism::CallNode) && node.name == :reveal_type &&
611
- TypeTranslator.sorbet_t_namespaced?(node.receiver)
612
- yield node
613
- end
614
- node.compact_child_nodes.each { |child| walk_for_reveal_type(child, &) }
615
- end
616
-
617
499
  def reveal_type_diagnostic(path, call_node, display)
618
500
  Rigor::Analysis::Diagnostic.from_node(
619
501
  call_node,
@@ -624,16 +506,11 @@ module Rigor
624
506
  )
625
507
  end
626
508
 
627
- # T.bind / T.assert_type! priority slice 1 — runs the
628
- # static subtype check at recogniser time and records the
629
- # call only when the inferred type is *provably
630
- # incompatible* with the asserted type. Gradual
631
- # consistency rules (`Type#accepts` mode `:gradual`): a
632
- # `Dynamic[top]` inferred type
633
- # silences the check; a definite `:no` records for
634
- # diagnostic emission; `:maybe` (uncertain) is treated as
635
- # "trust the user" and silenced — the runtime check is
636
- # there for those cases.
509
+ # T.bind / T.assert_type! priority slice 1 — runs the static subtype check at recogniser time and
510
+ # records the call only when the inferred type is *provably incompatible* with the asserted type.
511
+ # Gradual consistency rules (`Type#accepts` mode `:gradual`): a `Dynamic[top]` inferred type
512
+ # silences the check; a definite `:no` records for diagnostic emission; `:maybe` (uncertain) is
513
+ # treated as "trust the user" and silenced — the runtime check is there for those cases.
637
514
  def record_assert_type_check(call_node, scope)
638
515
  check = AssertionRecognizer.assert_type_check(call_node, scope)
639
516
  return if check.nil?
@@ -647,29 +524,6 @@ module Rigor
647
524
  @assert_type_mismatches[call_node] = [display_for_type(inferred), display_for_type(asserted)]
648
525
  end
649
526
 
650
- def assert_type_mismatch_diagnostics(path, root)
651
- return [] if @assert_type_mismatches.empty?
652
-
653
- diagnostics = []
654
- walk_for_assert_type(root) do |call_node|
655
- recorded = @assert_type_mismatches.delete(call_node)
656
- next if recorded.nil?
657
-
658
- diagnostics << assert_type_mismatch_diagnostic(path, call_node, *recorded)
659
- end
660
- diagnostics
661
- end
662
-
663
- def walk_for_assert_type(node, &)
664
- return unless node.is_a?(Prism::Node)
665
-
666
- if node.is_a?(Prism::CallNode) && node.name == :assert_type! &&
667
- TypeTranslator.sorbet_t_namespaced?(node.receiver)
668
- yield node
669
- end
670
- node.compact_child_nodes.each { |child| walk_for_assert_type(child, &) }
671
- end
672
-
673
527
  def assert_type_mismatch_diagnostic(path, call_node, inferred_display, asserted_display)
674
528
  Rigor::Analysis::Diagnostic.from_node(
675
529
  call_node,