rigortype 0.3.7 → 0.3.9

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 (450) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +96 -15
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +69 -13
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +100 -29
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +316 -40
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +166 -30
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +70 -0
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -8,10 +8,12 @@ require_relative "environment/rbs_loader"
8
8
  require_relative "environment/reflection"
9
9
  require_relative "environment/reporters"
10
10
  require_relative "environment/hkt_registry_holder"
11
+ require_relative "environment/failure_slot"
11
12
  require_relative "environment/constant_type_cache_holder"
12
13
  require_relative "environment/missing_gem_constant_index"
13
14
  require_relative "environment/bundle_sig_discovery"
14
15
  require_relative "environment/lockfile_resolver"
16
+ require_relative "environment/installed_gem_set"
15
17
  require_relative "environment/rbs_collection_discovery"
16
18
  require_relative "plugin/isolation"
17
19
  require_relative "environment/rbs_coverage_report"
@@ -47,16 +49,15 @@ module Rigor
47
49
  :reporters, :name_scope,
48
50
  :synthetic_method_index, :project_patched_methods
49
51
 
50
- # @param class_registry [Rigor::Environment::ClassRegistry]
51
- # @param rbs_loader [Rigor::Environment::RbsLoader, nil] when nil the environment is "RBS-blind"; useful
52
+ # @param rbs_loader — when nil the environment is "RBS-blind"; useful
52
53
  # in tests that want to assert how the engine behaves without RBS data. The default Environment wires
53
54
  # the shared core loader, which is itself lazy: requesting an environment instance does NOT load RBS
54
55
  # until a method or class query actually consults the loader.
55
- # @param plugin_registry [Rigor::Plugin::Registry, nil] v0.1.1 Track 2 slice 7. The per-run plugin
56
+ # @param plugin_registry — v0.1.1 Track 2 slice 7. The per-run plugin
56
57
  # registry the inference engine consults at call sites for plugin `dynamic_return` rules. When nil
57
58
  # (the default), no plugin-level return-type contribution participates — useful for tests, the
58
59
  # `Environment.default` facade, and analyses that don't load plugins.
59
- # @param dependency_source_index [Rigor::Analysis::DependencySourceInference::Index, nil] ADR-10 slice
60
+ # @param dependency_source_index — ADR-10 slice
60
61
  # 2b-ii. The per-run index of opt-in gem sources the dispatcher consults BELOW RBS dispatch. When nil
61
62
  # (the default), no dep-source contribution participates and the dispatcher tier is a no-op.
62
63
  def initialize(class_registry: ClassRegistry.default, rbs_loader: nil, # rubocop:disable Metrics/ParameterLists
@@ -88,6 +89,10 @@ module Rigor
88
89
  # --no-stats` from doing the RBS env build at all.
89
90
  @hkt_registry_base = hkt_registry || Inference::HktRegistry::EMPTY
90
91
  @hkt_registry_holder = HktRegistryHolder.new
92
+ # Issue #784 — where either stage of the HKT-registry build (the plugin overlay, #791; the RBS
93
+ # `type`-alias scan) records a raise, so it surfaces once for the run instead of once per file — or,
94
+ # for the overlay, instead of aborting the run outright (see {#hkt_registry}).
95
+ @hkt_scan_failure = FailureSlot.new
91
96
  @constant_type_cache = ConstantTypeCacheHolder.new
92
97
  # ADR-82 WD9 — `[gem_name, version]` pairs for the locked gems with no resolvable RBS. The
93
98
  # root-constant ownership index over them is built lazily (first unresolved constant read) so runs
@@ -104,21 +109,91 @@ module Rigor
104
109
  # beat plugin entries, which beat the bundled JSON_VALUE. Memoised; single-threaded use only (under the
105
110
  # Ractor pool path each worker has its own Environment so cross-worker mutation is impossible; the LSP
106
111
  # single-publish-at-a-time invariant serialises here).
112
+ #
113
+ # BOTH stages are guarded (#784, #791): whichever raises is recorded on {#hkt_scan_failure} with the
114
+ # stage that failed and the other stage still runs, so this getter never raises into its callers —
115
+ # neither into a file's `analyze_body` rescue nor out of the run-owned demands, which have no rescue
116
+ # above them at all.
107
117
  def hkt_registry
108
118
  @hkt_registry_holder.fetch do
109
- with_plugin_overlay = if @plugin_registry.respond_to?(:hkt_overlay_registry)
110
- @hkt_registry_base.merge(@plugin_registry.hkt_overlay_registry)
111
- else
112
- @hkt_registry_base
113
- end
114
- Inference::HktRegistry.scan_rbs_loader(
115
- @rbs_loader,
116
- base: with_plugin_overlay,
117
- reporter: rbs_extended_reporter
118
- )
119
+ pre_scan = pre_scan_hkt_registry
120
+ begin
121
+ Inference::HktRegistry.scan_rbs_loader(
122
+ @rbs_loader,
123
+ base: pre_scan,
124
+ reporter: rbs_extended_reporter
125
+ )
126
+ rescue StandardError => e
127
+ # Issue #784 — the seam. This build is shared and memoised, and it is first demanded from inside
128
+ # a file's analysis, so a raise here would otherwise land in every file's `analyze_body` rescue
129
+ # and turn the whole run into N identical `internal analyzer error` rows (issue #776). Record it
130
+ # and degrade to the pre-scan registry: analysis proceeds, HKT inference from RBS `type` aliases
131
+ # reads its bound (`Dynamic[top]`, ADR-20 WD2), and the run surfaces ONE
132
+ # `rbs.coverage.hkt-scan-failed` row. Not a silent skip — the row is `:error` and names the
133
+ # exception and raise site — and the same shape as `RbsLoader#record_env_build_failure`.
134
+ # Memoising the degraded registry is what keeps the scan from being re-attempted per file.
135
+ record_hkt_registry_failure(e, stage: :scan)
136
+ pre_scan
137
+ end
119
138
  end
120
139
  end
121
140
 
141
+ # Issue #791 — the OTHER build behind {#hkt_registry}, and the reason the seam covers two stages
142
+ # rather than one. Aggregating the loaded plugins' manifest-declared HKT entries runs plugin-authored
143
+ # code (`Plugin::Registry#hkt_overlay_registry` reads each manifest); before this it sat one line ABOVE
144
+ # the #784 rescue, so a raise there escaped {#hkt_registry} entirely. Post-#788 that is not a per-file
145
+ # crash storm but an uncaught abort: {HktRegistryHolder#fetch} memoises only on success, so the raise
146
+ # is re-attempted at every demand, and the run-owned demands (`Runner::PoolCoordinator#hkt_scan_outcome`
147
+ # after the file loop, `Analysis::WorkerSession#drain_reporters`) sit outside any rescue.
148
+ #
149
+ # The degradation is narrower than the scan's: only the plugin overlay is dropped, and the RBS scan
150
+ # still runs on top of the bundled base, so a user's own `.rbs` HKT registrations survive a plugin
151
+ # defect. First-write-wins on the slot means a run whose overlay AND scan both raise reports the
152
+ # overlay — the actionable one, since the plugin is what a user can remove.
153
+ #
154
+ # @return the registry the RBS scan is layered on top of.
155
+ def pre_scan_hkt_registry
156
+ return @hkt_registry_base unless @plugin_registry.respond_to?(:hkt_overlay_registry)
157
+
158
+ @hkt_registry_base.merge(@plugin_registry.hkt_overlay_registry)
159
+ rescue StandardError => e
160
+ record_hkt_registry_failure(e, stage: :overlay)
161
+ @hkt_registry_base
162
+ end
163
+ private :pre_scan_hkt_registry
164
+
165
+ # Issue #784 — the `[error_class_name, first_message_line, raw_frame_or_nil, stage]` tuple the seam in
166
+ # {#hkt_registry} recorded, or nil when both stages built (or were never demanded). `stage` is `:scan`
167
+ # for the RBS `type`-alias scan and `:overlay` for the plugin-manifest aggregation (#791); it decides
168
+ # the row's wording, because "the implicit HKT scan over RBS `type` aliases raised" would point a user
169
+ # at their own `.rbs` for a plugin's defect. The run reads the tuple only after demanding
170
+ # {#hkt_registry} itself — after the file loop on the sequential paths, at drain time in each pool
171
+ # worker (the coordinator never analyses a file under the pool, so the drain is how its snapshot learns
172
+ # the outcome: the #696 lesson), and from a resolved environment when the analyze set is empty — so the
173
+ # row never depends on which files happened to be analysed.
174
+ def hkt_scan_failure
175
+ @hkt_scan_failure.value
176
+ end
177
+
178
+ def record_hkt_registry_failure(error, stage:)
179
+ frames = error.backtrace || []
180
+ frame = frames.find { |f| f.include?("/lib/rigor/") } || frames.first
181
+ # `Class#name` is nil for an anonymous exception class; its nearest NAMED ancestor is the stable
182
+ # description (`inspect` embeds an object address, which would make the row's text differ across
183
+ # processes). Each String is frozen individually, not just the Array: the tuple crosses the fork
184
+ # boundary Marshal-clean and the drain channel's stated invariant
185
+ # ({Analysis::WorkerSession#drain_reporters}) is that its payload is also `Ractor.shareable?`, which a
186
+ # shallow freeze over `chomp`'s fresh String would not satisfy.
187
+ named = error.class.ancestors.find { |a| a.is_a?(Class) && a.name }
188
+ @hkt_scan_failure.record([
189
+ (error.class.name || "anonymous #{named&.name || 'Exception'}").dup.freeze,
190
+ error.message.to_s.lines.first.to_s.chomp.freeze,
191
+ frame&.dup&.freeze,
192
+ stage
193
+ ])
194
+ end
195
+ private :record_hkt_registry_failure
196
+
122
197
  # ADR-82 WD9 — the gem name owning `root_constant_name`, when that gem is locked in the project's
123
198
  # Gemfile.lock, ships no resolvable RBS, and its entry file declares the constant at top level. Nil for
124
199
  # everything else — the caller then keeps the generic provenance cause. The index is built once, lazily,
@@ -178,18 +253,17 @@ module Rigor
178
253
  # Builds an Environment that consults the project's local signatures and any opt-in stdlib libraries on
179
254
  # top of RBS core.
180
255
  #
181
- # @param root [String, Pathname] project root used to auto-detect the default signature path. Defaults
256
+ # @param root — project root used to auto-detect the default signature path. Defaults
182
257
  # to the current working directory.
183
- # @param libraries [Array<String, Symbol>] additional stdlib libraries to load on top of
258
+ # @param libraries — additional stdlib libraries to load on top of
184
259
  # {DEFAULT_LIBRARIES}. The final list is the union of the two, de-duplicated while preserving order.
185
260
  # Pass an empty array (the default) to load only the defaults.
186
- # @param signature_paths [Array<String, Pathname>, nil] explicit list of `sig/`-style directories. When
261
+ # @param signature_paths — explicit list of `sig/`-style directories. When
187
262
  # `nil` (the default), the canonical project layout `<root>/sig` is used if it exists, otherwise no
188
263
  # signature path is loaded.
189
- # @param cache_store [Rigor::Cache::Store, nil] persistent cache threaded into the underlying
264
+ # @param cache_store — persistent cache threaded into the underlying
190
265
  # {Environment::RbsLoader} so constant lookups (and, in later v0.0.9 slices, other reflection
191
266
  # artefacts) consult the cache. Pass `nil` (the default) to skip caching for this environment.
192
- # @return [Rigor::Environment]
193
267
  # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
194
268
  def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil,
195
269
  plugin_registry: nil, dependency_source_index: nil,
@@ -259,7 +333,7 @@ module Rigor
259
333
  missing_gems, overlay_paths = missing_gems_and_overlay_paths(
260
334
  locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
261
335
  rbs_collection_paths: collection_paths, plugin_registry: plugin_registry,
262
- signature_paths: resolved_paths
336
+ signature_paths: resolved_paths, bundle_root: bundle_root
263
337
  )
264
338
  loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
265
339
  collection_paths + overlay_paths
@@ -284,6 +358,13 @@ module Rigor
284
358
  # the bundle walk and `rbs collection install` all stay authoritative.
285
359
  deferred_signature_paths: plugin_sig_paths
286
360
  )
361
+ # Issue #824 / ADR-32 WD13 — one `:info` per inline member the loader stood down against the
362
+ # project's own `sig/`. Recorded HERE because this is where the reporter is: the run's
363
+ # `source_rbs_synthesis` stream already carries WD12's "parsed but not honoured" rows, drains out of
364
+ # pool workers, and is regenerated (never cached) on every run, so the member-level stand-down needs
365
+ # no plumbing of its own. Costs nothing for a project with no inline RBS: the loader's reader
366
+ # short-circuits on an empty `virtual_rbs`.
367
+ record_inline_member_standdowns(loader, source_rbs_synthesis_reporter, root)
287
368
  # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
288
369
  # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
289
370
  # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
@@ -335,13 +416,13 @@ module Rigor
335
416
  # and consumed twice: as `[gem_name, version]` pairs for the ADR-82 WD9 constant-ownership index, and
336
417
  # as the eligible-gem set for the ADR-72 overlay resolution. Returns `[pairs, overlay_paths]`.
337
418
  def missing_gems_and_overlay_paths(locked:, default_libraries:, bundle_sig_paths:,
338
- rbs_collection_paths:, plugin_registry:, signature_paths: [])
339
- return [[], []] if locked.empty?
419
+ rbs_collection_paths:, plugin_registry:, signature_paths: [],
420
+ bundle_root: nil)
421
+ if locked.empty?
422
+ return unlocked_missing_gems(default_libraries, bundle_sig_paths, rbs_collection_paths, bundle_root)
423
+ end
340
424
 
341
- rows = RbsCoverageReport.classify(
342
- locked_gems: locked, default_libraries: default_libraries,
343
- bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
344
- ).select { |row| row.source == :missing }
425
+ rows = missing_rows(locked, default_libraries, bundle_sig_paths, rbs_collection_paths)
345
426
  overlays = gem_overlay_paths(
346
427
  missing_gem_names: rows.map(&:gem_name), plugin_registry: plugin_registry,
347
428
  signature_paths: signature_paths
@@ -349,6 +430,27 @@ module Rigor
349
430
  [rows.map { |row| [row.gem_name, row.version] }, overlays]
350
431
  end
351
432
 
433
+ # Issue #530 — a project with no `Gemfile.lock` still has gems on disk, and before this it handed the
434
+ # ADR-82 WD9 index nothing, so every constant reaching into an RBS-less gem kept the generic cause and
435
+ # the whole boundary reported as `engine_gap` rather than `add_rbs`. The installed set stands in for
436
+ # the locked one ({InstalledGemSet} says why that stays honest), and only for the provenance index:
437
+ # the second element is always empty because the ADR-72 overlays change what type-checks and so remain
438
+ # gated on the project's own declaration.
439
+ def unlocked_missing_gems(default_libraries, bundle_sig_paths, rbs_collection_paths, bundle_root)
440
+ installed = InstalledGemSet.gems(bundle_path: bundle_root)
441
+ return [[], []] if installed.empty?
442
+
443
+ rows = missing_rows(installed, default_libraries, bundle_sig_paths, rbs_collection_paths)
444
+ [rows.map { |row| [row.gem_name, row.version] }, []]
445
+ end
446
+
447
+ def missing_rows(gems, default_libraries, bundle_sig_paths, rbs_collection_paths)
448
+ RbsCoverageReport.classify(
449
+ locked_gems: gems, default_libraries: default_libraries,
450
+ bundle_sig_paths: bundle_sig_paths, rbs_collection_paths: rbs_collection_paths
451
+ ).select { |row| row.source == :missing }
452
+ end
453
+
352
454
  # ADR-72 — resolve the bundled RBS overlay directories to load for this project. A gem is eligible when
353
455
  # it is classified `:missing` (see {missing_rbs_gem_rows}) and its conflicting opt-in plugin twin (if
354
456
  # any) is NOT already reachable. Returns `[Pathname]`, deterministically ordered, or `[]` when no
@@ -463,6 +565,54 @@ module Rigor
463
565
  reporter&.record(plugin_id: plugin.manifest.id, path: path, message: message.to_s, kind: kind)
464
566
  end
465
567
 
568
+ # ADR-32 WD13 — turn each {Environment::RbsLoader#inline_member_standdowns} record into a WD12
569
+ # `:not_honoured` entry, so the run's `plugin.rbs-inline.source-rbs-annotation-not-honoured` row names
570
+ # BOTH sources and which one won. WD12's rule is what makes this necessary rather than optional: an
571
+ # annotation Rigor parses and does not honour is reported, never swallowed — and a silent per-member
572
+ # strip is exactly the shape that rule forbids.
573
+ #
574
+ # The row is positioned at the annotated `.rb` (line 1, as every entry on this stream is): the member's
575
+ # own position lives in the synthesized RBS buffer, which describes a document nobody has, and the
576
+ # ADR-54 environment cache drops positions anyway, so a line read off it would differ warm and cold.
577
+ def record_inline_member_standdowns(loader, reporter, root)
578
+ return if reporter.nil?
579
+
580
+ loader.inline_member_standdowns.each do |class_name, method_name, kind, signature_path, virtual_name|
581
+ plugin_id, path = split_virtual_source_name(virtual_name)
582
+ next if path.nil?
583
+
584
+ reporter.record(
585
+ plugin_id: plugin_id, path: path, kind: :not_honoured,
586
+ message: inline_member_standdown_message(class_name, method_name, kind, signature_path, root)
587
+ )
588
+ end
589
+ end
590
+
591
+ # Splits a virtual buffer name back into the pair {.collect_virtual_rbs} composed it from
592
+ # (`virtual:<plugin id>:<source path>`). Limited to three fields so a path carrying a colon survives.
593
+ # Returns `[nil, nil]` for a name in any other shape — a synthesizer channel this method does not own.
594
+ def split_virtual_source_name(virtual_name)
595
+ prefix, plugin_id, path = virtual_name.to_s.split(":", 3)
596
+ return [nil, nil] unless prefix == "virtual" && plugin_id && !plugin_id.empty? && path && !path.empty?
597
+
598
+ [plugin_id, path]
599
+ end
600
+
601
+ def inline_member_standdown_message(class_name, method_name, kind, signature_path, root)
602
+ separator = kind == :singleton ? "." : "#"
603
+ "`#{class_name}#{separator}#{method_name}` is also declared in " \
604
+ "`#{path_relative_to(signature_path, root)}`, and an explicit `.rbs` declaration wins over an " \
605
+ "inline annotation for the same member, so the inline signature was dropped. Left to collide the " \
606
+ "two would fail the class's definition build, and every call on it — real methods and typos " \
607
+ "alike — would read `Dynamic[top]`. Remove one of the two declarations to make the inline " \
608
+ "annotation bind."
609
+ end
610
+
611
+ def path_relative_to(path, root)
612
+ prefix = "#{root}#{File::SEPARATOR}"
613
+ path.to_s.start_with?(prefix) ? path.to_s.delete_prefix(prefix) : path.to_s
614
+ end
615
+
466
616
  SYNTHESIZER_CACHE_PRODUCER_ID = "plugin.source_rbs_synthesizer"
467
617
  private_constant :SYNTHESIZER_CACHE_PRODUCER_ID
468
618
 
@@ -576,6 +726,15 @@ module Rigor
576
726
  rbs_loader.rbs_module?(name)
577
727
  end
578
728
 
729
+ # Issue #915 — the module names RBS puts in `name`'s SINGLETON ancestry, the declaration-side twin of
730
+ # `Scope#singleton_extends_of`. `Inference::Narrowing` reads it the same one-directional way: presence
731
+ # withholds a `Bot`, absence asserts nothing (a class the environment does not know answers `[]`).
732
+ def singleton_extended_modules(name)
733
+ return [] unless rbs_loader
734
+
735
+ rbs_loader.singleton_extended_module_names(normalize_class_name(name))
736
+ end
737
+
579
738
  # Compares two class/module names using analyzer-owned class data. Returns `:equal`, `:subclass`,
580
739
  # `:superclass`, `:disjoint`, or `:unknown`. The static registry handles built-ins cheaply; the RBS
581
740
  # loader handles project/stdlib classes without relying on host Ruby constants being loaded.
@@ -600,8 +759,11 @@ module Rigor
600
759
  rbs_loader.class_known?(name)
601
760
  end
602
761
 
762
+ # Allocates only when there is a prefix to strip: `String#delete_prefix` copies unconditionally, and
763
+ # this runs once per class-registry / hierarchy probe (~320k times on the lib self-check).
603
764
  def normalize_class_name(name)
604
- name.to_s.delete_prefix("::")
765
+ name = name.to_s
766
+ name.start_with?("::") ? name.delete_prefix("::") : name
605
767
  end
606
768
 
607
769
  # ADR-13 slice 3b — composes the per-run plugin-supplied {Rigor::TypeNode::ResolverChain} into a single
@@ -53,8 +53,6 @@ module Rigor
53
53
 
54
54
  module_function
55
55
 
56
- # @param contributions [Array<FlowContribution>]
57
- # @return [MergeResult]
58
56
  def merge(contributions)
59
57
  contributions = Array(contributions)
60
58
  return MergeResult.new if contributions.empty?
@@ -49,24 +49,24 @@ module Rigor
49
49
 
50
50
  attr_reader(*SLOT_NAMES, :provenance)
51
51
 
52
- # @param return_type [Object, nil] normal-edge return type. Use `nil` when the contribution does not
52
+ # @param return_type — normal-edge return type. Use `nil` when the contribution does not
53
53
  # refine the return type selected from the RBS contract.
54
- # @param truthy_facts [Array, nil] facts that hold only on the truthy control-flow edge. Edge-local: a
54
+ # @param truthy_facts — facts that hold only on the truthy control-flow edge. Edge-local: a
55
55
  # truthy-edge fact does NOT imply its falsey-edge complement (ADR-2 § "Plugin Contribution Merging").
56
- # @param falsey_facts [Array, nil] dual of `truthy_facts`.
57
- # @param post_return_facts [Array, nil] facts that hold after the call returns normally on every edge —
56
+ # @param falsey_facts — dual of `truthy_facts`.
57
+ # @param post_return_facts — facts that hold after the call returns normally on every edge —
58
58
  # the carrier for assertion-style contributions.
59
- # @param mutations [Array, nil] receiver and argument mutation effects.
60
- # @param invalidations [Array, nil] targeted fact invalidations beyond what mutation effects already
59
+ # @param mutations — receiver and argument mutation effects.
60
+ # @param invalidations — targeted fact invalidations beyond what mutation effects already
61
61
  # imply.
62
- # @param exceptional [Object, nil] non-returning, raising, or unreachable effect.
63
- # @param role_conformance [Array, nil] capability-role conformance facts the contribution provides.
64
- # @param effects [Rigor::Effects::LabelSet, nil] ADR-103 WD5 — the effect labels this call edge
62
+ # @param exceptional — non-returning, raising, or unreachable effect.
63
+ # @param role_conformance — capability-role conformance facts the contribution provides.
64
+ # @param effects — ADR-103 WD5 — the effect labels this call edge
65
65
  # attributes to its callee, as an upper bound. `nil` means "says nothing about effects", which is
66
66
  # NOT the same as the empty set (which asserts the call performs none). Merged by union, the
67
67
  # conservative direction: two sources that each name part of a call's footprint together name
68
68
  # more of it, and neither can shrink the other's claim.
69
- # @param provenance [Provenance] source-family, plugin-id, node, and cache-descriptor metadata. Defaults
69
+ # @param provenance — source-family, plugin-id, node, and cache-descriptor metadata. Defaults
70
70
  # to `Provenance.builtin`.
71
71
  # rubocop:disable Metrics/ParameterLists
72
72
  def initialize(return_type: nil, truthy_facts: nil, falsey_facts: nil,
@@ -87,7 +87,7 @@ module Rigor
87
87
  freeze
88
88
  end
89
89
 
90
- # @return [Boolean] true when every content slot is unset (nil or an empty collection). Provenance does
90
+ # @return true when every content slot is unset (nil or an empty collection). Provenance does
91
91
  # not count toward emptiness — an empty bundle still carries source attribution.
92
92
  def empty?
93
93
  SLOT_NAMES.all? { |slot| slot_empty?(slot, public_send(slot)) }
@@ -116,8 +116,6 @@ module Rigor
116
116
  # | exceptional | exceptional | exception | :raise |
117
117
  # | role_conformance | normal | role | (per-role target) |
118
118
  # | effects | normal | effects | :effects |
119
- #
120
- # @return [Array<Element>]
121
119
  def to_element_list # rubocop:disable Metrics/AbcSize
122
120
  elements = []
123
121
  elements << element_for(:return, :normal, :return_type, return_type) unless return_type.nil?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../type"
4
+ require_relative "range_constant"
4
5
 
5
6
  module Rigor
6
7
  module Inference
@@ -27,10 +28,7 @@ module Rigor
27
28
  module Acceptance
28
29
  module_function
29
30
 
30
- # @param self_type [Rigor::Type]
31
- # @param other_type [Rigor::Type]
32
- # @param mode [Symbol] `:gradual` (default) or `:strict`.
33
- # @return [Rigor::Type::AcceptsResult]
31
+ # @param mode — `:gradual` (default) or `:strict`.
34
32
  def accepts(self_type, other_type, mode: :gradual)
35
33
  raise ArgumentError, "Acceptance mode #{mode.inspect} is not implemented yet" unless mode == :gradual
36
34
 
@@ -63,6 +61,7 @@ module Rigor
63
61
  Type::Nominal => :accepts_nominal,
64
62
  Type::Constant => :accepts_constant,
65
63
  Type::IntegerRange => :accepts_integer_range,
64
+ Type::FloatRange => :accepts_float_range,
66
65
  Type::Difference => :accepts_difference,
67
66
  Type::Refined => :accepts_refined,
68
67
  Type::Intersection => :accepts_intersection,
@@ -201,7 +200,8 @@ module Rigor
201
200
  # {#accepts_nominal_args});
202
201
  # - Constant[v] when v.is_a?(klass(C)). The type_args of self are ignored here because a Constant
203
202
  # carries a concrete value, not a generic instantiation, and the analyzer has no way to refute the
204
- # args from a literal alone.
203
+ # args from a literal alone. A Range value is the one exception — see
204
+ # {#accepts_range_constant_type_arg}.
205
205
  # - Tuple[*] when self is the Array (or a supertype) family. The Tuple is projected to
206
206
  # `Nominal[Array, [union(elements)]]` so the existing generic-arg machinery handles it.
207
207
  # - HashShape{*} when self is the Hash (or a supertype) family, projected to
@@ -215,6 +215,7 @@ module Rigor
215
215
  when Type::Singleton then accepts_nominal_from_singleton(self_type, other_type, mode)
216
216
  when Type::StructClass, Type::DataClass then accepts_nominal_from_class_factory(self_type, other_type, mode)
217
217
  when Type::IntegerRange then accepts_nominal_from_integer_range(self_type, other_type, mode)
218
+ when Type::FloatRange then accepts_nominal_from_float_range(self_type, other_type, mode)
218
219
  else accepts_nominal_from_shape(self_type, other_type, mode)
219
220
  end
220
221
  end
@@ -311,6 +312,32 @@ module Rigor
311
312
  end
312
313
  end
313
314
 
315
+ # ADR-109 WD4 — the Float twin of the IntegerRange rule: `Nominal[Float]` and everything Float
316
+ # is-a accept any `FloatRange`; nothing else does.
317
+ FLOAT_NOMINAL_ANCESTORS = %w[Float Numeric Comparable Object BasicObject].freeze
318
+ private_constant :FLOAT_NOMINAL_ANCESTORS
319
+
320
+ def accepts_nominal_from_float_range(self_type, _other_type, mode)
321
+ unless self_type.type_args.empty?
322
+ return Type::AcceptsResult.no(
323
+ mode: mode,
324
+ reasons: "Nominal[#{self_type.class_name}] with type args rejects FloatRange"
325
+ )
326
+ end
327
+
328
+ if FLOAT_NOMINAL_ANCESTORS.include?(self_type.class_name)
329
+ Type::AcceptsResult.yes(
330
+ mode: mode,
331
+ reasons: "FloatRange is-a #{self_type.class_name}"
332
+ )
333
+ else
334
+ Type::AcceptsResult.no(
335
+ mode: mode,
336
+ reasons: "Nominal[#{self_type.class_name}] rejects FloatRange"
337
+ )
338
+ end
339
+ end
340
+
314
341
  # v0.0.2 — meta-type rule. A `Singleton[T]` is the class object for `T`, so it is an instance of
315
342
  # `Class` (when `T` is a class) and always an instance of `Module`. Without this rule a method whose
316
343
  # parameter is typed `Class | Module` would reject every `is_a?(SomeClass)` call and similar
@@ -492,7 +519,7 @@ module Rigor
492
519
 
493
520
  def accepts_nominal_from_constant(self_type, constant, mode)
494
521
  ruby_class = resolve_class(self_type.class_name)
495
- return constant_is_a_result(ruby_class, constant, self_type, mode) if ruby_class
522
+ return accepts_loaded_class_from_constant(ruby_class, constant, self_type, mode) if ruby_class
496
523
 
497
524
  # The host process may not have required the constant's declared self_type (e.g. `BigDecimal`
498
525
  # since Ruby 3.4 is no longer a default gem). Fall back to inspecting the value's own class
@@ -527,6 +554,13 @@ module Rigor
527
554
  end
528
555
  end
529
556
 
557
+ def accepts_loaded_class_from_constant(ruby_class, constant, self_type, mode)
558
+ result = constant_is_a_result(ruby_class, constant, self_type, mode)
559
+ return result unless result.yes?
560
+
561
+ accepts_range_constant_type_arg(self_type, constant, mode) || result
562
+ end
563
+
530
564
  def constant_is_a_result(ruby_class, constant, self_type, mode)
531
565
  if constant.value.is_a?(ruby_class)
532
566
  Type::AcceptsResult.yes(mode: mode, reasons: "Constant value is_a?(#{self_type.class_name})")
@@ -538,10 +572,53 @@ module Rigor
538
572
  end
539
573
  end
540
574
 
575
+ # Issue #833 — the single exception to "the type_args of self are ignored for a Constant".
576
+ #
577
+ # A `Constant<Range>` is the one literal that does pin a generic instantiation: the engine
578
+ # builds it only when both endpoints are static, so `Range[T]` CAN be refuted from the literal.
579
+ # Without this, `Nominal[Range, [Integer]]` accepted `0.0...1.0` and `OverloadSelector` pass 1
580
+ # pinned whichever `Range[…]` arm came first in declaration order — for `Kernel#rand` that is
581
+ # `(::Range[Integer]) -> Integer?`, ahead of every Float arm.
582
+ #
583
+ # Returns nil — "no opinion, keep the class-level answer" — outside the envelope: a raw `Range`
584
+ # or any other arity of type_args, and a range the shared reader cannot read endpoints from.
585
+ # A missing endpoint contributes nothing, because a beginless or endless literal inhabits
586
+ # `Range[T]` for every T (`Range[Integer?]` is the common core spelling of the slicing param).
587
+ #
588
+ # Known coarse spot, deliberately not special-cased: a mixed-endpoint literal (`1.0..2`) is
589
+ # rejected by `Range[Integer]` and by `Range[Float]` alike, so overload selection falls through
590
+ # to its gradual pass and then to the first-overload fallback exactly as it did before.
591
+ def accepts_range_constant_type_arg(self_type, constant, mode)
592
+ return nil unless self_type.type_args.size == 1
593
+
594
+ endpoints = RangeConstant.literal_endpoints(constant)
595
+ return nil if endpoints.nil?
596
+
597
+ element = self_type.type_args.first
598
+ combine_range_endpoint_results(
599
+ endpoints.map { |endpoint| accepts(element, Type::Combinator.constant_of(endpoint), mode: mode) },
600
+ element,
601
+ mode
602
+ )
603
+ end
604
+
605
+ def combine_range_endpoint_results(per_endpoint, element, mode)
606
+ if per_endpoint.any?(&:no?)
607
+ Type::AcceptsResult.no(mode: mode, reasons: "a Range endpoint is rejected by #{element.describe}")
608
+ elsif per_endpoint.any?(&:maybe?)
609
+ Type::AcceptsResult.maybe(
610
+ mode: mode,
611
+ reasons: "a Range endpoint could not be proven accepted by #{element.describe}"
612
+ )
613
+ else
614
+ Type::AcceptsResult.yes(mode: mode, reasons: "every Range endpoint accepted by #{element.describe}")
615
+ end
616
+ end
617
+
541
618
  # IntegerRange[a..b] accepts:
542
619
  # - Constant[n] where n is an Integer covered by [a..b];
543
620
  # - IntegerRange[c..d] where [c..d] ⊆ [a..b];
544
- # - Nominal[Integer] only when self is the universal range (`int<min, max>`), since otherwise an
621
+ # - Nominal[Integer] only when self is the universal range (`Integer[min..max]`), since otherwise an
545
622
  # arbitrary Integer could fall outside the bound.
546
623
  # Anything else is rejected.
547
624
  def accepts_integer_range(self_type, other_type, mode)
@@ -616,6 +693,65 @@ module Rigor
616
693
  end
617
694
  end
618
695
 
696
+ # ADR-109 WD4 — `FloatRange` accepts:
697
+ # - Constant[f] where f is a Float the range covers (never NaN: `cover?` compares);
698
+ # - FloatRange[c..d] whose canonical closed bounds sit inside self's.
699
+ # `Nominal[Float]` is never accepted, not even by the universal range: an arbitrary Float may be
700
+ # NaN, and the universal range is exactly "every Float except NaN".
701
+ def accepts_float_range(self_type, other_type, mode)
702
+ case other_type
703
+ when Type::Constant
704
+ accepts_float_range_from_constant(self_type, other_type, mode)
705
+ when Type::FloatRange
706
+ accepts_float_range_from_float_range(self_type, other_type, mode)
707
+ when Type::Nominal
708
+ Type::AcceptsResult.no(
709
+ mode: mode,
710
+ reasons: "#{self_type.describe} rejects Nominal[#{other_type.class_name}] (could be NaN or fall outside)"
711
+ )
712
+ else
713
+ Type::AcceptsResult.no(
714
+ mode: mode,
715
+ reasons: "FloatRange rejects #{other_type.class}"
716
+ )
717
+ end
718
+ end
719
+
720
+ def accepts_float_range_from_constant(self_type, constant, mode)
721
+ unless constant.value.is_a?(Float)
722
+ return Type::AcceptsResult.no(
723
+ mode: mode,
724
+ reasons: "FloatRange rejects non-Float Constant"
725
+ )
726
+ end
727
+
728
+ if self_type.covers?(constant.value)
729
+ Type::AcceptsResult.yes(
730
+ mode: mode,
731
+ reasons: "Constant[#{constant.value}] is in #{self_type.describe}"
732
+ )
733
+ else
734
+ Type::AcceptsResult.no(
735
+ mode: mode,
736
+ reasons: "Constant[#{constant.value}] outside #{self_type.describe}"
737
+ )
738
+ end
739
+ end
740
+
741
+ def accepts_float_range_from_float_range(self_type, other_range, mode)
742
+ if self_type.min <= other_range.min && other_range.canonical_max <= self_type.canonical_max
743
+ Type::AcceptsResult.yes(
744
+ mode: mode,
745
+ reasons: "#{other_range.describe} ⊆ #{self_type.describe}"
746
+ )
747
+ else
748
+ Type::AcceptsResult.no(
749
+ mode: mode,
750
+ reasons: "#{other_range.describe} not contained in #{self_type.describe}"
751
+ )
752
+ end
753
+ end
754
+
619
755
  # `Difference[base, removed]` accepts another type X when the base accepts X *and* X's value set is
620
756
  # provably disjoint from `removed`. The disjointness test is the subtle part — it is NOT the same as
621
757
  # `removed.accepts(X)`, because `Nominal[String]` includes `""` even though `Constant[""]` does not
@@ -936,7 +1072,7 @@ module Rigor
936
1072
  def resolve_class(name)
937
1073
  return nil unless name.is_a?(::String)
938
1074
 
939
- parts = name.delete_prefix("::").split("::")
1075
+ parts = class_name_parts(name)
940
1076
  return nil if parts.empty?
941
1077
 
942
1078
  mod = ::Object
@@ -950,7 +1086,16 @@ module Rigor
950
1086
  rescue ::StandardError, ::ScriptError, ::SystemExit
951
1087
  nil
952
1088
  end
1089
+
1090
+ # #775 — the split of a class name is a pure function of the string, and the subtype check asks it
1091
+ # twice per Nominal-vs-Nominal comparison (~370k times on the lib self-check) for the same few
1092
+ # hundred names. Only the split is memoised; the constant walk above still runs every time, so a
1093
+ # class loaded (or a spec constant removed) after the first ask is answered by the live tree.
1094
+ def class_name_parts(name)
1095
+ @class_name_parts[name] ||= name.delete_prefix("::").split("::").freeze
1096
+ end
953
1097
  end
1098
+ @class_name_parts = {}
954
1099
  end
955
1100
  end
956
1101
  end
@@ -34,15 +34,14 @@ module Rigor
34
34
  #
35
35
  # See docs/internal-spec/inference-engine.md for the binding contract.
36
36
  class BlockParameterBinder
37
- # @param expected_param_types [Array<Rigor::Type>] positional block parameter types in order. Indices
37
+ # @param expected_param_types — positional block parameter types in order. Indices
38
38
  # the binder cannot fill from this array (because the array is shorter than the parameter list, or
39
39
  # because the slot is a kind we do not pull from the array) default to `Dynamic[Top]`.
40
40
  def initialize(expected_param_types: [])
41
41
  @expected_param_types = expected_param_types
42
42
  end
43
43
 
44
- # @param block_node [Prism::BlockNode]
45
- # @return [Hash{Symbol => Rigor::Type}] ordered map from parameter name to bound type. Anonymous
44
+ # @return ordered map from parameter name to bound type. Anonymous
46
45
  # parameters are skipped; MultiTargetNode destructuring slots delegate to {MultiTargetBinder} and
47
46
  # contribute every named local in declaration order. Numbered-parameter forms (`_1`, `_2`, ...) bind
48
47
  # `:_1`, `:_2`, ... up to the maximum the block body refers to.