rigortype 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ require_relative "../environment/rbs_loader"
6
+ require_relative "../rooted_glob"
7
+
8
+ module Rigor
9
+ module SigGen
10
+ # Issues #2603 and #2605 — the run-wide view of the parameterless headers of generic classes (`class Array`, as
11
+ # sig-gen spelled it before #2436) that `sig/` holds, read before the writer changes anything.
12
+ #
13
+ # rbs rejects a class with `GenericParameterMismatchError` while any one of its declarations opens it bare, so
14
+ # the repair is decided per class, across every file the project's RBS env loads from `signature_paths:`: either
15
+ # every bare opening of the class is repaired, or none is. A class is held back ({#left_bare}) when the body of
16
+ # any of its bare openings names one of the parameters as a relative constant ({.shadowing_names}), because the
17
+ # inserted parameter would capture that name, or when one of its bare openings lies outside the sig root, which
18
+ # the writer never rewrites. Every file under the sig root is read, not only those a candidate of the run routes
19
+ # to, so a bare header whose source file is gone is repaired too (#2603); the other signature paths are read
20
+ # and never written.
21
+ #
22
+ # A file that is not valid UTF-8, or does not parse, is not read: rbs never loads it, and the writer refuses it.
23
+ class GenericHeaderScan
24
+ # One bare opening: the file it is in, the class it opens, the parameter names its body spells, and whether
25
+ # the file is under the sig root (the writer may rewrite it).
26
+ Opening = Data.define(:target, :class_name, :names, :writable)
27
+
28
+ # @param sig_root — the directory the writer writes to; the only files it may rewrite.
29
+ # @param signature_dirs — the other directories the RBS env loads (`signature_paths:`), read only. A file
30
+ # under the sig root is read once, as a writable one.
31
+ # @param spelling_for — called with a class name, returns the header spelling to insert
32
+ # (`"[unchecked out E]"`), or nil for a class that is not generic with plain parameters only.
33
+ def self.scan(sig_root:, spelling_for:, signature_dirs: [])
34
+ new(spelling_for).tap do |scan|
35
+ scan.read_tree(sig_root, writable: true)
36
+ signature_dirs.each { |dir| scan.read_tree(dir, writable: false) }
37
+ end
38
+ end
39
+
40
+ # The names in `names` that the declaration's text after its name (superclass, members, nested declarations)
41
+ # spells as a relative constant (`E`, `E::X`, not `::E` or `Foo::E`). A method type that declares a name as
42
+ # its own type parameter (`def m: [E] (E) -> E`) binds every `E` inside it, so its text does not count for
43
+ # that name. The rest is matched as text, so a comment that spells the name counts.
44
+ def self.shadowing_names(source, decl, names)
45
+ start = decl.location[:name].end_pos
46
+ body = source[start...decl.location.end_pos]
47
+ names.select do |name|
48
+ masked = mask_binders(body, binder_ranges(decl, name), start)
49
+ masked.match?(/(?<![\w:])#{Regexp.escape(name)}(?!\w)/)
50
+ end
51
+ end
52
+
53
+ # The source ranges of the method types under `decl` (nested declarations included) that declare `name` as a
54
+ # type parameter of their own.
55
+ def self.binder_ranges(decl, name, ranges = [])
56
+ (decl.respond_to?(:members) ? decl.members : []).each do |member|
57
+ if member.is_a?(RBS::AST::Members::MethodDefinition)
58
+ member.overloads.each do |overload|
59
+ type = overload.method_type
60
+ next unless type.location && type.type_params.any? { |param| param.name.to_s == name }
61
+
62
+ ranges << (type.location.start_pos...type.location.end_pos)
63
+ end
64
+ end
65
+ binder_ranges(member, name, ranges)
66
+ end
67
+ ranges
68
+ end
69
+
70
+ def self.mask_binders(body, ranges, offset)
71
+ masked = body.dup
72
+ ranges.each do |range|
73
+ from = range.begin - offset
74
+ masked[from, range.size] = " " * range.size if from >= 0 && from + range.size <= masked.size
75
+ end
76
+ masked
77
+ end
78
+ private_class_method :mask_binders
79
+
80
+ # The parameter names `[unchecked out K, unchecked out V]` declares; nil when it does not parse.
81
+ def self.parameter_names(spelling)
82
+ _, _, decls = RBS::Parser.parse_signature("class RigorSigGenProbe#{spelling}\nend\n")
83
+ decls.first&.type_params&.map { |param| param.name.to_s }
84
+ rescue RBS::ParsingError
85
+ nil
86
+ end
87
+
88
+ def initialize(spelling_for)
89
+ @spelling_for = spelling_for
90
+ @openings = []
91
+ @seen = Set.new
92
+ end
93
+
94
+ # Every class to repair, with the spelling to insert: each bare opening of it, in every file read, is clear.
95
+ def repairs
96
+ @repairs ||= by_class.reject { |_, openings| openings.any? { |o| blocking?(o) } }
97
+ .to_h { |name, _| [name, spelling(name)] }
98
+ end
99
+
100
+ # The files that hold a bare opening of a class in {#repairs}, as their `to_s`.
101
+ def repair_targets
102
+ @openings.select { |o| repairs.key?(o.class_name) }.map(&:target).uniq
103
+ end
104
+
105
+ # One entry per class held back: `{ class:, targets:, blocking: [...] }`, where `targets` is every file with a
106
+ # bare opening of it and `blocking` the openings that hold it back, each `{ target:, reason:
107
+ # "outside_sig_root" }` (a file the writer does not rewrite) or `{ target:, reason: "names_parameter", names: }`.
108
+ def left_bare
109
+ by_class.filter_map do |name, openings|
110
+ blocking = openings.select { |o| blocking?(o) }
111
+ next if blocking.empty?
112
+
113
+ { class: name, targets: openings.map(&:target).uniq, blocking: blocking.map { |o| blocking_entry(o) } }
114
+ end
115
+ end
116
+
117
+ def read_tree(dir, writable:)
118
+ return unless dir.directory?
119
+
120
+ RootedGlob.glob(dir.to_s, "**/*.rbs").sort.each do |path|
121
+ key = realpath(path)
122
+ read_file(Pathname(path), writable) if @seen.add?(key)
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def blocking?(opening)
129
+ !opening.writable || !opening.names.empty?
130
+ end
131
+
132
+ def blocking_entry(opening)
133
+ return { target: opening.target, reason: "outside_sig_root" } unless opening.writable
134
+
135
+ { target: opening.target, reason: "names_parameter", names: opening.names }
136
+ end
137
+
138
+ def realpath(path)
139
+ File.realpath(path)
140
+ rescue SystemCallError
141
+ File.expand_path(path)
142
+ end
143
+
144
+ def by_class
145
+ @by_class ||= @openings.group_by(&:class_name).sort.to_h
146
+ end
147
+
148
+ def spelling(name)
149
+ @spellings ||= {}
150
+ return @spellings[name] if @spellings.key?(name)
151
+
152
+ @spellings[name] = @spelling_for.call(name)
153
+ end
154
+
155
+ def read_file(path, writable)
156
+ source = path.read
157
+ return unless source.valid_encoding?
158
+
159
+ _, _, decls = RBS::Parser.parse_signature(source)
160
+ collect([path.to_s, writable], source, decls, [])
161
+ rescue RBS::ParsingError, SystemCallError
162
+ nil
163
+ end
164
+
165
+ def collect(target, source, decls, prefix)
166
+ decls.each do |decl|
167
+ next unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
168
+
169
+ # RBS resolves `module Foo; class ::Set` to the top-level `Set`, and so does this key (#2654).
170
+ path = Environment::RbsLoader.declared_class_path(decl, prefix)
171
+ record(target, source, decl, path.join("::"))
172
+ collect(target, source, decl.members, path)
173
+ end
174
+ end
175
+
176
+ def record(target, source, decl, name)
177
+ return unless decl.type_params.empty? && decl.location&.[](:name)
178
+
179
+ spelled = spelling(name)
180
+ params = spelled && self.class.parameter_names(spelled)
181
+ return if params.nil?
182
+
183
+ file, writable = target
184
+ @openings << Opening.new(target: file, class_name: name, writable: writable,
185
+ names: self.class.shadowing_names(source, decl, params))
186
+ end
187
+ end
188
+ end
189
+ end
@@ -23,7 +23,7 @@ module Rigor
23
23
  class InlineDeclarations
24
24
  # One inline-declared method.
25
25
  #
26
- # - `method_types` — the declared overloads (`RBS::MethodType`, names as written, unresolved).
26
+ # - `method_types` — the declared overloads, names as written and unresolved.
27
27
  # - `annotations` — the annotation strings the author wrote on the member (`deprecated` for
28
28
  # `%a{deprecated}`), without the synthesizer's own `rigor:v1:inferred-*` markers.
29
29
  # - `return_inferred` — the author declared the parameters and not the return: rbs-inline defaulted it,
@@ -112,7 +112,7 @@ module Rigor
112
112
  decls.each do |decl|
113
113
  next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
114
114
 
115
- inner = prefix + [decl.name.to_s.delete_prefix("::")]
115
+ inner = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
116
116
  @generic_classes[inner.join("::")] ||= decl.type_params.map(&:name) unless decl.type_params.empty?
117
117
  decl.members.each { |member| block.call(inner.join("::"), member) }
118
118
  each_member(decl.members, inner, &block)
@@ -126,8 +126,8 @@ module Rigor
126
126
  return unless decl.is_a?(RBS::AST::Declarations::Class) ||
127
127
  decl.is_a?(RBS::AST::Declarations::Module)
128
128
 
129
- local_name = decl.name.to_s.sub(/\A::/, "")
130
- full = prefix.empty? ? local_name : "#{prefix.join('::')}::#{local_name}"
129
+ path = ::Rigor::Environment::RbsLoader.declared_class_path(decl, prefix)
130
+ full = path.join("::")
131
131
  accumulator[full] ||= rbs_path
132
132
  decl.members.each do |member|
133
133
  # An `overloading?` member (`def f: ... | ...`) adds to a declaration elsewhere; routing a candidate to
@@ -137,7 +137,7 @@ module Rigor
137
137
  self.class.member_keys(member).each { |name, kind| @members[[full, name, kind]] ||= rbs_path }
138
138
  end
139
139
 
140
- record_decls(decl.members, prefix + [local_name], rbs_path, accumulator)
140
+ record_decls(decl.members, path, rbs_path, accumulator)
141
141
  end
142
142
  end
143
143
  end
@@ -15,7 +15,7 @@ module Rigor
15
15
  # ever `skipped` (`sig.skipped.top-level-def`, #1676).
16
16
  # - `method_name` — the def's `Symbol` name.
17
17
  # - `kind` — `:instance` or `:singleton`.
18
- # - `inferred_return` — `Rigor::Type` instance (or `nil` when the inference pass disqualified the def).
18
+ # - `inferred_return` — the inferred return type (or `nil` when the inference pass disqualified the def).
19
19
  # - `declared_return_rbs` — the existing RBS-declared return spelling, or `nil` when no RBS declares the
20
20
  # method.
21
21
  # - `declared_rbs` — for an `inline_overwrite`, or a `tighter_return` over the class's own declaration, the
@@ -33,13 +33,14 @@ module Rigor
33
33
  class MethodCandidate
34
34
  attr_reader :path, :class_name, :method_name, :kind, :classification,
35
35
  :inferred_return, :declared_return_rbs, :rbs, :skip_reason,
36
- :namespace_kinds, :class_shells, :class_superclasses,
36
+ :namespace_kinds, :class_shells, :class_superclasses, :class_type_params,
37
37
  :annotations, :effect_reason, :declared_rbs, :declared_annotations
38
38
 
39
39
  def initialize(path:, class_name:, method_name:, kind:, classification:, # rubocop:disable Metrics/ParameterLists
40
40
  inferred_return: nil, declared_return_rbs: nil, rbs: nil, skip_reason: nil,
41
41
  namespace_kinds: {}, class_shells: [], class_superclasses: {},
42
- annotations: [], effect_reason: nil, declared_rbs: nil, declared_annotations: [])
42
+ annotations: [], effect_reason: nil, declared_rbs: nil, declared_annotations: [],
43
+ class_type_params: {})
43
44
  @path = path
44
45
  @class_name = class_name
45
46
  @method_name = method_name
@@ -55,6 +56,10 @@ module Rigor
55
56
  # superclasses appear; computed ones are absent. The Writer emits `class Bar < Base` for the leaf when
56
57
  # present.
57
58
  @class_superclasses = class_superclasses.freeze
59
+ # Issue #2436 — qualified class name => the type-parameter list a loaded declaration gives it, spelled for
60
+ # a header (`{ "Array" => "[unchecked out E]" }`). A header the writer opens for one of these classes
61
+ # carries the list; a class absent from the map is written without one.
62
+ @class_type_params = class_type_params.freeze
58
63
  @annotations = annotations.freeze
59
64
  @effect_reason = effect_reason
60
65
  @declared_rbs = declared_rbs
@@ -85,7 +90,35 @@ module Rigor
85
90
  declared_return_rbs: @declared_return_rbs, rbs: @rbs, skip_reason: @skip_reason,
86
91
  namespace_kinds: @namespace_kinds, class_shells: @class_shells,
87
92
  class_superclasses: @class_superclasses, annotations: annotations, effect_reason: reason,
88
- declared_rbs: @declared_rbs, declared_annotations: @declared_annotations
93
+ declared_rbs: @declared_rbs, declared_annotations: @declared_annotations,
94
+ class_type_params: @class_type_params
95
+ )
96
+ end
97
+
98
+ # A copy carrying the run's reopened-class type parameters (#2436).
99
+ def with_class_type_params(class_type_params)
100
+ self.class.new(
101
+ path: @path, class_name: @class_name, method_name: @method_name, kind: @kind,
102
+ classification: @classification, inferred_return: @inferred_return,
103
+ declared_return_rbs: @declared_return_rbs, rbs: @rbs, skip_reason: @skip_reason,
104
+ namespace_kinds: @namespace_kinds, class_shells: @class_shells,
105
+ class_superclasses: @class_superclasses, annotations: @annotations, effect_reason: @effect_reason,
106
+ declared_rbs: @declared_rbs, declared_annotations: @declared_annotations,
107
+ class_type_params: class_type_params
108
+ )
109
+ end
110
+
111
+ # A copy carrying more class shells: a class whose every member a later pass withheld still has to be
112
+ # declared for its subclasses' headers (#3029).
113
+ def with_class_shells(class_shells)
114
+ self.class.new(
115
+ path: @path, class_name: @class_name, method_name: @method_name, kind: @kind,
116
+ classification: @classification, inferred_return: @inferred_return,
117
+ declared_return_rbs: @declared_return_rbs, rbs: @rbs, skip_reason: @skip_reason,
118
+ namespace_kinds: @namespace_kinds, class_shells: class_shells,
119
+ class_superclasses: @class_superclasses, annotations: @annotations, effect_reason: @effect_reason,
120
+ declared_rbs: @declared_rbs, declared_annotations: @declared_annotations,
121
+ class_type_params: @class_type_params
89
122
  )
90
123
  end
91
124
 
@@ -4,6 +4,7 @@ require "prism"
4
4
 
5
5
  require_relative "../environment"
6
6
  require_relative "../project_environment"
7
+ require_relative "../run_scoped_slots"
7
8
  require_relative "../scope"
8
9
  require_relative "../type"
9
10
  require_relative "../source/constant_path"
@@ -19,9 +20,7 @@ module Rigor
19
20
  # Walks the `--observe=PATH...` tree (default: the project's test roots), parses every `.rb` file with `Prism`,
20
21
  # scope- indexes it the same way the main generator does, and records the per-call-site argument-type tuples
21
22
  # for every `Prism::CallNode` whose receiver types as a `Type::Nominal`. The {Generator} consumes the
22
- # resulting map to render `--params=observed` RBS:
23
- #
24
- # @return => Array<Array<Rigor::Type>>}]
23
+ # resulting map to render `--params=observed` RBS.
25
24
  #
26
25
  # ADR-5 clause 2 compliance: the observed union is the MOST PERMISSIVE parameter contract the existing
27
26
  # callers prove sufficient — by construction it accepts every type any caller has already passed. The
@@ -29,8 +28,8 @@ module Rigor
29
28
  # user opts in.
30
29
  #
31
30
  # MVP scope:
32
- # - Explicit-receiver calls only (`foo.bar(args)`). Implicit-self calls inside class bodies and RSpec-style
33
- # `let` / `subject` bindings ride on slice 5's optional `rigor-rspec` integration.
31
+ # - Explicit-receiver calls only (`foo.bar(args)`). Implicit-self calls inside class bodies are skipped;
32
+ # RSpec `let` / `subject` receivers are resolved from the spec file (ADR-14 slice 5).
34
33
  # - Calls whose receiver does not type as a `Type::Nominal` (e.g. `(some_dynamic).bar(...)`) are skipped —
35
34
  # the collector cannot attribute them to a specific class.
36
35
  # - A `T | nil` receiver with one `Nominal` / `Singleton` non-nil member counts as that member (#1389).
@@ -58,10 +57,18 @@ module Rigor
58
57
  environment = build_environment(resolved)
59
58
  discovered_classes = preindex_source_classes
60
59
  observations = Hash.new { |h, k| h[k] = [] }
61
- resolved.each do |path|
62
- collect_from_file(path, environment, discovered_classes, observations)
60
+ # ADR-130 WD5 — with interface dispatch off, as {Generator#run} analyses: an argument read through a
61
+ # `Rigor::Plugin` interface member is not written into a parameter type either.
62
+ Inference::MethodDispatcher::RbsDispatch.without_interface_dispatch do
63
+ resolved.each do |path|
64
+ collect_from_file(path, environment, discovered_classes, observations)
65
+ end
63
66
  end
64
67
  observations.transform_values(&:freeze).freeze
68
+ ensure
69
+ # Issue #2869 — release this analysis's run-scoped slots, as every other entry point that runs one does, so
70
+ # a later run (the generator's, under its own interface-dispatch install) starts from none of them.
71
+ RunScopedSlots.release
65
72
  end
66
73
 
67
74
  # Whether `arg` names something {#collect} reads: a directory, or a `.rb` file. The CLI's
@@ -3,7 +3,7 @@
3
3
  module Rigor
4
4
  module SigGen
5
5
  # Per-call-site argument observation produced by {ObservationCollector}. ADR-14 follow-up: the earlier MVP
6
- # shape (`Array[Type]` of positional types only) could not represent keyword arguments — keyword calls like
6
+ # shape (positional types only) could not represent keyword arguments — keyword calls like
7
7
  # `MethodCatalog.new(path: ..., mutating_selectors: ...)` were silently skipped in that shape. The new shape
8
8
  # carries positional and keyword arg types in parallel so the per-position / per-keyword unions can each be
9
9
  # reconstructed independently.
@@ -74,6 +74,12 @@ module Rigor
74
74
  @sig_root_dir ||= @project_root / sig_root_name
75
75
  end
76
76
 
77
+ # Every directory the project's RBS env loads signatures from (`signature_paths:`), resolved against the
78
+ # project root. The writer reads them to decide a repair (#2605) and writes only under {#sig_root_dir}.
79
+ def signature_dirs
80
+ Array(@configuration.signature_paths).map { |path| @project_root / path }
81
+ end
82
+
77
83
  private
78
84
 
79
85
  def source_relative_to_root(source_path)
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/node_children"
6
+
7
+ module Rigor
8
+ module SigGen
9
+ # Issue #3035 — the files a sig-gen run must not read as ordinary definitions: every scanned `.rb`
10
+ # file Ruby loads ONLY through a platform-conditional `require` / `require_relative`.
11
+ #
12
+ # `stackprof.rb` opens with `if RUBY_ENGINE == 'truffleruby'` → `require "stackprof/truffleruby"`,
13
+ # and `truffleruby.rb` re-implements `StackProf.run` / `.results` as raising stubs — on MRI the C
14
+ # extension defines them. Scanning every `.rb` under `lib/` reads that stub file as an ordinary
15
+ # definition and proposes `StackProf.run -> bot` against a method whose real signature returns
16
+ # `Hash[Symbol, untyped]`.
17
+ #
18
+ # {.call} answers the guarded set from the resolved file list alone: it parses each file once,
19
+ # collects the literal `require` / `require_relative` sites, marks a site guarded when it sits
20
+ # inside a conditional whose predicate reads `RUBY_ENGINE`, `RUBY_PLATFORM` or `defined?(…)`,
21
+ # resolves each feature against the scanned set (`require_relative` against the file's own
22
+ # directory; `require` against the file's ancestor directories — the load-path approximation a
23
+ # gem's `lib/` root satisfies), and keeps the files whose EVERY incoming edge is guarded —
24
+ # directly, or through a file already known guarded (a helper `truffleruby.rb` requires
25
+ # unconditionally is still truffleruby-only). A file with no scanned require site is never
26
+ # guarded: an entry point, an autoload target or a `load`ed file cannot be told apart from a
27
+ # guarded one, and the unguarded reading is the one that emits.
28
+ #
29
+ # Limits, deliberately: require sites in files OUTSIDE the scanned set are invisible (a sibling
30
+ # the run did not read may require the file unguarded); `load`, `autoload` and computed features
31
+ # are not edges; a `require` sitting in the guard's own predicate — an `&&` / `||` operand, say —
32
+ # inherits the branch's guarded mark without the walk checking whether Ruby reaches it before the
33
+ # platform test decides; the `begin … rescue LoadError` fallback (`require "json"` else
34
+ # `require "json/pure"`) is not a platform guard, so both of its edges read unguarded; and a file
35
+ # that fails to parse poisons the whole answer — its require edges are unknowable, so {.call}
36
+ # returns an empty set rather than risk a false guarded verdict.
37
+ module PlatformGuardedFiles
38
+ # The constants a conditional predicate may read to make a require platform-dependent.
39
+ GUARD_CONSTANTS = %i[RUBY_ENGINE RUBY_PLATFORM].freeze
40
+ private_constant :GUARD_CONSTANTS
41
+
42
+ # The conditional forms whose whole subtree a platform predicate guards — `if` / `unless` in
43
+ # statement, modifier and ternary spelling, `case` and `case/in` on a guarded subject. Both
44
+ # branches count: `else` of `RUBY_ENGINE == 'x'` is `RUBY_ENGINE != 'x'`, still conditional.
45
+ CONDITIONALS = [Prism::IfNode, Prism::UnlessNode, Prism::CaseNode, Prism::CaseMatchNode].freeze
46
+ private_constant :CONDITIONALS
47
+
48
+ REQUIRE_CALLS = %i[require require_relative].freeze
49
+ private_constant :REQUIRE_CALLS
50
+
51
+ module_function
52
+
53
+ # @param paths — the `.rb` files the run will analyse, as spelled by the run.
54
+ # @param ruby_version — the Prism version to parse with (the configuration's `target_ruby`).
55
+ # @return a Set of `File.expand_path`'d paths that are required only under platform guards.
56
+ def call(paths, ruby_version: nil)
57
+ files = paths.map { |path| File.expand_path(path) }.uniq
58
+ known = files.to_set
59
+ edges = Hash.new { |hash, key| hash[key] = [] }
60
+ files.each do |file|
61
+ sites = require_sites(file, ruby_version, known)
62
+ return Set.new if sites.nil?
63
+
64
+ sites.each { |target, guarded| edges[target] << { from: file, guarded: guarded } }
65
+ end
66
+ guarded_closure(files, edges)
67
+ end
68
+
69
+ # Every `[target, guarded]` pair `file`'s literal require sites resolve to inside `known`;
70
+ # `nil` when the file does not parse (see the class comment for why that voids the answer).
71
+ def require_sites(file, ruby_version, known)
72
+ result = Prism.parse(File.read(file), filepath: file, version: ruby_version)
73
+ return nil if result.errors.any?
74
+
75
+ sites = []
76
+ walk_require_sites(result.value) do |node, guarded|
77
+ target = require_target(file, node, known)
78
+ sites << [target, guarded] if target
79
+ end
80
+ sites
81
+ end
82
+
83
+ # An explicit stack, not recursion: a require site deep in a generated file must not exhaust it.
84
+ def walk_require_sites(root, &block)
85
+ stack = [[root, false]]
86
+ until stack.empty?
87
+ node, guarded = stack.pop
88
+ next unless node.is_a?(Prism::Node)
89
+
90
+ block.call(node, guarded) if require_call?(node)
91
+ child_guarded = guarded || platform_conditional?(node)
92
+ node.rigor_each_child { |child| stack << [child, child_guarded] }
93
+ end
94
+ end
95
+
96
+ # A `require "feature"` / `require_relative "feature"` with one string literal and no receiver —
97
+ # the same strictness {Analysis::RequireRelativeClosure} reads `require_relative` with.
98
+ def require_call?(node)
99
+ return false unless node.is_a?(Prism::CallNode) && node.receiver.nil?
100
+ return false unless REQUIRE_CALLS.include?(node.name)
101
+
102
+ arguments = node.arguments&.arguments
103
+ arguments&.size == 1 && arguments.first.is_a?(Prism::StringNode)
104
+ end
105
+
106
+ # Whether `node` is a conditional whose predicate makes what it guards platform-dependent: a
107
+ # `RUBY_ENGINE` / `RUBY_PLATFORM` read or a `defined?(…)` anywhere in it.
108
+ def platform_conditional?(node)
109
+ return false unless CONDITIONALS.any? { |klass| node.is_a?(klass) }
110
+
111
+ predicate = node.predicate
112
+ !predicate.nil? && platform_expression?(predicate)
113
+ end
114
+
115
+ # Iterative like {#walk_require_sites}: a predicate can chain thousands of `||` operands. A hit is
116
+ # the only answer — a constant that is NOT a guard (`RUBY_VERSION`, `ENV`, a namespaced path) does
117
+ # not end the walk, or `RUBY_ENGINE == "x" && RUBY_VERSION >= "y"` would read unguarded on the
118
+ # strength of which operand the stack pops first.
119
+ def platform_expression?(root)
120
+ stack = [root]
121
+ until stack.empty?
122
+ node = stack.pop
123
+ next unless node.is_a?(Prism::Node)
124
+ return true if node.is_a?(Prism::DefinedNode)
125
+ return true if node.is_a?(Prism::ConstantReadNode) && GUARD_CONSTANTS.include?(node.name)
126
+ # `::RUBY_ENGINE` names the same constant; `Foo::RUBY_ENGINE` is a different one.
127
+ return true if node.is_a?(Prism::ConstantPathNode) && node.parent.nil? &&
128
+ GUARD_CONSTANTS.include?(node.name)
129
+
130
+ node.rigor_each_child { |child| stack << child }
131
+ end
132
+ false
133
+ end
134
+
135
+ # The scanned file `node`'s feature resolves to, or nil when it names nothing the run reads
136
+ # (a gem, a `.so`, a computed path's sibling).
137
+ def require_target(site_file, node, known)
138
+ feature = node.arguments.arguments.first.unescaped
139
+ feature += ".rb" unless feature.end_with?(".rb")
140
+ if node.name == :require_relative
141
+ candidate = File.expand_path(feature, File.dirname(site_file))
142
+ return candidate if known.include?(candidate)
143
+
144
+ return nil
145
+ end
146
+
147
+ # `require` resolves through the load path; the file's own directory upward is the
148
+ # approximation that finds `lib/stackprof/truffleruby.rb` for `require "stackprof/truffleruby"`
149
+ # in `lib/stackprof.rb`, nearest ancestor first.
150
+ dir = File.dirname(site_file)
151
+ loop do
152
+ candidate = File.expand_path(feature, dir)
153
+ return candidate if known.include?(candidate)
154
+
155
+ parent = File.dirname(dir)
156
+ return nil if parent == dir
157
+
158
+ dir = parent
159
+ end
160
+ end
161
+
162
+ # The fixpoint: a file is guarded-only when it has at least one scanned require edge and every
163
+ # edge reaching it is either syntactically guarded or issued from a file already guarded-only.
164
+ def guarded_closure(files, edges)
165
+ guarded = Set.new
166
+ loop do
167
+ added = false
168
+ files.each do |file|
169
+ next if guarded.include?(file)
170
+
171
+ incoming = edges[file]
172
+ next if incoming.empty?
173
+ next unless incoming.all? { |edge| edge[:guarded] || guarded.include?(edge[:from]) }
174
+
175
+ guarded << file
176
+ added = true
177
+ end
178
+ break unless added
179
+ end
180
+ guarded
181
+ end
182
+ end
183
+ end
184
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "json"
4
4
 
5
+ require_relative "../cli/json_document"
6
+
5
7
  require_relative "classification"
6
8
  require_relative "superclass_spelling"
7
9
  require_relative "write_reporter"
@@ -14,8 +16,7 @@ module Rigor
14
16
  # - `:print` (default) — RBS skeletons grouped by source file and class declaration, ready for the user to
15
17
  # paste into `sig/<path>.rbs`.
16
18
  # - `:diff` — a unified-style diff comparing the existing RBS spelling (if any) against the inferred
17
- # spelling. The MVP renders a minimal "- declared / + inferred" block; full per-file diffing arrives with
18
- # slice 2's `--write` merge.
19
+ # spelling, as a minimal "- declared / + inferred" block.
19
20
  # - `:json` — machine-readable payload with the same classification table as `:print`, plus every `skipped`
20
21
  # row with its `skip_reason` (#778).
21
22
  class Renderer
@@ -95,11 +96,14 @@ module Rigor
95
96
  # mode used to hard-code `class`, which turned a module into a class the moment it held an emittable method —
96
97
  # output that raises `RBS::DuplicatedDeclarationError` on load if the real `module` is declared anywhere else
97
98
  # (#227). Defaulting to `class` when the map has no entry keeps the pre-existing spelling for a leaf class.
99
+ # A generic class the run reopens carries its declaration's type parameters, as the written header does (#2436).
98
100
  def declaration_header(candidate, class_name)
99
- return "module #{class_name}" if candidate.namespace_kinds[class_name] == :module
101
+ params = candidate.class_type_params[class_name]
102
+ return "module #{class_name}#{params}" if candidate.namespace_kinds[class_name] == :module
100
103
 
101
104
  superclass = candidate.class_superclasses[class_name]
102
- superclass ? "class #{class_name} < #{SuperclassSpelling.absolute(superclass)}" : "class #{class_name}"
105
+ head = "class #{class_name}#{params}"
106
+ superclass ? "#{head} < #{SuperclassSpelling.absolute(superclass)}" : head
103
107
  end
104
108
 
105
109
  def render_diff(candidates)
@@ -130,19 +134,21 @@ module Rigor
130
134
  payload = { candidates: candidates.map(&:to_h) }
131
135
  payload[:observation] = observation if observation
132
136
  payload[:diagnostics] = diagnostics.map(&:to_h) if diagnostics.any?
133
- @out.puts(JSON.pretty_generate(payload))
137
+ @out.puts(JSON.pretty_generate(CLI::JsonDocument.stamp(payload)))
134
138
  end
135
139
 
136
140
  public
137
141
 
138
142
  # The per-source-file outcomes of a `--write` run, and the `--check` verdict, are reported by
139
143
  # {WriteReporter}; these delegate so the command keeps one renderer.
140
- def render_write(results:, format:, refused: [])
141
- WriteReporter.new(out: @out).render_write(results: results, format: format, refused: refused)
144
+ def render_write(results:, format:, refused: [], left_bare: [])
145
+ WriteReporter.new(out: @out).render_write(results: results, format: format, refused: refused,
146
+ left_bare: left_bare)
142
147
  end
143
148
 
144
- def render_check(results:, format:, refused: [])
145
- WriteReporter.new(out: @out).render_check(results: results, format: format, refused: refused)
149
+ def render_check(results:, format:, refused: [], left_bare: [])
150
+ WriteReporter.new(out: @out).render_check(results: results, format: format, refused: refused,
151
+ left_bare: left_bare)
146
152
  end
147
153
 
148
154
  def self.refusal_lines(refused)