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
@@ -8,35 +8,26 @@ require_relative "acceptance_router"
8
8
 
9
9
  module Rigor
10
10
  module Type
11
- # `Refined[base, predicate_id]` — predicate-subset half of
12
- # the OQ3 refinement-carrier strategy
13
- # ([ADR-3](docs/adr/3-type-representation.md), Working
14
- # Decision Option C). Sibling of `Type::Difference`, which
15
- # carries the point-removal half.
11
+ # `Refined[base, predicate_id]` — predicate-subset half of the OQ3 refinement-carrier strategy
12
+ # ([ADR-3](docs/adr/3-type-representation.md), Working Decision Option C). Sibling of
13
+ # `Type::Difference`, which carries the point-removal half.
16
14
  #
17
15
  # lowercase-string = Refined[Nominal[String], :lowercase]
18
16
  # uppercase-string = Refined[Nominal[String], :uppercase]
19
17
  # numeric-string = Refined[Nominal[String], :numeric]
20
18
  #
21
- # The carrier wraps a base type and a `predicate_id` Symbol
22
- # drawn from {PREDICATES}. The recogniser is invoked at
23
- # constant-fold and acceptance time over a `Constant<base>`
24
- # value; for non-Constant receivers the carrier is a marker
25
- # the catalog tier consults to project `String#downcase` /
19
+ # The carrier wraps a base type and a `predicate_id` Symbol drawn from {PREDICATES}. The recogniser
20
+ # is invoked at constant-fold and acceptance time over a `Constant<base>` value; for non-Constant
21
+ # receivers the carrier is a marker the catalog tier consults to project `String#downcase` /
26
22
  # `String#upcase` (etc.) into the matching refinement.
27
23
  #
28
- # Display routes through {CANONICAL_NAMES}: registered
29
- # `(base_class_name, predicate_id)` pairs print in their
30
- # kebab-case spelling (`lowercase-string`); unregistered
31
- # combinations fall back to the `base & predicate?` operator
32
- # form per
33
- # [`type-operators.md`](docs/type-specification/type-operators.md).
24
+ # Display routes through {CANONICAL_NAMES}: registered `(base_class_name, predicate_id)` pairs print
25
+ # in their kebab-case spelling (`lowercase-string`); unregistered combinations fall back to the
26
+ # `base & predicate?` operator form per [`type-operators.md`](docs/type-specification/type-operators.md).
34
27
  #
35
- # Construction MUST go through `Type::Combinator.refined` /
36
- # the per-name factories (`Combinator.lowercase_string`,
37
- # `Combinator.uppercase_string`, `Combinator.numeric_string`).
38
- # Direct `.new` is an internal escape hatch for tests and
39
- # combinator's own implementation.
28
+ # Construction MUST go through `Type::Combinator.refined` / the per-name factories
29
+ # (`Combinator.lowercase_string`, `Combinator.uppercase_string`, `Combinator.numeric_string`). Direct
30
+ # `.new` is an internal escape hatch for tests and combinator's own implementation.
40
31
  class Refined
41
32
  attr_reader :base, :predicate_id
42
33
 
@@ -55,8 +46,8 @@ module Rigor
55
46
  "#{base.describe(verbosity)} & #{predicate_id}?"
56
47
  end
57
48
 
58
- # Erases to the base nominal: every refinement MUST erase
59
- # to its base per [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
49
+ # Erases to the base nominal: every refinement MUST erase to its base per
50
+ # [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
60
51
  def erase_to_rbs
61
52
  base.erase_to_rbs
62
53
  end
@@ -83,12 +74,9 @@ module Rigor
83
74
  "#<Rigor::Type::Refined #{describe(:short)}>"
84
75
  end
85
76
 
86
- # Recognises a Ruby value against this carrier's
87
- # predicate. The trinary return is intentional: `true` /
88
- # `false` when the predicate registry decides, `nil`
89
- # when the predicate is unknown to the registry, so
90
- # callers (today {Inference::Acceptance}) can fall
91
- # through to gradual-mode `:maybe`.
77
+ # Recognises a Ruby value against this carrier's predicate. The trinary return is intentional:
78
+ # `true` / `false` when the predicate registry decides, `nil` when the predicate is unknown to the
79
+ # registry, so callers (today {Inference::Acceptance}) can fall through to gradual-mode `:maybe`.
92
80
  # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
93
81
  def matches?(value)
94
82
  recogniser = PREDICATES[predicate_id]
@@ -98,60 +86,42 @@ module Rigor
98
86
  end
99
87
  # rubocop:enable Style/ReturnNilInPredicateMethodDefinition
100
88
 
101
- # `predicate_id => recogniser` table. The recogniser is
102
- # called with a Ruby value (typically the inner `value`
103
- # of a `Constant`) and returns truthy when the value
104
- # satisfies the predicate. The recogniser MUST be total
105
- # (return false rather than raise) over arbitrary input,
106
- # so callers can pass any `Constant#value` without a
107
- # type-prefilter.
89
+ # `predicate_id => recogniser` table. The recogniser is called with a Ruby value (typically the
90
+ # inner `value` of a `Constant`) and returns truthy when the value satisfies the predicate. The
91
+ # recogniser MUST be total (return false rather than raise) over arbitrary input, so callers can
92
+ # pass any `Constant#value` without a type-prefilter.
108
93
  #
109
- # Plugin-contributed predicates are not yet wired; today
110
- # the table covers the built-in catalogue.
94
+ # Plugin-contributed predicates are not yet wired; today the table covers the built-in catalogue.
111
95
  #
112
96
  # Recogniser policy:
113
97
  #
114
- # - `:numeric` recognises a string that is a *single Ruby
115
- # numeric literal* exactly the syntax that, written in
116
- # Ruby source, evaluates to an `Integer` / `Float` /
117
- # `Rational` / `Complex`. The recogniser delegates to the
118
- # real Ruby parser ({Refined.ruby_numeric_literal?} via
119
- # Prism), so it tracks Ruby's grammar precisely: decimal /
120
- # `0x` hex / `0o` (or leading-zero) octal / `0b` binary /
121
- # `0d` decimal integers, underscore digit separators
122
- # (`1_000`), decimal fractions and scientific floats
123
- # (`1.5`, `1E-5`), and the `r` rational / `i` imaginary
124
- # suffixes (`1r`, `2i`, `0xffr`). A single leading sign is
125
- # folded into the literal (`-1`, `+1.5`), but a doubled
126
- # sign (`--1`, `++1`) parses as a unary-operator chain a
127
- # `CallNode`, not a literal and is rejected, as are
128
- # multi-dot junk (`1.2.3`), partial literals (`0x`, `1_`),
129
- # whitespace-padded strings, and crucially non-ASCII
130
- # "digits" (full-width `1`, superscript `²`, other Unicode
131
- # number characters): Ruby's lexer only accepts `[0-9]` in
132
- # a numeric literal, so those are `CallNode`s too. The
133
- # stricter base-N predicates below remain proper subsets.
134
- # - `:decimal_int` is "what `Integer(s, 10)` would parse
135
- # without remainder" — one or more decimal digits,
136
- # optional leading sign, no whitespace, no fractional
137
- # tail.
138
- # - `:octal_int` and `:hex_int` REQUIRE their conventional
139
- # prefix (`0o` / `0O` / leading `0` for octal; `0x` /
140
- # `0X` for hex) so the predicate is disjoint from
141
- # `:decimal_int`. A bare `"755"` is decimal-int-string,
142
- # not octal-int-string. This matches the typical user
143
- # intent — a refinement marks a string that "looks like
144
- # octal", not "happens to be base-8 valid".
98
+ # - `:numeric` recognises a string that is a *single Ruby numeric literal* — exactly the syntax
99
+ # that, written in Ruby source, evaluates to an `Integer` / `Float` / `Rational` / `Complex`. The
100
+ # recogniser delegates to the real Ruby parser ({Refined.ruby_numeric_literal?} via Prism), so it
101
+ # tracks Ruby's grammar precisely: decimal / `0x` hex / `0o` (or leading-zero) octal / `0b`
102
+ # binary / `0d` decimal integers, underscore digit separators (`1_000`), decimal fractions and
103
+ # scientific floats (`1.5`, `1E-5`), and the `r` rational / `i` imaginary suffixes (`1r`, `2i`,
104
+ # `0xffr`). A single leading sign is folded into the literal (`-1`, `+1.5`), but a doubled sign
105
+ # (`--1`, `++1`) parses as a unary-operator chain — a `CallNode`, not a literal and is
106
+ # rejected, as are multi-dot junk (`1.2.3`), partial literals (`0x`, `1_`), whitespace-padded
107
+ # strings, and crucially non-ASCII "digits" (full-width `1`, superscript `²`, other Unicode
108
+ # number characters): Ruby's lexer only accepts `[0-9]` in a numeric literal, so those are
109
+ # `CallNode`s too. The stricter base-N predicates below remain proper subsets.
110
+ # - `:decimal_int` is "what `Integer(s, 10)` would parse without remainder"one or more decimal
111
+ # digits, optional leading sign, no whitespace, no fractional tail.
112
+ # - `:octal_int` and `:hex_int` REQUIRE their conventional prefix (`0o` / `0O` / leading `0` for
113
+ # octal; `0x` / `0X` for hex) so the predicate is disjoint from `:decimal_int`. A bare `"755"` is
114
+ # decimal-int-string, not octal-int-string. This matches the typical user intent — a refinement
115
+ # marks a string that "looks like octal", not "happens to be base-8 valid".
145
116
  DECIMAL_INT_STRING_PATTERN = /\A-?\d+\z/
146
117
  OCTAL_INT_STRING_PATTERN = /\A-?(?:0[oO][0-7]+|0[0-7]+)\z/
147
118
  HEX_INT_STRING_PATTERN = /\A-?0[xX][0-9a-fA-F]+\z/
148
119
  private_constant :DECIMAL_INT_STRING_PATTERN,
149
120
  :OCTAL_INT_STRING_PATTERN, :HEX_INT_STRING_PATTERN
150
121
 
151
- # Prism node classes that represent a numeric literal. A
152
- # string is a numeric-string exactly when the parser reduces
153
- # the whole input to a single one of these (the leading sign
154
- # is already folded into the literal by the parser).
122
+ # Prism node classes that represent a numeric literal. A string is a numeric-string exactly when
123
+ # the parser reduces the whole input to a single one of these (the leading sign is already folded
124
+ # into the literal by the parser).
155
125
  NUMERIC_LITERAL_NODES = [
156
126
  Prism::IntegerNode,
157
127
  Prism::FloatNode,
@@ -160,11 +130,9 @@ module Rigor
160
130
  ].freeze
161
131
  private_constant :NUMERIC_LITERAL_NODES
162
132
 
163
- # Cheap pre-filter applied before invoking the parser: every
164
- # Ruby numeric literal starts with an ASCII digit, optionally
165
- # preceded by exactly one sign. Strings that fail this never
166
- # reach Prism (the common non-numeric case stays allocation-
167
- # and parse-free).
133
+ # Cheap pre-filter applied before invoking the parser: every Ruby numeric literal starts with an
134
+ # ASCII digit, optionally preceded by exactly one sign. Strings that fail this never reach Prism
135
+ # (the common non-numeric case stays allocation- and parse-free).
168
136
  NUMERIC_LITERAL_PREFIX = /\A[+-]?\d/
169
137
  private_constant :NUMERIC_LITERAL_PREFIX
170
138
 
@@ -176,10 +144,8 @@ module Rigor
176
144
  def self.ruby_numeric_literal?(value)
177
145
  return false unless value.is_a?(String)
178
146
  return false if value.empty?
179
- # A numeric literal carries no whitespace; reject any
180
- # leading / trailing / interior space so the *whole* string
181
- # must be the literal (Prism would otherwise accept a
182
- # trailing-space `"1 "`).
147
+ # A numeric literal carries no whitespace; reject any leading / trailing / interior space so the
148
+ # *whole* string must be the literal (Prism would otherwise accept a trailing-space `"1 "`).
183
149
  return false if value.match?(/\s/)
184
150
  return false unless value.match?(NUMERIC_LITERAL_PREFIX)
185
151
 
@@ -203,28 +169,21 @@ module Rigor
203
169
  decimal_int: ->(v) { v.is_a?(String) && DECIMAL_INT_STRING_PATTERN.match?(v) },
204
170
  octal_int: ->(v) { v.is_a?(String) && OCTAL_INT_STRING_PATTERN.match?(v) },
205
171
  hex_int: ->(v) { v.is_a?(String) && HEX_INT_STRING_PATTERN.match?(v) },
206
- # `literal-string` is a flow-tracked predicate, not a value-
207
- # level predicate: a String is literal-string when it is
208
- # known to come from a source-code literal (or composition
209
- # of literals). Every concrete `Constant<String>` is
210
- # already literal by construction, so the inspection
211
- # recogniser returns true for any String — the property is
212
- # really tracked in the flow analysis (interpolation,
213
- # concatenation, RBS::Extended `return: literal-string`)
214
- # rather than recovered by inspecting an arbitrary string.
172
+ # `literal-string` is a flow-tracked predicate, not a value-level predicate: a String is
173
+ # literal-string when it is known to come from a source-code literal (or composition of
174
+ # literals). Every concrete `Constant<String>` is already literal by construction, so the
175
+ # inspection recogniser returns true for any String — the property is really tracked in the flow
176
+ # analysis (interpolation, concatenation, RBS::Extended `return: literal-string`) rather than
177
+ # recovered by inspecting an arbitrary string.
215
178
  literal_string: ->(v) { v.is_a?(String) }
216
179
  }.freeze
217
180
 
218
- # Maps `[base_class_name, predicate_id]` pairs to their
219
- # kebab-case canonical name. Registered shapes print
220
- # through `describe`; unregistered combinations fall back
221
- # to the operator form.
181
+ # Maps `[base_class_name, predicate_id]` pairs to their kebab-case canonical name. Registered
182
+ # shapes print through `describe`; unregistered combinations fall back to the operator form.
222
183
  #
223
- # ADR-15 Phase 4b.x — `Ractor.make_shareable` (not `.freeze`)
224
- # because the keys are nested two-element Arrays. Plain
225
- # `.freeze` would leave the inner arrays mutable, so a
226
- # worker Ractor reading `CANONICAL_NAMES[[base, predicate]]`
227
- # would trip `Ractor::IsolationError`.
184
+ # ADR-15 Phase 4b.x — `Ractor.make_shareable` (not `.freeze`) because the keys are nested
185
+ # two-element Arrays. Plain `.freeze` would leave the inner arrays mutable, so a worker Ractor
186
+ # reading `CANONICAL_NAMES[[base, predicate]]` would trip `Ractor::IsolationError`.
228
187
  CANONICAL_NAMES = Ractor.make_shareable({
229
188
  ["String", :lowercase] => "lowercase-string",
230
189
  ["String", :not_lowercase] => "non-lowercase-string",
@@ -239,21 +198,15 @@ module Rigor
239
198
  })
240
199
  private_constant :CANONICAL_NAMES
241
200
 
242
- # Bidirectional `predicate_id ↔ complement_predicate_id`
243
- # registry. `~Refined[base, p]` narrows to
244
- # `Refined[base, COMPLEMENT_PAIRS[p]]` when the part is the
245
- # refinement's base the precise carrier the spec promises
246
- # under the `~T` operator. Predicates without a registered
247
- # complement fall back to the imprecise but sound
248
- # `Difference[part, refined]` carrier from the existing
249
- # narrowing rule.
201
+ # Bidirectional `predicate_id ↔ complement_predicate_id` registry. `~Refined[base, p]` narrows to
202
+ # `Refined[base, COMPLEMENT_PAIRS[p]]` when the part is the refinement's base — the precise carrier
203
+ # the spec promises under the `~T` operator. Predicates without a registered complement fall back
204
+ # to the imprecise but sound `Difference[part, refined]` carrier from the existing narrowing rule.
250
205
  #
251
- # Adding a new pair here is an additive change: register the
252
- # complement predicate in {PREDICATES}, give it a kebab-case
253
- # canonical name in {CANONICAL_NAMES}, and add the bidirectional
254
- # entry below. No call site needs to know about the new pair
255
- # `complement_refined` consults this map and routes through
256
- # the registered complement automatically.
206
+ # Adding a new pair here is an additive change: register the complement predicate in {PREDICATES},
207
+ # give it a kebab-case canonical name in {CANONICAL_NAMES}, and add the bidirectional entry below.
208
+ # No call site needs to know about the new pair — `complement_refined` consults this map and
209
+ # routes through the registered complement automatically.
257
210
  COMPLEMENT_PAIRS = {
258
211
  lowercase: :not_lowercase,
259
212
  not_lowercase: :lowercase,
@@ -7,13 +7,11 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # The singleton type for a Ruby class or module. Inhabitants are the
11
- # class object itself (e.g. the constant `Foo`), not its instances.
12
- # In RBS this corresponds to `singleton(Foo)`.
10
+ # The singleton type for a Ruby class or module. Inhabitants are the class object itself (e.g. the
11
+ # constant `Foo`), not its instances. In RBS this corresponds to `singleton(Foo)`.
13
12
  #
14
- # `Singleton[Foo]` and `Nominal[Foo]` share the same `class_name` but
15
- # are NEVER equal; they describe disjoint values (the class object vs.
16
- # instances of the class).
13
+ # `Singleton[Foo]` and `Nominal[Foo]` share the same `class_name` but are NEVER equal; they describe
14
+ # disjoint values (the class object vs. instances of the class).
17
15
  #
18
16
  # See docs/type-specification/rbs-compatible-types.md (singleton(T)).
19
17
  class Singleton
@@ -7,25 +7,20 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # The class object produced by `Struct.new(:x, :y)` (ADR-48 Struct
11
- # follow-up). The mutable sibling of {DataClass}: it models the *class*
12
- # (the value bound to `Point` in `Point = Struct.new(:x, :y)`, or the
13
- # anonymous superclass in `class Point < Struct.new(:x, :y)`), carrying
14
- # the ordered member-name list so `Point.new(...)` can materialise a
15
- # {StructInstance}.
10
+ # The class object produced by `Struct.new(:x, :y)` (ADR-48 Struct follow-up). The mutable sibling of
11
+ # {DataClass}: it models the *class* (the value bound to `Point` in `Point = Struct.new(:x, :y)`, or
12
+ # the anonymous superclass in `class Point < Struct.new(:x, :y)`), carrying the ordered member-name
13
+ # list so `Point.new(...)` can materialise a {StructInstance}.
16
14
  #
17
- # `keyword_init` records the `Struct.new(..., keyword_init: true)` flag
18
- # so `.new` only materialises a precise instance for the matching call
19
- # form a positional `.new(1, 2)` on a `keyword_init: true` struct, or
20
- # a keyword `.new(x: 1)` on a positional struct, is a different runtime
21
- # shape and must degrade rather than fold a wrong member map.
15
+ # `keyword_init` records the `Struct.new(..., keyword_init: true)` flag so `.new` only materialises a
16
+ # precise instance for the matching call form — a positional `.new(1, 2)` on a `keyword_init: true`
17
+ # struct, or a keyword `.new(x: 1)` on a positional struct, is a different runtime shape and must
18
+ # degrade rather than fold a wrong member map.
22
19
  #
23
- # `class_name` carries the binding name when known (the named-subclass
24
- # form) and is `nil` for the anonymous result of a bare `Struct.new(...)`
25
- # before it is assigned to a constant.
20
+ # `class_name` carries the binding name when known (the named-subclass form) and is `nil` for the
21
+ # anonymous result of a bare `Struct.new(...)` before it is assigned to a constant.
26
22
  #
27
- # Equality and hashing are structural over the member list, the class
28
- # name, and the keyword-init flag.
23
+ # Equality and hashing are structural over the member list, the class name, and the keyword-init flag.
29
24
  #
30
25
  # See docs/adr/48-data-struct-value-folding.md § "Struct follow-up".
31
26
  class StructClass
@@ -7,28 +7,23 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A `Struct.new` value instance (ADR-48 Struct follow-up) —
11
- # `Point.new(1, 2)`. The mutable sibling of {DataInstance}: a closed,
12
- # total, class-tagged member map (member name -> value type),
10
+ # A `Struct.new` value instance (ADR-48 Struct follow-up) — `Point.new(1, 2)`. The mutable sibling of
11
+ # {DataInstance}: a closed, total, class-tagged member map (member name -> value type),
13
12
  # HashShape-shaped but nominal.
14
13
  #
15
- # Unlike {DataInstance}, a `Struct` instance is **mutable** — `s.x = v`,
16
- # `s[:x] = v`, and escape can invalidate the member map. The folding
17
- # tier therefore only projects member reads off a **fresh** instance
18
- # (the transient receiver of a `.new(...).x` / `.with(...).x` chain,
19
- # which provably cannot have been mutated between materialisation and
20
- # the read); a read off a *stored* binding degrades to `Dynamic[top]`
21
- # rather than fold a possibly-stale member value. Promoting the
22
- # fold to mutation-free bound locals is the deferred slice 3 (see ADR).
14
+ # Unlike {DataInstance}, a `Struct` instance is **mutable** — `s.x = v`, `s[:x] = v`, and escape can
15
+ # invalidate the member map. The folding tier therefore only projects member reads off a **fresh**
16
+ # instance (the transient receiver of a `.new(...).x` / `.with(...).x` chain, which provably cannot
17
+ # have been mutated between materialisation and the read); a read off a *stored* binding degrades to
18
+ # `Dynamic[top]` rather than fold a possibly-stale member value. Promoting the fold to mutation-free
19
+ # bound locals is the deferred slice 3 (see ADR).
23
20
  #
24
- # That mutability-gating lives in the dispatch tier (`StructFolding`),
25
- # not the carrier: the carrier itself just records the member map. Like
26
- # {DataInstance}, non-folded methods project to the `Struct` nominal (or
27
- # the tagged class) through {RbsDispatch}'s `receiver_descriptor`, so
28
- # non-member calls resolve without mis-firing undefined-method.
21
+ # That mutability-gating lives in the dispatch tier (`StructFolding`), not the carrier: the carrier
22
+ # itself just records the member map. Like {DataInstance}, non-folded methods project to the
23
+ # `Struct` nominal (or the tagged class) through {RbsDispatch}'s `receiver_descriptor`, so non-member
24
+ # calls resolve without mis-firing undefined-method.
29
25
  #
30
- # Equality and hashing are structural over the (member -> type) map and
31
- # the class name.
26
+ # Equality and hashing are structural over the (member -> type) map and the class name.
32
27
  #
33
28
  # See docs/adr/48-data-struct-value-folding.md § "Struct follow-up".
34
29
  class StructInstance
@@ -67,9 +62,8 @@ module Rigor
67
62
  "#{class_name || 'Struct'}(#{rendered.join(', ')})"
68
63
  end
69
64
 
70
- # Erases to the tagging class nominal (conservative: the structural
71
- # members are not RBS-expressible as a class instance). The
72
- # anonymous case erases to the `Struct` supertype.
65
+ # Erases to the tagging class nominal (conservative: the structural members are not RBS-expressible
66
+ # as a class instance). The anonymous case erases to the `Struct` supertype.
73
67
  def erase_to_rbs
74
68
  name = class_name
75
69
  return "Struct" if name.nil?
@@ -7,30 +7,25 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A heterogeneous, fixed-arity array shape. Inhabitants are exactly
11
- # the Ruby `Array` instances whose length matches `elements.size`
12
- # and whose element at position `i` inhabits `elements[i]`.
10
+ # A heterogeneous, fixed-arity array shape. Inhabitants are exactly the Ruby `Array` instances whose
11
+ # length matches `elements.size` and whose element at position `i` inhabits `elements[i]`.
13
12
  #
14
- # In RBS this corresponds to the tuple form `[A, B, C]`. A tuple
15
- # is always a subtype of `Array[union(elements)]`; method dispatch
16
- # therefore degrades to the underlying `Nominal[Array, [union]]`
17
- # while acceptance keeps the per-position precision.
13
+ # In RBS this corresponds to the tuple form `[A, B, C]`. A tuple is always a subtype of
14
+ # `Array[union(elements)]`; method dispatch therefore degrades to the underlying `Nominal[Array,
15
+ # [union]]` while acceptance keeps the per-position precision.
18
16
  #
19
- # Slice 5 phase 1 introduces the carrier and surfaces it from the
20
- # `ArrayNode` literal handler when every element is a non-splat
21
- # value. Tuple-aware refinements (`tuple[0]`, `tuple.first`,
22
- # destructuring) are implemented in `ShapeDispatch`, which runs
23
- # above {Rigor::Inference::MethodDispatcher::RbsDispatch}.
17
+ # Slice 5 phase 1 introduces the carrier and surfaces it from the `ArrayNode` literal handler when
18
+ # every element is a non-splat value. Tuple-aware refinements (`tuple[0]`, `tuple.first`,
19
+ # destructuring) are implemented in `ShapeDispatch`, which runs above
20
+ # {Rigor::Inference::MethodDispatcher::RbsDispatch}.
24
21
  #
25
- # Equality and hashing are structural across an ordered, frozen
26
- # element list. The empty Tuple `Tuple[]` is permitted; the array
27
- # literal handler keeps `[]` as raw `Nominal[Array]` (no element
28
- # evidence to lock the arity), but external constructors MAY build
29
- # `Tuple[]` directly when the zero-arity discipline is intended.
22
+ # Equality and hashing are structural across an ordered, frozen element list. The empty Tuple
23
+ # `Tuple[]` is permitted; the array literal handler keeps `[]` as raw `Nominal[Array]` (no element
24
+ # evidence to lock the arity), but external constructors MAY build `Tuple[]` directly when the
25
+ # zero-arity discipline is intended.
30
26
  #
31
27
  # See docs/type-specification/rbs-compatible-types.md (tuple) and
32
- # docs/type-specification/rigor-extensions.md (hash-shape and
33
- # tuple kin).
28
+ # docs/type-specification/rigor-extensions.md (hash-shape and tuple kin).
34
29
  class Tuple
35
30
  attr_reader :elements
36
31
 
@@ -6,12 +6,11 @@ require_relative "acceptance_router"
6
6
 
7
7
  module Rigor
8
8
  module Type
9
- # A normalized non-empty union of two or more distinct types. Unions are
10
- # constructed exclusively through Rigor::Type::Combinator.union, which
11
- # flattens nested unions, deduplicates structurally-equal members, and
12
- # collapses single-member or empty results to the appropriate scalar
13
- # type. Direct calls to .new are an internal contract: callers MUST pass
14
- # an already-normalized members array.
9
+ # A normalized non-empty union of two or more distinct types. Unions are constructed exclusively
10
+ # through Rigor::Type::Combinator.union, which flattens nested unions, deduplicates
11
+ # structurally-equal members, and collapses single-member or empty results to the appropriate scalar
12
+ # type. Direct calls to .new are an internal contract: callers MUST pass an already-normalized
13
+ # members array.
15
14
  #
16
15
  # See docs/type-specification/normalization.md.
17
16
  class Union
@@ -26,22 +25,19 @@ module Rigor
26
25
  freeze
27
26
  end
28
27
 
29
- # Display-only adoption of two concise RBS spellings for the
30
- # union (see docs/type-specification/normalization.md § "Interaction
31
- # with display" and rbs-compatible-types.md § "Optionals"). Both are
32
- # purely cosmetic: `@members` keeps every carrier verbatim, so the
33
- # underlying type identity, RBS erasure, and round-trip are unchanged
34
- # — only the human-facing rendering reads like the RBS the user wrote.
28
+ # Display-only adoption of two concise RBS spellings for the union (see
29
+ # docs/type-specification/normalization.md § "Interaction with display" and rbs-compatible-types.md
30
+ # § "Optionals"). Both are purely cosmetic: `@members` keeps every carrier verbatim, so the
31
+ # underlying type identity, RBS erasure, and round-trip are unchanged — only the human-facing
32
+ # rendering reads like the RBS the user wrote.
35
33
  #
36
- # * `true | false` → `bool` (the RBS boolean alias). The
37
- # `bool` token leads the rendering, so `false | Foo | true` reads
38
- # as `bool | Foo` rather than burying the pair mid-list.
39
- # * `T | nil` → `T?` (the RBS optional sugar). Only
40
- # applied when exactly one *logical* member remains beside `nil`,
41
- # matching the rbs gem's own `to_s`: a multi-member union such as
42
- # `Integer | String | nil` stays explicit rather than gaining a
43
- # parenthesised `(Integer | String)?`. The two collapses compose,
44
- # so `false | true | nil` reads as `bool?`.
34
+ # * `true | false` → `bool` (the RBS boolean alias). The `bool` token leads the
35
+ # rendering, so `false | Foo | true` reads as `bool | Foo` rather than burying the pair
36
+ # mid-list.
37
+ # * `T | nil` → `T?` (the RBS optional sugar). Only applied when exactly one
38
+ # *logical* member remains beside `nil`, matching the rbs gem's own `to_s`: a multi-member
39
+ # union such as `Integer | String | nil` stays explicit rather than gaining a parenthesised
40
+ # `(Integer | String)?`. The two collapses compose, so `false | true | nil` reads as `bool?`.
45
41
  def describe(verbosity = :short)
46
42
  return "#{optional_inner(verbosity)}?" if optional?
47
43
 
@@ -53,22 +49,16 @@ module Rigor
53
49
  end
54
50
  end
55
51
 
56
- # ADR-1 § "RBS round-trip is lossless" + the value-lattice
57
- # rule `untyped | T = untyped` (every `T` is gradually
58
- # consistent with `untyped`). When any union member erases
59
- # to `"untyped"`, the whole union erases to `"untyped"`
60
- # the RBS surface has no carrier for "Dynamic-origin
61
- # alongside a static facet", and the gradual-consistency
62
- # contract guarantees the substitution is sound at every
52
+ # ADR-1 § "RBS round-trip is lossless" + the value-lattice rule `untyped | T = untyped` (every `T`
53
+ # is gradually consistent with `untyped`). When any union member erases to `"untyped"`, the whole
54
+ # union erases to `"untyped"` the RBS surface has no carrier for "Dynamic-origin alongside a
55
+ # static facet", and the gradual-consistency contract guarantees the substitution is sound at every
63
56
  # call site.
64
57
  #
65
- # Post-erasure dedupe removes `String | String` artefacts
66
- # that arise when two structurally-distinct `Constant`
67
- # carriers (e.g. `Constant<"Alice">` / `Constant<"Bob">`)
68
- # share an RBS-erased envelope. The members themselves
69
- # are already structurally deduped at construction by
70
- # `Type::Combinator.union`, but the post-erase strings
71
- # can collide.
58
+ # Post-erasure dedupe removes `String | String` artefacts that arise when two structurally-distinct
59
+ # `Constant` carriers (e.g. `Constant<"Alice">` / `Constant<"Bob">`) share an RBS-erased envelope.
60
+ # The members themselves are already structurally deduped at construction by
61
+ # `Type::Combinator.union`, but the post-erase strings can collide.
72
62
  def erase_to_rbs
73
63
  erased = members.map(&:erase_to_rbs)
74
64
  return "untyped" if erased.include?("untyped")
@@ -100,9 +90,8 @@ module Rigor
100
90
 
101
91
  private
102
92
 
103
- # Both `true` and `false` literals are present, so the pair can
104
- # render as `bool`. A union carrying only one of them stays a
105
- # plain literal (`true` / `false`) — that asymmetry is meaningful.
93
+ # Both `true` and `false` literals are present, so the pair can render as `bool`. A union carrying
94
+ # only one of them stays a plain literal (`true` / `false`) — that asymmetry is meaningful.
106
95
  def boolean_pair?
107
96
  members.any? { |m| boolean_literal?(m, true) } &&
108
97
  members.any? { |m| boolean_literal?(m, false) }
@@ -121,10 +110,9 @@ module Rigor
121
110
  member.is_a?(Constant) && member.value.nil?
122
111
  end
123
112
 
124
- # `nil` is present and, once the `bool` pair is treated as a
125
- # single logical member, exactly one non-`nil` member remains —
126
- # so the whole union renders as `T?`. Counting the bool pair as
127
- # one is what lets `false | true | nil` reach `bool?`.
113
+ # `nil` is present and, once the `bool` pair is treated as a single logical member, exactly one
114
+ # non-`nil` member remains — so the whole union renders as `T?`. Counting the bool pair as one is
115
+ # what lets `false | true | nil` reach `bool?`.
128
116
  def optional?
129
117
  return false unless members.any? { |m| nil_literal?(m) }
130
118
 
@@ -9,20 +9,13 @@ module Rigor
9
9
  # sequence of type-argument nodes already produced by the
10
10
  # parser at one level of depth.
11
11
  #
12
- # Args are themselves {TypeNode::Identifier} or
13
- # {TypeNode::Generic}. Nested generics ride the same shape:
14
- # `Pick<Address, "name" | "surname">` reaches the resolver as
15
- # `Generic("Pick", [Identifier("Address"), Generic("Union", [...])])`
16
- # — actually the union spelling depends on the parser's
17
- # eventual convention (slice 3 pins it); for now the field
18
- # set is the only public commitment.
12
+ # Args are themselves {TypeNode::Identifier} or {TypeNode::Generic}. Nested generics ride the same
13
+ # shape: `Pick<Address, "name" | "surname">` reaches the resolver as
14
+ # `Generic("Pick", [Identifier("Address"), Union([...])])`.
19
15
  #
20
- # The carrier is intentionally permissive about `args.size`.
21
- # The grammar-level rule "no brackets Identifier; brackets
22
- # Generic" lives on the parser side; nothing here forbids a
23
- # zero-arg Generic so plugins can synthesise nodes for
24
- # diagnostic or testing purposes without the parser fighting
25
- # back.
16
+ # The carrier is intentionally permissive about `args.size`. The grammar-level rule "no brackets ⇒
17
+ # Identifier; brackets Generic" lives on the parser side; nothing here forbids a zero-arg Generic
18
+ # so plugins can synthesise nodes for diagnostic or testing purposes without the parser fighting back.
26
19
  class Generic < Data.define(:head, :args)
27
20
  def initialize(head:, args:)
28
21
  unless head.is_a?(String) && !head.empty?
@@ -38,10 +31,9 @@ module Rigor
38
31
  "TypeNode::IntegerLiteral, got #{args.inspect}"
39
32
  end
40
33
 
41
- # Freeze the String head + Array args so the Data
42
- # object is `Ractor.shareable?`. Each `a` is already a
43
- # shareable TypeNode value object (checked above), so
44
- # freezing the wrapping Array is sufficient.
34
+ # Freeze the String head + Array args so the Data object is `Ractor.shareable?`. Each `a` is
35
+ # already a shareable TypeNode value object (checked above), so freezing the wrapping Array
36
+ # is sufficient.
45
37
  frozen_head = head.frozen? ? head : head.dup.freeze
46
38
  frozen_args = args.frozen? ? args : args.dup.freeze
47
39
  super(head: frozen_head, args: frozen_args)
@@ -49,15 +41,11 @@ module Rigor
49
41
 
50
42
  private
51
43
 
52
- # ADR-13 slice 3 expanded the accepted set to include
53
- # {IntegerLiteral} so the parser can emit a uniform AST for
54
- # `int<5, 10>` (angle bounds) and `int_mask[1, 2, 4]`
55
- # (square-bracketed bitflag union). The follow-up further
56
- # admits {SymbolLiteral} / {StringLiteral} / {IndexedAccess}
57
- # / {Union} so `Pick[T, :a | "b"]` carries through to the
58
- # resolver as a uniform AST. Slice 1 originally accepted
59
- # only `Identifier` / `Generic`; every later addition stays
60
- # additive — every slice-1-shape Generic remains valid.
44
+ # ADR-13 slice 3 expanded the accepted set to include {IntegerLiteral} so the parser can emit a
45
+ # uniform AST for `int<5, 10>` (angle bounds) and `int_mask[1, 2, 4]` (square-bracketed bitflag
46
+ # union). The follow-up further admits {SymbolLiteral} / {StringLiteral} / {IndexedAccess} /
47
+ # {Union} so `Pick[T, :a | "b"]` carries through to the resolver as a uniform AST. Every addition
48
+ # stays additive every earlier-shape Generic remains valid.
61
49
  def valid_arg?(arg)
62
50
  arg.is_a?(Identifier) || arg.is_a?(Generic) || arg.is_a?(IntegerLiteral) ||
63
51
  arg.is_a?(SymbolLiteral) || arg.is_a?(StringLiteral) ||