rigortype 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ module Rigor
6
+ class Environment
7
+ # Issue #2422 — what a bundled plugin's deferred signature file loads as, against the environment every other
8
+ # signature source has already built. The #610 arity check stands a file down whole first
9
+ # (`RbsLoader.generic_arity_conflict`); {.plan} decides the rest declaration by declaration and member by
10
+ # member, with {GatedSignaturePlan}'s #1713 helpers:
11
+ #
12
+ # - a method another source declares (a `def`, an attribute or an alias), where that source is NOT the project's own
13
+ # (an `rbs collection install`, a gem's `sig/`, a vendored or another plugin's file), is *composed*: the plugin's
14
+ # `def` becomes an overload continuation, `def m: <plugin overloads> | ...`, so RBS puts the plugin's overloads
15
+ # first and keeps the other source's as the fallback. The plugin's signatures are often written wider on purpose
16
+ # (`Object#html_safe?: () -> bool` where the collection says `-> false`), and standing them down would let the
17
+ # narrower one fold correct code into dead branches;
18
+ # - any other method, attribute or alias another source declares stands down: the project's own `sig/` or
19
+ # inline RBS binds by #610's rule, and an attribute, an alias or one side of a `def self?.m` cannot be
20
+ # continued ({GatedSignaturePlan.untaken} narrows an `attr_accessor` or `self?` to its untaken half);
21
+ # - a constant, global, type alias, interface or class alias another source already declares, and a type
22
+ # whose kind (class or module) contradicts another source's, stands down on its own, where adding it would
23
+ # have raised `RBS::DuplicatedDeclarationError` and dropped the plugin's whole file;
24
+ # - a class header's superclass clause another source already gives the class one stands down (#2439), where two
25
+ # different clauses would have raised `RBS::SuperclassMismatchError`; the plugin's declaration reopens the class.
26
+ #
27
+ # A declaration with nothing taken comes back as the same object, so a file with no collision loads as it
28
+ # stands. {.standdown_rows} reads what stood down back off the built environment, so a warm run off the env
29
+ # cache reports what the cold run decided.
30
+ module DeferredSignaturePlan
31
+ module_function
32
+
33
+ # @param own — the buffer names that are the project's own sources: its `signature_paths:` files (not the
34
+ # collection or gem directories also on that list) and its inline RBS.
35
+ def plan(decls, env, own, namespace = nil)
36
+ planned = decls.filter_map { |decl| plan_declaration(decl, env, own, namespace) }
37
+ same?(planned, decls) ? decls : planned
38
+ end
39
+
40
+ def plan_declaration(decl, env, own, namespace)
41
+ name = GatedSignaturePlan.qualify(namespace, declared_name(decl))
42
+ if GatedSignaturePlan.type_declaration?(decl)
43
+ return nil if kind_clash?(env, decl, name)
44
+
45
+ return plan_type(decl, env, own, name)
46
+ end
47
+ declared_elsewhere(env, decl, name) ? nil : decl
48
+ end
49
+
50
+ def plan_type(decl, env, own, name)
51
+ others = GatedSignaturePlan.other_declarations(env, name)
52
+ taken = GatedSignaturePlan.declared_keys(others)
53
+ external = others.reject { |other| own.include?(buffer_of(other)) }
54
+ defined = GatedSignaturePlan.declared_keys(external) | definition_keys(external)
55
+ members = decl.members.filter_map do |member|
56
+ next plan_declaration(member, env, own, name) if declaration?(member)
57
+ next member if taken.empty?
58
+
59
+ composed(member, defined) || GatedSignaturePlan.untaken(member, taken)
60
+ end
61
+ drop = superclass_given?(decl, others)
62
+ return decl if same?(members, decl.members) && !drop
63
+
64
+ GatedSignaturePlan.rebuild(decl, members: members, drop_super_class: drop)
65
+ end
66
+
67
+ # Issue #2439 — true when `decl` is a class with a superclass clause and another source (`others`) already
68
+ # gives the class one. Two different clauses raise `RBS::SuperclassMismatchError` and the class and every
69
+ # descendant read `Dynamic[top]`, so the plugin's header stands down and reopens the class without one. The
70
+ # two names are not compared: they are not resolved yet, and the other source's superclass binds either way.
71
+ def superclass_given?(decl, others)
72
+ super_class?(decl) && others.any? { |other| super_class?(other) }
73
+ end
74
+
75
+ # `member` as an overload continuation, or nil when it cannot be one: only a plain one-sided `def` can, and
76
+ # only onto a method — a `def`, an attribute or an alias, all of which RBS continues — that a source other
77
+ # than the project's own declares.
78
+ def composed(member, defined)
79
+ return nil unless member.is_a?(::RBS::AST::Members::MethodDefinition) && !member.overloading?
80
+ return nil if member.kind == :singleton_instance
81
+
82
+ key = [member.name, member.kind]
83
+ return nil unless defined.include?(key)
84
+
85
+ member.update(overloading: true)
86
+ end
87
+
88
+ # The `[name, kind]` keys a plain or continued `def` in `decls` declares — what a continuation can extend.
89
+ def definition_keys(decls)
90
+ decls.each_with_object(Set.new) do |decl, keys|
91
+ decl.members.each do |member|
92
+ next unless member.is_a?(::RBS::AST::Members::MethodDefinition)
93
+
94
+ sides = member.kind == :singleton_instance ? %i[instance singleton] : [member.kind]
95
+ sides.each { |side| keys << [member.name, side] }
96
+ end
97
+ end
98
+ end
99
+
100
+ def kind_clash?(env, decl, name)
101
+ type_name = ::RBS::TypeName.parse("::#{name}")
102
+ return true if env.class_alias_decls.key?(type_name)
103
+
104
+ first = GatedSignaturePlan.other_declarations(env, name).first
105
+ !first.nil? && !first.instance_of?(decl.class)
106
+ end
107
+
108
+ # The buffer of the declaration another source already makes under `decl`'s name, or nil.
109
+ def declared_elsewhere(env, decl, name)
110
+ entry = declaration_entry(env, decl, name)
111
+ entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
112
+ end
113
+
114
+ def declaration_entry(env, decl, name)
115
+ type_name = -> { ::RBS::TypeName.parse("::#{name}") }
116
+ case decl
117
+ when ::RBS::AST::Declarations::Constant then env.constant_decls[type_name.call]
118
+ when ::RBS::AST::Declarations::Global then env.global_decls[decl.name]
119
+ when ::RBS::AST::Declarations::TypeAlias then env.type_alias_decls[type_name.call]
120
+ when ::RBS::AST::Declarations::Interface then env.interface_decls[type_name.call]
121
+ when ::RBS::AST::Declarations::ClassAlias, ::RBS::AST::Declarations::ModuleAlias
122
+ env.class_alias_decls[type_name.call] || env.class_decls[type_name.call]
123
+ end
124
+ end
125
+
126
+ # A class or module alias names itself as `new_name`; every other declaration as `name`.
127
+ def declared_name(decl)
128
+ decl.respond_to?(:new_name) ? decl.new_name : decl.name
129
+ end
130
+
131
+ def declaration?(member)
132
+ member.is_a?(::RBS::AST::Declarations::Base)
133
+ end
134
+
135
+ def same?(planned, original)
136
+ planned.size == original.size && planned.zip(original).all? { |a, b| a.equal?(b) }
137
+ end
138
+
139
+ def buffer_of(decl)
140
+ RbsLoader.declaration_buffer_name(decl)
141
+ end
142
+
143
+ # One {.standdown_rows} walk over a deferred file: the built env, the file's buffer name, the rows so far, and
144
+ # the env's type-name resolver, built on first use (only a dropped superclass clause needs it).
145
+ class Walk
146
+ attr_reader :env, :file, :rows
147
+
148
+ def initialize(env, file)
149
+ @env = env
150
+ @file = file
151
+ @rows = []
152
+ end
153
+
154
+ # rbs 4.x builds the resolver with `TypeNameResolver.build(env)`; rbs 3.x's constructor takes the env, which
155
+ # the 4.x signature Rigor reads (`new(all_names, aliases)`) does not describe.
156
+ def resolver
157
+ @resolver ||= begin
158
+ klass = ::RBS::Resolver::TypeNameResolver
159
+ if klass.respond_to?(:build)
160
+ klass.build(env)
161
+ else
162
+ klass.new(env) # rigor:disable call.wrong-arity
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ # What {.plan} left out of the deferred `file` (its parsed `decls`) in the built `env`, as `[declaration,
169
+ # cause_file]` rows — `Object#acts_like?`, `Time.at`, `FFI::Pointer::NULL`, `Foo < Bar`, or a type name — with
170
+ # the buffer of the other source's declaration, or nil when it cannot be named. A composed method is not a row:
171
+ # the plugin's overloads still bind.
172
+ def standdown_rows(env, file, decls)
173
+ walk = Walk.new(env, file)
174
+ decls.each { |decl| declaration_rows(walk, decl, nil) }
175
+ walk.rows.uniq
176
+ end
177
+
178
+ # `context` is RBS's resolver context for where `decl` is written: nil at the top level, else `[outer,
179
+ # absolute name of the enclosing declaration]`, as `RBS::Environment#append_context` builds it.
180
+ def declaration_rows(walk, decl, context)
181
+ name = GatedSignaturePlan.qualify(namespace_of(context), declared_name(decl))
182
+ return type_rows(walk, decl, name, context) if GatedSignaturePlan.type_declaration?(decl)
183
+
184
+ cause = displaced_by(walk.env, decl, name, walk.file)
185
+ walk.rows << [decl.is_a?(::RBS::AST::Declarations::Global) ? decl.name.to_s : name, cause] if cause
186
+ end
187
+
188
+ def namespace_of(context)
189
+ context && context.last.to_s.delete_prefix("::")
190
+ end
191
+
192
+ # A non-nil buffer name (possibly empty) when another source's declaration of `decl` is the one loaded.
193
+ def displaced_by(env, decl, name, file)
194
+ entry = declaration_entry(env, decl, name)
195
+ return nil if entry.nil?
196
+
197
+ buffer = RbsLoader.declaration_buffer_name(entry.decl)
198
+ buffer == file ? nil : buffer
199
+ end
200
+
201
+ def type_rows(walk, decl, name, context)
202
+ loaded, others = loaded_and_others(walk, name)
203
+ if loaded.empty?
204
+ cause = others.empty? ? alias_cause(walk.env, name) : buffer_of(others.first)
205
+ walk.rows << [name, cause] if cause
206
+ return
207
+ end
208
+
209
+ walk.rows.concat(superclass_rows(walk, decl, name, context, others))
210
+ walk.rows.concat(member_rows(decl, name, loaded, others))
211
+ inner = [context, ::RBS::TypeName.parse("::#{name}")]
212
+ decl.members.each { |member| declaration_rows(walk, member, inner) if declaration?(member) }
213
+ end
214
+
215
+ # The declarations of `name` the walked file loaded, and the ones other sources made (synthesized stubs left out).
216
+ def loaded_and_others(walk, name)
217
+ entry = walk.env.class_decls[::RBS::TypeName.parse("::#{name}")]
218
+ declarations = entry.nil? ? [] : RbsLoader.entry_declarations(entry)
219
+ loaded = declarations.select { |other| buffer_of(other) == walk.file }
220
+ [loaded, declarations.reject { |other| loaded.include?(other) || GatedSignaturePlan.synthetic?(other) }]
221
+ end
222
+
223
+ # The buffer of a class or module alias declaring `name` (empty when it cannot be named), or nil.
224
+ def alias_cause(env, name)
225
+ entry = env.class_alias_decls[::RBS::TypeName.parse("::#{name}")]
226
+ entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
227
+ end
228
+
229
+ # The plugin's superclass clause as a row (`ActiveRecord::StatementInvalid < ActiveRecordError`) when the plan
230
+ # dropped it for another source's ({.superclass_given?}) and that source's names a different class or type
231
+ # arguments. The plan drops an identical clause too, but nothing is lost, so no row.
232
+ def superclass_rows(walk, decl, name, context, others)
233
+ return [] unless superclass_given?(decl, others)
234
+
235
+ cause = others.find { |other| super_class?(other) }
236
+ return [] if same_super_class?(walk, decl.super_class, context, cause.super_class)
237
+
238
+ written = decl.super_class
239
+ args = written.args.empty? ? "" : "[#{written.args.join(', ')}]"
240
+ [["#{name} < #{written.name}#{args}", buffer_of(cause)]]
241
+ end
242
+
243
+ # Whether the plugin's clause `written`, resolved as RBS resolves a superclass (in the context the class is
244
+ # written in, not its own), names the class and type arguments of `other`, the built env's already-absolute
245
+ # clause, class aliases normalised on both sides. An unresolvable name differs.
246
+ def same_super_class?(walk, written, context, other)
247
+ name = absolute_name(walk, written.name, context)
248
+ return false if name.nil? || normalized(walk.env, name) != normalized(walk.env, other.name)
249
+
250
+ written.args.map { |arg| normalized_type(walk, arg, context) } ==
251
+ other.args.map { |arg| normalized_type(walk, arg, nil) }
252
+ end
253
+
254
+ def absolute_name(walk, name, context)
255
+ name.absolute? ? name : walk.resolver.resolve(name, context: context)
256
+ end
257
+
258
+ def normalized(env, name)
259
+ name.class? ? env.normalize_module_name(name) : name
260
+ end
261
+
262
+ def normalized_type(walk, type, context)
263
+ return type.to_s unless type.respond_to?(:map_type_name)
264
+
265
+ type.map_type_name { |name, _, _| normalized(walk.env, absolute_name(walk, name, context) || name) }.to_s
266
+ end
267
+
268
+ def super_class?(decl)
269
+ decl.is_a?(::RBS::AST::Declarations::Class) && !decl.super_class.nil?
270
+ end
271
+
272
+ def member_rows(decl, name, loaded, others)
273
+ present = GatedSignaturePlan.declared_keys(loaded) | definition_keys(loaded)
274
+ decl.members.flat_map do |member|
275
+ RbsLoader.member_method_keys(member).reject { |key| present.include?(key) }.filter_map do |key|
276
+ cause = others.find { |other| other.members.any? { |m| RbsLoader.member_method_keys(m).include?(key) } }
277
+ cause && [GatedSignaturePlan.member_label(name, key), buffer_of(cause)]
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -13,7 +13,7 @@ module Rigor
13
13
  module GatedSignatureGuard
14
14
  module_function
15
15
 
16
- # @param dir — the vendored directory (Pathname).
16
+ # @param dir — the vendored directory.
17
17
  # @return the absolute-less names (`"Prime::PseudoPrimeGenerator"`) of every class and module it declares.
18
18
  def type_names(dir)
19
19
  RootedGlob.glob(dir, "*.rbs").flat_map do |file|
@@ -213,10 +213,13 @@ module Rigor
213
213
  end
214
214
  end
215
215
 
216
- def rebuild(decl, members:)
216
+ # `decl` with `members` in place of its own. A class keeps its superclass unless `drop_super_class` (#2439: a
217
+ # plugin's class header another source already gives a superclass reopens the class without one).
218
+ def rebuild(decl, members:, drop_super_class: false)
217
219
  if decl.is_a?(::RBS::AST::Declarations::Class)
218
220
  ::RBS::AST::Declarations::Class.new(
219
- name: decl.name, type_params: decl.type_params, super_class: decl.super_class, members: members,
221
+ name: decl.name, type_params: decl.type_params, super_class: drop_super_class ? nil : decl.super_class,
222
+ members: members,
220
223
  annotations: decl.annotations, location: decl.location, comment: decl.comment
221
224
  )
222
225
  else
@@ -7,7 +7,7 @@ module Rigor
7
7
  # Gemfile.lock parser for target-project bundler awareness (O4 Layer 3, implemented).
8
8
  #
9
9
  # Parses a target project's `Gemfile.lock` via Bundler's `LockfileParser` and exposes the locked gem set
10
- # as a frozen `Hash[String, LockfileResolver::LockedGem]` keyed by gem name. Used by
10
+ # as a frozen map from gem name to `LockedGem`. Used by
11
11
  # {Rigor::Environment::BundleSigDiscovery} as a filter so the discovered `sig/` directories under the
12
12
  # bundler install root are limited to gems the project actually declares (and at the version it declared
13
13
  # them).
@@ -38,8 +38,12 @@ module Rigor
38
38
  # variant for the readers that want a single version (coverage report, doctor): the `ruby` one when the
39
39
  # lockfile has it, otherwise the last parsed. Defaults to the single `[version, platform]` pair, and the
40
40
  # pair must be one of `variants`.
41
- LockedGem = Data.define(:name, :version, :platform, :git_source, :variants) do
42
- def initialize(name:, version:, platform:, git_source: false, variants: nil)
41
+ #
42
+ # `path_source` is the absolute directory a lockfile `PATH` stanza resolved this gem from (its `remote:`
43
+ # expanded against the lockfile's directory), and `nil` for every other source, `GIT` included. Issue
44
+ # #2441 reads it to tell the project's own path gems apart from the rest of the bundle. Defaults `nil`.
45
+ LockedGem = Data.define(:name, :version, :platform, :git_source, :variants, :path_source) do
46
+ def initialize(name:, version:, platform:, git_source: false, variants: nil, path_source: nil)
43
47
  version = -version.to_s
44
48
  platform = -platform.to_s
45
49
  variants = (variants || [[version, platform]]).map { |v, p| [-v.to_s, -p.to_s].freeze }.uniq.freeze
@@ -52,7 +56,8 @@ module Rigor
52
56
  version: version,
53
57
  platform: platform,
54
58
  git_source: git_source ? true : false,
55
- variants: variants
59
+ variants: variants,
60
+ path_source: path_source && -path_source.to_s
56
61
  )
57
62
  end
58
63
  end
@@ -115,9 +120,9 @@ module Rigor
115
120
  def self.do_parse_dependency_names(path)
116
121
  parser = Bundler::LockfileParser.new(File.read(path.to_s))
117
122
  # `#dependencies` is the parsed `DEPENDENCIES` section keyed by gem name, so the version constraint
118
- # and the pinned-source `!` suffix are already stripped for us. The upstream bundler RBS shim does
119
- # not declare it.
120
- parser.dependencies.keys.to_set { |name| -name.to_s }.freeze # rigor:disable undefined-method
123
+ # and the pinned-source `!` suffix are already stripped for us. Rigor's vendored bundler
124
+ # signature declares it as a Hash keyed by gem name.
125
+ parser.dependencies.keys.to_set { |name| -name.to_s }.freeze
121
126
  rescue ::Ractor::IsolationError => e
122
127
  warn_isolated(path, e)
123
128
  EMPTY_NAMES
@@ -171,10 +176,9 @@ module Rigor
171
176
  parser = Bundler::LockfileParser.new(body)
172
177
  locked = parser.specs.each_with_object({}) do |spec, h|
173
178
  # `Bundler::LazySpecification` carries name, version, platform. Platform is `Gem::Platform` or the
174
- # symbol `:ruby`; both stringify cleanly. The upstream bundler RBS shim
175
- # (references/rbs/sig/shims/bundler.rbs) does NOT declare `LazySpecification#platform` so the call
176
- # site needs a suppression marker.
177
- platform = spec.platform.to_s # rigor:disable undefined-method
179
+ # symbol `:ruby`; both stringify cleanly. Rigor's vendored bundler signature declares
180
+ # `LazySpecification#platform`, so the call needs no suppression.
181
+ platform = spec.platform.to_s
178
182
  # `#source` is the `Bundler::Source::*` this spec resolved from — `Git` for a `GIT` lockfile
179
183
  # stanza, `Rubygems` for a `GEM` one, `Path`/`Gemspec` for `path:`. Only `Git` sources land under
180
184
  # {BundleSigDiscovery}'s `bundler/gems/` walk.
@@ -182,7 +186,8 @@ module Rigor
182
186
  # instead of letting the last one overwrite the rest (#1844).
183
187
  entry = LockedGem.new(
184
188
  name: spec.name, version: spec.version.to_s, platform: platform,
185
- git_source: spec.source.is_a?(Bundler::Source::Git)
189
+ git_source: spec.source.is_a?(Bundler::Source::Git),
190
+ path_source: path_source_dir(spec.source, path)
186
191
  )
187
192
  h[entry.name] = (prior = h[entry.name]) ? merge_variant(prior, entry) : entry
188
193
  end
@@ -196,6 +201,17 @@ module Rigor
196
201
  end
197
202
  private_class_method :do_parse
198
203
 
204
+ # The absolute directory a `PATH` stanza names, resolved against the lockfile's own directory — never
205
+ # against `Bundler.root`, which is Rigor's process and not the analysed project. `Bundler::Source::Git`
206
+ # subclasses `Path`, so a `GIT` stanza is excluded by name.
207
+ def self.path_source_dir(source, lockfile_path)
208
+ return nil unless source.is_a?(Bundler::Source::Path)
209
+ return nil if source.is_a?(Bundler::Source::Git)
210
+
211
+ File.expand_path(source.path.to_s, File.dirname(File.expand_path(lockfile_path.to_s)))
212
+ end
213
+ private_class_method :path_source_dir
214
+
199
215
  # Folds a further platform variant of an already-parsed name into it. The representative keeps a `ruby`
200
216
  # variant once seen; otherwise the last variant wins, as it did before variants were kept.
201
217
  def self.merge_variant(prior, entry)
@@ -14,7 +14,7 @@ module Rigor
14
14
  # declared types. See {MemberConsistency} for the rule; this class holds the type work.
15
15
  #
16
16
  # A contradiction is reported only where it is PROVEN: no value, and no call, satisfies both sides.
17
- # Everything short of a proof is undecided. Four places carry that burden:
17
+ # Everything short of a proof is undecided. Five places carry that burden:
18
18
  #
19
19
  # - {#disjoint?} proves two types share no value, from the RBS hierarchy of Ruby core and stdlib
20
20
  # classes ({RbsProof}) only: two such classes neither of which is an RBS ancestor of the other, or a
@@ -103,6 +103,7 @@ module Rigor
103
103
  # `:inline` (the inline side is more precise), `:contradiction` or `:undecided`; `detail` names the
104
104
  # deciding position for the two reported outcomes and is nil otherwise.
105
105
  def compare(signature_member, inline_member, method_name)
106
+ return compare_variables(signature_member, inline_member) if variable?(signature_member)
106
107
  return EQUAL if same_declaration?(signature_member, inline_member)
107
108
  return SIGNATURE_MORE_PRECISE if inferred_signature?(inline_member)
108
109
 
@@ -174,6 +175,22 @@ module Rigor
174
175
  member_annotation_strings(member).include?(INFERRED_SIGNATURE_DIRECTIVE)
175
176
  end
176
177
 
178
+ def variable?(member)
179
+ member.is_a?(::RBS::AST::Members::InstanceVariable) ||
180
+ member.is_a?(::RBS::AST::Members::ClassInstanceVariable)
181
+ end
182
+
183
+ # Issue #2440 — an instance variable (or class instance variable) declared on both sides. Two
184
+ # declarations of one type are equal and merge silently. Any other pair is undecided, never ranked
185
+ # and never a contradiction: `sig/` binds and the inline declaration is reported at `:info`. A
186
+ # variable has no call to satisfy, so "more precise" has no reader to favour, and the committed
187
+ # `.rbs` (typically `rbs-inline --output`'s own) is the declaration the project's other tools read.
188
+ def compare_variables(signature_member, inline_member)
189
+ return EQUAL if signature_member.type == inline_member.type
190
+
191
+ [:undecided, "the type is `#{signature_member.type}` in `sig/` and `#{inline_member.type}` inline"]
192
+ end
193
+
177
194
  def compare_aliases(signature_member, inline_member)
178
195
  both = signature_member.is_a?(::RBS::AST::Members::Alias) && inline_member.is_a?(::RBS::AST::Members::Alias)
179
196
  return EQUAL if both && signature_member.old_name == inline_member.old_name
@@ -59,6 +59,9 @@ module Rigor
59
59
  NO_RECORDS = [].freeze
60
60
  private_constant :NO_RECORDS
61
61
 
62
+ # The key kinds {.member_keys} gives a variable member.
63
+ VARIABLE_KINDS = %i[instance_variable class_instance_variable].freeze
64
+
62
65
  # What a disjointness proof may lean on (ADR-112 WD5, #1075): the RBS class hierarchy the analysis
63
66
  # itself uses, restricted to classes Ruby core and the stdlib declare. Never Ruby constants loaded in
64
67
  # the analyzer process — those depend on which libraries happen to be required, and rbs and Ruby
@@ -115,8 +118,8 @@ module Rigor
115
118
  class << self
116
119
  # @param signature_members — `{[class, method, kind] => [signature_path, member, visibility]}`, the
117
120
  # first project `.rbs` member declaring each key, with its effective visibility.
118
- # @param inline_members — `[[virtual_name, class_name, member, visibility], ...]`, every member the
119
- # inline sources declare.
121
+ # @param inline_members — `[[virtual_name, class_name, member, visibility, nesting], ...]`, every member
122
+ # the inline sources declare, with the lexical nesting {.member_keys} keys a mixin by.
120
123
  # @param shadowable — the first segment of every class / module name the project's inputs declare
121
124
  # ({Comparator#initialize}).
122
125
  # @param proof — an {RbsProof} for the reported rows, or nil for the environment build.
@@ -131,9 +134,9 @@ module Rigor
131
134
 
132
135
  state = { comparator: Comparator.new(shadowable, proof: proof), records: [], inline: Set.new,
133
136
  signature: Hash.new { |hash, file| hash[file] = Set.new }, checked: {}.compare_by_identity }
134
- inline_members.each do |virtual_name, class_name, member, visibility|
137
+ inline_members.each do |virtual_name, class_name, member, visibility, nesting|
135
138
  state[:records].concat(refinement_records(state, class_name, member, nil, virtual_name))
136
- resolve_member(state, signature_members, [virtual_name, class_name, member, visibility])
139
+ resolve_member(state, signature_members, [virtual_name, class_name, member, visibility, nesting])
137
140
  end
138
141
  Resolution.new(
139
142
  records: state[:records].uniq.sort_by { |r| record_sort_key(r) }.freeze,
@@ -159,11 +162,57 @@ module Rigor
159
162
  end
160
163
  end
161
164
 
165
+ # Issue #2440 — every key one RBS member contributes to a definition build: its method keys
166
+ # ({.member_method_keys}) and, for an instance variable (`@x: T`) or a class instance variable
167
+ # (`self.@x: T`), `[:@x, :instance_variable]` / `[:@x, :class_instance_variable]`. rbs 4.2 raises
168
+ # `InstanceVariableDuplicationError` / `ClassInstanceVariableDuplicationError` for two of those in one
169
+ # class even when the types agree, so a `sig/` and an inline declaration of one has to be reconciled
170
+ # like a method. A method name never starts with `@`, so the two key spaces cannot meet. A class
171
+ # variable (`@@x`) contributes nothing: rbs does not reject a duplicate. Neither does an attribute's
172
+ # implied instance variable, which rbs lets an explicit `@x` declaration override.
173
+ #
174
+ # A mixin (`include M`, `extend M`, `prepend M`) is keyed by the lexical nesting of the declaration
175
+ # it sits in and the module and type arguments exactly as written, `[:"A/C|M[String]", :include]`:
176
+ # rbs 4.2 counts every instance variable `M` declares once per declaration of the class that mixes it
177
+ # in, so the same `include M` in `sig/` and inline raises `InstanceVariableDuplicationError ... in ::M`
178
+ # for the class. A relative name resolves through the nesting it is written in, so `include M` inside
179
+ # `module A; class C` (`A::M` when that exists) and inside `class A::C` (`::M`) are different mixins,
180
+ # and so are `M` and `::M`; only an identical spelling in an identical nesting is the same member and
181
+ # stands down. `rbs-inline --output` mirrors the Ruby file's nesting, so its committed output still
182
+ # pairs with the inline buffer. A mixin with different arguments keys differently and is left alone.
183
+ # Two members sharing a mixin key declare no method types to compare, so they read as equal.
184
+ #
185
+ # `nesting` is the declaration names as written, outermost first, joined by `/`
186
+ # ({RbsLoader.each_declared_member}); nil keys a mixin by its spelling alone.
187
+ #
188
+ # Kept apart from {.member_method_keys} on purpose: the plugin-signature plans key collisions by
189
+ # method only, and this rule is the inline-vs-`sig/` one.
190
+ def member_keys(member, nesting = nil)
191
+ case member
192
+ when ::RBS::AST::Members::InstanceVariable then [[member.name, :instance_variable]]
193
+ when ::RBS::AST::Members::ClassInstanceVariable then [[member.name, :class_instance_variable]]
194
+ when ::RBS::AST::Members::Include then [[mixin_key(member, nesting), :include]]
195
+ when ::RBS::AST::Members::Extend then [[mixin_key(member, nesting), :extend]]
196
+ when ::RBS::AST::Members::Prepend then [[mixin_key(member, nesting), :prepend]]
197
+ else member_method_keys(member)
198
+ end
199
+ end
200
+
201
+ # Whether a key `kind` names a variable rather than a method.
202
+ def variable_kind?(kind)
203
+ VARIABLE_KINDS.include?(kind)
204
+ end
205
+
162
206
  private
163
207
 
208
+ def mixin_key(member, nesting)
209
+ args = member.args.empty? ? "" : "[#{member.args.join(', ')}]"
210
+ :"#{nesting}|#{member.name}#{args}"
211
+ end
212
+
164
213
  def resolve_member(state, signature_members, inline)
165
- _virtual_name, class_name, member, = inline
166
- overlapping = member_method_keys(member).filter_map do |method_name, kind|
214
+ _virtual_name, class_name, member, _visibility, nesting = inline
215
+ overlapping = member_keys(member, nesting).filter_map do |method_name, kind|
167
216
  key = [class_name, method_name, kind]
168
217
  owner = signature_members[key]
169
218
  owner && [key, *owner]
@@ -235,7 +284,7 @@ module Rigor
235
284
  end
236
285
 
237
286
  def full_keys(class_name, member)
238
- member_method_keys(member).map { |method_name, kind| [class_name, method_name, kind] }
287
+ member_keys(member).map { |method_name, kind| [class_name, method_name, kind] }
239
288
  end
240
289
 
241
290
  # The `.rbs` line is read only for an outcome that reports it: asking an `RBS::Buffer` for a line
@@ -107,8 +107,9 @@ module Rigor
107
107
  # difference is worth stating because the two look like the same problem. There, a stale
108
108
  # `bundler/gems/` directory left behind by a gem that moved to a released version made the loader
109
109
  # load the WRONG SIGNATURES, collapsing the RBS environment. Here the index's whole output is a gem
110
- # NAME, so reading a stale checkout of the same gem yields the same answer; and the rubygems lookup
111
- # is consulted FIRST in {.build}, so a gem now installed from RubyGems never reaches this map at all.
110
+ # NAME, so reading a stale checkout of the same gem yields the same answer; and the RubyGems-layout
111
+ # bundle directory is consulted FIRST in {.build}, so a gem installed from RubyGems at its locked
112
+ # version never reaches this map.
112
113
  def git_gem_dirs(bundle_path)
113
114
  return {} if bundle_path.nil?
114
115