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,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require_relative "../../rbs_extended"
5
+ require_relative "../../scope/resolution_chain"
6
+
7
+ module Rigor
8
+ module Lens
9
+ class Claims
10
+ # Normalizes RBS ASTs without erasing unsupported forms or using display text as identity.
11
+ class TypeReader
12
+ Atom = Data.define(:kind, :value, :arguments, :relative) do
13
+ def identity
14
+ [kind, value, arguments.map { |arms| arms.to_set(&:identity) }]
15
+ end
16
+
17
+ def display
18
+ return value.inspect if kind == :literal
19
+
20
+ suffix = arguments.empty? ? "" : "[#{arguments.map { |arms| arms.map(&:display).join(' | ') }.join(', ')}]"
21
+ "::#{value}#{suffix}"
22
+ end
23
+ end
24
+
25
+ attr_reader :reasons
26
+
27
+ def self.parse(text)
28
+ # An empty, blank or comment-only text parses to nil rather than raising.
29
+ type = ::RBS::Parser.parse_type(text, require_eof: true)
30
+ type ? [type, nil] : [nil, "syntax_error"]
31
+ rescue ::RBS::ParsingError
32
+ extended = RbsExtended.parse_return_type_override("rigor:v1:return: #{text}")
33
+ [nil, extended ? "unsupported_grammar_feature" : "syntax_error"]
34
+ end
35
+
36
+ # `owner` is the member's owner; an RBS declaration of the member sits in its namespace, which a builder
37
+ # block's lexical nesting does not name.
38
+ def initialize(scope:, nesting:, singleton_nesting: false, owner: nil)
39
+ @scope = scope
40
+ @loader = scope.environment.rbs_loader
41
+ @resolver = @loader.type_name_resolver
42
+ @nesting = nesting
43
+ @singleton_nesting = singleton_nesting
44
+ @context = rbs_context(Array(nesting))
45
+ @owner_context = owner && owner != "Object" ? rbs_context(owner_namespaces(owner)) : nil
46
+ @reasons = Set.new
47
+ end
48
+
49
+ def read(type, claim: false, aliases: Set.new)
50
+ case type
51
+ when ::RBS::Types::Bases::Any
52
+ @reasons << "dynamic_type"
53
+ []
54
+ when ::RBS::Types::Bases::Bool then [literal(true), literal(false)]
55
+ when ::RBS::Types::Bases::Nil then [literal(nil)]
56
+ when ::RBS::Types::Literal then [literal(type.literal)]
57
+ when ::RBS::Types::Union then type.types.flat_map { |part| read(part, claim: claim, aliases: aliases) }
58
+ when ::RBS::Types::Optional then read(type.type, claim: claim, aliases: aliases) + [literal(nil)]
59
+ when ::RBS::Types::Alias then read_alias(type, claim, aliases)
60
+ when ::RBS::Types::ClassInstance then read_class(type, claim, aliases)
61
+ else
62
+ @reasons << "unsupported_type"
63
+ type.each_type { |part| read(part, claim: claim, aliases: aliases) }
64
+ []
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def rbs_context(names)
71
+ names.reduce(nil) do |outer, name|
72
+ type_name = ::RBS::TypeName.parse("::#{name}")
73
+ @loader.class_known?(name) ? [outer, type_name] : outer
74
+ end
75
+ end
76
+
77
+ def owner_namespaces(owner)
78
+ segments = owner.split("::")
79
+ segments.each_index.map { |index| segments[0..index].join("::") }
80
+ end
81
+
82
+ def literal(value, relative: false)
83
+ Atom.new(kind: :literal, value: value, arguments: [], relative: relative)
84
+ end
85
+
86
+ def resolve(name, claim)
87
+ resolved = @resolver&.resolve(name, context: claim ? @context : nil)
88
+ if resolved.nil?
89
+ @reasons << "unresolved_name"
90
+ elsif claim && !name.absolute? && name.class?
91
+ compare_ruby_name(name, resolved)
92
+ end
93
+ resolved
94
+ end
95
+
96
+ def compare_ruby_name(name, resolved)
97
+ # An unlocated alias body has no known cref. A singleton class's anonymous cref is not a
98
+ # named namespace either; its constant table and ancestry cannot be read as the owner's.
99
+ if @nesting.nil? || @singleton_nesting
100
+ @reasons << "ambiguous_name"
101
+ return
102
+ end
103
+
104
+ ruby = ruby_name(name.to_s)
105
+ @reasons << "ambiguous_name" if ruby && ruby != resolved.to_s.delete_prefix("::")
106
+ end
107
+
108
+ def read_class(type, claim, aliases)
109
+ name = resolve(type.name, claim)
110
+ arguments = type.args.map { |arg| read(arg, claim: claim, aliases: aliases) }
111
+ return [] unless name
112
+
113
+ key = name.to_s.delete_prefix("::")
114
+ if !@loader.class_known?(key) || arguments.size != @loader.class_type_param_names(key).size
115
+ @reasons << "unsupported_type"
116
+ end
117
+ singles = { "TrueClass" => true, "FalseClass" => false, "NilClass" => nil }
118
+ if arguments.empty? && singles.key?(key)
119
+ return [literal(singles.fetch(key), relative: claim && !type.name.absolute?)]
120
+ end
121
+
122
+ [Atom.new(kind: :class, value: key, arguments: arguments, relative: claim && !type.name.absolute?)]
123
+ end
124
+
125
+ def read_alias(type, claim, aliases)
126
+ name = resolve(type.name, claim)
127
+ type.args.each { |arg| read(arg, claim: claim, aliases: aliases) }
128
+ return [] unless name
129
+
130
+ relative = claim && !type.name.absolute?
131
+ return [] if relative && !alias_name_settled?(type.name, name)
132
+
133
+ entry = @loader.type_alias_declaration(name)
134
+ if entry.nil? || !entry.type_params.empty? || !type.args.empty? || aliases.include?(name)
135
+ @reasons << "unsupported_type"
136
+ return []
137
+ end
138
+ # The resolved declaration binds names in the alias's namespace, not the claim's lexical scope. Arms
139
+ # reached through a relatively written name stay relative, so the conflict guard still applies.
140
+ arms = read(entry.type, aliases: aliases | [name])
141
+ relative ? arms.map { |arm| arm.with(relative: true) } : arms
142
+ end
143
+
144
+ # A type alias has no Ruby meaning, so the only reading to cross-check is the RBS one: the claim's lexical
145
+ # context and the member's RBS namespace must resolve a relative alias name alike. An unlocated body or a
146
+ # singleton-class body has no context to read it in.
147
+ def alias_name_settled?(written, resolved)
148
+ if @nesting.nil? || @singleton_nesting
149
+ @reasons << "ambiguous_name"
150
+ return false
151
+ end
152
+ return true if @owner_context.nil? || @owner_context == @context
153
+
154
+ declared = @resolver&.resolve(written, context: @owner_context)
155
+ return true if declared == resolved
156
+
157
+ @reasons << "ambiguous_name"
158
+ false
159
+ end
160
+
161
+ # Project constants only: a missing entry falls back to RBS, which also knows libraries and gems.
162
+ # Resolve the first segment before the rest, so a Ruby-only namespace cannot fall through to core.
163
+ def ruby_name(written)
164
+ head, *tail = written.split("::")
165
+ found = project_constant_in(@nesting.reverse, head)
166
+ found ||= ancestor_constant(head)
167
+ found = head if found.nil? && (@nesting.empty? || @loader.rbs_module?(@nesting.last)) && project_name?(head)
168
+ found && ([found] + tail).join("::")
169
+ end
170
+
171
+ def project_constant_in(bases, name)
172
+ bases.each do |base|
173
+ candidate = base == "Object" ? name : "#{base}::#{name}"
174
+ return candidate if project_name?(candidate)
175
+ end
176
+ nil
177
+ end
178
+
179
+ def project_name?(name)
180
+ @scope.declared_class?(name) || @scope.bound_constant_names(name).include?(name)
181
+ end
182
+
183
+ def ancestor_constant(name)
184
+ chain = Scope::ResolutionChain.for(@scope, @nesting.last || "Object", :instance, :constants)
185
+ found = chain_constant(chain, name)
186
+ settled = chain.settle(@scope, found) { |retro| chain_constant(retro, name) }
187
+ if chain.truncated? || chain.wildcard_mixin? || settled != :chain || opaque_ancestry?(chain)
188
+ @reasons << "ambiguous_name"
189
+ return nil
190
+ end
191
+ found
192
+ end
193
+
194
+ # A superclass the tables cannot name (`Class.new(Base)`, `< pick`, a builder class's `Struct`/`Data`) or an
195
+ # external ancestor no RBS declares (`< SomeAlias`): the constants it holds are not on the chain.
196
+ def opaque_ancestry?(chain)
197
+ return true if chain.opaque_tail?(@scope)
198
+
199
+ chain.entries.any? { |entry| entry.external? && entry.candidates.none? { |name| @loader.class_known?(name) } }
200
+ end
201
+
202
+ def chain_constant(chain, name)
203
+ chain.search(@scope) do |entry|
204
+ bases = entry.external? ? external_ancestors(entry) : [entry.name]
205
+ project_constant_in(bases, name)
206
+ end
207
+ end
208
+
209
+ def external_ancestors(entry)
210
+ name = entry.candidates.find { |candidate| @loader.class_known?(candidate) }
211
+ name ? @loader.ancestor_names_for(name) : []
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "claims/request"
4
+ require_relative "claims/type_reader"
5
+ require_relative "claims/comparison"
6
+
7
+ module Rigor
8
+ module Lens
9
+ # Read-only documentation coverage against the contracts retained by this lens run.
10
+ class Claims
11
+ REASONS = %w[no_such_declaration unsupported_declaration declaration_mismatch unsupported_subject
12
+ no_authored_contract unsupported_contract_source extrbs_contract refined_contract
13
+ unsupported_overloads no_such_parameter unsupported_parameter_kind unmatched_parameter
14
+ unsupported_grammar_feature unresolved_name ambiguous_name dynamic_type unsupported_type
15
+ unproven].freeze
16
+ POSITIONALS = %w[required optional rest post].freeze
17
+
18
+ def initialize(request:, outcome:, run:)
19
+ @request = request
20
+ @outcome = outcome
21
+ @run = run
22
+ end
23
+
24
+ def call
25
+ @request.verify_sources!(@outcome.targets)
26
+ { "schema" => "lens-claims/v0", "revision" => 1, "request_digest" => @request.digest,
27
+ "grammar" => "rbs", "capabilities" => %w[return required_param required_keyword],
28
+ "results" => @request.claims.map { |claim| result(claim) } }
29
+ end
30
+
31
+ private
32
+
33
+ def result(claim)
34
+ answer = { "id" => claim.fetch("id"), "verdict" => "unknown", "reason" => "unproven",
35
+ "relation" => nil, "direction" => nil, "site" => nil, "member" => nil,
36
+ "slot" => nil, "contract" => nil, "evidence" => [] }
37
+ target = @outcome.targets.find { |entry| entry.absolute == @request.paths.fetch(claim.fetch("file")) }
38
+ return answer.merge("verdict" => "unavailable", "reason" => target.status) unless target.status == "ok"
39
+
40
+ parsed, grammar_reason = TypeReader.parse(claim.fetch("claim"))
41
+ if grammar_reason == "syntax_error"
42
+ return answer.merge("verdict" => "invalid_claim",
43
+ "reason" => "syntax_error")
44
+ end
45
+
46
+ site, member, reason = bind(claim.fetch("declaration"), target.reader)
47
+ return answer.merge("reason" => reason) if reason
48
+
49
+ answer.merge!("site" => site.data.fetch("key"), "member" => member.slice("owner", "scope", "name"))
50
+ check_slot(claim, parsed, grammar_reason, site, member, target, answer)
51
+ end
52
+
53
+ def bind(declaration, reader)
54
+ sites = reader.sites.select { |site| same_span?(site.data.fetch("span"), declaration.fetch("span")) }
55
+ if sites.empty?
56
+ omission = reader.omissions.any? { |entry| same_span?(entry.fetch("span"), declaration.fetch("span")) }
57
+ return [nil, nil, omission ? "unsupported_declaration" : "no_such_declaration"]
58
+ end
59
+ return [nil, nil, "unsupported_declaration"] unless sites.one? && sites.first.data["kind"] == "method"
60
+
61
+ site = sites.first
62
+ member = @outcome.member_rows.find do |row|
63
+ row.values_at("owner", "scope", "name") == declaration.values_at("owner", "scope", "name") &&
64
+ row.fetch("sites").include?(site.data.fetch("key")) && declaration["kind"] == "method"
65
+ end
66
+ member ? [site, member, nil] : [nil, nil, "declaration_mismatch"]
67
+ end
68
+
69
+ def same_span?(left, right)
70
+ left.values_at("start_offset", "end_offset") == right.values_at("start_offset", "end_offset")
71
+ end
72
+
73
+ def check_slot(claim, parsed, grammar_reason, site, member, target, answer)
74
+ tag = claim.fetch("tag")
75
+ return answer.merge("reason" => "unsupported_subject") unless supported_subject?(tag)
76
+
77
+ raw = @run.raw_contracts[member.values_at("owner", "scope", "name")]
78
+ overloads = member.fetch("overloads")
79
+ reasons = member_reasons(site, member) + [grammar_reason].compact
80
+ slot, index, subject_reason = subject(tag, site, overloads.one? ? overloads.first : nil)
81
+ reasons << subject_reason if subject_reason
82
+ if slot
83
+ read_slot(slot, index, tag, overloads.first, raw, answer, reasons)
84
+ type = authored_type(raw, tag, index)
85
+ compare_types(parsed, type, site, target, answer, reasons) if parsed && type
86
+ end
87
+ reason = REASONS.find { |candidate| reasons.include?(candidate) }
88
+ reason ? answer.merge("verdict" => "unknown", "reason" => reason, "evidence" => []) : answer
89
+ end
90
+
91
+ def supported_subject?(tag)
92
+ %w[param return].include?(tag.fetch("name")) && (tag["name"] == "return" || tag["subject"])
93
+ end
94
+
95
+ def authored_type(raw, tag, index)
96
+ contract_type(raw.types.first.type, tag, index) if raw&.types&.one?
97
+ end
98
+
99
+ def member_reasons(site, member)
100
+ reasons = []
101
+ overloads = member.fetch("overloads")
102
+ reasons << "no_authored_contract" if member["basis"] != "contract"
103
+ reasons << "unsupported_overloads" unless overloads.one? && overloads.first["params"]
104
+ reasons << "extrbs_contract" if site.contract_comments.any? do |comment|
105
+ comment.location.slice.match?(/\A#\s*@extrbs\b/)
106
+ end
107
+ reasons
108
+ end
109
+
110
+ def read_slot(slot, index, tag, overload, raw, answer, reasons)
111
+ answer.merge!("relation" => "documentation_coverage", "slot" => slot,
112
+ "direction" => tag["name"] == "param" ? "claim_within_contract" : "contract_within_claim")
113
+ contract = tag["name"] == "return" ? overload["return"] : overload.fetch("params")[index]["type"]
114
+ inspect_contract(contract, raw, tag, index, answer, reasons)
115
+ end
116
+
117
+ def inspect_contract(contract, raw, tag, index, answer, reasons)
118
+ if contract.nil? || contract["state"] == "unavailable" || contract["provenance"] == "inferred"
119
+ reasons << "no_authored_contract"
120
+ return
121
+ end
122
+ provenance = contract["provenance"]
123
+ reasons << "unsupported_contract_source" if provenance == "library" || provenance&.start_with?("plugin:")
124
+ answer["contract"] = contract.slice("type", "provenance", "path")
125
+ reasons << "refined_contract" if refined?(raw, tag, index)
126
+ end
127
+
128
+ def refined?(raw, tag, index)
129
+ return false if raw.nil?
130
+ return raw.annotations.any? { |text| text.start_with?("rigor:v1:return:") } if tag["name"] == "return"
131
+
132
+ raw.types.any? do |method_type|
133
+ function = method_type.type
134
+ param = function_params(function)[index]
135
+ if function.respond_to?(:required_keywords)
136
+ keyword = function.required_keywords.find do |_key, value|
137
+ value.equal?(param)
138
+ end
139
+ end
140
+ name = keyword ? keyword.first : param&.name
141
+ name && raw.annotations.any? { |text| text.match?(/\Arigor:v1:param:\s*#{Regexp.escape(name.to_s)}\s/) }
142
+ end
143
+ end
144
+
145
+ def subject(tag, site, overload)
146
+ return [{ "kind" => "return" }, nil, nil] if tag["name"] == "return" && overload
147
+ return [nil, nil, nil] if tag["name"] == "return"
148
+
149
+ params = site.data["params"] || []
150
+ param = params.find { |entry| entry["name"] == tag["subject"] }
151
+ return [nil, nil, "no_such_parameter"] unless param
152
+ return [nil, nil, "unsupported_parameter_kind"] unless %w[required keyreq].include?(param["kind"])
153
+ return [nil, nil, nil] unless overload && overload["params"]
154
+
155
+ declared = overload.fetch("params")
156
+ index = parameter_index(param, params, declared)
157
+ return [nil, nil, "unmatched_parameter"] unless index
158
+
159
+ [{ "kind" => "param", "name" => param["name"], "param_kind" => param["kind"], "index" => index }, index, nil]
160
+ end
161
+
162
+ def parameter_index(param, params, declared)
163
+ if param["kind"] == "keyreq"
164
+ return declared.index { |entry| entry["kind"] == "keyreq" && entry["name"] == param["name"] }
165
+ end
166
+
167
+ source_positions = params.select { |entry| POSITIONALS.include?(entry["kind"]) }
168
+ contract_positions = declared.select { |entry| POSITIONALS.include?(entry["kind"]) }
169
+ return unless source_positions.map { |entry| entry["kind"] } == contract_positions.map { |entry| entry["kind"] }
170
+
171
+ source_positions.index(param)
172
+ end
173
+
174
+ def contract_type(function, tag, index)
175
+ tag["name"] == "return" ? function.return_type : function_params(function)[index]&.type
176
+ end
177
+
178
+ def function_params(function)
179
+ return [] unless function.respond_to?(:required_positionals)
180
+
181
+ function.required_positionals + function.optional_positionals + [function.rest_positionals].compact +
182
+ function.trailing_positionals + function.required_keywords.values + function.optional_keywords.values +
183
+ [function.rest_keywords].compact
184
+ end
185
+
186
+ def compare_types(parsed, contract, site, target, answer, reasons)
187
+ scope = @run.claim_contexts.fetch(target.absolute)
188
+ nesting = site.data.fetch("lexical_nesting")
189
+ reader = TypeReader.new(scope: scope, nesting: nesting, singleton_nesting: site.data["singleton_nesting"],
190
+ owner: site.data["owner"])
191
+ claim_arms = reader.read(parsed, claim: true)
192
+ contract_arms = reader.read(contract)
193
+ reasons.concat(reader.reasons.to_a)
194
+ return unless reasons.empty?
195
+
196
+ left, right = if answer["direction"] == "claim_within_contract"
197
+ [claim_arms, contract_arms]
198
+ else
199
+ [contract_arms, claim_arms]
200
+ end
201
+ comparator = Comparison.new(scope.environment.rbs_loader,
202
+ relative_conflict: nesting == [] && site.data["owner"] == "Object")
203
+ verdict, reason, evidence = comparator.call(left, right)
204
+ answer.merge!("verdict" => verdict, "reason" => reason, "evidence" => evidence)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -16,9 +16,13 @@ module Rigor
16
16
  # `overloads` hold `{rbs, params, block, return}`; a return the contract leaves to inference is nil there,
17
17
  # with `return_inferred` set. `buffers` are the declaring buffer names.
18
18
  Contract = Data.define(:overloads, :provenance, :path, :return_inferred, :buffers)
19
+ RawContract = Data.define(:types, :annotations)
20
+
21
+ attr_reader :raw_contracts
19
22
 
20
23
  def initialize(provenance)
21
24
  @provenance = provenance
25
+ @raw_contracts = {}
22
26
  end
23
27
 
24
28
  # The member's contract, or nil when it has none of its own. `name` is the Ruby method name.
@@ -28,6 +32,7 @@ module Rigor
28
32
  return nil if defs.empty?
29
33
 
30
34
  annotations = annotations_of(method, defs)
35
+ @raw_contracts[[owner, scope.to_s, name]] = RawContract.new(types: defs.map(&:type), annotations: annotations)
31
36
  return nil if annotations.include?(INFERRED_SIGNATURE)
32
37
 
33
38
  contract(defs, annotations)
@@ -24,7 +24,7 @@ module Rigor
24
24
  def to_h
25
25
  { "schema" => SCHEMA, "revision" => REVISION, "producer" => producer, "context" => context,
26
26
  "capabilities" => CAPABILITIES, "files" => @outcome.targets.map { |target| file(target) },
27
- "members" => @outcome.member_rows }
27
+ "members" => @outcome.member_rows, "claims" => nil }
28
28
  end
29
29
 
30
30
  private
@@ -26,7 +26,10 @@ module Rigor
26
26
  # `kind` is :module, :class or :singleton. `module_function` is :off, :on (a bare `module_function` written
27
27
  # directly in a module body covers the later direct defs) or :unknown (the reading Ruby gives and the one
28
28
  # `check`'s tables give could differ, so the later defs are omissions).
29
- Body = Struct.new(:current, :uncertain, :detached, :kind, :module_function)
29
+ # `lexical` is the named part of the body's Ruby cref chain, which a builder block does not extend (Ruby
30
+ # `class_eval`s the block on the new class, but its constants resolve in the scope that wrote it) and a
31
+ # `class << self` body extends only with its anonymous singleton class, left out here.
32
+ Body = Struct.new(:current, :uncertain, :detached, :kind, :module_function, :lexical)
30
33
 
31
34
  ATTRIBUTE_CALLS = { attr_reader: "reader", attr_writer: "writer", attr_accessor: "accessor" }.freeze
32
35
  OMITTED_CALLS = {
@@ -92,7 +95,7 @@ module Rigor
92
95
  end
93
96
 
94
97
  def detached
95
- @bodies.push(Body.new("public", false, true, :detached, :off))
98
+ @bodies.push(Body.new("public", false, true, :detached, :off, @bodies.last&.lexical))
96
99
  yield
97
100
  ensure
98
101
  @bodies.pop
@@ -124,14 +127,14 @@ module Rigor
124
127
  return detached { walk(node.body, nesting, singleton: singleton, in_block: true) } if node.body
125
128
  end
126
129
 
127
- walk_body(node.body, nesting, singleton: true, kind: :singleton)
130
+ walk_body(node.body, nesting, singleton: true, kind: :singleton, lexical: lexical_nesting(nesting))
128
131
  end
129
132
 
130
133
  # A class body's statements, tracking the running visibility and `module_function`.
131
- def walk_body(body, nesting, singleton:, kind:)
134
+ def walk_body(body, nesting, singleton:, kind:, lexical: nesting)
132
135
  return if body.nil?
133
136
 
134
- @bodies.push(Body.new("public", false, false, kind, :off))
137
+ @bodies.push(Body.new("public", false, false, kind, :off, lexical))
135
138
  statements = body.is_a?(Prism::StatementsNode) ? body.body : [body]
136
139
  statements.each { |child| walk_body_statement(child, nesting, singleton) }
137
140
  ensure
@@ -425,7 +428,7 @@ module Rigor
425
428
  "block" => nil, "body_return" => nil }, contracts: trailing ? :trailing : false)
426
429
  end
427
430
  block = node.value.block
428
- walk_body(block.body, nesting + [name], singleton: false, kind: :builder) if block&.body
431
+ walk_body(block.body, nesting + [name], singleton: false, kind: :builder, lexical: nesting) if block&.body
429
432
  end
430
433
 
431
434
  # Ruby forces a field reader named `initialize_copy` (say) private, but not its `Struct` writer: a `Data`
@@ -539,7 +542,9 @@ module Rigor
539
542
  data = { "kind" => "method", "name" => node.name.to_s, "owner" => owner, "scope" => scope.to_s,
540
543
  "attribute" => nil, "members" => [node.name.to_s], "module_function" => module_function,
541
544
  "visibility" => module_function ? "private" : visibility(node, nesting, scope),
542
- "params" => shape_params, "block" => shape_block, "body_return" => returns }
545
+ "params" => shape_params, "block" => shape_block, "body_return" => returns,
546
+ "lexical_nesting" => lexical_nesting(nesting),
547
+ "singleton_nesting" => scope == :singleton && node.receiver.nil? }
543
548
  return data unless module_function
544
549
 
545
550
  # Internal to the member pass: the singleton copy is public until `private_class_method` says otherwise,
@@ -549,6 +554,12 @@ module Rigor
549
554
  data.merge("singleton_visibility" => "public", "singleton_body_return" => copy)
550
555
  end
551
556
 
557
+ # The Ruby cref chain a def written here reads its constants through: the enclosing body's, which differs
558
+ # from the owner nesting under a builder block.
559
+ def lexical_nesting(nesting)
560
+ @bodies.last&.lexical || nesting
561
+ end
562
+
552
563
  # Whether the instance-scope return may differ from the singleton copy's: the body reads `self`, instance or
553
564
  # class state, or `super` (nested defs excluded, blocks included, which over-declines safely), or the return
554
565
  # names the owner's type, which is where a `self` that flowed through `itself`, `tap` or another module
@@ -628,13 +639,15 @@ module Rigor
628
639
  def alias_site(names, owner)
629
640
  new_name, old_name = names
630
641
  last, copy = alias_original(owner, old_name)
642
+ copied = copy&.data || {}
631
643
  visibility = FORCED_PRIVATE.include?(new_name) ? "private" : last&.data&.fetch("visibility")
632
644
  # `initialize` reads `void` by its name, which neither side of an alias across it keeps.
633
645
  returns = copy && ![new_name, old_name].include?("initialize") ? copy.data["body_return"] : nil
634
646
  { "kind" => "method", "name" => new_name, "owner" => owner, "scope" => "instance", "attribute" => nil,
635
647
  "members" => [new_name], "module_function" => false, "visibility" => visibility,
636
- "params" => copy&.data&.fetch("params"), "block" => copy&.data&.fetch("block"),
648
+ "params" => copied["params"], "block" => copied["block"],
637
649
  "body_return" => returns || Slot.unavailable("not_computed"),
650
+ "lexical_nesting" => copied["lexical_nesting"], "singleton_nesting" => copied["singleton_nesting"],
638
651
  "alias_of" => old_name, "aliased_node" => copy && aliased_node(copy) }
639
652
  end
640
653
 
@@ -29,7 +29,7 @@ module Rigor
29
29
  return Rigor::VERSION if absolute.start_with?("#{BUNDLED_PLUGINS}/")
30
30
 
31
31
  spec = Gem.loaded_specs.each_value.find do |gem|
32
- absolute.start_with?("#{gem.full_gem_path}/") # rigor:disable undefined-method
32
+ absolute.start_with?("#{gem.full_gem_path}/")
33
33
  end
34
34
  spec&.version&.to_s
35
35
  rescue StandardError
@@ -23,6 +23,12 @@ module Rigor
23
23
  Target = Data.define(:argument, :path, :absolute, :status, :reason, :reader, :diagnostics, :inputs)
24
24
  Outcome = Data.define(:targets, :member_rows, :plugins, :warm)
25
25
 
26
+ attr_reader :claim_contexts
27
+
28
+ def raw_contracts
29
+ @contract_reader&.raw_contracts || {}
30
+ end
31
+
26
32
  # @param root — the project root (absolute); every path in the answer is relative to it
27
33
  def initialize(configuration:, root:, arguments:, workers:)
28
34
  @configuration = configuration
@@ -80,6 +86,7 @@ module Rigor
80
86
  @contracts = {}
81
87
  @defaulted_blocks = {}
82
88
  @site_paths = {}
89
+ @claim_contexts = {}
83
90
  end
84
91
 
85
92
  # A file requested again, in any spelling, is answered once: the later entries read `duplicate`, naming the
@@ -134,6 +141,7 @@ module Rigor
134
141
  end
135
142
  end
136
143
  @captured[absolute] = reader
144
+ @claim_contexts[absolute] = scope
137
145
  end
138
146
 
139
147
  # The constant's type as `check` reads it ({Reflection.constant_type_for}): the value the project's source
data/lib/rigor/lens.rb CHANGED
@@ -10,7 +10,7 @@ module Rigor
10
10
  # Bumped on every breaking change to the `lens/v0` document (docs/internal-spec/lens.md § Status).
11
11
  REVISION = 1
12
12
  CAPABILITIES = %w[class module method attribute constant module_function builder alias body_return
13
- diagnostics inputs].freeze
13
+ diagnostics inputs claims].freeze
14
14
 
15
15
  module_function
16
16
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "environment/required_features"
4
+
5
+ module Rigor
6
+ # Issue #2961 — answers what `RBS::EnvironmentLoader#has_library?(version: nil)` answers, for the
7
+ # {ConfigAudit} library warnings. The CLI has not loaded `rbs` when that audit runs, and loading it costs
8
+ # ~90 ms on the warm boot-slim path, so without a loaded `rbs` this probes the filesystem instead: a gem's
9
+ # `sig/` directory, or a release-versioned directory under the bundled rbs gem's `stdlib/<name>/`
10
+ # (`Repository::GemRBS#load!`). A probe that cannot place the rbs gem answers true: it never warns when unsure.
11
+ module LibraryProbe
12
+ module_function
13
+
14
+ def predicate
15
+ if defined?(::RBS::EnvironmentLoader)
16
+ return ->(lib) { ::RBS::EnvironmentLoader.new.has_library?(library: lib, version: nil) }
17
+ end
18
+
19
+ stdlib = stdlib_root
20
+ return ->(_lib) { true } if stdlib.nil?
21
+
22
+ ->(lib) { gem_sig_dir?(lib) || stdlib_library?(stdlib, lib) }
23
+ end
24
+
25
+ # The names of `library_names` the environment loads: a default library, a vendored feature token, or a name
26
+ # RBS has. The loader drops any other name (`RbsLoader.library_loader` keeps only `has_library?` ones), so it
27
+ # covers nothing. The predicate is built only when a name needs it: building it can construct an RBS loader.
28
+ # Callers have `Environment` loaded, which defines `DEFAULT_LIBRARIES`.
29
+ def loadable(library_names)
30
+ has = nil
31
+ library_names.select do |name|
32
+ Environment::DEFAULT_LIBRARIES.include?(name) || Environment::RequiredFeatures.feature_of_token(name) ||
33
+ (has ||= predicate).call(name)
34
+ end
35
+ end
36
+
37
+ # The `libraries:` names worth auditing: a vendored feature name (`prime`) becomes a vendored feature token
38
+ # that the loader never asks `has_library?` about, so it is not an RBS library name to check.
39
+ def auditable(libraries)
40
+ Array(libraries).map(&:to_s) - Environment::RequiredFeatures::VENDORED_DIRS.keys
41
+ end
42
+
43
+ def stdlib_root
44
+ dir = File.join(Gem::Specification.find_by_name("rbs").gem_dir, "stdlib")
45
+ File.directory?(dir) ? dir : nil
46
+ rescue Gem::LoadError
47
+ nil
48
+ end
49
+
50
+ def gem_sig_dir?(name)
51
+ File.directory?(File.join(Gem::Specification.find_by_name(name).gem_dir, "sig"))
52
+ rescue Gem::MissingSpecError
53
+ false
54
+ rescue Gem::LoadError
55
+ true
56
+ end
57
+
58
+ def stdlib_library?(stdlib, name)
59
+ return false if name.include?("/") || name.include?("\0")
60
+
61
+ Dir.children(File.join(stdlib, name)).any? do |child|
62
+ Gem::Version.correct?(child) && !Gem::Version.create(child).prerelease?
63
+ end
64
+ rescue SystemCallError
65
+ false
66
+ end
67
+ end
68
+ end