rigortype 0.3.9 → 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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -35,10 +35,17 @@ module Rigor
35
35
  # silent wrong answer for a silent absent one at no gain (the same reasoning as
36
36
  # {.toplevel_first_constant_type}'s caller-derived rungs).
37
37
  def resolve_constant_path_name(name, scope, &known)
38
+ walk_constant_path(name, scope, known, caller_derived: true)
39
+ end
40
+
41
+ # {.resolve_constant_path_name}'s walk. `caller_derived: false` is {.resolve_constant_type}'s flag: the
42
+ # head skips the enclosing class's ancestors — the one step a recorded top-level body takes from its
43
+ # caller — while every later segment still walks its owner's ancestors, as Ruby does.
44
+ def walk_constant_path(name, scope, known, caller_derived:)
38
45
  segments = name.to_s.split("::")
39
46
  return nil unless segments.size > 1
40
47
 
41
- owner = path_head_owner(segments.first, scope)
48
+ owner = path_head_owner(segments.first, scope, caller_derived)
42
49
  last = segments.size - 1
43
50
  segments.each_with_index do |segment, index|
44
51
  next if index.zero?
@@ -50,23 +57,29 @@ module Rigor
50
57
  end
51
58
  owner
52
59
  end
60
+ private_class_method :walk_constant_path
53
61
 
54
62
  # Step 2.5's rung as a type: the segment-wise name, then that name looked up like any other
55
63
  # candidate so the answer comes from the same source-precedence order as every other rung.
56
- def constant_path_type(name, scope)
57
- qualified = resolve_constant_path_name(name, scope) { |candidate| constant_type_at(candidate, scope) }
58
- qualified && constant_type_at(qualified, scope)
64
+ #
65
+ # Issue #1290 — the final segment's candidates are looked up with the reference's `shadows` names
66
+ # ({.constant_type_at}), so an owner's own `B` another file writes stops the walk before the owner's
67
+ # superclass answers with its published `B`.
68
+ def constant_path_type(name, scope, shadows:, caller_derived: true)
69
+ known = ->(candidate) { constant_type_at(candidate, scope, shadows) }
70
+ qualified = walk_constant_path(name, scope, known, caller_derived: caller_derived)
71
+ qualified && constant_type_at(qualified, scope, shadows)
59
72
  end
60
73
  private_class_method :constant_path_type
61
74
 
62
75
  # The namespace a path's FIRST segment names — the bare-name ladder, restricted to answers that can
63
76
  # OWN the segments that follow. A value constant is not one of them, so it declines here rather
64
77
  # than producing a namespace prefix no source knows.
65
- def path_head_owner(head, scope)
78
+ def path_head_owner(head, scope, caller_derived)
66
79
  hit = first_namespace_hit(lexical_nesting_chain(scope), head, scope)
67
80
  return hit if hit
68
81
 
69
- prefix = enclosing_class_path(scope)
82
+ prefix = caller_derived ? enclosing_class_path(scope) : nil
70
83
  unless prefix.nil? || prefix.empty?
71
84
  hit = first_namespace_hit(bounded_ancestor_scopes(prefix, scope), head, scope)
72
85
  return hit if hit
@@ -45,9 +45,10 @@ 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
 
@@ -140,10 +141,16 @@ module Rigor
140
141
  # answer to `::Foo`, and falling back to it was the bug (`::Rails` inside `module MyApp` typed
141
142
  # as `MyApp::Rails`).
142
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
+ #
143
150
  # This is the shared owner of the lexical-constant resolution: `Inference::ExpressionTyper`
144
151
  # reads it to type a constant read, and `Inference::Narrowing` reads it to recognise a
145
152
  # value-pinned `Constant[Regexp]` match-predicate operand.
146
- def resolve_constant_type(name, scope: Scope.empty, rooted: false)
153
+ def resolve_constant_type(name, scope: Scope.empty, rooted: false, caller_derived: true)
147
154
  # Issue #644 — the ADR-46 name edge, recorded ONCE PER REFERENCE and BEFORE the ladder, so it does not
148
155
  # depend on which resolver answers. A reference that resolves through RBS (`Math::PI`) or through the
149
156
  # class registry is exactly as dependent on the project's constant write set as one that resolves
@@ -153,10 +160,25 @@ module Rigor
153
160
  # go stale. Over-recording is ADR-46's safe direction.
154
161
  record_constant_reference(name) if Analysis::DependencyRecorder.active?
155
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)
156
178
  # Issue #716 — a body whose `Module.nesting` was RECORDED EMPTY is definitively top level, and Ruby
157
179
  # resolves its constants there no matter which namespace calls it. Steps 1 and 2 both derive from the
158
180
  # caller and are both wrong for it, so it takes its own ladder.
159
- 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)
160
182
 
161
183
  # Step 1 — `Module.nesting`, innermost first. Each entry contributes only its OWN constants.
162
184
  #
@@ -174,19 +196,20 @@ module Rigor
174
196
  # beating the top-level whole-string answer is the point: `A::B` inside `P::Guard` with
175
197
  # `P::A < P::Base` is `P::Base::B`, and step 3's `A::B` is a real but different class.
176
198
  #
177
- # Step 3 — the bare name (top level).
178
- first_constant_hit(lexical_nesting_chain(scope), name, scope) ||
179
- ancestor_constant_type(name, scope, enclosing_class_path(scope)) ||
180
- constant_path_type(name, scope) ||
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) ||
181
203
  constant_type_at(name, scope)
182
204
  end
205
+ private_class_method :lexical_constant_type
183
206
 
184
207
  # The first candidate `<entry>::<name>` any source knows, walking `entries` in order, or nil. The one
185
208
  # place a qualifying prefix is joined to a name, so every rung of the ladder consults its candidates
186
209
  # identically.
187
- def first_constant_hit(entries, name, scope)
210
+ def first_constant_hit(entries, name, scope, shadows)
188
211
  entries.each do |entry|
189
- hit = constant_type_at("#{entry}::#{name}", scope)
212
+ hit = constant_type_at("#{entry}::#{name}", scope, shadows)
190
213
  return hit if hit
191
214
  end
192
215
  nil
@@ -194,10 +217,10 @@ module Rigor
194
217
  private_class_method :first_constant_hit
195
218
 
196
219
  # Step 2's rung on its own: nil when there is no enclosing class path to take ancestors of.
197
- def ancestor_constant_type(name, scope, prefix)
220
+ def ancestor_constant_type(name, scope, prefix, shadows)
198
221
  return nil if prefix.nil? || prefix.empty?
199
222
 
200
- first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope)
223
+ first_constant_hit(ancestor_constant_scopes(prefix, scope), name, scope, shadows)
201
224
  end
202
225
  private_class_method :ancestor_constant_type
203
226
 
@@ -222,21 +245,27 @@ module Rigor
222
245
  #
223
246
  # Issue #656's rung goes LAST here rather than second: for a top-level body Ruby's own first answer
224
247
  # for `A::B` is the top level, and a whole-string hit there already means "`A` owns `B`". The
225
- # segment-wise walk only has something to add once that misses.
226
- def toplevel_first_constant_type(name, scope)
227
- constant_type_at(name, scope) || caller_derived_constant_type(name, scope) || constant_path_type(name, scope)
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)
228
257
  end
229
258
  private_class_method :toplevel_first_constant_type
230
259
 
231
260
  # The peel and its ancestors — {.resolve_constant_type}'s steps 1 and 2 as they answer for a scope with
232
261
  # no recorded chain, factored out so the top-level ladder above can consult them BELOW the top level
233
262
  # instead of above it.
234
- def caller_derived_constant_type(name, scope)
263
+ def caller_derived_constant_type(name, scope, shadows)
235
264
  prefix = enclosing_class_path(scope)
236
265
  return nil if prefix.nil? || prefix.empty?
237
266
 
238
- first_constant_hit(peeled_nesting(prefix), name, scope) ||
239
- ancestor_constant_type(name, scope, prefix)
267
+ first_constant_hit(peeled_nesting(prefix), name, scope, shadows) ||
268
+ ancestor_constant_type(name, scope, prefix, shadows)
240
269
  end
241
270
  private_class_method :caller_derived_constant_type
242
271
 
@@ -244,7 +273,25 @@ module Rigor
244
273
  # `Singleton[C]`), source-discovered classes, in-source value constants, then RBS-side
245
274
  # constants. In-source values win over RBS constant decls because the user's source is
246
275
  # authoritative for its own constants. Returns nil when no source knows `candidate`.
247
- 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)
248
295
  env = scope.environment
249
296
 
250
297
  singleton = env.singleton_for_name(candidate)
@@ -264,7 +311,7 @@ module Rigor
264
311
  return in_source_value
265
312
  end
266
313
 
267
- env.constant_for_name(candidate)
314
+ env.constant_for_name(candidate) || (WRITTEN_CANDIDATE if shadows&.include?(candidate))
268
315
  end
269
316
  private_class_method :constant_type_at
270
317
 
@@ -280,87 +327,6 @@ module Rigor
280
327
  end
281
328
  private_class_method :record_constant_reference
282
329
 
283
- # #354 — the project classes and modules whose own constants `class_name` inherits, in Ruby's
284
- # ancestor order: included / prepended modules before the superclass (Ruby places mixins nearer),
285
- # transitively, breadth-first. `class_name` itself is excluded — step 1 already covered it.
286
- #
287
- # Only PROJECT ancestors appear. `Scope#superclass_of` / `#includes_of` carry as-written names
288
- # from the discovery pre-pass, and an as-written name that resolves to no discovered class or
289
- # module is dropped — so a `class Foo < ActiveRecord::Base` contributes nothing and a constant
290
- # owned by an RBS-known ancestor still resolves only if the bare name reaches it at step 3. That
291
- # gap is deliberate for this slice: widening to the RBS ancestor graph is a separate question
292
- # with its own FP surface.
293
- #
294
- # Memoised per run because step 2 runs on every constant reference whose lexical candidates all
295
- # miss — which is the common case for a core-class reference (`String` inside `class Foo`). The
296
- # bucket keys on the identity of the runner-seeded run-generation token (ADR-84 WD2), falling
297
- # back to the per-file discovery table for runner-less scopes, so a re-run in one process (LSP,
298
- # ADR-62 warm loop) cannot hit stale entries.
299
- def ancestor_constant_scopes(class_name, scope)
300
- # ADR-46: `superclass_of` / `includes_of` record a cross-file class dependency per consumer
301
- # file, and the memo is run-scoped rather than file-scoped — a hit would skip the recording and
302
- # under-record the edge for every later file. Recording runs are rare (incremental only), so
303
- # they simply bypass the memo rather than complicate its key.
304
- return compute_ancestor_constant_scopes(class_name, scope) if Analysis::DependencyRecorder.active?
305
-
306
- generation = scope.run_generation || scope.discovered_superclasses
307
- slot = Thread.current[ANCESTOR_SCOPES_KEY]
308
- unless slot && slot[0].equal?(generation)
309
- slot = [generation, {}]
310
- Thread.current[ANCESTOR_SCOPES_KEY] = slot
311
- end
312
- bucket = slot[1]
313
- bucket.fetch(class_name) { bucket[class_name] = compute_ancestor_constant_scopes(class_name, scope) }
314
- end
315
- private_class_method :ancestor_constant_scopes
316
-
317
- def compute_ancestor_constant_scopes(class_name, scope)
318
- queue = [class_name]
319
- seen = { class_name => true }
320
- out = []
321
- until queue.empty?
322
- current = queue.shift
323
- # Mixins first, then the superclass — Ruby's ancestor order.
324
- scope.includes_of(current).each do |raw|
325
- resolved = resolve_ancestor_name(current, raw, scope)
326
- next if resolved.nil? || seen[resolved]
327
-
328
- seen[resolved] = true
329
- out << resolved
330
- queue << resolved
331
- end
332
- raw_super = scope.superclass_of(current)
333
- next if raw_super.nil?
334
-
335
- resolved_super = resolve_ancestor_name(current, raw_super, scope)
336
- next if resolved_super.nil? || seen[resolved_super]
337
-
338
- seen[resolved_super] = true
339
- out << resolved_super
340
- queue << resolved_super
341
- end
342
- out.freeze
343
- end
344
- private_class_method :compute_ancestor_constant_scopes
345
-
346
- # Resolves an ancestor name AS WRITTEN (`"Base"`, or a qualified `"A::B"`) against the nesting in
347
- # force where the subclass's header is written — `Scope#ancestor_name_candidates`, the single
348
- # owner of that order, which `Scope#enqueue_ancestors` reads for method lookup and the
349
- # override-visibility rule reads for its own walk. Returns nil when no candidate names a
350
- # discovered project class or module.
351
- def resolve_ancestor_name(subclass_qualified, raw, scope)
352
- scope.ancestor_name_candidates(subclass_qualified, raw)
353
- .find { |candidate| known_project_namespace?(candidate, scope) }
354
- end
355
- private_class_method :resolve_ancestor_name
356
-
357
- def known_project_namespace?(name, scope)
358
- scope.discovered_superclasses.key?(name) ||
359
- scope.discovered_includes.key?(name) ||
360
- scope.discovered_classes.key?(name)
361
- end
362
- private_class_method :known_project_namespace?
363
-
364
330
  # Pulls the enclosing qualified class name out of `scope.self_type` when one is set.
365
331
  # `Nominal[T]` and `Singleton[T]` both expose `class_name`. Returns nil at the top level.
366
332
  def enclosing_class_path(scope)
@@ -447,7 +413,7 @@ module Rigor
447
413
  nil
448
414
  end
449
415
 
450
- # Returns the full RBS singleton-side class definition.
416
+ # The singleton-side twin of {#instance_definition}, nil under the same conditions.
451
417
  def singleton_definition(class_name, scope: nil, environment: nil)
452
418
  loader = rbs_loader_for(scope, environment)
453
419
  return nil if loader.nil?
@@ -510,6 +476,7 @@ module Rigor
510
476
  end
511
477
  end
512
478
 
513
- # Loaded last: the segment-wise path walk reopens the module above and reads its private candidate
514
- # lookups, so the facade has to exist first.
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"
515
482
  require_relative "reflection/constant_path"
@@ -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,12 +25,19 @@ 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,
30
36
  :discovered_extends,
31
37
  :discovered_class_sources,
32
38
  :constant_sources,
39
+ :constant_writers,
40
+ :constant_shadowers,
33
41
  :published_constant_names,
34
42
  :local_constant_names,
35
43
  :published_constant_alias_names,
@@ -37,7 +45,11 @@ module Rigor
37
45
  :data_member_layouts,
38
46
  :struct_member_layouts,
39
47
  :param_inferred_types,
40
- :run_generation
48
+ :run_generation,
49
+ :patched_line_readers,
50
+ :clears_last_status,
51
+ :defines_case_equality,
52
+ :implicit_self_evidence
41
53
  )
42
54
 
43
55
  class DiscoveryIndex
@@ -60,6 +72,52 @@ module Rigor
60
72
  # as written, and no ancestor name can be nil.
61
73
  UNKEYED_HEADER_NESTING = nil
62
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
+
63
121
  # The shared all-empty index `Scope.empty` (and every scope that never sees a seeding pass) points at — one
64
122
  # allocation per process.
65
123
  EMPTY = new(
@@ -67,6 +125,12 @@ module Rigor
67
125
  class_ivars: EMPTY_TABLE,
68
126
  class_cvars: EMPTY_TABLE,
69
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,
70
134
  discovered_classes: EMPTY_TABLE,
71
135
  in_source_constants: EMPTY_TABLE,
72
136
  discovered_methods: EMPTY_TABLE,
@@ -87,7 +151,37 @@ module Rigor
87
151
  discovered_def_sources: EMPTY_TABLE,
88
152
  discovered_singleton_def_sources: EMPTY_TABLE,
89
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,
90
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,
91
185
  # Issue #682 — `{qualified class name => Module.nesting where its declaration HEADER is written}`,
92
186
  # innermost first and EXCLUDING the declaration's own entry. Read by `Scope#ancestor_name_candidates`,
93
187
  # which resolves a superclass / include name in that cref instead of peeling the subclass's qualified
@@ -102,6 +196,14 @@ module Rigor
102
196
  # a name no site recorded under its own key.
103
197
  discovered_header_nestings: EMPTY_TABLE,
104
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,
105
207
  # Issue #898 — the singleton-side twin of `discovered_includes`: `{qualified class or module name =>
106
208
  # [module names it `extend`s, as written]}`, built by the same `ScopeIndexer` walk that #526 already
107
209
  # ran to fold an extended module's instance defs onto the extending class's singleton. #526 consumed
@@ -114,6 +216,16 @@ module Rigor
114
216
  # dependency recording, so the runner seeds it only on a recording run; every other run leaves it
115
217
  # empty and the edge costs one nil check.
116
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,
117
229
  # Issue #644 — the two halves of `Scope#published_constant?`, the question
118
230
  # {Analysis::CheckRules::PublishedConstantGuard} asks. `published_constant_names` is the LAST
119
231
  # SEGMENTS of the project-wide published table (run-wide, seeded by the runner);
@@ -147,7 +259,28 @@ module Rigor
147
259
  # cross a run boundary (LSP / ADR-62 warm-loop re-runs land in a fresh bucket). Nil on scopes that
148
260
  # never see the runner seed (single-file probes, `run_source` before the seed applies): the memo
149
261
  # falls back to today's per-file `discovered_def_nodes` identity for those.
150
- 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
151
284
  )
152
285
  end
153
286
  end