rigortype 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -3,8 +3,10 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../environment"
6
+ require_relative "../project_environment"
6
7
  require_relative "../scope"
7
8
  require_relative "../type"
9
+ require_relative "../source/constant_path"
8
10
  require_relative "../source/literals"
9
11
  require_relative "../source/node_children"
10
12
  require_relative "../inference/scope_indexer"
@@ -18,7 +20,7 @@ module Rigor
18
20
  # for every `Prism::CallNode` whose receiver types as a `Type::Nominal`. The {Generator} consumes the
19
21
  # resulting map to render `--params=observed` RBS:
20
22
  #
21
- # @return [Hash{[class_name, method_name] => Array<Array<Rigor::Type>>}]
23
+ # @return => Array<Array<Rigor::Type>>}]
22
24
  #
23
25
  # ADR-5 clause 2 compliance: the observed union is the MOST PERMISSIVE parameter contract the existing
24
26
  # callers prove sufficient — by construction it accepts every type any caller has already passed. The
@@ -32,10 +34,9 @@ module Rigor
32
34
  # the collector cannot attribute them to a specific class.
33
35
  # - Zero-argument calls give no observation; methods are matched by `(class_name, method_name)` only.
34
36
  class ObservationCollector # rubocop:disable Metrics/ClassLength
35
- # @param configuration [Rigor::Configuration]
36
- # @param paths [Array<String>] observe paths (files /
37
+ # @param paths — observe paths (files /
37
38
  # directories).
38
- # @param source_paths [Array<String>] source-tree paths
39
+ # @param source_paths source-tree paths
39
40
  # (defaults to `configuration.paths`) pre-walked to
40
41
  # register every project-defined class so that calls
41
42
  # like `Foo.new.bar(x)` in the observe tree resolve
@@ -51,10 +52,11 @@ module Rigor
51
52
  def collect
52
53
  return {} if @paths.empty?
53
54
 
54
- environment = build_environment
55
+ resolved = resolve_paths(@paths)
56
+ environment = build_environment(resolved)
55
57
  discovered_classes = preindex_source_classes
56
58
  observations = Hash.new { |h, k| h[k] = [] }
57
- resolve_paths(@paths).each do |path|
59
+ resolved.each do |path|
58
60
  collect_from_file(path, environment, discovered_classes, observations)
59
61
  end
60
62
  observations.transform_values(&:freeze).freeze
@@ -62,11 +64,11 @@ module Rigor
62
64
 
63
65
  private
64
66
 
65
- def build_environment
66
- Environment.for_project(
67
- libraries: @configuration.libraries,
68
- signature_paths: @configuration.signature_paths
69
- )
67
+ # Issue #821 — same universe as the generator and as `rigor check`; see
68
+ # {Generator#build_environment}. An observation collected against a narrower environment types its
69
+ # argument differently from the run that will consume it.
70
+ def build_environment(source_files = [])
71
+ ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
70
72
  end
71
73
 
72
74
  def resolve_paths(args)
@@ -118,11 +120,11 @@ module Rigor
118
120
  return unless node.is_a?(Prism::Node)
119
121
 
120
122
  if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
121
- name = qualified_constant_path(node.constant_path)
122
- if name
123
- full = (prefix + [name]).join("::")
123
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
124
+ if child_prefix
125
+ full = child_prefix.join("::")
124
126
  accumulator[full] = Type::Combinator.singleton_of(full)
125
- walk_class_decls(node.body, prefix + [name], accumulator) if node.body
127
+ walk_class_decls(node.body, child_prefix, accumulator) if node.body
126
128
  return
127
129
  end
128
130
  end
@@ -21,9 +21,8 @@ module Rigor
21
21
  # When the source path is not under any configured source root (e.g. files supplied directly on the CLI
22
22
  # from outside `lib/`), the full relative path is preserved under the sig root.
23
23
  class PathMapper
24
- # @param configuration [Rigor::Configuration]
25
- # @param project_root [String, Pathname] (defaults to `Dir.pwd`)
26
- # @param layout_index [LayoutIndex, nil] optional class
24
+ # @param project_root — (defaults to `Dir.pwd`)
25
+ # @param layout_index optional class
27
26
  # → existing sig file index; routes the target to the
28
27
  # consolidated file when the class is already declared.
29
28
  def initialize(configuration:, project_root: Dir.pwd, layout_index: nil)
@@ -32,12 +31,11 @@ module Rigor
32
31
  @layout_index = layout_index
33
32
  end
34
33
 
35
- # @param source_path [String]
36
- # @param class_name [String, nil] fully-qualified Ruby
34
+ # @param class_name — fully-qualified Ruby
37
35
  # class name. When supplied and matched by the
38
36
  # `LayoutIndex`, the consolidated sig file's path is
39
37
  # returned instead of the 1:1 mirror.
40
- # @return [Pathname] absolute path of the target `.rbs`
38
+ # @return absolute path of the target `.rbs`
41
39
  # file for the candidate.
42
40
  def target_for(source_path, class_name: nil)
43
41
  existing = existing_target_for(class_name)
@@ -23,16 +23,16 @@ module Rigor
23
23
 
24
24
  module_function
25
25
 
26
- # @param line [String] a rendered RBS method line, e.g. `def self.parse: (String) -> Integer`.
27
- # @return [String, nil] the parse error's first line, or nil when the line is valid.
26
+ # @param line a rendered RBS method line, e.g. `def self.parse: (String) -> Integer`.
27
+ # @return the parse error's first line, or nil when the line is valid.
28
28
  def method_line_error(line)
29
29
  return nil if line.nil?
30
30
 
31
31
  source_error("class #{PROBE_CLASS}\n #{line}\nend\n")
32
32
  end
33
33
 
34
- # @param source [String] a complete `.rbs` file's text.
35
- # @return [String, nil] the parse error's first line, or nil when the file is valid.
34
+ # @param source a complete `.rbs` file's text.
35
+ # @return the parse error's first line, or nil when the file is valid.
36
36
  def source_error(source)
37
37
  ::RBS::Parser.parse_signature(::RBS::Buffer.new(name: "(rigor sig-gen)", content: source))
38
38
  nil
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
 
5
5
  require_relative "classification"
6
+ require_relative "superclass_spelling"
6
7
 
7
8
  module Rigor
8
9
  module SigGen
@@ -21,10 +22,8 @@ module Rigor
21
22
  @out = out
22
23
  end
23
24
 
24
- # @param candidates [Array<MethodCandidate>]
25
- # @param mode [:print, :diff]
26
- # @param format [String] "text" or "json"
27
- # @param selection [Array<Symbol>] subset of
25
+ # @param format — "text" or "json"
26
+ # @param selection subset of
28
27
  # {Classification} constants to include; an empty
29
28
  # array means "all emittable classifications".
30
29
  def render(candidates:, mode:, format:, selection:)
@@ -91,7 +90,7 @@ module Rigor
91
90
  return "module #{class_name}" if candidate.namespace_kinds[class_name] == :module
92
91
 
93
92
  superclass = candidate.class_superclasses[class_name]
94
- superclass ? "class #{class_name} < #{superclass}" : "class #{class_name}"
93
+ superclass ? "class #{class_name} < #{SuperclassSpelling.absolute(superclass)}" : "class #{class_name}"
95
94
  end
96
95
 
97
96
  def render_diff(candidates)
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "classification"
4
+
5
+ module Rigor
6
+ module SigGen
7
+ # Answers for the `sig.skipped.*` identifiers `rigor sig-gen` prints when it declines to emit a
8
+ # signature for a method.
9
+ #
10
+ # ADR-108 WD4 — a skip id is an ANSWER ("Rigor cannot prove a type here"), and the command that exists
11
+ # to answer identifiers must be able to answer it. `rigor explain` resolves against
12
+ # {Analysis::RuleCatalog}, which carries diagnostic rules only, so `rigor explain
13
+ # sig.skipped.untyped-return` used to reply `Unknown rule` for an id Rigor had just printed. This is the
14
+ # second catalogue `explain` consults; it is deliberately separate from the rule catalog because a skip
15
+ # reason has no severity, no profile mapping and no suppression, and forcing it into {RuleCatalog::Entry}
16
+ # would mean inventing four fields that do not apply.
17
+ #
18
+ # Every id in {Classification::SKIP_DIAGNOSTIC_IDS} MUST have an entry here; the gate is
19
+ # `spec/rigor/sig_gen/skip_reason_catalog_spec.rb`.
20
+ module SkipReasonCatalog
21
+ class Entry < Data.define(:id, :summary, :explanation, :next_step)
22
+ # Hash-shaped form for `rigor explain --format=json`. String keys, and `kind` distinguishes a skip
23
+ # reason from a rule entry in a stream a consumer may receive either from.
24
+ def to_h
25
+ {
26
+ "id" => id,
27
+ "kind" => "sig_skip_reason",
28
+ "summary" => summary,
29
+ "explanation" => explanation,
30
+ "next_step" => next_step
31
+ }
32
+ end
33
+ end
34
+
35
+ ENTRIES = {
36
+ "sig.skipped.untyped-return" => Entry.new(
37
+ id: "sig.skipped.untyped-return",
38
+ summary: "The method's return types as Dynamic[top], so no signature was written.",
39
+ explanation: "Rigor inferred the body but the value it returns has no proven type — the last " \
40
+ "expression widened to Dynamic[top] somewhere up its chain. Emitting `untyped` here " \
41
+ "would record noise as a contract: the file would gain a signature that says nothing " \
42
+ "and stops a later, better inference from being noticed. This is the commonest skip " \
43
+ "reason on a project whose dependencies have no RBS.",
44
+ next_step: "Do not write the return type by hand — the skip is the finding. Trace the returned " \
45
+ "expression back to where it became dynamic (`rigor annotate PATH` reads the `#=>` " \
46
+ "column, `rigor trace --format=json --line=N` for one line) and close THAT: install or " \
47
+ "write RBS for the dependency, enable the plugin for the framework, or report an " \
48
+ "engine gap."
49
+ ),
50
+ "sig.skipped.user-authored" => Entry.new(
51
+ id: "sig.skipped.user-authored",
52
+ summary: "An RBS declaration for this method already exists and was left alone.",
53
+ explanation: "Not a gap. The project already states a type for this member, and sig-gen does not " \
54
+ "overwrite a hand-authored contract without being told to. Where `sig/` and an inline " \
55
+ "annotation both declare the member, `sig/` wins.",
56
+ next_step: "Read the existing declaration. If it disagrees with what Rigor infers, raise the " \
57
+ "disagreement rather than silently retyping it; pass `--overwrite` only once you have " \
58
+ "decided the generated type is the better contract."
59
+ ),
60
+ "sig.skipped.unrenderable-rbs" => Entry.new(
61
+ id: "sig.skipped.unrenderable-rbs",
62
+ summary: "Rigor rendered a line that `rbs` itself rejects, so the method was dropped.",
63
+ explanation: "A defect in Rigor's renderer, not in your code. The method is skipped rather than " \
64
+ "written so one pathological signature cannot make the whole generated file " \
65
+ "unparseable, and the count is reported so the defect is not silent.",
66
+ next_step: "Report it, with the method and the file. `rigor sig-gen --print PATH` shows what was " \
67
+ "generated for the rest of the file and is the useful attachment."
68
+ ),
69
+ "sig.skipped.complex-shape" => Entry.new(
70
+ id: "sig.skipped.complex-shape",
71
+ summary: "Reserved for a shape too complex to render; no code path produces it today.",
72
+ explanation: "The identifier is part of the reserved `sig.skipped.*` family and is carried so the " \
73
+ "family stays stable, but no generator path emits it.",
74
+ next_step: "If you have actually seen this id in output, that is itself worth reporting — it means " \
75
+ "a path exists that this catalogue does not describe."
76
+ ),
77
+ "sig.skipped.unresolvable-superclass" => Entry.new(
78
+ id: "sig.skipped.unresolvable-superclass",
79
+ summary: "The class's superclass chain does not end in a class the RBS environment knows.",
80
+ explanation: "Writing the declaration would make things worse rather than better: RBS would read " \
81
+ "the class as rooted at an unknown parent and collapse it on the next run, losing the " \
82
+ "members it can resolve today.",
83
+ next_step: "Give the environment the missing ancestor — install or write RBS for the gem that " \
84
+ "defines it, or add the project file that declares it to the analysed paths — then " \
85
+ "re-run `rigor sig-gen`."
86
+ ),
87
+ "sig.skipped.overridden-by-unsigned-subclass" => Entry.new(
88
+ id: "sig.skipped.overridden-by-unsigned-subclass",
89
+ summary: "A subclass overrides this method and its override was not itself emitted.",
90
+ explanation: "A declaration on the parent is inherited by every subclass that does not redeclare " \
91
+ "the member. Emitting it here would hand the subclass a type describing the parent's " \
92
+ "body, not the override the subclass actually runs.",
93
+ next_step: "Generate signatures for the overriding subclass too — widen the `sig-gen` path set to " \
94
+ "cover it — and the parent's declaration becomes safe to emit."
95
+ )
96
+ }.freeze
97
+
98
+ module_function
99
+
100
+ # @param token — a `sig.skipped.*` identifier.
101
+ # @return the matching entry, or nil when the token is not a known skip reason.
102
+ def resolve(token)
103
+ ENTRIES[token.to_s]
104
+ end
105
+
106
+ def all
107
+ ENTRIES.values
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module SigGen
5
+ # Renders a recorded superclass token into the spelling that goes on an emitted `class` header.
6
+ #
7
+ # Issue [#609](https://github.com/rigortype/rigor/issues/609): every token that survives
8
+ # {Generator#demote_unresolvable_superclasses} names a class the RBS environment or this run declares at an
9
+ # ABSOLUTE position — both guards ask `Reflection.rbs_class_known?` and the emitted-name set, and both are
10
+ # keyed by fully qualified name. The writer then emitted that token bare inside a nested `module` tree, where
11
+ # RBS resolves its first segment relatively against the enclosing namespaces, so `class Show < Help::Show`
12
+ # written under `module Views; module Help` re-points at `::Views::Help::Show` — the class being declared.
13
+ # The self-referential ancestry is what made `AncestorBuilder#singleton_ancestors` recurse until the stack
14
+ # was exhausted on the reporter's project, and what `RBS::RecursiveAncestorError` collapses the class over on
15
+ # a smaller one. Anchoring the token with `::` says the name the guards already checked.
16
+ module SuperclassSpelling
17
+ module_function
18
+
19
+ # @param token — a recorded superclass token, possibly carrying type arguments (`Foo::Bar[untyped]`).
20
+ def absolute(token)
21
+ return token if token.nil? || token.start_with?("::")
22
+
23
+ "::#{token}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -20,9 +20,7 @@ module Rigor
20
20
  # The module is sig-gen-local; the broader question of whether the inference engine itself should always
21
21
  # construct generics with explicit type_args is queued as an ADR-14 follow-up.
22
22
  module TypeElaborator
23
- # @param type [Rigor::Type]
24
- # @param environment [Rigor::Environment]
25
- # @return [Rigor::Type] same shape with bare generic
23
+ # @return same shape with bare generic
26
24
  # nominals filled in.
27
25
  def self.elaborate(type, environment:)
28
26
  arity_cache = {}
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "rbs"
5
5
 
6
6
  require_relative "classification"
7
+ require_relative "superclass_spelling"
7
8
  require_relative "write_result"
8
9
 
9
10
  module Rigor
@@ -50,8 +51,7 @@ module Rigor
50
51
  # ADR-14 follow-up: this is the consolidated-layout entry point. The legacy `write(source_path,
51
52
  # candidates)` below assumes all candidates share a target and remains for spec convenience.
52
53
  #
53
- # @param candidates [Array<MethodCandidate>]
54
- # @return [Array<WriteResult>] one per target sig file.
54
+ # @return one per target sig file.
55
55
  def write_all(candidates)
56
56
  emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
57
57
  return [] if emittable.empty?
@@ -62,12 +62,10 @@ module Rigor
62
62
  .map { |target, group| write_target(target, group) }
63
63
  end
64
64
 
65
- # @param source_path [String]
66
- # @param candidates [Array<MethodCandidate>] only
65
+ # @param candidates — only
67
66
  # emittable classifications (new-method /
68
67
  # tighter-return) are honoured; the caller is
69
68
  # responsible for filtering.
70
- # @return [WriteResult]
71
69
  def write(source_path, candidates)
72
70
  emittable = candidates.select { |c| EMITTABLE.include?(c.classification) }
73
71
  return WriteResult.new(source_path: source_path, target_path: nil, action: :noop) if emittable.empty?
@@ -261,7 +259,7 @@ module Rigor
261
259
  return "" unless keyword == :class
262
260
 
263
261
  superclass = supers[qualified]
264
- superclass ? " < #{superclass}" : ""
262
+ superclass ? " < #{SuperclassSpelling.absolute(superclass)}" : ""
265
263
  end
266
264
 
267
265
  # Per ADR-14 gap-#3 (a) the keyword for a segment comes from `namespace_kinds` when known. The default for
@@ -660,6 +658,8 @@ module Rigor
660
658
  end
661
659
 
662
660
  def eligible_for_replacement?(candidate, decl, source)
661
+ return false if replaces_declared_void?(candidate, decl)
662
+
663
663
  case candidate.classification
664
664
  when Classification::TIGHTER_RETURN then true
665
665
  when Classification::NEW_METHOD then tightens_untyped?(candidate, decl, source)
@@ -667,6 +667,23 @@ module Rigor
667
667
  end
668
668
  end
669
669
 
670
+ # Issue #836 — `--overwrite` never turns a declared `-> void` into a value return. The generator no
671
+ # longer proposes one (`Generator#declares_void?` classifies the method `equivalent`), but the writer
672
+ # reaches the same member through case 2 above whenever the RBS environment did not resolve the `def` to
673
+ # this declaration: an observed-parameter upgrade of `(untyped m) -> void` to `(Module m) -> Registry`
674
+ # counts one `untyped` fewer and would be applied, erasing the author's statement that the return is not
675
+ # part of the contract. A candidate that keeps `void` is still eligible, which is what case 2 is for —
676
+ # the `initialize` stub is always spelled `-> void`.
677
+ def replaces_declared_void?(candidate, decl)
678
+ member = find_method_member(decl, candidate.method_name, candidate.kind)
679
+ return false if member.nil?
680
+
681
+ declared_void = member.overloads.any? do |overload|
682
+ overload.method_type.type.return_type.is_a?(RBS::Types::Bases::Void)
683
+ end
684
+ declared_void && !candidate.rbs.to_s.end_with?(" -> void")
685
+ end
686
+
670
687
  # Compares the existing member's source-side RBS text against the candidate's proposed RBS text. Returns
671
688
  # true when the new spelling has STRICTLY FEWER bare `untyped` tokens than the existing one — i.e. at
672
689
  # least one `untyped` slot becomes a concrete type AND no concrete slot becomes `untyped`. Word-boundary
@@ -16,7 +16,24 @@ module Rigor
16
16
  # The audit deliberately mirrors the loader's own acceptance test (`path.directory?` + a
17
17
  # recursive `**/*.rbs` glob) so a `:ok` verdict means the loader did load from it and a
18
18
  # warning means it did not.
19
+ #
20
+ # {.bundled_plugin_routes} answers the mirror-image question — an entry that loads too
21
+ # *little of the plugin*, rather than nothing at all. See its own comment.
19
22
  module SignaturePathAudit
23
+ # The `plugins/` tree of the engine running this code, anchored from THIS file
24
+ # (`<root>/lib/rigor/signature_path_audit.rb`) so a git checkout and an installed
25
+ # `rigortype` gem answer identically — the anchor {Plugin::Loader::ENGINE_ROOT} uses, for
26
+ # the reason it documents.
27
+ #
28
+ # Deliberately re-derived rather than read off `Plugin::Loader`: ADR-87 WD4 keeps this
29
+ # file, which every `rigor check` loads before the cache probe, off the plugin
30
+ # subsystem's require graph — and that graph is order-dependent (`plugin/registry.rb`
31
+ # resolves `NodeRuleWalk` at load, which only `rigor/plugin.rb` has required by then), so
32
+ # requiring the loader from here fails outright. `signature_path_audit_spec` pins the two
33
+ # anchors equal, and pins this discovery against `Loader.bundled_plugin_sig_path`, so the
34
+ # duplication cannot drift unnoticed.
35
+ BUNDLED_PLUGINS_ROOT = File.expand_path("../../plugins", __dir__)
36
+
20
37
  # One configured `signature_paths:` entry's resolution status.
21
38
  #
22
39
  # `status` is one of:
@@ -60,17 +77,11 @@ module Rigor
60
77
  # `nil` — the unset default, where Rigor auto-detects `<root>/sig` — to get an empty
61
78
  # result: an absent auto-detected `sig/` is a normal setup, not a misconfiguration, so it
62
79
  # is never audited.
63
- #
64
- # @param signature_paths [Array<String, Pathname>, nil]
65
- # @return [Array<Entry>]
66
80
  def self.audit(signature_paths)
67
81
  Array(signature_paths).map { |path| classify(path.to_s) }
68
82
  end
69
83
 
70
84
  # The subset of {audit} that resolved to nothing — the entries worth warning about.
71
- #
72
- # @param signature_paths [Array<String, Pathname>, nil]
73
- # @return [Array<Entry>]
74
85
  def self.warnings(signature_paths)
75
86
  audit(signature_paths).select(&:warning?)
76
87
  end
@@ -82,5 +93,141 @@ module Rigor
82
93
  count = Dir.glob(File.join(path, "**", "*.rbs")).size
83
94
  Entry.new(path: path, status: count.zero? ? :empty : :ok, rbs_file_count: count)
84
95
  end
96
+
97
+ # One configured entry that reaches the `sig/` of a plugin THIS engine bundles, while
98
+ # `plugins:` never names that plugin (issue #697).
99
+ BundledPluginRoute = Data.define(:path, :gem) do
100
+ # Names the plugin and the one edit that fixes it. The consequence is spelled out
101
+ # because the symptom the user actually sees is `call.undefined-method` on code that
102
+ # runs — nothing in that row points back at the `signature_paths:` line.
103
+ def message
104
+ "signature_paths: #{path.inspect} loads the signatures of the bundled plugin #{gem.inspect}, " \
105
+ "which `plugins:` does not name — so the plugin's manifest never loads, and the classes it " \
106
+ "declares only partially on purpose (ADR-26 `open_receivers:`) are read as complete, which " \
107
+ "reports methods your code really defines as undefined. " \
108
+ "Add #{gem.inspect} to `plugins:` instead of naming its `sig/` in `signature_paths:`."
109
+ end
110
+
111
+ def to_h
112
+ { "path" => path, "gem" => gem, "message" => message }
113
+ end
114
+ end
115
+
116
+ # The mirror image of {.warnings}: an entry the loader reads perfectly well, which
117
+ # nevertheless leaves the user worse off than not configuring it.
118
+ #
119
+ # A bundled plugin ships two halves that only work together — the RBS under its `sig/`,
120
+ # and the manifest that says which of those declarations are deliberately partial
121
+ # (ADR-26 `open_receivers:`). `plugins:` loads both; `signature_paths:` loads only the
122
+ # first, and the partial declarations then read as complete, so a scope the application
123
+ # really defines draws `call.undefined-method`. This is a configuration mistake with no
124
+ # visible cause, so it is reported here rather than fixed by teaching the check rules yet
125
+ # another route to the same protection — which is [#660](https://github.com/rigortype/rigor/issues/660)'s
126
+ # question to settle, not this warning's.
127
+ #
128
+ # **What it asks is what the entry LOADS, not what it looks like**, exactly as
129
+ # `Environment::RbsLoader.bundled_overlay_twin_signatures_loaded?` does for the overlay
130
+ # stand-down: both sides are `File.realpath`'d and matched against the plugin's actual
131
+ # `.rbs` files, so a symlink or a case-variant spelling answers the same as the directory
132
+ # itself, and an entry naming a file (or a directory that does not exist) — which the
133
+ # loader reads nothing from — matches nothing. The two are not single-homed because the
134
+ # overlay-side predicate lives on the RBS loader, and ADR-87 WD4 keeps this file, which
135
+ # every `rigor check` loads before the cache probe, off the engine's require graph.
136
+ #
137
+ # @param signature_paths — {Configuration#signature_paths}; `nil` (the unset default,
138
+ # where Rigor auto-detects `<root>/sig`) is never audited.
139
+ # @param plugins — the raw {Configuration#plugins} list.
140
+ def self.bundled_plugin_routes(signature_paths, plugins = [])
141
+ entries = resolved_entries(signature_paths)
142
+ return [] if entries.empty?
143
+
144
+ root = canonical_path(BUNDLED_PLUGINS_ROOT)
145
+ return [] if root.nil? || entries.none? { |(_, dir)| overlapping?(dir, root) }
146
+
147
+ listed = listed_plugin_names(plugins)
148
+ bundled_plugin_sig_dirs.reject { |gem, _| listed?(gem, listed) }.flat_map do |gem, sig_dir|
149
+ files = canonical_rbs_files(sig_dir)
150
+ next [] if files.empty?
151
+
152
+ entries.filter_map do |(entry, dir)|
153
+ BundledPluginRoute.new(path: entry, gem: gem) if files.any? { |file| under?(file, dir) }
154
+ end
155
+ end
156
+ end
157
+
158
+ # `[entry as configured, canonical directory]` for every entry the loader would read from.
159
+ def self.resolved_entries(signature_paths)
160
+ Array(signature_paths).filter_map do |entry|
161
+ dir = canonical_path(entry.to_s)
162
+ [entry.to_s, dir] if dir && File.directory?(dir)
163
+ end
164
+ end
165
+ private_class_method :resolved_entries
166
+
167
+ # Gem name of every plugin the engine bundles that ships signatures, to its `sig/`.
168
+ # Derived from the `plugins/` tree rather than listed, for {Plugin::FirstParty}'s reason:
169
+ # a list would be a second source of truth, and its first drift would silently stop
170
+ # warning about the plugin that drifted.
171
+ def self.bundled_plugin_sig_dirs
172
+ return {} unless File.directory?(BUNDLED_PLUGINS_ROOT)
173
+
174
+ Dir.children(BUNDLED_PLUGINS_ROOT).sort.filter_map do |gem|
175
+ sig = File.join(BUNDLED_PLUGINS_ROOT, gem, "sig")
176
+ [gem, sig] if File.directory?(sig)
177
+ end.to_h
178
+ end
179
+
180
+ # Every name a `plugins:` entry puts on the record, in both the bare-string and the
181
+ # `gem:` / `id:` hash forms. An `enabled: false` entry counts as named even though it
182
+ # loads nothing: the user has demonstrably found the plugin, and telling them to add what
183
+ # is already there would be worse than staying quiet. Under-warning is this module's
184
+ # standing FP-safe direction.
185
+ def self.listed_plugin_names(plugins)
186
+ Array(plugins).flat_map do |raw|
187
+ case raw
188
+ when String then [raw]
189
+ when Hash
190
+ keyed = raw.to_h { |key, value| [key.to_s, value] }
191
+ [keyed["gem"], keyed["id"]].compact.map(&:to_s)
192
+ else []
193
+ end
194
+ end.to_set
195
+ end
196
+ private_class_method :listed_plugin_names
197
+
198
+ # A bundled plugin's manifest id is its gem name minus the `rigor-` prefix, and `plugins:`
199
+ # accepts either spelling as `id:`, so both count as naming it.
200
+ def self.listed?(gem, listed)
201
+ require_relative "plugin/first_party"
202
+ listed.include?(gem) || listed.include?(gem.delete_prefix(Plugin::FirstParty::GEM_PREFIX))
203
+ end
204
+ private_class_method :listed?
205
+
206
+ def self.canonical_rbs_files(dir)
207
+ Dir.glob(File.join(dir, "**", "*.rbs")).filter_map { |file| canonical_path(file) }
208
+ end
209
+ private_class_method :canonical_rbs_files
210
+
211
+ # `File.realpath`, not `File.expand_path`: it resolves symlinks and, on a case-folding
212
+ # filesystem, the on-disk spelling. It raises for a path that does not exist, which is
213
+ # the answer wanted here — a path the loader cannot read is not a route to anything.
214
+ def self.canonical_path(path)
215
+ File.realpath(path.to_s)
216
+ rescue SystemCallError
217
+ nil
218
+ end
219
+ private_class_method :canonical_path
220
+
221
+ def self.under?(path, dir)
222
+ path.start_with?("#{dir}#{File::SEPARATOR}")
223
+ end
224
+ private_class_method :under?
225
+
226
+ # Whether the two directories could possibly share a file. The cheap prune that keeps the
227
+ # per-plugin globs off every run whose `signature_paths:` point at the project's own tree.
228
+ def self.overlapping?(one, other)
229
+ one == other || under?(one, other) || under?(other, one)
230
+ end
231
+ private_class_method :overlapping?
85
232
  end
86
233
  end
@@ -31,9 +31,6 @@ module Rigor
31
31
 
32
32
  # The Symbol a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
33
33
  # (including `nil`).
34
- #
35
- # @param node [Prism::Node, nil]
36
- # @return [Symbol, nil]
37
34
  def symbol_or_string(node)
38
35
  return nil unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
39
36
 
@@ -44,9 +41,6 @@ module Rigor
44
41
  # (including `nil`). The String-returning sibling of {.symbol_or_string} — for callers that key on the
45
42
  # raw name rather than the interned Symbol (route helpers, factory names, filter targets).
46
43
  # `#unescaped` round-trips an interpolation-free `:foo` / `"foo"` to `"foo"` for both kinds.
47
- #
48
- # @param node [Prism::Node, nil]
49
- # @return [String, nil]
50
44
  def symbol_or_string_name(node)
51
45
  return nil unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
52
46
 
@@ -56,9 +50,6 @@ module Rigor
56
50
  # The Symbol a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
57
51
  # `Prism::StringNode` and `nil`). Stricter than {.symbol_or_string}: a DSL that accepts only `:draft`
58
52
  # and not `"draft"` keeps that distinction by reaching for this rather than the Symbol-or-String form.
59
- #
60
- # @param node [Prism::Node, nil]
61
- # @return [Symbol, nil]
62
53
  def symbol(node)
63
54
  return nil unless node.is_a?(Prism::SymbolNode)
64
55
 
@@ -68,9 +59,6 @@ module Rigor
68
59
  # The String a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
69
60
  # `Prism::StringNode` and `nil`). The String-returning sibling of {.symbol} — SymbolNode-only, but the
70
61
  # caller wants the raw name rather than the interned Symbol.
71
- #
72
- # @param node [Prism::Node, nil]
73
- # @return [String, nil]
74
62
  def symbol_name(node)
75
63
  return nil unless node.is_a?(Prism::SymbolNode)
76
64
 
@@ -79,9 +67,6 @@ module Rigor
79
67
 
80
68
  # Every literal Symbol/String positional argument of a call, in source order. Non-literal arguments
81
69
  # are dropped. Returns `[]` when the call has no argument list.
82
- #
83
- # @param call_node [Prism::CallNode, nil]
84
- # @return [Array<Symbol>]
85
70
  def symbol_arguments(call_node)
86
71
  args = call_node&.arguments&.arguments
87
72
  return [] if args.nil?
@@ -93,20 +78,12 @@ module Rigor
93
78
  # {.symbol_name} — for callers that need a predicate rather than an extraction (hash-key matching in
94
79
  # keyword or assoc argument positions, e.g. `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
95
80
  # Uses `#unescaped` (not `#value`) for round-trip consistency.
96
- #
97
- # @param node [Prism::Node, nil]
98
- # @param name [String]
99
- # @return [Boolean]
100
81
  def symbol_named?(node, name)
101
82
  node.is_a?(Prism::SymbolNode) && node.unescaped == name
102
83
  end
103
84
 
104
85
  # The literal Symbol/String at positional `index`, or `nil` when the call has no argument list, the
105
86
  # index is out of range, or the argument there is not a literal Symbol/String.
106
- #
107
- # @param call_node [Prism::CallNode, nil]
108
- # @param index [Integer]
109
- # @return [Symbol, nil]
110
87
  def symbol_arg(call_node, index)
111
88
  args = call_node&.arguments&.arguments
112
89
  return nil if args.nil?
@@ -106,8 +106,6 @@ module Rigor
106
106
  # e.g. `node.body`). Pure and re-entrant; `break` / `next` / `return` in the block behave exactly as they
107
107
  # would with `compact_child_nodes.each`. Walkers holding a known `Prism::Node` call `#rigor_each_child`
108
108
  # directly; this wrapper is the nil-tolerant entry.
109
- #
110
- # @yieldparam child [Prism::Node]
111
109
  def each_child(node, &)
112
110
  node.rigor_each_child(&) if node.is_a?(Prism::Node)
113
111
  end