rigortype 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -4,16 +4,26 @@ 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"
13
15
  require_relative "../inference/scope_indexer"
14
16
  require_relative "../inference/rbs_type_translator"
17
+ require_relative "alias_index"
15
18
  require_relative "meta_class_shape"
16
19
  require_relative "rbs_validity"
20
+ require_relative "classification"
21
+ require_relative "effect_annotation"
22
+ require_relative "declaration_equivalence"
23
+ require_relative "inline_declarations"
24
+ require_relative "layout_index"
25
+ require_relative "method_candidate"
26
+ require_relative "writer"
17
27
 
18
28
  module Rigor
19
29
  module SigGen
@@ -39,26 +49,48 @@ module Rigor
39
49
  # - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
40
50
  # strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
41
51
  # reserves the eventual `:strict` mode).
52
+ # - A declared `void` return is never compared as a value type. It is the author's statement that the
53
+ # return is not part of the contract, which no inference can synthesize, so the method classifies
54
+ # `equivalent` carrying `void` itself as the declared spelling (#836; see {#declares_void?}).
55
+ # - A declared `untyped` return (the RBS `Bases::Any` spelling, including the one ADR-93 synthesizes for
56
+ # a parameter-only inline `# @rbs` annotation) is the ABSENCE of a return statement, not the author's
57
+ # word about it — the opposite of `void`. It is treated like no declaration at all: the inferred return
58
+ # is proposed as `tighter-return`, carrying `untyped` as the declared spelling so `--diff` still shows a
59
+ # declaration existed (#995; see {#declared_untyped?}).
60
+ # - A `tighter-return` over the class's own declaration replaces the return and nothing else: the rest of
61
+ # the line is the declaration's, spelled as written, so `--overwrite` never changes which calls the
62
+ # signature accepts. A declaration with more than one overload, or one spelled as an `alias`, is left
63
+ # alone (#1436; see {#tighter_return_line}).
64
+ # - A proposal that erases to an RBS literal never tightens an existing declaration: the declared type is
65
+ # the author's abstraction over the body and the literal is what it hides (#837; see
66
+ # {#pins_literal_over_declaration?}). A method with no declaration is unaffected — clause 1 still emits
67
+ # the strictest carrier the body proves.
42
68
  class Generator # rubocop:disable Metrics/ClassLength
43
69
  # Methods the generator rendered into RBS that `rbs` itself rejects. Populated by {#build_candidate}; each
44
70
  # one is a Rigor rendering DEFECT, not a property of the user's code, so the CLI reports them as such.
45
71
  UnrenderableMethod = Data.define(:path, :class_name, :method_name, :rbs, :error)
46
72
 
47
- # @return [Array<UnrenderableMethod>] empty on a healthy run; read after {#run}.
73
+ # @return empty on a healthy run; read after {#run}.
48
74
  attr_reader :unrenderable, :unresolvable_superclasses
49
75
 
50
- # @param configuration [Rigor::Configuration]
51
- # @param paths [Array<String>] files / directories to scan.
52
- # @param observations [Hash{[String, Symbol] => Array<Array<Rigor::Type>>}]
76
+ # @param paths — files / directories to scan.
77
+ # @param observations — => Array<Array<Rigor::Type>>}]
53
78
  # ADR-14 slice 3 — per-target-method arg-tuple observations
54
79
  # produced by {ObservationCollector}. An empty Hash (the default)
55
80
  # means "no observations available; emit `untyped` for every
56
81
  # parameter position" per ADR-5 clause 2.
57
- def initialize(configuration:, paths:, observations: {}, include_private: false)
82
+ # @param overwrite — `--overwrite`: a member declared inline whose `sig/` copy disagrees is proposed as an
83
+ # `inline-overwrite` of the whole member instead of refused (ADR-112 WD4).
84
+ def initialize(configuration:, paths:, observations: {}, include_private: false, effect_annotator: nil,
85
+ overwrite: false)
58
86
  @configuration = configuration
87
+ @overwrite = overwrite
59
88
  @paths = paths
60
89
  @observations = normalize_observations(observations)
61
90
  @include_private = include_private
91
+ # ADR-103 WD9 — `nil` when the project's `effects:` opt-in is off, which is what keeps an
92
+ # effects-off run byte-identical to one from before the annotation slot was filled.
93
+ @effect_annotator = effect_annotator
62
94
  # Per-file scratch state. `analyse_file` resets each one to a fresh container for every file walked so
63
95
  # candidates from one file don't leak into another; initialising empty here gives downstream consumers
64
96
  # (`build_candidate`, `method_def_prefix`) a never-nil invariant without per-call-site defensive guards.
@@ -84,18 +116,103 @@ module Rigor
84
116
  map.transform_values { |entries| entries.map { |entry| ObservedCall.from(entry) } }
85
117
  end
86
118
 
87
- # @return [Array<MethodCandidate>]
88
119
  def run
89
- @environment = build_environment
90
120
  resolved = resolve_paths(@paths)
121
+ @environment = build_environment(resolved)
91
122
  candidates = resolved.flat_map { |path| analyse_file(path, @environment) }
92
- demote_overridden_base_methods(
93
- demote_unresolvable_superclasses(resolve_superclass_spellings(candidates))
123
+ annotate_effects(
124
+ demote_overridden_base_methods(
125
+ demote_unresolvable_superclasses(demote_inline_generic_classes(resolve_superclass_spellings(candidates)))
126
+ )
94
127
  )
95
128
  end
96
129
 
97
130
  private
98
131
 
132
+ # ADR-103 WD9 — the annotation-emission slot ADR-14 reserved, run LAST so a candidate a later
133
+ # demotion turns into a skip never carries an annotation it will not print. Only emittable rows are
134
+ # asked: an annotation is a claim attached to a line, and a row that produces no line makes none.
135
+ def annotate_effects(candidates)
136
+ return candidates if @effect_annotator.nil?
137
+
138
+ candidates.map do |candidate|
139
+ next candidate unless Classification::EMITTABLE.include?(candidate.classification)
140
+
141
+ annotations, reason = @effect_annotator.annotate(
142
+ class_name: candidate.class_name, method_name: candidate.method_name, kind: candidate.kind,
143
+ path: candidate.path
144
+ )
145
+ reason.nil? && annotations.empty? ? candidate : candidate.with_effect_annotation(annotations, reason)
146
+ end
147
+ end
148
+
149
+ # ADR-112 WD4 — nothing is written that would open a class an inline declaration makes generic (`# @rbs
150
+ # generic T`), or a namespace nested in one, in a file the project's `sig/` does not already declare it in;
151
+ # nor into a `sig/` declaration of the class whose type parameters are named otherwise (`class Box[U]`
152
+ # beside an inline `Box[T]`, which rbs accepts), where a copied `-> T` would name a parameter nothing binds.
153
+ #
154
+ # The writer spells a class header without type parameters, and rbs rejects a class whose declarations
155
+ # disagree on them (`GenericParameterMismatchError`): the class, and every class whose signature mentions
156
+ # it, fails its definition build and reads `Dynamic[top]` — while `rigor check` still exits 0. Skipping
157
+ # costs the generic class its `sig/` members; the inline declaration still binds when the source is
158
+ # analysed. A class `sig/` already declares is written into that declaration, whose parameters are the
159
+ # project's own.
160
+ def demote_inline_generic_classes(candidates)
161
+ generic = inline_declarations.generic_classes
162
+ return candidates if generic.empty?
163
+
164
+ layout = LayoutIndex.new(signature_paths: @configuration.signature_paths)
165
+ candidates.map do |candidate|
166
+ next candidate unless Classification::EMITTABLE.include?(candidate.classification)
167
+ next candidate unless unsafe_for_generic?(candidate.class_name, generic, layout)
168
+
169
+ demoted_candidate(candidate, :inline_generic_class)
170
+ end
171
+ end
172
+
173
+ def unsafe_for_generic?(class_name, generic, layout)
174
+ return false if class_name.nil?
175
+
176
+ file = layout.file_for(class_name)
177
+ return opens_generic_class?(class_name, generic) if file.nil?
178
+
179
+ generic.key?(class_name) && signature_type_params(file, class_name) != generic.fetch(class_name)
180
+ end
181
+
182
+ # The type parameter names `file` declares `class_name` with; nil when it cannot be read, which counts as
183
+ # a mismatch — writing into a declaration whose parameters are unknown is the unsafe direction.
184
+ def signature_type_params(file, class_name)
185
+ @signature_type_params ||= {}
186
+ @signature_type_params.fetch([file, class_name]) do
187
+ @signature_type_params[[file, class_name]] = read_signature_type_params(file, class_name)
188
+ end
189
+ end
190
+
191
+ def read_signature_type_params(file, class_name)
192
+ _buffer, _directives, decls = ::RBS::Parser.parse_signature(File.read(file))
193
+ find_declaration(decls, [], class_name)&.type_params&.map(&:name)
194
+ rescue StandardError
195
+ nil
196
+ end
197
+
198
+ def find_declaration(decls, prefix, class_name)
199
+ decls.each do |decl|
200
+ next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
201
+
202
+ name = (prefix + [decl.name.to_s.delete_prefix("::")]).join("::")
203
+ return decl if name == class_name
204
+
205
+ found = find_declaration(decl.members, name.split("::"), class_name)
206
+ return found if found
207
+ end
208
+ nil
209
+ end
210
+
211
+ def opens_generic_class?(class_name, generic)
212
+ segments = class_name.split("::")
213
+ (1..segments.size).any? { |n| generic.key?(segments.first(n).join("::")) }
214
+ end
215
+
99
216
  # Issue #744 — a base class's method is NOT emitted when a project subclass overrides it and the
100
217
  # override is not emitted itself.
101
218
  #
@@ -222,7 +339,8 @@ module Rigor
222
339
  inferred_return: candidate.inferred_return, declared_return_rbs: candidate.declared_return_rbs,
223
340
  rbs: candidate.rbs, skip_reason: candidate.skip_reason,
224
341
  namespace_kinds: candidate.namespace_kinds, class_shells: candidate.class_shells,
225
- class_superclasses: superclasses
342
+ class_superclasses: superclasses, declared_rbs: candidate.declared_rbs,
343
+ declared_annotations: candidate.declared_annotations
226
344
  )
227
345
  end
228
346
 
@@ -311,11 +429,13 @@ module Rigor
311
429
  )
312
430
  end
313
431
 
314
- def build_environment
315
- Environment.for_project(
316
- libraries: @configuration.libraries,
317
- signature_paths: @configuration.signature_paths
318
- )
432
+ # Issue #821 — the SAME type universe `rigor check` analyses against: the project's `sig/`, the plugins,
433
+ # the bundle's per-gem signatures and the `rbs collection`. Built with two keywords only, the generator
434
+ # could not see a gem-declared superclass, and the skip guard above (`record_unresolvable_superclass`)
435
+ # then declined to emit every class descending from one — every Rails model, on a project whose
436
+ # collection was installed and whose `check` read it.
437
+ def build_environment(source_files = [])
438
+ ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
319
439
  end
320
440
 
321
441
  def resolve_paths(args)
@@ -413,14 +533,22 @@ module Rigor
413
533
  end
414
534
  end
415
535
 
536
+ # [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
537
+ # `Source::ConstantPath.declaration_prefix`, the same reader the engine's declaration walks use, so a
538
+ # ROOTED header re-anchors here too. Appending the rendered name to the enclosing prefix wrote
539
+ # `class ::Foo` inside `module MyApp` out as `MyApp::Foo` — sig-gen's own copy of #708, and the one
540
+ # copy that persists its answer into a file the user keeps.
541
+ #
542
+ # `prefix` stays the nesting the header is WRITTEN in for {#record_superclass}: `::` re-anchors the
543
+ # declaration, not the superclass expression beside it, which Ruby still evaluates in the enclosing cref.
416
544
  def descend_into_namespace?(node, prefix, out)
417
- name = qualified_constant_path(node.constant_path)
418
- return false unless name
545
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
546
+ return false unless child_prefix
419
547
 
420
- full = (prefix + [name]).join("::")
548
+ full = child_prefix.join("::")
421
549
  @namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
422
550
  record_superclass(node, full, prefix)
423
- walk_namespace_body(node, prefix + [name], out)
551
+ walk_namespace_body(node, child_prefix, out)
424
552
  true
425
553
  end
426
554
 
@@ -661,10 +789,22 @@ module Rigor
661
789
  # `ObservationCollector`'s `.new` → `:initialize` routing), positional and keyword arg types come from the
662
790
  # per-position / per-keyword union of observed types; otherwise every position keeps `untyped` per ADR-5
663
791
  # clause 2.
664
- def initialize_stub_candidate(path, def_node, class_name)
792
+ #
793
+ # Issue #1436 — a constructor the class's own `sig/` already declares is `equivalent`, like any other
794
+ # declared member, unless the stub clears an `untyped` the declaration still carries: that is the
795
+ # `--params=observed` upgrade `--overwrite` applies ({Writer.fewer_untyped?}, the writer's own test).
796
+ # Without observations the stub is all `untyped` and never qualifies, so it is never listed as new.
797
+ def initialize_stub_candidate(path, def_node, class_name, scope)
665
798
  params = def_node.parameters
666
799
  rbs = "def initialize: (#{render_param_list(params, class_name, :initialize)})" \
667
800
  "#{block_signature_suffix(params)} -> void"
801
+ method_def = lookup_existing_method(class_name, :initialize, :instance, scope&.environment, scope)
802
+ member = signature_member(method_def, class_name)
803
+ if member && !Writer.fewer_untyped?(rbs, member.location&.source.to_s)
804
+ declared = declares_void?(method_def) ? VOID_RETURN_RBS : build_declared_return(method_def)&.erase_to_rbs
805
+ return equivalent(path, def_node, class_name, :instance, Type::Combinator.untyped, declared)
806
+ end
807
+
668
808
  build_candidate(
669
809
  path: path, class_name: class_name, method_name: :initialize,
670
810
  kind: :instance, classification: Classification::NEW_METHOD,
@@ -689,12 +829,12 @@ module Rigor
689
829
 
690
830
  observations = arity_matched_observations(class_name, method_name, params)
691
831
  parts = []
692
- params.requireds.each_index { |i| parts << positional_type(observations, i, "") }
832
+ params.requireds.each_index { |i| parts << positional_type(observations, i, "", class_name) }
693
833
  offset = params.requireds.size
694
- params.optionals.each_index { |i| parts << positional_type(observations, offset + i, "?") }
834
+ params.optionals.each_index { |i| parts << positional_type(observations, offset + i, "?", class_name) }
695
835
  parts << "*untyped" if params.rest
696
836
  parts.concat(Array.new(params.posts.size, "untyped"))
697
- params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
837
+ params.keywords.each { |kw| parts << render_keyword_param(kw, observations, class_name) }
698
838
  parts.concat(keyword_rest_parts(params))
699
839
  parts.join(", ")
700
840
  end
@@ -744,15 +884,15 @@ module Rigor
744
884
 
745
885
  # A bare union is a valid positional type in RBS (`(String | Integer, ?String | Integer)`), so unlike the
746
886
  # return position it is not parenthesised.
747
- def positional_type(observations, index, prefix)
887
+ def positional_type(observations, index, prefix, owner = nil)
748
888
  types = observations.filter_map { |obs| obs.positional[index] }
749
- "#{prefix}#{union_erase(types)}"
889
+ "#{prefix}#{union_erase(types, owner: owner)}"
750
890
  end
751
891
 
752
- def render_keyword_param(keyword, observations)
892
+ def render_keyword_param(keyword, observations, owner = nil)
753
893
  optional_marker = keyword.is_a?(Prism::OptionalKeywordParameterNode) ? "?" : ""
754
894
  types = observations.filter_map { |obs| obs.keyword[keyword.name] }
755
- rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(types))
895
+ rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(types, owner: owner))
756
896
  "#{optional_marker}#{keyword.name}: #{rendered}"
757
897
  end
758
898
 
@@ -772,7 +912,15 @@ module Rigor
772
912
  def classify_def(path, def_node, class_name, kind, scope_index)
773
913
  return nil if visibility_excludes?(def_node, class_name, kind, scope_index)
774
914
  return nil if initialize_excludes?(def_node, kind)
775
- return initialize_stub_candidate(path, def_node, class_name) if non_trivial_initialize?(def_node, kind)
915
+
916
+ inline = inline_declarations.lookup(path, class_name, def_node.name, kind)
917
+ if inline
918
+ return skipped(path, def_node, class_name, kind, :inline_declared) if skip_inline_declared?
919
+ return inline_def_candidate(path, def_node, class_name, kind, scope_index, inline) if inline.declared?
920
+ end
921
+ if non_trivial_initialize?(def_node, kind)
922
+ return initialize_stub_candidate(path, def_node, class_name, scope_index[def_node])
923
+ end
776
924
 
777
925
  inferred = infer_return_type(def_node, scope_index)
778
926
  return skipped(path, def_node, class_name, kind, :untyped_return) if inferred.nil? || dynamic_top?(inferred)
@@ -787,6 +935,242 @@ module Rigor
787
935
  end
788
936
  end
789
937
 
938
+ # ADR-112 WD4 — the inline declarations of every analysed file, read once per run. See
939
+ # {InlineDeclarations} for why the synthesized text rather than the environment.
940
+ def inline_declarations
941
+ @inline_declarations ||= InlineDeclarations.build(@environment)
942
+ end
943
+
944
+ def skip_inline_declared?
945
+ @configuration.sig_gen_inline_declared == :skip
946
+ end
947
+
948
+ # ADR-112 WD4 — a `def` the author declared inline is written from that declaration, as rbs-inline reads it,
949
+ # not from what the body infers: `rigor check` reads the `sig/` member beside the inline one (ADR-112 WD5),
950
+ # so a line built from inference (`untyped` parameters, a return the body happens to prove) would override
951
+ # or contradict the contract the author wrote beside the code.
952
+ #
953
+ # The one inferred part is a return the author left unwritten (`# @rbs name: String`, no `return:`) —
954
+ # ADR-107's mixed provenance, parameters authored and return generated — filled from the body; a body that
955
+ # types as `untyped` there is skipped exactly as an undeclared one is. `initialize` returns `void` by
956
+ # definition, whatever its last expression is.
957
+ def inline_def_candidate(path, def_node, class_name, kind, scope_index, inline)
958
+ types = inline.method_types
959
+ return_defaulted = inline.return_inferred
960
+ if return_defaulted && initialize_def?(def_node, kind)
961
+ void = ::RBS::Types::Bases::Void.new(location: nil)
962
+ types = types.map { |mt| defaulted_return?(mt) ? with_return_type(mt, void) : mt }
963
+ return_defaulted = false
964
+ end
965
+
966
+ scope = scope_index[def_node]
967
+ method_def = lookup_existing_method(class_name, def_node.name, kind, scope&.environment, scope)
968
+ head = "#{method_def_prefix(class_name, def_node.name, kind)}#{def_node.name}: "
969
+ if return_defaulted
970
+ return mixed_provenance_candidate(path, def_node, class_name, kind, scope_index, inline, method_def, head)
971
+ end
972
+
973
+ inline_candidate(path, class_name, def_node.name, kind, head + types.join(" | "), inline, method_def)
974
+ end
975
+
976
+ # A parameter-only annotation (`# @rbs name: String`, no `return:`): parameters authored, return inferred.
977
+ # Only the authored half is weighed against `sig/`. When the parameters differ the method is refused, even
978
+ # under `--overwrite` — the body is typed under the parameters the environment binds, which are `sig/`'s
979
+ # once it declares the member, so a return inferred for the new line would describe parameters about to
980
+ # change. When they match, the return is an ordinary inferred proposal: {#compare_against_declared}
981
+ # leaves a declared `void`, a deliberately wider type and a declined literal alone, and proposes a
982
+ # strictly narrower return as `tighter-return`, spelled with the authored parameters.
983
+ def mixed_provenance_candidate(path, def_node, class_name, kind, scope_index, inline, method_def, head) # rubocop:disable Metrics/ParameterLists
984
+ types = inline.method_types
985
+ existing = signature_member(method_def, class_name)
986
+ if existing && parameters_differ?(existing, types, def_node.name, class_name)
987
+ return skipped(path, def_node, class_name, kind, :inline_differs)
988
+ end
989
+
990
+ inferred, overloads = inferred_overloads(def_node, scope_index, class_name, types)
991
+ return skipped(path, def_node, class_name, kind, :untyped_return) if inferred.nil?
992
+
993
+ rbs = head + overloads.join(" | ")
994
+ if existing && annotations_present?(existing, inline) && existing_types(existing, def_node.name)
995
+ return compare_against_declared(path, def_node, class_name, kind, inferred, method_def, rendered: rbs)
996
+ end
997
+
998
+ inline_candidate(path, class_name, def_node.name, kind, rbs, inline, method_def, inferred)
999
+ end
1000
+
1001
+ # `[inferred return, overload strings]`, or `[nil, nil]` when the body types as `untyped`.
1002
+ def inferred_overloads(def_node, scope_index, class_name, types)
1003
+ inferred = infer_return_type(def_node, scope_index)
1004
+ return [nil, nil] if inferred.nil? || dynamic_top?(inferred)
1005
+
1006
+ [inferred, with_defaulted_return(types, paren_wrap_union(elaborated_rbs(inferred, owner: class_name)))]
1007
+ end
1008
+
1009
+ # Whether a `sig/` member's overloads, returns aside, differ from the inline declaration's. Such a member is
1010
+ # refused even under `--overwrite` when the inline return is to be inferred (see {#inline_def_candidate});
1011
+ # deleting the `sig/` member lets the next run infer it under the inline parameters.
1012
+ def parameters_differ?(existing, types, method_name, class_name)
1013
+ written = existing_types(existing, method_name)
1014
+ return false if written.nil?
1015
+
1016
+ !equivalence(class_name).same_parameters?(written, types)
1017
+ end
1018
+
1019
+ def initialize_def?(def_node, kind)
1020
+ kind == :instance && def_node.name == :initialize
1021
+ end
1022
+
1023
+ def defaulted_return?(method_type)
1024
+ method_type.type.return_type.is_a?(::RBS::Types::Bases::Any)
1025
+ end
1026
+
1027
+ def with_return_type(method_type, return_type)
1028
+ method_type.update(type: method_type.type.with_return_type(return_type))
1029
+ end
1030
+
1031
+ # A class name no project declares, standing in for the inferred return while `RBS::MethodType#to_s` spells
1032
+ # the rest of an overload, so the return itself keeps sig-gen's own spelling (`[Float, String]`, a folded
1033
+ # alias) rather than being re-spelled through a parse.
1034
+ INFERRED_RETURN_PLACEHOLDER = "Rigor__SigGenInferredReturn"
1035
+ private_constant :INFERRED_RETURN_PLACEHOLDER
1036
+
1037
+ # Each overload whose return rbs-inline defaulted to `untyped` takes `returned` (already spelled for a
1038
+ # return position); the rest stay as written.
1039
+ def with_defaulted_return(method_types, returned)
1040
+ method_types.map do |method_type|
1041
+ defaulted_return?(method_type) ? spell_with_return(method_type, returned) : method_type.to_s
1042
+ end
1043
+ end
1044
+
1045
+ # One overload spelled with `returned` (already spelled for a return position) as its return.
1046
+ def spell_with_return(method_type, returned)
1047
+ placeholder = ::RBS::Parser.parse_type(INFERRED_RETURN_PLACEHOLDER)
1048
+ with_return_type(method_type, placeholder).to_s.sub(/-> #{INFERRED_RETURN_PLACEHOLDER}\z/o, "-> #{returned}")
1049
+ end
1050
+
1051
+ # Classifies an inline-declared member's `rbs` line against what `sig/` already says for it (ADR-112 WD4, as
1052
+ # decided on #1422):
1053
+ #
1054
+ # - nothing on this class itself (or only the inline declaration) — `new-method`;
1055
+ # - the same types, and every annotation the author wrote inline — `equivalent`;
1056
+ # - anything else — the two declarations disagree, and neither is presumed right. Without `--overwrite`
1057
+ # the member is REFUSED (`sig.skipped.inline-differs`): nothing is written, and `--write` / `--check`
1058
+ # exit 1 until a person reconciles them. With `--overwrite` it is `inline-overwrite`, which replaces the
1059
+ # whole `sig/` member with the inline line — never a mix of the two, whose slots (and type variables)
1060
+ # need not correspond.
1061
+ def inline_candidate(path, class_name, method_name, kind, rbs, inline, method_def, inferred = nil) # rubocop:disable Metrics/ParameterLists
1062
+ fields = { path: path, class_name: class_name, method_name: method_name, kind: kind,
1063
+ inferred_return: inferred }
1064
+ existing = signature_member(method_def, class_name)
1065
+ if existing.nil?
1066
+ build_candidate(**fields, classification: Classification::NEW_METHOD, rbs: rbs,
1067
+ declared_annotations: inline.annotation_lines)
1068
+ elsif current_copy?(existing, rbs, inline, method_name, class_name)
1069
+ build_candidate(**fields, classification: Classification::EQUIVALENT)
1070
+ elsif @overwrite
1071
+ # An `attr_*` member is replaced in its own spelling, by the inline attribute's.
1072
+ line = attr_member?(existing) && inline.attr_line ? inline.attr_line : rbs
1073
+ build_candidate(**fields, classification: Classification::INLINE_OVERWRITE, rbs: line,
1074
+ declared_annotations: inline.annotation_lines,
1075
+ declared_rbs: squish(existing.location&.source))
1076
+ else
1077
+ build_candidate(**fields, classification: Classification::SKIPPED, skip_reason: :inline_differs)
1078
+ end
1079
+ end
1080
+
1081
+ # The member a project `.rbs` declares for `class_name` itself — not an ancestor's, and not the inline
1082
+ # declaration, whose buffer is the synthesizer's `virtual:` one. An `overloading?` member (`def x: … |
1083
+ # ...`) is not a counterpart either: it adds overloads to the inline declaration rather than restating
1084
+ # it, and sig-gen leaves it alone.
1085
+ def signature_member(method_def, class_name)
1086
+ return nil if method_def.nil?
1087
+
1088
+ method_def.defs.filter_map do |type_def|
1089
+ next unless type_def.defined_in.to_s.delete_prefix("::") == class_name
1090
+
1091
+ member = type_def.member
1092
+ next if member.respond_to?(:overloading?) && member.overloading?
1093
+
1094
+ member unless member.location&.buffer&.name.to_s.start_with?("virtual:")
1095
+ end.first
1096
+ end
1097
+
1098
+ # The two declarations are compared as types ({DeclarationEquivalence}): parameter names, union spelling
1099
+ # and a leading `::` that resolves to the same constant do not count; overload order does. The copy must
1100
+ # also carry every annotation the author wrote inline. A `sig/` member whose text is unavailable is left
1101
+ # as it is rather than refused for good.
1102
+ def current_copy?(member, rbs, inline, method_name, class_name)
1103
+ written = existing_types(member, method_name)
1104
+ mine = member_types(rbs)
1105
+ return true if written.nil? || mine.nil?
1106
+
1107
+ equivalence(class_name).same?(written, mine) && annotations_present?(member, inline)
1108
+ end
1109
+
1110
+ def annotations_present?(member, inline)
1111
+ (inline.annotations - member.annotations.map { |annotation| annotation.string.to_s.strip }).empty?
1112
+ end
1113
+
1114
+ def attr_member?(member)
1115
+ member.is_a?(::RBS::AST::Members::AttrReader) || member.is_a?(::RBS::AST::Members::AttrWriter) ||
1116
+ member.is_a?(::RBS::AST::Members::AttrAccessor)
1117
+ end
1118
+
1119
+ # The overloads a `sig/` member declares for `method_name`: a `def`'s, parsed from its own text; an
1120
+ # attribute's reader (`() -> T`) or writer (`(T) -> T`), from its type.
1121
+ def existing_types(member, method_name)
1122
+ return member_types(member.location&.source) if member.is_a?(::RBS::AST::Members::MethodDefinition)
1123
+ return nil unless attr_member?(member)
1124
+
1125
+ shape = method_name.to_s.end_with?("=") ? "(#{member.type}) -> #{member.type}" : "() -> #{member.type}"
1126
+ [::RBS::Parser.parse_method_type(shape)]
1127
+ rescue ::RBS::BaseError
1128
+ nil
1129
+ end
1130
+
1131
+ def equivalence(class_name)
1132
+ @equivalence ||= {}
1133
+ @equivalence[class_name] ||= DeclarationEquivalence.new(resolve: type_name_resolver(class_name))
1134
+ end
1135
+
1136
+ # A type name as written, resolved the way RBS resolves it inside `class_name`'s declaration: an absolute
1137
+ # `::Foo` is `Foo`; a relative `Foo` is the innermost `…::Foo` the environment knows, so inside `module NS`
1138
+ # it is `NS::Foo` when `NS` declares one, and `::Foo` only when it does not.
1139
+ def type_name_resolver(class_name)
1140
+ nesting = class_name.split("::")
1141
+ lambda do |name|
1142
+ next name.delete_prefix("::") if name.start_with?("::")
1143
+
1144
+ nesting.size.downto(1).map { |n| (nesting.first(n) + [name]).join("::") }
1145
+ .find { |candidate| class_known?(candidate) } || name
1146
+ end
1147
+ end
1148
+
1149
+ def class_known?(name)
1150
+ @class_known ||= {}
1151
+ @class_known.fetch(name) { @class_known[name] = Reflection.rbs_class_known?(name, environment: @environment) }
1152
+ end
1153
+
1154
+ # A member line's overloads, parsed with type names unresolved so the two sides compare as written.
1155
+ def member_types(source)
1156
+ member_overloads(source)&.map(&:method_type)
1157
+ end
1158
+
1159
+ # The same, with each overload's own annotations (`%a{pure} (Integer) -> String`).
1160
+ def member_overloads(source)
1161
+ return nil if source.nil?
1162
+
1163
+ _buffer, _directives, decls = ::RBS::Parser.parse_signature("class Rigor__SigGenProbe\n#{source}\nend\n")
1164
+ parsed = decls.first&.members&.first
1165
+ parsed.is_a?(::RBS::AST::Members::MethodDefinition) ? parsed.overloads : nil
1166
+ rescue ::RBS::BaseError
1167
+ nil
1168
+ end
1169
+
1170
+ def squish(text)
1171
+ text.to_s.gsub(/\s+/, " ").strip
1172
+ end
1173
+
790
1174
  # Mirrors the `def.return-type-mismatch` rule's body-type extraction: type the implicit-return expression
791
1175
  # under the scope the indexer associated with the body. The parameter bindings (typed `untyped` per the
792
1176
  # indexer's default) come from `with_local` inside `StatementEvaluator`; the result is the carrier the
@@ -796,9 +1180,10 @@ module Rigor
796
1180
  # types with the implicit-return expression's type. The earlier MVP only typed the implicit-return path,
797
1181
  # which routinely produced single-branch artefacts like `parse_options: () -> nil` (the actual runtime
798
1182
  # return is `options | nil`) or `find: () -> V` (actually `V | nil` via `return nil unless ...`). The walk
799
- # excludes nested `DefNode` / lambda / block scopes whose returns belong to different methods. Delegates
800
- # to {Rigor::Inference::DefReturnTyper} — the same body-typing + explicit-return-union the `rigor
801
- # annotate` def-line annotator uses.
1183
+ # includes a `return` inside an ordinary block, which exits the method, and excludes the
1184
+ # {Rigor::Inference::ReturnBarrier} bodies — nested `def`, lambda, `define_method` — whose returns belong to a
1185
+ # different method (issue #1382). Delegates to {Rigor::Inference::DefReturnTyper} — the same body-typing +
1186
+ # explicit-return-union the `rigor annotate` def-line annotator uses.
802
1187
  def infer_return_type(def_node, scope_index)
803
1188
  Inference::DefReturnTyper.call(def_node, scope_index)
804
1189
  end
@@ -836,8 +1221,21 @@ module Rigor
836
1221
  )
837
1222
  end
838
1223
 
839
- def compare_against_declared(path, def_node, class_name, kind, inferred, method_def)
1224
+ # The RBS spelling carried for a `void`-declared method, so `--diff` shows the author's own word rather
1225
+ # than the `top` the translator would erase it to.
1226
+ VOID_RETURN_RBS = "void"
1227
+ private_constant :VOID_RETURN_RBS
1228
+
1229
+ # @param rendered — the line a proposal is spelled as when it is not the ordinary inferred one: a
1230
+ # parameter-only inline annotation keeps its authored parameters ({#mixed_provenance_candidate}).
1231
+ def compare_against_declared(path, def_node, class_name, kind, inferred, method_def, rendered: nil)
1232
+ return equivalent(path, def_node, class_name, kind, inferred, VOID_RETURN_RBS) if declares_void?(method_def)
1233
+
840
1234
  declared = build_declared_return(method_def)
1235
+ if declared_untyped?(declared)
1236
+ return declared_untyped_candidate(path, def_node, class_name, kind, inferred, method_def, rendered: rendered)
1237
+ end
1238
+
841
1239
  declared_rbs = declared&.erase_to_rbs
842
1240
  inferred_rbs = inferred.erase_to_rbs
843
1241
 
@@ -845,10 +1243,21 @@ module Rigor
845
1243
  return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
846
1244
  end
847
1245
 
848
- unless tighter?(declared, inferred) && !computed_literal_tightening?(inferred, def_node)
1246
+ unless tighter?(declared, inferred) && !literal_decline?(inferred, def_node)
849
1247
  return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
850
1248
  end
851
1249
 
1250
+ tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, declared_rbs, rendered)
1251
+ end
1252
+
1253
+ # Both tightening branches end here. The proposal is the declaration with its return replaced
1254
+ # ({#tighter_return_line}); when there is no single declared overload to replace it in, the method is
1255
+ # `equivalent` — the declaration stands, as it does for any narrowing the generator declines. `declared_rbs`
1256
+ # carries the class's own declaration so `--diff` and `--check` show the line the proposal replaces.
1257
+ def tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, declared_return, rendered) # rubocop:disable Metrics/ParameterLists
1258
+ line = rendered || tighter_return_line(def_node, method_def, class_name, kind, inferred)
1259
+ return equivalent(path, def_node, class_name, kind, inferred, declared_return) if line.nil?
1260
+
852
1261
  build_candidate(
853
1262
  path: path,
854
1263
  class_name: class_name,
@@ -856,11 +1265,111 @@ module Rigor
856
1265
  kind: kind,
857
1266
  classification: Classification::TIGHTER_RETURN,
858
1267
  inferred_return: inferred,
859
- declared_return_rbs: declared_rbs,
860
- rbs: render_rbs_line(def_node, inferred, class_name, kind)
1268
+ declared_return_rbs: declared_return,
1269
+ declared_rbs: declared_line(method_def, class_name),
1270
+ rbs: line
861
1271
  )
862
1272
  end
863
1273
 
1274
+ # Issue #1436 — a `tighter-return` changes the return and only the return. It used to be rendered like a
1275
+ # new method, from the `def`'s runtime shape: every parameter `untyped`, `(?)` spelled out as
1276
+ # `(*untyped)`, names and block types dropped. Under `--overwrite` that replaced a declared `(Integer x)`
1277
+ # with `(untyped)` — a change to which calls the signature accepts, made by a proposal that claims to be
1278
+ # about the return. The class's own declaration is kept whole instead: its visibility, the overload's
1279
+ # annotations, type parameters, parameters and block, with only the return replaced.
1280
+ #
1281
+ # Three shapes are declined (`nil`):
1282
+ # - several overloads. The body is typed once, so the inferred return covers every overload together and
1283
+ # says nothing about which overload returns what: collapsing them into one line would drop the
1284
+ # overloads, and giving each the joint return would widen the overloads the author wrote narrower.
1285
+ # - a name the class's own `sig/` spells as an `alias`. Its parameters are the aliased method's, and a
1286
+ # proposal would read as a rewrite of that method's line rather than of the alias. An alias only an
1287
+ # ancestor declares takes the override path below, like any other ancestor declaration.
1288
+ # - nothing else: when only an ancestor declares the method, the proposal is a new override on this
1289
+ # class, and is rendered from the `def`'s own shape like any new method. The ancestor's parameters
1290
+ # describe the ancestor's `def`, not this one, which may take other arguments (ADR-14 clause 2).
1291
+ def tighter_return_line(def_node, method_def, class_name, kind, inferred)
1292
+ member = signature_member(method_def, class_name)
1293
+ return render_rbs_line(def_node, inferred, class_name, kind) if member.nil?
1294
+ return nil if method_def.alias_of
1295
+
1296
+ overload = sole_declared_overload(member, method_def, def_node.name)
1297
+ return nil if overload.nil?
1298
+
1299
+ returned = paren_wrap_union(elaborated_rbs(inferred, owner: class_name))
1300
+ annotations = overload.annotations.map { |a| "#{a.location&.source || "%a{#{a.string}}"} " }.join
1301
+ visibility = member.respond_to?(:visibility) && member.visibility ? "#{member.visibility} " : ""
1302
+ "#{visibility}#{method_def_prefix(class_name, def_node.name, kind)}#{def_node.name}: " \
1303
+ "#{annotations}#{spell_with_return(overload.method_type, returned)}"
1304
+ end
1305
+
1306
+ # The one overload the class's own `sig/` member declares, parsed from its text with its annotations so it
1307
+ # keeps the spelling the author wrote (`Integer`, not the resolved `::Integer`). An attribute declares one
1308
+ # implicitly and carries no overload annotations.
1309
+ def sole_declared_overload(member, method_def, method_name)
1310
+ return nil unless method_def.method_types.size == 1
1311
+
1312
+ overloads =
1313
+ if member.is_a?(::RBS::AST::Members::MethodDefinition)
1314
+ member_overloads(member.location&.source)
1315
+ else
1316
+ existing_types(member, method_name)&.map do |method_type|
1317
+ ::RBS::AST::Members::MethodDefinition::Overload.new(method_type: method_type, annotations: [])
1318
+ end
1319
+ end
1320
+ overloads&.size == 1 ? overloads.first : nil
1321
+ end
1322
+
1323
+ # The class's own declaration, as the `-` line of `--diff` and `--check` shows it. A method only an
1324
+ # ancestor declares has none: the proposal adds an override rather than replacing a line.
1325
+ def declared_line(method_def, class_name)
1326
+ source = signature_member(method_def, class_name)&.location&.source
1327
+ source && squish(source)
1328
+ end
1329
+
1330
+ # Issue #836 — a declared `void` is return INTENT, never a wide value type waiting to be narrowed, so it
1331
+ # is not compared against the body at all.
1332
+ #
1333
+ # `void` says the return value is not part of the method's contract. No synthesis can produce it: a type
1334
+ # built from a body is always the type of the body's last expression, so `void` exists only on the
1335
+ # CHECKING side, against a declaration the author wrote. That puts it in the same position ADR-107 gives
1336
+ # a parameter type — authored intent inference cannot derive — and proposing the body's value for it is
1337
+ # a category error rather than a false positive to tune.
1338
+ #
1339
+ # Mechanically the misreading came from the translator: `docs/type-specification/special-types.md`
1340
+ # § `void` records that the engine maps RBS `void` to `top`, `Type::Top` accepts everything, so
1341
+ # {#tighter?} answered "yes" for every `void`-declared method whose body happens to return a typed
1342
+ # value. Seven of the fifteen tighter-returns in Rigor's own `sig/` were this shape, and the ADR-107 G3
1343
+ # provenance gate inherited the misclassification with them.
1344
+ #
1345
+ # ANY overload declaring `void` is enough. One proposal is rendered for the whole method, so an overload
1346
+ # set that mixes `void` with a value return still has an author saying "not part of the contract" about
1347
+ # the return sig-gen would rewrite.
1348
+ def declares_void?(method_def)
1349
+ method_def.method_types.any? { |mt| mt.type.return_type.is_a?(RBS::Types::Bases::Void) }
1350
+ end
1351
+
1352
+ # Issue #995 — a declared `untyped` (RBS `Bases::Any`, translated by
1353
+ # {Inference::RbsTypeTranslator} to `Type::Dynamic`) says nothing at all, unlike `void` (#836), which is
1354
+ # the author's word that the value is not part of the contract. `tighter?`'s acceptance check cannot
1355
+ # tell the two apart — `untyped` accepts and is accepted by everything, so its backward check, which
1356
+ # exists to refuse a declaration that carries real information the inferred form would narrow, also
1357
+ # refused this one — and the candidate fell into `equivalent`: neither emitted nor counted among the
1358
+ # skips. ADR-93's inline `# @rbs param: Type` annotation (no return clause) synthesizes exactly this
1359
+ # spelling for every method it documents, so the silent drop hit the parameter-annotated, return-bare
1360
+ # shape ADR-14 exists to help.
1361
+ def declared_untyped?(declared)
1362
+ declared.is_a?(Type::Dynamic)
1363
+ end
1364
+
1365
+ # Proposed without {#compare_against_declared}'s tightening guards: a declared `untyped` return carries no
1366
+ # information to weigh {#tighter?} or {#literal_decline?} against, the same position a method with no
1367
+ # declaration at all is in. The line is built as for any tightening ({#tighter_return_line}), and
1368
+ # `declared_return_rbs` carries `"untyped"` so the `[tighter, was: untyped]` print tag says one existed.
1369
+ def declared_untyped_candidate(path, def_node, class_name, kind, inferred, method_def, rendered: nil)
1370
+ tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, "untyped", rendered)
1371
+ end
1372
+
864
1373
  def build_declared_return(method_def)
865
1374
  translated = method_def.method_types.filter_map { |mt| translate_method_type_return(mt) }
866
1375
  return nil if translated.empty?
@@ -868,10 +1377,19 @@ module Rigor
868
1377
  translated.size == 1 ? translated.first : Type::Combinator.union(*translated)
869
1378
  end
870
1379
 
1380
+ # `alias_expander:` matters here as much as it does at any dispatch site (see
1381
+ # {Inference::RbsTypeTranslator}'s own doc comment): without it, a declared return spelled as a
1382
+ # project alias (`Type::t`, `Environment::ordering`, `Inference::closure_classification`) degrades
1383
+ # to `Dynamic[Top]` exactly like a bare `untyped` would, and {#declared_untyped?} cannot tell an
1384
+ # author's meaningful alias apart from an absent declaration. That collapsed a well-typed, already
1385
+ # exact declaration into a spurious {#declared_untyped_candidate} the moment #995 made "declared
1386
+ # untyped" a proposable position — the ADR-107 G3 gate caught it on this repo's own `sig/` tree
1387
+ # (`Environment#class_ordering`'s `-> ordering` and every `-> Type::t` return in `type.rbs` /
1388
+ # `inference.rbs` / `scope.rbs`).
871
1389
  def translate_method_type_return(method_type)
872
1390
  Inference::RbsTypeTranslator.translate(
873
1391
  method_type.type.return_type,
874
- self_type: nil, instance_type: nil, type_vars: {}
1392
+ self_type: nil, instance_type: nil, type_vars: {}, alias_expander: @environment&.rbs_loader
875
1393
  )
876
1394
  rescue StandardError
877
1395
  nil
@@ -943,6 +1461,48 @@ module Rigor
943
1461
  inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
944
1462
  end
945
1463
 
1464
+ # The two reasons a `Type::Constant` the body proves is not the method's contract. Both say the same
1465
+ # thing from opposite ends: the precision is real about this body and says nothing about the promise
1466
+ # the method makes to its callers.
1467
+ def literal_decline?(inferred, def_node)
1468
+ computed_literal_tightening?(inferred, def_node) || pins_literal_over_declaration?(inferred)
1469
+ end
1470
+
1471
+ # Issue #837 — a proposal that erases to an RBS LITERAL type never tightens an existing declaration.
1472
+ #
1473
+ # `Type::Top#describe` really does return `"top"`, but `describe` is the surface every `Rigor::Type::*`
1474
+ # class implements and every one of them declares `String`. The declared nominal is the author's
1475
+ # abstraction over the body, and the literal is the implementation detail that abstraction deliberately
1476
+ # hides — the same reading #836 gave a declared `void`. ADR-107 § Decision puts both on the intent
1477
+ # side: no synthesis produces an abstraction, because a type built from a body is always the type of
1478
+ # the body's last expression, so the choice to be wider than the body only ever exists in a declaration
1479
+ # someone wrote.
1480
+ #
1481
+ # The generator already answered this way whenever the declaration happened to be a UNION.
1482
+ # `Configuration.discover` declares `String?` against a body proving `".rigor.dist.yml" |
1483
+ # ".rigor.yml" | nil` and {#loses_declared_union_member?} refuses it, because no literal covers the
1484
+ # declared `String`. Whether the author wrote `String` or `String?` is not a decision about literals,
1485
+ # so the two shapes now answer alike.
1486
+ #
1487
+ # Scoped by the ERASURE, not by the carrier. `Type::Constant` also carries values RBS has no literal
1488
+ # spelling for (`Float`, `Regexp`, `Set`, `Pathname`, `Date`, …) and those erase to the class name, so
1489
+ # `def pi: () -> Numeric` against a body of `3.14` still proposes the ordinary nominal `Float`. A union
1490
+ # is refused when ANY member is a literal: a mixed `("a" | Float)` pins `"a"` just as a bare one does.
1491
+ # A literal nested inside a shape carrier (`[1, 2]`, a `HashShape` value) is a different question,
1492
+ # already answered for the collection declarations that matter by {#narrows_collection_to_shape?}.
1493
+ def pins_literal_over_declaration?(inferred)
1494
+ members = inferred.is_a?(Type::Union) ? inferred.members : [inferred]
1495
+ members.any? { |member| rbs_literal?(member) }
1496
+ end
1497
+
1498
+ # `Type::Constant#erase_to_rbs` spells the values RBS writes as literal types (`true` / `false` /
1499
+ # `nil`, an Integer, a Symbol, a String) as that literal and every other carrier as its value's class
1500
+ # name. Asking which of the two branches it took keeps the list of literal-spellable classes from
1501
+ # forking a second copy into this file.
1502
+ def rbs_literal?(type)
1503
+ type.is_a?(Type::Constant) && type.erase_to_rbs != type.value.class.name
1504
+ end
1505
+
946
1506
  # Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
947
1507
  # `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
948
1508
  # `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` — the carrier knows the empty-tuple cardinality
@@ -950,6 +1510,11 @@ module Rigor
950
1510
  # signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
951
1511
  # Constant"; in that case the precision came from inference over an internal computation, not the
952
1512
  # author's contract, so refuse to tighten.
1513
+ #
1514
+ # Since #837 the literal-ERASING half of this is refused whichever expression produced it. What stays
1515
+ # this guard's own is the `Constant` that erases to a class name: `def average = @total / 2.0` folds to
1516
+ # `Constant<2.5>` and would propose the perfectly ordinary `Float`, which is still the fold's answer
1517
+ # about one body rather than the method's contract.
953
1518
  def computed_literal_tightening?(inferred, def_node)
954
1519
  return false unless inferred.is_a?(Type::Constant)
955
1520
 
@@ -1004,15 +1569,54 @@ module Rigor
1004
1569
  params = def_node.parameters
1005
1570
  head = "(#{render_param_list(params, class_name, def_node.name)})#{block_signature_suffix(params)}"
1006
1571
  prefix = method_def_prefix(class_name, def_node.name, kind)
1007
- "#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
1572
+ "#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred, owner: class_name))}"
1008
1573
  end
1009
1574
 
1010
1575
  # Routes the inferred carrier through {TypeElaborator} so bare generic nominals (`Array` / `Hash` / `Set`
1011
1576
  # / `Range` / `Enumerable`) get their `untyped` type parameters filled in before erasing to RBS. The
1012
1577
  # elaborator consults the class's RBS-declared type-parameter list via
1013
1578
  # `Reflection.class_type_param_names`.
1014
- def elaborated_rbs(type)
1015
- TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs
1579
+ # `owner:` is the class or module the rendered member belongs to. {AliasIndex} needs it to judge whether
1580
+ # a matching alias is one this reader would expect to see; a call site that cannot supply it renders the
1581
+ # type unfolded, which is the same output as before #1002.
1582
+ def elaborated_rbs(type, owner: nil)
1583
+ alias_index.fold(TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs, owner)
1584
+ end
1585
+
1586
+ # Issue #1002 — the reverse of {#translate_method_type_return}'s `alias_expander:`. Folding happens here,
1587
+ # at the single seam every rendered type passes through (`render_rbs_line`, `union_erase` for parameter
1588
+ # and ivar positions, the attr path), so `--print`, `--diff`, `--write` and `--format=json` cannot drift
1589
+ # from one another: they all read the `rbs` string a candidate was built with. Deliberately NOT in
1590
+ # {Type::Union#erase_to_rbs} — that carrier method is the type model's RBS surface, shared with
1591
+ # diagnostics, `rigor type-of` and the engine's own comparisons, and none of those should start naming a
1592
+ # project alias. Sig-gen's rendering preference is a CLI concern and stays on the CLI side.
1593
+ def alias_index
1594
+ @alias_index ||= AliasIndex.build(
1595
+ environment: @environment,
1596
+ signature_paths: project_signature_paths,
1597
+ excluded_paths: plugin_signature_paths
1598
+ )
1599
+ end
1600
+
1601
+ # The project's OWN signature paths, resolved exactly as `Environment.for_project` resolves its
1602
+ # `resolved_paths` — the configured list, or `<root>/sig` when the project configured none. The
1603
+ # loader's `signature_paths` is NOT the same list: it also carries plugin signature paths, gem `sig/`
1604
+ # directories discovered under the project's bundle, the `rbs collection` tree and Rigor's own gem
1605
+ # overlays, none of which declare vocabulary this project chose.
1606
+ def project_signature_paths
1607
+ configured = @configuration.signature_paths
1608
+ return configured unless configured.nil?
1609
+
1610
+ default = Pathname(Dir.pwd) / "sig"
1611
+ default.directory? ? [default] : []
1612
+ end
1613
+
1614
+ # A loaded plugin's own signature directory is the plugin's vocabulary, not the project's, whether the
1615
+ # plugin tier contributed it or the project wired it into `signature_paths:` itself (#697).
1616
+ def plugin_signature_paths
1617
+ @environment&.plugin_registry&.signature_paths || []
1618
+ rescue StandardError
1619
+ []
1016
1620
  end
1017
1621
 
1018
1622
  # RBS / Steep require return-position unions to be parenthesised when they appear bare at the top level of
@@ -1038,15 +1642,15 @@ module Rigor
1038
1642
  false
1039
1643
  end
1040
1644
 
1041
- def union_erase(types)
1645
+ def union_erase(types, owner: nil)
1042
1646
  return "untyped" if types.empty?
1043
- return elaborated_rbs(types.first) if types.size == 1
1647
+ return elaborated_rbs(types.first, owner: owner) if types.size == 1
1044
1648
 
1045
1649
  # `Type::Combinator.union` dedupes by structural type equality. The carrier-level `erase_to_rbs` now
1046
1650
  # absorbs `untyped` members and dedupes the post-erase strings (`String | String` → `String` for
1047
1651
  # distinct `Constant<"Alice">` / `Constant<"Bob">` envelopes), so the sig-gen layer only needs to
1048
1652
  # elaborate bare generics before erasing.
1049
- elaborated_rbs(Type::Combinator.union(*types))
1653
+ elaborated_rbs(Type::Combinator.union(*types), owner: owner)
1050
1654
  end
1051
1655
 
1052
1656
  # ADR-14 slice 4 — `attr_reader` / `attr_writer` / `attr_accessor` recognition. Each Symbol-named entry in
@@ -1062,6 +1666,7 @@ module Rigor
1062
1666
  attr_writer: [:writer],
1063
1667
  attr_accessor: %i[reader writer]
1064
1668
  }.freeze
1669
+ Ractor.make_shareable(ATTR_KINDS)
1065
1670
  private_constant :ATTR_KINDS
1066
1671
 
1067
1672
  # Per-file context the attr_* walker threads through its recursive descent. Keeps parameter lists in
@@ -1082,9 +1687,9 @@ module Rigor
1082
1687
 
1083
1688
  case node
1084
1689
  when Prism::ClassNode, Prism::ModuleNode
1085
- name = qualified_constant_path(node.constant_path)
1086
- if name
1087
- walk_attr_calls(node.body, prefix + [name], false, ctx) if node.body
1690
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
1691
+ if child_prefix
1692
+ walk_attr_calls(node.body, child_prefix, false, ctx) if node.body
1088
1693
  return
1089
1694
  end
1090
1695
  when Prism::SingletonClassNode
@@ -1166,9 +1771,9 @@ module Rigor
1166
1771
 
1167
1772
  case node
1168
1773
  when Prism::ClassNode, Prism::ModuleNode
1169
- name = qualified_constant_path(node.constant_path)
1170
- if name
1171
- collect_init_ivar_obs(node.body, prefix + [name], result) if node.body
1774
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
1775
+ if child_prefix
1776
+ collect_init_ivar_obs(node.body, child_prefix, result) if node.body
1172
1777
  return
1173
1778
  end
1174
1779
  when Prism::DefNode
@@ -1270,6 +1875,12 @@ module Rigor
1270
1875
  end
1271
1876
 
1272
1877
  def build_attr_candidate(class_name, method_name, variant, ivar_type, ctx)
1878
+ inline = inline_declarations.lookup(ctx.path, class_name, method_name, :instance)
1879
+ if inline
1880
+ return attr_skipped(ctx.path, class_name, method_name, :inline_declared) if skip_inline_declared?
1881
+ return inline_attr_candidate(class_name, method_name, inline, ctx) if inline.declared?
1882
+ end
1883
+
1273
1884
  if ivar_type.nil? || dynamic_top?(ivar_type)
1274
1885
  return attr_skipped(ctx.path, class_name, method_name, :untyped_return)
1275
1886
  end
@@ -1284,6 +1895,15 @@ module Rigor
1284
1895
  end
1285
1896
  end
1286
1897
 
1898
+ # ADR-112 WD4 — an attribute typed inline (`attr_reader :name #: String`) is written from that type, in
1899
+ # the long `def` form the rest of the attr path uses.
1900
+ def inline_attr_candidate(class_name, method_name, inline, ctx)
1901
+ scope = ctx.scope_index.each_value.first
1902
+ method_def = lookup_existing_method(class_name, method_name, :instance, scope&.environment, scope)
1903
+ rbs = "def #{method_name}: #{inline.method_types.join(' | ')}"
1904
+ inline_candidate(ctx.path, class_name, method_name, :instance, rbs, inline, method_def)
1905
+ end
1906
+
1287
1907
  def attr_new_candidate(path, class_name, method_name, variant, ivar_type)
1288
1908
  build_candidate(
1289
1909
  path: path,
@@ -1292,7 +1912,7 @@ module Rigor
1292
1912
  kind: :instance,
1293
1913
  classification: Classification::NEW_METHOD,
1294
1914
  inferred_return: ivar_type,
1295
- rbs: render_attr_rbs_line(method_name, variant, ivar_type)
1915
+ rbs: render_attr_rbs_line(method_name, variant, ivar_type, class_name)
1296
1916
  )
1297
1917
  end
1298
1918
 
@@ -1309,7 +1929,7 @@ module Rigor
1309
1929
  path: path, class_name: class_name, method_name: method_name,
1310
1930
  kind: :instance, classification: Classification::TIGHTER_RETURN,
1311
1931
  inferred_return: ivar_type, declared_return_rbs: declared_rbs,
1312
- rbs: render_attr_rbs_line(method_name, variant, ivar_type)
1932
+ rbs: render_attr_rbs_line(method_name, variant, ivar_type, class_name)
1313
1933
  )
1314
1934
  end
1315
1935
 
@@ -1333,8 +1953,8 @@ module Rigor
1333
1953
  # form can normalise post-emit; the writer-side member detection (slice 2) treats existing `attr_*`
1334
1954
  # declarations as user-authored so a paired source-side `attr_reader` never produces a duplicate `def`
1335
1955
  # insertion.
1336
- def render_attr_rbs_line(method_name, variant, ivar_type)
1337
- erased = elaborated_rbs(ivar_type)
1956
+ def render_attr_rbs_line(method_name, variant, ivar_type, owner = nil)
1957
+ erased = elaborated_rbs(ivar_type, owner: owner)
1338
1958
  wrapped = paren_wrap_union(erased)
1339
1959
  case variant
1340
1960
  when :reader then "def #{method_name}: () -> #{wrapped}"
@@ -1354,7 +1974,7 @@ module Rigor
1354
1974
  types = meta_member_types(class_name, layout.member_names)
1355
1975
  shape = MetaClassShape.of(
1356
1976
  kind: layout.kind, members: layout.member_names, keyword_init: layout.keyword_init,
1357
- member_types: types.transform_values { |type| paren_wrap_union(union_erase([type])) }
1977
+ member_types: types.transform_values { |type| paren_wrap_union(union_erase([type], owner: class_name)) }
1358
1978
  )
1359
1979
  shape.member_decls.filter_map do |member|
1360
1980
  meta_member_candidate(path, class_name, member, types, scope, environment)