rigortype 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +96 -15
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +69 -13
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +100 -29
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +316 -40
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +166 -30
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +70 -0
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -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
data/lib/rigor/scope.rb CHANGED
@@ -21,7 +21,7 @@ module Rigor
21
21
  attr_reader :environment, :locals, :fact_store, :self_type,
22
22
  :ivars, :cvars, :globals,
23
23
  :indexed_narrowings, :method_chain_narrowings,
24
- :declaration_sourced,
24
+ :declaration_sourced, :published_constant_sourced,
25
25
  :source_path, :discovery, :struct_fold_safe_locals,
26
26
  :opaque_block_self, :lexical_nesting,
27
27
  :dynamic_origins, :local_origins, :ivar_origins,
@@ -49,12 +49,21 @@ module Rigor
49
49
  def discovered_method_visibilities = @discovery.discovered_method_visibilities
50
50
  def discovered_superclasses = @discovery.discovered_superclasses
51
51
  def discovered_includes = @discovery.discovered_includes
52
+ def discovered_extends = @discovery.discovered_extends
52
53
  def discovered_class_sources = @discovery.discovered_class_sources
53
54
  # Issue #644 — `{qualified constant name => Set[declaring file]}`; seeded only on an ADR-46 recording run.
54
55
  def constant_sources = @discovery.constant_sources
55
56
  def published_constant_names = @discovery.published_constant_names
56
57
  def local_constant_names = @discovery.local_constant_names
57
58
 
59
+ # Issue #667 — the instance-variable names of `class_name` whose class-ivar seed comes from a foreign
60
+ # published constant (`@mode = AppConfig::MODE` in `initialize`, read in a sibling method).
61
+ # `StatementEvaluator#seed_instance_ivars` stamps the flow mark for these at method-body entry, the way
62
+ # ADR-58's seed stamps its own; a method-local write or narrowing then drops it through `with_ivar`.
63
+ def published_constant_ivars_for(class_name)
64
+ @discovery.published_constant_ivars[class_name] || EMPTY_PUBLISHED_CONSTANT_IVARS
65
+ end
66
+
58
67
  # Issue #644 — true when the constant reference `name` (as written: `MODE` or `AppConfig::MODE`) is
59
68
  # published by the cross-file value-constant table and is NOT assigned by the file being analysed: a
60
69
  # value this file's author cannot see the declaration of. {Analysis::CheckRules::PublishedConstantGuard}
@@ -76,14 +85,27 @@ module Rigor
76
85
  # over already-qualified names rather than a second lexical ladder: the engine's ladder
77
86
  # (`Reflection.resolve_constant_type`) answers with a TYPE and not with the candidate that won, and
78
87
  # reimplementing the walk here is the very thing that made the receiver resolution wrong once already.
88
+ #
89
+ # Issue #667 — a third answer sits beside those two. `MODE2 = AppConfig::MODE` is assigned by this file,
90
+ # so the exemption above releases it, yet its value is still one the author never saw: the alias only
91
+ # renames the foreign declaration. `published_constant_alias_names` holds the last segments of exactly
92
+ # those aliases, and matching on the last segment over-answers in the WITHHOLDING direction, which is the
93
+ # safe one.
79
94
  def published_constant?(name)
80
95
  names = @discovery.published_constant_names
96
+ return true if published_constant_alias?(name)
81
97
  return false if names.empty?
82
98
  return false unless names.include?(name.split("::").last)
83
99
 
84
100
  !locally_declared_constant?(name)
85
101
  end
86
102
 
103
+ def published_constant_alias?(name)
104
+ aliases = @discovery.published_constant_alias_names
105
+ !aliases.empty? && aliases.include?(name.split("::").last)
106
+ end
107
+ private :published_constant_alias?
108
+
87
109
  def locally_declared_constant?(reference_name)
88
110
  local = @discovery.local_constant_names
89
111
  return false if local.empty?
@@ -153,9 +175,21 @@ module Rigor
153
175
  # threaded by reference through transitions; reset per method body (a fresh entry scope drops it), so the
154
176
  # name keys never collide across bodies.
155
177
  EMPTY_ORIGINS = {}.freeze
178
+ # Issue #667 — the set of variable references currently bound to a value COPIED out of a constant the
179
+ # project published and this file does not declare. Members are frozen `[kind, name]` pairs, the same
180
+ # spelling {EMPTY_DECLARATION_SOURCED} uses, but a DELIBERATELY SEPARATE carrier: this mark joins by
181
+ # UNION where ADR-58's kinds intersect, and it answers a different question (what a value's *constancy*
182
+ # rests on, not what a binding's *optionality* rests on) for a different consumer. Folding it into the
183
+ # ADR-58 set would be the third establishing transition its *Non-transitivity* passage reserves as a
184
+ # decision, on a Set that already carries two opposite join policies.
185
+ EMPTY_PUBLISHED_CONSTANT_SOURCED = Set.new.freeze
186
+ # Issue #667 — the empty answer of {#published_constant_ivars_for}, so a class with no such ivar (every
187
+ # class in a project that publishes nothing) allocates none.
188
+ EMPTY_PUBLISHED_CONSTANT_IVARS = Set.new.freeze
156
189
  private_constant :EMPTY_VAR_BINDINGS, :EMPTY_INDEXED_NARROWINGS,
157
190
  :EMPTY_CHAIN_NARROWINGS, :EMPTY_DECLARATION_SOURCED,
158
- :EMPTY_FOLD_SAFE, :EMPTY_ORIGINS
191
+ :EMPTY_FOLD_SAFE, :EMPTY_ORIGINS, :EMPTY_PUBLISHED_CONSTANT_SOURCED,
192
+ :EMPTY_PUBLISHED_CONSTANT_IVARS
159
193
 
160
194
  class << self
161
195
  def empty(environment: Environment.default, source_path: nil)
@@ -207,6 +241,7 @@ module Rigor
207
241
  indexed_narrowings: EMPTY_INDEXED_NARROWINGS,
208
242
  method_chain_narrowings: EMPTY_CHAIN_NARROWINGS,
209
243
  declaration_sourced: EMPTY_DECLARATION_SOURCED,
244
+ published_constant_sourced: EMPTY_PUBLISHED_CONSTANT_SOURCED,
210
245
  source_path: nil,
211
246
  struct_fold_safe_locals: EMPTY_FOLD_SAFE,
212
247
  opaque_block_self: false,
@@ -231,6 +266,7 @@ module Rigor
231
266
  @indexed_narrowings = indexed_narrowings
232
267
  @method_chain_narrowings = method_chain_narrowings
233
268
  @declaration_sourced = declaration_sourced
269
+ @published_constant_sourced = published_constant_sourced
234
270
  @source_path = source_path
235
271
  @struct_fold_safe_locals = struct_fold_safe_locals
236
272
  @opaque_block_self = opaque_block_self
@@ -314,6 +350,10 @@ module Rigor
314
350
  indexed_narrowings: new_indexed_narrowings,
315
351
  method_chain_narrowings: new_chain_narrowings,
316
352
  declaration_sourced: drop_declaration_sourced_for(:local, name),
353
+ # Issue #667 — rebinding is flow-live for the published-constant mark too: the new value need
354
+ # not be a copy of anything. `with_published_constant_mark` re-stamps afterward when the write's
355
+ # rvalue is one.
356
+ published_constant_sourced: drop_published_constant_sourced_for(:local, name),
317
357
  local_origins: drop_origin(@local_origins, name),
318
358
  optimistic_locals: drop_origin(@optimistic_locals, name))
319
359
  end
@@ -419,6 +459,7 @@ module Rigor
419
459
  indexed_narrowings: new_indexed_narrowings,
420
460
  method_chain_narrowings: new_chain_narrowings,
421
461
  declaration_sourced: drop_declaration_sourced_for(:ivar, name),
462
+ published_constant_sourced: drop_published_constant_sourced_for(:ivar, name),
422
463
  ivar_origins: drop_origin(@ivar_origins, name),
423
464
  optimistic_ivars: drop_origin(@optimistic_ivars, name))
424
465
  end
@@ -445,6 +486,22 @@ module Rigor
445
486
  rebuild(declaration_sourced: add_declaration_sourced(:local, name))
446
487
  end
447
488
 
489
+ # Issue #667 — record that `name` is currently bound to a value copied out of a foreign published
490
+ # constant. Always applied AFTER the `with_local` / `with_ivar` that binds the value (both drop the mark
491
+ # unconditionally), exactly as {#with_local_declaration_mark} is.
492
+ def with_published_constant_mark(kind, name)
493
+ rebuild(published_constant_sourced: add_published_constant_sourced(kind, name))
494
+ end
495
+
496
+ # Issue #667 — true when `(kind, name)`'s current binding is a copy of a constant the project published
497
+ # and this file does not declare. Asked through
498
+ # {Analysis::CheckRules::PublishedConstantGuard}, never directly from a rule.
499
+ def published_constant_sourced?(kind, name)
500
+ return false if @published_constant_sourced.empty?
501
+
502
+ @published_constant_sourced.include?([kind.to_sym, name.to_sym])
503
+ end
504
+
448
505
  # ADR-58 WD1 — true when `(kind, name)`'s binding optionality is purely declaration-sourced (no flow-live
449
506
  # write/narrowing has touched it).
450
507
  def declaration_sourced?(kind, name)
@@ -744,6 +801,34 @@ module Rigor
744
801
  @discovery.discovered_includes[class_name.to_s] || []
745
802
  end
746
803
 
804
+ # Issue #898 — the module names `extend`ed onto `class_name`'s SINGLETON, as written, gathered up the
805
+ # as-written superclass chain because a singleton class inherits its superclass's singleton class
806
+ # (`class Base; extend Comparable; end; class Widget < Base; end` leaves `Widget.is_a?(Comparable)`
807
+ # true). Empty for a class the project never extends, and for every scope that saw no seeding pass.
808
+ #
809
+ # This is EVIDENCE FOR a singleton ancestor, never against one: the walk sees only what a constant
810
+ # argument to a receiverless `extend` (or, since #915, a receiverless `include` / `prepend` inside a
811
+ # `class << self` body) written inside a declaration body spells, so a runtime `Widget.extend(m)` and an
812
+ # `extend` in a file outside the analysed set are both absent from it, as is an `extend` declared only
813
+ # in RBS — that one reaches the same consumer through `Environment#singleton_extended_modules` instead.
814
+ # Its one consumer ({Inference::Narrowing.narrow_class}) therefore reads it
815
+ # only to WITHHOLD a `Bot`, and never to assert that a guard matches.
816
+ def singleton_extends_of(class_name)
817
+ table = @discovery.discovered_extends
818
+ return [] if table.empty?
819
+
820
+ names = []
821
+ current = class_name.to_s
822
+ seen = {}
823
+ while current && !seen[current] && seen.size <= ANCESTOR_WALK_LIMIT
824
+ seen[current] = true
825
+ record_class_dependency(current) if Analysis::DependencyRecorder.active?
826
+ names.concat(table[current] || [])
827
+ current = @discovery.discovered_superclasses[current]
828
+ end
829
+ names.uniq
830
+ end
831
+
747
832
  # ADR-24 slice 2 — the user-side ancestor walk: resolves `method_name` against `class_name`'s own `def`s,
748
833
  # then breadth-first through its included / prepended modules (mixins first, as Ruby orders them) and its
749
834
  # superclass chain. Returns `[def_node, owner]`, or `[nil, nil]` when nothing in the project defines it.
@@ -810,9 +895,56 @@ module Rigor
810
895
  [nil, nil]
811
896
  end
812
897
 
898
+ # Issue #633 — the ancestors a project class reaches that the project itself does NOT declare: the
899
+ # `< StandardError` / `< Array` superclasses and the `include Comparable` mixins the two walks above
900
+ # deliberately drop, gathered breadth-first over the project ancestry so an inherited edge counts too.
901
+ #
902
+ # Each entry is the CANDIDATE LIST for one as-written name ({#ancestor_name_candidates}: the nesting
903
+ # spellings first, the bare name last), not a resolved class — resolving it means asking the RBS
904
+ # environment, which this frozen-index walk does not read. The caller takes the first candidate its own
905
+ # oracle knows and ignores the rest, the same "most-qualified first" order the project-side resolution
906
+ # uses.
907
+ def external_ancestor_name_candidates(class_name, name_memo: {})
908
+ groups = []
909
+ queue = [class_name.to_s]
910
+ seen = {}
911
+ visited = 0
912
+ until queue.empty?
913
+ current = queue.shift
914
+ next if current.nil? || seen[current]
915
+
916
+ seen[current] = true
917
+ visited += 1
918
+ break if visited > ANCESTOR_WALK_LIMIT
919
+
920
+ collect_external_ancestors(current, queue, groups, name_memo)
921
+ end
922
+ groups
923
+ end
924
+
925
+ # One node of {#external_ancestor_name_candidates}: the project-declared ancestors continue the walk,
926
+ # everything else is reported as a candidate list.
927
+ def collect_external_ancestors(current, queue, groups, name_memo)
928
+ raw_names = includes_of(current).dup
929
+ raw_super = superclass_of(current)
930
+ raw_names << raw_super if raw_super
931
+ raw_names.each do |raw|
932
+ resolved = resolve_ancestor_class_name(current, raw, name_memo)
933
+ if resolved
934
+ queue.push(resolved)
935
+ else
936
+ groups << ancestor_name_candidates(current, raw)
937
+ end
938
+ end
939
+ end
940
+ private :collect_external_ancestors
941
+
813
942
  # The BFS node cap; a hierarchy past it gives up rather than walking unboundedly (ADR-41 WD4).
814
943
  ANCESTOR_WALK_LIMIT = 100
815
944
 
945
+ EMPTY_HEADER_NESTING = [].freeze
946
+ private_constant :EMPTY_HEADER_NESTING
947
+
816
948
  def ancestor_walk_gave_up
817
949
  Inference::BudgetTrace.hit(Inference::BudgetTrace::ANCESTOR_WALK_LIMIT)
818
950
  [nil, nil]
@@ -929,10 +1061,25 @@ module Rigor
929
1061
  return [raw.delete_prefix("::")] if raw.start_with?("::")
930
1062
 
931
1063
  recorded = @discovery.discovered_header_nestings[subclass_qualified.to_s]
932
- entries = recorded || peeled_header_nesting(subclass_qualified)
1064
+ entries = recorded ? recorded_header_nesting(recorded, raw) : peeled_header_nesting(subclass_qualified)
933
1065
  entries.map { |entry| "#{entry}::#{raw_ancestor}" } << raw_ancestor.to_s
934
1066
  end
935
1067
 
1068
+ # Issue #728 — the chain of the declaration site that WROTE `raw`, which is the cref Ruby resolves that
1069
+ # one name in. A class's sites need not agree: `class Foo < Base` at the top level and a rooted
1070
+ # `class ::Foo; include Helper; end` inside `module Outer` are two sites of `Foo`, and the union of
1071
+ # their chains put `Outer::Base` — a class Ruby never looks at — ahead of `::Base` for the superclass
1072
+ # the top-level site wrote. That is a WRONG CLASS, not a wider candidate list.
1073
+ #
1074
+ # The unkeyed entry is the union, and answers a name no site recorded under its own key: an `include`
1075
+ # attributed to this class from OUTSIDE its declaration (`Recv.class_eval { include M }`, which
1076
+ # `Inference::ScopeIndexer#walk_class_includes` names but the header walk cannot see), or a dynamic
1077
+ # mixin argument. It is the pre-#728 answer, so those are unchanged rather than degraded.
1078
+ def recorded_header_nesting(bucket, raw)
1079
+ bucket[raw] || bucket[DiscoveryIndex::UNKEYED_HEADER_NESTING] || EMPTY_HEADER_NESTING
1080
+ end
1081
+ private :recorded_header_nesting
1082
+
936
1083
  # The pre-#682 reading of a qualified class name: every proper prefix of it, innermost first, as if the
937
1084
  # class had been declared one `module` keyword per segment.
938
1085
  def peeled_header_nesting(subclass_qualified)
@@ -974,6 +1121,21 @@ module Rigor
974
1121
  end
975
1122
  private :record_class_dependency
976
1123
 
1124
+ # Issue #639 — the EXISTENCE edge for a bare class reference, `class:<last segment>`, the hit-side twin of
1125
+ # the miss-side key `Inference::ExpressionTyper` already records. ADR-46 slice 1c left the existence hit
1126
+ # edgeless on the reasoning that a file merely referencing `Post` depends on `Post`'s methods, not on its
1127
+ # bare existence; that stops holding the moment the DECLARING FILE IS DELETED, because existence is then
1128
+ # exactly what changed and a reference-only consumer had recorded nothing to be re-checked by.
1129
+ #
1130
+ # NAME-keyed rather than a positive edge to the declaring file, which is the distinction the ADR's cost
1131
+ # argument turns on: the declared-class set of a file does not move when a method body in it is edited,
1132
+ # so a bare referent is re-checked when the class appears or disappears and at no other time. A file edge
1133
+ # would have re-checked every bare referent of every class the file declares on any edit to it.
1134
+ def record_class_existence(class_name)
1135
+ segment = class_name.to_s.delete_prefix("::").split("::").last
1136
+ Analysis::DependencyRecorder.read_name(:class, segment) if segment
1137
+ end
1138
+
977
1139
  # Issue #644 — the positive ADR-46 edge for a cross-file VALUE constant. `Reflection.constant_type_at`
978
1140
  # calls this the moment a candidate resolves through `in_source_constants`, so the reader depends on the
979
1141
  # file that wrote the constant: editing the literal, or deleting the file, re-checks the reader. Recorded
@@ -1125,7 +1287,8 @@ module Rigor
1125
1287
  @globals == other.globals &&
1126
1288
  @indexed_narrowings == other.indexed_narrowings &&
1127
1289
  @method_chain_narrowings == other.method_chain_narrowings &&
1128
- @declaration_sourced == other.declaration_sourced
1290
+ @declaration_sourced == other.declaration_sourced &&
1291
+ @published_constant_sourced == other.published_constant_sourced
1129
1292
  end
1130
1293
  alias eql? ==
1131
1294
 
@@ -1142,6 +1305,7 @@ module Rigor
1142
1305
  indexed_narrowings: @indexed_narrowings,
1143
1306
  method_chain_narrowings: @method_chain_narrowings,
1144
1307
  declaration_sourced: @declaration_sourced,
1308
+ published_constant_sourced: @published_constant_sourced,
1145
1309
  source_path: @source_path,
1146
1310
  struct_fold_safe_locals: @struct_fold_safe_locals,
1147
1311
  opaque_block_self: @opaque_block_self,
@@ -1163,6 +1327,7 @@ module Rigor
1163
1327
  indexed_narrowings: indexed_narrowings,
1164
1328
  method_chain_narrowings: method_chain_narrowings,
1165
1329
  declaration_sourced: declaration_sourced,
1330
+ published_constant_sourced: published_constant_sourced,
1166
1331
  source_path: source_path,
1167
1332
  struct_fold_safe_locals: struct_fold_safe_locals,
1168
1333
  opaque_block_self: opaque_block_self,
@@ -1208,6 +1373,12 @@ module Rigor
1208
1373
  # path made the binding flow-live (a method-local nil write / failed-guard narrowing), the merge is
1209
1374
  # flow-live and `possible-nil-receiver` fires as before.
1210
1375
  declaration_sourced: join_declaration_sourced(other),
1376
+ # Issue #667 — UNION, the opposite of the line above, which is the first reason this mark does not
1377
+ # ride the ADR-58 Set. The mark only ever WITHHOLDS a firing, so keeping it when either arm bound the
1378
+ # name from a published constant is the false-positive-safe merge: `if c then m = MODE else m = MODE2
1379
+ # end; m == :x` must not warn because one arm's copy is invisible to the reader's author. ADR-67
1380
+ # WD6b's `:inferred_param` taint takes the same direction for the same reason.
1381
+ published_constant_sourced: join_published_constant_sourced(other),
1211
1382
  source_path: source_path,
1212
1383
  # Issue #589 — the fold-safe set MUST survive a merge. It was simply absent from this constructor
1213
1384
  # call, so it fell back to the empty default and every `if` / `while` in a method silently revoked
@@ -1339,6 +1510,34 @@ module Rigor
1339
1510
  end
1340
1511
 
1341
1512
  # ADR-58 WD1 — set/clear the declaration-sourced provenance mark.
1513
+ # Issue #667 — the #667 carrier's add/drop pair. Deliberately not reusing
1514
+ # {#add_declaration_sourced} / {#drop_declaration_sourced_for}: the two sets are joined by opposite
1515
+ # policies, so sharing the storage would make every future edit to either one a decision about both.
1516
+ def add_published_constant_sourced(kind, name)
1517
+ ref = [kind.to_sym, name.to_sym].freeze
1518
+ return @published_constant_sourced if @published_constant_sourced.include?(ref)
1519
+
1520
+ (@published_constant_sourced.dup << ref).freeze
1521
+ end
1522
+
1523
+ def drop_published_constant_sourced_for(kind, name)
1524
+ return @published_constant_sourced if @published_constant_sourced.empty?
1525
+
1526
+ ref = [kind.to_sym, name.to_sym]
1527
+ return @published_constant_sourced unless @published_constant_sourced.include?(ref)
1528
+
1529
+ (@published_constant_sourced - [ref]).freeze
1530
+ end
1531
+
1532
+ def join_published_constant_sourced(other)
1533
+ mine = @published_constant_sourced
1534
+ theirs = other.published_constant_sourced
1535
+ return mine if mine.equal?(theirs) || theirs.empty?
1536
+ return theirs if mine.empty?
1537
+
1538
+ (mine | theirs).freeze
1539
+ end
1540
+
1342
1541
  def add_declaration_sourced(kind, name)
1343
1542
  ref = [kind.to_sym, name.to_sym]
1344
1543
  return @declaration_sourced if @declaration_sourced.include?(ref)