rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -22,25 +22,19 @@ module Rigor
22
22
  class OutOfRangeError < StandardError; end
23
23
 
24
24
  class << self
25
- # @param source [String]
26
- # @param root [Prism::Node]
27
- # @param line [Integer] 1-indexed line number
28
- # @param column [Integer] 1-indexed column number (byte index within the line)
29
- # @return [Prism::Node, nil]
25
+ # @param line — 1-indexed line number
26
+ # @param column — 1-indexed column number (byte index within the line)
30
27
  def at_position(source:, root:, line:, column:)
31
28
  new(source: source, root: root).at_position(line: line, column: column)
32
29
  end
33
30
 
34
- # @param root [Prism::Node]
35
- # @param offset [Integer] 0-indexed byte offset
36
- # @return [Prism::Node, nil]
31
+ # @param offset — 0-indexed byte offset
37
32
  def at_offset(root:, offset:)
38
33
  new(source: nil, root: root).at_offset(offset)
39
34
  end
40
35
  end
41
36
 
42
- # @param source [String, nil] used by `#at_position`; may be omitted when only `#at_offset` is needed.
43
- # @param root [Prism::Node]
37
+ # @param source used by `#at_position`; may be omitted when only `#at_offset` is needed.
44
38
  def initialize(source:, root:)
45
39
  @source = source
46
40
  @root = root
@@ -48,7 +42,7 @@ module Rigor
48
42
 
49
43
  # Resolve a `(line, column)` pair (1-indexed) to the deepest enclosing node.
50
44
  #
51
- # @raise [OutOfRangeError] if the line or column falls outside the source buffer.
45
+ # @raise OutOfRangeError if the line or column falls outside the source buffer.
52
46
  def at_position(line:, column:)
53
47
  offset = position_to_offset(line, column)
54
48
  at_offset(offset)
@@ -25,8 +25,7 @@ module Rigor
25
25
  module NodeWalker
26
26
  module_function
27
27
 
28
- # @yieldparam node [Prism::Node]
29
- # @return [Enumerator] when no block is given.
28
+ # @return when no block is given.
30
29
  def each(root, &)
31
30
  return to_enum(__method__, root) unless block_given?
32
31
 
@@ -48,9 +47,7 @@ module Rigor
48
47
  # block invocation MUST copy it (`Plugin::NodeContext` does). Used by the plugin engine to give
49
48
  # `node_rule` blocks their enclosing class / method / block context (ADR-37 slice 1d).
50
49
  #
51
- # @yieldparam node [Prism::Node]
52
- # @yieldparam ancestors [Array<Prism::Node>]
53
- # @return [Enumerator] when no block is given.
50
+ # @return when no block is given.
54
51
  def each_with_ancestors(root, &)
55
52
  return to_enum(__method__, root) unless block_given?
56
53
 
@@ -91,9 +91,7 @@ module Rigor
91
91
  INFO_GUARDED = %i[h5_systemic_cluster h6_genuine_bugs].freeze
92
92
  private_constant :INFO_GUARDED
93
93
 
94
- # @param diagnostics [Array<Analysis::Diagnostic>]
95
- # @param include_info [Boolean] let H5/H6 see `:info` diagnostics
96
- # @return [Array<Hint>]
94
+ # @param include_info — let H5/H6 see `:info` diagnostics
97
95
  def recognise(diagnostics, include_info: false)
98
96
  claimed = {}.compare_by_identity
99
97
  recognisers.filter_map do |recogniser|
data/lib/rigor/triage.rb CHANGED
@@ -34,11 +34,9 @@ module Rigor
34
34
  # `rbs.coverage.missing-gem`) survives; the count-based H5/H6 recognisers guard against info themselves so
35
35
  # recognition trace never reads as a bug.
36
36
  #
37
- # @param diagnostics [Array<Analysis::Diagnostic>]
38
- # @param top [Integer] hotspot-file cap
39
- # @param hints [Boolean] run the heuristic catalogue
40
- # @param include_info [Boolean] route info into the volume views
41
- # @return [Report]
37
+ # @param top — hotspot-file cap
38
+ # @param hints run the heuristic catalogue
39
+ # @param include_info route info into the volume views
42
40
  def analyze(diagnostics, top: 10, hints: true, include_info: false)
43
41
  routed = include_info ? diagnostics : diagnostics.reject { |d| d.severity == :info }
44
42
  Report.new(
@@ -26,9 +26,8 @@ module Rigor
26
26
 
27
27
  attr_reader :trinary, :mode, :reasons
28
28
 
29
- # @param trinary [Rigor::Trinary]
30
- # @param mode [Symbol] currently `:gradual` (default) or `:strict`.
31
- # @param reasons [Array<String>, String, nil] textual reasons; a
29
+ # @param mode — currently `:gradual` (default) or `:strict`.
30
+ # @param reasons textual reasons; a
32
31
  # single string is wrapped, `nil` becomes an empty array.
33
32
  def initialize(trinary, mode: :gradual, reasons: nil)
34
33
  raise ArgumentError, "trinary must be Rigor::Trinary, got #{trinary.class}" unless trinary.is_a?(Trinary)
@@ -42,18 +41,37 @@ module Rigor
42
41
 
43
42
  class << self
44
43
  def yes(mode: :gradual, reasons: nil)
45
- new(Trinary.yes, mode: mode, reasons: reasons)
44
+ literal_result(Trinary.yes, mode, reasons)
46
45
  end
47
46
 
48
47
  def no(mode: :gradual, reasons: nil)
49
- new(Trinary.no, mode: mode, reasons: reasons)
48
+ literal_result(Trinary.no, mode, reasons)
50
49
  end
51
50
 
52
51
  def maybe(mode: :gradual, reasons: nil)
53
- new(Trinary.maybe, mode: mode, reasons: reasons)
52
+ literal_result(Trinary.maybe, mode, reasons)
53
+ end
54
+
55
+ private
56
+
57
+ # #775 — a result is an immutable value, so one instance serves every site that asks for the same
58
+ # verdict with the same literal reason (`"structural equality"`, `"exact name match"`, ...): the
59
+ # acceptance rules answer ~360k times on the lib self-check, most of them through a handful of
60
+ # such literals, and each answer cost the result plus its reasons array. A reason built at the
61
+ # site (an unfrozen interpolated String) or a reason list still builds a fresh result.
62
+ def literal_result(trinary, mode, reasons)
63
+ literal = reasons.nil? || (reasons.is_a?(String) && reasons.frozen?)
64
+ return new(trinary, mode: mode, reasons: reasons) unless literal
65
+
66
+ by_mode = (@literal_results[trinary] ||= {})
67
+ by_reason = (by_mode[mode] ||= {})
68
+ by_reason[reasons] ||= new(trinary, mode: mode, reasons: reasons)
54
69
  end
55
70
  end
56
71
 
72
+ # `Trinary` verdict => mode => literal reason (or nil) => the shared result.
73
+ @literal_results = {}
74
+
57
75
  def yes?
58
76
  trinary.yes?
59
77
  end
@@ -7,6 +7,7 @@ require_relative "nominal"
7
7
  require_relative "singleton"
8
8
  require_relative "constant"
9
9
  require_relative "integer_range"
10
+ require_relative "float_range"
10
11
  require_relative "tuple"
11
12
  require_relative "hash_shape"
12
13
  require_relative "data_class"
@@ -60,7 +61,7 @@ module Rigor
60
61
  # list of Rigor::Type values that carry the receiver's generic instantiation (`Array[Integer]` is
61
62
  # `Nominal["Array", [Nominal["Integer"]]]`). Omitting the keyword produces the raw form
62
63
  # `Nominal["Array"]`, which is structurally distinct from any applied form.
63
- def nominal_of(class_name_or_object, type_args: [])
64
+ def nominal_of(class_name_or_object, type_args: Nominal::EMPTY_TYPE_ARGS)
64
65
  Nominal.new(resolve_class_name(class_name_or_object), type_args)
65
66
  end
66
67
 
@@ -68,8 +69,16 @@ module Rigor
68
69
  Singleton.new(resolve_class_name(class_name_or_object))
69
70
  end
70
71
 
72
+ # #775 — `nil`, `true` and `false` are interned: a Constant is an immutable value, and these three
73
+ # are built on every optional translation, nil check and predicate fold (~200k of the ~210k Constant
74
+ # constructions on the lib self-check), so one shared instance each is unobservable and free.
71
75
  def constant_of(value)
72
- Constant.new(value)
76
+ case value
77
+ when nil then NIL_CONSTANT
78
+ when true then TRUE_CONSTANT
79
+ when false then FALSE_CONSTANT
80
+ else Constant.new(value)
81
+ end
73
82
  end
74
83
 
75
84
  # Widens every value-pinned (`Constant`) constituent of `type` to its nominal base (`Constant[1]` ->
@@ -87,9 +96,11 @@ module Rigor
87
96
  # rather than flooring to `Dynamic[top]` (ADR-56).
88
97
  widen_value_pinned(type.base)
89
98
  when IntegerRange
90
- # `int<1, 6>` is likewise a value-narrowed `Integer` (it erases to `Integer` in RBS); widen it
99
+ # `Integer[1..6]` is likewise a value-narrowed `Integer` (it erases to `Integer` in RBS); widen it
91
100
  # so a bounded-int accumulator converges (ADR-56).
92
101
  nominal_of("Integer")
102
+ when FloatRange
103
+ nominal_of("Float")
93
104
  when Union
94
105
  union(*type.members.map { |member| widen_value_pinned(member) })
95
106
  else
@@ -131,6 +142,23 @@ module Rigor
131
142
  IntegerRange.new(IntegerRange::NEG_INFINITY, IntegerRange::POS_INFINITY)
132
143
  end
133
144
 
145
+ # ADR-109 WD4 — `Float[min..max]` / `Float[min...max]`. Bounds are non-NaN Floats; the infinities
146
+ # are ordinary bounds. Raises on an empty range (the payload builder declines before reaching
147
+ # here).
148
+ def float_range(min, max, exclude_end: false)
149
+ FloatRange.new(min, max, exclude_end: exclude_end)
150
+ end
151
+
152
+ # Every Float except NaN: `Float[-Float::INFINITY..]`.
153
+ def non_nan_float
154
+ FloatRange.new(-Float::INFINITY, Float::INFINITY)
155
+ end
156
+
157
+ # Every Float except NaN and the infinities: `Float[-Float::MAX..Float::MAX]`.
158
+ def finite_float
159
+ FloatRange.new(-Float::MAX, Float::MAX)
160
+ end
161
+
134
162
  # Point-removal refinement carrier (ADR-3 OQ3 Option C). Use `non_empty_string` / `non_zero_int` /
135
163
  # `non_empty_array` / `non_empty_hash` for the imported built-in shapes; raw `difference(base,
136
164
  # removed)` for ad-hoc refinements an `RBS::Extended` annotation introduces.
@@ -382,6 +410,13 @@ module Rigor
382
410
  # Normalized union. Flattens nested Unions, deduplicates structurally equal members, drops Bot, and
383
411
  # collapses 0/1-member results.
384
412
  def union(*types)
413
+ # #775 — the same carrier on both sides of a join is already its own union. With the closed-alias
414
+ # translation memo every `Rigor::Type::t` reader shares one 21-member Union, so a control-flow join of
415
+ # an untouched binding would otherwise re-flatten and re-unique 42 members to rebuild an equal object.
416
+ # `rigor trace` still sees the merge (the tracer records degenerate merges).
417
+ first = types.first
418
+ return first if types.size == 2 && first.equal?(types[1]) && !Inference::FlowTracer.active?
419
+
385
420
  result = collapse_union(normalized_union_members(types))
386
421
  if Inference::BudgetTrace.enabled? && result.is_a?(Union)
387
422
  Inference::BudgetTrace.observe(Inference::BudgetTrace::UNION_ARITY, result.members.size)
@@ -790,12 +825,35 @@ module Rigor
790
825
 
791
826
  return [top] if flattened.any?(Top)
792
827
 
793
- unique_members(flattened)
828
+ absorb_float_ranges(unique_members(flattened))
829
+ end
830
+
831
+ # ADR-109 WD5 — a `FloatRange` member is absorbed by a bare `Nominal[Float]` member or by a
832
+ # `FloatRange` member that contains it. The truthy edge of `x > c` is a range and the falsy
833
+ # edge keeps `Float`, so the join after every Float guard would otherwise carry both and every
834
+ # later guard would narrow member by member into a growing union (`Float[...1.0] |
835
+ # Float[0.0...1.0]`) that names one set. `IntegerRange` keeps its pre-ADR-109 behaviour
836
+ # (`Integer | Integer[0..5]` stays as written) because both of its edges narrow.
837
+ def absorb_float_ranges(members)
838
+ return members unless members.any?(FloatRange)
839
+
840
+ members.reject do |member|
841
+ member.is_a?(FloatRange) &&
842
+ members.any? { |other| !other.equal?(member) && float_range_absorbed_by?(member, other) }
843
+ end
844
+ end
845
+
846
+ def float_range_absorbed_by?(range, other)
847
+ case other
848
+ when Nominal then other.class_name == "Float" && other.type_args.empty?
849
+ when FloatRange then other != range && other.accepts(range).yes?
850
+ else false
851
+ end
794
852
  end
795
853
 
796
854
  def unique_members(types)
797
855
  types.each_with_object([]) do |type, unique|
798
- unique << type unless unique.any? { |member| member == type }
856
+ unique << type unless unique.any? { |member| member.equal?(type) || member == type }
799
857
  end
800
858
  end
801
859
 
@@ -864,6 +922,11 @@ module Rigor
864
922
 
865
923
  # Eager-allocated at load time; see `untyped` method comment above.
866
924
  @untyped = Dynamic.new(Top.instance)
925
+
926
+ NIL_CONSTANT = Constant.new(nil)
927
+ TRUE_CONSTANT = Constant.new(true)
928
+ FALSE_CONSTANT = Constant.new(false)
929
+ private_constant :NIL_CONSTANT, :TRUE_CONSTANT, :FALSE_CONSTANT
867
930
  end
868
931
  end
869
932
  end
@@ -24,8 +24,8 @@ module Rigor
24
24
  class DataClass
25
25
  attr_reader :members, :class_name
26
26
 
27
- # @param members [Array<Symbol>] ordered member names.
28
- # @param class_name [String, nil] the bound class name, or nil for
27
+ # @param members ordered member names.
28
+ # @param class_name the bound class name, or nil for
29
29
  # the anonymous `Data.define(...)` result.
30
30
  def initialize(members, class_name = nil)
31
31
  unless members.is_a?(Array) && members.all?(Symbol)
@@ -27,9 +27,9 @@ module Rigor
27
27
  class DataInstance
28
28
  attr_reader :members, :class_name
29
29
 
30
- # @param members [Hash{Symbol => Rigor::Type}] ordered member -> type
30
+ # @param members ordered member -> type
31
31
  # map. Every declared member is present (Data instances are total).
32
- # @param class_name [String, nil] the tagging class name, or nil for
32
+ # @param class_name the tagging class name, or nil for
33
33
  # an instance of an anonymous `Data.define(...)` class.
34
34
  def initialize(members, class_name = nil)
35
35
  unless members.is_a?(Hash) && members.each_key.all?(Symbol)
@@ -44,12 +44,12 @@ module Rigor
44
44
  freeze
45
45
  end
46
46
 
47
- # @return [Array<Symbol>] ordered member names.
47
+ # @return ordered member names.
48
48
  def member_names
49
49
  members.keys
50
50
  end
51
51
 
52
- # @return [Rigor::Type, nil] the member's value type, or nil when the
52
+ # @return the member's value type, or nil when the
53
53
  # name is not a declared member.
54
54
  def member_type(name)
55
55
  members[name]
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../trinary"
4
+ require_relative "acceptance_router"
5
+ require_relative "plain_lattice"
6
+
7
+ module Rigor
8
+ module Type
9
+ # A bounded float range carrier: every `Float` that the Ruby range literal it displays as would
10
+ # `cover?` (ADR-109 WD4). Bounds are doubles; `-Float::INFINITY` and `Float::INFINITY` are ordinary
11
+ # bounds, never sentinels, because Ruby orders them. The range may exclude its end (`0.0...1.0`)
12
+ # and never its beginning, because Ruby's literal has no exclusive begin.
13
+ #
14
+ # No bounded range contains NaN: `(a..b).cover?(Float::NAN)` is false for every `a` and `b`, so
15
+ # the range from `-Float::INFINITY` to `Float::INFINITY` is "every Float except NaN"
16
+ # (`non-nan-float`), not `Float`. The whole of `Float` (`nil..nil` in Ruby, which does cover NaN)
17
+ # is never a carrier; the payload builder normalises it to `Nominal[Float]`.
18
+ #
19
+ # Two spellings bound the same set when their ends are neighbouring doubles: `0.0...1.0` and
20
+ # `0.0..1.0.prev_float` cover the same floats. Equality, hashing and containment run on the
21
+ # canonical closed form (`canonical_max`); `describe` keeps the written form.
22
+ #
23
+ # Erasure to RBS is always "Float".
24
+ class FloatRange
25
+ attr_reader :min, :max
26
+
27
+ def initialize(min, max, exclude_end: false)
28
+ validate_bound!(min, "min")
29
+ validate_bound!(max, "max")
30
+ # `-0.0` and `0.0` are one point in the order (`-0.0 == 0.0`, `(-0.0..0.0).cover?(0.0)`); keep
31
+ # the one spelling so the display cannot suggest two ranges.
32
+ @min = min.zero? ? 0.0 : min
33
+ @max = max.zero? ? 0.0 : max
34
+ @exclude_end = exclude_end == true
35
+ if @min > canonical_max
36
+ raise ArgumentError, "FloatRange #{Range.new(@min, @max, @exclude_end).inspect} is empty"
37
+ end
38
+
39
+ freeze
40
+ end
41
+
42
+ def exclude_end?
43
+ @exclude_end
44
+ end
45
+
46
+ # The greatest double the range contains. `Float::INFINITY.prev_float` is `Float::MAX`, so an
47
+ # exclusive infinite end reads as "finite above".
48
+ def canonical_max
49
+ @exclude_end ? max.prev_float : max
50
+ end
51
+
52
+ # Every Float except NaN.
53
+ def universal?
54
+ min == -Float::INFINITY && canonical_max == Float::INFINITY
55
+ end
56
+
57
+ # Ruby's own predicate is the definition: the carrier covers `value` exactly when the range it
58
+ # displays as does.
59
+ def covers?(value)
60
+ return false unless value.is_a?(Float)
61
+
62
+ Range.new(min, max, @exclude_end).cover?(value)
63
+ end
64
+
65
+ # Keyed by the canonical closed bounds so a written `-Float::MAX...Float::INFINITY` reads as the
66
+ # set it is.
67
+ ALIAS_NAMES = Ractor.make_shareable({
68
+ [-Float::INFINITY, Float::INFINITY] => "non-nan-float",
69
+ [-Float::MAX, Float::MAX] => "finite-float"
70
+ })
71
+
72
+ def describe(_verbosity = :short)
73
+ ALIAS_NAMES[[min, canonical_max]] || generic_description
74
+ end
75
+
76
+ # The written form: an infinite bound is left out where Ruby's literal can (beginless, endless),
77
+ # spelled `Float::INFINITY` where it cannot (`...Float::INFINITY` is "finite above"), and the two
78
+ # extreme finite doubles read as the constants a Rubyist would write.
79
+ def generic_description
80
+ op = @exclude_end ? "..." : ".."
81
+ lo = min == -Float::INFINITY ? "" : float_literal(min)
82
+ hi = max == Float::INFINITY && !@exclude_end ? "" : float_literal(max)
83
+ "Float[#{lo}#{op}#{hi}]"
84
+ end
85
+
86
+ def erase_to_rbs
87
+ "Float"
88
+ end
89
+
90
+ include Rigor::Type::PlainLattice
91
+
92
+ include Rigor::Type::AcceptanceRouter
93
+
94
+ # Hand-written rather than `value_fields`: two carriers are the same type when they cover the
95
+ # same doubles, whatever end they were written with.
96
+ def ==(other)
97
+ other.is_a?(FloatRange) && min == other.min && canonical_max == other.canonical_max
98
+ end
99
+ alias eql? ==
100
+
101
+ def hash
102
+ [self.class, min, canonical_max].hash
103
+ end
104
+
105
+ def inspect
106
+ "#<Rigor::Type::FloatRange #{describe(:short)}>"
107
+ end
108
+
109
+ private
110
+
111
+ def float_literal(value)
112
+ case value
113
+ when Float::INFINITY then "Float::INFINITY"
114
+ when -Float::INFINITY then "-Float::INFINITY"
115
+ when Float::MAX then "Float::MAX"
116
+ when -Float::MAX then "-Float::MAX"
117
+ else value.inspect
118
+ end
119
+ end
120
+
121
+ def validate_bound!(bound, label)
122
+ return if bound.is_a?(Float) && !bound.nan?
123
+
124
+ raise ArgumentError, "FloatRange #{label} must be a non-NaN Float, got #{bound.inspect}"
125
+ end
126
+ end
127
+ end
128
+ end
@@ -34,21 +34,21 @@ module Rigor
34
34
 
35
35
  attr_reader :pairs, :required_keys, :optional_keys, :read_only_keys, :extra_keys
36
36
 
37
- # @param pairs [Hash{Symbol|String => Rigor::Type}] ordered map of
37
+ # @param pairs ordered map of
38
38
  # keys to declared types. Keys MUST be Symbol or String;
39
39
  # values MUST be Rigor::Type instances. The hash is duped and
40
40
  # frozen at construction; callers MUST NOT mutate the input
41
41
  # afterwards (mutation does not affect the carrier, but the
42
42
  # carrier is a value object).
43
- # @param required_keys [Array<Symbol|String>, nil] keys that MUST
43
+ # @param required_keys keys that MUST
44
44
  # be present. When omitted, every non-optional key is required.
45
45
  # When supplied without optional_keys, every remaining known key
46
46
  # is treated as optional.
47
- # @param optional_keys [Array<Symbol|String>, nil] keys that MAY
47
+ # @param optional_keys keys that MAY
48
48
  # be absent. Optional absence is not a stored nil.
49
- # @param read_only_keys [Array<Symbol|String>] entries that cannot
49
+ # @param read_only_keys entries that cannot
50
50
  # be written through this shape view.
51
- # @param extra_keys [Symbol] :closed rejects keys outside pairs;
51
+ # @param extra_keys :closed rejects keys outside pairs;
52
52
  # :open permits them.
53
53
  def initialize(pairs = nil, **keywords)
54
54
  pairs, policy = split_constructor_args(pairs, keywords)
@@ -8,10 +8,11 @@ require_relative "plain_lattice"
8
8
  module Rigor
9
9
  module Type
10
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.
11
+ # infinities `:neg_infinity` / `:pos_infinity`. Displayed with the Ruby range literal that
12
+ # `Range#cover?` would accept the same integers for (`Integer[1..10]`, `Integer[0..]`,
13
+ # `Integer[..-1]`; ADR-109). The named aliases `positive-int` (1..), `non-negative-int` (0..),
14
+ # `negative-int` (..-1), `non-positive-int` (..0) all surface through this single carrier and are
15
+ # recovered in `describe` for human-friendly output.
15
16
  #
16
17
  # Constraints on construction:
17
18
  # - both bounds must be either `Integer` or one of the two infinity sentinels;
@@ -76,8 +77,10 @@ module Rigor
76
77
  Float::INFINITY
77
78
  end
78
79
 
80
+ # The universal range reads as the class it erases to: a range with no bound carries no fact
81
+ # a Rubyist would spell.
79
82
  ALIAS_NAMES = Ractor.make_shareable({
80
- [NEG_INFINITY, POS_INFINITY] => "int",
83
+ [NEG_INFINITY, POS_INFINITY] => "Integer",
81
84
  [1, POS_INFINITY] => "positive-int",
82
85
  [0, POS_INFINITY] => "non-negative-int",
83
86
  [NEG_INFINITY, -1] => "negative-int",
@@ -88,11 +91,13 @@ module Rigor
88
91
  ALIAS_NAMES[[min, max]] || generic_description
89
92
  end
90
93
 
94
+ # Always the closed form: for integers `a...b` and `a..b-1` are the same set, and the closed
95
+ # spelling is the one the grammar reads back without a canonicalisation step.
91
96
  def generic_description
92
- return "int<#{min}, max>" if max == POS_INFINITY
93
- return "int<min, #{max}>" if min == NEG_INFINITY
97
+ return "Integer[#{min}..]" if max == POS_INFINITY
98
+ return "Integer[..#{max}]" if min == NEG_INFINITY
94
99
 
95
- "int<#{min}, #{max}>"
100
+ "Integer[#{min}..#{max}]"
96
101
  end
97
102
 
98
103
  def erase_to_rbs
@@ -25,13 +25,19 @@ module Rigor
25
25
  class Nominal
26
26
  attr_reader :class_name, :type_args
27
27
 
28
- def initialize(class_name, type_args = [])
28
+ # The shared empty argument list. The raw form `Nominal["Array"]` is by far the most constructed
29
+ # type (~600k on the lib self-check); a fresh `[]` default plus its defensive `dup` cost two
30
+ # allocations per construction for an array nobody can observe.
31
+ EMPTY_TYPE_ARGS = [].freeze
32
+
33
+ def initialize(class_name, type_args = EMPTY_TYPE_ARGS)
29
34
  raise ArgumentError, "class_name must be a String, got #{class_name.class}" unless class_name.is_a?(String)
30
35
  raise ArgumentError, "class_name must not be empty" if class_name.empty?
31
36
  raise ArgumentError, "type_args must be an Array, got #{type_args.class}" unless type_args.is_a?(Array)
32
37
 
33
38
  @class_name = class_name.freeze
34
- @type_args = type_args.dup.freeze
39
+ # A frozen array is already immutable, so sharing it is unobservable; only a mutable one is copied.
40
+ @type_args = type_args.frozen? ? type_args : type_args.dup.freeze
35
41
  freeze
36
42
  end
37
43
 
@@ -135,8 +135,8 @@ module Rigor
135
135
  NUMERIC_LITERAL_PREFIX = /\A[+-]?\d/
136
136
  private_constant :NUMERIC_LITERAL_PREFIX
137
137
 
138
- # @param value [Object] typically a `Constant#value`
139
- # @return [Boolean] true when `value` is a String that is a
138
+ # @param value typically a `Constant#value`
139
+ # @return true when `value` is a String that is a
140
140
  # single, complete Ruby numeric literal. Total over
141
141
  # arbitrary input — never raises (Prism reports malformed
142
142
  # input through `errors`, it does not throw).
@@ -216,7 +216,7 @@ module Rigor
216
216
  }.freeze
217
217
  private_constant :COMPLEMENT_PAIRS
218
218
 
219
- # @return [Symbol, nil] the registered complement predicate
219
+ # @return the registered complement predicate
220
220
  # id, or nil when no pair is registered for this predicate.
221
221
  def complement_predicate_id
222
222
  COMPLEMENT_PAIRS[predicate_id]
@@ -26,10 +26,10 @@ module Rigor
26
26
  class StructClass
27
27
  attr_reader :members, :class_name, :keyword_init
28
28
 
29
- # @param members [Array<Symbol>] ordered member names.
30
- # @param class_name [String, nil] the bound class name, or nil for
29
+ # @param members ordered member names.
30
+ # @param class_name the bound class name, or nil for
31
31
  # the anonymous `Struct.new(...)` result.
32
- # @param keyword_init [Boolean] the `keyword_init:` flag.
32
+ # @param keyword_init the `keyword_init:` flag.
33
33
  def initialize(members, class_name = nil, keyword_init: false)
34
34
  unless members.is_a?(Array) && members.all?(Symbol)
35
35
  raise ArgumentError, "members must be an Array of Symbols, got #{members.inspect}"
@@ -29,9 +29,9 @@ module Rigor
29
29
  class StructInstance
30
30
  attr_reader :members, :class_name
31
31
 
32
- # @param members [Hash{Symbol => Rigor::Type}] ordered member -> type
32
+ # @param members ordered member -> type
33
33
  # map. Every declared member is present (Struct instances are total).
34
- # @param class_name [String, nil] the tagging class name, or nil for
34
+ # @param class_name the tagging class name, or nil for
35
35
  # an instance of an anonymous `Struct.new(...)` class.
36
36
  def initialize(members, class_name = nil)
37
37
  unless members.is_a?(Hash) && members.each_key.all?(Symbol)
@@ -46,12 +46,12 @@ module Rigor
46
46
  freeze
47
47
  end
48
48
 
49
- # @return [Array<Symbol>] ordered member names.
49
+ # @return ordered member names.
50
50
  def member_names
51
51
  members.keys
52
52
  end
53
53
 
54
- # @return [Rigor::Type, nil] the member's value type, or nil when the
54
+ # @return the member's value type, or nil when the
55
55
  # name is not a declared member.
56
56
  def member_type(name)
57
57
  members[name]
data/lib/rigor/type.rb CHANGED
@@ -17,6 +17,7 @@ require_relative "type/nominal"
17
17
  require_relative "type/singleton"
18
18
  require_relative "type/constant"
19
19
  require_relative "type/integer_range"
20
+ require_relative "type/float_range"
20
21
  require_relative "type/tuple"
21
22
  require_relative "type/hash_shape"
22
23
  require_relative "type/data_class"
@@ -48,7 +48,7 @@ module Rigor
48
48
  # stays additive — every earlier-shape Generic remains valid.
49
49
  def valid_arg?(arg)
50
50
  arg.is_a?(Identifier) || arg.is_a?(Generic) || arg.is_a?(IntegerLiteral) ||
51
- arg.is_a?(SymbolLiteral) || arg.is_a?(StringLiteral) ||
51
+ arg.is_a?(SymbolLiteral) || arg.is_a?(StringLiteral) || arg.is_a?(RangeLiteral) ||
52
52
  arg.is_a?(IndexedAccess) || arg.is_a?(Union)
53
53
  end
54
54
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module TypeNode
5
+ # Range-literal AST node: a Ruby `Range` literal in a type-arg position, spelled as Ruby spells
6
+ # it (`1..10`, `1...10`, `1..`, `..10`, `nil..nil`). ADR-109 makes it the bound of the numeric
7
+ # range refinements, `Integer[1..10]`; under any other head the resolver lifts it to a
8
+ # `Constant<Range>` so a plugin resolver may consume it like the other leaf literals.
9
+ #
10
+ # The value is the `Range` object itself, which already carries `begin`, `end` and
11
+ # `exclude_end?`; there is no second representation to keep in step. A missing endpoint is
12
+ # `nil`, as in Ruby.
13
+ class RangeLiteral < Data.define(:value)
14
+ def initialize(value:)
15
+ unless value.is_a?(Range)
16
+ raise ArgumentError,
17
+ "TypeNode::RangeLiteral value must be a Range, " \
18
+ "got #{value.inspect}"
19
+ end
20
+
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -32,7 +32,7 @@ module Rigor
32
32
  freeze
33
33
  end
34
34
 
35
- # @return [Array<Rigor::Plugin::TypeNodeResolver>] ordered
35
+ # @return ordered
36
36
  # resolver instances, in plugin-registration order.
37
37
  attr_reader :resolvers
38
38