rigortype 0.3.7 → 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 (450) 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 +96 -15
  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 +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  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 +69 -13
  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 +100 -29
  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 +316 -40
  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 +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  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 +166 -30
  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 +70 -0
  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_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -3,6 +3,7 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../environment"
6
+ require_relative "../project_environment"
6
7
  require_relative "../scope"
7
8
  require_relative "../cache/store"
8
9
  require_relative "../plugin"
@@ -16,6 +17,7 @@ require_relative "../effects/envelope_index"
16
17
  require_relative "../inference/scope_indexer"
17
18
  require_relative "../inference/method_dispatcher/file_folding"
18
19
  require_relative "check_rules"
20
+ require_relative "crash_signature"
19
21
  require_relative "dependency_recorder"
20
22
  require_relative "dependency_source_inference"
21
23
  require_relative "diagnostic"
@@ -72,12 +74,11 @@ module Rigor
72
74
  :rbs_extended_reporter, :boundary_cross_reporter,
73
75
  :prepare_diagnostics
74
76
 
75
- # @param configuration [Rigor::Configuration]
76
- # @param cache_store [Rigor::Cache::Store, nil] persistent cache the session exposes to plugin-side
77
+ # @param cache_store — persistent cache the session exposes to plugin-side
77
78
  # producers and the RBS loader. Pass `nil` to disable caching.
78
- # @param plugin_blueprints [Array<Rigor::Plugin::Blueprint>] replay descriptors. Empty array yields a
79
+ # @param plugin_blueprints — replay descriptors. Empty array yields a
79
80
  # session with no plugin contributions.
80
- # @param explain [Boolean] when true, `#analyze` additionally emits one `:info` `fallback` diagnostic
81
+ # @param explain — when true, `#analyze` additionally emits one `:info` `fallback` diagnostic
81
82
  # per directly-unrecognised node, mirroring {Rigor::Analysis::Runner#explain_diagnostics}.
82
83
  def initialize(configuration:, cache_store: nil, # rubocop:disable Metrics/MethodLength,Metrics/ParameterLists
83
84
  plugin_blueprints: [], explain: false, buffer: nil,
@@ -136,11 +137,7 @@ module Rigor
136
137
  rbs_extended_reporter: @rbs_extended_reporter,
137
138
  boundary_cross_reporter: @boundary_cross_reporter,
138
139
  source_rbs_synthesis_reporter: @source_rbs_synthesis_reporter,
139
- bundler_bundle_path: configuration.bundler_bundle_path,
140
- bundler_auto_detect: configuration.bundler_auto_detect,
141
- bundler_lockfile: configuration.bundler_lockfile,
142
- rbs_collection_lockfile: configuration.rbs_collection_lockfile,
143
- rbs_collection_auto_detect: configuration.rbs_collection_auto_detect,
140
+ **ProjectEnvironment.dependency_discovery_options(configuration),
144
141
  synthetic_method_index: @synthetic_method_index,
145
142
  project_patched_methods: @project_patched_methods,
146
143
  source_files: @source_files
@@ -228,7 +225,7 @@ module Rigor
228
225
  rescue Errno::ENOENT => e
229
226
  [analyzer_error(path, e.message)]
230
227
  rescue StandardError => e
231
- [analyzer_error(path, "internal analyzer error: #{e.class}: #{e.message}")]
228
+ [analyzer_error(path, CrashSignature.check_rule_message(e))]
232
229
  end
233
230
  private :analyze_body
234
231
 
@@ -243,15 +240,42 @@ module Rigor
243
240
  # how you ran it" defect the diagnostic exists to end. Draining it out of the workers is what makes
244
241
  # the two paths say the same thing. The payload is `[String, String, String, Array<String>]` tuples —
245
242
  # Marshal-clean for the fork backend and shareable for the Ractor one.
243
+ #
244
+ # Issue #784 — `hkt_scan_failure` rides the same channel for the same reason: the PARENT's own
245
+ # Environment never demands `#hkt_registry` under the pool, so its slot is always nil, and draining it
246
+ # out of the workers is the only way `--workers=N` says what `--workers=0` says.
247
+ #
248
+ # Issue #805 — the Marshal-clean requirement binds every stream here, not just the ones written for it.
249
+ # The `RbsExtended::Reporter`'s unresolved / lossy-projection entries used to carry the `RBS::Location`
250
+ # itself, and `Marshal.dump` on one raises `TypeError` (a C-extension object with no `_dump`), so a
251
+ # project whose `sig/` produced a single such event killed each worker HERE — after its files were
252
+ # analysed — and the run degraded to in-process re-analysis with a `pool-degraded` row. All three
253
+ # streams now carry `(path, line, column)` primitives.
246
254
  def drain_reporters
255
+ # Issue #784 — demand the registry once per worker before reading the slot, so a worker whose share
256
+ # of files happened to contain no `Klass.method` call still reports the run's outcome (the same
257
+ # reason `PoolCoordinator#hkt_scan_outcome` demands it on the sequential path). The Environment
258
+ # object is built in the constructor, but its RBS env is lazy: on a worker that analysed a file this
259
+ # is a memoised read; on one that demanded nothing it is the same cache-served env load that file
260
+ # would have paid. The fork backend never hands a worker an empty slice, so no idle worker pays it.
261
+ @environment&.hkt_registry
247
262
  {
248
263
  rbs_extended: {
249
264
  unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
250
- lossy_projections: @rbs_extended_reporter.lossy_projections
265
+ lossy_projections: @rbs_extended_reporter.lossy_projections,
266
+ # Issue #785 — the HKT directive stream rides the same channel for the same reason
267
+ # `hkt_scan_failure` does: the directives are read by the registry scan, and under the pool the
268
+ # PARENT never demands that scan, so a diagnostic wired off the parent's reporter would appear
269
+ # at `--workers=0` and vanish at `--workers=N`. The demand above is what fills this.
270
+ hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors,
271
+ # ADR-109 WD3 — the deprecated-form stream rides the same channel: the payload is read where
272
+ # the annotated method is dispatched, which under the pool is only ever inside a worker.
273
+ deprecated_forms: @rbs_extended_reporter.deprecated_forms
251
274
  },
252
275
  boundary_cross: @boundary_cross_reporter.entries,
253
276
  source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
254
- definition_build_failures: @environment&.rbs_loader&.definition_build_failures || []
277
+ definition_build_failures: @environment&.rbs_loader&.definition_build_failures || [],
278
+ hkt_scan_failure: @environment&.hkt_scan_failure
255
279
  }
256
280
  end
257
281
 
@@ -51,14 +51,14 @@ module Rigor
51
51
  # migration note and a user's `bleeding_edge:` list both key on.
52
52
  #
53
53
  # @!attribute id
54
- # @return [String] the stable feature id (contract vocabulary).
54
+ # @return the stable feature id (contract vocabulary).
55
55
  # @!attribute summary
56
- # @return [String] a one-line description of what it changes. For a `:behaviour`
56
+ # @return a one-line description of what it changes. For a `:behaviour`
57
57
  # feature this is the *whole* explanation — there is no severity diff to read.
58
58
  # @!attribute kind
59
- # @return [Symbol] one of {KINDS}.
59
+ # @return one of {KINDS}.
60
60
  # @!attribute severity_overrides
61
- # @return [Hash{String => Symbol}] canonical rule id → the severity this feature
61
+ # @return canonical rule id → the severity this feature
62
62
  # imposes. Composed *below* the user's own `severity_overrides:` and *above* the
63
63
  # active `severity_profile` (see {Configuration::SeverityProfile.resolve}). Empty for
64
64
  # a `:behaviour` feature.
@@ -76,12 +76,10 @@ module Rigor
76
76
  super
77
77
  end
78
78
 
79
- # @return [Boolean]
80
79
  def severity?
81
80
  kind == :severity
82
81
  end
83
82
 
84
- # @return [Boolean]
85
83
  def behaviour?
86
84
  kind == :behaviour
87
85
  end
@@ -193,36 +191,30 @@ module Rigor
193
191
  # cleanup can lag graduation by as many releases as it takes. The id also stays in the
194
192
  # contract vocabulary the CHANGELOG migration note keys on. Entries are removed only once
195
193
  # no call site names them.
196
- #
197
- # @return [Array<String>]
198
194
  GRADUATED = [].freeze
199
195
 
200
196
  module_function
201
197
 
202
- # @return [Array<Feature>] the whole overlay.
198
+ # @return the whole overlay.
203
199
  def features
204
200
  FEATURES
205
201
  end
206
202
 
207
- # @return [Array<String>] every feature id in the overlay.
203
+ # @return every feature id in the overlay.
208
204
  def feature_ids
209
205
  FEATURES.map(&:id)
210
206
  end
211
207
 
212
- # @param id [String]
213
- # @return [Feature, nil]
214
208
  def feature(id)
215
209
  FEATURES.find { |f| f.id == id }
216
210
  end
217
211
 
218
- # @param id [String]
219
- # @return [Boolean] whether the id has graduated to default-on ({GRADUATED}).
212
+ # @return whether the id has graduated to default-on ({GRADUATED}).
220
213
  def graduated?(id)
221
214
  GRADUATED.include?(id)
222
215
  end
223
216
 
224
- # @param id [String]
225
- # @return [Boolean] whether the id names a feature this gem knows at all — queued or
217
+ # @return whether the id names a feature this gem knows at all — queued or
226
218
  # graduated. Distinct from "adopted"; see {Configuration#bleeding_edge_active?}.
227
219
  def known_id?(id)
228
220
  graduated?(id) || FEATURES.any? { |f| f.id == id }
@@ -234,10 +226,9 @@ module Rigor
234
226
  # `severity_overrides:` keeps an unknown rule id inert until it lands (robust across gem
235
227
  # versions).
236
228
  #
237
- # @param selector [Hash] `{ "mode" => "none" }`,
229
+ # @param selector — `{ "mode" => "none" }`,
238
230
  # `{ "mode" => "all" }`, `{ "mode" => "all", "except" => [ids] }`,
239
231
  # or `{ "mode" => "list", "ids" => [ids] }`.
240
- # @return [Array<Feature>]
241
232
  def active_features(selector)
242
233
  case selector["mode"]
243
234
  when "all"
@@ -254,8 +245,7 @@ module Rigor
254
245
  # The merged severity-override map the active features impose for a selector. Frozen so
255
246
  # the result is `Ractor.shareable?`.
256
247
  #
257
- # @param selector [Hash] see {#active_features}.
258
- # @return [Hash{String => Symbol}]
248
+ # @param selector — see {#active_features}.
259
249
  def severity_overrides_for(selector)
260
250
  active_features(selector).each_with_object({}) do |feature, acc|
261
251
  acc.merge!(feature.severity_overrides)
@@ -267,8 +257,7 @@ module Rigor
267
257
  # Precomputed once per Configuration; frozen (with frozen members) so the carrier stays
268
258
  # `Ractor.shareable?` across the worker boundary.
269
259
  #
270
- # @param selector [Hash] see {#active_features}.
271
- # @return [Set<String>]
260
+ # @param selector — see {#active_features}.
272
261
  def active_ids_for(selector)
273
262
  Set.new(active_features(selector).map(&:id)).freeze
274
263
  end
@@ -276,8 +265,7 @@ module Rigor
276
265
  # Feature ids named by a selector that are NOT in the overlay (typo / graduated / from a
277
266
  # newer gem). Surfaced by `rigor show-bleedingedge` as a hint; never an error.
278
267
  #
279
- # @param selector [Hash] see {#active_features}.
280
- # @return [Array<String>]
268
+ # @param selector — see {#active_features}.
281
269
  def unknown_selected_ids(selector)
282
270
  named =
283
271
  case selector["mode"]
@@ -118,7 +118,7 @@ module Rigor
118
118
  )
119
119
  end
120
120
 
121
- # @return [Rigor::Inference::HktRegistry] frozen registry
121
+ # @return frozen registry
122
122
  # pre-seeded with all bundled HKT registrations +
123
123
  # bodies. Allocated fresh each call rather than
124
124
  # memoised — memoisation through a module-level
@@ -242,7 +242,7 @@ module Rigor
242
242
  ["CSV", :parse_line, :singleton] => CSV_ROW_SPEC
243
243
  }.freeze
244
244
 
245
- # @return [Rigor::Type, nil] the reduced HKT type for
245
+ # @return the reduced HKT type for
246
246
  # the given (class_name, method_name, kind) triple,
247
247
  # or `nil` when no built-in override is registered.
248
248
  # When `arg_types` is supplied AND the entry carries a
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "strscan"
4
4
 
5
+ require_relative "../rbs_extended/reporter"
5
6
  require_relative "../type"
6
7
  require_relative "../type_node"
7
8
 
@@ -30,11 +31,12 @@ module Rigor
30
31
  # - **No-argument refinement names** (`non-empty-string`, `non-zero-int`,
31
32
  # `lowercase-string`, …) live in `REGISTRY` and resolve through `lookup(name)`.
32
33
  # - **Parameterised refinement payloads** (`non-empty-array[Integer]`,
33
- # `non-empty-hash[Symbol, Integer]`, `int<5, 10>`) are accepted by `parse(payload)`. The
34
- # full grammar is documented on `Parser`. The two surfaces share `REGISTRY` for the
35
- # no-arg head names; the parameterised head names live in `PARAMETERISED_TYPE_BUILDERS`
36
- # (square-bracket form, type args) and `PARAMETERISED_INT_BUILDERS` (angle-bracket form,
37
- # integer bounds).
34
+ # `non-empty-hash[Symbol, Integer]`, `Integer[5..10]`, and the deprecated `int<5, 10>`)
35
+ # are accepted by `parse(payload)`. The full grammar is documented on `Parser`. The two
36
+ # surfaces share `REGISTRY` for the no-arg head names; the parameterised head names live
37
+ # in `PARAMETERISED_TYPE_BUILDERS` (square-bracket form, type args), `RANGE_HEAD_BUILDERS`
38
+ # (a numeric class over a range literal) and `PARAMETERISED_INT_BUILDERS` (the deprecated
39
+ # angle-bracket form, integer bounds).
38
40
  module ImportedRefinements
39
41
  REGISTRY = {
40
42
  "non-empty-string" => -> { Type::Combinator.non_empty_string },
@@ -57,7 +59,10 @@ module Rigor
57
59
  "non-empty-lowercase-string" => -> { Type::Combinator.non_empty_lowercase_string },
58
60
  "non-empty-uppercase-string" => -> { Type::Combinator.non_empty_uppercase_string },
59
61
  "literal-string" => -> { Type::Combinator.literal_string },
60
- "non-empty-literal-string" => -> { Type::Combinator.non_empty_literal_string }
62
+ "non-empty-literal-string" => -> { Type::Combinator.non_empty_literal_string },
63
+ # ADR-109 WD4 — names for the two Float ranges people mean most; each is an alias of a range.
64
+ "non-nan-float" => -> { Type::Combinator.non_nan_float },
65
+ "finite-float" => -> { Type::Combinator.finite_float }
61
66
  }.freeze
62
67
  private_constant :REGISTRY
63
68
 
@@ -138,13 +143,52 @@ module Rigor
138
143
  }.freeze
139
144
  private_constant :PARAMETERISED_TYPE_BUILDERS
140
145
 
141
- # `name<min, max>` — integer-bound parameterised refinements. Each builder takes an
146
+ # `Class[range]` — a numeric class head bounded by a Ruby range literal (ADR-109). The
147
+ # builder receives the `Range` value and returns the carrier, or `nil` when the range is
148
+ # empty or its endpoints are not literals of the head class; `nil` surfaces as the ordinary
149
+ # unresolved-payload diagnostic. `Float` joins this table with the `FloatRange` carrier.
150
+ RANGE_HEAD_BUILDERS = {
151
+ "Integer" => lambda { |range|
152
+ lo = range.begin
153
+ hi = range.end
154
+ return nil unless (lo.nil? || lo.is_a?(Integer)) && (hi.nil? || hi.is_a?(Integer))
155
+
156
+ # `1...10` is the set `1..9`: Integer ranges canonicalise to the closed form.
157
+ hi -= 1 if range.exclude_end? && hi.is_a?(Integer)
158
+ return nil if lo.is_a?(Integer) && hi.is_a?(Integer) && lo > hi
159
+
160
+ Type::Combinator.integer_range(lo || Type::IntegerRange::NEG_INFINITY, hi || Type::IntegerRange::POS_INFINITY)
161
+ },
162
+ # ADR-109 WD4 — `Float[R]` is the set of Floats `R.cover?` accepts. A missing or `nil` endpoint
163
+ # is the infinity on that side, except that `nil..nil` covers NaN in Ruby and so is `Float`
164
+ # itself; an Integer endpoint coerces (`(0..1).cover?(0.5)` is true); an exclusive end is kept
165
+ # (the carrier canonicalises it for equality), and an empty range declines.
166
+ "Float" => lambda { |range|
167
+ return Type::Combinator.nominal_of("Float") if range.begin.nil? && range.end.nil?
168
+
169
+ lo = ImportedRefinements.float_range_endpoint(range.begin, -Float::INFINITY)
170
+ hi = ImportedRefinements.float_range_endpoint(range.end, Float::INFINITY)
171
+ return nil if lo.nil? || hi.nil?
172
+
173
+ exclusive = range.exclude_end? && !range.end.nil?
174
+ return nil if lo > (exclusive ? hi.prev_float : hi)
175
+
176
+ Type::Combinator.float_range(lo, hi, exclude_end: exclusive)
177
+ }
178
+ }.freeze
179
+ private_constant :RANGE_HEAD_BUILDERS
180
+
181
+ # `name<min, max>` — the PHPStan-style integer-bound form ADR-109 deprecates: still parsed so
182
+ # signatures written against v0.1–v0.3 keep resolving, never displayed. Each builder takes an
142
183
  # `Array<Integer>` and returns a `Rigor::Type` (or `nil`). Bounds are signed integer
143
- # literals; `min` MUST be ≤ `max` for the carrier to construct successfully
144
- # (`Type::IntegerRange` enforces the invariant).
184
+ # literals. A reversed pair (`min` > `max`) is declined as `nil` like every other shape
185
+ # mismatch, so the caller surfaces `dynamic.rbs-extended.unresolved` at the annotation;
186
+ # `Type::IntegerRange` raises on it instead, and that `ArgumentError` used to escape as an
187
+ # `internal analyzer error` that abandoned the whole Ruby file.
145
188
  PARAMETERISED_INT_BUILDERS = {
146
189
  "int" => lambda { |bounds|
147
190
  return nil unless bounds.size == 2
191
+ return nil if bounds[0] > bounds[1]
148
192
 
149
193
  Type::Combinator.integer_range(bounds[0], bounds[1])
150
194
  }
@@ -153,32 +197,32 @@ module Rigor
153
197
 
154
198
  module_function
155
199
 
156
- # @param name [String] kebab-case refinement name.
157
- # @return [Rigor::Type, nil] the matching refinement carrier, or `nil` if the name is
200
+ # @param name — kebab-case refinement name.
201
+ # @return the matching refinement carrier, or `nil` if the name is
158
202
  # not registered.
159
203
  def lookup(name)
160
204
  builder = REGISTRY[name.to_s]
161
205
  builder&.call
162
206
  end
163
207
 
164
- # @param payload [String] the trailing payload of a `rigor:v1:return:` (or sibling)
208
+ # @param payload — the trailing payload of a `rigor:v1:return:` (or sibling)
165
209
  # directive. Accepts the bare-name forms `lookup` already handles plus the
166
210
  # parameterised forms documented on {Parser}.
167
- # @param name_scope [Rigor::TypeNode::NameScope, nil]
211
+ # @param name_scope —
168
212
  # ADR-13 slice 3 — when provided, the parser consults the scope's `#resolver` chain
169
213
  # after the built-in registry and built-in parametric forms but before the RBS
170
214
  # Nominal fallback. `nil` (default) preserves the slice-1 / slice-2 behaviour of
171
215
  # consulting only built-ins + RBS.
172
- # @param reporter [Rigor::RbsExtended::Reporter, nil]
216
+ # @param reporter —
173
217
  # ADR-13 slice 3b — collector that the Resolver feeds `dynamic.shape.lossy-projection`
174
218
  # events into when a shape-projection head (`pick_of`, `omit_of`, `partial_of`,
175
219
  # `required_of`, `readonly_of`) is applied to a carrier that does not preserve shape
176
220
  # information. `nil` (default) suppresses event accumulation; legacy call sites that
177
221
  # have no reporter to thread keep the pre-slice-3b silent fall-through.
178
- # @param source_location [RBS::Location, nil] location attribution for the events the
222
+ # @param source_location — location attribution for the events the
179
223
  # Resolver records. Carries the annotation's filename / line / column so the runner
180
224
  # can stamp diagnostics with the user-visible source site.
181
- # @return [Rigor::Type, nil] the resolved refinement carrier, or `nil` when the payload
225
+ # @return the resolved refinement carrier, or `nil` when the payload
182
226
  # is unparseable or names a refinement / class no registered source resolved.
183
227
  def parse(payload, name_scope: nil, reporter: nil, source_location: nil)
184
228
  Parser.new(
@@ -200,6 +244,20 @@ module Rigor
200
244
  PARAMETERISED_INT_BUILDERS[name]
201
245
  end
202
246
 
247
+ def range_head_builder(name)
248
+ RANGE_HEAD_BUILDERS[name]
249
+ end
250
+
251
+ # A Float-head range endpoint as a double: `nil` (absent) is `default`, an Integer literal
252
+ # coerces, a non-NaN Float stands, anything else declines with `nil`.
253
+ def float_range_endpoint(value, default)
254
+ case value
255
+ when nil then default
256
+ when Integer then value.to_f
257
+ when Float then value.nan? ? nil : value
258
+ end
259
+ end
260
+
203
261
  def known?(name)
204
262
  REGISTRY.key?(name.to_s) ||
205
263
  PARAMETERISED_TYPE_BUILDERS.key?(name.to_s) ||
@@ -218,8 +276,10 @@ module Rigor
218
276
  # parametric := simple_name '[' type_arg_list ']'
219
277
  # | simple_name '<' int_bound_list '>'
220
278
  # type_arg_list := type_arg (',' type_arg)*
221
- # type_arg := type | class_name
279
+ # type_arg := range_literal | type | class_name
222
280
  # class_name := /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
281
+ # range_literal := endpoint? ('..' | '...') endpoint? (at least one side written)
282
+ # endpoint := signed_int | 'nil'
223
283
  # int_bound_list := signed_int (',' signed_int)*
224
284
  # signed_int := /-?\d+/
225
285
  #
@@ -273,7 +333,16 @@ module Rigor
273
333
  # delimiter.
274
334
  SYMBOL_LITERAL = /:(?<value>[a-zA-Z_][a-zA-Z0-9_]*[?!=]?)/
275
335
  STRING_LITERAL = /"(?<value>[^"\\]*)"/
276
- private_constant :SIMPLE_NAME, :CLASS_NAME, :SIGNED_INT, :SYMBOL_LITERAL, :STRING_LITERAL
336
+ # ADR-109 — a Ruby range literal at type-arg position, `1..10` / `1...10` / `1..` / `..10` /
337
+ # `nil..nil`, and for the Float head `0.0...1.0` / `1e-3..` / `-Float::INFINITY..Float::MAX`.
338
+ # Tried before the class-name and integer scans so `Float::INFINITY..` and `1..10` are not
339
+ # consumed as a class name or a bare integer; the operator is mandatory, so a bare integer or
340
+ # constant never matches. `..` with nothing on either side is not Ruby and is rejected after
341
+ # the match.
342
+ RANGE_ENDPOINT = /-?(?:\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|Float::INFINITY|Float::MAX)|nil/
343
+ RANGE_LITERAL = /(?<lo>#{RANGE_ENDPOINT})?\s*(?<op>\.\.\.?)\s*(?<hi>#{RANGE_ENDPOINT})?/
344
+ private_constant :SIMPLE_NAME, :CLASS_NAME, :SIGNED_INT, :SYMBOL_LITERAL, :STRING_LITERAL,
345
+ :RANGE_ENDPOINT, :RANGE_LITERAL
277
346
 
278
347
  def parse_type_ast
279
348
  if (class_name = @scanner.scan(CLASS_NAME))
@@ -381,7 +450,9 @@ module Rigor
381
450
  end
382
451
 
383
452
  def parse_single_type_arg_ast
384
- if (class_name = @scanner.scan(CLASS_NAME))
453
+ if (range = scan_range_literal)
454
+ range
455
+ elsif (class_name = @scanner.scan(CLASS_NAME))
385
456
  parse_class_arg_tail_ast(class_name)
386
457
  elsif (literal = @scanner.scan(SIGNED_INT))
387
458
  TypeNode::IntegerLiteral.new(value: Integer(literal))
@@ -420,6 +491,36 @@ module Rigor
420
491
  Integer(literal)
421
492
  end
422
493
 
494
+ # Returns a {TypeNode::RangeLiteral} when the scanner sits on a Ruby range literal, `nil`
495
+ # (without consuming input) otherwise. `nil` and an absent endpoint both become Ruby's
496
+ # `nil` endpoint; the two spellings denote the same range.
497
+ def scan_range_literal
498
+ return nil unless @scanner.scan(RANGE_LITERAL)
499
+
500
+ lo = @scanner[:lo]
501
+ hi = @scanner[:hi]
502
+ return nil if lo.nil? && hi.nil?
503
+
504
+ TypeNode::RangeLiteral.new(
505
+ value: Range.new(range_endpoint(lo), range_endpoint(hi), @scanner[:op] == "...")
506
+ )
507
+ end
508
+
509
+ # The endpoint as the Ruby value the token denotes: an Integer literal stays an Integer (the
510
+ # `Integer` head needs to tell `1` from `1.0`), a Float literal or one of the four spelled
511
+ # constants becomes the double.
512
+ def range_endpoint(token)
513
+ case token
514
+ when nil, "nil" then nil
515
+ when "Float::INFINITY" then Float::INFINITY
516
+ when "-Float::INFINITY" then -Float::INFINITY
517
+ when "Float::MAX" then Float::MAX
518
+ when "-Float::MAX" then -Float::MAX
519
+ when /[.eE]/ then Float(token)
520
+ else Integer(token)
521
+ end
522
+ end
523
+
423
524
  def skip_ws
424
525
  @scanner.skip(/\s+/)
425
526
  end
@@ -462,7 +563,7 @@ module Rigor
462
563
  # `StringLiteral`) carries a Ruby value that lifts directly to a `Constant<value>`
463
564
  # carrier through the same helper.
464
565
  LITERAL_AST_NODES = [
465
- TypeNode::IntegerLiteral, TypeNode::SymbolLiteral, TypeNode::StringLiteral
566
+ TypeNode::IntegerLiteral, TypeNode::SymbolLiteral, TypeNode::StringLiteral, TypeNode::RangeLiteral
466
567
  ].freeze
467
568
  private_constant :LITERAL_AST_NODES
468
569
 
@@ -518,6 +619,11 @@ module Rigor
518
619
  end
519
620
 
520
621
  def resolve_generic(node)
622
+ # ADR-109 — `Integer[1..10]`. A range literal under a numeric head is that head's range
623
+ # refinement or nothing: an empty or malformed range declines here rather than falling
624
+ # through to a `Nominal[Integer, [Constant<Range>]]` the RBS fallback would otherwise build.
625
+ return resolve_range_head(node) if range_headed?(node)
626
+
521
627
  builtin = try_builtin_parametric(node)
522
628
  return builtin unless builtin.nil?
523
629
 
@@ -546,6 +652,19 @@ module Rigor
546
652
  try_parametric_type_builder(node) || try_parametric_int_builder(node)
547
653
  end
548
654
 
655
+ def range_headed?(node)
656
+ !ImportedRefinements.range_head_builder(node.head).nil? &&
657
+ node.args.any?(TypeNode::RangeLiteral)
658
+ end
659
+
660
+ # Exactly one range literal under the head; `Integer[1..10, 3]` is not a range refinement.
661
+ # Anything else under the same head (`Integer[Foo]`) keeps its pre-ADR-109 path.
662
+ def resolve_range_head(node)
663
+ return nil unless node.args.size == 1
664
+
665
+ ImportedRefinements.range_head_builder(node.head).call(node.args.first.value)
666
+ end
667
+
549
668
  def try_parametric_type_builder(node)
550
669
  builder = ImportedRefinements.parametric_type_builder(node.head)
551
670
  return nil if builder.nil?
@@ -558,11 +677,14 @@ module Rigor
558
677
  result
559
678
  end
560
679
 
561
- # ADR-13 slice 3b — record one `dynamic.shape.lossy-projection` event per (head,
562
- # source_location) pair when the projection actually degraded. The builders return
563
- # the source carrier unchanged on non-HashShape / non-Tuple receivers (see
564
- # `Type::Combinator.pick_of` / `omit_of` and the HashShape-only `partial_of` /
565
- # `required_of` / `readonly_of`), so detection is "first arg was lossy".
680
+ # ADR-13 slice 3b — record one `dynamic.shape.lossy-projection` event per (head, position) pair when
681
+ # the projection actually degraded. The builders return the source carrier unchanged on
682
+ # non-HashShape / non-Tuple receivers (see `Type::Combinator.pick_of` / `omit_of` and the
683
+ # HashShape-only `partial_of` / `required_of` / `readonly_of`), so detection is "first arg was lossy".
684
+ #
685
+ # Issue #805 — the `RBS::Location` is flattened to `(path, line, column)` here, at record time: the
686
+ # entry is drained out of every fork-pool worker, and a location survives neither the drain's
687
+ # `Marshal.dump` nor its cross-worker dedup (see {Rigor::RbsExtended::Reporter}).
566
688
  def record_lossy_projection_if_applicable(node, args, result)
567
689
  return if @reporter.nil?
568
690
  return if result.nil?
@@ -570,10 +692,8 @@ module Rigor
570
692
  return if args.empty?
571
693
  return unless Type::Combinator.shape_projection_lossy?(args.first)
572
694
 
573
- @reporter.record_lossy_projection(
574
- head: node.head,
575
- source_location: @source_location
576
- )
695
+ path, line, column = RbsExtended::Reporter.position_of(@source_location)
696
+ @reporter.record_lossy_projection(head: node.head, path: path, line: line, column: column)
577
697
  end
578
698
 
579
699
  def try_parametric_int_builder(node)
@@ -583,7 +703,23 @@ module Rigor
583
703
  bounds = node.args.map { |a| a.is_a?(TypeNode::IntegerLiteral) ? a.value : nil }
584
704
  return nil if bounds.any?(&:nil?)
585
705
 
586
- builder.call(bounds)
706
+ result = builder.call(bounds)
707
+ record_deprecated_form(node, bounds, result) unless result.nil?
708
+ result
709
+ end
710
+
711
+ # ADR-109 WD3 — the angle-bracket form still resolves, and the run says so once per annotation with
712
+ # the spelling the same type now displays as. Recorded only when the payload resolved: an
713
+ # unresolvable one already surfaces as `dynamic.rbs-extended.unresolved`, and a second row for it
714
+ # would name a replacement for something that never parsed.
715
+ def record_deprecated_form(node, bounds, result)
716
+ return if @reporter.nil?
717
+
718
+ path, line, column = RbsExtended::Reporter.position_of(@source_location)
719
+ @reporter.record_deprecated_form(
720
+ payload: "#{node.head}<#{bounds.join(', ')}>", replacement: result.describe,
721
+ path: path, line: line, column: column
722
+ )
587
723
  end
588
724
 
589
725
  def resolve_args(args)
@@ -117,8 +117,7 @@ module Rigor
117
117
 
118
118
  # --- private ------------------------------------------------------
119
119
 
120
- # @param value [String] a non-empty string
121
- # @return [Rigor::Type]
120
+ # @param value — a non-empty string
122
121
  def self.classify_string(value)
123
122
  if Type::Refined.ruby_numeric_literal?(value)
124
123
  NUMERIC_STRING
@@ -133,8 +132,7 @@ module Rigor
133
132
  # Called ONLY with the names in {RUNTIME_STRING_CONSTANTS} — Rigor's own source — and only
134
133
  # while this file is being loaded. It is never handed a name from the analysed program.
135
134
  #
136
- # @param name [String] a qualified constant path without a leading "::"
137
- # @return [String, nil]
135
+ # @param name — a qualified constant path without a leading "::"
138
136
  def self.runtime_string_value(name)
139
137
  mod = ::Object
140
138
  name.split("::").each do |part|
@@ -161,7 +159,7 @@ module Rigor
161
159
  end
162
160
  private_class_method :runtime_string_value
163
161
 
164
- # @return [Hash{String => Rigor::Type}] frozen, built once at load time.
162
+ # @return frozen, built once at load time.
165
163
  def self.build_runtime_string_refinements
166
164
  RUNTIME_STRING_CONSTANTS.each_with_object({}) do |name, table|
167
165
  value = runtime_string_value(name)
@@ -175,9 +173,9 @@ module Rigor
175
173
 
176
174
  # --- public API ---------------------------------------------------
177
175
 
178
- # @param name [String] unqualified constant name (e.g. `"Math::PI"`,
176
+ # @param name — unqualified constant name (e.g. `"Math::PI"`,
179
177
  # `"RUBY_VERSION"`, `"Ruby::ENGINE"`)
180
- # @return [Rigor::Type, nil] refined type, or nil to fall through
178
+ # @return refined type, or nil to fall through
181
179
  def self.lookup(name)
182
180
  FOLDED_CONSTANTS[name] || RUNTIME_STRING_REFINEMENTS[name]
183
181
  end
@@ -62,11 +62,11 @@ module Rigor
62
62
 
63
63
  module_function
64
64
 
65
- # @param body [String, nil] a regex sub-pattern, typically the inner body of a
65
+ # @param body — a regex sub-pattern, typically the inner body of a
66
66
  # `(?<name>body)` named capture. Anchors (`\A`, `\z`, `^`, `$`) are not stripped — the
67
67
  # recogniser table targets bodies that the regex engine treats as anchored to the
68
68
  # capture group bounds.
69
- # @return [Rigor::Type, nil] the matching imported refinement carrier, or `nil` if
69
+ # @return the matching imported refinement carrier, or `nil` if
70
70
  # `body` is not a recognised shape.
71
71
  def for_capture_body(body)
72
72
  return nil if body.nil? || body.empty?
@@ -100,8 +100,8 @@ module Rigor
100
100
  # free-whitespace/`#`-comment flag from the `source` alone, so consumers bail on it before
101
101
  # calling here.
102
102
  #
103
- # @param source [String, nil] the full regex source string.
104
- # @return [Rigor::Type, nil] the matching imported refinement carrier, or `nil`.
103
+ # @param source — the full regex source string.
104
+ # @return the matching imported refinement carrier, or `nil`.
105
105
  def for_whole_pattern(source)
106
106
  return nil if source.nil?
107
107
  return nil unless source.start_with?('\A') && source.end_with?('\z')
@@ -97,16 +97,15 @@ module Rigor
97
97
 
98
98
  # Looks up a refined return type for the given call.
99
99
  #
100
- # @param owner_class_name [String, nil] the class on which the method is defined
100
+ # @param owner_class_name — the class on which the method is defined
101
101
  # (e.g., `"Kernel"`). Pass `nil` when the caller hasn't resolved a defining owner
102
102
  # yet — the lookup will then fall back to matching by `(method_name, kind)` against
103
103
  # entries whose owner is currently in the table.
104
- # @param method_name [Symbol]
105
- # @param kind [Symbol] one of `:singleton`, `:instance`. The caller passes the shape of
104
+ # @param kind — one of `:singleton`, `:instance`. The caller passes the shape of
106
105
  # the actual call site; the table stores `:both` for entries that match either.
107
- # @param arg_types [Array<Rigor::Type>] positional argument types. Forwarded to the
106
+ # @param arg_types — positional argument types. Forwarded to the
108
107
  # handler so future entries can discriminate on argument shape.
109
- # @return [Rigor::Type, nil] the refined return type, or `nil` when no override
108
+ # @return the refined return type, or `nil` when no override
110
109
  # matches.
111
110
  def self.lookup(owner_class_name:, method_name:, kind:, arg_types: [])
112
111
  return nil if owner_class_name.nil?
@@ -127,7 +126,7 @@ module Rigor
127
126
  end.freeze
128
127
  private_constant :OWNERS_BY_METHOD
129
128
 
130
- # @return [Array<String>] the candidate owner class names for a bare method-name
129
+ # @return the candidate owner class names for a bare method-name
131
130
  # lookup. Empty when no override names this method.
132
131
  NO_OWNERS = [].freeze
133
132
  private_constant :NO_OWNERS