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
@@ -48,6 +48,8 @@ module Rigor
48
48
  # with the running Ruby**, where "the analyzer's Ruby is the target's Ruby" already covers them.
49
49
  # A third-party gem's `VERSION` is not read: its version is the project's `Gemfile.lock`'s to say,
50
50
  # and the lockfile is not visible from the inference layer.
51
+ # * The ROOTED spelling of any of the above — `::RUBY_VERSION`, `::RUBY_ENGINE`, `::Psych::VERSION` —
52
+ # names the same constant as its bare twin and folds identically; see {.read_constant}.
51
53
  # * `RUBY_PLATFORM` — **never** folded. Every comparison against it is by construction
52
54
  # platform-dependent, and the machine running `rigor` need not be the machine running the program.
53
55
  # * `<=>` — never folded (it yields -1/0/1, not a branch verdict), and neither are `defined?(Ractor)`
@@ -68,7 +70,7 @@ module Rigor
68
70
 
69
71
  # Predefined constants read from the analyzer's runtime, mapped to the operand kind they produce.
70
72
  # `:string` operands accept every comparison; `:engine` operands accept equality only.
71
- PREDEFINED = { RUBY_VERSION: :string, RUBY_ENGINE: :engine }.freeze
73
+ PREDEFINED = { "RUBY_VERSION" => :string, "RUBY_ENGINE" => :engine }.freeze
72
74
  private_constant :PREDEFINED
73
75
 
74
76
  # Operand kinds that carry a plain Ruby String, i.e. the ones `Gem::Version.new` may wrap and the
@@ -88,8 +90,8 @@ module Rigor
88
90
  GEM_VERSION_PATH = "Gem::Version"
89
91
  private_constant :GEM_VERSION_PATH
90
92
 
91
- # @param node [Prism::Node, nil] an `if` / `unless` predicate
92
- # @return [Symbol, nil] `:truthy` / `:falsey` when the guard is decidable on the analyzer's Ruby,
93
+ # @param node an `if` / `unless` predicate
94
+ # @return `:truthy` / `:falsey` when the guard is decidable on the analyzer's Ruby,
93
95
  # otherwise nil (both arms stay live)
94
96
  def verdict(node)
95
97
  return nil unless node.is_a?(Prism::CallNode)
@@ -109,35 +111,40 @@ module Rigor
109
111
  end
110
112
 
111
113
  # Reads one side of the comparison into `[kind, value]`, or nil when it is not readable.
112
- #
113
- # @return [Array(Symbol, Object), nil]
114
114
  def read_operand(node)
115
115
  case node
116
116
  when Prism::StringNode then [:literal_string, node.unescaped]
117
- when Prism::ConstantReadNode then read_predefined(node)
118
- when Prism::ConstantPathNode then read_version_constant(node)
117
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then read_constant(node)
119
118
  when Prism::CallNode then read_gem_version(node)
120
119
  end
121
120
  end
122
121
  private_class_method :read_operand
123
122
 
124
- def read_predefined(node)
125
- kind = PREDEFINED[node.name]
126
- return nil unless kind
127
-
128
- value = runtime_value(node.name.to_s)
129
- value && [kind, value]
130
- end
131
- private_class_method :read_predefined
132
-
133
- def read_version_constant(node)
123
+ # Dispatches on the RESOLVED name rather than on the node class. `::RUBY_VERSION` names the very
124
+ # constant `RUBY_VERSION` names — the leading `::` only makes the top-level lookup explicit — but
125
+ # Prism spells it as a `ConstantPathNode` with a nil parent, so a node-class dispatch sent it down
126
+ # the `X::VERSION` path and declined it for not being a curated qualified name. The same program
127
+ # then had two diagnostic sets for two spellings of one guard
128
+ # ([#877](https://github.com/rigortype/rigor/issues/877)).
129
+ #
130
+ # The foldable set is unchanged and stays closed: an unqualified name folds only when it is one of
131
+ # {PREDEFINED}, a qualified one only when it is in {VERSION_CONSTANTS}.
132
+ def read_constant(node)
134
133
  path = Source::ConstantPath.qualified_name_or_nil(node)
135
- return nil unless path && VERSION_CONSTANTS.include?(path)
134
+ return nil unless path
135
+
136
+ kind =
137
+ if path.include?("::")
138
+ :string if VERSION_CONSTANTS.include?(path)
139
+ else
140
+ PREDEFINED[path]
141
+ end
142
+ return nil unless kind
136
143
 
137
144
  value = runtime_value(path)
138
- value && [:string, value]
145
+ value && [kind, value]
139
146
  end
140
- private_class_method :read_version_constant
147
+ private_class_method :read_constant
141
148
 
142
149
  # `Gem::Version.new(<readable>)`. The inner operand must be a plain version String — an engine name
143
150
  # is not a version, and `Gem::Version.new` raises on anything `Gem::Version.correct?` rejects, so a
@@ -165,7 +172,7 @@ module Rigor
165
172
  # `const_get` would execute the target file inside the analyzer. `Module#autoload?` is what
166
173
  # separates the two ([#680](https://github.com/rigortype/rigor/issues/680)).
167
174
  #
168
- # @return [String, nil] the constant's value when it is a non-empty String
175
+ # @return the constant's value when it is a non-empty String
169
176
  def runtime_value(path)
170
177
  mod = ::Object
171
178
  path.split("::").each do |part|
@@ -9,11 +9,11 @@ module Rigor
9
9
  # consistency, normalization, or erasure.
10
10
  #
11
11
  # @!attribute class_name
12
- # @return [String] the receiver class whose method declared `-> void`.
12
+ # @return the receiver class whose method declared `-> void`.
13
13
  # @!attribute method_name
14
- # @return [Symbol] the method whose return was recovered.
14
+ # @return the method whose return was recovered.
15
15
  # @!attribute kind
16
- # @return [Symbol] `:instance` or `:singleton`.
16
+ # @return `:instance` or `:singleton`.
17
17
  VoidOrigin = Data.define(:class_name, :method_name, :kind) do
18
18
  # A human-facing `Class#method` / `Class.method` label for the diagnostic message.
19
19
  def label
@@ -75,10 +75,8 @@ module Rigor
75
75
  # consult; the memo persists across consults for the run. The memo is probed before the visited
76
76
  # set is allocated so the common post-warmup hit path allocates nothing.
77
77
  #
78
- # @param def_node [Prism::DefNode]
79
- # @param owner [String] the qualified receiver class the `def` belongs to.
80
- # @param kind [Symbol] `:instance` or `:singleton`.
81
- # @return [Inference::VoidOrigin, nil]
78
+ # @param owner — the qualified receiver class the `def` belongs to.
79
+ # @param kind `:instance` or `:singleton`.
82
80
  def origin_for(def_node, owner, kind)
83
81
  return nil if def_node.nil?
84
82
 
@@ -51,7 +51,7 @@ module Rigor
51
51
  # computed from the stale text would be wrong. Consumers check {#desynchronized?} and decline to answer
52
52
  # rather than answering wrongly; the mark clears on the next `didOpen` or full-text change.
53
53
  #
54
- # @return [Boolean] true when the changes were applied.
54
+ # @return true when the changes were applied.
55
55
  def apply_changes(uri:, changes:, version:)
56
56
  text = IncrementalSync.apply_all(@entries[uri]&.bytes, changes)
57
57
  @desynchronized.delete(uri)
@@ -63,13 +63,13 @@ module Rigor
63
63
  false
64
64
  end
65
65
 
66
- # @return [Boolean] true when the last `didChange` for `uri` could not be applied, so the held text no
66
+ # @return true when the last `didChange` for `uri` could not be applied, so the held text no
67
67
  # longer matches the editor's.
68
68
  def desynchronized?(uri)
69
69
  @desynchronized.key?(uri)
70
70
  end
71
71
 
72
- # @return [String, nil] why `uri` is desynchronised, for the log line; nil when it is in sync.
72
+ # @return why `uri` is desynchronised, for the log line; nil when it is in sync.
73
73
  def desynchronization_reason(uri)
74
74
  @desynchronized[uri]
75
75
  end
@@ -91,7 +91,7 @@ module Rigor
91
91
  @dirty.delete(uri)
92
92
  end
93
93
 
94
- # @return [Boolean] true when `uri` has unsaved changes. A buffer that is dirty may only be published
94
+ # @return true when `uri` has unsaved changes. A buffer that is dirty may only be published
95
95
  # from an analysis that bound ITS bytes — see the publish-set invariant in
96
96
  # `docs/design/20260517-language-server.md`.
97
97
  def dirty?(uri)
@@ -42,7 +42,7 @@ module Rigor
42
42
  @project_context = project_context
43
43
  end
44
44
 
45
- # @return [Array<Hash>, nil] LSP `CompletionItem[]` or nil
45
+ # @return LSP `CompletionItem[]` or nil
46
46
  # when the cursor isn't at a position the provider can
47
47
  # enumerate completions for. Returning nil maps to
48
48
  # `result: null` per the LSP spec — clients treat it as
@@ -68,7 +68,7 @@ module Rigor
68
68
  end
69
69
  end
70
70
 
71
- # @return [Integer] number of currently-pending tasks.
71
+ # @return number of currently-pending tasks.
72
72
  def pending_size
73
73
  @mutex.synchronize { @tasks.size }
74
74
  end
@@ -30,13 +30,13 @@ module Rigor
30
30
  hint: 4
31
31
  }.freeze
32
32
 
33
- # @param debouncer [Rigor::LanguageServer::Debouncer, nil]
33
+ # @param debouncer
34
34
  # when present, `publish_for` schedules its work through
35
35
  # the debouncer (cancels prior pending task for the same
36
36
  # URI, fires after `debounce_seconds` quiet-time). Nil
37
37
  # keeps the slice 4-7 synchronous behaviour — primarily
38
38
  # useful for specs.
39
- # @param debounce_seconds [Numeric] quiet-time before the
39
+ # @param debounce_seconds quiet-time before the
40
40
  # debounced publish fires. 0 with a debouncer means
41
41
  # "schedule on next-tick" (still async); without a
42
42
  # debouncer the value is unused.
@@ -212,7 +212,7 @@ module Rigor
212
212
  notify(uri, diagnostics)
213
213
  end
214
214
 
215
- # @return [Hash, nil] `{ uri:, path:, bytes: }` when `uri` is eligible for the batch, or nil to
215
+ # @return `{ uri:, path:, bytes: }` when `uri` is eligible for the batch, or nil to
216
216
  # exclude it. Mirrors `#run_and_notify`'s own guards: a buffer closed during the debounce window is
217
217
  # dropped silently (its didClose empty publish already cleared the markers); a desynchronised buffer
218
218
  # publishes an EMPTY set immediately (same as the single-buffer path) rather than joining the batch.
@@ -320,7 +320,7 @@ module Rigor
320
320
  end
321
321
  end
322
322
 
323
- # @return [Hash, nil] the LSP `Diagnostic` Hash, or nil to
323
+ # @return the LSP `Diagnostic` Hash, or nil to
324
324
  # skip diagnostics outside the buffer's own path (e.g.
325
325
  # `.rigor.yml`-anchored info diagnostics get filtered —
326
326
  # they belong to the project, not the buffer).
@@ -30,7 +30,7 @@ module Rigor
30
30
  @project_context = project_context
31
31
  end
32
32
 
33
- # @return [Array<Hash>, nil] LSP `DocumentSymbol[]` for the
33
+ # @return LSP `DocumentSymbol[]` for the
34
34
  # buffer at `uri`. Returns nil when the URI isn't open or
35
35
  # doesn't parse cleanly enough to surface symbols — LSP
36
36
  # clients fall back to no-outline in that case.
@@ -23,7 +23,7 @@ module Rigor
23
23
  @project_context = project_context
24
24
  end
25
25
 
26
- # @return [Array<Hash>, nil] LSP `FoldingRange[]` for the
26
+ # @return LSP `FoldingRange[]` for the
27
27
  # buffer, or nil when the URI isn't open / parseable.
28
28
  def provide(uri)
29
29
  path, entry = buffer_for(uri)
@@ -29,7 +29,7 @@ module Rigor
29
29
  @renderer = renderer
30
30
  end
31
31
 
32
- # @return [Hash, nil] an LSP `Hover` payload or nil when no
32
+ # @return an LSP `Hover` payload or nil when no
33
33
  # expression sits at the queried position. Returning nil
34
34
  # maps to `result: null` per the LSP spec — clients
35
35
  # suppress the hover popup in that case.
@@ -13,7 +13,7 @@ module Rigor
13
13
  # (method call, constant, local, ivar, literal) gets the most relevant type-aware presentation per
14
14
  # `docs/design/20260517-lsp-hover-completion.md`.
15
15
  class HoverRenderer
16
- # @param node_scope_lookup [#[]] node-to-scope table built by `ScopeIndexer.index`. The renderer indexes
16
+ # @param node_scope_lookup node-to-scope table built by `ScopeIndexer.index`. The renderer indexes
17
17
  # into it to retrieve the receiver's narrow scope when specialising on `CallNode`. The lookup never
18
18
  # returns nil (the indexer's Hash carries `default_scope` as its default value), so the renderer trusts
19
19
  # the lookup result.
@@ -83,7 +83,7 @@ module Rigor
83
83
  )
84
84
  end
85
85
 
86
- # @return [[RBS::Definition::Method, String, Symbol], nil] the resolved method definition, the receiver
86
+ # @return , nil] the resolved method definition, the receiver
87
87
  # class name, and the dispatch kind (`:instance` or `:singleton`); nil when the receiver shape isn't
88
88
  # yet supported or the method doesn't resolve through the RBS env.
89
89
  def lookup_method(receiver_type, method_name, scope)
@@ -46,10 +46,10 @@ module Rigor
46
46
  # Applies every change in order, each against the result of the previous — the LSP contract for a
47
47
  # multi-change `didChange` notification.
48
48
  #
49
- # @param text [String, nil] the held buffer text; nil when no buffer is open for the URI.
50
- # @param changes [Array<Hash>] the `contentChanges` array.
51
- # @return [String] the new buffer text.
52
- # @raise [UnappliableChange] if any change cannot be applied.
49
+ # @param text the held buffer text; nil when no buffer is open for the URI.
50
+ # @param changes the `contentChanges` array.
51
+ # @return the new buffer text.
52
+ # @raise UnappliableChange if any change cannot be applied.
53
53
  def apply_all(text, changes)
54
54
  raise UnappliableChange, "contentChanges must be an Array, got #{changes.class}" unless changes.is_a?(Array)
55
55
 
@@ -64,7 +64,7 @@ module Rigor
64
64
  # companion to `range` and is accepted-but-ignored: it is redundant with `range`, historically ambiguous
65
65
  # about its units, and `range` is the authoritative field.
66
66
  #
67
- # @raise [UnappliableChange]
67
+ # @raise UnappliableChange
68
68
  def apply(text, change)
69
69
  raise UnappliableChange, "contentChanges entry must be a Hash, got #{change.class}" unless change.is_a?(Hash)
70
70
 
@@ -95,7 +95,7 @@ module Rigor
95
95
  raise UnappliableChange, "held text is not valid UTF-8: #{e.message}"
96
96
  end
97
97
 
98
- # @return [Array<Array(Integer, Integer)>] one `[content_start, content_end]` pair per line, in Ruby
98
+ # @return one `[content_start, content_end]` pair per line, in Ruby
99
99
  # character indices. `content_end` excludes the line terminator, so it doubles as the clamp target for
100
100
  # an over-long `character`. A trailing terminator yields a final empty span — the virtual last line an
101
101
  # editor puts the cursor on, and the anchor for an end-of-document insert.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../environment"
4
+ require_relative "../project_environment"
4
5
  require_relative "../cache/store"
5
6
  require_relative "../analysis/runner"
6
7
  require_relative "../analysis/incremental_session"
@@ -74,11 +75,7 @@ module Rigor
74
75
  dependency_source_index: project_scan.dependency_source_index,
75
76
  synthetic_method_index: project_scan.synthetic_method_index,
76
77
  project_patched_methods: project_scan.project_patched_methods,
77
- bundler_bundle_path: @configuration.bundler_bundle_path,
78
- bundler_auto_detect: @configuration.bundler_auto_detect,
79
- bundler_lockfile: @configuration.bundler_lockfile,
80
- rbs_collection_lockfile: @configuration.rbs_collection_lockfile,
81
- rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect
78
+ **ProjectEnvironment.dependency_discovery_options(@configuration)
82
79
  )
83
80
  end
84
81
 
@@ -15,10 +15,10 @@ module Rigor
15
15
  # dropped. The same claim/consume shape `DiagnosticPublisher#run_project_round` already uses for the
16
16
  # whole-project save round (#246), generalised to an arbitrary key type and an arbitrary batch action.
17
17
  class PublishBatcher
18
- # @param on_batch [#call] `(keys) -> void`, called with the deduplicated Array of keys pending at the
18
+ # @param on_batch `(keys) -> void`, called with the deduplicated Array of keys pending at the
19
19
  # start of one round. May be called more than once in a row when keys keep arriving while a round
20
20
  # runs.
21
- # @param on_error [#call, nil] `(exception) -> void`, called when `on_batch` raises. A round must
21
+ # @param on_error `(exception) -> void`, called when `on_batch` raises. A round must
22
22
  # never wedge the coalescing lock for every future `#enqueue` call — ownership is always released
23
23
  # before this fires. Whatever was mid-flight when the round raised is lost; anything enqueued by a
24
24
  # concurrent `#enqueue` call after this round's drain but before the rescue stays pending and rides
@@ -20,9 +20,9 @@ module Rigor
20
20
  @project_context = project_context
21
21
  end
22
22
 
23
- # @param positions [Array<Hash>] LSP `Position[]` — each
23
+ # @param positions LSP `Position[]` — each
24
24
  # `{ line:, character: }` 0-based.
25
- # @return [Array<Hash>, nil] one `SelectionRange` per
25
+ # @return one `SelectionRange` per
26
26
  # position, or nil when the URI / buffer isn't resolvable.
27
27
  def provide(uri, positions)
28
28
  path, entry = buffer_for(uri)
@@ -42,11 +42,11 @@ module Rigor
42
42
  :signature_help_provider, :folding_range_provider,
43
43
  :selection_range_provider, :project_context
44
44
 
45
- # @param completion_provider [Rigor::LanguageServer::CompletionProvider, nil]
45
+ # @param completion_provider
46
46
  # resolves `textDocument/completion`. Nil → `MethodNotFound`.
47
- # @param signature_help_provider [Rigor::LanguageServer::SignatureHelpProvider, nil]
47
+ # @param signature_help_provider
48
48
  # resolves `textDocument/signatureHelp`. Nil → `MethodNotFound`.
49
- # @param project_context [Rigor::LanguageServer::ProjectContext, nil] the per-session cache of
49
+ # @param project_context the per-session cache of
50
50
  # `Environment` + `Cache::Store` the providers read on every request. When present,
51
51
  # `workspace/didChangeWatchedFiles` and `workspace/didChangeConfiguration` invalidate the cache; nil
52
52
  # means "no project context": each request rebuilds env from scratch (mainly for specs and backward
@@ -69,7 +69,7 @@ module Rigor
69
69
  @project_context = project_context
70
70
  end
71
71
 
72
- # @return [Boolean] true once the client has called `exit` and
72
+ # @return true once the client has called `exit` and
73
73
  # the server has set its terminal exit code. The CLI loop
74
74
  # reads this between dispatches to know when to stop.
75
75
  def exited?
@@ -78,10 +78,10 @@ module Rigor
78
78
 
79
79
  # Routes one LSP method call.
80
80
  #
81
- # @param method [String] the LSP method name (e.g. "initialize").
82
- # @param params [Hash, nil] the LSP `params` payload (Hash for
81
+ # @param method the LSP method name (e.g. "initialize").
82
+ # @param params the LSP `params` payload (Hash for
83
83
  # request / notification methods; nil for the empty case).
84
- # @return [Hash, nil] one of:
84
+ # @return one of:
85
85
  # - the response result Hash for request methods,
86
86
  # - nil for notification methods,
87
87
  # - { error: { code:, message: } } for state / shape errors.
@@ -191,8 +191,8 @@ module Rigor
191
191
  caps
192
192
  end
193
193
 
194
- # `initialized` is a notification — no response body. Slice 7 will hook this to register
195
- # `workspace/didChangeWatchedFiles` if the client advertised the capability.
194
+ # `initialized` is a notification — no response body. Nothing is registered here yet: the server handles
195
+ # `workspace/didChangeWatchedFiles` when a client sends it, but does not register for it dynamically.
196
196
  def handle_initialized
197
197
  nil
198
198
  end
@@ -44,7 +44,7 @@ module Rigor
44
44
  @project_context = project_context
45
45
  end
46
46
 
47
- # @return [Hash, nil] LSP `SignatureHelp` payload or nil
47
+ # @return LSP `SignatureHelp` payload or nil
48
48
  # when the cursor isn't inside a resolvable method call.
49
49
  def provide(uri:, line:, character:, context: nil)
50
50
  _ = context # Trigger info accepted but not routed in v1.
@@ -14,7 +14,7 @@ module Rigor
14
14
  FILE_SCHEME = "file://"
15
15
  private_constant :FILE_SCHEME
16
16
 
17
- # @return [String, nil] absolute filesystem path for a
17
+ # @return absolute filesystem path for a
18
18
  # `file://` URI, or nil for unsupported schemes.
19
19
  def to_path(uri)
20
20
  return nil unless uri.is_a?(String) && uri.start_with?(FILE_SCHEME)
@@ -40,6 +40,13 @@ module Rigor
40
40
  #
41
41
  # Rigor::Plugin.register(MyRailsPlugin)
42
42
  class Base # rubocop:disable Metrics/ClassLength
43
+ # Issue #701 — the kind wrapper a `dynamic_return receivers:` entry may carry, spelled as RBS spells a
44
+ # class object and as the `call.undefined-method` message prints one, so an author writes the gate the
45
+ # way the diagnostic they are answering names the receiver. Declared on the class rather than inside
46
+ # `class << self` because both the load-time validator and the per-dispatch gate read it.
47
+ SINGLETON_RECEIVER_PATTERN = /\Asingleton\((.+)\)\z/
48
+ private_constant :SINGLETON_RECEIVER_PATTERN
49
+
43
50
  class << self
44
51
  # Declares the plugin's manifest. Called once at class definition time — the resulting {Manifest} is
45
52
  # cached on the class so {Rigor::Plugin::Loader} reads it without constructing the plugin.
@@ -241,8 +248,21 @@ module Rigor
241
248
  # end
242
249
  #
243
250
  # `receivers:` is a non-empty Array of class names; the engine calls the block only when the call's
244
- # receiver type's class equals or inherits from one of them (via `Environment#class_ordering`). It
245
- # MAY be omitted — then the rule is receiver-independent and fires on `methods:` alone.
251
+ # receiver type's class equals or inherits from one of them (via `Environment#class_ordering`) AND
252
+ # is of the kind the entry names. It MAY be omitted — then the rule is receiver-independent and
253
+ # fires on `methods:` alone.
254
+ #
255
+ # Issue #701 — an entry carries the receiver KIND in RBS's own spelling. `"Widget"` matches an
256
+ # INSTANCE receiver; `"singleton(Widget)"` matches the class object itself; a rule that wants both
257
+ # lists both:
258
+ #
259
+ # dynamic_return receivers: ["Widget"], methods: [:price] # Widget#price
260
+ # dynamic_return receivers: ["singleton(Widget)"], methods: [:build] # Widget.build
261
+ # dynamic_return receivers: ["Widget", "singleton(Widget)"] # either
262
+ #
263
+ # An instance rule that also answered on the class typed a call the plugin never modelled, and since
264
+ # #653 a plugin's answer suppresses `call.undefined-method` — so the class-level miss went silent on
265
+ # the say-so of a rule written for the instance.
246
266
  #
247
267
  # `methods:` is an Array of Symbol method names. When provided, the block is skipped unless
248
268
  # `call_node.name` is in the list — declarative and cheaper than an in-block guard (the engine
@@ -261,6 +281,9 @@ module Rigor
261
281
  # # found; the block does the precise per-call lookup
262
282
  # end
263
283
  #
284
+ # A callable's entries carry the kind the same way — `-> { names.map { "singleton(#{_1})" } }` for a
285
+ # discovered set of class-level receivers.
286
+ #
264
287
  # The callable runs through `instance_exec`, so it reads the plugin's own `#prepare`-built indexes.
265
288
  # It MUST be idempotent and post-`#prepare`-safe — reference a lazily-built / memoised index (as
266
289
  # activestorage's `attachment_index` and activerecord's `model_index` are), never a value captured at
@@ -376,11 +399,23 @@ module Rigor
376
399
  # ADR-52 slice 3 — a run-time callable is resolved per instance after `#prepare`; its shape is
377
400
  # checked at resolution time.
378
401
  return if receivers.respond_to?(:call)
379
- return if receivers.is_a?(Array) && !receivers.empty? && receivers.all? { |r| r.is_a?(String) && !r.empty? }
402
+ return if receivers.is_a?(Array) && !receivers.empty? &&
403
+ receivers.all? { |r| valid_dynamic_return_receiver?(r) }
380
404
 
381
405
  raise ArgumentError,
382
406
  "Plugin::Base.dynamic_return receivers: must be a non-empty Array of class-name Strings " \
383
- "or a callable, got #{receivers.inspect}"
407
+ "(\"Foo\" for an instance receiver, \"singleton(Foo)\" for the class itself) or a " \
408
+ "callable, got #{receivers.inspect}"
409
+ end
410
+
411
+ # Issue #701 — the entry grammar: a non-empty class name, optionally wrapped in RBS's own
412
+ # `singleton(...)`. A bare `singleton()` is rejected rather than read as a class literally named
413
+ # that, so a typo in the kind wrapper fails at load instead of never matching.
414
+ def valid_dynamic_return_receiver?(entry)
415
+ return false unless entry.is_a?(String) && !entry.empty?
416
+ return SINGLETON_RECEIVER_PATTERN.match?(entry) if entry.start_with?("singleton(")
417
+
418
+ true
384
419
  end
385
420
 
386
421
  def validate_dynamic_return_methods!(methods)
@@ -397,7 +432,8 @@ module Rigor
397
432
  end
398
433
 
399
434
  private :validate_dynamic_return_gate!, :validate_dynamic_return_receivers!,
400
- :validate_dynamic_return_methods!, :validate_dynamic_return_file_methods!
435
+ :validate_dynamic_return_methods!, :validate_dynamic_return_file_methods!,
436
+ :valid_dynamic_return_receiver?
401
437
 
402
438
  # ADR-37 slice 2 — declares a predicate/assertion narrowing contribution, method-gated. The narrow
403
439
  # successor to the `post_return_facts` slot of the deleted `flow_contribution_for` hook (ADR-52 WD3):
@@ -545,19 +581,15 @@ module Rigor
545
581
 
546
582
  # ADR-37 slice 2 — the return type contributed by this plugin's {.dynamic_return} rules for a call, or
547
583
  # nil. The engine calls this from `MethodDispatcher`; a rule fires only when `receiver_type`'s class
548
- # equals or inherits from one of its declared `receivers:`. First non-nil wins (declaration order).
549
- # Failures isolate to nil.
584
+ # equals or inherits from one of its declared `receivers:` AND is of that entry's kind (#701). First
585
+ # non-nil wins (declaration order). Failures isolate to nil.
550
586
  def dynamic_return_type(call_node:, scope:, receiver_type:)
551
587
  rules = self.class.dynamic_returns
552
588
  return nil if rules.empty? || receiver_type.nil?
553
589
 
554
- # `class_name` is nil for a receiver carrier with no nominal class (a refinement dimension, an
555
- # inferred shape) — fine for a receiver-less (methods-only) rule (ADR-52 WD2), which gates on the
556
- # method name alone and reads the receiver shape inside its own block.
557
- class_name = dynamic_return_receiver_class_name(receiver_type)
558
590
  environment = scope&.environment
559
591
  rules.each do |rule|
560
- next unless dynamic_return_rule_applies?(rule, call_node, class_name, environment, scope)
592
+ next unless dynamic_return_rule_applies?(rule, call_node, receiver_type, environment, scope)
561
593
 
562
594
  result = instance_exec(call_node, scope, &rule[:block])
563
595
  return result if result
@@ -803,10 +835,9 @@ module Rigor
803
835
  # survives only as the building block for the rare producer that needs `FileEntry` rows directly;
804
836
  # plugin code calls `watch:`.
805
837
  #
806
- # @param roots [Array<String>] search roots (relative to the project root, or absolute paths)
807
- # @param patterns [Array<String>] glob suffixes joined under each root via `File.join(root, pattern)`.
838
+ # @param roots search roots (relative to the project root, or absolute paths)
839
+ # @param patterns glob suffixes joined under each root via `File.join(root, pattern)`.
808
840
  # Multiple patterns union into one descriptor (`"**/*.erb", "**/*.html"` etc.).
809
- # @return [Rigor::Cache::Descriptor]
810
841
  def glob_descriptor(roots, *patterns)
811
842
  files = collect_glob_files(Array(roots), patterns)
812
843
  entries = files.map do |path|
@@ -836,7 +867,9 @@ module Rigor
836
867
 
837
868
  # ADR-37 slice 2 — the class name to match a `dynamic_return` `receivers:` entry against, from a
838
869
  # receiver `Type`. Covers the instance (`Nominal[X]`) and class (`Singleton[X]`) shapes; other carriers
839
- # decline (nil → no match).
870
+ # decline (nil → no match). Which of the two KINDS the name is then matched as is
871
+ # {#dynamic_return_receiver_matches?}'s question — the `Result` / `Maybe` carriers are instances of
872
+ # their class, like `Nominal`.
840
873
  def dynamic_return_receiver_class_name(receiver_type)
841
874
  case receiver_type
842
875
  when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
@@ -848,7 +881,7 @@ module Rigor
848
881
  # The gate for one `dynamic_return` rule. Method-name gate first — a Symbol-array probe vs the receiver
849
882
  # ancestry resolution below (ADR-52 WD1); both are pure predicates, so order only affects cost. A
850
883
  # receiver-less rule (ADR-52 WD2) skips the ancestry check entirely and fires on the method name alone.
851
- def dynamic_return_rule_applies?(rule, call_node, class_name, environment, scope)
884
+ def dynamic_return_rule_applies?(rule, call_node, receiver_type, environment, scope)
852
885
  return false if rule[:methods] && !resolved_dynamic_return_methods(rule).include?(call_node.name)
853
886
 
854
887
  if rule[:file_methods]
@@ -858,11 +891,29 @@ module Rigor
858
891
  return false unless resolved_dynamic_return_file_methods(rule, path).include?(call_node.name)
859
892
  end
860
893
 
861
- receivers = resolved_dynamic_return_receivers(rule)
862
- return true if receivers.nil?
894
+ entries = resolved_dynamic_return_receiver_entries(rule)
895
+ return true if entries.nil?
896
+
897
+ dynamic_return_receiver_matches?(entries, receiver_type, environment)
898
+ end
899
+
900
+ # Issue #701 — the receiver gate, kind included. An entry written for an instance (`"Widget"`) must not
901
+ # answer for the class object: the block was never given the class-level call to model, and since #653
902
+ # its answer would suppress that call's `call.undefined-method`, hiding a genuine miss on the plugin's
903
+ # say-so. A rule that wants the class object declares `"singleton(Widget)"`, and one that wants both
904
+ # lists both.
905
+ #
906
+ # A nil class name is a carrier with no nominal class (a refinement dimension, an inferred shape); a
907
+ # receiver-gated rule cannot speak about it and declines. A receiver-less rule (ADR-52 WD2) never
908
+ # reaches here.
909
+ def dynamic_return_receiver_matches?(entries, receiver_type, environment)
910
+ class_name = dynamic_return_receiver_class_name(receiver_type)
863
911
  return false if class_name.nil?
864
912
 
865
- receivers.any? { |c| class_matches_receiver?(class_name, c, environment) }
913
+ singleton = receiver_type.is_a?(Rigor::Type::Singleton)
914
+ entries.any? do |name, singleton_entry|
915
+ singleton_entry == singleton && class_matches_receiver?(class_name, name, environment)
916
+ end
866
917
  end
867
918
 
868
919
  # ADR-52 slice 4 — the rule's method-name set. A static Array is returned as-is (`#include?` over
@@ -910,6 +961,24 @@ module Rigor
910
961
  Array(instance_exec(&receivers)).map { |c| c.to_s.dup.freeze }.freeze
911
962
  end
912
963
 
964
+ # Issue #701 — the resolved `receivers:` entries as `[class_name, singleton?]` pairs, or nil for a
965
+ # receiver-less rule. Memoised per rule beside the callable resolution above, so the kind is parsed
966
+ # once per run rather than once per dispatch (this sits on the per-dispatch plugin path).
967
+ def resolved_dynamic_return_receiver_entries(rule)
968
+ receivers = resolved_dynamic_return_receivers(rule)
969
+ return nil if receivers.nil?
970
+
971
+ (@dynamic_return_runtime_cache ||= {})[[:receiver_entries, rule]] ||=
972
+ receivers.map { |entry| parse_dynamic_return_receiver(entry) }.freeze
973
+ end
974
+
975
+ def parse_dynamic_return_receiver(entry)
976
+ match = SINGLETON_RECEIVER_PATTERN.match(entry)
977
+ return [match[1].freeze, true].freeze if match
978
+
979
+ [entry, false].freeze
980
+ end
981
+
913
982
  # True when `class_name` equals or inherits from `constraint`, matched through
914
983
  # `Environment#class_ordering` (the mechanism `MacroBlockSelfType` / `additional_initializers` use).
915
984
  # Degrades to "no match" on any resolution failure (false-positive-safe).
@@ -976,7 +1045,7 @@ module Rigor
976
1045
  Cache::Descriptor.new(
977
1046
  files: boundary.files,
978
1047
  configs: boundary.configs,
979
- globs: watch_glob_entries(producer[:watch])
1048
+ globs: (watch_glob_entries(producer[:watch]) + boundary.globs).uniq
980
1049
  )
981
1050
  end
982
1051