rigortype 0.4.5 → 0.4.7

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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -35,7 +35,8 @@ module Rigor
35
35
  # LocalVariableReadNode. Composite receivers (`expect(foo.bar)`, `expect { ... }.to raise_error`)
36
36
  # fall through.
37
37
  # 3. The matcher is one of the recognised forms above (`be_a` / `be_kind_of` / `be_instance_of` /
38
- # `be_an_instance_of` / `be_nil` / `eq(literal)` / `eql(literal)` / `match(/regex/)`).
38
+ # `be_an_instance_of` / `be_nil` / `eq(literal)` / `eql(literal)` / `match(/regex/)`, the last a meet
39
+ # with the local's members; see `regex_match_type`).
39
40
  #
40
41
  # ## Equality is the local's own method
41
42
  #
@@ -152,10 +153,7 @@ module Rigor
152
153
  when :eq, :eql
153
154
  equality_type_for_literal_arg(matcher, target_local, negative: negative, scope: scope)
154
155
  when :match
155
- # `match(/regex/)` narrows x to String. `match("...")` or `match(arbitrary_object)` falls
156
- # through — the broader matcher dispatch needs the receiver to be a String, but we can only
157
- # assert that for a literal regex.
158
- string_type_for_regex_arg(matcher)
156
+ regex_match_type(matcher, target_local, negative: negative, scope: scope)
159
157
  end
160
158
  end
161
159
 
@@ -293,15 +291,108 @@ module Rigor
293
291
  end
294
292
  end
295
293
 
296
- def string_type_for_regex_arg(matcher)
294
+ # --- `match(/regex/)` (#2033) ---
295
+ #
296
+ # RSpec's `match` passes when `expected === actual`, when `actual == expected`, or when `actual` responds to
297
+ # `match` and `actual.match(expected)` is truthy. `Regexp#===` matches a Symbol by its name, so a Symbol
298
+ # passes too, and keeps being a Symbol. The positive fact is therefore a meet with the local's members, as
299
+ # `eq`'s is: a String or Symbol member is kept as it is, and a member of a core class that has no `match` or
300
+ # `to_str` and whose `==` never equals a Regexp (`nil`, `true`, `false`, the numerics) is dropped, unless the
301
+ # project defines one of those methods on it or a core ancestor. An `Object`, `BasicObject`, `Kernel` or
302
+ # `Comparable` member reads `String | Symbol` ({#module_match_member}); any other module means no fact. Any other member (a user class, `Dynamic`) may pass, and
303
+ # the analyzer emits no fact; so does a subject none of whose members can pass (a `nil` local stays `nil`). The negative form emits none either: a String that does not match passes
304
+ # `not_to match`, so nothing leaves the local.
305
+ REGEX_MATCH_KEPT_CLASSES = %w[String Symbol].freeze
306
+ REGEX_MATCH_METHODS = %i[match to_str].freeze
307
+ Ractor.make_shareable(REGEX_MATCH_KEPT_CLASSES)
308
+ Ractor.make_shareable(REGEX_MATCH_METHODS)
309
+ private_constant :REGEX_MATCH_KEPT_CLASSES, :REGEX_MATCH_METHODS
310
+
311
+ def regex_match_type(matcher, target_local, negative:, scope:)
312
+ return nil if negative || scope.nil?
313
+ return nil unless regex_literal_arg?(matcher)
314
+
315
+ current = scope.local(target_local)
316
+ return nil if current.nil?
317
+
318
+ members = current.is_a?(Rigor::Type::Union) ? current.members : [current]
319
+ kept = []
320
+ members.each do |member|
321
+ met = regex_match_member(member, scope)
322
+ return nil if met.equal?(UNDECIDED)
323
+
324
+ kept << met unless met.nil?
325
+ end
326
+ kept.empty? ? nil : Rigor::Type::Combinator.union(*kept)
327
+ end
328
+
329
+ def regex_literal_arg?(matcher)
297
330
  args = matcher.arguments&.arguments || []
298
- return nil unless args.size == 1
331
+ args.size == 1 &&
332
+ (args.first.is_a?(Prism::RegularExpressionNode) || args.first.is_a?(Prism::InterpolatedRegularExpressionNode))
333
+ end
299
334
 
300
- arg = args.first
301
- return nil unless arg.is_a?(Prism::RegularExpressionNode) ||
302
- arg.is_a?(Prism::InterpolatedRegularExpressionNode)
335
+ # The member itself when it may pass, nil when it cannot, UNDECIDED when Rigor cannot tell.
336
+ def regex_match_member(member, scope)
337
+ module_member = module_match_member(member, scope)
338
+ return module_member unless module_member.nil?
303
339
 
304
- Rigor::Type::Combinator.nominal_of("String")
340
+ class_name = member_class_name(member)
341
+ return UNDECIDED if class_name.nil?
342
+ return member if REGEX_MATCH_KEPT_CLASSES.include?(class_name)
343
+ return member if project_defines?(class_name, :==, scope)
344
+ return member if REGEX_MATCH_METHODS.any? { |name| project_defines_on_chain?(class_name, name, scope) }
345
+
346
+ nil
347
+ end
348
+
349
+ OPEN_SURFACE_CLASS_NAMES = %w[Object BasicObject].freeze
350
+ # The modules String's or Symbol's own ancestry includes (`Kernel`, `Comparable`), read from the running Ruby.
351
+ STRING_SYMBOL_MODULES = (String.ancestors + Symbol.ancestors).grep_v(Class).filter_map(&:name).uniq.freeze
352
+ Ractor.make_shareable(OPEN_SURFACE_CLASS_NAMES)
353
+ Ractor.make_shareable(STRING_SYMBOL_MODULES)
354
+ private_constant :OPEN_SURFACE_CLASS_NAMES, :STRING_SYMBOL_MODULES
355
+
356
+ # The meet for an `Object`, `BasicObject` or module member, or nil for any other member. Its instances may be
357
+ # anything, a `to_str` or `match` object included. A member that declares `match` or `to_str` itself (its
358
+ # signature, a core ancestor, or the project's source) is kept whole. `Object`, `BasicObject` and a module
359
+ # String or Symbol includes otherwise read `String | Symbol`, as the String they most often are, so a String
360
+ # method called after the matcher reports nothing. Any other module is UNDECIDED: a passing value may include
361
+ # it without being a String. (Decision shared with #2256.)
362
+ def module_match_member(member, scope)
363
+ return nil unless member.is_a?(Rigor::Type::Nominal) && member.type_args.empty?
364
+
365
+ name = member.class_name
366
+ string_like = OPEN_SURFACE_CLASS_NAMES.include?(name) || STRING_SYMBOL_MODULES.include?(name)
367
+ return nil unless string_like || scope.environment&.rbs_module?(name)
368
+ return member if REGEX_MATCH_METHODS.any? { |method_name| declares_method?(name, method_name, scope) }
369
+ return UNDECIDED unless string_like
370
+
371
+ Rigor::Type::Combinator.union(Rigor::Type::Combinator.nominal_of("String"),
372
+ Rigor::Type::Combinator.nominal_of("Symbol"))
373
+ end
374
+
375
+ def declares_method?(name, method_name, scope)
376
+ project_defines_on_chain?(name, method_name, scope) ||
377
+ !Rigor::Reflection.instance_method_definition(name, method_name, scope: scope).nil?
378
+ end
379
+
380
+ # Each core class's (and `Object` / `BasicObject` / `Kernel`'s) runtime ancestor chain. The project's
381
+ # reopenings are looked up along it by name, since the scope's ancestry walk does not cross core classes:
382
+ # `class Numeric; def to_str` reaches a Float member, `module Kernel; def match` a nil one.
383
+ CORE_ANCESTOR_CHAINS = [*EQUALITY_CORE_CLASSES, Object, BasicObject, Kernel].to_h do |mod|
384
+ [mod.name, mod.ancestors.filter_map(&:name).freeze]
385
+ end.freeze
386
+ Ractor.make_shareable(CORE_ANCESTOR_CHAINS)
387
+ private_constant :CORE_ANCESTOR_CHAINS
388
+
389
+ # Whether the project's source defines `method_name` on `class_name` or an ancestor, core ones included: a
390
+ # `def`, an included module, or a `define_method`.
391
+ def project_defines_on_chain?(class_name, method_name, scope)
392
+ CORE_ANCESTOR_CHAINS.fetch(class_name, [class_name]).any? do |name|
393
+ !scope.user_def_through_ancestors(name, method_name).first.nil? ||
394
+ scope.discovered_method_through_ancestors?(name, method_name, :instance)
395
+ end
305
396
  end
306
397
 
307
398
  NO_LITERAL = Object.new.freeze
@@ -14,7 +14,7 @@ module Rigor
14
14
  # narrows `expect(x).to MATCHER` assertions downstream, and binds `let`/`subject` locals to their
15
15
  # inferred return types inside `it` bodies.
16
16
  #
17
- # Tier 3A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
+ # Tier 3A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
18
18
  # Ships four checks:
19
19
  #
20
20
  # 1. **Duplicate `let` / `subject` declarations** in the same scope (`warning`). RSpec's runtime lets
@@ -44,7 +44,7 @@ module Rigor
44
44
  # - **No shared-context resolution.** `include_context`, `shared_context`, and `it_behaves_like` are
45
45
  # recognised as scope-opening calls but their declarations are not pulled into the host scope.
46
46
  # - **Constant validation is not done here.** `RSpec.describe SomeClass do` does not validate
47
- # `SomeClass`; the engine's `inference.unresolved-constant` already catches that.
47
+ # `SomeClass`; the engine's constant-resolution diagnostics already catch that.
48
48
  class Rspec < Rigor::Plugin::Base
49
49
  manifest(
50
50
  id: "rspec",
@@ -30,7 +30,7 @@ module Rigor
30
30
  # - `route_to(...)` / `redirect_to(...)` — needs the routes table from `rigor-rails-routes`
31
31
  # (`:helper_table` cross-plugin fact). Future slice.
32
32
  # - `have_enqueued_job(JobClass)` / `have_enqueued_mail(MailerClass)` — class-existence check overlaps
33
- # with engine's `inference.unresolved-constant`; queued behind a decision on which surface owns the
33
+ # with the engine's constant-resolution diagnostics; queued behind a decision on which surface owns the
34
34
  # diagnostic.
35
35
  # - `have_received(:method)` — overlaps with engine's `call.undefined-method`; same coordination
36
36
  # question.
@@ -44,9 +44,8 @@ module Rigor
44
44
  description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
45
45
  )
46
46
 
47
- # ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is now pure per-node
48
- # logic. The diagnostic points at the matcher name (`message_loc`), reproducing the previous
49
- # `message_loc || location` positioning.
47
+ # ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is pure per-node
48
+ # logic. The diagnostic points at the matcher name (`message_loc`).
50
49
  node_rule Prism::CallNode do |node, _scope, path|
51
50
  violation = HaveHttpStatusAnalyzer.violation_for(node)
52
51
  next [] unless violation
@@ -31,7 +31,7 @@ module Rigor
31
31
  # have_db_index(:col)
32
32
  #
33
33
  # All look up `:col` against the model's `columns:` (the published `:model_index` fact's flat
34
- # `Array<String>` per model — see `Analyzer#column_names`). Unknown columns fire `unknown-column`.
34
+ # name list per model — see `Analyzer#column_names`). Unknown columns fire `unknown-column`.
35
35
  #
36
36
  # ### Association matchers
37
37
  #
@@ -107,8 +107,7 @@ module Rigor
107
107
 
108
108
  # A direct matcher invocation is a `CallNode` whose `name` is in `MATCHER_TABLE` and whose first
109
109
  # argument is a `SymbolNode`. The chain shape (`should`, `expect(...).to`, `is_expected.to`) is
110
- # irrelevant — we always recurse to the inner matcher, so a diagnostic fires on the matcher
111
- # regardless of the surrounding chain.
110
+ # irrelevant: the engine visits the inner matcher call, so the diagnostic fires on it regardless.
112
111
  def matcher_invocation?(node)
113
112
  node.is_a?(Prism::CallNode) && MATCHER_TABLE.key?(node.name) && symbol_first_arg?(node)
114
113
  end
@@ -172,11 +171,10 @@ module Rigor
172
171
  end
173
172
 
174
173
  # `entry` is the published fact's per-model Hash: `{ table:, columns:, associations:, ... }`.
175
- # `columns:` is a flat `Array<String>` (`Activerecord#index_to_published_hash`); `associations:` is
176
- # `Array<Hash>` with String `:name`, Symbol `:kind` (`:singular` | `:collection`), `:nullable`,
177
- # `:polymorphic`, and a `:target` that is a String except on polymorphic rows, where it is nil —
178
- # the row shape `ModelDiscoverer#build_association_row` publishes. Only `:name` and `:kind` are
179
- # read here.
174
+ # `columns:` is a flat list of column names (`Activerecord#index_to_published_hash`); `associations:`
175
+ # is a list of rows with `:name`, `:kind` (`:singular` | `:collection`), `:nullable`, `:polymorphic`,
176
+ # and a `:target` that is nil on polymorphic rows — the row shape `ModelDiscoverer#build_association_row`
177
+ # publishes. Only `:name` and `:kind` are read here.
180
178
  def column_names(entry) = entry[:columns] || []
181
179
  def association_rows(entry) = entry[:associations] || []
182
180
  def find_association(entry, name) = association_rows(entry).find { |a| a[:name] == name.to_s }
@@ -97,8 +97,11 @@ module Rigor
97
97
 
98
98
  # Cached separately from `:worker_index` because the two invalidate on different files: editing a
99
99
  # schedule changes which workers are reached without touching `app/workers` at all. The `watch:` roots
100
- # the glob at the working directory so that CREATING a schedule file — not just editing one — is seen.
101
- producer :scheduled_workers, watch: -> { [[".", *@schedule_paths]] } do |_params|
100
+ # the glob at the working directory so that CREATING a schedule file — not just editing one — is seen. Each
101
+ # configured path is escaped to a literal name, so `sidekiq[1].yml` is watched as that file, and an
102
+ # absolute path is watched under its own directory (`RootedGlob.split_file`, #2222).
103
+ producer :scheduled_workers,
104
+ watch: -> { @schedule_paths.map { |path| Rigor::RootedGlob.split_file(path) } } do |_params|
102
105
  ScheduleScan.new(
103
106
  io_boundary: io_boundary,
104
107
  schedule_paths: @schedule_paths
@@ -31,11 +31,10 @@ module Rigor
31
31
  # behaviour: it always raises). This lets the engine's existing flow analysis treat code after
32
32
  # `T.absurd` as unreachable, matching what users of Sorbet expect.
33
33
  # - When the branch is REACHABLE (the discriminant's type isn't `bot`), the recogniser also records
34
- # the call node in a per-plugin set. The plugin's `diagnostics_for_file` later walks the AST for
35
- # `T.absurd` calls and emits a `plugin.sorbet.absurd-reachable` warning at every call_node whose
36
- # object identity matches the recorded set. We rely on the runner only parsing each file once per
37
- # run, so the same Prism node object is seen in both the `dynamic_return` rule and
38
- # `diagnostics_for_file`.
34
+ # the call node in a per-plugin set. The plugin's node rule later emits a
35
+ # `plugin.sorbet.absurd-reachable` warning for each recorded call node, matched by object identity.
36
+ # We rely on the runner only parsing each file once per run, so the same Prism node object is seen
37
+ # in both the `dynamic_return` rule and the node rule.
39
38
  module AbsurdRecognizer
40
39
  # @return true when `call_node` is `T.absurd(x)`.
41
40
  def self.absurd_call?(call_node)
@@ -21,7 +21,7 @@ module Rigor
21
21
  # | `T.unsafe(x)` | return type ← `Dynamic[top]` |
22
22
  # | `T.reveal_type(expr)` | return type ← `inferred(expr)` (passes through) |
23
23
  # | `T.assert_type!(expr, T)` | return type ← translated `T` + static subtype check |
24
- # | `T.bind(self, T)` | return type ← `Constant[nil]` + post_return_fact narrowing self to translated `T` |
24
+ # | `T.bind(self, T)` | return type ← `Constant[nil]` + `self` narrowing fact |
25
25
  #
26
26
  # The Sorbet runtime's `T.let` / `T.cast` actually return the inner expression unchanged at runtime;
27
27
  # their job is purely to *assert* a static type. From Rigor's static perspective the simplest
@@ -36,7 +36,7 @@ module Rigor
36
36
  # `T.reveal_type` is "diagnostic-only" in Sorbet: it passes the value through unchanged at runtime
37
37
  # AND surfaces the inferred static type as a build-time message. The recogniser contributes the
38
38
  # inferred type (so chained call sites still resolve as if the `T.reveal_type` wrapper weren't
39
- # there); the plugin's `diagnostics_for_file` hook surfaces the `plugin.sorbet.reveal-type` `:info`
39
+ # there); the plugin's node rule surfaces the `plugin.sorbet.reveal-type` `:info`
40
40
  # message for human consumption.
41
41
  #
42
42
  # `T.bind(self, T)` is recognised as block-scope self narrowing. The recogniser returns a
@@ -113,9 +113,9 @@ module Rigor
113
113
 
114
114
  # `T.assert_type!(expr, T)` shares the typed-assertion contribution shape with `T.cast` (return is
115
115
  # the asserted type), so the recogniser delegates the return-type half through
116
- # `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from
117
- # `cast` lives in the plugin's `diagnostics_for_file` hook (mirroring the absurd-recognizer
118
- # pattern: record the call here, emit the diagnostic from the per-file walker).
116
+ # `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from `cast`
117
+ # is run by the plugin's `record_assert_type_check`, which records a mismatch for the node rule to
118
+ # emit (the absurd-recognizer pattern).
119
119
  def assert_type_check(call_node, scope)
120
120
  return nil if scope.nil?
121
121
 
@@ -135,8 +135,7 @@ module Rigor
135
135
  # `T.reveal_type(expr)` returns `expr` unchanged at runtime; the Sorbet-side semantics is "make
136
136
  # the inferred static type visible to the user." The contribution mirrors `T.must` minus the
137
137
  # nil-stripping: the call's return type is the inner expression's inferred type. The companion
138
- # diagnostic is emitted by the plugin's `diagnostics_for_file` hook; this method handles the
139
- # contribution only.
138
+ # diagnostic is emitted by a node rule; this method handles the contribution only.
140
139
  def resolve_reveal_type(call_node, scope)
141
140
  inner = nth_argument(call_node, 0)
142
141
  return Rigor::Type::Combinator.untyped if inner.nil? || scope.nil?
@@ -57,7 +57,7 @@ module Rigor
57
57
  @entries[key_for(class_name, method_name, kind)]
58
58
  end
59
59
 
60
- # @return frozen mapping `{ include: [...], extend: [...] }`.
60
+ # @return frozen `:include` / `:extend` buckets of mixed-in module names.
61
61
  # Returns {EMPTY_MIXINS} when no mixins were recorded.
62
62
  def mixins_for(class_name)
63
63
  @mixins[class_name] || EMPTY_MIXINS
@@ -22,8 +22,8 @@ module Rigor
22
22
  #
23
23
  # The parser walks the chain right-to-left, gathering whatever it recognises (`params` / `returns` /
24
24
  # `void` / `abstract` / `override` / `overridable` / `final` / `type_parameters` / `checked` /
25
- # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers and
26
- # `type_parameters` are recorded but not yet acted on.
25
+ # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers are recorded but not
26
+ # yet acted on; `type_parameters` is recognised and its payload discarded.
27
27
  #
28
28
  # The parser is intentionally tolerant — unknown chain nodes degrade to "the rest of the chain is
29
29
  # opaque" rather than raising. The plugin emits a diagnostic (`plugin.sorbet.parse-error`) only when
@@ -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
@@ -502,8 +499,7 @@ module Rigor
502
499
  # ADR-88 WD2 — deterministic fold order matters now that the catalog VALUE is digested into the
503
500
  # incremental fact-surface fingerprint: a duplicate `(class, method, kind)` sig's last-wins winner
504
501
  # 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.
502
+ # order is already stable; this walk relies on it rather than re-sorting.
507
503
  Dir.glob("**/*#{ext}", base: absolute).each do |relative|
508
504
  harvest_file(canonicalize(File.join(absolute, relative)), catalog, sigil_by_path, parse_errors_by_path)
509
505
  end
@@ -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
@@ -10,8 +10,9 @@ require_relative "serializer_index"
10
10
  module Rigor
11
11
  module Plugin
12
12
  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}.
13
+ # Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the
14
+ # plugin's `IoBoundary`, runs a {SerializerCollector} over each and merges the results into one
15
+ # {SerializerIndex}.
15
16
  class SerializerDiscoverer
16
17
  def initialize(io_boundary:, dirs:, project_paths: [])
17
18
  @io_boundary = io_boundary
@@ -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:)
@@ -62,7 +62,7 @@ module Rigor
62
62
  if io_boundary.directory?(File.expand_path(entry))
63
63
  [entry, "**/*.rb"]
64
64
  elsif entry.end_with?(".rb") && io_boundary.file?(File.expand_path(entry))
65
- [File.dirname(entry), File.basename(entry)]
65
+ [File.dirname(entry), Rigor::RootedGlob.literal(File.basename(entry))]
66
66
  end
67
67
  end
68
68
  end
@@ -66,8 +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: #1154 — an `attr_reader` over an ivar assigned in `initialize` from a parameter
70
- # is skipped (untyped return); the element type is hand-written.
71
69
  attr_reader buckets: Array[Bucket]
72
70
 
73
71
  # `project_root` is `Pathname.new`'d, so a `Pathname` is accepted as well as a `String`
@@ -3,6 +3,7 @@ module Rigor
3
3
  module CheckRules
4
4
  def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
5
5
  def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root) -> Hash[Symbol, untyped]
6
+ # authored: returns Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver, which has no RBS declaration yet (#2404); stays `untyped` until it is declared
6
7
  def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> untyped
7
8
  def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
8
9
  def self?.filter_suppressed: (Array[Diagnostic] diagnostics, comments: untyped, disabled_rules: Array[String]) -> Array[Diagnostic]
@@ -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
@@ -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
@@ -45,16 +45,15 @@ module Rigor
45
45
  # The collection a file with nothing to say contributes.
46
46
  def self.empty: (?String? path) -> FileCollection
47
47
 
48
- # sig-gen gap: #1154 — `initialize` freezes the kwarg tables rather than assigning them
49
- # verbatim, so sig-gen proposes the default constant's shape for each reader.
50
48
  attr_reader path: String?
51
- # sig-gen gap: #1154 — `{ "Class#m" => Summary }`.
49
+ # sig-gen gap: #2312 — `{ "Class#m" => Summary }`, through the private `freeze_table` helper, whose parameters
50
+ # are undeclared.
52
51
  attr_reader summaries: Hash[String, Summary]
53
- # sig-gen gap: #1154 — `{ "Class#m" => [Edge] }`, sorted and de-duplicated.
52
+ # sig-gen gap: #2312 — `{ "Class#m" => [Edge] }`, sorted and de-duplicated.
54
53
  attr_reader edges: Hash[String, Array[Edge]]
55
- # sig-gen gap: #1154 — `{ class_name => superclass_name }`, `OPAQUE_ANCESTOR` included.
54
+ # sig-gen gap: #2312 — `{ class_name => superclass_name }`, `OPAQUE_ANCESTOR` included.
56
55
  attr_reader superclasses: Hash[String, String]
57
- # sig-gen gap: #1154 — `{ class_name => [module_name] }`, include/prepend merged.
56
+ # sig-gen gap: #2312 — `{ class_name => [module_name] }`, include/prepend merged.
58
57
  attr_reader includes: Hash[String, Array[String]]
59
58
 
60
59
  def initialize: (?path: String?, ?summaries: Hash[String, Summary],
@@ -32,17 +32,19 @@ module Rigor
32
32
  # an empty vocabulary (fail-open).
33
33
  def self.load_file: (untyped path) -> Registry
34
34
 
35
- # sig-gen gap: #1154 — `attr_reader` over an ivar assigned in `initialize` from the
36
- # parameter of the same name; sig-gen proposes `untyped` for it.
37
35
  attr_reader vocabulary_version: Integer
38
- # sig-gen gap: #1154 — every declared label, sorted; implied ancestors are recognised by
39
- # {#known?} but not listed here.
36
+ # sig-gen gap: #2325 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
37
+ # type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
38
+ # `map(&:to_s)`); every declared label, sorted. Implied ancestors are recognised by {#known?} but not listed
39
+ # here.
40
40
  attr_reader labels: Array[String]
41
41
  # The roots of the vocabulary — the outermost segments {#with} treats as already owned.
42
42
  # Unmarked residue: `@roots` is computed from `@known` in `initialize`, not assigned from a
43
- # parameter, so #1154 does not cover it.
43
+ # parameter, so no parameter credit applies.
44
44
  attr_reader roots: Array[String]
45
- # sig-gen gap: #1154 — one line per root, from the data file's `descriptions:`.
45
+ # sig-gen gap: #2325 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
46
+ # a declared type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
47
+ # keys and texts `to_s`'d); one line per root, from the data file's `descriptions:`.
46
48
  attr_reader descriptions: Hash[String, String]
47
49
 
48
50
  def initialize: (vocabulary_version: Integer, labels: Array[untyped],
@@ -12,17 +12,18 @@ module Rigor
12
12
  # could not finish.
13
13
  def self.tainted: (String cause, ?String? detail) -> Summary
14
14
 
15
- # sig-gen gap: #1154 — `initialize` normalizes the `bundles:`/`declared_bundles:` kwargs through
16
- # `normalize_bundles` (join-merging per origin), so the readers do not trace to the parameters.
15
+ # sig-gen gap: #2312 — `initialize` normalizes the `bundles:`/`declared_bundles:` kwargs through
16
+ # `normalize_bundles` (join-merging per origin), a private helper whose parameters are undeclared.
17
17
  attr_reader bundles: Hash[Origin, LabelSet]
18
- # sig-gen gap: #1154 — as above.
18
+ # sig-gen gap: #2312 — as above.
19
19
  attr_reader declared_bundles: Hash[Origin, LabelSet]
20
20
  # Unmarked residue — `proven`/`declared` are flattened from the bundle tables by `initialize`,
21
- # not assigned from a parameter, so #1154 does not cover them (same shape as
21
+ # not assigned from a parameter at all, so no parameter credit applies (same shape as
22
22
  # `Registry#additional_initializers`).
23
23
  attr_reader declared: LabelSet
24
24
  attr_reader proven: LabelSet
25
- # sig-gen gap: #1154 — `causes` are `[cause, detail]` pairs normalized in `initialize`.
25
+ # sig-gen gap: #2312 — `causes` are `[cause, detail]` pairs normalized by the private `normalize_causes` helper,
26
+ # whose parameters are undeclared.
26
27
  attr_reader causes: Array[[String, String?]]
27
28
 
28
29
  def initialize: (?bundles: Hash[Origin, LabelSet], ?declared_bundles: Hash[Origin, LabelSet],
@@ -31,8 +31,6 @@ module Rigor
31
31
  def nominal_for_name: (String | Symbol name) -> Type::Nominal?
32
32
  def singleton_for_name: (String | Symbol name) -> Type::Singleton?
33
33
  def constant_for_name: (String | Symbol name) -> Type::t?
34
- # sig-gen gap: #1154 — the answer comes through `rbs_loader`, an `attr_reader` over an ivar assigned in
35
- # `initialize` from a parameter, which reaches `untyped`.
36
34
  def global_for_name: (String | Symbol name, ?builtin: bool) -> Type::t?
37
35
  def class_known?: (String | Symbol name) -> bool
38
36
  def rbs_module?: (String | Symbol name) -> bool
@@ -57,7 +55,8 @@ module Rigor
57
55
  attr_reader libraries: Array[String]
58
56
  attr_reader signature_paths: Array[String | _ToPath]
59
57
  attr_reader cache_store: untyped?
60
- # sig-gen gap: #160 — sig-gen emits no `attr_reader` for the loader's readers; typed as the initializer stores it.
58
+ # sig-gen gap: #160 — sig-gen emits no `attr_reader` for the loader's readers; typed as the initializer stores
59
+ # it.
61
60
  attr_reader deferred_signature_paths: Array[Pathname]
62
61
 
63
62
  def self.default: () -> RbsLoader
@@ -82,6 +81,9 @@ module Rigor
82
81
  def singleton_extended_module_names: (String | Symbol class_name) -> Array[String]
83
82
  def constant_type: (String name) -> Type::t?
84
83
  def constant_names: () -> Array[String]
84
+ # sig-gen gap: #2326 — the answer is `global_declarations[name.to_sym]&.first`, a lookup in the loader's memo
85
+ # hash `@state`, which sig-gen skips as untyped.
86
+ def global_type: (String | Symbol name) -> Type::t?
85
87
  def each_known_class_name: () { (String name) -> void } -> void
86
88
  | () -> ::Enumerator[String, void]
87
89
  end