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,15 +7,13 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A literal carrier under ADR-3 OQ1 Option C (Hybrid). Wraps a Ruby
11
- # literal value of one of the supported immutable-ish classes. Compound
12
- # literal shapes (Tuple, HashShape, Record) get dedicated classes in
13
- # later slices; Range is carried only when both static endpoints are
14
- # known enough for tuple slicing.
10
+ # A literal carrier under ADR-3 OQ1 Option C (Hybrid). Wraps a Ruby literal value of one of the
11
+ # supported immutable-ish classes. Compound literal shapes (Tuple, HashShape, Record) get dedicated
12
+ # classes in later slices; Range is carried only when both static endpoints are known enough for
13
+ # tuple slicing.
15
14
  #
16
- # See docs/adr/4-type-inference-engine.md for the tentative answer to
17
- # the open question and docs/type-specification/rigor-extensions.md for
18
- # the refinement neighbourhood this carrier lives in.
15
+ # See docs/adr/4-type-inference-engine.md for the tentative answer to the open question and
16
+ # docs/type-specification/rigor-extensions.md for the refinement neighbourhood this carrier lives in.
19
17
  class Constant
20
18
  SCALAR_CLASSES = [
21
19
  Integer,
@@ -28,10 +26,9 @@ module Rigor
28
26
  Regexp,
29
27
  Pathname,
30
28
  ::Set,
31
- # `Date` covers `DateTime` (a subclass). `Time` is core.
32
- # Both arise only from deterministic constructor folding
33
- # (`Date.new` / `Time.utc`) — there is no Date / Time
34
- # literal node — so a `Constant` carrier is always sound.
29
+ # `Date` covers `DateTime` (a subclass). `Time` is core. Both arise only from deterministic
30
+ # constructor folding (`Date.new` / `Time.utc`) there is no Date / Time literal node — so a
31
+ # `Constant` carrier is always sound.
35
32
  Date,
36
33
  Time,
37
34
  TrueClass,
@@ -56,22 +53,18 @@ module Rigor
56
53
  freeze
57
54
  end
58
55
 
59
- # Mutable-ish carriers are stored as a frozen copy so a later
60
- # in-place mutation cannot rewrite the literal under us. `Time`
61
- # joins `String` / `Set` here: `Time#localtime` mutates the
62
- # receiver's zone in place, so the carrier holds a frozen copy
63
- # (the catalog also blocklists the mutators). `Date` is already
64
- # immutable, but is duped-and-frozen for symmetry.
56
+ # Mutable-ish carriers are stored as a frozen copy so a later in-place mutation cannot rewrite the
57
+ # literal under us. `Time` joins `String` / `Set` here: `Time#localtime` mutates the receiver's
58
+ # zone in place, so the carrier holds a frozen copy (the catalog also blocklists the mutators).
59
+ # `Date` is already immutable, but is duped-and-frozen for symmetry.
65
60
  def freezable_carrier?(value)
66
61
  value.is_a?(String) || value.is_a?(::Set) ||
67
62
  value.is_a?(Date) || value.is_a?(Time)
68
63
  end
69
64
 
70
- # `Date#inspect` / `DateTime#inspect` spell out the internal
71
- # astronomical-Julian-day representation, which is unreadable
72
- # in a diagnostic. ISO-8601 is the compact, deterministic
73
- # form. `Time#inspect` is already compact (`2026-01-01
74
- # 00:00:00 UTC`), so it keeps the default.
65
+ # `Date#inspect` / `DateTime#inspect` spell out the internal astronomical-Julian-day representation,
66
+ # which is unreadable in a diagnostic. ISO-8601 is the compact, deterministic form. `Time#inspect`
67
+ # is already compact (`2026-01-01 00:00:00 UTC`), so it keeps the default.
75
68
  def describe(_verbosity = :short)
76
69
  case value
77
70
  when Date then value.iso8601
@@ -79,17 +72,13 @@ module Rigor
79
72
  end
80
73
  end
81
74
 
82
- # RBS supports `Literal` types for booleans, nil, integer
83
- # literals (positive and negative), symbol literals, and
84
- # string literals. Erasing to these preserves the
85
- # carrier's precision at the RBS boundary `Constant<64>`
86
- # round-trips as `64`, not as `Integer` and
87
- # `RbsTypeTranslator#translate_literal` already maps the
88
- # parsed RBS Literal back to `Constant`. Scalar carriers
89
- # without RBS Literal support (Float, Range, Rational,
90
- # Complex, Regexp, Pathname) keep their pre-existing
91
- # widen-to-class-name behaviour because RBS rejects their
92
- # literal spellings as syntax errors.
75
+ # RBS supports `Literal` types for booleans, nil, integer literals (positive and negative), symbol
76
+ # literals, and string literals. Erasing to these preserves the carrier's precision at the RBS
77
+ # boundary `Constant<64>` round-trips as `64`, not as `Integer` — and
78
+ # `RbsTypeTranslator#translate_literal` already maps the parsed RBS Literal back to `Constant`.
79
+ # Scalar carriers without RBS Literal support (Float, Range, Rational, Complex, Regexp, Pathname)
80
+ # keep their pre-existing widen-to-class-name behaviour because RBS rejects their literal spellings
81
+ # as syntax errors.
93
82
  def erase_to_rbs
94
83
  case value
95
84
  when true then "true"
@@ -7,23 +7,18 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # The class object produced by `Data.define(:x, :y)` (ADR-48). Models
11
- # the *class*, not an instance the value bound to `Point` in
12
- # `Point = Data.define(:x, :y)` or the anonymous superclass in
13
- # `class Point < Data.define(:x, :y)`. Parameterised by the ordered
14
- # member-name list so that `Point.new(...)` can materialise a precise
15
- # {DataInstance}.
10
+ # The class object produced by `Data.define(:x, :y)` (ADR-48). Models the *class*, not an instance —
11
+ # the value bound to `Point` in `Point = Data.define(:x, :y)` or the anonymous superclass in `class
12
+ # Point < Data.define(:x, :y)`. Parameterised by the ordered member-name list so that `Point.new(...)`
13
+ # can materialise a precise {DataInstance}.
16
14
  #
17
- # `class_name` carries the binding name when known (the named-subclass
18
- # form, ADR-48 slice 3) and is `nil` for the anonymous result of a bare
19
- # `Data.define(...)` before it is assigned to a constant. It tags the
20
- # instances `.new` produces; it does not change the carrier's folding
21
- # behaviour.
15
+ # `class_name` carries the binding name when known (the named-subclass form, ADR-48 slice 3) and is
16
+ # `nil` for the anonymous result of a bare `Data.define(...)` before it is assigned to a constant. It
17
+ # tags the instances `.new` produces; it does not change the carrier's folding behaviour.
22
18
  #
23
- # Equality and hashing are structural over the member list and the
24
- # class name. Two distinct `Data.define(:x)` results are equal *as
25
- # types* they describe the same shape; the engine distinguishes the
26
- # constants they are bound to by the binding, not by the carrier.
19
+ # Equality and hashing are structural over the member list and the class name. Two distinct
20
+ # `Data.define(:x)` results are equal *as types* — they describe the same shape; the engine
21
+ # distinguishes the constants they are bound to by the binding, not by the carrier.
27
22
  #
28
23
  # See docs/adr/48-data-struct-value-folding.md.
29
24
  class DataClass
@@ -7,26 +7,21 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A `Data.define` value instance (ADR-48) — `Point.new(1, 2)`. Models a
11
- # closed, total, class-tagged member map (member name -> value type).
12
- # HashShape-shaped, but nominal: a `DataInstance` tagged `Point` is a
13
- # different type from one tagged `Line` even with identical members,
14
- # and it erases to its class nominal rather than an RBS record.
10
+ # A `Data.define` value instance (ADR-48) — `Point.new(1, 2)`. Models a closed, total, class-tagged
11
+ # member map (member name -> value type). HashShape-shaped, but nominal: a `DataInstance` tagged
12
+ # `Point` is a different type from one tagged `Line` even with identical members, and it erases to
13
+ # its class nominal rather than an RBS record.
15
14
  #
16
- # `Data` instances are frozen, so the member map is sound for the
17
- # instance's whole lifetime — the reason `Data` is the first target
18
- # (a `Struct` instance can be mutated through a setter or `[]=`, which
19
- # would invalidate the map; that follow-up is deferred — see ADR-48).
15
+ # `Data` instances are frozen, so the member map is sound for the instance's whole lifetime — the
16
+ # reason `Data` is the first target (a `Struct` instance can be mutated through a setter or `[]=`,
17
+ # which would invalidate the map; that follow-up is deferred see ADR-48).
20
18
  #
21
- # Member reads fold to the member's type (`Point.new(1, 2).x` ->
22
- # `Constant[1]`); `[]`, `to_h`, `deconstruct`, `deconstruct_keys`,
23
- # `members`, and `with` project precisely. Methods this carrier does
24
- # not fold project to the `Data` nominal (or the tagged class) through
25
- # {RbsDispatch}'s `receiver_descriptor`, so non-member calls resolve
26
- # without mis-firing undefined-method.
19
+ # Member reads fold to the member's type (`Point.new(1, 2).x` -> `Constant[1]`); `[]`, `to_h`,
20
+ # `deconstruct`, `deconstruct_keys`, `members`, and `with` project precisely. Methods this carrier
21
+ # does not fold project to the `Data` nominal (or the tagged class) through {RbsDispatch}'s
22
+ # `receiver_descriptor`, so non-member calls resolve without mis-firing undefined-method.
27
23
  #
28
- # Equality and hashing are structural over the (member -> type) map and
29
- # the class name.
24
+ # Equality and hashing are structural over the (member -> type) map and the class name.
30
25
  #
31
26
  # See docs/adr/48-data-struct-value-folding.md.
32
27
  class DataInstance
@@ -65,9 +60,8 @@ module Rigor
65
60
  "#{class_name || 'Data'}(#{rendered.join(', ')})"
66
61
  end
67
62
 
68
- # Erases to the tagging class nominal (conservative: the structural
69
- # members are not RBS-expressible as a class instance). The
70
- # anonymous case erases to the `Data` supertype.
63
+ # Erases to the tagging class nominal (conservative: the structural members are not RBS-expressible
64
+ # as a class instance). The anonymous case erases to the `Data` supertype.
71
65
  def erase_to_rbs
72
66
  name = class_name
73
67
  return "Data" if name.nil?
@@ -6,35 +6,26 @@ require_relative "acceptance_router"
6
6
 
7
7
  module Rigor
8
8
  module Type
9
- # `Difference[base, removed]` — the value set of `base` minus
10
- # the value set of `removed`. Implements the point-removal
11
- # half of the OQ3 refinement-carrier strategy
12
- # ([ADR-3](docs/adr/3-type-representation.md), Working
13
- # Decision Option C):
9
+ # `Difference[base, removed]` — the value set of `base` minus the value set of `removed`. Implements
10
+ # the point-removal half of the OQ3 refinement-carrier strategy ([ADR-3](docs/adr/3-type-representation.md),
11
+ # Working Decision Option C):
14
12
  #
15
13
  # non-empty-string = Difference[Nominal[String], Constant[""]]
16
14
  # non-zero-int = Difference[Nominal[Integer], Constant[0]]
17
15
  # non-empty-array[T] = Difference[Nominal[Array, [T]], Tuple[]]
18
16
  # non-empty-hash[K,V] = Difference[Nominal[Hash, [K,V]], HashShape{}]
19
17
  #
20
- # The carrier itself is structural: it stores `base` and
21
- # `removed` as inner `Type` references and answers projection
22
- # / acceptance / display questions by composing those inner
23
- # answers per the lattice algebra in
24
- # [`value-lattice.md`](docs/type-specification/value-lattice.md).
25
- # The canonical-name registry (display side) lives in
26
- # `Rigor::Type::Combinator` and prints kebab-case names like
27
- # `non-empty-string` for the recognised shapes; unrecognised
28
- # differences fall back to the raw `base - removed`
29
- # operator form per [`type-operators.md`](docs/type-specification/type-operators.md).
18
+ # The carrier itself is structural: it stores `base` and `removed` as inner `Type` references and
19
+ # answers projection / acceptance / display questions by composing those inner answers per the
20
+ # lattice algebra in [`value-lattice.md`](docs/type-specification/value-lattice.md). The
21
+ # canonical-name registry (display side) lives in `Rigor::Type::Combinator` and prints kebab-case
22
+ # names like `non-empty-string` for the recognised shapes; unrecognised differences fall back to the
23
+ # raw `base - removed` operator form per [`type-operators.md`](docs/type-specification/type-operators.md).
30
24
  #
31
- # Construction goes through `Type::Combinator.difference` /
32
- # `Combinator.non_empty_string` etc. — direct `.new` calls
33
- # are an internal contract; callers MUST ensure both bounds
34
- # are valid `Rigor::Type` values and that `removed` is a
35
- # subtype-or-equal of `base` (otherwise the difference does
36
- # not narrow anything and a normalisation upstream should
37
- # collapse to `base`).
25
+ # Construction goes through `Type::Combinator.difference` / `Combinator.non_empty_string` etc. —
26
+ # direct `.new` calls are an internal contract; callers MUST ensure both bounds are valid
27
+ # `Rigor::Type` values and that `removed` is a subtype-or-equal of `base` (otherwise the difference
28
+ # does not narrow anything and a normalisation upstream should collapse to `base`).
38
29
  class Difference
39
30
  attr_reader :base, :removed
40
31
 
@@ -51,8 +42,8 @@ module Rigor
51
42
  "#{base.describe(verbosity)} - #{removed.describe(verbosity)}"
52
43
  end
53
44
 
54
- # Erases to the base nominal: every refinement MUST erase
55
- # to its base per [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
45
+ # Erases to the base nominal: every refinement MUST erase to its base per
46
+ # [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
56
47
  def erase_to_rbs
57
48
  base.erase_to_rbs
58
49
  end
@@ -81,15 +72,13 @@ module Rigor
81
72
 
82
73
  private
83
74
 
84
- # Renders the kebab-case shorthand for recognised
85
- # imported-built-in shapes. Parameterised bases keep their
86
- # type-args in the canonical form (`non-empty-array[T]`,
87
- # `non-empty-hash[K, V]`) so element-precision survives the
88
- # display round-trip. Unrecognised shapes fall back to the
89
- # raw `base - removed` operator form.
75
+ # Renders the kebab-case shorthand for recognised imported-built-in shapes. Parameterised bases
76
+ # keep their type-args in the canonical form (`non-empty-array[T]`, `non-empty-hash[K, V]`) so
77
+ # element-precision survives the display round-trip. Unrecognised shapes fall back to the raw
78
+ # `base - removed` operator form.
90
79
  #
91
- # The recognised set is kept in sync with the imported-built-in
92
- # catalogue ([`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md)).
80
+ # The recognised set is kept in sync with the imported-built-in catalogue
81
+ # ([`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md)).
93
82
  def canonical_name
94
83
  return nil unless base.is_a?(Nominal)
95
84
 
@@ -6,11 +6,9 @@ require_relative "acceptance_router"
6
6
 
7
7
  module Rigor
8
8
  module Type
9
- # The dynamic-origin wrapper: marks values whose type came from an
10
- # unchecked source. Carries a static facet that records the analyzer's
11
- # best static knowledge. See docs/type-specification/value-lattice.md
12
- # for the algebra and docs/type-specification/special-types.md for the
13
- # untyped/Dynamic[T] relationship.
9
+ # The dynamic-origin wrapper: marks values whose type came from an unchecked source. Carries a static
10
+ # facet that records the analyzer's best static knowledge. See docs/type-specification/value-lattice.md
11
+ # for the algebra and docs/type-specification/special-types.md for the untyped/Dynamic[T] relationship.
14
12
  #
15
13
  # Construct via Rigor::Type::Combinator.dynamic(static_facet).
16
14
  class Dynamic
@@ -7,20 +7,16 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A hash shape with statically known keys. Inhabitants are Ruby
11
- # `Hash` instances whose known entries inhabit the corresponding
12
- # value types. RBS records correspond to the exact closed subset;
13
- # Rigor extends that carrier with optional keys, read-only entry
14
- # views, and an open/closed extra-key policy.
10
+ # A hash shape with statically known keys. Inhabitants are Ruby `Hash` instances whose known entries
11
+ # inhabit the corresponding value types. RBS records correspond to the exact closed subset; Rigor
12
+ # extends that carrier with optional keys, read-only entry views, and an open/closed extra-key policy.
15
13
  #
16
- # Keys are restricted to Symbol and String values. Exact closed
17
- # symbol-keyed shapes erase to the RBS record syntax
18
- # `{ a: Integer, ?b: String }`; all other shapes degrade to
19
- # `Hash[K, V]` or raw `Hash` when no useful bounds are available.
14
+ # Keys are restricted to Symbol and String values. Exact closed symbol-keyed shapes erase to the RBS
15
+ # record syntax `{ a: Integer, ?b: String }`; all other shapes degrade to `Hash[K, V]` or raw `Hash`
16
+ # when no useful bounds are available.
20
17
  #
21
- # Equality and hashing are structural over the (key -> Rigor::Type)
22
- # pair set and policy fields. Hash insertion order is preserved by
23
- # the underlying storage but does NOT affect equality (matching
18
+ # Equality and hashing are structural over the (key -> Rigor::Type) pair set and policy fields. Hash
19
+ # insertion order is preserved by the underlying storage but does NOT affect equality (matching
24
20
  # Ruby's `Hash#==`).
25
21
  #
26
22
  # See docs/type-specification/rbs-compatible-types.md (records) and
@@ -29,6 +25,9 @@ module Rigor
29
25
  ALLOWED_KEY_CLASSES = [Symbol, String].freeze
30
26
  EXTRA_KEY_POLICIES = %i[open closed].freeze
31
27
  POLICY_KEYWORDS = %i[required_keys optional_keys read_only_keys extra_keys].freeze
28
+ # A Symbol whose text matches renders as a bare RBS record key (`lang:`) in {#erase_to_rbs};
29
+ # anything else must be quoted with a fat arrow (`"data-contrast" =>`). See {#erase_key_prefix}.
30
+ BARE_RECORD_KEY = /\A[A-Za-z_][A-Za-z0-9_]*[?!]?\z/
32
31
 
33
32
  attr_reader :pairs, :required_keys, :optional_keys, :read_only_keys, :extra_keys
34
33
 
@@ -65,15 +64,14 @@ module Rigor
65
64
  "{ #{rendered.join(', ')} }"
66
65
  end
67
66
 
68
- # Erases to the RBS record form `{ a: Integer, ?b: String }`
69
- # for exact closed symbol-keyed shapes. Open shapes and
70
- # string-keyed closed shapes degrade to a generic Hash bound.
67
+ # Erases to the RBS record form `{ a: Integer, ?b: String }` for exact closed symbol-keyed shapes.
68
+ # Open shapes and string-keyed closed shapes degrade to a generic Hash bound.
71
69
  def erase_to_rbs
72
70
  return "{}" if pairs.empty? && closed?
73
71
  return hash_erasure unless closed?
74
72
  return hash_erasure if pairs.each_key.any? { |k| !k.is_a?(Symbol) }
75
73
 
76
- rendered = pairs.map { |k, v| "#{record_key(k)}: #{v.erase_to_rbs}" }
74
+ rendered = pairs.map { |k, v| "#{erase_key_prefix(k)} #{v.erase_to_rbs}" }
77
75
  "{ #{rendered.join(', ')} }"
78
76
  end
79
77
 
@@ -196,6 +194,9 @@ module Rigor
196
194
  "#{prefix.join(' ')} #{value.describe(verbosity)}"
197
195
  end
198
196
 
197
+ # `describe` is a human-facing display contract (it feeds diagnostic messages, never a parser),
198
+ # so it keeps the compact `"a":` form for a quoted key. The RBS-erasure path uses
199
+ # {#erase_key_prefix} instead, which must emit a parseable key.
199
200
  def render_key(key)
200
201
  case key
201
202
  when Symbol then key.to_s
@@ -203,8 +204,21 @@ module Rigor
203
204
  end
204
205
  end
205
206
 
206
- def record_key(key)
207
- optional_key?(key) ? "?#{key}" : key.to_s
207
+ # An RBS record entry's key + separator, for the {#erase_to_rbs} path (which only ever sees
208
+ # Symbol keys string-keyed shapes degrade to `Hash[...]` before reaching here). A Symbol whose
209
+ # text is a plain identifier is a bare key with a colon (`lang:`); a hyphenated / punctuated
210
+ # Symbol MUST use the quoted `"data-contrast" =>` form, because RBS rejects both a bare
211
+ # non-identifier (`data-contrast:`) and a quoted key with a colon (`"data-contrast":`). Getting
212
+ # this wrong made sig-gen emit an unparseable Mastodon `html_attributes` shape that crashed the
213
+ # whole RBS env build. Valid identifier keys (incl. Ruby keywords like `class`) keep the colon
214
+ # form, so only genuinely non-identifier keys change. Optional keys carry a leading `?`.
215
+ def erase_key_prefix(key)
216
+ optional = optional_key?(key) ? "?" : ""
217
+ if BARE_RECORD_KEY.match?(key.to_s)
218
+ "#{optional}#{key}:"
219
+ else
220
+ "#{optional}#{key.to_s.inspect} =>"
221
+ end
208
222
  end
209
223
 
210
224
  def hash_erasure
@@ -7,23 +7,19 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # A bounded integer range carrier. Each bound is either an `Integer`
11
- # or one of the symbolic infinities `:neg_infinity` / `:pos_infinity`.
12
- # Inspired by PHPStan's `int<min, max>` family the named aliases
13
- # `positive-int` (1..), `non-negative-int` (0..), `negative-int`
14
- # (..-1), `non-positive-int` (..0) all surface through this single
15
- # carrier and are recovered in `describe` for human-friendly output.
10
+ # A bounded integer range carrier. Each bound is either an `Integer` or one of the symbolic
11
+ # infinities `:neg_infinity` / `:pos_infinity`. Inspired by PHPStan's `int<min, max>` family — the
12
+ # named aliases `positive-int` (1..), `non-negative-int` (0..), `negative-int` (..-1),
13
+ # `non-positive-int` (..0) all surface through this single carrier and are recovered in `describe`
14
+ # for human-friendly output.
16
15
  #
17
16
  # Constraints on construction:
18
- # - both bounds must be either `Integer` or one of the two infinity
19
- # sentinels;
17
+ # - both bounds must be either `Integer` or one of the two infinity sentinels;
20
18
  # - if both bounds are concrete, `min <= max` must hold;
21
- # - the universal case `(-∞, +∞)` is structurally distinct from
22
- # `Nominal[Integer]` it carries no extra information today but
23
- # keeps the carrier closed under range narrowing.
19
+ # - the universal case `(-∞, +∞)` is structurally distinct from `Nominal[Integer]` — it carries no
20
+ # extra information today but keeps the carrier closed under range narrowing.
24
21
  #
25
- # Erasure to RBS is always "Integer": RBS itself does not natively
26
- # express bounded integer ranges.
22
+ # Erasure to RBS is always "Integer": RBS itself does not natively express bounded integer ranges.
27
23
  class IntegerRange
28
24
  NEG_INFINITY = :neg_infinity
29
25
  POS_INFINITY = :pos_infinity
@@ -64,9 +60,8 @@ module Rigor
64
60
  int.between?(lower, upper)
65
61
  end
66
62
 
67
- # Returns the lower bound as a numeric (with `-Float::INFINITY` for
68
- # `:neg_infinity`). Use this in arithmetic comparisons; never compare
69
- # `:neg_infinity` directly with an `Integer`.
63
+ # Returns the lower bound as a numeric (with `-Float::INFINITY` for `:neg_infinity`). Use this in
64
+ # arithmetic comparisons; never compare `:neg_infinity` directly with an `Integer`.
70
65
  def lower
71
66
  m = min
72
67
  return m if m.is_a?(Integer)
@@ -7,36 +7,28 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # `Intersection[M1, M2, …]` — value set is the meet of every
11
- # member's value set. The carrier composes refinements that
12
- # share a base, in particular the catalogued
13
- # `non-empty-lowercase-string` (= `Difference[String, ""] &
14
- # Refined[String, :lowercase]`) and
15
- # `non-empty-uppercase-string` shapes from
16
- # [`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md).
17
- # See [ADR-3](docs/adr/3-type-representation.md) for the
18
- # OQ3 working decision and the rationale for keeping
19
- # Intersection a thin wrapper rather than per-shape carriers.
10
+ # `Intersection[M1, M2, …]` — value set is the meet of every member's value set. The carrier composes
11
+ # refinements that share a base, in particular the catalogued `non-empty-lowercase-string` (=
12
+ # `Difference[String, ""] & Refined[String, :lowercase]`) and `non-empty-uppercase-string` shapes
13
+ # from [`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md). See
14
+ # [ADR-3](docs/adr/3-type-representation.md) for the OQ3 working decision and the rationale for
15
+ # keeping Intersection a thin wrapper rather than per-shape carriers.
20
16
  #
21
- # Construction MUST go through `Type::Combinator.intersection`
22
- # (or the per-name factories
23
- # `Combinator.non_empty_lowercase_string` /
24
- # `Combinator.non_empty_uppercase_string`). The factory:
17
+ # Construction MUST go through `Type::Combinator.intersection` (or the per-name factories
18
+ # `Combinator.non_empty_lowercase_string` / `Combinator.non_empty_uppercase_string`). The factory:
25
19
  #
26
20
  # - flattens nested intersections,
27
21
  # - drops `Top` members (Top is the identity of intersection),
28
22
  # - collapses to `Bot` if any member is `Bot` (Bot is absorbing),
29
23
  # - deduplicates structurally-equal members,
30
- # - sorts the surviving members by `describe(:short)` so two
31
- # structurally-equal intersections built in different orders
32
- # compare equal,
24
+ # - sorts the surviving members by `describe(:short)` so two structurally-equal intersections built
25
+ # in different orders compare equal,
33
26
  # - returns `Top` for the empty intersection,
34
- # - returns the lone member for a 1-element intersection (so
35
- # the carrier is never inhabited by a degenerate single-member
36
- # shape).
27
+ # - returns the lone member for a 1-element intersection (so the carrier is never inhabited by a
28
+ # degenerate single-member shape).
37
29
  #
38
- # Direct `.new` callers MUST pass an already-normalised member
39
- # list and are expected to be tests or the combinator itself.
30
+ # Direct `.new` callers MUST pass an already-normalised member list and are expected to be tests or
31
+ # the combinator itself.
40
32
  class Intersection
41
33
  attr_reader :members
42
34
 
@@ -52,13 +44,10 @@ module Rigor
52
44
  members.map { |m| m.describe(verbosity) }.join(" & ")
53
45
  end
54
46
 
55
- # An intersection of refinements over the same base type
56
- # erases to that base. We use the first member's erasure
57
- # because the v0.0.4 catalogue (`non-empty-lowercase-string`
58
- # etc.) is restricted to same-base composition; richer
59
- # cross-base intersections will need a stricter erasure
60
- # rule (likely "lowest common ancestor" via the inference
61
- # engine's class hierarchy).
47
+ # An intersection of refinements over the same base type erases to that base. We use the first
48
+ # member's erasure because the v0.0.4 catalogue (`non-empty-lowercase-string` etc.) is restricted
49
+ # to same-base composition; richer cross-base intersections will need a stricter erasure rule
50
+ # (likely "lowest common ancestor" via the inference engine's class hierarchy).
62
51
  def erase_to_rbs
63
52
  members.first.erase_to_rbs
64
53
  end
@@ -77,15 +66,13 @@ module Rigor
77
66
 
78
67
  private
79
68
 
80
- # Maps a structurally-recognised composite shape to its
81
- # kebab-case canonical name. The recognised set is kept in
82
- # sync with the imported-built-in catalogue
69
+ # Maps a structurally-recognised composite shape to its kebab-case canonical name. The recognised
70
+ # set is kept in sync with the imported-built-in catalogue
83
71
  # ([`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md)).
84
72
  #
85
- # Detection is order-independent — `Combinator.intersection`
86
- # sorts the canonical member list, but reading the registry
87
- # the other way around (a user-authored Intersection built
88
- # in any order) MUST still print in its canonical spelling.
73
+ # Detection is order-independent — `Combinator.intersection` sorts the canonical member list, but
74
+ # reading the registry the other way around (a user-authored Intersection built in any order) MUST
75
+ # still print in its canonical spelling.
89
76
  def canonical_name
90
77
  return nil unless members.size == 2
91
78
 
@@ -99,12 +86,10 @@ module Rigor
99
86
  end
100
87
  end
101
88
 
102
- # Returns a stable role tag for the recognised composite
103
- # members so `canonical_name` can pattern-match on a sorted
104
- # role pair regardless of construction order. Returns nil
105
- # when the member is not part of any catalogued composite —
106
- # any nil contribution disqualifies the canonical-name path
107
- # and the operator-form fallback kicks in.
89
+ # Returns a stable role tag for the recognised composite members so `canonical_name` can
90
+ # pattern-match on a sorted role pair regardless of construction order. Returns nil when the
91
+ # member is not part of any catalogued composite — any nil contribution disqualifies the
92
+ # canonical-name path and the operator-form fallback kicks in.
108
93
  def canonical_role(member)
109
94
  case member
110
95
  when Difference
@@ -7,23 +7,18 @@ require_relative "plain_lattice"
7
7
 
8
8
  module Rigor
9
9
  module Type
10
- # An instance type for a Ruby class or module. The class is identified by
11
- # its fully-qualified Ruby name; the registry attached to the
12
- # environment owns the class lookup.
10
+ # An instance type for a Ruby class or module. The class is identified by its fully-qualified Ruby
11
+ # name; the registry attached to the environment owns the class lookup.
13
12
  #
14
- # Slice 4 phase 2d adds `type_args`: an ordered, frozen array of
15
- # `Rigor::Type` values that carry the receiver's generic
16
- # instantiation. The empty array is the canonical "raw" form
17
- # (`Nominal[Array]`); a non-empty array represents an applied
18
- # generic (`Nominal[Array, [Integer]]`). Two Nominals are
19
- # structurally equal only when their `class_name` AND `type_args`
20
- # match, so the raw form and any applied form are intentionally
21
- # distinct values. Acceptance routes treat the raw form leniently
22
- # for backward compatibility with phase 2b call sites that have not
23
- # yet learned to carry generics.
13
+ # Slice 4 phase 2d adds `type_args`: an ordered, frozen array of `Rigor::Type` values that carry the
14
+ # receiver's generic instantiation. The empty array is the canonical "raw" form (`Nominal[Array]`); a
15
+ # non-empty array represents an applied generic (`Nominal[Array, [Integer]]`). Two Nominals are
16
+ # structurally equal only when their `class_name` AND `type_args` match, so the raw form and any
17
+ # applied form are intentionally distinct values. Acceptance routes treat the raw form leniently for
18
+ # backward compatibility with phase 2b call sites that have not yet learned to carry generics.
24
19
  #
25
- # Type arguments MUST be `Rigor::Type` instances. The constructor
26
- # freezes the array; callers MUST NOT mutate it after construction.
20
+ # Type arguments MUST be `Rigor::Type` instances. The constructor freezes the array; callers MUST NOT
21
+ # mutate it after construction.
27
22
  #
28
23
  # See docs/type-specification/rbs-compatible-types.md.
29
24
  class Nominal
@@ -4,22 +4,18 @@ require_relative "../trinary"
4
4
 
5
5
  module Rigor
6
6
  module Type
7
- # Supplies the lattice-membership trio for the "plain" carriers — the
8
- # concrete value types that are neither a lattice extreme (`Top` /
9
- # `Bot` / `Dynamic`) nor a wrapper that computes membership from an
10
- # inner type.
7
+ # Supplies the lattice-membership trio for the "plain" carriers — the concrete value types that are
8
+ # neither a lattice extreme (`Top` / `Bot` / `Dynamic`) nor a wrapper that computes membership from
9
+ # an inner type.
11
10
  #
12
- # Every such carrier answers `top` / `bot` / `dynamic` with the same
13
- # `Trinary.no` ("this value is not that lattice point"), so the trio
14
- # lived as a byte-identical copy in a dozen carriers. The extremes
15
- # override the relevant member (`Top#top` / `Bot#bot` /
16
- # `Dynamic#dynamic` answer `Trinary.yes`) and the delegators (`App`,
17
- # `Difference`, `Refined`, `Union`) compute `dynamic` from their inner
11
+ # Every such carrier answers `top` / `bot` / `dynamic` with the same `Trinary.no` ("this value is not
12
+ # that lattice point"), so the trio lived as a byte-identical copy in a dozen carriers. The extremes
13
+ # override the relevant member (`Top#top` / `Bot#bot` / `Dynamic#dynamic` answer `Trinary.yes`) and
14
+ # the delegators (`App`, `Difference`, `Refined`, `Union`) compute `dynamic` from their inner
18
15
  # type(s); none of those include this module.
19
16
  #
20
- # Mirrors the existing {AcceptanceRouter} / `ValueSemantics` mixins —
21
- # narrow trait sharing, never carrier inheritance (which the type-object
22
- # contract forbids).
17
+ # Mirrors the existing {AcceptanceRouter} / `ValueSemantics` mixins — narrow trait sharing, never
18
+ # carrier inheritance (which the type-object contract forbids).
23
19
  module PlainLattice
24
20
  def top
25
21
  Trinary.no