rigortype 0.3.8 → 0.4.0

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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -45,15 +45,13 @@ module Rigor
45
45
  # out of scope for the v0.0.7 first pass; v0.1.0's plugin API added it as a separate
46
46
  # concern.
47
47
  module Reflection
48
- # #354 — thread-local slot for the per-run ancestor-scope memo. See {.ancestor_constant_scopes}.
49
- ANCESTOR_SCOPES_KEY = :__rigor_ancestor_constant_scopes__
50
- private_constant :ANCESTOR_SCOPES_KEY
48
+ # Issue #1290 — what {.constant_type_at} answers for a candidate the project writes but no source types, on
49
+ # a ladder that checks the census. Never a type: {.resolve_constant_type} replaces it before it returns.
50
+ WRITTEN_CANDIDATE = Object.new.freeze
51
+ private_constant :WRITTEN_CANDIDATE
51
52
 
52
53
  module_function
53
54
 
54
- # @param class_name [String, Symbol]
55
- # @param scope [Rigor::Scope]
56
- # @return [Boolean]
57
55
  def class_known?(class_name, scope: Scope.empty)
58
56
  return true if scope.discovered_classes.key?(class_name.to_s)
59
57
 
@@ -92,7 +90,7 @@ module Rigor
92
90
  loader.project_declared_class?(class_name)
93
91
  end
94
92
 
95
- # @return [Symbol] one of `:equal`, `:subclass`, `:superclass`,
93
+ # @return one of `:equal`, `:subclass`, `:superclass`,
96
94
  # `:disjoint`, `:unknown`.
97
95
  def class_ordering(lhs, rhs, scope: Scope.empty)
98
96
  scope.environment.class_ordering(lhs, rhs)
@@ -143,10 +141,16 @@ module Rigor
143
141
  # answer to `::Foo`, and falling back to it was the bug (`::Rails` inside `module MyApp` typed
144
142
  # as `MyApp::Rails`).
145
143
  #
144
+ # `caller_derived: false` drops what {.toplevel_first_constant_type} takes from the caller — its
145
+ # namespaces and ancestors, for a bare name and for a path's head — for a reader whose miss is a
146
+ # meaningful answer rather than a read Ruby raises on. A constant compound write is one: an unset
147
+ # top-level `REGISTRY ||= {}` is the memoization idiom, and the caller's `Plugin::REGISTRY` is a binding
148
+ # Ruby never consults there. A path's later segments still walk their owner's ancestors.
149
+ #
146
150
  # This is the shared owner of the lexical-constant resolution: `Inference::ExpressionTyper`
147
151
  # reads it to type a constant read, and `Inference::Narrowing` reads it to recognise a
148
152
  # value-pinned `Constant[Regexp]` match-predicate operand.
149
- def resolve_constant_type(name, scope: Scope.empty, rooted: false)
153
+ def resolve_constant_type(name, scope: Scope.empty, rooted: false, caller_derived: true)
150
154
  # Issue #644 — the ADR-46 name edge, recorded ONCE PER REFERENCE and BEFORE the ladder, so it does not
151
155
  # depend on which resolver answers. A reference that resolves through RBS (`Math::PI`) or through the
152
156
  # class registry is exactly as dependent on the project's constant write set as one that resolves
@@ -156,10 +160,25 @@ module Rigor
156
160
  # go stale. Over-recording is ADR-46's safe direction.
157
161
  record_constant_reference(name) if Analysis::DependencyRecorder.active?
158
162
  return constant_type_at(name, scope) if rooted
163
+
164
+ # Issue #1290 — a candidate the project writes but no source types is where Ruby's lookup stops, so it
165
+ # REPLACES what a lower rung answers, and nothing else. The first pass hands the census's shadowing
166
+ # names to the rungs ({.constant_type_at}); a stop is `Dynamic[top]` only when the plain ladder, run
167
+ # again, answers below it. When nothing below answers, the reference stays unresolved exactly as before,
168
+ # so the nil its caller turns into the fallback, the missing-constant edge and the gem-origin label stay.
169
+ hit = lexical_constant_type(name, scope, caller_derived, scope.shadowing_constant_names(name.to_s))
170
+ return hit unless hit.equal?(WRITTEN_CANDIDATE)
171
+
172
+ Type::Combinator.untyped if lexical_constant_type(name, scope, caller_derived, nil)
173
+ end
174
+
175
+ # {.resolve_constant_type}'s unrooted ladder. `shadows` is the census's shadowing names for the
176
+ # reference, which the rungs above the last check their candidates against, or nil for the plain ladder.
177
+ def lexical_constant_type(name, scope, caller_derived, shadows)
159
178
  # Issue #716 — a body whose `Module.nesting` was RECORDED EMPTY is definitively top level, and Ruby
160
179
  # resolves its constants there no matter which namespace calls it. Steps 1 and 2 both derive from the
161
180
  # caller and are both wrong for it, so it takes its own ladder.
162
- return toplevel_first_constant_type(name, scope) if recorded_toplevel_nesting?(scope)
181
+ return toplevel_first_constant_type(name, scope, caller_derived, shadows) if recorded_toplevel_nesting?(scope)
163
182
 
164
183
  # Step 1 — `Module.nesting`, innermost first. Each entry contributes only its OWN constants.
165
184
  #
@@ -171,18 +190,26 @@ module Rigor
171
190
  # § "Implementation Guidelines"). Only project ancestors are walked; an RBS-known superclass
172
191
  # contributes no name here (see {.ancestor_constant_scopes}).
173
192
  #
174
- # Step 3 — the bare name (top level).
175
- first_constant_hit(lexical_nesting_chain(scope), name, scope) ||
176
- ancestor_constant_type(name, scope, enclosing_class_path(scope)) ||
193
+ # Step 2.5 (#656) — the path read SEGMENT BY SEGMENT, which is the only rung that can answer a
194
+ # path whose head and tail have different owners. See {.resolve_constant_path_name}. It sits
195
+ # BELOW the whole-path rungs so no read that resolves today moves, and ABOVE step 3 because
196
+ # beating the top-level whole-string answer is the point: `A::B` inside `P::Guard` with
197
+ # `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
198
+ #
199
+ # Step 3 — the bare name (top level). It takes no `shadows`: nothing sits below it for a stop to replace.
200
+ first_constant_hit(lexical_nesting_chain(scope), name, scope, shadows) ||
201
+ ancestor_constant_type(name, scope, enclosing_class_path(scope), shadows) ||
202
+ constant_path_type(name, scope, shadows: shadows) ||
177
203
  constant_type_at(name, scope)
178
204
  end
205
+ private_class_method :lexical_constant_type
179
206
 
180
207
  # The first candidate `<entry>::<name>` any source knows, walking `entries` in order, or nil. The one
181
208
  # place a qualifying prefix is joined to a name, so every rung of the ladder consults its candidates
182
209
  # identically.
183
- def first_constant_hit(entries, name, scope)
210
+ def first_constant_hit(entries, name, scope, shadows)
184
211
  entries.each do |entry|
185
- hit = constant_type_at("#{entry}::#{name}", scope)
212
+ hit = constant_type_at("#{entry}::#{name}", scope, shadows)
186
213
  return hit if hit
187
214
  end
188
215
  nil
@@ -190,10 +217,10 @@ module Rigor
190
217
  private_class_method :first_constant_hit
191
218
 
192
219
  # Step 2's rung on its own: nil when there is no enclosing class path to take ancestors of.
193
- def ancestor_constant_type(name, scope, prefix)
220
+ def ancestor_constant_type(name, scope, prefix, shadows)
194
221
  return nil if prefix.nil? || prefix.empty?
195
222
 
196
- first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope)
223
+ first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope, shadows)
197
224
  end
198
225
  private_class_method :ancestor_constant_type
199
226
 
@@ -215,20 +242,30 @@ module Rigor
215
242
  # dependabot-core — `docs/notes/20260905-toplevel-def-cref-movable-sites.md`). Rigor reports nothing
216
243
  # about them either way, so retracting them would trade a silent wrong answer for a silent absent one at
217
244
  # no gain. Making them FIRE is a separate question with its own false-positive budget.
218
- def toplevel_first_constant_type(name, scope)
219
- constant_type_at(name, scope) || caller_derived_constant_type(name, scope)
245
+ #
246
+ # Issue #656's rung goes LAST here rather than second: for a top-level body Ruby's own first answer
247
+ # for `A::B` is the top level, and a whole-string hit there already means "`A` owns `B`". The
248
+ # segment-wise walk only has something to add once that misses. Its head consults the caller's
249
+ # ancestors, so under `caller_derived: false` it resolves the head at the top level alone.
250
+ #
251
+ # Issue #1290 — the top level is this ladder's FIRST rung, so a top-level name the project writes stops it
252
+ # ({.constant_type_at}) before a caller's `Plug::TOP` can answer a read Ruby gives to the top-level `TOP`.
253
+ def toplevel_first_constant_type(name, scope, caller_derived, shadows)
254
+ constant_type_at(name, scope, shadows) ||
255
+ (caller_derived && caller_derived_constant_type(name, scope, shadows)) ||
256
+ constant_path_type(name, scope, caller_derived: caller_derived, shadows: shadows)
220
257
  end
221
258
  private_class_method :toplevel_first_constant_type
222
259
 
223
260
  # The peel and its ancestors — {.resolve_constant_type}'s steps 1 and 2 as they answer for a scope with
224
261
  # no recorded chain, factored out so the top-level ladder above can consult them BELOW the top level
225
262
  # instead of above it.
226
- def caller_derived_constant_type(name, scope)
263
+ def caller_derived_constant_type(name, scope, shadows)
227
264
  prefix = enclosing_class_path(scope)
228
265
  return nil if prefix.nil? || prefix.empty?
229
266
 
230
- first_constant_hit(peeled_nesting(prefix), name, scope) ||
231
- ancestor_constant_type(name, scope, prefix)
267
+ first_constant_hit(peeled_nesting(prefix), name, scope, shadows) ||
268
+ ancestor_constant_type(name, scope, prefix, shadows)
232
269
  end
233
270
  private_class_method :caller_derived_constant_type
234
271
 
@@ -236,22 +273,45 @@ module Rigor
236
273
  # `Singleton[C]`), source-discovered classes, in-source value constants, then RBS-side
237
274
  # constants. In-source values win over RBS constant decls because the user's source is
238
275
  # authoritative for its own constants. Returns nil when no source knows `candidate`.
239
- def constant_type_at(candidate, scope)
276
+ #
277
+ # Issue #1290 — `shadows`, passed on every rung that has a rung below it, is the last source: a candidate
278
+ # no other source types answers {WRITTEN_CANDIDATE} when `shadows` holds it, and {.resolve_constant_type}
279
+ # settles that stop. `shadows` is `Scope#shadowing_constant_names` for the reference, whose last segment
280
+ # every candidate shares. Ruby's lookup stops at a constant that exists, whatever its value, and the
281
+ # cross-file table publishes only a frozen scalar one file writes: another file's `App::LIMIT = { n: 1 }`
282
+ # was missed by every source, and the ladder moved on to a published top-level `LIMIT = 5`, typing `LIMIT`
283
+ # inside `module App` as `5` and reporting `call.undefined-method` on correct code. The value is one the
284
+ # analyzer does not carry, so the stop is gradual.
285
+ #
286
+ # Only an EXACT census name stops a candidate. The census keeps a path write as written
287
+ # ([#690](https://github.com/rigortype/rigor/issues/690)), so a suffix match would reach the as-written
288
+ # `Foo::BAR` from `M::Foo::BAR`, but would also stop `Admin::UsersController::X` on a top-level
289
+ # `UsersController::X`, losing the answer an ordinary Rails layout gets from the namespaced controller's
290
+ # superclass. A name written only through `||=` does not shadow, however many files memoize it: `X ||= v`
291
+ # resolves `X` through this same lookup, so wherever a lower rung answers, the memo finds that constant and
292
+ # never creates the name. The answer moves with the project's write set for the last segment, which the
293
+ # `constant:` edge {.resolve_constant_type} records once per reference already covers.
294
+ def constant_type_at(candidate, scope, shadows = nil)
240
295
  env = scope.environment
241
296
 
242
297
  singleton = env.singleton_for_name(candidate)
243
298
  return singleton if singleton
244
299
 
300
+ # Issue #639 — the class-EXISTENCE edge. Without it a reference-only consumer (`Foo` with no call on
301
+ # it) recorded nothing, so deleting the declaring file left the warm `--incremental` run serving
302
+ # `singleton(Foo)` where `--no-cache` answers the honest unresolved type. Recorded ahead of the lookup
303
+ # rather than on the hit: every rung of the ladder spells the same last segment, which is the whole of
304
+ # the key, so one call covers the candidates instead of a branch around each.
305
+ scope.record_class_existence(candidate) if Analysis::DependencyRecorder.active?
245
306
  in_source_class = scope.discovered_classes[candidate]
246
307
  return in_source_class if in_source_class
247
308
 
248
- in_source_value = scope.in_source_constants[candidate]
249
- if in_source_value
309
+ if (in_source_value = scope.in_source_constants[candidate])
250
310
  scope.record_constant_dependency(candidate) if Analysis::DependencyRecorder.active?
251
311
  return in_source_value
252
312
  end
253
313
 
254
- env.constant_for_name(candidate)
314
+ env.constant_for_name(candidate) || (WRITTEN_CANDIDATE if shadows&.include?(candidate))
255
315
  end
256
316
  private_class_method :constant_type_at
257
317
 
@@ -267,87 +327,6 @@ module Rigor
267
327
  end
268
328
  private_class_method :record_constant_reference
269
329
 
270
- # #354 — the project classes and modules whose own constants `class_name` inherits, in Ruby's
271
- # ancestor order: included / prepended modules before the superclass (Ruby places mixins nearer),
272
- # transitively, breadth-first. `class_name` itself is excluded — step 1 already covered it.
273
- #
274
- # Only PROJECT ancestors appear. `Scope#superclass_of` / `#includes_of` carry as-written names
275
- # from the discovery pre-pass, and an as-written name that resolves to no discovered class or
276
- # module is dropped — so a `class Foo < ActiveRecord::Base` contributes nothing and a constant
277
- # owned by an RBS-known ancestor still resolves only if the bare name reaches it at step 3. That
278
- # gap is deliberate for this slice: widening to the RBS ancestor graph is a separate question
279
- # with its own FP surface.
280
- #
281
- # Memoised per run because step 2 runs on every constant reference whose lexical candidates all
282
- # miss — which is the common case for a core-class reference (`String` inside `class Foo`). The
283
- # bucket keys on the identity of the runner-seeded run-generation token (ADR-84 WD2), falling
284
- # back to the per-file discovery table for runner-less scopes, so a re-run in one process (LSP,
285
- # ADR-62 warm loop) cannot hit stale entries.
286
- def ancestor_constant_scopes(class_name, scope)
287
- # ADR-46: `superclass_of` / `includes_of` record a cross-file class dependency per consumer
288
- # file, and the memo is run-scoped rather than file-scoped — a hit would skip the recording and
289
- # under-record the edge for every later file. Recording runs are rare (incremental only), so
290
- # they simply bypass the memo rather than complicate its key.
291
- return compute_ancestor_constant_scopes(class_name, scope) if Analysis::DependencyRecorder.active?
292
-
293
- generation = scope.run_generation || scope.discovered_superclasses
294
- slot = Thread.current[ANCESTOR_SCOPES_KEY]
295
- unless slot && slot[0].equal?(generation)
296
- slot = [generation, {}]
297
- Thread.current[ANCESTOR_SCOPES_KEY] = slot
298
- end
299
- bucket = slot[1]
300
- bucket.fetch(class_name) { bucket[class_name] = compute_ancestor_constant_scopes(class_name, scope) }
301
- end
302
- private_class_method :ancestor_constant_scopes
303
-
304
- def compute_ancestor_constant_scopes(class_name, scope)
305
- queue = [class_name]
306
- seen = { class_name => true }
307
- out = []
308
- until queue.empty?
309
- current = queue.shift
310
- # Mixins first, then the superclass — Ruby's ancestor order.
311
- scope.includes_of(current).each do |raw|
312
- resolved = resolve_ancestor_name(current, raw, scope)
313
- next if resolved.nil? || seen[resolved]
314
-
315
- seen[resolved] = true
316
- out << resolved
317
- queue << resolved
318
- end
319
- raw_super = scope.superclass_of(current)
320
- next if raw_super.nil?
321
-
322
- resolved_super = resolve_ancestor_name(current, raw_super, scope)
323
- next if resolved_super.nil? || seen[resolved_super]
324
-
325
- seen[resolved_super] = true
326
- out << resolved_super
327
- queue << resolved_super
328
- end
329
- out.freeze
330
- end
331
- private_class_method :compute_ancestor_constant_scopes
332
-
333
- # Resolves an ancestor name AS WRITTEN (`"Base"`, or a qualified `"A::B"`) against the nesting in
334
- # force where the subclass's header is written — `Scope#ancestor_name_candidates`, the single
335
- # owner of that order, which `Scope#enqueue_ancestors` reads for method lookup and the
336
- # override-visibility rule reads for its own walk. Returns nil when no candidate names a
337
- # discovered project class or module.
338
- def resolve_ancestor_name(subclass_qualified, raw, scope)
339
- scope.ancestor_name_candidates(subclass_qualified, raw)
340
- .find { |candidate| known_project_namespace?(candidate, scope) }
341
- end
342
- private_class_method :resolve_ancestor_name
343
-
344
- def known_project_namespace?(name, scope)
345
- scope.discovered_superclasses.key?(name) ||
346
- scope.discovered_includes.key?(name) ||
347
- scope.discovered_classes.key?(name)
348
- end
349
- private_class_method :known_project_namespace?
350
-
351
330
  # Pulls the enclosing qualified class name out of `scope.self_type` when one is set.
352
331
  # `Nominal[T]` and `Singleton[T]` both expose `class_name`. Returns nil at the top level.
353
332
  def enclosing_class_path(scope)
@@ -434,7 +413,7 @@ module Rigor
434
413
  nil
435
414
  end
436
415
 
437
- # Returns the full RBS singleton-side class definition.
416
+ # The singleton-side twin of {#instance_definition}, nil under the same conditions.
438
417
  def singleton_definition(class_name, scope: nil, environment: nil)
439
418
  loader = rbs_loader_for(scope, environment)
440
419
  return nil if loader.nil?
@@ -465,15 +444,14 @@ module Rigor
465
444
  end
466
445
  private_class_method :rbs_loader_for
467
446
 
468
- # @return [Boolean] true when the analyzed source contains a class / module declaration
447
+ # @return true when the analyzed source contains a class / module declaration
469
448
  # for the given name. Does NOT consult the RBS loader (use {.class_known?} for the
470
449
  # union).
471
450
  def discovered_class?(class_name, scope: Scope.empty)
472
451
  scope.discovered_classes.key?(class_name.to_s)
473
452
  end
474
453
 
475
- # @param kind [:instance, :singleton]
476
- # @return [Boolean] true when the ScopeIndexer recorded a `def` for the given method on
454
+ # @return true when the ScopeIndexer recorded a `def` for the given method on
477
455
  # the given class with the matching kind.
478
456
  #
479
457
  # ADR-46 — a MISS records a negative cross-file dependency, so a consumer whose analysis
@@ -497,3 +475,8 @@ module Rigor
497
475
  end
498
476
  end
499
477
  end
478
+
479
+ # Loaded last: the ancestor walk and the segment-wise path walk reopen the module above and read its
480
+ # private candidate lookups, so the facade has to exist first.
481
+ require_relative "reflection/constant_ancestors"
482
+ 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
@@ -15,6 +15,7 @@ module Rigor
15
15
  :class_ivars,
16
16
  :class_cvars,
17
17
  :program_globals,
18
+ :program_global_seeds,
18
19
  :discovered_classes,
19
20
  :in_source_constants,
20
21
  :discovered_methods,
@@ -24,17 +25,31 @@ module Rigor
24
25
  :discovered_def_sources,
25
26
  :discovered_singleton_def_sources,
26
27
  :discovered_method_visibilities,
28
+ :discovered_parameter_envelopes,
27
29
  :discovered_superclasses,
30
+ :discovered_deferred_ranges,
31
+ :discovered_refinements,
32
+ :discovered_global_write_census,
28
33
  :discovered_header_nestings,
29
34
  :discovered_includes,
35
+ :discovered_prepends,
36
+ :discovered_extends,
30
37
  :discovered_class_sources,
31
38
  :constant_sources,
39
+ :constant_writers,
40
+ :constant_shadowers,
32
41
  :published_constant_names,
33
42
  :local_constant_names,
43
+ :published_constant_alias_names,
44
+ :published_constant_ivars,
34
45
  :data_member_layouts,
35
46
  :struct_member_layouts,
36
47
  :param_inferred_types,
37
- :run_generation
48
+ :run_generation,
49
+ :patched_line_readers,
50
+ :clears_last_status,
51
+ :defines_case_equality,
52
+ :implicit_self_evidence
38
53
  )
39
54
 
40
55
  class DiscoveryIndex
@@ -52,6 +67,57 @@ module Rigor
52
67
  # either kind.
53
68
  METHOD_KIND_BOTH = :both
54
69
 
70
+ # Issue #728 — the key a `discovered_header_nestings` bucket stores its class-wide chain under, beside
71
+ # the per-ancestor-name entries. `nil` is used because every other key in a bucket is an ancestor name
72
+ # as written, and no ancestor name can be nil.
73
+ UNKEYED_HEADER_NESTING = nil
74
+
75
+ # Issue #986 — a bucket entry is normally ONE chain (`["Wrap"]`, innermost first). When the
76
+ # compact-header rename pass lands two declarations of one class on a single key and both wrote the
77
+ # same raw ancestor name in DIFFERENT crefs, the entry is instead the LIST of those chains —
78
+ # `[["Wrap"], []]` — because no union of them is Ruby's answer for either site. `Scope` resolves each
79
+ # alternative and declines outright when they name two different project classes: which one wins
80
+ # depends on the runtime load order of the two `include`s, which static analysis cannot know, so
81
+ # picking either is unsound. A chain's entries are Strings and an alternatives list's are Arrays, so
82
+ # the two shapes cannot be confused.
83
+ def self.ambiguous_header_nesting?(entries)
84
+ entries.first.is_a?(Array)
85
+ end
86
+
87
+ # Issue #992 — the two class-wide keys a `discovered_parameter_envelopes` bucket can carry beside its
88
+ # `[kind, method_name]` entries. Their PRESENCE is the fact; the value is always
89
+ # `Source::ParameterEnvelope::OPAQUE`, so a bucket folds under the one join every other entry does.
90
+ # Symbols, because every per-method key is an Array and neither can collide with the other.
91
+ #
92
+ # - {ENVELOPE_MODULE_MARK}: the name is declared with `module` (or `Const = Module.new do … end`) somewhere
93
+ # in the project. An instance of it is an instance of an unknown includer.
94
+ # - {ENVELOPE_DYNAMIC_MARK}: the class body rewrites its method table in a way no literal argument names
95
+ # (`class_eval`, a computed `define_method`, `send`, a non-constant mixin), or a constant-receiver form
96
+ # of those names it from outside.
97
+ # - {ENVELOPE_OBJECT_EXTENDED_MARK}: some method body passes the module to `extend`, so an object of any
98
+ # class may carry its instance methods ahead of that class's own.
99
+ # - {ENVELOPE_REFINED_MARK}: a `refine` block refines the class, which redefines its methods wherever a `using`
100
+ # is in effect. Issue #1367 splits it from {ENVELOPE_DYNAMIC_MARK}, which it used to share: an implicit
101
+ # conversion (`to_str`, `to_int`) ignores a refinement, so the `global.*` rules must tell the two apart.
102
+ # Every reader that asks whether a surface is rewritten reads both ({.rewritten_surface?}).
103
+ ENVELOPE_MODULE_MARK = :"<module>"
104
+ ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
105
+ ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
106
+ ENVELOPE_REFINED_MARK = :"<refined>"
107
+
108
+ # Whether an envelope bucket says the project rewrites the class's method table beyond its literal names: a
109
+ # dynamic rewrite, or a refinement.
110
+ def self.rewritten_surface?(bucket)
111
+ bucket.key?(ENVELOPE_DYNAMIC_MARK) || bucket.key?(ENVELOPE_REFINED_MARK)
112
+ end
113
+
114
+ # Issue #992 — the class key a WHOLE-PROJECT discovery pass adds to `discovered_parameter_envelopes`
115
+ # (`ScopeIndexer#finalize_def_index`), with an empty bucket. A single file's walk alone — `run_source`,
116
+ # the LSP `prebuilt:` scope — sees one `def` and cannot see the reopening, subclass or `class_eval` in
117
+ # another file that would make it opaque, so `call.wrong-arity` reads an envelope only when this key is
118
+ # present. Not a constant character, so no class can be named it.
119
+ ENVELOPE_PROJECT_WIDE = "<project-wide>"
120
+
55
121
  # The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
56
122
  # allocation per process.
57
123
  EMPTY = new(
@@ -59,6 +125,12 @@ module Rigor
59
125
  class_ivars: EMPTY_TABLE,
60
126
  class_cvars: EMPTY_TABLE,
61
127
  program_globals: EMPTY_TABLE,
128
+ # Issue #1362 — for each global in `program_globals` that Ruby's own signatures declare (`$VERBOSE: bool?`,
129
+ # `$stdout: IO`), its declared type joined with its writes in the file: what every method body and the top
130
+ # level start from, under the ADR-58 `:global` mark. Any other global starts from its `program_globals` entry,
131
+ # which keeps the writes alone for the consumers that compare against them (the mark's gates, and `$;` for
132
+ # `split`). Filled by `Inference::ScopeIndexer.index` from the file's own tree only.
133
+ program_global_seeds: EMPTY_TABLE,
62
134
  discovered_classes: EMPTY_TABLE,
63
135
  in_source_constants: EMPTY_TABLE,
64
136
  discovered_methods: EMPTY_TABLE,
@@ -79,21 +151,81 @@ module Rigor
79
151
  discovered_def_sources: EMPTY_TABLE,
80
152
  discovered_singleton_def_sources: EMPTY_TABLE,
81
153
  discovered_method_visibilities: EMPTY_TABLE,
154
+ # Issue #992 — `{qualified class name => {[kind, method_name] => Source::ParameterEnvelope}}`, the
155
+ # parameter envelope of every method the declaration walk records, plus {ENVELOPE_MODULE_MARK} /
156
+ # {ENVELOPE_DYNAMIC_MARK}. Written beside `discovered_methods` by one recorder, so an `alias` / `attr_*`
157
+ # / `define_method` name is always present here as `OPAQUE`, and folded across files and reopenings
158
+ # with `Source::ParameterEnvelope.merge`: one value per name, a real envelope only while every
159
+ # contribution agrees. Read by `call.wrong-arity` for a method no signature declares.
160
+ discovered_parameter_envelopes: EMPTY_TABLE,
82
161
  discovered_superclasses: EMPTY_TABLE,
162
+ # Issue #1097 — `{file path => [[start_offset, end_offset, name, kind, owner], ...]}`, every
163
+ # `def` / block / lambda body range in the file. `Scope#*_def_shadows_call?` reads it to tell
164
+ # an eager class-body call (orderable by offset against a same-name, same-owner def entry)
165
+ # from a deferred one (contained in any range — it runs at invocation time, when every
166
+ # class-body def exists). Def entries carry the method name, the `:instance` / `:singleton` /
167
+ # `:both` (module_function) kind, and the qualified owner; block / lambda rows and defs nested
168
+ # inside another deferred range carry nils and answer only the containment half. Plain data,
169
+ # so the ADR-85 seed bundle round-trips it unchanged.
170
+ discovered_deferred_ranges: EMPTY_TABLE,
171
+ # Issue #1120 — `{refined class name => {method name => [refining module names]}}`, the instance
172
+ # methods a `refine X do … end` block defines. They are not X's methods everywhere: Ruby activates them
173
+ # only lexically after a `using` of the refining module, so `call.undefined-method` reads this table
174
+ # together with the call site's file ({Analysis::CheckRules::LexicalMethodSites}) and never through
175
+ # `discovered_methods`. A refinement a `Module.new { … }` block defines has no nameable module; it is
176
+ # keyed by the name the walk gives the block's owner. Plain data, so the ADR-85 seed bundle
177
+ # round-trips it unchanged.
178
+ discovered_refinements: EMPTY_TABLE,
179
+ # Issue #1367 — the project's `Inference::GlobalWriteCensus`: the globals any `alias` names, the
180
+ # `write` / `to_str` / `to_int` / hatch names any file defines (inside a refinement or not), and the
181
+ # modules a top-level mixin names, over every file and inside method bodies too. The `global.*` write rules
182
+ # read it. The project pre-pass seeds the whole project's census and `Inference::ScopeIndexer.index` adds the
183
+ # analysed file's own. Plain data, so the ADR-85 seed bundle round-trips it unchanged.
184
+ discovered_global_write_census: EMPTY_NAME_SET,
83
185
  # Issue #682 — `{qualified class name => Module.nesting where its declaration HEADER is written}`,
84
186
  # innermost first and EXCLUDING the declaration's own entry. Read by `Scope#ancestor_name_candidates`,
85
187
  # which resolves a superclass / include name in that cref instead of peeling the subclass's qualified
86
188
  # name — the peel is the nested spelling's answer, and it searched an `Admin::Base` that a compact
87
189
  # `class Admin::Widget < Base` written at the top level never reaches. An absent entry means "not
88
190
  # recorded" and keeps the peel, so a scope that never saw a declaration walk is unchanged.
191
+ #
192
+ # Issue #728 — the value is keyed by the ancestor NAME the site wrote, because a class's declaration
193
+ # sites need not share a cref: `class Foo < Base` at the top level and `class ::Foo; include Helper`
194
+ # inside `module Outer` resolve `Base` and `Helper` in different ones, and a single chain per class
195
+ # gave `Outer` to both. {UNKEYED_HEADER_NESTING} holds the union over every ancestor-naming site, for
196
+ # a name no site recorded under its own key.
89
197
  discovered_header_nestings: EMPTY_TABLE,
90
198
  discovered_includes: EMPTY_TABLE,
199
+ # Issue #1123 — `{qualified class or module name => [module names it `prepend`s, as written]}`,
200
+ # stored in instance-ancestor SEARCH order (nearest prepend first), the `discovered_extends`
201
+ # convention. The instance-side twin `discovered_includes` holds the same names — prepends
202
+ # included — in that order too since #1173: prepends ahead of includes, each nearest-first. The
203
+ # kind still cannot be told apart there, so `Scope#user_def_through_ancestors` reads the wedge
204
+ # from here. An absent entry means "prepends nothing", which is the un-preprended behaviour
205
+ # every scope had before it existed.
206
+ discovered_prepends: EMPTY_TABLE,
207
+ # Issue #898 — the singleton-side twin of `discovered_includes`: `{qualified class or module name =>
208
+ # [module names it `extend`s, as written]}`, built by the same `ScopeIndexer` walk that #526 already
209
+ # ran to fold an extended module's instance defs onto the extending class's singleton. #526 consumed
210
+ # the table inside the indexer and threw it away; `Narrowing` needs it to survive onto the scope,
211
+ # because `Singleton[C]`'s ancestry is exactly what `extend` writes and nothing else records it.
212
+ discovered_extends: EMPTY_TABLE,
91
213
  discovered_class_sources: EMPTY_TABLE,
92
214
  # Issue #644 — `{qualified constant name => Set[declaring file]}`, the write attribution behind the
93
215
  # cross-file value-constant table. Read only by `Scope#record_constant_dependency` during ADR-46
94
216
  # dependency recording, so the runner seeds it only on a recording run; every other run leaves it
95
217
  # empty and the edge costs one nil check.
96
218
  constant_sources: EMPTY_TABLE,
219
+ # Issue #617 — the censused names that bind (any write other than a memo `||=`, or memos of the
220
+ # segment in two files), grouped by LAST SEGMENT, wildcard keys (`*::LIMIT`) under their segment.
221
+ # `Scope#bound_constant_names` reads it for a constant compound write whose plain read resolves to
222
+ # nothing: such a name is bound, just not to a value the analyzer carries. Seeded on every run, because
223
+ # the question is a typing one rather than a recording one.
224
+ constant_writers: EMPTY_TABLE,
225
+ # Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by LAST SEGMENT.
226
+ # `Scope#shadowing_constant_names` reads it for the lexical ladder: a candidate no source types but this
227
+ # table holds is where Ruby's lookup stops. Seeded on every run beside `constant_writers`.
228
+ constant_shadowers: EMPTY_TABLE,
97
229
  # Issue #644 — the two halves of `Scope#published_constant?`, the question
98
230
  # {Analysis::CheckRules::PublishedConstantGuard} asks. `published_constant_names` is the LAST
99
231
  # SEGMENTS of the project-wide published table (run-wide, seeded by the runner);
@@ -102,6 +234,16 @@ module Rigor
102
234
  # author cannot see. Both empty outside a runner-seeded scope, which makes the guard a no-op there.
103
235
  published_constant_names: EMPTY_NAME_SET,
104
236
  local_constant_names: EMPTY_NAME_SET,
237
+ # Issue #667 — the two per-file tables that carry a published constant's provenance across a COPY,
238
+ # which the name sets above cannot: they answer about a reference's spelling, and a copy has none.
239
+ # `published_constant_alias_names` is the last segments of the constants THIS FILE assigns straight
240
+ # from a foreign published one (`MODE2 = AppConfig::MODE`), which the local-declaration exemption
241
+ # would otherwise release. `published_constant_ivars` is `{class name => Set[ivar name]}` for an ivar
242
+ # whose class-ivar seed is such a copy (`@mode = AppConfig::MODE` in `initialize`), stamped onto the
243
+ # flow carrier at method entry. Both are seeded by `ScopeIndexer.index` and only when the project
244
+ # published something, so a project with no cross-file value constants pays no walk.
245
+ published_constant_alias_names: EMPTY_NAME_SET,
246
+ published_constant_ivars: EMPTY_TABLE,
105
247
  data_member_layouts: EMPTY_TABLE,
106
248
  struct_member_layouts: EMPTY_TABLE,
107
249
  # ADR-67 WD3 — the call-site parameter-inference table, keyed by `[class_name, method_name, kind]` (the
@@ -117,7 +259,28 @@ module Rigor
117
259
  # cross a run boundary (LSP / ADR-62 warm-loop re-runs land in a fresh bucket). Nil on scopes that
118
260
  # never see the runner seed (single-file probes, `run_source` before the seed applies): the memo
119
261
  # falls back to today's per-file `discovered_def_nodes` identity for those.
120
- run_generation: nil
262
+ run_generation: nil,
263
+ # Issue #1359 — the `gets` / `readline` names this file patches in through the `define_method` family
264
+ # (`$stdin.define_singleton_method(:gets) { … }`, `IO.define_method(:gets)`, `alias_method :gets, :x`, or a
265
+ # computed name, which may be either), which `Inference::LastLine.reads_line?` declines on, as it does on a
266
+ # name `BlockCallTiming.project_defines_anywhere?` finds. Filled by `Inference::ScopeIndexer.index` from the
267
+ # file's own tree only.
268
+ patched_line_readers: EMPTY_NAME_SET,
269
+ # Issue #1360 — true when this file holds a call that may set `$?` to nil (`Inference::LastStatus.clears?`: a
270
+ # `wait`-family call with a flags argument, or `waitall`), which may run between any subprocess and a read of
271
+ # `$?`, so `Inference::LastStatus.after` binds it nowhere in the file. Filled by `Inference::ScopeIndexer.index`
272
+ # from the file's own tree only.
273
+ clears_last_status: false,
274
+ # Issue #1360 — true when this file holds a `define_method` or `define_singleton_method` call whose literal
275
+ # name is `===` (`Inference::ErrorInfo.defines_case_equality?`), which may give a class the singleton `===`
276
+ # `rescue` matches with; the class it lands on is not recorded, so no rescued class binds `$!` in the file.
277
+ # Filled by `Inference::ScopeIndexer.index` from the file's own tree only.
278
+ defines_case_equality: false,
279
+ # Issue #1415 — the file's evidence about the `self` its implicit-self `gets` / `readline` readers run with
280
+ # (`Inference::LastLine::SelfEvidence`, read on the first ask), which `Inference::LastLine.reads_line?` reads
281
+ # them by. Filled by `Inference::ScopeIndexer.index` from the file's own tree only; nil, where no file was
282
+ # indexed, declines every such reader.
283
+ implicit_self_evidence: nil
121
284
  )
122
285
  end
123
286
  end