rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -42,8 +42,8 @@ module Rigor
42
42
  # `IoBoundary`. The catalog is frozen after the first build and consulted by the `dynamic_return` rule
43
43
  # at every gated call site. RBI files share the catalog with project-source sigs — both produce
44
44
  # `MethodSignature` entries keyed by `(class_name, method_name, kind)`. When a key collides across
45
- # files, the last-walked sig wins (ordering is platform-dependent: `Dir.glob` returns directory
46
- # entries in filesystem order). Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
45
+ # files, the last-walked sig wins (`Dir.glob` results are sorted, so the walk order is stable).
46
+ # Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
47
47
  # `sorbet/rbi/gems/` — lands in a later slice once the catalog gains per-source provenance.
48
48
  #
49
49
  # The plugin emits `plugin.sorbet.parse-error` warnings for malformed sig blocks (no block / empty
@@ -79,12 +79,10 @@ module Rigor
79
79
  "rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
80
80
  "enforce_sigil" => { kind: :boolean, default: true }
81
81
  },
82
- # #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig` itself reaches the
83
- # class object through `extend T::Sig` (or the `< T::ImmutableStruct` / `< T::Enum`
84
- # family, which carry the edge themselves), which is
85
- # why `rbs_complete_extends:` — the extend-edge allow-list — carries `T::Sig` /
86
- # `T::Helpers` / `T::Generic`, and `rbs_complete_ancestors:` carries the Struct/Enum family
87
- # for the subclass bridge.
82
+ # #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig` reaches the class object
83
+ # through `extend T::Sig` (or the `T::Struct` / `T::Enum` family, which carry the edge themselves):
84
+ # `rbs_complete_extends:` is that extend-edge allow-list, and `rbs_complete_ancestors:` carries the
85
+ # Struct/Enum family for the subclass bridge.
88
86
  signature_paths: ["sig"],
89
87
  rbs_complete_extends: %w[T::Sig T::Helpers T::Generic],
90
88
  rbs_complete_ancestors: %w[T::Struct T::InexactStruct T::ImmutableStruct T::Enum],
@@ -122,8 +120,7 @@ module Rigor
122
120
  @configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
123
121
  @rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
124
122
  # Schema default `true` — only files marked `# typed: true` / `:strict` / `:strong` contribute
125
- # their sigs. Set to `false` to record every file's sigs regardless of sigil (current behaviour
126
- # pre-this-config).
123
+ # their sigs. Set to `false` to record every file's sigs regardless of sigil.
127
124
  @enforce_sigil = config.fetch("enforce_sigil")
128
125
  # Per-call-site assertion gating: `@sigil_by_path` (built during catalog harvest) is consulted so
129
126
  # `T.let` / `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only fire in files Sorbet itself
@@ -135,20 +132,20 @@ module Rigor
135
132
  @parse_errors_by_path = {}
136
133
  @catalog_built = false
137
134
  # ADR-11 slice 6 — Prism nodes for `T.absurd` calls we observed in the `dynamic_return` rule to be
138
- # *reachable* (i.e., their discriminant didn't narrow to `bot`). `diagnostics_for_file` walks the
139
- # per-file AST and surfaces these as `plugin.sorbet.absurd-reachable` warnings. Hash is keyed on
140
- # the Prism node's `object_id` because the runner only parses each file once per run, so identity
141
- # is stable across the two plugin hooks.
135
+ # *reachable* (i.e., their discriminant didn't narrow to `bot`). The node rule below surfaces these
136
+ # as `plugin.sorbet.absurd-reachable` warnings. The Hash is keyed by node identity
137
+ # (`compare_by_identity`) because the runner only parses each file once per run, so identity is
138
+ # stable across the two plugin hooks.
142
139
  @reachable_absurd_nodes = {}.compare_by_identity
143
140
  # ADR-11 light follow-up — `T.reveal_type` calls observed in the `dynamic_return` rule, paired
144
141
  # with the display string for the inferred type at the call site. Mirrors the absurd-node
145
- # compare-by-identity hash; `diagnostics_for_file` surfaces each entry as a
142
+ # compare-by-identity hash; the node rule below surfaces each entry as a
146
143
  # `plugin.sorbet.reveal-type` `:info` diagnostic.
147
144
  @reveal_type_calls = {}.compare_by_identity
148
145
  # T.bind / T.assert_type! priority slice 1 — `T.assert_type!` calls observed in the
149
146
  # `dynamic_return` rule whose static subtype check FAILED, paired with the inferred + asserted
150
- # type display strings. Same compare-by-identity discipline. `diagnostics_for_file` walks the
151
- # file AST for `T.assert_type!` calls and surfaces matching entries as
147
+ # type display strings. Same compare-by-identity discipline. The node rule below surfaces matching
148
+ # entries as
152
149
  # `plugin.sorbet.assert-type-mismatch` `:error` diagnostics.
153
150
  @assert_type_mismatches = {}.compare_by_identity
154
151
  end
@@ -245,7 +242,7 @@ module Rigor
245
242
  # analysis treats code after it as unreachable; the legacy contribution's `exceptional: :raises`
246
243
  # slot was never consumed on the dispatcher path — only `return_type` survives the merge). When
247
244
  # the discriminant *isn't* narrowed to `bot` at this scope, also records the call node so
248
- # `diagnostics_for_file` can surface a `plugin.sorbet.absurd-reachable` warning.
245
+ # the node rule can surface a `plugin.sorbet.absurd-reachable` warning.
249
246
  if AbsurdRecognizer.absurd_call?(call_node)
250
247
  @reachable_absurd_nodes[call_node] = true unless AbsurdRecognizer.exhaustive?(call_node, scope)
251
248
  return Rigor::Type::Combinator.bot
@@ -255,7 +252,7 @@ module Rigor
255
252
  # cheaper to recognise (no catalog walk required) and they win over any cataloged signature: the
256
253
  # user explicitly asserted the type at the call site. The light follow-up extends the recogniser
257
254
  # to `T.must_because` (alias of `T.must`) and `T.reveal_type` (passes the type through; the
258
- # human-facing diagnostic is recorded here for `diagnostics_for_file` to emit).
255
+ # human-facing diagnostic is recorded here for the node rule to emit).
259
256
  #
260
257
  # Per-call-site sigil gating: with `enforce_sigil: true` (default), assertions only fire in files
261
258
  # Sorbet itself would enforce. Files at `# typed: false` (or sigil-less, which Sorbet treats as
@@ -347,19 +344,96 @@ module Rigor
347
344
  when Rigor::Type::Singleton
348
345
  chain_lookup(self_type.class_name, method_name, anchor_kind: :singleton, mixin_kind: :extend)
349
346
  when Rigor::Type::Nominal
350
- chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
347
+ chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
348
+ struct_prop_lookup(self_type.class_name, method_name, scope)
351
349
  end
352
350
  rescue StandardError
353
351
  nil
354
352
  end
355
353
 
354
+ # Issue #2541 — after the sig catalog (the class and its mixins) declines an instance-side call, a
355
+ # `prop` / `const` accessor the class or a superclass declares under a `T::Struct`-family root.
356
+ # The accessor's type is translated here rather than at harvest: a relative constant in it resolves the
357
+ # way Ruby's lexical lookup would from the declaring class body — under each enclosing namespace,
358
+ # innermost first, then at the top level — against the classes the walk declared and the RBS
359
+ # environment. A constant that resolves nowhere reads `Dynamic[top]`, never a guessed class.
360
+ def struct_prop_lookup(class_name, method_name, scope)
361
+ signature = @catalog.lookup_struct_prop(class_name: class_name, method_name: method_name)
362
+ return signature if signature.nil?
363
+
364
+ source, nesting = @catalog.struct_prop_type(signature)
365
+ return signature if source.nil?
366
+
367
+ signature.with(return_type: struct_prop_type(source, nesting, signature.class_name, scope))
368
+ end
369
+
370
+ def struct_prop_type(source, nesting, class_name, scope)
371
+ @struct_prop_types ||= {}
372
+ @struct_prop_types[[source, nesting, class_name, scope.nil?]] ||= begin
373
+ node = Prism.parse(source).value.statements&.body&.first
374
+ resolve = ->(name) { resolve_struct_constant(name, nesting, class_name, scope) }
375
+ TypeTranslator.translate(node, resolve)
376
+ end
377
+ end
378
+
379
+ # Ruby's lookup for a relative constant in the class body: the lexical nesting, innermost first; then
380
+ # the class's ancestors; then the top level. The nesting step answers a class it finds, and declines
381
+ # (nil, so the constant reads `Dynamic[top]`) when it meets a constant that is not a declared class.
382
+ # Ancestry is not modelled precisely: a class or constant of that name under any ancestor declines.
383
+ # The top level answers only when the struct's ancestry is closed — no class on its walked superclass
384
+ # chain `include`s or `prepend`s anything, whose constants could shadow it — and no other walked
385
+ # `*::<name>` exists.
386
+ def resolve_struct_constant(name, nesting, class_name, scope)
387
+ environment = scope&.environment
388
+ return name.delete_prefix("::") if name.start_with?("::")
389
+
390
+ nesting.size.downto(1) do |depth|
391
+ candidate = "#{nesting.first(depth).join('::')}::#{name}"
392
+ return candidate if struct_constant_class?(candidate, environment)
393
+ return nil if struct_constant_bound?(candidate, scope)
394
+ end
395
+ @catalog.constant_ancestry(class_name).each do |ancestor|
396
+ candidate = "#{ancestor}::#{name}"
397
+ return nil if struct_constant_class?(candidate, environment) || struct_constant_bound?(candidate, scope)
398
+ end
399
+ return nil if struct_constant_elsewhere?(name)
400
+ return nil if @catalog.mixes_in_ancestry?(class_name)
401
+
402
+ struct_constant_class?(name, environment) ? name : nil
403
+ end
404
+
405
+ def struct_constant_class?(name, environment)
406
+ @catalog.namespace?(name) || !environment&.nominal_for_name(name).nil?
407
+ end
408
+
409
+ # A walked constant write, or any constant the engine types at the rooted path `::<name>` — an RBS
410
+ # constant declaration included. The probe goes through the plugin-facing `Scope#type_of` on a parsed
411
+ # constant-path node; a name nothing declares types as `Dynamic[top]` and is not bound.
412
+ def struct_constant_bound?(name, scope)
413
+ return true if @catalog.constant?(name)
414
+ return false if scope.nil?
415
+
416
+ node = Prism.parse("::#{name}").value.statements&.body&.first
417
+ type = scope.type_of(node)
418
+ !(type.nil? || type.is_a?(Rigor::Type::Dynamic))
419
+ rescue StandardError
420
+ true
421
+ end
422
+
423
+ def struct_constant_elsewhere?(name)
424
+ @struct_constant_names ||= @catalog.declared_constant_names
425
+ suffix = "::#{name}"
426
+ @struct_constant_names.any? { |declared| declared.end_with?(suffix) }
427
+ end
428
+
356
429
  def instance_chain_lookup(receiver_node, method_name, scope)
357
430
  return nil if scope.nil?
358
431
 
359
432
  receiver_type = scope.type_of(receiver_node)
360
433
  return nil unless receiver_type.is_a?(Rigor::Type::Nominal)
361
434
 
362
- chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
435
+ chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
436
+ struct_prop_lookup(receiver_type.class_name, method_name, scope)
363
437
  rescue StandardError
364
438
  # `scope.type_of` can raise on unrecognised synthetic nodes; degrade to "no contribution" rather
365
439
  # than bubbling the failure into the dispatcher.
@@ -502,8 +576,7 @@ module Rigor
502
576
  # ADR-88 WD2 — deterministic fold order matters now that the catalog VALUE is digested into the
503
577
  # incremental fact-surface fingerprint: a duplicate `(class, method, kind)` sig's last-wins winner
504
578
  # must not vary by machine / run. `Dir.glob` sorts its results by default on Ruby 3.0+, so the fold
505
- # order is already stable (the slice-1 comment's "filesystem order" caveat predates that default);
506
- # this walk relies on it rather than re-sorting.
579
+ # order is already stable; this walk relies on it rather than re-sorting.
507
580
  Dir.glob("**/*#{ext}", base: absolute).each do |relative|
508
581
  harvest_file(canonicalize(File.join(absolute, relative)), catalog, sigil_by_path, parse_errors_by_path)
509
582
  end
@@ -552,7 +625,7 @@ module Rigor
552
625
  catalog
553
626
  end
554
627
 
555
- errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
628
+ errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path, struct_catalog: catalog)
556
629
  # ADR-88 WD2 — store Marshal-clean `{kind:, line:, column:}` tuples, not the `ParseError` (it holds a
557
630
  # live Prism node the producer bundle could not serialise). `diagnostics_for_file` rebuilds the
558
631
  # diagnostic from the tuple, at the same 1-based line / `start_column + 1` position `from_node` gave.
@@ -11,9 +11,8 @@ module Rigor
11
11
  #
12
12
  # 1. **Collect pass.** Walk the file once to gather every state name declared inside a
13
13
  # `state_machine do ... end` block (`state :draft`, `state :submitted`, ...).
14
- # 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)` and `event :sym`
15
- # reference against the collected state set. Levenshtein distance ≤ 3 drives the did-you-mean
16
- # suggestions.
14
+ # 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)` reference against
15
+ # the collected state set. `DidYouMean::SpellChecker` drives the did-you-mean suggestions.
17
16
  #
18
17
  # Useful for `aasm` / `statesman` / hand-rolled DSLs and any framework where declarations and uses
19
18
  # live in the same file. The same skeleton lifts to GraphQL types, ActiveModel validations, route
@@ -2,15 +2,15 @@
2
2
 
3
3
  require "prism"
4
4
 
5
- require "rigor/inference/declaration_walk"
5
+ require "rigor/reflection"
6
6
 
7
7
  require_relative "serializer_index"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
11
  class Typelizer < Rigor::Plugin::Base
12
- # A {Rigor::Inference::DeclarationWalk} collector over one file: the classes it declares, and whether each
13
- # one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
12
+ # A collector over one file, built on {Rigor::Reflection.class_declarations_in}: the classes it declares, and
13
+ # whether each one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
14
14
  #
15
15
  # A `module` that includes the DSL is deliberately not recorded: typelizer registers the module's own
16
16
  # name and `target_serializers` then calls `.descendants` on it, which a plain Module lacks. A class that
@@ -22,8 +22,6 @@ module Rigor
22
22
  # `included do include Typelizer::DSL end` and a `def` that includes it credit nothing to the lexical
23
23
  # class. A `class << self` body is a different node and is not entered either.
24
24
  class SerializerCollector
25
- include Rigor::Inference::DeclarationWalk::Collector
26
-
27
25
  DSL_MODULE = "Typelizer::DSL"
28
26
  DSL_CALLS = %i[include extend].freeze
29
27
 
@@ -33,17 +31,10 @@ module Rigor
33
31
  @dsl = {}
34
32
  end
35
33
 
36
- def on_declaration(node, context, body_context)
37
- return Rigor::Inference::DeclarationWalk::DESCEND unless node.is_a?(Prism::ClassNode)
38
-
39
- name = body_context.prefix.join("::")
40
- unless name.empty?
41
- superclass = superclass_name(node.superclass)
42
- @entries[name] = superclass if superclass || !@entries.key?(name)
43
- @nestings[name] = context.nesting || []
44
- @dsl[name] = true if dsl_in?(node.body) && !body_context.singleton_cref
45
- end
46
- Rigor::Inference::DeclarationWalk::DESCEND
34
+ # Walks `root` (a parsed file's `Prism::Node`) and records what it finds; answers the collector itself.
35
+ def collect(root)
36
+ Rigor::Reflection.class_declarations_in(root).each { |declaration| record(declaration) }
37
+ self
47
38
  end
48
39
 
49
40
  # The classes this file declares, as {SerializerIndex::ClassEntry} values.
@@ -58,6 +49,15 @@ module Rigor
58
49
 
59
50
  private
60
51
 
52
+ def record(declaration)
53
+ name = declaration.name
54
+ node = declaration.node
55
+ superclass = superclass_name(node.superclass)
56
+ @entries[name] = superclass if superclass || !@entries.key?(name)
57
+ @nestings[name] = declaration.nesting
58
+ @dsl[name] = true if dsl_in?(node.body)
59
+ end
60
+
61
61
  # Whether a statement reachable without entering a def, block, lambda or nested declaration is a
62
62
  # receiverless `include` / `extend` of the DSL module.
63
63
  def dsl_in?(node)
@@ -2,16 +2,15 @@
2
2
 
3
3
  require "prism"
4
4
 
5
- require "rigor/inference/declaration_walk"
6
-
7
5
  require_relative "serializer_collector"
8
6
  require_relative "serializer_index"
9
7
 
10
8
  module Rigor
11
9
  module Plugin
12
10
  class Typelizer < Rigor::Plugin::Base
13
- # Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the plugin's `IoBoundary`, runs a
14
- # {SerializerCollector} over each and merges the results into one {SerializerIndex}.
11
+ # Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the
12
+ # plugin's `IoBoundary`, runs a {SerializerCollector} over each and merges the results into one
13
+ # {SerializerIndex}.
15
14
  class SerializerDiscoverer
16
15
  def initialize(io_boundary:, dirs:, project_paths: [])
17
16
  @io_boundary = io_boundary
@@ -29,10 +28,7 @@ module Rigor
29
28
  parsed = Prism.parse(contents)
30
29
  next unless parsed.success?
31
30
 
32
- collector = SerializerCollector.new
33
- Rigor::Inference::DeclarationWalk.run(
34
- parsed.value, [collector], Rigor::Inference::DeclarationWalk::Context.root(nesting: [])
35
- )
31
+ collector = SerializerCollector.new.collect(parsed.value)
36
32
  in_dirs = dir_files.include?(path)
37
33
  entries.concat(collector.class_entries.map { |entry| entry.with(in_dirs: in_dirs) })
38
34
  end
@@ -3,16 +3,16 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Typelizer < Rigor::Plugin::Base
6
- # The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the {SerializerCollector} found them. A frozen value
7
- # object the `:serializer_index` producer caches.
6
+ # The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the
7
+ # {SerializerCollector} found them. A frozen value object the `:serializer_index` producer caches.
8
8
  #
9
9
  # {#roots} answers the one question, conservatively — a wrong "no" costs a missing root, a wrong "yes"
10
10
  # hides a dead class.
11
11
  class SerializerIndex
12
12
  # A class the project declares. `superclass` is the constant as written (`nil` when absent) and
13
13
  # `nesting` the `Module.nesting` chain (innermost first) its header was written under, which is where
14
- # Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`; `in_dirs` when a file under the
15
- # typelizer `dirs` declares it (only such a class is published as a root).
14
+ # Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`;
15
+ # `in_dirs` when a file under the typelizer `dirs` declares it (only such a class is published as a root).
16
16
  ClassEntry = Data.define(:name, :superclass, :nesting, :dsl, :in_dirs)
17
17
 
18
18
  def initialize(classes:)
@@ -66,9 +66,6 @@ module Rigor
66
66
 
67
67
  # Always an Array of Bucket — `initialize` freezes what it is given and every caller hands it
68
68
  # Bucket instances (parse_row rows, from_diagnostics groups, `without`'s reject, or `[]`).
69
- # sig-gen gap: #2321 — `initialize` assigns `buckets.freeze`, an expression derived from the
70
- # parameter rather than a bare read of it, so the reader is skipped (untyped return) and the element type
71
- # is hand-written.
72
69
  attr_reader buckets: Array[Bucket]
73
70
 
74
71
  # `project_root` is `Pathname.new`'d, so a `Pathname` is accepted as well as a `String`
@@ -0,0 +1,6 @@
1
+ module Rigor::Analysis::CheckRules::RuleWalk
2
+ end
3
+
4
+ class Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver
5
+ def initialize: (untyped) -> void
6
+ end
@@ -2,8 +2,8 @@ module Rigor
2
2
  module Analysis
3
3
  module CheckRules
4
4
  def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
5
- def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root) -> Hash[Symbol, untyped]
6
- def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> untyped
5
+ def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root, ?plugin_api_surface: bool) -> Hash[Symbol, untyped]
6
+ def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver
7
7
  def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
8
8
  def self?.filter_suppressed: (Array[Diagnostic] diagnostics, comments: untyped, disabled_rules: Array[String]) -> Array[Diagnostic]
9
9
  end
@@ -3,7 +3,7 @@ module Rigor
3
3
  module Reachability
4
4
  class ScanCache
5
5
  def self.open: (untyped, ?target_ruby: untyped) -> Rigor::Analysis::Reachability::ScanCache
6
- def self.source_digest: () -> String
6
+ def self.source_digest: () -> ("unreadable" | String)
7
7
  def initialize: (path: untyped, header: untyped) -> void
8
8
  def save: () -> nil
9
9
  end
@@ -0,0 +1,14 @@
1
+ class Rigor::Cache::Descriptor::PluginEntry
2
+ def initialize: (id: untyped, version: untyped, ?config_hash: untyped) -> void
3
+ def to_h: () -> Hash[String, untyped]
4
+ end
5
+
6
+ class Rigor::Cache::Descriptor
7
+ def initialize: (?files: untyped, ?gems: untyped, ?plugins: untyped, ?configs: untyped, ?dependencies: untyped, ?globs: untyped) -> void
8
+ def self.compose: (*untyped) -> Rigor::Cache::Descriptor
9
+ def cache_key_for: (producer_id: untyped, ?params: untyped) -> String
10
+ def to_canonical_bytes: () -> String
11
+ def to_canonical_hash: () -> Hash[String, untyped]
12
+ def ==: (untyped) -> (false | true)
13
+ def hash: () -> Integer
14
+ end
@@ -1,4 +1,4 @@
1
1
  class Rigor::CLI::DiffCommand
2
2
  def initialize: (argv: untyped, out: untyped, err: untyped) -> void
3
- def run: () -> (0 | 1 | Integer)
3
+ def run: () -> (0 | 1 | 64)
4
4
  end
@@ -1,4 +1,4 @@
1
1
  class Rigor::CLI::ExplainCommand
2
2
  def initialize: (argv: untyped, out: untyped, err: untyped) -> void
3
- def run: () -> (0 | Integer)
3
+ def run: () -> (0 | 64)
4
4
  end
@@ -33,7 +33,7 @@ module Rigor
33
33
  def self.load_file: (untyped path) -> Registry
34
34
 
35
35
  attr_reader vocabulary_version: Integer
36
- # sig-gen gap: #2321 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
36
+ # sig-gen gap: #2325 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
37
37
  # type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
38
38
  # `map(&:to_s)`); every declared label, sorted. Implied ancestors are recognised by {#known?} but not listed
39
39
  # here.
@@ -42,7 +42,7 @@ module Rigor
42
42
  # Unmarked residue: `@roots` is computed from `@known` in `initialize`, not assigned from a
43
43
  # parameter, so no parameter credit applies.
44
44
  attr_reader roots: Array[String]
45
- # sig-gen gap: #2321 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
45
+ # sig-gen gap: #2325 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
46
46
  # a declared type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
47
47
  # keys and texts `to_s`'d); one line per root, from the data file's `descriptions:`.
48
48
  attr_reader descriptions: Hash[String, String]
@@ -19,7 +19,9 @@ module Rigor
19
19
  def hkt_scan_failure: () -> [String, String, String?, Symbol]?
20
20
 
21
21
  def self.default: () -> Environment
22
- def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> Environment
22
+ def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?, ?eager: bool) -> Environment
23
+ # authored: the return is the `signature_paths:` argument's type (authored intent) or the default ["sig"]; sig-gen types the `||` fallback untyped.
24
+ def self.project_signature_root_candidates: (Array[String | _ToPath]? signature_paths) -> Array[String | _ToPath]
23
25
  # authored: the return is the `signature_paths:` argument's type (authored intent) or the default [Pathname]; sig-gen types the `||` fallback untyped.
24
26
  def self.project_signature_roots: (Array[String | _ToPath]? signature_paths, ?root: String) -> Array[String | _ToPath]
25
27
 
@@ -36,6 +38,7 @@ module Rigor
36
38
  def rbs_module?: (String | Symbol name) -> bool
37
39
  def singleton_extended_modules: (String | Symbol name) -> Array[String]
38
40
  def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
41
+ def prepare_for_typing!: () -> Environment
39
42
  def reflection: () -> untyped?
40
43
  def missing_rbs_gem_owner: (String | Symbol root_constant_name) -> String?
41
44
 
@@ -3,7 +3,8 @@ module Rigor
3
3
  type closure_classification = :non_escaping | :escaping | :unknown
4
4
 
5
5
  class ExpressionTyper
6
- def self.harvest_return_memo: () -> Hash[untyped, Array[untyped]]
6
+ def self.harvest_return_memo: (?Hash[untyped, Hash[untyped, untyped]]? bucket) -> Hash[untyped, Array[untyped]]
7
+ def self.current_return_memo: () -> Hash[untyped, Hash[untyped, untyped]]?
7
8
  def initialize: (scope: Scope, ?tracer: FallbackTracer?) -> void
8
9
  def type_of: (untyped node) -> Type::t
9
10
  def return_type_for: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
@@ -172,6 +173,8 @@ module Rigor
172
173
  def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
173
174
  def self?.constant_writes_for_file: (untyped root) -> Hash[String, untyped]
174
175
  def self?.constant_literal_value: (untyped rvalue) -> Array[untyped]?
176
+ # authored: the `Set[String]` values are source paths by contract; a Set's element type stays `untyped` in sig-gen (#2177, #2317), so the generator
177
+ # would never propose it.
175
178
  def self?.finalize_constant_writes: (Hash[String, Hash[String, untyped]] writes) -> [Hash[String, Type::Constant], Hash[String, Set[String]]]
176
179
  def self?.propagate: (untyped node, Hash[untyped, Scope] table, Scope parent_scope) -> void
177
180
 
@@ -4,15 +4,10 @@ module Rigor
4
4
  # the read-before-write nil soundness gate does not widen their ivars. See
5
5
  # lib/rigor/plugin/additional_initializer.rb for the full contract.
6
6
  class AdditionalInitializer
7
- # sig-gen gap: #2321 — `attr_reader` rows over ivars `initialize` assigns from expressions derived from its
8
- # parameters are skipped (untyped return); the constructor validates `receiver_constraint` is a non-empty String
9
- # and normalizes both lists with `map(&:to_sym)`, so the stored types are hand-written.
10
7
  attr_reader receiver_constraint: String
11
8
 
12
- # sig-gen gap: #2321 — as above.
13
9
  attr_reader methods: Array[Symbol]
14
10
 
15
- # sig-gen gap: #2321 — as above.
16
11
  attr_reader block_methods: Array[Symbol]
17
12
 
18
13
  # The validators accept Symbol or non-empty String elements; `initialize` normalizes to
@@ -4,9 +4,20 @@
4
4
  # declared surface and give plugin subclasses (once they enter a check
5
5
  # target) method resolution for the DSL + hook surface.
6
6
  #
7
- # Collaborators that have no RBS yet (Diagnostic, Cache::Descriptor,
8
- # FlowContribution, NodeContext, Prism nodes) are typed `untyped`; the
9
- # precision ceiling is the sparse `sig/` tree, not this file.
7
+ # ADR-128 WD2 — every hook position takes a plugin-facing interface
8
+ # (`sig/rigor/plugin/interfaces.rbs`), a concrete Prism node class, a
9
+ # `Rigor::Type::t` or a core type, instead of `untyped`. A hook's `scope`
10
+ # is non-nil where every engine call site passes one: the per-file hooks
11
+ # receive the seeded `Scope.empty(...)`, `MethodDispatcher#try_plugin_contribution`
12
+ # returns before consulting a plugin when its scope is nil, and
13
+ # `StatementEvaluator#apply_plugin_assertions` has already read its own. The
14
+ # existing `scope.nil?` guards in plugin source stay valid either way.
15
+ #
16
+ # Collaborators that have no RBS yet (FlowContribution) stay `untyped`;
17
+ # the precision ceiling is the sparse `sig/` tree. A parameter row stays
18
+ # lenient even where its type IS declared — `cache_for`'s `?descriptor:` and
19
+ # `IoBoundary#replay` both take a `Cache::Descriptor` and both read `untyped`
20
+ # — by ADR-107 clause 2, not for want of a declaration.
10
21
  class Rigor::Plugin::Base
11
22
  # --- class-level declaration DSLs ---------------------------------
12
23
 
@@ -17,16 +28,32 @@ class Rigor::Plugin::Base
17
28
  def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped, ?generation_cap: untyped) { (untyped params) -> untyped } -> Symbol
18
29
  def self.producers: () -> Hash[Symbol, untyped]
19
30
 
20
- def self.node_rule: (untyped node_type) { (*untyped) -> untyped } -> Class
31
+ # One overload per node class the bundled plugins name, so the block's `node` is that class; `Prism::Node` is the
32
+ # fallback for any other (RBS cannot write `singleton(N)` over a type variable). The block runs `instance_exec`'d
33
+ # on the plugin: `file_context` is whatever the `node_file_context` block returned (plugin-defined), and a rule
34
+ # may answer one diagnostic, an array of them, or nil.
35
+ def self.node_rule: (singleton(::Prism::CallNode) node_type) { (::Prism::CallNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::CallNode)
36
+ | (singleton(::Prism::ClassNode) node_type) { (::Prism::ClassNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ClassNode)
37
+ | (singleton(::Prism::ProgramNode) node_type) { (::Prism::ProgramNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ProgramNode)
38
+ | (singleton(::Prism::Node) node_type) { (::Prism::Node node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::Node)
21
39
  def self.node_rules: () -> Array[untyped]
22
40
 
23
- def self.node_file_context: () { (untyped root, untyped scope) -> untyped } -> void
24
- def self.node_file_context_block: () -> (^(untyped root, untyped scope) -> untyped)?
41
+ def self.node_file_context: () { (::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped } -> void
42
+ def self.node_file_context_block: () -> (^(::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped)?
25
43
 
26
- def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(untyped path) -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
44
+ # A rule with a static `methods:` Array only ever runs its block on a `CallNode`: the name gate
45
+ # (`Registry#build_name_gates`, `Base#dynamic_return_rule_applies?`) matches on the call's name, which
46
+ # none of the other nodes in the union below has, so the gate declines them (#2681, #2764). Every other
47
+ # declaration shape is offered the whole union. A callable `methods:` also only ever sees a `CallNode`
48
+ # at runtime, since the same gate declines the nameless nodes, but RBS cannot tell a callable from an
49
+ # Array argument by overload alone, so it keeps the union, which is sound.
50
+ def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, methods: Array[untyped], ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
51
+ | (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
27
52
  def self.dynamic_returns: () -> Array[untyped]
28
53
 
29
- def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
54
+ # The block answers the facts that hold after the call returns normally, or nil. The element is a
55
+ # `Rigor::FlowContribution::Fact`, which has no RBS declaration yet, so it stays `untyped`.
56
+ def self.narrowing_facts: (methods: Array[untyped]) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Array[untyped]? } -> nil
30
57
  # The engine-facing reader for the declared `narrowing_facts` rules. Named
31
58
  # `type_specifiers` before ADR-80; that spelling was removed in 0.3.0.
32
59
  def self.narrowing_facts_rules: () -> Array[untyped]
@@ -35,15 +62,16 @@ class Rigor::Plugin::Base
35
62
 
36
63
  # --- instance surface --------------------------------------------
37
64
 
38
- attr_reader services: untyped
39
- attr_reader config: untyped
65
+ attr_reader services: Rigor::Plugin::_Services
66
+ # Schema-driven per plugin, so the values stay `untyped` (ADR-128 WD2).
67
+ attr_reader config: Hash[String, untyped]
40
68
 
41
- def initialize: (services: untyped, ?config: untyped) -> void
69
+ def initialize: (services: Rigor::Plugin::_Services, ?config: Hash[String, untyped]) -> void
42
70
 
43
71
  # Overridable lifecycle / extension hooks (default no-ops).
44
- def init: (untyped services) -> nil
45
- def prepare: (untyped services) -> nil
46
- def diagnostics_for_file: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
72
+ def init: (Rigor::Plugin::_Services services) -> nil
73
+ def prepare: (Rigor::Plugin::_Services services) -> nil
74
+ def diagnostics_for_file: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[Rigor::Analysis::Diagnostic]
47
75
  # authored: hook-contract widening — the element type comes from a plugin gem sig-gen never
48
76
  # sees, so no engine work would let it answer; the declared `Array[Rigor::Plugin::TemplateUnit]`
49
77
  # over a bare-`[]` default body is the author's contract.
@@ -54,12 +82,19 @@ class Rigor::Plugin::Base
54
82
  def declared_members: (untyped class_name) -> Array[untyped]
55
83
 
56
84
  # Engine-executed dispatch over the declared DSLs.
57
- def node_rule_diagnostics: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
85
+ def node_rule_diagnostics: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[untyped]
58
86
  # sig-gen gap: #1007 — the true `return result if result` branch (base.rb:594) flows through an
59
87
  # `instance_exec`'d, plugin-supplied Proc that sig-gen's inference does not fold into the return
60
88
  # join, so it proposes the literal fallback `nil` instead of the honest contract.
61
- def dynamic_return_type: (call_node: untyped, scope: untyped, receiver_type: untyped) -> Rigor::Type::t?
62
- def narrowing_facts_for: (call_node: untyped, scope: untyped) -> Array[untyped]
89
+ # `call_node` is a `CallNode` except where the engine dispatches a compound write or a block argument. The
90
+ # compound writes (`x.attr op= v`, `x.attr ||= v`, `x.attr &&= v`, and the `x[i]` forms) reach a rule through
91
+ # `ExpressionTyper#attribute_compound_read_type`, `#call_operator_write_type_for` and `#index_read_dispatch`;
92
+ # the `&:symbol` block arguments through `ExpressionTyper#symbol_block_return_type` and `#apply_hash_block`.
93
+ # A rule the name gate does not screen out (a `receivers:`-only rule, for one) is offered those nodes, so the
94
+ # `dynamic_return` block union above is the full set. A rule that reads a `CallNode`-only member (`receiver`,
95
+ # `name`, `arguments`) must guard with `call_node.is_a?(::Prism::CallNode)` first (#2717).
96
+ def dynamic_return_type: (call_node: ::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode, scope: Rigor::Plugin::_Scope, receiver_type: Rigor::Type::t) -> Rigor::Type::t?
97
+ def narrowing_facts_for: (call_node: ::Prism::CallNode, scope: Rigor::Plugin::_Scope) -> Array[untyped]
63
98
 
64
99
  # Authoring helpers.
65
100
  def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> Rigor::Analysis::Diagnostic
@@ -84,5 +119,5 @@ class Rigor::Plugin::Base
84
119
  def effect_ancestry: () -> Array[Rigor::Plugin::EffectAncestry]
85
120
  def io_boundary: () -> Rigor::Plugin::IoBoundary
86
121
  def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> Proc
87
- def plugin_entry: () -> untyped
122
+ def plugin_entry: () -> Rigor::Cache::Descriptor::PluginEntry
88
123
  end
@@ -19,7 +19,7 @@ module Rigor
19
19
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), and `initialize`
20
20
  # stores the private `validate_receiver!` helper's result (#2312).
21
21
  attr_reader receiver: String
22
- # sig-gen gap: #2321 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
22
+ # sig-gen gap: #2325 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
23
23
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `String | Symbol`), stored as
24
24
  # `method.to_sym`.
25
25
  attr_reader method: Symbol
@@ -28,7 +28,7 @@ module Rigor
28
28
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `Array()` of anything
29
29
  # answering `to_s`), stored from the private `normalize_labels` helper (#2312).
30
30
  attr_reader labels: Array[String]
31
- # sig-gen gap: #2321 — `narrow:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
31
+ # sig-gen gap: #2325 — `narrow:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
32
32
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
33
33
  attr_reader narrow: String?
34
34
  attr_reader discharge: bool
@@ -41,7 +41,7 @@ module Rigor
41
41
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), stored from the
42
42
  # private `validate_taint!` helper (#2312).
43
43
  attr_reader taint: String?
44
- # sig-gen gap: #2321 — `callee:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
44
+ # sig-gen gap: #2325 — `callee:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
45
45
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
46
46
  attr_reader callee: String?
47
47
  # sig-gen gap: #2312 — `callee_fallbacks:` is `untyped` (input `untyped`, looks deliberate; a declared type is