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
@@ -92,23 +92,60 @@ module Rigor
92
92
  end
93
93
  end
94
94
 
95
+ # The shared empty answers of the per-method readers. Dispatch consults the three readers on every RBS
96
+ # method it resolves (~150k each on the lib self-check) and nearly every method carries no `rigor:v1:`
97
+ # annotation, so a fresh `[]` / `{}` per decline was pure allocation. Callers never mutate a reader's
98
+ # result (`read_flow_contribution` selects into fresh arrays; the override map is documented read-only).
99
+ NO_DIRECTIVES = [].freeze
100
+ private_constant :NO_DIRECTIVES
101
+ NO_OVERRIDES = {}.freeze
102
+ private_constant :NO_OVERRIDES
103
+
104
+ # `%a{rigor:v1:inferred-return}` — the declaration states the member's presence and its parameters and
105
+ # says nothing about what it returns, so the return comes from the body. Normative in
106
+ # `docs/type-specification/rbs-extended.md`.
107
+ INFERRED_RETURN_DIRECTIVE = "rigor:v1:inferred-return"
108
+
95
109
  module_function
96
110
 
111
+ # Whether `method_def` declares its return type inferred rather than contracted (issue #823).
112
+ #
113
+ # The producer is `rigor-rbs-inline`: inside a file that carries one annotation, upstream rbs-inline
114
+ # emits a `(untyped, …) -> untyped` skeleton for every unannotated `def` too, and an accepted signature
115
+ # outranks body inference — so one annotation used to retype every sibling in its file to `untyped`.
116
+ # The plugin now marks exactly the type slots it DEFAULTED, and {MethodDispatcher::RbsDispatch} declines
117
+ # a marked member so the call takes the same body-inference tier an undeclared method takes. Nothing
118
+ # else about the declaration changes: the class keeps its full method surface, `new` keeps its arity,
119
+ # and cross-file references still resolve.
120
+ #
121
+ # Reads `RBS::Definition::Method#annotations`, so it answers for an inherited member too, and a
122
+ # hand-written `.rbs` may carry the annotation for the same effect.
123
+ #
124
+ # @param method_def — an `RBS::Definition::Method`, or anything else answering `#annotations`.
125
+ def inferred_return?(method_def)
126
+ return false if method_def.nil?
127
+
128
+ annotations = method_def.annotations
129
+ return false if annotations.nil? || annotations.empty?
130
+
131
+ annotations.any? { |annotation| annotation.string.to_s.strip == INFERRED_RETURN_DIRECTIVE }
132
+ end
133
+
97
134
  # Reads RBS::Extended predicate effects off `RBS::Definition::Method#annotations`. Returns the effects in
98
135
  # source order; duplicates and unrecognised `rigor:v1:` directives are dropped. Returns an empty array (NEVER
99
136
  # `nil`) for a method with no recognised annotations so callers can iterate unconditionally.
100
137
  #
101
- # @param environment [Rigor::Environment, nil] ADR-13 slice
138
+ # @param environment ADR-13 slice
102
139
  # 3b. When provided, threads the plugin-supplied
103
140
  # `name_scope:` and the per-run reporter through the
104
141
  # annotation-parse path. `nil` (default) preserves the
105
142
  # pre-slice-3b behaviour — no plugin resolvers consulted
106
143
  # and no diagnostics accumulated.
107
144
  def read_predicate_effects(method_def, environment: nil)
108
- return [] if method_def.nil?
145
+ return NO_DIRECTIVES if method_def.nil?
109
146
 
110
147
  annotations = method_def.annotations
111
- return [] if annotations.nil? || annotations.empty?
148
+ return NO_DIRECTIVES if annotations.nil? || annotations.empty?
112
149
 
113
150
  name_scope = environment&.name_scope
114
151
  reporter = environment&.rbs_extended_reporter
@@ -127,10 +164,11 @@ module Rigor
127
164
  end
128
165
 
129
166
  # The right-hand side accepts either a Capitalised class name (with optional `~` negation, optional `::`
130
- # prefix, qualified names) OR a kebab-case refinement payload routed through
131
- # `Builtins::ImportedRefinements::Parser` (bare names, `name[T]`, `name<min, max>`). The two arms share the
132
- # same overall directive shape; the parser detects which form matched by looking at the `class_name` vs
133
- # `refinement` capture groups.
167
+ # prefix, qualified names) OR a refinement payload routed through
168
+ # `Builtins::ImportedRefinements::Parser` (bare kebab-case names, `name[T]`, the numeric range form
169
+ # `Integer[1..10]`, and the deprecated `name<min, max>`). The two arms share the same overall directive
170
+ # shape; the parser detects which form matched by looking at the `class_name` vs `refinement` capture
171
+ # groups. A class head followed by a bracketed range literal is a refinement (ADR-109), not a class name.
134
172
  PREDICATE_DIRECTIVE_PATTERN = /
135
173
  \A
136
174
  rigor:v1:(?<directive>predicate-if-(?:true|false))
@@ -141,7 +179,7 @@ module Rigor
141
179
  (?:
142
180
  (?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
143
181
  |
144
- (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?)
182
+ (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?|[A-Z][A-Za-z0-9_]*\[[^\]]*\.\.[^\]]*\])
145
183
  )
146
184
  \s*
147
185
  \z
@@ -183,10 +221,10 @@ module Rigor
183
221
  #
184
222
  # See {.read_predicate_effects} for the `environment:` keyword contract.
185
223
  def read_assert_effects(method_def, environment: nil)
186
- return [] if method_def.nil?
224
+ return NO_DIRECTIVES if method_def.nil?
187
225
 
188
226
  annotations = method_def.annotations
189
- return [] if annotations.nil? || annotations.empty?
227
+ return NO_DIRECTIVES if annotations.nil? || annotations.empty?
190
228
 
191
229
  name_scope = environment&.name_scope
192
230
  reporter = environment&.rbs_extended_reporter
@@ -214,7 +252,7 @@ module Rigor
214
252
  (?:
215
253
  (?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
216
254
  |
217
- (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?)
255
+ (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?|[A-Z][A-Za-z0-9_]*\[[^\]]*\.\.[^\]]*\])
218
256
  )
219
257
  \s*
220
258
  \z
@@ -352,7 +390,8 @@ module Rigor
352
390
 
353
391
  # The trailing payload supports the full refinement grammar in `Builtins::ImportedRefinements::Parser` — bare
354
392
  # kebab-case names plus parameterised forms like `non-empty-array[Integer]`, `non-empty-hash[Symbol,
355
- # Integer]`, and `int<5, 10>`. The directive head is consumed by the regex; the rest is forwarded to the
393
+ # Integer]`, `Integer[5..10]`, and the deprecated `int<5, 10>`. The directive head is consumed by the regex;
394
+ # the rest is forwarded to the
356
395
  # refinement parser. Anything the parser cannot resolve falls back to nil so the call site keeps the
357
396
  # RBS-declared return type.
358
397
  RETURN_DIRECTIVE_PATTERN = /
@@ -449,7 +488,7 @@ module Rigor
449
488
 
450
489
  # Returned for `rigor:v1:param: <name> <refinement>`. The parameter name is a Ruby identifier (Symbol); the
451
490
  # type is any `Rigor::Type` the refinement parser resolves (bare kebab-case name, parameterised form, or
452
- # `int<...>` range — the same grammar the `return:` directive accepts).
491
+ # `Integer[a..b]` range, or the deprecated `int<a, b>` — the same grammar the `return:` directive accepts).
453
492
  ParamOverride = Data.define(:param_name, :type)
454
493
 
455
494
  # Reads every `rigor:v1:param: <name> <refinement>` directive off `RBS::Definition::Method#annotations` and
@@ -469,10 +508,10 @@ module Rigor
469
508
  # argument-check purposes; passing a too-wide `Nominal[String]` argument is flagged as an argument-type
470
509
  # mismatch at the call site.
471
510
  def read_param_type_overrides(method_def, environment: nil)
472
- return [] if method_def.nil?
511
+ return NO_DIRECTIVES if method_def.nil?
473
512
 
474
513
  annotations = method_def.annotations
475
- return [] if annotations.nil? || annotations.empty?
514
+ return NO_DIRECTIVES if annotations.nil? || annotations.empty?
476
515
 
477
516
  name_scope = environment&.name_scope
478
517
  reporter = environment&.rbs_extended_reporter
@@ -491,9 +530,10 @@ module Rigor
491
530
  # frozen Hash<Symbol, Rigor::Type>; missing keys mean "use the RBS-declared type". Callers MUST treat the
492
531
  # hash as read-only.
493
532
  def param_type_override_map(method_def, environment: nil)
494
- read_param_type_overrides(method_def, environment: environment)
495
- .to_h { |o| [o.param_name, o.type] }
496
- .freeze
533
+ overrides = read_param_type_overrides(method_def, environment: environment)
534
+ return NO_OVERRIDES if overrides.empty?
535
+
536
+ overrides.to_h { |o| [o.param_name, o.type] }.freeze
497
537
  end
498
538
 
499
539
  # The `is` glue word is optional so authors can write either `param: id is non-empty-string` (consistent with
@@ -708,9 +748,9 @@ module Rigor
708
748
  # `RBS::Extended` conflict channel) rather than silently resolved. Returns `nil` when the list
709
749
  # carries neither spelling.
710
750
  #
711
- # @param annotations [Array<#string>] the node's annotations, in source order.
712
- # @param owner_key [String] the method key (`Class#m` / `Class.m`) or class name the bound binds.
713
- # @param source [Symbol] {Effects::Envelope::SOURCES} member to stamp when the labelled spelling
751
+ # @param annotations the node's annotations, in source order.
752
+ # @param owner_key the method key (`Class#m` / `Class.m`) or class name the bound binds.
753
+ # @param source {Effects::Envelope::SOURCES} member to stamp when the labelled spelling
714
754
  # matched; `%a{pure}` always stamps `:pure_annotation`, and a class-level read overrides both.
715
755
  def read_effect_envelope(annotations, owner_key:, source: :effect_annotation, registry: nil, reporter: nil)
716
756
  return nil if annotations.nil? || annotations.empty?
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ # {Reflection}'s second constant-resolution phase, split out for the reader rather than for the
5
+ # loader: `reflection.rb` asks what a whole spelling names under a series of prefixes, and this file
6
+ # asks what a PATH names segment by segment. Reopening the module rather than nesting under it keeps
7
+ # the walk inside the facade its callers already read, and lets it use the same private candidate
8
+ # lookups (`constant_type_at`, `enclosing_class_path`, `ancestor_constant_scopes`) the ladder uses.
9
+ module Reflection
10
+ module_function
11
+
12
+ # Issue #656 — Ruby's lookup for an as-written, unrooted constant PATH (`A::B`, `A::B::C`), which is
13
+ # a different question from "what does this whole string name under some prefix". Ruby resolves the
14
+ # FIRST segment through the ladder a bare name gets — `Module.nesting`, then the enclosing class's
15
+ # ancestors, then the top level — and then resolves each LATER segment inside the constant the
16
+ # previous segment produced, searching THAT constant's own ancestors in turn. So `A::B` written in
17
+ # `P::Guard` with `P::A < P::Base` names `P::Base::B`, while every whole-path candidate
18
+ # (`P::Guard::A::B`, `P::A::B`, `A::B`) either misses or names a real but different class.
19
+ #
20
+ # `known` decides the FINAL segment only, so the constant typer (any constant, values included) and
21
+ # `Inference::Narrowing`'s class-guard resolver (classes only) share one walk without sharing an
22
+ # acceptance test — the two cannot answer different names for the same spelling. Every earlier
23
+ # segment must name a namespace, because only a namespace can own the next one.
24
+ #
25
+ # Returns nil for a single-segment name — the callers' own ladders already own that case — and the
26
+ # moment any segment declines. Declining hands the caller back its unchanged fallback instead of a
27
+ # guess: a walk that finds the WRONG constant is worse than the wrong constant answered today
28
+ # (AGENTS.md § "Implementation Guidelines").
29
+ #
30
+ # Checked against MRI 4.0.5 rather than recollection: a later segment does NOT reach the top level
31
+ # for either a Class or a Module owner (`P::Mod::B` raises `NameError` where a top-level `B`
32
+ # exists), which is why {.constant_in_namespace} consults the owner's ancestors and stops. Rigor
33
+ # still lets its callers fall through to their own top-level rung there — that read raises at
34
+ # runtime and Rigor reports nothing about it either way, so retracting the resolution would trade a
35
+ # silent wrong answer for a silent absent one at no gain (the same reasoning as
36
+ # {.toplevel_first_constant_type}'s caller-derived rungs).
37
+ def resolve_constant_path_name(name, scope, &known)
38
+ segments = name.to_s.split("::")
39
+ return nil unless segments.size > 1
40
+
41
+ owner = path_head_owner(segments.first, scope)
42
+ last = segments.size - 1
43
+ segments.each_with_index do |segment, index|
44
+ next if index.zero?
45
+ return nil if owner.nil?
46
+
47
+ owner = constant_in_namespace(owner, segment, scope) do |candidate|
48
+ index == last ? known.call(candidate) : namespace_known?(candidate, scope)
49
+ end
50
+ end
51
+ owner
52
+ end
53
+
54
+ # Step 2.5's rung as a type: the segment-wise name, then that name looked up like any other
55
+ # candidate so the answer comes from the same source-precedence order as every other rung.
56
+ def constant_path_type(name, scope)
57
+ qualified = resolve_constant_path_name(name, scope) { |candidate| constant_type_at(candidate, scope) }
58
+ qualified && constant_type_at(qualified, scope)
59
+ end
60
+ private_class_method :constant_path_type
61
+
62
+ # The namespace a path's FIRST segment names — the bare-name ladder, restricted to answers that can
63
+ # OWN the segments that follow. A value constant is not one of them, so it declines here rather
64
+ # than producing a namespace prefix no source knows.
65
+ def path_head_owner(head, scope)
66
+ hit = first_namespace_hit(lexical_nesting_chain(scope), head, scope)
67
+ return hit if hit
68
+
69
+ prefix = enclosing_class_path(scope)
70
+ unless prefix.nil? || prefix.empty?
71
+ hit = first_namespace_hit(bounded_ancestor_scopes(prefix, scope), head, scope)
72
+ return hit if hit
73
+ end
74
+
75
+ head if namespace_known?(head, scope)
76
+ end
77
+ private_class_method :path_head_owner
78
+
79
+ # {.first_constant_hit}'s namespace twin: the first `<entry>::<name>` that can own further segments.
80
+ def first_namespace_hit(entries, name, scope)
81
+ entries.each do |entry|
82
+ candidate = "#{entry}::#{name}"
83
+ return candidate if namespace_known?(candidate, scope)
84
+ end
85
+ nil
86
+ end
87
+ private_class_method :first_namespace_hit
88
+
89
+ # `segment` looked up inside `owner` and then through `owner`'s own ancestors — Ruby's lookup for
90
+ # every segment after the first. `Object` is deliberately absent: MRI raises rather than reaching a
91
+ # top-level constant through an explicit `::` receiver.
92
+ def constant_in_namespace(owner, segment, scope)
93
+ candidate = "#{owner}::#{segment}"
94
+ return candidate if yield(candidate)
95
+
96
+ bounded_ancestor_scopes(owner, scope).each do |ancestor|
97
+ inherited = "#{ancestor}::#{segment}"
98
+ return inherited if yield(inherited)
99
+ end
100
+ nil
101
+ end
102
+ private_class_method :constant_in_namespace
103
+
104
+ # How many of {.ancestor_constant_scopes}' entries one segment may consult, under
105
+ # `Scope::ANCESTOR_WALK_LIMIT` — the budget `Scope`'s method lookup already spends on this same
106
+ # graph, reused rather than re-chosen so one pathological ancestry costs one bound. The list
107
+ # itself is the memoised one step 2 builds, so this bounds the per-segment consultation the walk
108
+ # adds and not the graph. Reported through the shared counter, so a truncated walk is visible in a
109
+ # budget trace instead of looking like a plain miss.
110
+ def bounded_ancestor_scopes(class_name, scope)
111
+ scopes = ancestor_constant_scopes(class_name, scope)
112
+ return scopes if scopes.size <= Scope::ANCESTOR_WALK_LIMIT
113
+
114
+ Inference::BudgetTrace.hit(Inference::BudgetTrace::ANCESTOR_WALK_LIMIT)
115
+ scopes.first(Scope::ANCESTOR_WALK_LIMIT)
116
+ end
117
+ private_class_method :bounded_ancestor_scopes
118
+
119
+ # Whether `name` can OWN a constant. `class_known?` covers the RBS and registry classes and modules;
120
+ # the project tables cover a namespace the project declares with no RBS for it.
121
+ def namespace_known?(name, scope)
122
+ scope.environment.class_known?(name) || known_project_namespace?(name, scope)
123
+ end
124
+ private_class_method :namespace_known?
125
+ end
126
+ end
@@ -51,9 +51,6 @@ module Rigor
51
51
 
52
52
  module_function
53
53
 
54
- # @param class_name [String, Symbol]
55
- # @param scope [Rigor::Scope]
56
- # @return [Boolean]
57
54
  def class_known?(class_name, scope: Scope.empty)
58
55
  return true if scope.discovered_classes.key?(class_name.to_s)
59
56
 
@@ -92,7 +89,7 @@ module Rigor
92
89
  loader.project_declared_class?(class_name)
93
90
  end
94
91
 
95
- # @return [Symbol] one of `:equal`, `:subclass`, `:superclass`,
92
+ # @return one of `:equal`, `:subclass`, `:superclass`,
96
93
  # `:disjoint`, `:unknown`.
97
94
  def class_ordering(lhs, rhs, scope: Scope.empty)
98
95
  scope.environment.class_ordering(lhs, rhs)
@@ -171,9 +168,16 @@ module Rigor
171
168
  # § "Implementation Guidelines"). Only project ancestors are walked; an RBS-known superclass
172
169
  # contributes no name here (see {.ancestor_constant_scopes}).
173
170
  #
171
+ # Step 2.5 (#656) — the path read SEGMENT BY SEGMENT, which is the only rung that can answer a
172
+ # path whose head and tail have different owners. See {.resolve_constant_path_name}. It sits
173
+ # BELOW the whole-path rungs so no read that resolves today moves, and ABOVE step 3 because
174
+ # beating the top-level whole-string answer is the point: `A::B` inside `P::Guard` with
175
+ # `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
176
+ #
174
177
  # Step 3 — the bare name (top level).
175
178
  first_constant_hit(lexical_nesting_chain(scope), name, scope) ||
176
179
  ancestor_constant_type(name, scope, enclosing_class_path(scope)) ||
180
+ constant_path_type(name, scope) ||
177
181
  constant_type_at(name, scope)
178
182
  end
179
183
 
@@ -215,8 +219,12 @@ module Rigor
215
219
  # dependabot-core — `docs/notes/20260905-toplevel-def-cref-movable-sites.md`). Rigor reports nothing
216
220
  # about them either way, so retracting them would trade a silent wrong answer for a silent absent one at
217
221
  # no gain. Making them FIRE is a separate question with its own false-positive budget.
222
+ #
223
+ # Issue #656's rung goes LAST here rather than second: for a top-level body Ruby's own first answer
224
+ # for `A::B` is the top level, and a whole-string hit there already means "`A` owns `B`". The
225
+ # segment-wise walk only has something to add once that misses.
218
226
  def toplevel_first_constant_type(name, scope)
219
- constant_type_at(name, scope) || caller_derived_constant_type(name, scope)
227
+ constant_type_at(name, scope) || caller_derived_constant_type(name, scope) || constant_path_type(name, scope)
220
228
  end
221
229
  private_class_method :toplevel_first_constant_type
222
230
 
@@ -242,11 +250,16 @@ module Rigor
242
250
  singleton = env.singleton_for_name(candidate)
243
251
  return singleton if singleton
244
252
 
253
+ # Issue #639 — the class-EXISTENCE edge. Without it a reference-only consumer (`Foo` with no call on
254
+ # it) recorded nothing, so deleting the declaring file left the warm `--incremental` run serving
255
+ # `singleton(Foo)` where `--no-cache` answers the honest unresolved type. Recorded ahead of the lookup
256
+ # rather than on the hit: every rung of the ladder spells the same last segment, which is the whole of
257
+ # the key, so one call covers the candidates instead of a branch around each.
258
+ scope.record_class_existence(candidate) if Analysis::DependencyRecorder.active?
245
259
  in_source_class = scope.discovered_classes[candidate]
246
260
  return in_source_class if in_source_class
247
261
 
248
- in_source_value = scope.in_source_constants[candidate]
249
- if in_source_value
262
+ if (in_source_value = scope.in_source_constants[candidate])
250
263
  scope.record_constant_dependency(candidate) if Analysis::DependencyRecorder.active?
251
264
  return in_source_value
252
265
  end
@@ -465,15 +478,14 @@ module Rigor
465
478
  end
466
479
  private_class_method :rbs_loader_for
467
480
 
468
- # @return [Boolean] true when the analyzed source contains a class / module declaration
481
+ # @return true when the analyzed source contains a class / module declaration
469
482
  # for the given name. Does NOT consult the RBS loader (use {.class_known?} for the
470
483
  # union).
471
484
  def discovered_class?(class_name, scope: Scope.empty)
472
485
  scope.discovered_classes.key?(class_name.to_s)
473
486
  end
474
487
 
475
- # @param kind [:instance, :singleton]
476
- # @return [Boolean] true when the ScopeIndexer recorded a `def` for the given method on
488
+ # @return true when the ScopeIndexer recorded a `def` for the given method on
477
489
  # the given class with the matching kind.
478
490
  #
479
491
  # ADR-46 — a MISS records a negative cross-file dependency, so a consumer whose analysis
@@ -497,3 +509,7 @@ module Rigor
497
509
  end
498
510
  end
499
511
  end
512
+
513
+ # Loaded last: the segment-wise path walk reopens the module above and reads its private candidate
514
+ # lookups, so the facade has to exist first.
515
+ require_relative "reflection/constant_path"
@@ -70,7 +70,7 @@ module Rigor
70
70
  # call is what enabled YJIT (so the `?`-suffix predicate convention does
71
71
  # not apply; hence the Naming/PredicateMethod exemption).
72
72
  #
73
- # @return [Boolean] true if this call enabled YJIT; false if it was a
73
+ # @return true if this call enabled YJIT; false if it was a
74
74
  # no-op (unavailable / opted out / already enabled).
75
75
  def enable_now # rubocop:disable Naming/PredicateMethod
76
76
  return false unless available?
@@ -90,8 +90,8 @@ module Rigor
90
90
  # up front (unavailable / opted out / already enabled), so callers never
91
91
  # pay for a thread that could only no-op.
92
92
  #
93
- # @param seconds [Numeric] the amortization deadline.
94
- # @return [Thread, nil] the deadline thread, or nil when no-op up front.
93
+ # @param seconds the amortization deadline.
94
+ # @return the deadline thread, or nil when no-op up front.
95
95
  def enable_after(seconds)
96
96
  return nil unless available?
97
97
  return nil if disabled?
@@ -143,7 +143,7 @@ module Rigor
143
143
  # Re-arming goes through {enable_after}, so the child records the same
144
144
  # absolute deadline it inherited and a grandchild fork carries it too.
145
145
  #
146
- # @return [Thread, nil] the child's deadline thread; nil when YJIT was
146
+ # @return the child's deadline thread; nil when YJIT was
147
147
  # enabled immediately, and nil when the call is a no-op up front
148
148
  # (unavailable / opted out / already enabled — a child forked after the
149
149
  # parent enabled inherits the enabled state and has nothing to do).
@@ -166,8 +166,6 @@ module Rigor
166
166
 
167
167
  # The resolved {enable_after} deadline: {DEADLINE_ENV} when it parses to a
168
168
  # non-negative float, else {DEFAULT_DEADLINE_SECONDS}.
169
- #
170
- # @return [Numeric]
171
169
  def deadline_seconds
172
170
  raw = ENV.fetch(DEADLINE_ENV, nil)
173
171
  return DEFAULT_DEADLINE_SECONDS if raw.nil? || raw.empty?
@@ -176,12 +174,12 @@ module Rigor
176
174
  value && value >= 0 ? value : DEFAULT_DEADLINE_SECONDS
177
175
  end
178
176
 
179
- # @return [Boolean] whether this Ruby exposes `RubyVM::YJIT.enable`.
177
+ # @return whether this Ruby exposes `RubyVM::YJIT.enable`.
180
178
  def available?
181
179
  defined?(RubyVM::YJIT.enable) ? true : false
182
180
  end
183
181
 
184
- # @return [Boolean] whether the opt-out env switch is set.
182
+ # @return whether the opt-out env switch is set.
185
183
  def disabled?
186
184
  ENV[DISABLE_ENV] == "1"
187
185
  end
@@ -27,10 +27,13 @@ module Rigor
27
27
  :discovered_superclasses,
28
28
  :discovered_header_nestings,
29
29
  :discovered_includes,
30
+ :discovered_extends,
30
31
  :discovered_class_sources,
31
32
  :constant_sources,
32
33
  :published_constant_names,
33
34
  :local_constant_names,
35
+ :published_constant_alias_names,
36
+ :published_constant_ivars,
34
37
  :data_member_layouts,
35
38
  :struct_member_layouts,
36
39
  :param_inferred_types,
@@ -52,6 +55,11 @@ module Rigor
52
55
  # either kind.
53
56
  METHOD_KIND_BOTH = :both
54
57
 
58
+ # Issue #728 — the key a `discovered_header_nestings` bucket stores its class-wide chain under, beside
59
+ # the per-ancestor-name entries. `nil` is used because every other key in a bucket is an ancestor name
60
+ # as written, and no ancestor name can be nil.
61
+ UNKEYED_HEADER_NESTING = nil
62
+
55
63
  # The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
56
64
  # allocation per process.
57
65
  EMPTY = new(
@@ -86,8 +94,20 @@ module Rigor
86
94
  # name — the peel is the nested spelling's answer, and it searched an `Admin::Base` that a compact
87
95
  # `class Admin::Widget < Base` written at the top level never reaches. An absent entry means "not
88
96
  # recorded" and keeps the peel, so a scope that never saw a declaration walk is unchanged.
97
+ #
98
+ # Issue #728 — the value is keyed by the ancestor NAME the site wrote, because a class's declaration
99
+ # sites need not share a cref: `class Foo < Base` at the top level and `class ::Foo; include Helper`
100
+ # inside `module Outer` resolve `Base` and `Helper` in different ones, and a single chain per class
101
+ # gave `Outer` to both. {UNKEYED_HEADER_NESTING} holds the union over every ancestor-naming site, for
102
+ # a name no site recorded under its own key.
89
103
  discovered_header_nestings: EMPTY_TABLE,
90
104
  discovered_includes: EMPTY_TABLE,
105
+ # Issue #898 — the singleton-side twin of `discovered_includes`: `{qualified class or module name =>
106
+ # [module names it `extend`s, as written]}`, built by the same `ScopeIndexer` walk that #526 already
107
+ # ran to fold an extended module's instance defs onto the extending class's singleton. #526 consumed
108
+ # the table inside the indexer and threw it away; `Narrowing` needs it to survive onto the scope,
109
+ # because `Singleton[C]`'s ancestry is exactly what `extend` writes and nothing else records it.
110
+ discovered_extends: EMPTY_TABLE,
91
111
  discovered_class_sources: EMPTY_TABLE,
92
112
  # Issue #644 — `{qualified constant name => Set[declaring file]}`, the write attribution behind the
93
113
  # cross-file value-constant table. Read only by `Scope#record_constant_dependency` during ADR-46
@@ -102,6 +122,16 @@ module Rigor
102
122
  # author cannot see. Both empty outside a runner-seeded scope, which makes the guard a no-op there.
103
123
  published_constant_names: EMPTY_NAME_SET,
104
124
  local_constant_names: EMPTY_NAME_SET,
125
+ # Issue #667 — the two per-file tables that carry a published constant's provenance across a COPY,
126
+ # which the name sets above cannot: they answer about a reference's spelling, and a copy has none.
127
+ # `published_constant_alias_names` is the last segments of the constants THIS FILE assigns straight
128
+ # from a foreign published one (`MODE2 = AppConfig::MODE`), which the local-declaration exemption
129
+ # would otherwise release. `published_constant_ivars` is `{class name => Set[ivar name]}` for an ivar
130
+ # whose class-ivar seed is such a copy (`@mode = AppConfig::MODE` in `initialize`), stamped onto the
131
+ # flow carrier at method entry. Both are seeded by `ScopeIndexer.index` and only when the project
132
+ # published something, so a project with no cross-file value constants pays no walk.
133
+ published_constant_alias_names: EMPTY_NAME_SET,
134
+ published_constant_ivars: EMPTY_TABLE,
105
135
  data_member_layouts: EMPTY_TABLE,
106
136
  struct_member_layouts: EMPTY_TABLE,
107
137
  # ADR-67 WD3 — the call-site parameter-inference table, keyed by `[class_name, method_name, kind]` (the