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
@@ -31,11 +31,12 @@ module Rigor
31
31
  # - **No-argument refinement names** (`non-empty-string`, `non-zero-int`,
32
32
  # `lowercase-string`, …) live in `REGISTRY` and resolve through `lookup(name)`.
33
33
  # - **Parameterised refinement payloads** (`non-empty-array[Integer]`,
34
- # `non-empty-hash[Symbol, Integer]`, `int<5, 10>`) are accepted by `parse(payload)`. The
35
- # full grammar is documented on `Parser`. The two surfaces share `REGISTRY` for the
36
- # no-arg head names; the parameterised head names live in `PARAMETERISED_TYPE_BUILDERS`
37
- # (square-bracket form, type args) and `PARAMETERISED_INT_BUILDERS` (angle-bracket form,
38
- # 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).
39
40
  module ImportedRefinements
40
41
  REGISTRY = {
41
42
  "non-empty-string" => -> { Type::Combinator.non_empty_string },
@@ -58,7 +59,10 @@ module Rigor
58
59
  "non-empty-lowercase-string" => -> { Type::Combinator.non_empty_lowercase_string },
59
60
  "non-empty-uppercase-string" => -> { Type::Combinator.non_empty_uppercase_string },
60
61
  "literal-string" => -> { Type::Combinator.literal_string },
61
- "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 }
62
66
  }.freeze
63
67
  private_constant :REGISTRY
64
68
 
@@ -139,13 +143,52 @@ module Rigor
139
143
  }.freeze
140
144
  private_constant :PARAMETERISED_TYPE_BUILDERS
141
145
 
142
- # `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
143
183
  # `Array<Integer>` and returns a `Rigor::Type` (or `nil`). Bounds are signed integer
144
- # literals; `min` MUST be ≤ `max` for the carrier to construct successfully
145
- # (`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.
146
188
  PARAMETERISED_INT_BUILDERS = {
147
189
  "int" => lambda { |bounds|
148
190
  return nil unless bounds.size == 2
191
+ return nil if bounds[0] > bounds[1]
149
192
 
150
193
  Type::Combinator.integer_range(bounds[0], bounds[1])
151
194
  }
@@ -154,32 +197,32 @@ module Rigor
154
197
 
155
198
  module_function
156
199
 
157
- # @param name [String] kebab-case refinement name.
158
- # @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
159
202
  # not registered.
160
203
  def lookup(name)
161
204
  builder = REGISTRY[name.to_s]
162
205
  builder&.call
163
206
  end
164
207
 
165
- # @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)
166
209
  # directive. Accepts the bare-name forms `lookup` already handles plus the
167
210
  # parameterised forms documented on {Parser}.
168
- # @param name_scope [Rigor::TypeNode::NameScope, nil]
211
+ # @param name_scope
169
212
  # ADR-13 slice 3 — when provided, the parser consults the scope's `#resolver` chain
170
213
  # after the built-in registry and built-in parametric forms but before the RBS
171
214
  # Nominal fallback. `nil` (default) preserves the slice-1 / slice-2 behaviour of
172
215
  # consulting only built-ins + RBS.
173
- # @param reporter [Rigor::RbsExtended::Reporter, nil]
216
+ # @param reporter
174
217
  # ADR-13 slice 3b — collector that the Resolver feeds `dynamic.shape.lossy-projection`
175
218
  # events into when a shape-projection head (`pick_of`, `omit_of`, `partial_of`,
176
219
  # `required_of`, `readonly_of`) is applied to a carrier that does not preserve shape
177
220
  # information. `nil` (default) suppresses event accumulation; legacy call sites that
178
221
  # have no reporter to thread keep the pre-slice-3b silent fall-through.
179
- # @param source_location [RBS::Location, nil] location attribution for the events the
222
+ # @param source_location location attribution for the events the
180
223
  # Resolver records. Carries the annotation's filename / line / column so the runner
181
224
  # can stamp diagnostics with the user-visible source site.
182
- # @return [Rigor::Type, nil] the resolved refinement carrier, or `nil` when the payload
225
+ # @return the resolved refinement carrier, or `nil` when the payload
183
226
  # is unparseable or names a refinement / class no registered source resolved.
184
227
  def parse(payload, name_scope: nil, reporter: nil, source_location: nil)
185
228
  Parser.new(
@@ -201,6 +244,20 @@ module Rigor
201
244
  PARAMETERISED_INT_BUILDERS[name]
202
245
  end
203
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
+
204
261
  def known?(name)
205
262
  REGISTRY.key?(name.to_s) ||
206
263
  PARAMETERISED_TYPE_BUILDERS.key?(name.to_s) ||
@@ -219,8 +276,10 @@ module Rigor
219
276
  # parametric := simple_name '[' type_arg_list ']'
220
277
  # | simple_name '<' int_bound_list '>'
221
278
  # type_arg_list := type_arg (',' type_arg)*
222
- # type_arg := type | class_name
279
+ # type_arg := range_literal | type | class_name
223
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'
224
283
  # int_bound_list := signed_int (',' signed_int)*
225
284
  # signed_int := /-?\d+/
226
285
  #
@@ -274,7 +333,16 @@ module Rigor
274
333
  # delimiter.
275
334
  SYMBOL_LITERAL = /:(?<value>[a-zA-Z_][a-zA-Z0-9_]*[?!=]?)/
276
335
  STRING_LITERAL = /"(?<value>[^"\\]*)"/
277
- 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
278
346
 
279
347
  def parse_type_ast
280
348
  if (class_name = @scanner.scan(CLASS_NAME))
@@ -382,7 +450,9 @@ module Rigor
382
450
  end
383
451
 
384
452
  def parse_single_type_arg_ast
385
- if (class_name = @scanner.scan(CLASS_NAME))
453
+ if (range = scan_range_literal)
454
+ range
455
+ elsif (class_name = @scanner.scan(CLASS_NAME))
386
456
  parse_class_arg_tail_ast(class_name)
387
457
  elsif (literal = @scanner.scan(SIGNED_INT))
388
458
  TypeNode::IntegerLiteral.new(value: Integer(literal))
@@ -421,6 +491,36 @@ module Rigor
421
491
  Integer(literal)
422
492
  end
423
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
+
424
524
  def skip_ws
425
525
  @scanner.skip(/\s+/)
426
526
  end
@@ -463,7 +563,7 @@ module Rigor
463
563
  # `StringLiteral`) carries a Ruby value that lifts directly to a `Constant<value>`
464
564
  # carrier through the same helper.
465
565
  LITERAL_AST_NODES = [
466
- TypeNode::IntegerLiteral, TypeNode::SymbolLiteral, TypeNode::StringLiteral
566
+ TypeNode::IntegerLiteral, TypeNode::SymbolLiteral, TypeNode::StringLiteral, TypeNode::RangeLiteral
467
567
  ].freeze
468
568
  private_constant :LITERAL_AST_NODES
469
569
 
@@ -519,6 +619,11 @@ module Rigor
519
619
  end
520
620
 
521
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
+
522
627
  builtin = try_builtin_parametric(node)
523
628
  return builtin unless builtin.nil?
524
629
 
@@ -547,6 +652,19 @@ module Rigor
547
652
  try_parametric_type_builder(node) || try_parametric_int_builder(node)
548
653
  end
549
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
+
550
668
  def try_parametric_type_builder(node)
551
669
  builder = ImportedRefinements.parametric_type_builder(node.head)
552
670
  return nil if builder.nil?
@@ -585,7 +703,23 @@ module Rigor
585
703
  bounds = node.args.map { |a| a.is_a?(TypeNode::IntegerLiteral) ? a.value : nil }
586
704
  return nil if bounds.any?(&:nil?)
587
705
 
588
- 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
+ )
589
723
  end
590
724
 
591
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
@@ -41,8 +41,7 @@ module Rigor
41
41
 
42
42
  module_function
43
43
 
44
- # @param location [RBS::Location, nil]
45
- # @return [Array, nil] `[name, start_line, start_column, end_line, end_column]`, or nil when there is
44
+ # @return `[name, start_line, start_column, end_line, end_column]`, or nil when there is
46
45
  # no location to carry. Fail-soft: a location whose buffer cannot answer is dumped as nil rather
47
46
  # than failing the whole environment's dump.
48
47
  def dump(location)
@@ -55,8 +54,7 @@ module Rigor
55
54
  nil
56
55
  end
57
56
 
58
- # @param payload [Array, nil] what {dump} produced.
59
- # @return [RBS::Location, nil]
57
+ # @param payload what {dump} produced.
60
58
  def load(payload)
61
59
  return nil if payload.nil?
62
60
 
@@ -48,7 +48,12 @@ module Rigor
48
48
  # added to a schema-less project) invalidates the warm entry. A pre-8 entry carries no absence rows
49
49
  # and would validate fresh across exactly that edit, so cached entries must read as misses once and
50
50
  # rebuild carrying the rows.
51
- SCHEMA_VERSION = 8
51
+ # v9: #979 — the run-result dependency descriptor now carries a names-only {GlobEntry} directory
52
+ # listing per RBS signature root, so a `.rbs` file APPEARING under one (a `sig/roles.rbs` declaring the
53
+ # interface a `conforms-to` names) invalidates the warm entry. A pre-9 entry carries no signature-root
54
+ # row and would validate fresh across exactly that edit — the #577 reasoning, one slot over — so cached
55
+ # entries must read as misses once and rebuild carrying the rows.
56
+ SCHEMA_VERSION = 9
52
57
 
53
58
  # Per-slot entry value objects. Constructors validate enums / required fields and freeze the resulting
54
59
  # struct so no caller can mutate after the entry is in a Descriptor.
@@ -114,7 +119,7 @@ module Rigor
114
119
  new(path: path, comparator: :exists, value: PRESENT)
115
120
  end
116
121
 
117
- # @return [Boolean] whether this row records the path as absent (see {.absent}).
122
+ # @return whether this row records the path as absent (see {.absent}).
118
123
  def absent?
119
124
  comparator == :exists && value == ABSENT
120
125
  end
@@ -235,31 +240,58 @@ module Rigor
235
240
  FIELD_SEPARATOR = "\0"
236
241
  private_constant :ROW_SEPARATOR, :FIELD_SEPARATOR
237
242
 
243
+ # `:stat` is the ADR-87 WD2 signature above — it answers "did anything under this glob change",
244
+ # appearance and edit alike. `:names` (#979) answers only "did the SET of matching paths change":
245
+ # its rows are the sorted relative names, so a file whose bytes are unchanged never moves the
246
+ # signature however its stat tuple moved. A row exists to catch appearance/disappearance only —
247
+ # edits to the files it lists are already carried by their own `:stat` {FileEntry} rows — and a
248
+ # names row must therefore survive what those rows survive. A `:stat` glob over a signature tree
249
+ # would not: ADR-87 WD1 lets a `:stat` file row fall back to its recorded content digest when the
250
+ # tuple moves, so a `git checkout`, a `bundle install` or a CI cache restored over a fresh checkout
251
+ # keeps the file rows fresh while it would move every glob signature, turning every such run into a
252
+ # full re-analysis. `:names` also reads no stat at all, so it costs a `Dir.glob` and nothing else.
253
+ VALID_MODES = %i[stat names].freeze
254
+
238
255
  attr_reader :root, :pattern, :value
239
256
 
240
- value_fields :root, :pattern, :value
257
+ value_fields :root, :pattern, :mode, :value
258
+
259
+ def initialize(root:, pattern:, value:, mode: :stat)
260
+ raise ArgumentError, "GlobEntry mode must be one of #{VALID_MODES.inspect}, got #{mode.inspect}" \
261
+ unless VALID_MODES.include?(mode)
241
262
 
242
- def initialize(root:, pattern:, value:)
243
263
  @root = root.to_s.dup.freeze
244
264
  @pattern = pattern.to_s.dup.freeze
265
+ @mode = mode
245
266
  @value = value.to_s.dup.freeze
246
267
  freeze
247
268
  end
248
269
 
270
+ # Defaulted rather than an `attr_reader` because an entry Marshal-loaded from a store written before
271
+ # the field existed carries no `@mode` ivar; every such entry was recorded under the stat signature.
272
+ def mode
273
+ @mode || :stat
274
+ end
275
+
249
276
  # Builds the entry for the glob's CURRENT filesystem state.
250
- def self.compute(root:, pattern:)
251
- new(root: root, pattern: pattern, value: signature_for(root: root, pattern: pattern))
277
+ def self.compute(root:, pattern:, mode: :stat)
278
+ new(root: root, pattern: pattern, mode: mode,
279
+ value: signature_for(root: root, pattern: pattern, mode: mode))
252
280
  end
253
281
 
254
282
  # The aggregate signature the entry's `value` carries: SHA-256 over the sorted per-file rows. In the
255
283
  # default (`:stat`) mode a row is the file's `(path, size, mtime_ns, ctime_ns, inode)` tuple — NO
256
284
  # content read; in strict mode a row is `(path, content-sha256)`, restoring the pre-ADR-87 authority.
285
+ # In `:names` mode a row is the path alone, stat-free, so only the matching SET moves the signature.
257
286
  # Per-file stat failures (a file vanishing between the glob and the stat) drop the row — same race
258
287
  # posture as {Descriptor#file_entry_fresh?}. `Dir.glob` returns sorted entries by default so the row
259
288
  # order — and therefore the signature — is stable.
260
- def self.signature_for(root:, pattern:)
289
+ def self.signature_for(root:, pattern:, mode: :stat)
290
+ paths = Dir.glob(File.join(root, pattern))
291
+ return Digest::SHA256.hexdigest(paths.map { |path| "#{path}#{ROW_SEPARATOR}" }.join) if mode == :names
292
+
261
293
  strict = FileDigest.strict_validation?
262
- rows = Dir.glob(File.join(root, pattern)).filter_map do |path|
294
+ rows = paths.filter_map do |path|
263
295
  st = File.stat(path)
264
296
  next nil unless st.file?
265
297
 
@@ -276,23 +308,30 @@ module Rigor
276
308
  end
277
309
 
278
310
  # ADR-87 WD2 — fresh iff re-globbing + re-stat-ing reproduces the recorded signature. Zero file-content
279
- # bytes are read in the default mode. Any failure reads as stale (recompute), never a crash. A stored
280
- # signature recorded under the opposite validation mode simply mismatches and recomputes.
311
+ # bytes are read in the default mode, and no stat at all in `:names`. Any failure reads as stale
312
+ # (recompute), never a crash. A stored signature recorded under the opposite validation mode simply
313
+ # mismatches and recomputes.
281
314
  def self.fresh?(entry)
282
- signature_for(root: entry.root, pattern: entry.pattern) == entry.value
315
+ signature_for(root: entry.root, pattern: entry.pattern, mode: entry.mode) == entry.value
283
316
  rescue StandardError
284
317
  false
285
318
  end
286
319
 
287
- # Composition key — {.compose} unions per (root, pattern) slot; two contributions for the same slot
288
- # must agree on the value or {Conflict} is raised. Within one run the same glob stat-reads identically,
289
- # so contributions never conflict.
320
+ # Composition key — {.compose} unions per (root, pattern, mode) slot; two contributions for the same
321
+ # slot must agree on the value or {Conflict} is raised. Within one run the same glob stat-reads
322
+ # identically, so contributions never conflict. The mode is part of the key because the two signatures
323
+ # over one (root, pattern) are different questions, not rival answers to the same one.
290
324
  def slot_key
291
- "#{root}\0#{pattern}"
325
+ "#{root}\0#{pattern}\0#{mode}"
292
326
  end
293
327
 
294
328
  def to_h
295
- { "root" => root, "pattern" => pattern, "value" => value }
329
+ h = { "root" => root, "pattern" => pattern, "value" => value }
330
+ # Omitted in the default mode so a descriptor carrying only stat globs keeps its pre-#979 canonical
331
+ # bytes: this FIELD moves no producer's key. (Every key still moved once with SCHEMA_VERSION 9,
332
+ # which the storage layer mixes in; that is the #979 migration, not the mode.)
333
+ h["mode"] = mode.to_s unless mode == :stat
334
+ h
296
335
  end
297
336
  end
298
337
 
@@ -352,9 +391,8 @@ module Rigor
352
391
  dependencies: dependencies, globs: globs)
353
392
  end
354
393
 
355
- # @param producer_id [String]
356
- # @param params [Hash] inputs the producer was called with
357
- # @return [String] hex SHA-256 cache key for the value
394
+ # @param params — inputs the producer was called with
395
+ # @return hex SHA-256 cache key for the value
358
396
  def cache_key_for(producer_id:, params: {})
359
397
  payload = {
360
398
  "schema_version" => SCHEMA_VERSION,
@@ -377,7 +415,7 @@ module Rigor
377
415
  "dependencies" => sort_entries(dependencies, "gem_name").map(&:to_h),
378
416
  "files" => sort_entries(files, "path").map(&:to_h),
379
417
  "gems" => sort_entries(gems, "name").map(&:to_h),
380
- "globs" => globs.sort_by { |e| [e.root, e.pattern] }.map(&:to_h),
418
+ "globs" => globs.sort_by { |e| [e.root, e.pattern, e.mode.to_s] }.map(&:to_h),
381
419
  "plugins" => sort_entries(plugins, "id").map(&:to_h)
382
420
  }
383
421
  end
@@ -65,15 +65,15 @@ module Rigor
65
65
 
66
66
  module_function
67
67
 
68
- # @return [String] the gem root — the directory holding `lib/`, three levels above this file.
68
+ # @return the gem root — the directory holding `lib/`, three levels above this file.
69
69
  def root
70
70
  @root ||= File.expand_path("../../..", __dir__)
71
71
  end
72
72
 
73
- # @param root [String] the gem root, defaulted through {.root} so a spec can relocate the tree.
74
- # @return [String, nil] a digest identifying the engine's current source, or nil when the tree is
73
+ # @param root the gem root, defaulted through {.root} so a spec can relocate the tree.
74
+ # @return a digest identifying the engine's current source, or nil when the tree is
75
75
  # version-pinned and the caller should add no slot at all.
76
- # @raise [Unavailable] when a mutable tree's source cannot be read.
76
+ # @raise Unavailable when a mutable tree's source cannot be read.
77
77
  def identity(root = self.root)
78
78
  return nil if version_pinned?(root)
79
79
 
@@ -74,7 +74,7 @@ module Rigor
74
74
  memo[path] ||= Digest::SHA256.file(path).hexdigest
75
75
  end
76
76
 
77
- # @return [Boolean] whether digest-always validation is forced for this run. The env var wins over the
77
+ # @return whether digest-always validation is forced for this run. The env var wins over the
78
78
  # run-scoped config flag so an operator can bypass the stat tier without editing `.rigor.yml`.
79
79
  def self.strict_validation?
80
80
  return true if ENV[STRICT_ENV] == "1"
@@ -112,6 +112,14 @@ module Rigor
112
112
  hexdigest(path) == digest
113
113
  end
114
114
 
115
+ # The content-digest field of a packed `:stat` entry ({.pack_stat}); nil when the entry is absent or not
116
+ # well-formed. Exposed for the caller that must compare the recorded digest against bytes it hashes
117
+ # itself rather than against the file on disk — the incremental session's editor-mode freshness check,
118
+ # whose authority is the editor's buffer.
119
+ def self.content_digest(packed)
120
+ parse_stat(packed)&.first
121
+ end
122
+
115
123
  # The VALIDATION-side stat, served from the per-run table when one is installed — a collecting run
116
124
  # validates the effects entry and the diagnostics entry against the same ~thousands-of-files
117
125
  # dependency descriptor, and the second pass is pure repetition under the run's own stable-filesystem