rigortype 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -7,62 +7,45 @@ require_relative "multi_target_binder"
7
7
 
8
8
  module Rigor
9
9
  module Inference
10
- # Builds the entry scope of a block body by translating the block's
11
- # parameter list into a `name -> Rigor::Type` map.
10
+ # Builds the entry scope of a block body by translating the block's parameter list into a `name ->
11
+ # Rigor::Type` map.
12
12
  #
13
- # The binder is the symmetric counterpart of {MethodParameterBinder}
14
- # for `Prism::BlockNode`. The expected parameter types come from
15
- # the receiving method's RBS signature
16
- # ({Rigor::Inference::MethodDispatcher.expected_block_param_types});
17
- # parameters that the signature does not cover (or that the binder
18
- # cannot match by position) default to `Dynamic[Top]`. The default
19
- # is the Slice 1 fail-soft answer for unknown values, so a block
20
- # whose receiving method has no signature still binds every name
21
- # into the scope (a block body whose `Local x` reads return
22
- # `Dynamic[Top]` instead of falling through to the unbound-local
23
- # `Dynamic[Top]` event is the same observable type, but the
24
- # binding presence is what later slices need to attach narrowing
25
- # facts to).
13
+ # The binder is the symmetric counterpart of {MethodParameterBinder} for `Prism::BlockNode`. The
14
+ # expected parameter types come from the receiving method's RBS signature
15
+ # ({Rigor::Inference::MethodDispatcher.expected_block_param_types}); parameters that the signature does
16
+ # not cover (or that the binder cannot match by position) default to `Dynamic[Top]`. The default is the
17
+ # Slice 1 fail-soft answer for unknown values, so a block whose receiving method has no signature still
18
+ # binds every name into the scope (a block body whose `Local x` reads return `Dynamic[Top]` instead of
19
+ # falling through to the unbound-local `Dynamic[Top]` event is the same observable type, but the binding
20
+ # presence is what later slices need to attach narrowing facts to).
26
21
  #
27
- # MultiTargetNode parameters (`|(a, b), c|`) are bound by
28
- # delegating each destructuring slot to
29
- # {Rigor::Inference::MultiTargetBinder}, so a Tuple-shaped
30
- # expected element type projects element-wise into the inner
31
- # locals (Slice 6 phase C sub-phase 2). Numbered parameters
32
- # (`_1`, `_2`, ...) are bound from `Prism::NumberedParametersNode`
33
- # using the same per-position `expected_param_types:` array, so
34
- # `[1, 2, 3].each { _1 + _2 }` sees `_1`/`_2` typed identically
35
- # to their explicit `|x, y|` counterparts.
22
+ # MultiTargetNode parameters (`|(a, b), c|`) are bound by delegating each destructuring slot to
23
+ # {Rigor::Inference::MultiTargetBinder}, so a Tuple-shaped expected element type projects element-wise
24
+ # into the inner locals (Slice 6 phase C sub-phase 2). Numbered parameters (`_1`, `_2`, ...) are bound
25
+ # from `Prism::NumberedParametersNode` using the same per-position `expected_param_types:` array, so
26
+ # `[1, 2, 3].each { _1 + _2 }` sees `_1`/`_2` typed identically to their explicit `|x, y|` counterparts.
36
27
  #
37
- # The `it` implicit parameter (Ruby 3.4+) is bound from
38
- # `Prism::ItParametersNode`. It is the single-argument cousin of
39
- # `_1`: the binder produces `{ it: expected_param_types[0] }` so
40
- # the body's `Prism::ItLocalVariableReadNode` lookup sees the same
41
- # type as the explicit `|x|` form would.
28
+ # The `it` implicit parameter (Ruby 3.4+) is bound from `Prism::ItParametersNode`. It is the
29
+ # single-argument cousin of `_1`: the binder produces `{ it: expected_param_types[0] }` so the body's
30
+ # `Prism::ItLocalVariableReadNode` lookup sees the same type as the explicit `|x|` form would.
42
31
  #
43
- # Block-local declarations after `;` (e.g., `|x; y, z|`) are
44
- # still skipped they are explicitly block-local, so the outer
45
- # scope MUST NOT observe them and the binder leaves them unbound.
32
+ # Block-local declarations after `;` (e.g., `|x; y, z|`) are still skipped — they are explicitly
33
+ # block-local, so the outer scope MUST NOT observe them and the binder leaves them unbound.
46
34
  #
47
35
  # See docs/internal-spec/inference-engine.md for the binding contract.
48
36
  class BlockParameterBinder
49
- # @param expected_param_types [Array<Rigor::Type>] positional block
50
- # parameter types in order. Indices the binder cannot fill from
51
- # this array (because the array is shorter than the parameter
52
- # list, or because the slot is a kind we do not pull from the
53
- # array) default to `Dynamic[Top]`.
37
+ # @param expected_param_types [Array<Rigor::Type>] positional block parameter types in order. Indices
38
+ # the binder cannot fill from this array (because the array is shorter than the parameter list, or
39
+ # because the slot is a kind we do not pull from the array) default to `Dynamic[Top]`.
54
40
  def initialize(expected_param_types: [])
55
41
  @expected_param_types = expected_param_types
56
42
  end
57
43
 
58
44
  # @param block_node [Prism::BlockNode]
59
- # @return [Hash{Symbol => Rigor::Type}] ordered map from parameter
60
- # name to bound type. Anonymous parameters are skipped;
61
- # MultiTargetNode destructuring slots delegate to
62
- # {MultiTargetBinder} and contribute every named local in
63
- # declaration order. Numbered-parameter forms (`_1`, `_2`,
64
- # ...) bind `:_1`, `:_2`, ... up to the maximum the block
65
- # body refers to.
45
+ # @return [Hash{Symbol => Rigor::Type}] ordered map from parameter name to bound type. Anonymous
46
+ # parameters are skipped; MultiTargetNode destructuring slots delegate to {MultiTargetBinder} and
47
+ # contribute every named local in declaration order. Numbered-parameter forms (`_1`, `_2`, ...) bind
48
+ # `:_1`, `:_2`, ... up to the maximum the block body refers to.
66
49
  def bind(block_node)
67
50
  params_root = block_node.parameters
68
51
  return {} if params_root.nil?
@@ -81,12 +64,10 @@ module Rigor
81
64
 
82
65
  private
83
66
 
84
- # `|_1, _2|` numbered-parameter form. Prism exposes the
85
- # implicit count through `NumberedParametersNode#maximum`
86
- # (the highest `_N` referenced in the body); we materialise
87
- # bindings for `:_1` through `:_maximum` so the block body's
88
- # `LocalVariableReadNode` lookups see the same types as the
89
- # equivalent explicit `|x, y|` form would.
67
+ # `|_1, _2|` numbered-parameter form. Prism exposes the implicit count through
68
+ # `NumberedParametersNode#maximum` (the highest `_N` referenced in the body); we materialise bindings
69
+ # for `:_1` through `:_maximum` so the block body's `LocalVariableReadNode` lookups see the same types
70
+ # as the equivalent explicit `|x, y|` form would.
90
71
  def bind_numbered_parameters(numbered_node)
91
72
  bindings = {}
92
73
  numbered_node.maximum.times do |i|
@@ -95,9 +76,8 @@ module Rigor
95
76
  bindings
96
77
  end
97
78
 
98
- # `{ it.foo }` — Ruby 3.4 `it` is a single-argument implicit
99
- # parameter. Always binds the symbol `:it`; `ItLocalVariableReadNode`
100
- # in the body looks the binding up by name.
79
+ # `{ it.foo }` — Ruby 3.4 `it` is a single-argument implicit parameter. Always binds the symbol `:it`;
80
+ # `ItLocalVariableReadNode` in the body looks the binding up by name.
101
81
  def bind_it_parameter
102
82
  { it: positional_type_at(0) }
103
83
  end
@@ -117,27 +97,21 @@ module Rigor
117
97
  bindings
118
98
  end
119
99
 
120
- # Ruby blocks (NOT lambdas) auto-splat a single yielded
121
- # Tuple-shaped value when the block declares more than one
122
- # required positional parameter:
100
+ # Ruby blocks (NOT lambdas) auto-splat a single yielded Tuple-shaped value when the block declares more
101
+ # than one required positional parameter:
123
102
  #
124
103
  # { a: 1 }.each { |k, v| ... }
125
104
  #
126
- # yields `[key, value]` as a single arg, but the two-param
127
- # block sees `k = key, v = value`. RBS / IteratorDispatch
128
- # encode this as the block taking ONE `[K, V]` Tuple
129
- # parameter; without this fix-up the binder would assign
130
- # `k = Tuple[K, V]` and `v = Dynamic[Top]`, and any call
131
- # on `k.<method-not-on-Tuple>` would false-fire.
105
+ # yields `[key, value]` as a single arg, but the two-param block sees `k = key, v = value`. RBS /
106
+ # IteratorDispatch encode this as the block taking ONE `[K, V]` Tuple parameter; without this fix-up
107
+ # the binder would assign `k = Tuple[K, V]` and `v = Dynamic[Top]`, and any call on
108
+ # `k.<method-not-on-Tuple>` would false-fire.
132
109
  #
133
- # The rule fires only when (a) the receiver yields exactly
134
- # one value (`expected_param_types.size == 1`), (b) the
135
- # block declares more than one positional slot, and (c)
136
- # that single expected element is a Tuple. Multi-arg yields
137
- # (e.g. `each_with_index`'s `(element, index)` pair) are
138
- # NOT auto-splatted — matching Ruby semantics where a
139
- # multi-arg yield to a `|a, b, c|` block fills the extra
140
- # slot with nil rather than splatting any element.
110
+ # The rule fires only when (a) the receiver yields exactly one value (`expected_param_types.size ==
111
+ # 1`), (b) the block declares more than one positional slot, and (c) that single expected element is a
112
+ # Tuple. Multi-arg yields (e.g. `each_with_index`'s `(element, index)` pair) are NOT auto-splatted —
113
+ # matching Ruby semantics where a multi-arg yield to a `|a, b, c|` block fills the extra slot with nil
114
+ # rather than splatting any element.
141
115
  def apply_auto_splat(params_node)
142
116
  return unless @expected_param_types.size == 1
143
117
 
@@ -180,11 +154,9 @@ module Rigor
180
154
  cursor
181
155
  end
182
156
 
183
- # `|*rest|` binds an Array of the leftover positional arguments.
184
- # The expected-types array is per-position, not per-rest; we
185
- # cannot reliably pick a single element type for rest, so we
186
- # default to `Array[Dynamic[Top]]`. Element-type precision for
187
- # rest parameters is deferred (demand-gated).
157
+ # `|*rest|` binds an Array of the leftover positional arguments. The expected-types array is
158
+ # per-position, not per-rest; we cannot reliably pick a single element type for rest, so we default to
159
+ # `Array[Dynamic[Top]]`. Element-type precision for rest parameters is deferred (demand-gated).
188
160
  def bind_rest(params_node, bindings)
189
161
  rest = params_node.rest
190
162
  return unless rest.respond_to?(:name) && rest&.name
@@ -219,13 +191,11 @@ module Rigor
219
191
  bindings[block.name] = Type::Combinator.nominal_of(Proc)
220
192
  end
221
193
 
222
- # Required parameters in a block list can be either a plain
223
- # `RequiredParameterNode` (named) or a `MultiTargetNode` (the
224
- # `|(a, b), c|` destructuring form). Slice 6 phase C sub-phase 2
225
- # delegates the latter to {MultiTargetBinder}, which decomposes
226
- # the slot's expected Tuple element-wise and binds every named
227
- # inner local. Other shapes (anonymous required parameters,
228
- # forward arguments) are silently skipped.
194
+ # Required parameters in a block list can be either a plain `RequiredParameterNode` (named) or a
195
+ # `MultiTargetNode` (the `|(a, b), c|` destructuring form). Slice 6 phase C sub-phase 2 delegates the
196
+ # latter to {MultiTargetBinder}, which decomposes the slot's expected Tuple element-wise and binds
197
+ # every named inner local. Other shapes (anonymous required parameters, forward arguments) are
198
+ # silently skipped.
229
199
  def bind_required_param(param, cursor, bindings)
230
200
  case param
231
201
  when Prism::RequiredParameterNode
@@ -5,25 +5,21 @@ require_relative "budget_trace"
5
5
 
6
6
  module Rigor
7
7
  module Inference
8
- # ADR-56 WD3 — the single capped-fixpoint mechanism shared by the
9
- # non-escaping block captured-local write-back (slice A) and the
10
- # loop-body fixpoint (slice B). Computes the continuation binding of a
11
- # set of locals that a body (a block body or a loop body) may rebind
12
- # across an unknown number (0..N) of iterations.
8
+ # ADR-56 WD3 — the single capped-fixpoint mechanism shared by the non-escaping block captured-local
9
+ # write-back (slice A) and the loop-body fixpoint (slice B). Computes the continuation binding of a set of
10
+ # locals that a body (a block body or a loop body) may rebind across an unknown number (0..N) of
11
+ # iterations.
13
12
  #
14
- # The body may run zero times, so the seed (pre-state) binding is kept
15
- # as a join constituent throughout; it may compound (`a = [a]`), so the
16
- # join is iterated to a fixed point under a hard cap (ADR-41 WD4). On
17
- # the final permitted iteration value-pinned constituents are widened to
18
- # their nominal base to force convergence; a local that still moves is
19
- # collapsed to `Dynamic[top]` — the established escaping-block floor —
20
- # and a {BudgetTrace::BLOCK_WRITEBACK_CAP} hit is recorded.
13
+ # The body may run zero times, so the seed (pre-state) binding is kept as a join constituent throughout; it
14
+ # may compound (`a = [a]`), so the join is iterated to a fixed point under a hard cap (ADR-41 WD4). On the
15
+ # final permitted iteration value-pinned constituents are widened to their nominal base to force
16
+ # convergence; a local that still moves is collapsed to `Dynamic[top]` — the established escaping-block
17
+ # floor and a {BudgetTrace::BLOCK_WRITEBACK_CAP} hit is recorded.
21
18
  #
22
- # The mechanism is parameterized over an `evaluate_body` callable so
23
- # slice B can reuse it: given the current per-name bindings it returns
24
- # the per-name exit bindings produced by one body evaluation from those
25
- # bindings (names the body leaves unwritten in a given pass simply do
26
- # not appear in the returned hash).
19
+ # The mechanism is parameterized over an `evaluate_body` callable so slice B can reuse it: given the
20
+ # current per-name bindings it returns the per-name exit bindings produced by one body evaluation from
21
+ # those bindings (names the body leaves unwritten in a given pass simply do not appear in the returned
22
+ # hash).
27
23
  module BodyFixpoint
28
24
  # One body evaluation per iteration; ADR-55's shape (cap 3).
29
25
  CAP = 3
@@ -33,15 +29,14 @@ module Rigor
33
29
  # @param names [Array<Symbol>] the outer locals the body can rebind.
34
30
  # @param seed_bindings [Hash{Symbol=>Type}] pre-state binding per name.
35
31
  # @param widen [#call] value-pinned widener (Constant -> Nominal).
36
- # @param evaluate_body [#call] `bindings -> exit_bindings` — evaluates
37
- # the body once from `bindings` (the per-name current assumption) and
38
- # returns the per-name exit binding it produced.
32
+ # @param evaluate_body [#call] `bindings -> exit_bindings` — evaluates the body once from `bindings`
33
+ # (the per-name current assumption) and returns the per-name exit binding it produced.
39
34
  # @return [Hash{Symbol=>Type}] the continuation binding per name.
40
35
  def converge(names:, seed_bindings:, widen:, evaluate_body:)
41
36
  return {} if names.empty?
42
37
 
43
- # Running assumption per name; seeded with the pre-state binding,
44
- # which stays a join constituent throughout (0-iteration soundness).
38
+ # Running assumption per name; seeded with the pre-state binding, which stays a join constituent
39
+ # throughout (0-iteration soundness).
45
40
  assumption = seed_bindings.dup
46
41
 
47
42
  (0...CAP).each do |iteration|
@@ -54,14 +49,11 @@ module Rigor
54
49
  next if exit_type.nil? # body did not write it this pass
55
50
 
56
51
  if last_iteration
57
- # On the final permitted iteration widen BOTH the running
58
- # assumption and the fresh exit type to their nominal bases
59
- # before joining: Rigor's `union` keeps `Constant[1]` and
60
- # `Nominal[Integer]` as distinct members, so an accumulator
61
- # (`+=`/`*=`) producing a fresh constant per pass would never
62
- # converge without collapsing both sides first. If the join
63
- # is still wider than the widened assumption (structural
64
- # compounding, `a = [a]`), the local floors to `Dynamic[top]`.
52
+ # On the final permitted iteration widen BOTH the running assumption and the fresh exit type to
53
+ # their nominal bases before joining: Rigor's `union` keeps `Constant[1]` and `Nominal[Integer]`
54
+ # as distinct members, so an accumulator (`+=`/`*=`) producing a fresh constant per pass would
55
+ # never converge without collapsing both sides first. If the join is still wider than the
56
+ # widened assumption (structural compounding, `a = [a]`), the local floors to `Dynamic[top]`.
65
57
  base = widen.call(assumption[name])
66
58
  joined = Type::Combinator.union(base, widen.call(exit_type))
67
59
  if joined == base
@@ -2,61 +2,46 @@
2
2
 
3
3
  module Rigor
4
4
  module Inference
5
- # Opt-in counters for the hard-coded inference cutoffs — the
6
- # "budget" guards that silently return `Dynamic[top]` / `nil` /
7
- # a fallback bound rather than emitting a diagnostic. These are
8
- # the *operative* cutoffs in the engine today (the configurable
9
- # `budgets:` table in docs/type-specification/inference-budgets.md
10
- # is not yet wired); counting how often each fires on a real
5
+ # Opt-in counters for the hard-coded inference cutoffs — the "budget" guards that silently return
6
+ # `Dynamic[top]` / `nil` / a fallback bound rather than emitting a diagnostic. These are the *operative*
7
+ # cutoffs in the engine today (the configurable `budgets:` table in
8
+ # docs/type-specification/inference-budgets.md is not yet wired); counting how often each fires on a real
11
9
  # project is the only way to see where inference actually stops.
12
10
  #
13
11
  # Three categories, one per guard site:
14
12
  #
15
- # - {RECURSION_GUARD} — `ExpressionTyper#infer_user_method_return`
16
- # detected a `(receiver, method)` cycle and returned
17
- # `Dynamic[top]` (the de-facto recursion-depth budget, effective
18
- # depth 1).
19
- # - {ANCESTOR_WALK_LIMIT} — `resolve_user_def_through_ancestors`
20
- # hit the 100-node BFS cap and gave up resolving the self-call.
21
- # - {HKT_FUEL_EXHAUSTED} `HktReducer` ran out of its reduction
22
- # fuel budget and unwound to `app.bound`.
23
- # - {RECURSION_UNROLL_FUEL} the constant-arg recursion unroll
24
- # (ADR-55 slice 1) exhausted its per-entry fuel and fell back to
25
- # the plain `(receiver, method)` guard (in-cycle call → `Dynamic[top]`).
26
- # - {RECURSION_FIXPOINT_CAP} — the fixpoint return-summary iteration
27
- # (ADR-55 slice 2) hit its 3-evaluation cap without converging and
28
- # collapsed the summary to `untyped` (today's behaviour).
13
+ # - {RECURSION_GUARD} — `ExpressionTyper#infer_user_method_return` detected a `(receiver, method)` cycle
14
+ # and returned `Dynamic[top]` (the de-facto recursion-depth budget, effective depth 1).
15
+ # - {ANCESTOR_WALK_LIMIT} — `resolve_user_def_through_ancestors` hit the 100-node BFS cap and gave up
16
+ # resolving the self-call.
17
+ # - {HKT_FUEL_EXHAUSTED} — `HktReducer` ran out of its reduction fuel budget and unwound to `app.bound`.
18
+ # - {RECURSION_UNROLL_FUEL} — the constant-arg recursion unroll (ADR-55 slice 1) exhausted its per-entry
19
+ # fuel and fell back to the plain `(receiver, method)` guard (in-cycle call `Dynamic[top]`).
20
+ # - {RECURSION_FIXPOINT_CAP} the fixpoint return-summary iteration (ADR-55 slice 2) hit its 3-evaluation
21
+ # cap without converging and collapsed the summary to `untyped` (today's behaviour).
29
22
  #
30
- # Enabled only when `RIGOR_BUDGET_TRACE` is set (to any non-empty
31
- # value) in the environment, or via {enable!} in tests. When
32
- # disabled, {hit} is a single boolean check and returns
33
- # immediately, so normal runs pay nothing.
23
+ # Enabled only when `RIGOR_BUDGET_TRACE` is set (to any non-empty value) in the environment, or via
24
+ # {enable!} in tests. When disabled, {hit} is a single boolean check and returns immediately, so normal
25
+ # runs pay nothing.
34
26
  #
35
- # Counters are process-global (Mutex-guarded) so they aggregate
36
- # across threads, but they do NOT cross `fork` boundaries run
37
- # `rigor check --workers 0` to keep all inference in one process
38
- # when collecting a trace.
27
+ # Counters are process-global (Mutex-guarded) so they aggregate across threads, but they do NOT cross
28
+ # `fork` boundaries run `rigor check --workers 0` to keep all inference in one process when collecting a
29
+ # trace.
39
30
  module BudgetTrace
40
31
  RECURSION_GUARD = :recursion_guard
41
32
  ANCESTOR_WALK_LIMIT = :ancestor_walk_limit
42
33
  HKT_FUEL_EXHAUSTED = :hkt_fuel_exhausted
43
- # `ExpressionTyper#infer_user_method_return` exhausted its
44
- # constant-arg unroll fuel (ADR-55 slice 1) and fell back to the
45
- # plain `(receiver, method)` recursion guard i.e. the in-cycle
46
- # call widened to `Dynamic[top]` exactly as it does without the
47
- # unroll.
34
+ # `ExpressionTyper#infer_user_method_return` exhausted its constant-arg unroll fuel (ADR-55 slice 1) and
35
+ # fell back to the plain `(receiver, method)` recursion guard i.e. the in-cycle call widened to
36
+ # `Dynamic[top]` exactly as it does without the unroll.
48
37
  RECURSION_UNROLL_FUEL = :recursion_unroll_fuel
49
- # `ExpressionTyper#infer_user_method_return` ran the fixpoint
50
- # return-summary iteration (ADR-55 slice 2) to its 3-evaluation cap
51
- # without reaching convergence and collapsed the summary to
52
- # `untyped` — the in-cycle result widens to `Dynamic[top]` exactly
53
- # as it does without the fixpoint.
38
+ # `ExpressionTyper#infer_user_method_return` ran the fixpoint return-summary iteration (ADR-55 slice 2)
39
+ # to its 3-evaluation cap without reaching convergence and collapsed the summary to `untyped` the
40
+ # in-cycle result widens to `Dynamic[top]` exactly as it does without the fixpoint.
54
41
  RECURSION_FIXPOINT_CAP = :recursion_fixpoint_cap
55
- # `BodyFixpoint#converge` (ADR-56 slice A — non-escaping block
56
- # captured-local write-back) ran its 3-evaluation cap without the
57
- # written local's join converging and collapsed that local to
58
- # `Dynamic[top]` (the escaping-block floor). Shared by slice B's
59
- # loop-body fixpoint.
42
+ # `BodyFixpoint#converge` (ADR-56 slice A — non-escaping block captured-local write-back) ran its
43
+ # 3-evaluation cap without the written local's join converging and collapsed that local to
44
+ # `Dynamic[top]` (the escaping-block floor). Shared by slice B's loop-body fixpoint.
60
45
  BLOCK_WRITEBACK_CAP = :block_writeback_cap
61
46
 
62
47
  CATEGORIES = [
@@ -64,12 +49,10 @@ module Rigor
64
49
  RECURSION_FIXPOINT_CAP, BLOCK_WRITEBACK_CAP
65
50
  ].freeze
66
51
 
67
- # Distribution (histogram) categories — read-only observations of
68
- # a value's size at a site, used to choose budget defaults from an
69
- # observed tail rather than a guess (ADR-41 WD3 / Slice 2a). No cap
70
- # is enforced; these only record. `UNION_ARITY` is the member count
71
- # of every `Type::Union` that `Combinator.union` produces — the
72
- # distribution the `union_size` budget default should be set from.
52
+ # Distribution (histogram) categories — read-only observations of a value's size at a site, used to
53
+ # choose budget defaults from an observed tail rather than a guess (ADR-41 WD3 / Slice 2a). No cap is
54
+ # enforced; these only record. `UNION_ARITY` is the member count of every `Type::Union` that
55
+ # `Combinator.union` produces the distribution the `union_size` budget default should be set from.
73
56
  UNION_ARITY = :union_arity
74
57
 
75
58
  DISTRIBUTION_CATEGORIES = [UNION_ARITY].freeze
@@ -85,8 +68,8 @@ module Rigor
85
68
  @enabled
86
69
  end
87
70
 
88
- # Test / programmatic toggles. Production enablement is the
89
- # `RIGOR_BUDGET_TRACE` env var read once at load time.
71
+ # Test / programmatic toggles. Production enablement is the `RIGOR_BUDGET_TRACE` env var read once at
72
+ # load time.
90
73
  def enable!
91
74
  @enabled = true
92
75
  end
@@ -95,24 +78,23 @@ module Rigor
95
78
  @enabled = false
96
79
  end
97
80
 
98
- # Records one firing of `category`. No-op (one boolean check)
99
- # when tracing is disabled.
81
+ # Records one firing of `category`. No-op (one boolean check) when tracing is disabled.
100
82
  def hit(category)
101
83
  return unless @enabled
102
84
 
103
85
  @mutex.synchronize { @counts[category] += 1 }
104
86
  end
105
87
 
106
- # Frozen snapshot of the current counts, every known category
107
- # present (zero-filled) so consumers can render a stable table.
88
+ # Frozen snapshot of the current counts, every known category present (zero-filled) so consumers can
89
+ # render a stable table.
108
90
  def snapshot
109
91
  @mutex.synchronize do
110
92
  CATEGORIES.to_h { |category| [category, @counts[category]] }.freeze
111
93
  end
112
94
  end
113
95
 
114
- # Records one observation of `value` (an Integer size) into
115
- # `category`'s histogram. No-op (one boolean check) when disabled.
96
+ # Records one observation of `value` (an Integer size) into `category`'s histogram. No-op (one boolean
97
+ # check) when disabled.
116
98
  def observe(category, value)
117
99
  return unless @enabled
118
100
 
@@ -124,10 +106,9 @@ module Rigor
124
106
  @mutex.synchronize { @distributions[category].dup.freeze }
125
107
  end
126
108
 
127
- # Summary of a distribution category: total observation count, max
128
- # observed value, selected percentiles, and how many observations
129
- # met or exceeded each threshold in `over`. Percentiles use the
130
- # nearest-rank method over the expanded sample.
109
+ # Summary of a distribution category: total observation count, max observed value, selected percentiles,
110
+ # and how many observations met or exceeded each threshold in `over`. Percentiles use the nearest-rank
111
+ # method over the expanded sample.
131
112
  def summarize(category, over: [])
132
113
  hist = distribution(category)
133
114
  total = hist.values.sum
@@ -141,8 +122,7 @@ module Rigor
141
122
  over: over.to_h { |t| [t, hist.sum { |value, n| value >= t ? n : 0 }] } }
142
123
  end
143
124
 
144
- # Nearest-rank percentile over a `{value => count}` histogram
145
- # without materialising the full sample.
125
+ # Nearest-rank percentile over a `{value => count}` histogram without materialising the full sample.
146
126
  def percentile(hist, total, fraction)
147
127
  rank = (fraction * total).ceil
148
128
  cumulative = 0
@@ -7,12 +7,10 @@ module Rigor
7
7
  module Builtins
8
8
  # `Array` catalog. Singleton — load once, consult during dispatch.
9
9
  #
10
- # Array has more mutation surface than String: every method that
11
- # logically reshapes the array tends to call `rb_ary_modify` or
12
- # an internal helper (`ary_replace`, `ary_resize`, `ary_pop`,
13
- # `ary_push_internal`, …) that the classifier does not yet
14
- # recognise. The blocklist captures the methods we have
15
- # specifically observed flowing as `:leaf` despite mutating.
10
+ # Array has more mutation surface than String: every method that logically reshapes the array tends
11
+ # to call `rb_ary_modify` or an internal helper (`ary_replace`, `ary_resize`, `ary_pop`,
12
+ # `ary_push_internal`, …) that the classifier does not yet recognise. The blocklist captures the
13
+ # methods we have specifically observed flowing as `:leaf` despite mutating.
16
14
  ARRAY_CATALOG = MethodCatalog.for_topic(
17
15
  "array",
18
16
  mutating_selectors: {
@@ -7,12 +7,10 @@ module Rigor
7
7
  module Builtins
8
8
  # `Comparable` module catalog. Singleton — load once.
9
9
  #
10
- # `Comparable` is a Ruby module, not a class, so the
11
- # catalog is NOT routed through
12
- # `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
13
- # (which dispatches on the receiver's concrete class).
14
- # The data is wired into `MODULE_CATALOGS` in
15
- # `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
10
+ # `Comparable` is a Ruby module, not a class, so the catalog is NOT routed through
11
+ # `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS` (which dispatches on the receiver's concrete
12
+ # class). The data is wired into `MODULE_CATALOGS` in `MethodDispatcher::ConstantFolding`
13
+ # (ancestor-chain lookup).
16
14
  COMPARABLE_CATALOG = MethodCatalog.for_topic(
17
15
  "comparable",
18
16
  mutating_selectors: {
@@ -5,30 +5,24 @@ require_relative "method_catalog"
5
5
  module Rigor
6
6
  module Inference
7
7
  module Builtins
8
- # `Complex` catalog. Singleton — load once, consult during
9
- # dispatch.
8
+ # `Complex` catalog. Singleton — load once, consult during dispatch.
10
9
  #
11
- # `Complex` is a fully-immutable value type in Ruby: once a
12
- # complex number is constructed (via `Complex(real, imag)` or
13
- # `Complex.rect` / `Complex.polar`) its `real` and `imag` slots
14
- # are never rewritten. Every public instance method either
15
- # returns `self` unchanged or builds a fresh `Complex` /
16
- # `Numeric`. The C-body classifier already correctly flags the
17
- # four `:dispatch` methods (`<=>`, `to_s`, `inspect`,
18
- # `rationalize`) so there are no false-positive `:leaf`
19
- # entries to override. The blocklist therefore carries only
20
- # the conventional `:initialize_copy` defence-in-depth entry
21
- # so a hypothetical future `Constant<Complex>` carrier cannot
22
- # fold an aliasing copy through the catalog (mirrors
23
- # `range_catalog.rb`, `time_catalog.rb`, `date_catalog.rb`).
10
+ # `Complex` is a fully-immutable value type in Ruby: once a complex number is constructed (via
11
+ # `Complex(real, imag)` or `Complex.rect` / `Complex.polar`) its `real` and `imag` slots are never
12
+ # rewritten. Every public instance method either returns `self` unchanged or builds a fresh
13
+ # `Complex` / `Numeric`. The C-body classifier already correctly flags the four `:dispatch` methods
14
+ # (`<=>`, `to_s`, `inspect`, `rationalize`) so there are no false-positive `:leaf` entries to
15
+ # override. The blocklist therefore carries only the conventional `:initialize_copy`
16
+ # defence-in-depth entry so a hypothetical future `Constant<Complex>` carrier cannot fold an
17
+ # aliasing copy through the catalog (mirrors `range_catalog.rb`, `time_catalog.rb`,
18
+ # `date_catalog.rb`).
24
19
  COMPLEX_CATALOG = MethodCatalog.for_topic(
25
20
  "complex",
26
21
  mutating_selectors: {
27
22
  "Complex" => Set[
28
- # Defence in depth: `Complex` does not currently expose
29
- # a public `initialize_copy`, but blocking it keeps the
30
- # convention identical to every other catalog so future
31
- # CRuby additions cannot leak a copy-mutator through.
23
+ # Defence in depth: `Complex` does not currently expose a public `initialize_copy`, but
24
+ # blocking it keeps the convention identical to every other catalog so future CRuby additions
25
+ # cannot leak a copy-mutator through.
32
26
  :initialize_copy
33
27
  ]
34
28
  }