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
@@ -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
 
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../analysis/path_expansion"
4
+
5
+ module Rigor
6
+ class Environment
7
+ # Issue #2441 — the locked gems that ARE the analysed project: a `PATH` stanza whose directory Rigor analyses
8
+ # as project source. `rbs collection install` still fetches community RBS for such a gem (its generator skips
9
+ # only a `gemspec` source), and the project's `sig/` declares the same classes, so the collection copy would
10
+ # double-declare every member. The same gem is not `:missing` RBS either, so it must not make an ADR-72
11
+ # overlay or a WD9 missing-gem index entry eligible.
12
+ #
13
+ # "Analysed as project source" means the gem's directory is at or under one of the configured `paths:`
14
+ # entries, or contains one: rails lists `actionpack/lib` in `paths:` and locks `actionpack` from
15
+ # `PATH remote: actionpack`. A `PATH` gem no `paths:` entry reaches — a vendored `vendor/gems/foo`, a sibling
16
+ # checkout — is a dependency and keeps its collection RBS.
17
+ #
18
+ # #2592 — a reached gem counts only when the file walker would analyse at least one of its Ruby files: some
19
+ # `**/*.rb` under the part of the gem a `paths:` entry covers survives `Analysis::PathExpansion.reject_excluded`.
20
+ # A gem whose Ruby the configuration's `exclude:` patterns all leave out — `vendor/**`, `vendor/**/*.rb`, or
21
+ # `vendor/gems/foo/lib/**` when `lib/` holds all of it — keeps its collection RBS, and so does a reached
22
+ # directory with no Ruby at all. A `paths:` entry naming one `.rb` file inside the gem counts that file. The
23
+ # overlap is found on real paths, so a symlinked spelling of the same directory matches, but the files are
24
+ # walked and matched against `exclude:` through the `paths:` entry as written, as the walker matches them.
25
+ module ProjectPathGems
26
+ module_function
27
+
28
+ # @param locked — `{name => LockfileResolver::LockedGem}`.
29
+ # @param paths — the configured `paths:` entries; relative ones resolve against `root`.
30
+ # @param exclude_patterns — the configuration's `exclude_patterns`, matched against `exclude_base`.
31
+ # @return frozen set of gem names. Empty when `paths` is nil or empty.
32
+ def names(locked, paths:, root: Dir.pwd, exclude_patterns: [], exclude_base: root)
33
+ entries = Array(paths).map do |path|
34
+ logical = File.expand_path(path.to_s, root.to_s)
35
+ [logical, real(logical)]
36
+ end.uniq
37
+ return Set.new.freeze if entries.empty?
38
+
39
+ locked.each_value.filter_map do |gem|
40
+ dir = gem.path_source
41
+ next unless dir
42
+
43
+ dir = real(dir)
44
+ covered = entries.filter_map { |logical, real_entry| covered_part(dir, logical, real_entry) }
45
+ gem.name if covered.uniq.any? { |part| analysed_ruby?(part, Array(exclude_patterns), exclude_base.to_s) }
46
+ end.to_set.freeze
47
+ end
48
+
49
+ # The part of the gem at real directory `dir` that the `paths:` entry covers, spelled through the entry as
50
+ # written (`logical`), as the file walker spells the files it matches `exclude:` against: the entry itself
51
+ # when it lies at or under the gem, or the gem's directory reached through the entry when the gem lies under
52
+ # it. Nil when they do not overlap.
53
+ def covered_part(dir, logical, real_entry)
54
+ return logical if at_or_under?(real_entry, dir)
55
+ return unless at_or_under?(dir, real_entry)
56
+
57
+ rest = dir.byteslice(real_entry.bytesize..).delete_prefix("/")
58
+ rest.empty? ? logical : File.join(logical, rest)
59
+ end
60
+
61
+ # True when `path` is `dir` or lies under it. Joined with a `/` only when `dir` does not already end in one,
62
+ # so `/` reaches everything without spelling `//`, and `/proj/lib2` is not under `/proj/lib`.
63
+ def at_or_under?(path, dir)
64
+ return true if path == dir
65
+
66
+ path.start_with?(dir.end_with?("/") ? dir : "#{dir}/")
67
+ end
68
+
69
+ # Files are matched in slices as the walk finds them, so a large gem stops at the first slice holding a
70
+ # surviving file instead of listing its whole tree first (`Dir.glob` with a block still builds the list).
71
+ EXCLUDE_SLICE = 64
72
+ private_constant :EXCLUDE_SLICE
73
+
74
+ # True when the file walker ({Analysis::PathExpansion.ruby_files}) would analyse some `.rb` file of `part`:
75
+ # the `.rb` file itself when `part` names one, else a `**/*.rb` under the directory, that survives `patterns`.
76
+ def analysed_ruby?(part, patterns, base)
77
+ return surviving?([part], patterns, base) if File.file?(part)
78
+ return false unless File.directory?(part)
79
+
80
+ slice = []
81
+ found = each_ruby_file(part) do |file|
82
+ slice << file
83
+ next false if slice.size < EXCLUDE_SLICE
84
+
85
+ hit = surviving?(slice, patterns, base)
86
+ slice = []
87
+ hit
88
+ end
89
+ found || surviving?(slice, patterns, base)
90
+ end
91
+
92
+ # Walks `dir` as `Dir.glob("dir/**/*.rb")` matches: sorted, no dot-named entries, no descent through a
93
+ # symlinked directory. Stops and returns true as soon as the block returns true.
94
+ def each_ruby_file(dir, &)
95
+ Dir.children(dir).sort.each do |name|
96
+ next if name.start_with?(".")
97
+
98
+ path = File.join(dir, name)
99
+ if name.end_with?(".rb") && !File.directory?(path)
100
+ return true if File.exist?(path) && yield(path)
101
+ elsif File.directory?(path) && !File.symlink?(path)
102
+ return true if each_ruby_file(path, &)
103
+ end
104
+ end
105
+ false
106
+ rescue SystemCallError
107
+ false
108
+ end
109
+
110
+ def surviving?(files, patterns, base)
111
+ return false if files.empty?
112
+ return files.any? { |file| file.end_with?(".rb") } if patterns.empty?
113
+
114
+ Analysis::PathExpansion.reject_excluded(files, patterns, base: base).any? { |file| file.end_with?(".rb") }
115
+ end
116
+
117
+ def real(path)
118
+ File.realpath(path)
119
+ rescue SystemCallError
120
+ File.expand_path(path)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -19,12 +19,16 @@ module Rigor
19
19
  # access. Failure modes (missing lockfile, malformed YAML, missing collection directory) silently
20
20
  # degrade to an empty list.
21
21
  module RbsCollectionDiscovery
22
- # `stdlib`-typed entries in the lockfile are loaded into the RBS environment by the standard library
23
- # mechanism (rigor's `Environment::DEFAULT_LIBRARIES` already covers this surface). Including them as
24
- # `signature_paths:` entries would risk `RBS::DuplicatedDeclarationError` (the same hazard O7's
25
- # failure-memo handles). The other documented source types — `git` (the gem_rbs_collection repo),
26
- # `rubygems` (sigs lifted from a gem's bundled `sig/`), and `local` (a user-managed RBS dir) — all
27
- # produce a directory under the collection root and are admitted.
22
+ # `stdlib`-typed entries (`source: { type: stdlib }`) name a library rbs ships under its own `stdlib/`, and
23
+ # produce no directory under the collection root, so {.discover} never returns one. Issue #2946 — they are
24
+ # loaded all the same, as the `rbs` CLI's `EnvironmentLoader#add_collection` loads them: as RBS LIBRARIES.
25
+ # {.stdlib_library_names} lists them, and `Environment::BaseLibraries.resolve` appends them to the library list,
26
+ # which is de-duplicated, so an entry `DEFAULT_LIBRARIES` already names is not loaded twice. A collection
27
+ # gem that subclasses one (actionview's `NodeParser < ::Ripper`) then builds. An unknown name is dropped by
28
+ # the loader's `has_library?` filter, as a `libraries:` name is, and `ConfigAudit` reports it. The other
29
+ # documented source types — `git` (the gem_rbs_collection repo), `rubygems` (sigs lifted from a gem's
30
+ # bundled `sig/`), and `local` (a user-managed RBS dir) — all produce a directory under the collection root
31
+ # and are admitted by {.discover}.
28
32
  #
29
33
  # The `source.type` filter alone is NOT sufficient: gems that were extracted from Ruby's stdlib into
30
34
  # standalone default gems (e.g. `cgi`, `logger`, `base64`, `csv`, `bigdecimal`) are published in
@@ -77,6 +81,110 @@ module Rigor
77
81
  Rigor::ConfigPath.absolute(lockfile_path.to_s, project_root)
78
82
  end
79
83
 
84
+ # Issue #2946 — the `name` of every `source: { type: stdlib }` entry in the lockfile, in lockfile order and
85
+ # de-duplicated, whether or not rbs ships that library: the caller loads them as RBS libraries, and the
86
+ # loader's `has_library?` filter drops an unknown one. Unlike {.discover}, the collection directory need not
87
+ # exist, since a stdlib entry installs nothing there. Returns `[]` when no lockfile resolves or its YAML is
88
+ # unreadable. A pure YAML read, so the ADR-87 boot-slimming probe can rebuild the library list from it.
89
+ def self.stdlib_library_names(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
90
+ resolved = resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
91
+ auto_detect: auto_detect)
92
+ return [] if resolved.nil?
93
+
94
+ data = read_lockfile_yaml(resolved)
95
+ return [] if data.nil?
96
+
97
+ Array(data["gems"]).filter_map do |entry|
98
+ next unless entry.is_a?(Hash) && SKIPPED_SOURCE_TYPES.include?(source_type(entry))
99
+
100
+ name = entry["name"]
101
+ name.to_s if name.is_a?(String) && !name.empty?
102
+ end.uniq
103
+ end
104
+
105
+ # Issue #2971 — the `name` of every lockfile entry that is NOT a `stdlib` source and whose
106
+ # `<root>/<name>/<version>/` directory exists: the gems whose signatures come from the collection ({.discover}).
107
+ # The `libraries:` audit uses it so a name the collection provides is not reported as an unknown RBS library.
108
+ # A pure file read, like {.stdlib_library_names}.
109
+ def self.collection_gem_names(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
110
+ resolved = resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
111
+ auto_detect: auto_detect)
112
+ return [] if resolved.nil?
113
+
114
+ data = read_lockfile_yaml(resolved)
115
+ return [] if data.nil?
116
+
117
+ collection_root = resolve_collection_root(resolved, data)
118
+ gem_paths_from(collection_root, data, Set.new).map { |dir| dir.parent.basename.to_s }.uniq
119
+ end
120
+
121
+ # Issue #3030 — whether `file`, found under the collection gem directory `root`, sits below a `_`-prefixed
122
+ # directory (`_test/`, `_scripts/`). Such a file is not part of the gem's signatures: rbs's
123
+ # `EnvironmentLoader` loads a collection gem as a library, and `RBS::FileFinder.each_file(skip_hidden: true)`
124
+ # drops every file with a `_`-prefixed directory between it and the library root. A `git` source never
125
+ # copies them into the collection (`Sources::Git#cp_r` prunes them), but a `local` source symlinks the whole
126
+ # `<name>/<version>/` directory, so gem_rbs_collection's own `bin/test` setup brings each gem's `_test/`
127
+ # fixtures along. Only the directories count, as in rbs: a `_`-prefixed file name is loaded.
128
+ def self.hidden_file?(root, file)
129
+ relative = Pathname(file.to_s).relative_path_from(Pathname(root.to_s))
130
+ relative.ascend.drop(1).any? { |part| part.basename.to_s.start_with?("_") }
131
+ end
132
+
133
+ # Issue #3030 — `collection_paths` ({.discover}) without each gem directory a `signature_paths:` root already
134
+ # loads a copy of. A root is a copy of the entry `<collection>/<name>/<version>` when its real path is the
135
+ # entry's real path (a `local` source symlinks the working tree in), or when it is a gem_rbs_collection working
136
+ # tree for the same gem and version, `.../gems/<name>/<version>` (a `git` source installs a copy of it). That is
137
+ # what an author editing the tree points `signature_paths:` at from its `_test/` directory while the lockfile
138
+ # installs the same gem, and loading both declares every member twice. The `signature_paths:` copy is kept, as
139
+ # it is the one being edited. Any other root, such as a partial override `sig/overrides/foo/1.0`, leaves the
140
+ # collection copy loaded. Real paths are read so `..` and a symlinked spelling answer the same; a root that
141
+ # does not exist matches nothing.
142
+ def self.without_signature_path_copies(collection_paths, signature_roots)
143
+ reals = signature_roots.filter_map { |root| real_path(root) }
144
+ return collection_paths if reals.empty?
145
+
146
+ trees = reals.filter_map do |real|
147
+ real = Pathname(real)
148
+ [real.parent.basename.to_s, real.basename.to_s] if real.parent.parent.basename.to_s == "gems"
149
+ end.to_set
150
+ reals = reals.to_set
151
+ collection_paths.reject do |path|
152
+ path = Pathname(path.to_s)
153
+ trees.include?([path.parent.basename.to_s, path.basename.to_s]) || reals.include?(real_path(path))
154
+ end
155
+ end
156
+
157
+ def self.real_path(path)
158
+ File.realpath(path.to_s)
159
+ rescue SystemCallError
160
+ nil
161
+ end
162
+ private_class_method :real_path
163
+
164
+ # Issue #3030 — why a run with no collection lockfile looks like it was meant to have one: `:collection_test_dir`
165
+ # when `project_root` is a gem_rbs_collection test directory (`gems/<name>/<version>/_test/`, whose lockfile
166
+ # gem_rbs_collection's `bin/test` setup writes), `:collection_config` when it holds an `rbs_collection.yaml`
167
+ # (`rbs collection install` has not run), and nil otherwise, or when a lockfile resolves or the lookup is off.
168
+ # Rigor resolves no `manifest.yaml` or gemspec dependency itself, so without the lockfile the dependency RBS
169
+ # is absent. Two `stat`s and no read.
170
+ def self.missing_lockfile_hint(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
171
+ return nil if lockfile_path.nil? && !auto_detect
172
+ return nil if resolve_lockfile_path(lockfile_path: lockfile_path, project_root: project_root,
173
+ auto_detect: auto_detect)
174
+
175
+ root = Pathname(File.expand_path(project_root.to_s))
176
+ return :collection_test_dir if collection_test_dir?(root)
177
+ return :collection_config if root.join("rbs_collection.yaml").file?
178
+
179
+ nil
180
+ end
181
+
182
+ # `gems/<name>/<version>/_test/`, the layout of a gem_rbs_collection working tree.
183
+ def self.collection_test_dir?(root)
184
+ root.basename.to_s == "_test" && root.parent.parent.parent.basename.to_s == "gems"
185
+ end
186
+ private_class_method :collection_test_dir?
187
+
80
188
  # Returns the resolved lockfile path (`Pathname`) or `nil` when neither explicit nor auto-detect
81
189
  # produces one. Public so the stats banner can surface what rigor found.
82
190
  def self.resolve_lockfile_path(lockfile_path:, project_root: Dir.pwd, auto_detect: true)
@@ -110,12 +218,19 @@ module Rigor
110
218
  end
111
219
  private_class_method :resolve_collection_root
112
220
 
221
+ # An entry's `source.type`, or "" when `source:` is absent or not a mapping (`source: stdlib`): a malformed
222
+ # entry fails soft (ADR-5) instead of raising out of the run.
223
+ def self.source_type(entry)
224
+ source = entry["source"]
225
+ source.is_a?(Hash) ? source["type"].to_s : ""
226
+ end
227
+ private_class_method :source_type
228
+
113
229
  def self.gem_paths_from(collection_root, data, skip_gem_names)
114
230
  Array(data["gems"]).filter_map do |entry|
115
231
  next unless entry.is_a?(Hash)
116
232
 
117
- source_type = entry.dig("source", "type").to_s
118
- next if SKIPPED_SOURCE_TYPES.include?(source_type)
233
+ next if SKIPPED_SOURCE_TYPES.include?(source_type(entry))
119
234
 
120
235
  name = entry["name"]
121
236
  version = entry["version"]
@@ -5,8 +5,9 @@ module Rigor
5
5
  # Open item O4 Layer 3 slice 3 — graceful-degradation coverage report.
6
6
  #
7
7
  # When the user has a `Gemfile.lock` (via slice 1) and rigor has resolved its target-project RBS sources
8
- # (DEFAULT_LIBRARIES, `data/vendored_gem_sigs/`, slice-1 bundle-shipped `sig/`, slice-2
9
- # `rbs_collection.lock.yaml` paths), this module classifies each locked gem by RBS provenance and
8
+ # (the resolved library list — `Environment::BaseLibraries.resolve`, `data/vendored_gem_sigs/`,
9
+ # slice-1 bundle-shipped `sig/`, slice-2 `rbs_collection.lock.yaml` paths), this module classifies each
10
+ # locked gem by RBS provenance and
10
11
  # surfaces the "no RBS available" set so the run-start diagnostic in {Rigor::Analysis::Runner} can
11
12
  # suggest `rbs collection install` or `dependencies.source_inference:` for the uncovered gems.
12
13
  #
@@ -37,9 +38,9 @@ module Rigor
37
38
 
38
39
  # @param locked_gems — The lockfile-resolved gem set. Empty
39
40
  # hash → no coverage analysis to do.
40
- # @param default_libraries — gem names rigor auto-loads through
41
- # `RBS::EnvironmentLoader#add(library:)`. Pass `Rigor::Environment::DEFAULT_LIBRARIES` from callers
42
- # running in a project context.
41
+ # @param default_libraries — the resolved list of names rigor loads as RBS libraries
42
+ # (`Environment::BaseLibraries.resolve`: `DEFAULT_LIBRARIES` + `libraries:` + the collection
43
+ # lockfile's stdlib entries). Pass it from callers running in a project context.
43
44
  # @param bundle_sig_paths — the discovered `<bundle>/.../gems/<name>-<ver>/sig`
44
45
  # paths from {BundleSigDiscovery.discover}.
45
46
  # @param rbs_collection_paths — the discovered `<collection>/<name>/<version>/`
@@ -48,7 +49,7 @@ module Rigor
48
49
  def self.classify(locked_gems:, default_libraries:,
49
50
  bundle_sig_paths:, rbs_collection_paths:)
50
51
  default_set = default_libraries.to_set
51
- bundle_names = extract_gem_names_from_bundle_paths(bundle_sig_paths)
52
+ bundle_names = extract_gem_names_from_bundle_paths(bundle_sig_paths, locked_gems)
52
53
  collection_names = extract_gem_names_from_collection_paths(rbs_collection_paths)
53
54
 
54
55
  locked_gems.each_value.map do |locked|
@@ -73,11 +74,11 @@ module Rigor
73
74
  coverage_rows.select { |row| row.source == :missing }
74
75
  end
75
76
 
76
- def self.extract_gem_names_from_bundle_paths(paths)
77
- paths.each_with_object(Set.new) do |path, set|
78
- pathname = path.is_a?(Pathname) ? path : Pathname.new(path)
79
- set << BundleSigDiscovery.gem_name_from_sig_path(pathname)
80
- end
77
+ # Issue #2978 — each path's gem name as the locked entry it matches ({BundleSigDiscovery.locked_names_of}), not
78
+ # a parse of its directory name, which reads `foo-2fa-1.0` as `foo` and reported `foo-2fa` as missing RBS.
79
+ def self.extract_gem_names_from_bundle_paths(paths, locked_gems)
80
+ pathnames = paths.map { |path| path.is_a?(Pathname) ? path : Pathname.new(path) }
81
+ BundleSigDiscovery.locked_names_of(pathnames, locked_gems).to_set
81
82
  end
82
83
  private_class_method :extract_gem_names_from_bundle_paths
83
84
 
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../typing_state"
4
+
3
5
  module Rigor
4
6
  class Environment
5
7
  # Small hierarchy oracle backed by RBS instance definitions.
6
8
  class RbsHierarchy
9
+ TypingState.memo_field(self, :@ancestor_names_cache, :@class_ordering_cache,
10
+ reason: "keyed on the class names; answered from the loader's RBS environment, fixed " \
11
+ "for the loader's lifetime")
12
+
7
13
  def initialize(loader)
8
14
  @loader = loader
9
15
  @ancestor_names_cache = {}
@@ -41,21 +47,12 @@ module Rigor
41
47
  end
42
48
  end
43
49
 
44
- # Issue #696 review, second pass — the ancestry lookup moved to {RbsLoader#ancestor_names_for}, and
45
- # what moved with it is the reason.
46
- #
47
- # This method used to fetch `Cache::RbsClassAncestorTable` DIRECTLY, bypassing the loader's own
48
- # accessor, and branch on `loader.cache_store`: a whole-universe table build with a store, a
49
- # single-class demand without one. Same project, same question, three different answers to "which
50
- # classes failed to build" — 504 on a cold store, 0 on a warm one, 2 with no store. On the DEFAULT
51
- # `--workers=0` path nothing pre-warms the store, so the cold answer was the one written into the
52
- # run-result cache and replayed until a file changed.
53
- #
54
- # The cache-state branch still exists — it is worth 50x on the warm path — but it lives behind the
55
- # loader now, where both of its sides are marked as RIGOR'S OWN demand. Ordering two classes is not
56
- # the analysis asking whether either one's methods resolve, so neither side reaches the diagnostic,
57
- # and the answer is identical on both (the table's producer computes exactly this, keyed the same
58
- # way). What this method keeps is its own per-name memo, which is about repeated comparisons.
50
+ # Issue #696 review, second pass — the ancestry lookup lives in {RbsLoader#ancestor_names_for}.
51
+ # Read directly from `Cache::RbsClassAncestorTable`, the same question gave 504 failed classes on a
52
+ # cold store, 0 on a warm one and 2 with no store, and the cold answer was cached into the run result.
53
+ # Through the loader, both cache states are marked as Rigor's own demand, so ordering two classes
54
+ # never reaches the "which classes failed to build" diagnostic. The cache-state branch stays behind
55
+ # the loader (it is worth 50x on the warm path); this method keeps only its per-name memo.
59
56
  def ancestor_names(class_name)
60
57
  key = normalize_name(class_name)
61
58
  return @ancestor_names_cache[key] if @ancestor_names_cache.key?(key)