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,381 @@
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
+ # - an `include`, `extend` or `prepend` of a module this file declares but that stood down whole, or that another
27
+ # source declares as a class, stands down too (#2974), where keeping it would have raised `NoMixinFoundError` or
28
+ # `MixinClassError` for the class that mixes it in (`Integer`, for the overlay's `ActiveSupport::NumericWithFormat`);
29
+ # - an instance, class-instance or class variable another source declares on the type stands down (#2978), where
30
+ # two declarations of it would have raised `InstanceVariableDuplicationError` or its twins for the class.
31
+ #
32
+ # A declaration with nothing taken comes back as the same object, so a file with no collision loads as it
33
+ # stands. {.standdown_rows} reads what stood down back off the built environment, so a warm run off the env
34
+ # cache reports what the cold run decided.
35
+ module DeferredSignaturePlan
36
+ module_function
37
+
38
+ # @param own — the buffer names that are the project's own sources: its `signature_paths:` files (not the
39
+ # collection or gem directories also on that list) and its inline RBS.
40
+ def plan(decls, env, own, namespace = nil)
41
+ mixins = MixinGate.new(env, decls)
42
+ planned = decls.filter_map { |decl| plan_declaration(decl, env, own, namespace, mixins) }
43
+ same?(planned, decls) ? decls : planned
44
+ end
45
+
46
+ def plan_declaration(decl, env, own, namespace, mixins)
47
+ name = GatedSignaturePlan.qualify(namespace, declared_name(decl))
48
+ if GatedSignaturePlan.type_declaration?(decl)
49
+ return nil if kind_clash?(env, decl, name)
50
+
51
+ return plan_type(decl, env, own, name, mixins)
52
+ end
53
+ declared_elsewhere(env, decl, name) ? nil : decl
54
+ end
55
+
56
+ def plan_type(decl, env, own, name, mixins)
57
+ others = GatedSignaturePlan.other_declarations(env, name)
58
+ taken = GatedSignaturePlan.declared_keys(others)
59
+ variables = variable_keys(others)
60
+ external = others.reject { |other| own.include?(buffer_of(other)) }
61
+ defined = GatedSignaturePlan.declared_keys(external) | definition_keys(external)
62
+ members = decl.members.filter_map do |member|
63
+ next plan_declaration(member, env, own, name, mixins) if declaration?(member)
64
+ next nil if mixins.drop?(member, name) || variables.include?(variable_key(member))
65
+ next member if taken.empty?
66
+
67
+ composed(member, defined) || GatedSignaturePlan.untaken(member, taken)
68
+ end
69
+ drop = superclass_given?(decl, others)
70
+ return decl if same?(members, decl.members) && !drop
71
+
72
+ GatedSignaturePlan.rebuild(decl, members: members, drop_super_class: drop)
73
+ end
74
+
75
+ # Issue #2978 — an instance, class-instance or class variable another source declares on the type stands down:
76
+ # RBS raises `InstanceVariableDuplicationError` (or its class-instance and class-variable twins) for two
77
+ # declarations of one, and the class reads `Dynamic[top]`. Keyed by name and kind, not type: the other source's
78
+ # declaration binds either way.
79
+ VARIABLE_MEMBERS = {
80
+ ::RBS::AST::Members::InstanceVariable => :instance_variable,
81
+ ::RBS::AST::Members::ClassInstanceVariable => :class_instance_variable,
82
+ ::RBS::AST::Members::ClassVariable => :class_variable
83
+ }.freeze
84
+
85
+ # `[name, kind]` for a variable member, else nil.
86
+ def variable_key(member)
87
+ kind = VARIABLE_MEMBERS[member.class]
88
+ kind && [member.name, kind]
89
+ end
90
+
91
+ # The variable keys `decls` declare, an attribute's implied instance variable included (`attr_reader x: String`
92
+ # declares `@x` unless written `attr_reader x(): String`).
93
+ def variable_keys(decls)
94
+ decls.each_with_object(Set.new) do |decl, keys|
95
+ decl.members.each do |member|
96
+ key = variable_key(member) || attribute_variable_key(member)
97
+ keys << key if key
98
+ end
99
+ end
100
+ end
101
+
102
+ def attribute_variable_key(member)
103
+ return nil unless member.is_a?(::RBS::AST::Members::Attribute) && member.ivar_name != false
104
+
105
+ [member.ivar_name || :"@#{member.name}",
106
+ member.kind == :singleton ? :class_instance_variable : :instance_variable]
107
+ end
108
+
109
+ # Issue #2439 — true when `decl` is a class with a superclass clause and another source (`others`) already
110
+ # gives the class one. Two different clauses raise `RBS::SuperclassMismatchError` and the class and every
111
+ # descendant read `Dynamic[top]`, so the plugin's header stands down and reopens the class without one. The
112
+ # two names are not compared: they are not resolved yet, and the other source's superclass binds either way.
113
+ def superclass_given?(decl, others)
114
+ super_class?(decl) && others.any? { |other| super_class?(other) }
115
+ end
116
+
117
+ # `member` as an overload continuation, or nil when it cannot be one: only a plain one-sided `def` can, and
118
+ # only onto a method — a `def`, an attribute or an alias, all of which RBS continues — that a source other
119
+ # than the project's own declares.
120
+ def composed(member, defined)
121
+ return nil unless member.is_a?(::RBS::AST::Members::MethodDefinition) && !member.overloading?
122
+ return nil if member.kind == :singleton_instance
123
+
124
+ key = [member.name, member.kind]
125
+ return nil unless defined.include?(key)
126
+
127
+ member.update(overloading: true)
128
+ end
129
+
130
+ # The `[name, kind]` keys a plain or continued `def` in `decls` declares — what a continuation can extend.
131
+ def definition_keys(decls)
132
+ decls.each_with_object(Set.new) do |decl, keys|
133
+ decl.members.each do |member|
134
+ next unless member.is_a?(::RBS::AST::Members::MethodDefinition)
135
+
136
+ sides = member.kind == :singleton_instance ? %i[instance singleton] : [member.kind]
137
+ sides.each { |side| keys << [member.name, side] }
138
+ end
139
+ end
140
+ end
141
+
142
+ def kind_clash?(env, decl, name)
143
+ type_name = ::RBS::TypeName.parse("::#{name}")
144
+ return true if env.class_alias_decls.key?(type_name)
145
+
146
+ first = GatedSignaturePlan.other_declarations(env, name).first
147
+ !first.nil? && !first.instance_of?(decl.class)
148
+ end
149
+
150
+ # The buffer of the declaration another source already makes under `decl`'s name, or nil.
151
+ def declared_elsewhere(env, decl, name)
152
+ entry = declaration_entry(env, decl, name)
153
+ entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
154
+ end
155
+
156
+ def declaration_entry(env, decl, name)
157
+ type_name = -> { ::RBS::TypeName.parse("::#{name}") }
158
+ case decl
159
+ when ::RBS::AST::Declarations::Constant then env.constant_decls[type_name.call]
160
+ when ::RBS::AST::Declarations::Global then env.global_decls[decl.name]
161
+ when ::RBS::AST::Declarations::TypeAlias then env.type_alias_decls[type_name.call]
162
+ when ::RBS::AST::Declarations::Interface then env.interface_decls[type_name.call]
163
+ when ::RBS::AST::Declarations::ClassAlias, ::RBS::AST::Declarations::ModuleAlias
164
+ env.class_alias_decls[type_name.call] || env.class_decls[type_name.call]
165
+ end
166
+ end
167
+
168
+ # A class or module alias names itself as `new_name`; every other declaration as `name`.
169
+ def declared_name(decl)
170
+ decl.respond_to?(:new_name) ? decl.new_name : decl.name
171
+ end
172
+
173
+ def declaration?(member)
174
+ member.is_a?(::RBS::AST::Declarations::Base)
175
+ end
176
+
177
+ def same?(planned, original)
178
+ planned.size == original.size && planned.zip(original).all? { |a, b| a.equal?(b) }
179
+ end
180
+
181
+ def buffer_of(decl)
182
+ RbsLoader.declaration_buffer_name(decl)
183
+ end
184
+
185
+ # Issue #2974 — which of a deferred file's mixins name a module that will not be there as a module: one the file
186
+ # itself declares, inside a type that stood down whole on a kind clash (the project's `class ActiveSupport` takes
187
+ # the overlay's `module ActiveSupport` and the `NumericWithFormat` in it), or one another source declares as a
188
+ # class. A mixin of a module the file does not declare, which some other source may supply, is left alone.
189
+ class MixinGate
190
+ MIXINS = [::RBS::AST::Members::Include, ::RBS::AST::Members::Extend, ::RBS::AST::Members::Prepend].freeze
191
+
192
+ def initialize(env, decls)
193
+ @env = env
194
+ @declared = Set.new
195
+ @dropped = Set.new
196
+ decls.each { |decl| collect(decl, nil, false) }
197
+ end
198
+
199
+ # `namespace` is the absolute name, without the leading `::`, of the type `member` is written in.
200
+ def drop?(member, namespace)
201
+ return false unless MIXINS.any? { |klass| member.is_a?(klass) } && member.name.class?
202
+
203
+ target = resolve(member.name, namespace)
204
+ return false if target.nil?
205
+
206
+ first = GatedSignaturePlan.other_declarations(@env, target).first
207
+ return first.is_a?(::RBS::AST::Declarations::Class) unless first.nil?
208
+
209
+ @dropped.include?(target)
210
+ end
211
+
212
+ private
213
+
214
+ def collect(decl, namespace, inside_dropped)
215
+ return unless GatedSignaturePlan.type_declaration?(decl)
216
+
217
+ name = GatedSignaturePlan.qualify(namespace, decl.name)
218
+ dropped = inside_dropped || DeferredSignaturePlan.kind_clash?(@env, decl, name)
219
+ @declared << name
220
+ @dropped << name if dropped
221
+ decl.members.each { |member| collect(member, name, dropped) }
222
+ end
223
+
224
+ # The first lexical candidate for `type_name` written inside `namespace` that this file or the env declares,
225
+ # as RBS resolves a relative name outward from the innermost scope.
226
+ def resolve(type_name, namespace)
227
+ candidates(type_name, namespace).find do |name|
228
+ @declared.include?(name) || !GatedSignaturePlan.other_declarations(@env, name).empty?
229
+ end
230
+ end
231
+
232
+ def candidates(type_name, namespace)
233
+ text = type_name.to_s
234
+ return [text.delete_prefix("::")] if type_name.absolute?
235
+
236
+ scopes = namespace.to_s.split("::")
237
+ scopes.size.downto(0).map { |depth| [*scopes.first(depth), text].join("::") }
238
+ end
239
+ end
240
+
241
+ # One {.standdown_rows} walk over a deferred file: the built env, the file's buffer name, the rows so far, and
242
+ # the env's type-name resolver, built on first use (only a dropped superclass clause needs it).
243
+ class Walk
244
+ attr_reader :env, :file, :rows
245
+
246
+ def initialize(env, file)
247
+ @env = env
248
+ @file = file
249
+ @rows = []
250
+ end
251
+
252
+ # rbs 4.x builds the resolver with `TypeNameResolver.build(env)`; rbs 3.x's constructor takes the env, which
253
+ # the 4.x signature Rigor reads (`new(all_names, aliases)`) does not describe.
254
+ def resolver
255
+ @resolver ||= begin
256
+ klass = ::RBS::Resolver::TypeNameResolver
257
+ if klass.respond_to?(:build)
258
+ klass.build(env)
259
+ else
260
+ klass.new(env) # rigor:disable call.wrong-arity
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ # What {.plan} left out of the deferred `file` (its parsed `decls`) in the built `env`, as `[declaration,
267
+ # cause_file]` rows — `Object#acts_like?`, `Time.at`, `FFI::Pointer::NULL`, `Foo < Bar`, or a type name — with
268
+ # the buffer of the other source's declaration, or nil when it cannot be named. A composed method is not a row:
269
+ # the plugin's overloads still bind.
270
+ def standdown_rows(env, file, decls)
271
+ walk = Walk.new(env, file)
272
+ decls.each { |decl| declaration_rows(walk, decl, nil) }
273
+ walk.rows.uniq
274
+ end
275
+
276
+ # `context` is RBS's resolver context for where `decl` is written: nil at the top level, else `[outer,
277
+ # absolute name of the enclosing declaration]`, as `RBS::Environment#append_context` builds it.
278
+ def declaration_rows(walk, decl, context)
279
+ name = GatedSignaturePlan.qualify(namespace_of(context), declared_name(decl))
280
+ return type_rows(walk, decl, name, context) if GatedSignaturePlan.type_declaration?(decl)
281
+
282
+ cause = displaced_by(walk.env, decl, name, walk.file)
283
+ walk.rows << [decl.is_a?(::RBS::AST::Declarations::Global) ? decl.name.to_s : name, cause] if cause
284
+ end
285
+
286
+ def namespace_of(context)
287
+ context && context.last.to_s.delete_prefix("::")
288
+ end
289
+
290
+ # A non-nil buffer name (possibly empty) when another source's declaration of `decl` is the one loaded.
291
+ def displaced_by(env, decl, name, file)
292
+ entry = declaration_entry(env, decl, name)
293
+ return nil if entry.nil?
294
+
295
+ buffer = RbsLoader.declaration_buffer_name(entry.decl)
296
+ buffer == file ? nil : buffer
297
+ end
298
+
299
+ def type_rows(walk, decl, name, context)
300
+ loaded, others = loaded_and_others(walk, name)
301
+ if loaded.empty?
302
+ cause = others.empty? ? alias_cause(walk.env, name) : buffer_of(others.first)
303
+ walk.rows << [name, cause] if cause
304
+ return
305
+ end
306
+
307
+ walk.rows.concat(superclass_rows(walk, decl, name, context, others))
308
+ walk.rows.concat(member_rows(decl, name, loaded, others))
309
+ inner = [context, ::RBS::TypeName.parse("::#{name}")]
310
+ decl.members.each { |member| declaration_rows(walk, member, inner) if declaration?(member) }
311
+ end
312
+
313
+ # The declarations of `name` the walked file loaded, and the ones other sources made (synthesized stubs left out).
314
+ def loaded_and_others(walk, name)
315
+ entry = walk.env.class_decls[::RBS::TypeName.parse("::#{name}")]
316
+ declarations = entry.nil? ? [] : RbsLoader.entry_declarations(entry)
317
+ loaded = declarations.select { |other| buffer_of(other) == walk.file }
318
+ [loaded, declarations.reject { |other| loaded.include?(other) || GatedSignaturePlan.synthetic?(other) }]
319
+ end
320
+
321
+ # The buffer of a class or module alias declaring `name` (empty when it cannot be named), or nil.
322
+ def alias_cause(env, name)
323
+ entry = env.class_alias_decls[::RBS::TypeName.parse("::#{name}")]
324
+ entry && (RbsLoader.declaration_buffer_name(entry.decl) || "")
325
+ end
326
+
327
+ # The plugin's superclass clause as a row (`ActiveRecord::StatementInvalid < ActiveRecordError`) when the plan
328
+ # dropped it for another source's ({.superclass_given?}) and that source's names a different class or type
329
+ # arguments. The plan drops an identical clause too, but nothing is lost, so no row.
330
+ def superclass_rows(walk, decl, name, context, others)
331
+ return [] unless superclass_given?(decl, others)
332
+
333
+ cause = others.find { |other| super_class?(other) }
334
+ return [] if same_super_class?(walk, decl.super_class, context, cause.super_class)
335
+
336
+ written = decl.super_class
337
+ args = written.args.empty? ? "" : "[#{written.args.join(', ')}]"
338
+ [["#{name} < #{written.name}#{args}", buffer_of(cause)]]
339
+ end
340
+
341
+ # Whether the plugin's clause `written`, resolved as RBS resolves a superclass (in the context the class is
342
+ # written in, not its own), names the class and type arguments of `other`, the built env's already-absolute
343
+ # clause, class aliases normalised on both sides. An unresolvable name differs.
344
+ def same_super_class?(walk, written, context, other)
345
+ name = absolute_name(walk, written.name, context)
346
+ return false if name.nil? || normalized(walk.env, name) != normalized(walk.env, other.name)
347
+
348
+ written.args.map { |arg| normalized_type(walk, arg, context) } ==
349
+ other.args.map { |arg| normalized_type(walk, arg, nil) }
350
+ end
351
+
352
+ def absolute_name(walk, name, context)
353
+ name.absolute? ? name : walk.resolver.resolve(name, context: context)
354
+ end
355
+
356
+ def normalized(env, name)
357
+ name.class? ? env.normalize_module_name(name) : name
358
+ end
359
+
360
+ def normalized_type(walk, type, context)
361
+ return type.to_s unless type.respond_to?(:map_type_name)
362
+
363
+ type.map_type_name { |name, _, _| normalized(walk.env, absolute_name(walk, name, context) || name) }.to_s
364
+ end
365
+
366
+ def super_class?(decl)
367
+ decl.is_a?(::RBS::AST::Declarations::Class) && !decl.super_class.nil?
368
+ end
369
+
370
+ def member_rows(decl, name, loaded, others)
371
+ present = GatedSignaturePlan.declared_keys(loaded) | definition_keys(loaded)
372
+ decl.members.flat_map do |member|
373
+ RbsLoader.member_method_keys(member).reject { |key| present.include?(key) }.filter_map do |key|
374
+ cause = others.find { |other| other.members.any? { |m| RbsLoader.member_method_keys(m).include?(key) } }
375
+ cause && [GatedSignaturePlan.member_label(name, key), buffer_of(cause)]
376
+ end
377
+ end
378
+ end
379
+ end
380
+ end
381
+ 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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  class Environment
5
7
  # ADR-20 slice 2e — mutable single-slot memoization container for the per-Environment HKT registry. Held
@@ -10,6 +12,10 @@ module Rigor
10
12
  # prevent cross-thread races. If a future caller introduces a multi-threaded reader path against a
11
13
  # shared Environment, the synchronisation belongs at that caller's seam, not here.
12
14
  class HktRegistryHolder
15
+ TypingState.memo_field(self, :@value, :@loaded,
16
+ reason: "the one value its owner's block builds from the environment's fixed inputs, " \
17
+ "kept once built")
18
+
13
19
  def initialize
14
20
  @loaded = false
15
21
  @value = nil
@@ -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