rigortype 0.3.7 → 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 (450) 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 +96 -15
  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 +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  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 +69 -13
  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 +100 -29
  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 +316 -40
  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 +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  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 +166 -30
  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 +70 -0
  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_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -4,9 +4,11 @@ require "prism"
4
4
 
5
5
  require_relative "../configuration"
6
6
  require_relative "../environment"
7
+ require_relative "../project_environment"
7
8
  require_relative "../scope"
8
9
  require_relative "../reflection"
9
10
  require_relative "../type"
11
+ require_relative "../source/constant_path"
10
12
  require_relative "../source/literals"
11
13
  require_relative "../source/node_children"
12
14
  require_relative "../inference/def_return_typer"
@@ -24,28 +26,38 @@ module Rigor
24
26
  # body's last expression to derive an inferred return, looks up the project's existing RBS declaration (if
25
27
  # any), and emits one {MethodCandidate} per def.
26
28
  #
27
- # The MVP keeps the scope deliberately narrow:
28
- # - Only instance methods inside a `class` / `module` body are considered. Top-level / DSL-block / singleton
29
- # defs are skipped (`sig.skipped.complex-shape`).
30
- # - Parameter signatures are hard-coded to `untyped` per ADR-14 § "Robustness principle compliance" clause 2;
31
- # `--params=observed` arrives in slice 3.
32
- # - Optional / rest / keyword / block params disqualify the def (`sig.skipped.complex-shape`).
29
+ # The scope, as it stands after the slices that widened the MVP:
30
+ # - Instance and singleton methods inside a nameable `class` / `module` body are considered. Top-level /
31
+ # DSL-block defs have no nameable receiver and are not candidates.
32
+ # - Parameter types are `untyped` per ADR-14 § "Robustness principle compliance" clause 2 unless
33
+ # `--params=observed` supplies call-site observations; the parameter LIST always mirrors the def's runtime
34
+ # shape (required / optional / rest / trailing / keyword / keyword-rest / forwarding / block). Issue #778
35
+ # retired the slice-1 gate that skipped every shape beyond required positionals as
36
+ # `sig.skipped.complex-shape`: the body typer binds every parameter to `untyped` when no RBS declares the
37
+ # method, so the inferred return is the same clause-1 answer for every shape, and the renderer the
38
+ # `initialize` stub already used spells them all.
33
39
  # - A `Dynamic[top]` inferred return becomes `sig.skipped.untyped-return` — emitting `untyped` would obscure
34
40
  # rather than help.
35
41
  # - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
36
42
  # strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
37
43
  # reserves the eventual `:strict` mode).
44
+ # - A declared `void` return is never compared as a value type. It is the author's statement that the
45
+ # return is not part of the contract, which no inference can synthesize, so the method classifies
46
+ # `equivalent` carrying `void` itself as the declared spelling (#836; see {#declares_void?}).
47
+ # - A proposal that erases to an RBS literal never tightens an existing declaration: the declared type is
48
+ # the author's abstraction over the body and the literal is what it hides (#837; see
49
+ # {#pins_literal_over_declaration?}). A method with no declaration is unaffected — clause 1 still emits
50
+ # the strictest carrier the body proves.
38
51
  class Generator # rubocop:disable Metrics/ClassLength
39
52
  # Methods the generator rendered into RBS that `rbs` itself rejects. Populated by {#build_candidate}; each
40
53
  # one is a Rigor rendering DEFECT, not a property of the user's code, so the CLI reports them as such.
41
54
  UnrenderableMethod = Data.define(:path, :class_name, :method_name, :rbs, :error)
42
55
 
43
- # @return [Array<UnrenderableMethod>] empty on a healthy run; read after {#run}.
56
+ # @return empty on a healthy run; read after {#run}.
44
57
  attr_reader :unrenderable, :unresolvable_superclasses
45
58
 
46
- # @param configuration [Rigor::Configuration]
47
- # @param paths [Array<String>] files / directories to scan.
48
- # @param observations [Hash{[String, Symbol] => Array<Array<Rigor::Type>>}]
59
+ # @param paths — files / directories to scan.
60
+ # @param observations — => Array<Array<Rigor::Type>>}]
49
61
  # ADR-14 slice 3 — per-target-method arg-tuple observations
50
62
  # produced by {ObservationCollector}. An empty Hash (the default)
51
63
  # means "no observations available; emit `untyped` for every
@@ -80,10 +92,9 @@ module Rigor
80
92
  map.transform_values { |entries| entries.map { |entry| ObservedCall.from(entry) } }
81
93
  end
82
94
 
83
- # @return [Array<MethodCandidate>]
84
95
  def run
85
- @environment = build_environment
86
96
  resolved = resolve_paths(@paths)
97
+ @environment = build_environment(resolved)
87
98
  candidates = resolved.flat_map { |path| analyse_file(path, @environment) }
88
99
  demote_overridden_base_methods(
89
100
  demote_unresolvable_superclasses(resolve_superclass_spellings(candidates))
@@ -307,11 +318,13 @@ module Rigor
307
318
  )
308
319
  end
309
320
 
310
- def build_environment
311
- Environment.for_project(
312
- libraries: @configuration.libraries,
313
- signature_paths: @configuration.signature_paths
314
- )
321
+ # Issue #821 — the SAME type universe `rigor check` analyses against: the project's `sig/`, the plugins,
322
+ # the bundle's per-gem signatures and the `rbs collection`. Built with two keywords only, the generator
323
+ # could not see a gem-declared superclass, and the skip guard above (`record_unresolvable_superclass`)
324
+ # then declined to emit every class descending from one — every Rails model, on a project whose
325
+ # collection was installed and whose `check` read it.
326
+ def build_environment(source_files = [])
327
+ ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
315
328
  end
316
329
 
317
330
  def resolve_paths(args)
@@ -409,14 +422,22 @@ module Rigor
409
422
  end
410
423
  end
411
424
 
425
+ # [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
426
+ # `Source::ConstantPath.declaration_prefix`, the same reader the engine's declaration walks use, so a
427
+ # ROOTED header re-anchors here too. Appending the rendered name to the enclosing prefix wrote
428
+ # `class ::Foo` inside `module MyApp` out as `MyApp::Foo` — sig-gen's own copy of #708, and the one
429
+ # copy that persists its answer into a file the user keeps.
430
+ #
431
+ # `prefix` stays the nesting the header is WRITTEN in for {#record_superclass}: `::` re-anchors the
432
+ # declaration, not the superclass expression beside it, which Ruby still evaluates in the enclosing cref.
412
433
  def descend_into_namespace?(node, prefix, out)
413
- name = qualified_constant_path(node.constant_path)
414
- return false unless name
434
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
435
+ return false unless child_prefix
415
436
 
416
- full = (prefix + [name]).join("::")
437
+ full = child_prefix.join("::")
417
438
  @namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
418
439
  record_superclass(node, full, prefix)
419
- walk_namespace_body(node, prefix + [name], out)
440
+ walk_namespace_body(node, child_prefix, out)
420
441
  true
421
442
  end
422
443
 
@@ -650,8 +671,8 @@ module Rigor
650
671
  end
651
672
 
652
673
  # Emits `def initialize: (<shape>) -> void`. The return is always `void` because Ruby's `initialize`
653
- # return value is never meaningful. The parameter list mirrors the runtime shape (required / optional /
654
- # rest / keyword / keyword-rest / block).
674
+ # return value is never meaningful. The parameter list mirrors the runtime shape through the same
675
+ # {#render_param_list} every other def renders with.
655
676
  #
656
677
  # When `--params=observed` populates `@observations` for `[class_name, :initialize]` (via the
657
678
  # `ObservationCollector`'s `.new` → `:initialize` routing), positional and keyword arg types come from the
@@ -659,7 +680,7 @@ module Rigor
659
680
  # clause 2.
660
681
  def initialize_stub_candidate(path, def_node, class_name)
661
682
  params = def_node.parameters
662
- rbs = "def initialize: (#{render_initialize_param_list(params, class_name)})" \
683
+ rbs = "def initialize: (#{render_param_list(params, class_name, :initialize)})" \
663
684
  "#{block_signature_suffix(params)} -> void"
664
685
  build_candidate(
665
686
  path: path, class_name: class_name, method_name: :initialize,
@@ -668,57 +689,81 @@ module Rigor
668
689
  )
669
690
  end
670
691
 
671
- def render_initialize_param_list(params, class_name)
692
+ # The RBS parameter list of a def, mirroring its runtime shape position by position: `untyped` per
693
+ # required positional, `?untyped` per optional, `*untyped` for a rest, one `untyped` per trailing
694
+ # positional after the rest, `name: untyped` / `?name: untyped` per keyword, `**untyped` for a keyword
695
+ # rest, and `*untyped, **untyped` for `...` forwarding (its block half is {#block_signature_suffix}'s).
696
+ # `**nil` declares that no keywords are accepted, which RBS cannot spell, so it renders nothing.
697
+ #
698
+ # Per ADR-5 clause 2 every position is `untyped` unless `--params=observed` supplied call-site
699
+ # observations, in which case each leading positional and each keyword carries the union of the types the
700
+ # arity-compatible observations passed there ({#arity_matched_observations}). The rest, the trailing
701
+ # positionals and the keyword rest stay `untyped`: an observation does not say which of its arguments the
702
+ # splat absorbed. Before #778 only `initialize` rendered through this path and every other def with a
703
+ # shape beyond required positionals was skipped as `sig.skipped.complex-shape`.
704
+ def render_param_list(params, class_name, method_name)
672
705
  return "" unless params.is_a?(Prism::ParametersNode)
673
706
 
674
- observations = initialize_observations(class_name, params)
675
- offset = 0
707
+ observations = arity_matched_observations(class_name, method_name, params)
676
708
  parts = []
677
-
678
- params.requireds.each_with_index do |_, i|
679
- parts << initialize_positional_type(observations, offset + i, "")
680
- end
681
- offset += params.requireds.size
682
-
683
- params.optionals.each_with_index do |_, i|
684
- parts << initialize_positional_type(observations, offset + i, "?")
685
- end
686
-
709
+ params.requireds.each_index { |i| parts << positional_type(observations, i, "") }
710
+ offset = params.requireds.size
711
+ params.optionals.each_index { |i| parts << positional_type(observations, offset + i, "?") }
687
712
  parts << "*untyped" if params.rest
713
+ parts.concat(Array.new(params.posts.size, "untyped"))
688
714
  params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
689
- parts << "**untyped" if params.keyword_rest
715
+ parts.concat(keyword_rest_parts(params))
690
716
  parts.join(", ")
691
717
  end
692
718
 
719
+ def keyword_rest_parts(params)
720
+ case params.keyword_rest
721
+ when Prism::ForwardingParameterNode
722
+ # `...` forwards positionals as well as keywords; a rest already rendered covers the former.
723
+ params.rest ? ["**untyped"] : ["*untyped", "**untyped"]
724
+ when Prism::KeywordRestParameterNode then ["**untyped"]
725
+ else []
726
+ end
727
+ end
728
+
693
729
  # The RBS block suffix for a `def` that takes a `&block` parameter, e.g. ` ?{ (*untyped) -> untyped }`.
694
730
  # A block belongs AFTER the parameter parens in RBS (`(params) ?{ block } -> ret`), not inside them —
695
731
  # emitting it as a comma-joined member produced `(**untyped, ?{ (?) -> void })`, which RBS rejects
696
732
  # (`optional keyword argument type is expected`) and which then collapsed the whole env build. sig-gen
697
733
  # never observes the block's own signature, so it is rendered maximally lenient (ADR-5): an optional
698
- # block (`?{`, since a `&block` need not be passed) taking `*untyped` and returning `untyped`. Returns
699
- # "" when the method takes no block.
734
+ # block (`?{`, since a `&block` need not be passed) taking `*untyped` and returning `untyped`. `...`
735
+ # forwards a block too and gets the same suffix. Returns "" when the method takes no block.
700
736
  def block_signature_suffix(params)
701
737
  return "" unless params.is_a?(Prism::ParametersNode)
702
- return "" if params.block.nil?
738
+ return "" unless params.block || params.keyword_rest.is_a?(Prism::ForwardingParameterNode)
703
739
 
704
740
  " ?{ (*untyped) -> untyped }"
705
741
  end
706
742
 
707
- # Picks observations under `[class_name, :initialize]` whose positional arity matches the def's accepted
708
- # range (required..required+optional). Looser arities don't get used because they describe a different
709
- # overload the stub cannot express.
710
- def initialize_observations(class_name, params)
743
+ # Observations under `[class_name, method_name]` whose positional arity the def accepts: at least its
744
+ # required count (leading plus trailing), and, unless a rest or `...` absorbs the surplus, at most
745
+ # required plus optional. An arity outside that window describes a different overload the signature
746
+ # cannot express, so it says nothing about these positions. #778 widened the window for ordinary defs
747
+ # from the exact required count — `def optional(text = "x")` called as `optional` AND as `optional("y")`
748
+ # now credits both call sites.
749
+ def arity_matched_observations(class_name, method_name, params)
711
750
  return [] if @observations.empty?
712
751
 
713
- list = @observations[[class_name, :initialize]] || []
714
- min = params.requireds.size
715
- max = min + params.optionals.size
716
- list.select { |obs| (min..max).cover?(obs.positional.size) }
752
+ list = @observations[[class_name, method_name]] || []
753
+ min = params.requireds.size + params.posts.size
754
+ max = unbounded_positionals?(params) ? Float::INFINITY : min + params.optionals.size
755
+ list.select { |obs| obs.positional.size.between?(min, max) }
756
+ end
757
+
758
+ def unbounded_positionals?(params)
759
+ !params.rest.nil? || params.keyword_rest.is_a?(Prism::ForwardingParameterNode)
717
760
  end
718
761
 
719
- def initialize_positional_type(observations, index, prefix)
762
+ # A bare union is a valid positional type in RBS (`(String | Integer, ?String | Integer)`), so unlike the
763
+ # return position it is not parenthesised.
764
+ def positional_type(observations, index, prefix)
720
765
  types = observations.filter_map { |obs| obs.positional[index] }
721
- "#{prefix}#{types.empty? ? 'untyped' : paren_wrap_union(union_erase(types))}"
766
+ "#{prefix}#{union_erase(types)}"
722
767
  end
723
768
 
724
769
  def render_keyword_param(keyword, observations)
@@ -746,10 +791,6 @@ module Rigor
746
791
  return nil if initialize_excludes?(def_node, kind)
747
792
  return initialize_stub_candidate(path, def_node, class_name) if non_trivial_initialize?(def_node, kind)
748
793
 
749
- unless simple_parameter_shape?(def_node.parameters)
750
- return skipped(path, def_node, class_name, kind, :complex_shape)
751
- end
752
-
753
794
  inferred = infer_return_type(def_node, scope_index)
754
795
  return skipped(path, def_node, class_name, kind, :untyped_return) if inferred.nil? || dynamic_top?(inferred)
755
796
 
@@ -763,20 +804,6 @@ module Rigor
763
804
  end
764
805
  end
765
806
 
766
- # Required positionals only; the MVP's body-typing path gives well-defined returns for that shape.
767
- # Optional / rest / keyword / block parameters route through the `sig.skipped.complex-shape` reason until
768
- # slices 3+ widen the param policy.
769
- def simple_parameter_shape?(params)
770
- return true if params.nil?
771
- return false unless params.is_a?(Prism::ParametersNode)
772
-
773
- params.optionals.empty? &&
774
- params.rest.nil? &&
775
- params.keywords.empty? &&
776
- params.keyword_rest.nil? &&
777
- params.block.nil?
778
- end
779
-
780
807
  # Mirrors the `def.return-type-mismatch` rule's body-type extraction: type the implicit-return expression
781
808
  # under the scope the indexer associated with the body. The parameter bindings (typed `untyped` per the
782
809
  # indexer's default) come from `with_local` inside `StatementEvaluator`; the result is the carrier the
@@ -826,7 +853,14 @@ module Rigor
826
853
  )
827
854
  end
828
855
 
856
+ # The RBS spelling carried for a `void`-declared method, so `--diff` shows the author's own word rather
857
+ # than the `top` the translator would erase it to.
858
+ VOID_RETURN_RBS = "void"
859
+ private_constant :VOID_RETURN_RBS
860
+
829
861
  def compare_against_declared(path, def_node, class_name, kind, inferred, method_def)
862
+ return equivalent(path, def_node, class_name, kind, inferred, VOID_RETURN_RBS) if declares_void?(method_def)
863
+
830
864
  declared = build_declared_return(method_def)
831
865
  declared_rbs = declared&.erase_to_rbs
832
866
  inferred_rbs = inferred.erase_to_rbs
@@ -835,7 +869,7 @@ module Rigor
835
869
  return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
836
870
  end
837
871
 
838
- unless tighter?(declared, inferred) && !computed_literal_tightening?(inferred, def_node)
872
+ unless tighter?(declared, inferred) && !literal_decline?(inferred, def_node)
839
873
  return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
840
874
  end
841
875
 
@@ -851,6 +885,28 @@ module Rigor
851
885
  )
852
886
  end
853
887
 
888
+ # Issue #836 — a declared `void` is return INTENT, never a wide value type waiting to be narrowed, so it
889
+ # is not compared against the body at all.
890
+ #
891
+ # `void` says the return value is not part of the method's contract. No synthesis can produce it: a type
892
+ # built from a body is always the type of the body's last expression, so `void` exists only on the
893
+ # CHECKING side, against a declaration the author wrote. That puts it in the same position ADR-107 gives
894
+ # a parameter type — authored intent inference cannot derive — and proposing the body's value for it is
895
+ # a category error rather than a false positive to tune.
896
+ #
897
+ # Mechanically the misreading came from the translator: `docs/type-specification/special-types.md`
898
+ # § `void` records that the engine maps RBS `void` to `top`, `Type::Top` accepts everything, so
899
+ # {#tighter?} answered "yes" for every `void`-declared method whose body happens to return a typed
900
+ # value. Seven of the fifteen tighter-returns in Rigor's own `sig/` were this shape, and the ADR-107 G3
901
+ # provenance gate inherited the misclassification with them.
902
+ #
903
+ # ANY overload declaring `void` is enough. One proposal is rendered for the whole method, so an overload
904
+ # set that mixes `void` with a value return still has an author saying "not part of the contract" about
905
+ # the return sig-gen would rewrite.
906
+ def declares_void?(method_def)
907
+ method_def.method_types.any? { |mt| mt.type.return_type.is_a?(RBS::Types::Bases::Void) }
908
+ end
909
+
854
910
  def build_declared_return(method_def)
855
911
  translated = method_def.method_types.filter_map { |mt| translate_method_type_return(mt) }
856
912
  return nil if translated.empty?
@@ -933,6 +989,48 @@ module Rigor
933
989
  inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
934
990
  end
935
991
 
992
+ # The two reasons a `Type::Constant` the body proves is not the method's contract. Both say the same
993
+ # thing from opposite ends: the precision is real about this body and says nothing about the promise
994
+ # the method makes to its callers.
995
+ def literal_decline?(inferred, def_node)
996
+ computed_literal_tightening?(inferred, def_node) || pins_literal_over_declaration?(inferred)
997
+ end
998
+
999
+ # Issue #837 — a proposal that erases to an RBS LITERAL type never tightens an existing declaration.
1000
+ #
1001
+ # `Type::Top#describe` really does return `"top"`, but `describe` is the surface every `Rigor::Type::*`
1002
+ # class implements and every one of them declares `String`. The declared nominal is the author's
1003
+ # abstraction over the body, and the literal is the implementation detail that abstraction deliberately
1004
+ # hides — the same reading #836 gave a declared `void`. ADR-107 § Decision puts both on the intent
1005
+ # side: no synthesis produces an abstraction, because a type built from a body is always the type of
1006
+ # the body's last expression, so the choice to be wider than the body only ever exists in a declaration
1007
+ # someone wrote.
1008
+ #
1009
+ # The generator already answered this way whenever the declaration happened to be a UNION.
1010
+ # `Configuration.discover` declares `String?` against a body proving `".rigor.dist.yml" |
1011
+ # ".rigor.yml" | nil` and {#loses_declared_union_member?} refuses it, because no literal covers the
1012
+ # declared `String`. Whether the author wrote `String` or `String?` is not a decision about literals,
1013
+ # so the two shapes now answer alike.
1014
+ #
1015
+ # Scoped by the ERASURE, not by the carrier. `Type::Constant` also carries values RBS has no literal
1016
+ # spelling for (`Float`, `Regexp`, `Set`, `Pathname`, `Date`, …) and those erase to the class name, so
1017
+ # `def pi: () -> Numeric` against a body of `3.14` still proposes the ordinary nominal `Float`. A union
1018
+ # is refused when ANY member is a literal: a mixed `("a" | Float)` pins `"a"` just as a bare one does.
1019
+ # A literal nested inside a shape carrier (`[1, 2]`, a `HashShape` value) is a different question,
1020
+ # already answered for the collection declarations that matter by {#narrows_collection_to_shape?}.
1021
+ def pins_literal_over_declaration?(inferred)
1022
+ members = inferred.is_a?(Type::Union) ? inferred.members : [inferred]
1023
+ members.any? { |member| rbs_literal?(member) }
1024
+ end
1025
+
1026
+ # `Type::Constant#erase_to_rbs` spells the values RBS writes as literal types (`true` / `false` /
1027
+ # `nil`, an Integer, a Symbol, a String) as that literal and every other carrier as its value's class
1028
+ # name. Asking which of the two branches it took keeps the list of literal-spellable classes from
1029
+ # forking a second copy into this file.
1030
+ def rbs_literal?(type)
1031
+ type.is_a?(Type::Constant) && type.erase_to_rbs != type.value.class.name
1032
+ end
1033
+
936
1034
  # Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
937
1035
  # `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
938
1036
  # `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` — the carrier knows the empty-tuple cardinality
@@ -940,6 +1038,11 @@ module Rigor
940
1038
  # signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
941
1039
  # Constant"; in that case the precision came from inference over an internal computation, not the
942
1040
  # author's contract, so refuse to tighten.
1041
+ #
1042
+ # Since #837 the literal-ERASING half of this is refused whichever expression produced it. What stays
1043
+ # this guard's own is the `Constant` that erases to a class name: `def average = @total / 2.0` folds to
1044
+ # `Constant<2.5>` and would propose the perfectly ordinary `Float`, which is still the fold's answer
1045
+ # about one body rather than the method's contract.
943
1046
  def computed_literal_tightening?(inferred, def_node)
944
1047
  return false unless inferred.is_a?(Type::Constant)
945
1048
 
@@ -991,8 +1094,8 @@ module Rigor
991
1094
  end
992
1095
 
993
1096
  def render_rbs_line(def_node, inferred, class_name, kind)
994
- arity = required_arity(def_node)
995
- head = arity.zero? ? "()" : "(#{render_param_list(class_name, def_node.name, arity)})"
1097
+ params = def_node.parameters
1098
+ head = "(#{render_param_list(params, class_name, def_node.name)})#{block_signature_suffix(params)}"
996
1099
  prefix = method_def_prefix(class_name, def_node.name, kind)
997
1100
  "#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
998
1101
  end
@@ -1028,30 +1131,6 @@ module Rigor
1028
1131
  false
1029
1132
  end
1030
1133
 
1031
- def required_arity(def_node)
1032
- params = def_node.parameters
1033
- params.is_a?(Prism::ParametersNode) ? params.requireds.size : 0
1034
- end
1035
-
1036
- # Per ADR-5 clause 2 the default is `untyped` for every position. Observed-policy callers
1037
- # (`--params=observed`) pass an `observations:` map at construction time; the generator unions
1038
- # per-position arg types whose tuple arity matches the def's required-positional count. Observations from
1039
- # arities other than the def's count are discarded — they describe a different overload the MVP does not
1040
- # emit.
1041
- def render_param_list(class_name, method_name, arity)
1042
- tuples = matching_observations(class_name, method_name, arity)
1043
- return Array.new(arity, "untyped").join(", ") if tuples.empty?
1044
-
1045
- Array.new(arity) { |i| union_erase(tuples.map { |obs| obs.positional[i] }) }.join(", ")
1046
- end
1047
-
1048
- def matching_observations(class_name, method_name, arity)
1049
- return [] if @observations.empty?
1050
-
1051
- list = @observations[[class_name, method_name]] || []
1052
- list.select { |obs| obs.positional.size == arity }
1053
- end
1054
-
1055
1134
  def union_erase(types)
1056
1135
  return "untyped" if types.empty?
1057
1136
  return elaborated_rbs(types.first) if types.size == 1
@@ -1096,9 +1175,9 @@ module Rigor
1096
1175
 
1097
1176
  case node
1098
1177
  when Prism::ClassNode, Prism::ModuleNode
1099
- name = qualified_constant_path(node.constant_path)
1100
- if name
1101
- walk_attr_calls(node.body, prefix + [name], false, ctx) if node.body
1178
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
1179
+ if child_prefix
1180
+ walk_attr_calls(node.body, child_prefix, false, ctx) if node.body
1102
1181
  return
1103
1182
  end
1104
1183
  when Prism::SingletonClassNode
@@ -1180,9 +1259,9 @@ module Rigor
1180
1259
 
1181
1260
  case node
1182
1261
  when Prism::ClassNode, Prism::ModuleNode
1183
- name = qualified_constant_path(node.constant_path)
1184
- if name
1185
- collect_init_ivar_obs(node.body, prefix + [name], result) if node.body
1262
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
1263
+ if child_prefix
1264
+ collect_init_ivar_obs(node.body, child_prefix, result) if node.body
1186
1265
  return
1187
1266
  end
1188
1267
  when Prism::DefNode
@@ -21,20 +21,19 @@ module Rigor
21
21
  # in multiple files for additive member contributions, but the writer only needs one canonical target per
22
22
  # class.
23
23
  class LayoutIndex
24
- # @param signature_paths [Array<String, Pathname>, nil]
24
+ # @param signature_paths —
25
25
  # the `.rigor.yml`-configured signature directories.
26
26
  # When `nil` or empty, falls back to `<project_root>/sig`
27
27
  # if it exists (matching `Environment.for_project`'s
28
28
  # auto-detection convention).
29
- # @param project_root [String, Pathname]
30
29
  def initialize(signature_paths:, project_root: Dir.pwd)
31
30
  @signature_paths = resolve_paths(signature_paths, project_root)
32
31
  @index = nil
33
32
  end
34
33
 
35
- # @param class_name [String] fully-qualified Ruby class
34
+ # @param class_name — fully-qualified Ruby class
36
35
  # name (e.g. `"Rigor::Type::Top"`).
37
- # @return [Pathname, nil] absolute path of the sig file
36
+ # @return absolute path of the sig file
38
37
  # that already declares this class, or `nil` when no
39
38
  # existing declaration is found.
40
39
  def file_for(class_name)
@@ -29,11 +29,9 @@ module Rigor
29
29
 
30
30
  module_function
31
31
 
32
- # @param kind [:data, :struct]
33
- # @param members [Array<Symbol>] ordered member names from the ADR-48 layout.
34
- # @param member_types [Hash{Symbol => String}] erased RBS spellings; a member with no entry renders `untyped`.
35
- # @param keyword_init [Boolean] the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
36
- # @return [Shape]
32
+ # @param members — ordered member names from the ADR-48 layout.
33
+ # @param member_types — erased RBS spellings; a member with no entry renders `untyped`.
34
+ # @param keyword_init — the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
37
35
  def of(kind:, members:, member_types: {}, keyword_init: false)
38
36
  types = members.to_h { |member| [member, member_types[member] || "untyped"] }
39
37
  accessors = kind == :struct ? struct_accessors(members, types) : readers(members, types)
@@ -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