rigortype 0.3.8 → 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 (447) 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 +72 -1
  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 +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  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 +8 -24
  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 +81 -22
  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 +177 -26
  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 +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  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 +155 -21
  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 +2 -4
  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_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -41,7 +41,10 @@ module Rigor
41
41
  module Narrowing
42
42
  TRUSTED_EQUALITY_LITERAL_CLASSES = [String, Symbol, Integer, TrueClass, FalseClass, NilClass].freeze
43
43
  SINGLETON_LITERAL_CLASSES = [TrueClass, FalseClass, NilClass].freeze
44
- ClassNarrowingContext = Data.define(:exact, :polarity, :environment)
44
+ # `scope` is the analysed file's seeded scope where the caller has one, and nil otherwise (the
45
+ # Fact-shaped and RSpec-matcher entries hold an environment but no scope). Only the #898 singleton
46
+ # path reads it, and only to withhold a `Bot`, so a nil scope is the pre-#898 answer exactly.
47
+ ClassNarrowingContext = Data.define(:exact, :polarity, :environment, :scope)
45
48
  # A recognised `=~` regex pattern operand: the pattern `source` string and whether it was
46
49
  # compiled in extended (`//x`) mode. Extraction from a literal `RegularExpressionNode` and from
47
50
  # a value-pinned `Constant[Regexp]` share this carrier so the participation walk and the
@@ -139,9 +142,11 @@ module Rigor
139
142
  # counterpart of the scope narrowing {.case_when_scopes} performs for the same condition
140
143
  # shape, kept here so the branch a `case` expression's type drops and the clause whose
141
144
  # body scope goes dead derive from one judgment.
142
- def class_pattern_certainty(subject_type, class_name, environment:)
143
- truthy_bot = narrow_class(subject_type, class_name, environment: environment).is_a?(Type::Bot)
144
- falsey_bot = narrow_not_class(subject_type, class_name, environment: environment).is_a?(Type::Bot)
145
+ def class_pattern_certainty(subject_type, class_name, environment:, scope: nil)
146
+ truthy_bot = narrow_class(subject_type, class_name, environment: environment,
147
+ scope: scope).is_a?(Type::Bot)
148
+ falsey_bot = narrow_not_class(subject_type, class_name, environment: environment,
149
+ scope: scope).is_a?(Type::Bot)
145
150
 
146
151
  return :no if truthy_bot && !falsey_bot
147
152
  return :yes if !truthy_bot && falsey_bot
@@ -219,6 +224,54 @@ module Rigor
219
224
  narrow_integer_comparison_dispatch(type, comparator, bound)
220
225
  end
221
226
 
227
+ # Float-comparison fragment of `type` against a numeric literal `bound` (ADR-109 WD5). This
228
+ # is a TRUTHY-EDGE rule only: `x > c` is false for NaN, so the set that satisfies it is the
229
+ # range `Float[c..]`, but the set that fails it is that range's complement PLUS NaN, which no
230
+ # range spells. Callers apply it to the truthy edge and keep the entry type on the falsy edge.
231
+ # `x > c` narrows to the closed envelope `Float[c..]` rather than the exact `(c, ∞]` (one
232
+ # double wider; the exact form is deferred until a rule demands the removed point), `x < c`
233
+ # to the exact `Float[...c]`. Hooks in:
234
+ # - `Constant<Float>` is preserved when it satisfies the comparison, otherwise collapsed to
235
+ # `Bot` (a NaN constant satisfies nothing);
236
+ # - `FloatRange` becomes its intersection with the half-line, `Bot` when empty;
237
+ # - `Nominal[Float]` becomes the half-line itself;
238
+ # - `Union` narrows each member independently; Integer-rooted and other carriers flow
239
+ # through unchanged (the Integer rule above owns them).
240
+ def narrow_float_comparison(type, comparator, bound)
241
+ return type unless (bound.is_a?(Integer) || bound.is_a?(Float)) && %i[< <= > >=].include?(comparator)
242
+
243
+ bound = bound.to_f
244
+ return type if bound.nan? || bound.infinite?
245
+
246
+ narrow_float_comparison_dispatch(type, comparator, bound)
247
+ end
248
+
249
+ # The truthy edge of a numeric comparison: the Integer rule for Integer-rooted members, then
250
+ # the Float rule for Float-rooted ones. Never apply this to a falsy edge (see
251
+ # {.narrow_float_comparison}).
252
+ def narrow_numeric_comparison_truthy(type, comparator, bound)
253
+ narrow_float_comparison(narrow_integer_comparison(type, comparator, bound), comparator, bound)
254
+ end
255
+
256
+ # Intersects the Float-rooted members of `type` with `range` (a `FloatRange`), leaving every
257
+ # other member unchanged. `x.nan?` narrows its FALSY edge with `non_nan_float`; `x.finite?`
258
+ # narrows its TRUTHY edge with `finite_float`; the other edge of each keeps the entry type
259
+ # (NaN is truthy for `nan?` and nothing else is known there; `!finite?` admits NaN and ±∞).
260
+ def narrow_float_to_range(type, range)
261
+ case type
262
+ when Type::Constant
263
+ type.value.is_a?(Float) && !range.covers?(type.value) ? Type::Combinator.bot : type
264
+ when Type::FloatRange
265
+ intersect_float_bounds(type, range.min, range.max, range.exclude_end?)
266
+ when Type::Nominal
267
+ float_nominal?(type) ? range : type
268
+ when Type::Union
269
+ Type::Combinator.union(*type.members.map { |m| narrow_float_to_range(m, range) })
270
+ else
271
+ type
272
+ end
273
+ end
274
+
222
275
  # Equality fragment of `type` against an Integer `value`. `Constant<Integer>` is preserved
223
276
  # when it equals `value`, otherwise collapses to `Bot`. `IntegerRange` covers? `value`
224
277
  # narrows to `Constant[value]`; an out-of-range comparison collapses to `Bot`.
@@ -261,8 +314,9 @@ module Rigor
261
314
  # preserved; disjoint hierarchies collapse to `Bot`. Classes the environment cannot
262
315
  # resolve fall back to the conservative answer (the type unchanged) so the analyzer never
263
316
  # asserts narrowing it cannot prove.
264
- def narrow_class(type, class_name, exact: false, environment: Environment.default)
265
- context = ClassNarrowingContext.new(exact: exact, polarity: :positive, environment: environment)
317
+ def narrow_class(type, class_name, exact: false, environment: Environment.default, scope: nil)
318
+ context = ClassNarrowingContext.new(exact: exact, polarity: :positive, environment: environment,
319
+ scope: scope)
266
320
  narrow_class_dispatch(type, class_name, context)
267
321
  end
268
322
 
@@ -270,8 +324,9 @@ module Rigor
270
324
  # Inhabitants that DO satisfy the predicate are removed; inhabitants that do not are
271
325
  # preserved. Conservative on Top/Dynamic/Bot (preserved unchanged) because the analyzer
272
326
  # cannot prove the negative without a richer carrier.
273
- def narrow_not_class(type, class_name, exact: false, environment: Environment.default)
274
- context = ClassNarrowingContext.new(exact: exact, polarity: :negative, environment: environment)
327
+ def narrow_not_class(type, class_name, exact: false, environment: Environment.default, scope: nil)
328
+ context = ClassNarrowingContext.new(exact: exact, polarity: :negative, environment: environment,
329
+ scope: scope)
275
330
  narrow_class_dispatch(type, class_name, context)
276
331
  end
277
332
 
@@ -285,11 +340,11 @@ module Rigor
285
340
  # Walk the current type's union members, keep each part disjoint from `base`, and add
286
341
  # the removed-value Constant once when any current member covers it. `assert s is
287
342
  # ~non-empty-string` over `s: String | nil` narrows to `Constant[""] | NilClass`.
288
- # - `IntegerRange[a, b]` (v0.0.5+ slice). Complement is the two open halves `int<min,
289
- # a-1>` and `int<b+1, max>`, each intersected with the integer-domain parts of
343
+ # - `IntegerRange[a, b]` (v0.0.5+ slice). Complement is the two open halves
344
+ # `Integer[..a-1]` and `Integer[b+1..]`, each intersected with the integer-domain parts of
290
345
  # `current_type`. Non-integer parts (nil, String, …) of a Union receiver survive
291
- # unchanged. `assert n is ~int<5, 10>` over `n: Integer | nil` narrows to `int<min, 4> |
292
- # int<11, max> | NilClass`.
346
+ # unchanged. `assert n is ~Integer[5..10]` over `n: Integer | nil` narrows to `Integer[..4] |
347
+ # Integer[11..] | NilClass`.
293
348
  # - `Type::Intersection[M1, M2, …]` (v0.0.5+ slice). De Morgan: `D \ (M1 ∩ M2) = (D \ M1)
294
349
  # ∪ (D \ M2)`. Each member's complement is computed independently within
295
350
  # `current_type` and the results are unioned. Members the algebra cannot complement
@@ -337,10 +392,6 @@ module Rigor
337
392
 
338
393
  # Public predicate analyser. Returns `[truthy_scope, falsey_scope]`, always; when no
339
394
  # narrowing rule matches the predicate node both entries are the receiver scope unchanged.
340
- #
341
- # @param node [Prism::Node, nil]
342
- # @param scope [Rigor::Scope]
343
- # @return [Array(Rigor::Scope, Rigor::Scope)]
344
395
  def predicate_scopes(node, scope)
345
396
  return [scope, scope] if node.nil?
346
397
 
@@ -366,11 +417,9 @@ module Rigor
366
417
  # constants narrow as `is_a?`; integer/float-endpoint ranges narrow to `Numeric`;
367
418
  # string-endpoint ranges and regexp literals narrow to `String`.
368
419
  #
369
- # @param subject [Prism::Node, nil] the `case` subject.
370
- # @param conditions [Array<Prism::Node>] the `when`
420
+ # @param subject the `case` subject.
421
+ # @param conditions the `when`
371
422
  # clause's `conditions` array.
372
- # @param scope [Rigor::Scope]
373
- # @return [Array(Rigor::Scope, Rigor::Scope)]
374
423
  def case_when_scopes(subject, conditions, scope)
375
424
  # C1 — `case x when /re/` runs `/re/ === x`, which sets the regex match-data globals
376
425
  # exactly as a successful `=~` does. Narrow `$~`/`$&`/`$1..$N` on the clause body (the
@@ -480,7 +529,7 @@ module Rigor
480
529
  end
481
530
 
482
531
  # Complement of an `IntegerRange[a, b]` within `current_type`. Splits the range
483
- # complement into the two open halves `int<min, a-1>` and `int<b+1, max>` (skipping a
532
+ # complement into the two open halves `Integer[..a-1]` and `Integer[b+1..]` (skipping a
484
533
  # half when its bound is infinity), then intersects each half with the integer-domain
485
534
  # parts of `current_type`. Non-integer parts of a Union receiver (nil, String, …)
486
535
  # survive unchanged.
@@ -505,8 +554,8 @@ module Rigor
505
554
  Type::Combinator.union(*survivors)
506
555
  end
507
556
 
508
- # Returns the two open halves of an IntegerRange's complement: the left half `int<-∞,
509
- # a-1>` (when `a` is finite) and the right half `int<b+1, ∞>` (when `b` is finite).
557
+ # Returns the two open halves of an IntegerRange's complement: the left half
558
+ # `Integer[..a-1]` (when `a` is finite) and the right half `Integer[b+1..]` (when `b` is finite).
510
559
  # Universal ranges (both bounds infinite) yield an empty array — the complement is
511
560
  # empty.
512
561
  def integer_range_complement_halves(range)
@@ -994,7 +1043,7 @@ module Rigor
994
1043
 
995
1044
  def simple_dispatch_name?(name)
996
1045
  %i[nil? ! is_a? kind_of? instance_of? == != === =~ match? key? has_key? empty? any?
997
- none? respond_to?].include?(name)
1046
+ none? respond_to? nan? finite?].include?(name)
998
1047
  end
999
1048
 
1000
1049
  def dispatch_call_simple(node, scope, name)
@@ -1009,6 +1058,7 @@ module Rigor
1009
1058
  when :key?, :has_key? then analyse_key_presence_predicate(node, scope)
1010
1059
  when :empty?, :any?, :none? then analyse_array_emptiness_predicate(node, scope, name)
1011
1060
  when :respond_to? then analyse_respond_to_predicate(node, scope)
1061
+ when :nan?, :finite? then analyse_float_class_predicate(node, scope, name)
1012
1062
  end
1013
1063
  end
1014
1064
 
@@ -1551,19 +1601,41 @@ module Rigor
1551
1601
  return nil unless node.arguments.arguments.size == 2
1552
1602
 
1553
1603
  low, high = node.arguments.arguments
1554
- return nil unless low.is_a?(Prism::IntegerNode) && high.is_a?(Prism::IntegerNode)
1604
+ return nil unless numeric_literal_node?(low) && numeric_literal_node?(high)
1555
1605
 
1556
1606
  local_name = node.receiver.name
1557
1607
  current = scope.local(local_name)
1558
1608
  return nil if current.nil?
1559
1609
 
1560
- truthy = narrow_integer_comparison(
1561
- narrow_integer_comparison(current, :>=, low.value),
1610
+ # Truthy edge only, for Integer and Float members alike (ADR-109 WD5): a Float literal
1611
+ # bound leaves the Integer members untouched, and vice versa.
1612
+ truthy = narrow_numeric_comparison_truthy(
1613
+ narrow_numeric_comparison_truthy(current, :>=, low.value),
1562
1614
  :<=, high.value
1563
1615
  )
1564
1616
  [scope.with_local(local_name, truthy), scope]
1565
1617
  end
1566
1618
 
1619
+ # `x.nan?` / `x.finite?` (ADR-109 WD5). `nan?` narrows the FALSY edge to the non-NaN Floats
1620
+ # and leaves the truthy edge alone (NaN is not a carrier); `finite?` narrows the TRUTHY
1621
+ # edge to the finite Floats and leaves the falsy edge alone (it admits NaN and ±∞). A
1622
+ # non-Float member is untouched on both edges.
1623
+ def analyse_float_class_predicate(node, scope, predicate)
1624
+ return nil unless argument_free?(node)
1625
+ return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
1626
+
1627
+ local_name = node.receiver.name
1628
+ current = scope.local(local_name)
1629
+ return nil if current.nil?
1630
+
1631
+ case predicate
1632
+ when :nan?
1633
+ [scope, scope.with_local(local_name, narrow_float_to_range(current, Type::Combinator.non_nan_float))]
1634
+ when :finite?
1635
+ [scope.with_local(local_name, narrow_float_to_range(current, Type::Combinator.finite_float)), scope]
1636
+ end
1637
+ end
1638
+
1567
1639
  # Helper for {.narrow_integer_not_equal}. Only adjusts when the value sits exactly on
1568
1640
  # one endpoint, so the result stays contiguous; otherwise the input range is preserved.
1569
1641
  def narrow_integer_range_not_equal(range, value)
@@ -1628,20 +1700,96 @@ module Rigor
1628
1700
  current = scope.local(local_name)
1629
1701
  return nil if current.nil?
1630
1702
 
1631
- truthy = narrow_integer_comparison(current, normalised_op, bound)
1703
+ # ADR-109 WD5 — the Float rule runs on the truthy edge only: the falsy edge of `x > c`
1704
+ # admits NaN, so its Float-rooted members keep the entry type (the Integer rule below
1705
+ # leaves them untouched by construction).
1706
+ truthy = narrow_numeric_comparison_truthy(current, normalised_op, bound)
1632
1707
  falsey = narrow_integer_comparison(current, INVERT_COMPARISON_OP[normalised_op], bound)
1633
1708
  [scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
1634
1709
  end
1635
1710
 
1711
+ # An Integer or Float literal. Prism folds a leading minus into the literal, so `-1.5` is one
1712
+ # `FloatNode`.
1713
+ def numeric_literal_node?(node)
1714
+ node.is_a?(Prism::IntegerNode) || node.is_a?(Prism::FloatNode)
1715
+ end
1716
+
1636
1717
  def comparison_local_literal(left, right, comparator)
1637
- if left.is_a?(Prism::LocalVariableReadNode) && right.is_a?(Prism::IntegerNode)
1718
+ if left.is_a?(Prism::LocalVariableReadNode) && numeric_literal_node?(right)
1638
1719
  return [left.name, comparator, right.value]
1639
1720
  end
1640
- return nil unless right.is_a?(Prism::LocalVariableReadNode) && left.is_a?(Prism::IntegerNode)
1721
+ return nil unless right.is_a?(Prism::LocalVariableReadNode) && numeric_literal_node?(left)
1641
1722
 
1642
1723
  [right.name, REVERSE_COMPARISON_OP[comparator], left.value]
1643
1724
  end
1644
1725
 
1726
+ def narrow_float_comparison_dispatch(type, comparator, bound)
1727
+ case type
1728
+ when Type::Constant
1729
+ # A non-Float constant belongs to another rule and is never dropped here. The test is
1730
+ # the positive comparison, negated once: NaN fails every comparison, which is Ruby's
1731
+ # answer too, and rewriting `!(v < c)` as `v >= c` would silently let NaN through.
1732
+ return type unless type.value.is_a?(Float)
1733
+
1734
+ float_constant_satisfies?(type.value, comparator, bound) ? type : Type::Combinator.bot
1735
+ when Type::FloatRange
1736
+ lo, hi, hi_exclusive = float_comparison_half_line(comparator, bound)
1737
+ intersect_float_bounds(type, lo, hi, hi_exclusive)
1738
+ when Type::Nominal
1739
+ return type unless float_nominal?(type)
1740
+
1741
+ lo, hi, hi_exclusive = float_comparison_half_line(comparator, bound)
1742
+ intersect_float_bounds(Type::Combinator.non_nan_float, lo, hi, hi_exclusive)
1743
+ when Type::Union
1744
+ Type::Combinator.union(
1745
+ *type.members.map { |m| narrow_float_comparison_dispatch(m, comparator, bound) }
1746
+ )
1747
+ else
1748
+ type
1749
+ end
1750
+ end
1751
+
1752
+ def float_nominal?(nominal)
1753
+ nominal.class_name == "Float" && nominal.type_args.empty?
1754
+ end
1755
+
1756
+ def float_constant_satisfies?(value, comparator, bound)
1757
+ case comparator
1758
+ when :< then value < bound
1759
+ when :<= then value <= bound
1760
+ when :> then value > bound
1761
+ when :>= then value >= bound
1762
+ end
1763
+ end
1764
+
1765
+ # `[lo, hi]` with `hi` exclusive or not: `x < c` is `[-∞, c)`, `x <= c` is `[-∞, c]`, and both
1766
+ # `x > c` and `x >= c` are the closed envelope `[c, ∞]` (WD5).
1767
+ def float_comparison_half_line(comparator, bound)
1768
+ case comparator
1769
+ when :< then [-Float::INFINITY, bound, true]
1770
+ when :<= then [-Float::INFINITY, bound, false]
1771
+ else [bound, Float::INFINITY, false]
1772
+ end
1773
+ end
1774
+
1775
+ # Intersects `range` with `[lo, hi]` (`hi` exclusive when `hi_exclusive`), keeping whichever
1776
+ # upper bound is tighter on the canonical closed form together with its own exclusivity, so
1777
+ # `Float[0.0...1.0] ∩ [-∞, 2.0]` still displays `Float[0.0...1.0]`. Empty is `Bot`.
1778
+ def intersect_float_bounds(range, lower, upper, upper_exclusive)
1779
+ new_min = [range.min, lower].max
1780
+ half_canonical_max = upper_exclusive ? upper.prev_float : upper
1781
+ if half_canonical_max < range.canonical_max
1782
+ new_max = upper
1783
+ exclusive = upper_exclusive
1784
+ else
1785
+ new_max = range.max
1786
+ exclusive = range.exclude_end?
1787
+ end
1788
+ return Type::Combinator.bot if new_min > (exclusive ? new_max.prev_float : new_max)
1789
+
1790
+ Type::Combinator.float_range(new_min, new_max, exclude_end: exclusive)
1791
+ end
1792
+
1645
1793
  def narrow_integer_equal_dispatch(type, value)
1646
1794
  case type
1647
1795
  when Type::Constant then narrow_integer_equal_constant(type, value)
@@ -1854,8 +2002,10 @@ module Rigor
1854
2002
  current = scope.type_of(node.receiver)
1855
2003
  return nil if current.nil?
1856
2004
 
1857
- truthy_type = narrow_class(current, class_name, exact: exact, environment: scope.environment)
1858
- falsey_type = narrow_not_class(current, class_name, exact: exact, environment: scope.environment)
2005
+ truthy_type = narrow_class(current, class_name, exact: exact, environment: scope.environment,
2006
+ scope: scope)
2007
+ falsey_type = narrow_not_class(current, class_name, exact: exact, environment: scope.environment,
2008
+ scope: scope)
1859
2009
 
1860
2010
  [
1861
2011
  scope.with_method_chain_narrowing(*address, truthy_type),
@@ -1907,13 +2057,25 @@ module Rigor
1907
2057
  # shapes at once. Because the repair lives in the shared derivation, `is_a?`, `case`/`when`, `===`
1908
2058
  # and the constant typer were fixed together rather than one guard shape at a time. A scope no
1909
2059
  # declaration walk built still falls back to the peel; see {Reflection.lexical_nesting_chain}.
2060
+ #
2061
+ # A path whose head and tail have DIFFERENT owners is the one shape no `<prefix>::<full path>`
2062
+ # candidate can name (#656), so the whole-path loop falls through to the segment-wise walk
2063
+ # before it gives up: `A::B` guarded inside `P::Guard` with `P::A < P::Base` is `P::Base::B`,
2064
+ # which the loop above only ever reached as the top-level `A::B` — a real but different class,
2065
+ # and every reader call on the narrowed receiver was then checked against it. The walk is
2066
+ # {Reflection.resolve_constant_path_name}, shared with the constant typer so the guard and the
2067
+ # value it guards cannot name two classes, and it takes this scope's acceptance test so a
2068
+ # candidate is still adopted only when the environment or discovery knows it as a class.
1910
2069
  def resolve_class_name_lexically(bare_name, scope)
1911
2070
  chain = lexical_nesting_for(scope)
1912
2071
  chain.each do |prefix|
1913
2072
  candidate = "#{prefix}::#{bare_name}"
1914
2073
  return candidate if class_known_to_scope?(scope, candidate)
1915
2074
  end
1916
- bare_name
2075
+ walked = Reflection.resolve_constant_path_name(bare_name, scope) do |candidate|
2076
+ class_known_to_scope?(scope, candidate)
2077
+ end
2078
+ walked || bare_name
1917
2079
  end
1918
2080
 
1919
2081
  # Combines the environment's RBS-known set with the scope's in-source
@@ -1937,11 +2099,11 @@ module Rigor
1937
2099
  [
1938
2100
  scope.with_local(
1939
2101
  name,
1940
- narrow_class(current, class_name, exact: exact, environment: scope.environment)
2102
+ narrow_class(current, class_name, exact: exact, environment: scope.environment, scope: scope)
1941
2103
  ),
1942
2104
  scope.with_local(
1943
2105
  name,
1944
- narrow_not_class(current, class_name, exact: exact, environment: scope.environment)
2106
+ narrow_not_class(current, class_name, exact: exact, environment: scope.environment, scope: scope)
1945
2107
  )
1946
2108
  ]
1947
2109
  end
@@ -1996,8 +2158,10 @@ module Rigor
1996
2158
  current = scope.type_of(chain_arg)
1997
2159
  return nil if current.nil?
1998
2160
 
1999
- truthy_type = narrow_class(current, class_name, exact: false, environment: scope.environment)
2000
- falsey_type = narrow_not_class(current, class_name, exact: false, environment: scope.environment)
2161
+ truthy_type = narrow_class(current, class_name, exact: false, environment: scope.environment,
2162
+ scope: scope)
2163
+ falsey_type = narrow_not_class(current, class_name, exact: false, environment: scope.environment,
2164
+ scope: scope)
2001
2165
  [
2002
2166
  scope.with_method_chain_narrowing(*address, truthy_type),
2003
2167
  scope.with_method_chain_narrowing(*address, falsey_type)
@@ -2012,7 +2176,8 @@ module Rigor
2012
2176
  target_class = case_equality_target_class(receiver)
2013
2177
  return nil if target_class.nil?
2014
2178
 
2015
- narrowed = narrow_class(current, target_class, exact: false, environment: scope.environment)
2179
+ narrowed = narrow_class(current, target_class, exact: false, environment: scope.environment,
2180
+ scope: scope)
2016
2181
  [
2017
2182
  scope.with_local(local_name, narrowed),
2018
2183
  scope.with_local(local_name, current)
@@ -2283,8 +2448,9 @@ module Rigor
2283
2448
 
2284
2449
  def class_when_result(scope, current, target, falsey_acc)
2285
2450
  {
2286
- truthy: narrow_class(current, target, exact: false, environment: scope.environment),
2287
- falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment),
2451
+ truthy: narrow_class(current, target, exact: false, environment: scope.environment, scope: scope),
2452
+ falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment,
2453
+ scope: scope),
2288
2454
  fully_narrowable: true
2289
2455
  }
2290
2456
  end
@@ -2368,6 +2534,11 @@ module Rigor
2368
2534
  # through {#rooted_class_predicate_name}, which layers #614's extra decline on top of the
2369
2535
  # same walk. A rooted `::Foo` names the top level and never a lexically nearer shadow, so
2370
2536
  # it keeps the un-walked spelling. nil for any non-constant shape, as before.
2537
+ #
2538
+ # PUBLIC, alone among the helpers in this block (#655): `case`/`when`'s VALUE side lives in
2539
+ # `ExpressionTyper` and matched on the as-written spelling, so the two halves of one `case`
2540
+ # disagreed — the flow side narrowed through this walk while the value side resolved the
2541
+ # pattern against whatever the environment knew under that literal name.
2371
2542
  def lexical_class_name(node, scope)
2372
2543
  bare_name = static_class_name(node)
2373
2544
  return nil if bare_name.nil?
@@ -2375,6 +2546,7 @@ module Rigor
2375
2546
 
2376
2547
  resolve_class_name_lexically(bare_name, scope)
2377
2548
  end
2549
+ public :lexical_class_name
2378
2550
 
2379
2551
  # ----- narrow_class / narrow_not_class helpers -----
2380
2552
 
@@ -2437,8 +2609,7 @@ module Rigor
2437
2609
  end
2438
2610
 
2439
2611
  # Issue #657 — `Bot` is the assertion "this can never match", and only a KNOWN-disjoint ordering
2440
- # supports it. `subclass_of?` folds `:disjoint` and `:unknown` into one `false`, which is safe on
2441
- # the negative edge (where the pre-state survives) and an over-claim on the positive one:
2612
+ # supports it (the reason {#declines_bot?} exists, and the repro it was written against):
2442
2613
  #
2443
2614
  # module Taggable; def tag = "t"; end
2444
2615
  # class Integer; include Taggable; end
@@ -2460,8 +2631,7 @@ module Rigor
2460
2631
  def narrow_constant_to_class(constant, class_name, context)
2461
2632
  rigor_class = constant.value.class.name
2462
2633
  return constant if subclass_of?(rigor_class, class_name, context)
2463
- return Type::Combinator.untyped if !context.exact &&
2464
- class_ordering(rigor_class, class_name, context) == :unknown
2634
+ return Type::Combinator.untyped if declines_bot?(rigor_class, class_name, context)
2465
2635
 
2466
2636
  Type::Combinator.bot
2467
2637
  end
@@ -2505,8 +2675,14 @@ module Rigor
2505
2675
  end
2506
2676
  end
2507
2677
 
2678
+ # A shape projects to `Array` / `Hash`, and reopening either of those to include a project module
2679
+ # leaves the ordering `:unknown` for the same reason #657's `Integer` does — so `[1, 2]` under `when
2680
+ # Taggable` reached the same unsupported `Bot`. The carrier differs; the missing evidence does not.
2508
2681
  def narrow_shape_to_class(shape, projected_class, class_name, context)
2509
- subclass_of?(projected_class, class_name, context) ? shape : Type::Combinator.bot
2682
+ return shape if subclass_of?(projected_class, class_name, context)
2683
+ return Type::Combinator.untyped if declines_bot?(projected_class, class_name, context)
2684
+
2685
+ Type::Combinator.bot
2510
2686
  end
2511
2687
 
2512
2688
  def narrow_shape_not_class(shape, projected_class, class_name, context)
@@ -2517,8 +2693,69 @@ module Rigor
2517
2693
  # subclass of `Module`). Asking `Foo.is_a?(Class)` returns true; `Foo.is_a?(Foo)`
2518
2694
  # returns false unless `Foo` is `Class` itself. We approximate this by treating
2519
2695
  # singletons uniformly as `Class` instances.
2696
+ #
2697
+ # The approximation answers through `Class`, so a target `Class` cannot be ordered against — a
2698
+ # project module, typically — lands on the same `:unknown` #657 collapsed to `Bot`, and `case Widget
2699
+ # when Meta` was reported unreachable where `Widget` had `extend Meta`.
2700
+ #
2701
+ # Issue #898 — that decline is silent about a target the environment DOES order against `Class`, and
2702
+ # `extend Comparable` is one: both names are in RBS, neither includes the other, so the ordering says
2703
+ # `:disjoint` and the collapse fired on an arm MRI takes (`Widget.is_a?(Comparable)` is true — the
2704
+ # `extend` put `Comparable` in `Widget`'s SINGLETON ancestry, which asking about `Class` cannot see).
2705
+ # {#singleton_extend_declines_bot?} supplies the missing half from the project's own `extend` record.
2520
2706
  def narrow_singleton_to_class(singleton, class_name, context)
2521
- subclass_of?("Class", class_name, context) ? singleton : Type::Combinator.bot
2707
+ return singleton if subclass_of?("Class", class_name, context)
2708
+ return Type::Combinator.untyped if declines_bot?("Class", class_name, context)
2709
+ return Type::Combinator.untyped if singleton_extend_declines_bot?(singleton, class_name, context)
2710
+
2711
+ Type::Combinator.bot
2712
+ end
2713
+
2714
+ # Whether the class object's own `extend` record forbids the `Bot` the `Class` approximation would
2715
+ # otherwise authorise (#898). An extended `M` puts every ancestor of `M` in the singleton's
2716
+ # ancestry, so the question per extended module is `M <= target`: `:equal` (`extend Comparable`
2717
+ # under `when Comparable`) and `:subclass` support the arm, and `:unknown` (`extend Meta` under
2718
+ # `when Comparable`, `Meta` being project source the environment never joined to RBS) cannot rule
2719
+ # it out. `:superclass` and `:disjoint` support NOTHING and must keep the collapse — `Integer`
2720
+ # under `when Integer` orders `:superclass` against an extended `Comparable`, and reading that as
2721
+ # support would retract `case Widget when Integer` from every class that extends a core module.
2722
+ #
2723
+ # The answer is `Dynamic[top]`, never the preserved `Singleton[C]`, and the negative edge
2724
+ # ({#narrow_singleton_not_class}) is untouched — so this record is read ONLY to withhold a claim,
2725
+ # never to make one. That asymmetry is deliberate, and it is what keeps the fix honest against an
2726
+ # extend record that is structurally incomplete: `Widget.extend(m)` at a call site and an `extend`
2727
+ # written in a file outside the analysed set are both invisible to the walk behind
2728
+ # {Scope#singleton_extends_of}, and the declaration-side record joined to it below is no more
2729
+ # complete — RBS says nothing about a class it does not declare. A shape that turned this table into a positive
2730
+ # `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
2731
+ # `Singleton[C]` would also hand the arm a receiver whose singleton method table has no `clamp` on
2732
+ # it, trading the unreachable-clause false positive for an undefined-method one — the same trap
2733
+ # #657 recorded on the `Constant` carrier.
2734
+ #
2735
+ # Issue #915 — two sources, read as one. The source record above sees only what Ruby source spells;
2736
+ # a project that declares `extend M` in its own `sig/` puts the same module in the same singleton
2737
+ # ancestry, and the environment's declaration-side answer
2738
+ # ({Environment#singleton_extended_modules}) is consulted alongside it. Neither is asked to be
2739
+ # complete; each can only add a module that IS there, which is all the one-directional read needs.
2740
+ def singleton_extend_declines_bot?(singleton, class_name, context)
2741
+ return false if context.exact
2742
+
2743
+ extended = singleton_ancestor_modules(singleton, context)
2744
+ return false if extended.empty?
2745
+
2746
+ extended.any? do |module_name|
2747
+ %i[equal subclass unknown].include?(class_ordering(module_name, class_name, context))
2748
+ end
2749
+ end
2750
+
2751
+ def singleton_ancestor_modules(singleton, context)
2752
+ scope = context.scope
2753
+ from_source = scope.nil? ? [] : scope.singleton_extends_of(singleton.class_name)
2754
+ from_rbs = context.environment&.singleton_extended_modules(singleton.class_name) || []
2755
+ return from_source if from_rbs.empty?
2756
+ return from_rbs if from_source.empty?
2757
+
2758
+ from_source | from_rbs
2522
2759
  end
2523
2760
 
2524
2761
  def narrow_singleton_not_class(singleton, class_name, context)
@@ -2534,6 +2771,22 @@ module Rigor
2534
2771
  end
2535
2772
  end
2536
2773
 
2774
+ # Whether the positive edge must decline the `Bot` that `subclass_of?` returning false would
2775
+ # otherwise authorise (#657). `subclass_of?` folds `:disjoint` and `:unknown` into one `false`, and
2776
+ # only the first is evidence that the guard can never match; the second is the environment saying it
2777
+ # cannot tell, which any file reopening either side can make true.
2778
+ #
2779
+ # This only ever ADDS a decline: every caller keeps its own `subclass_of?` test first, so the
2780
+ # preserving edge is untouched, and a `:disjoint`, `:equal`, `:subclass` or `:superclass` verdict
2781
+ # never reaches here. There is no ordering it turns INTO a match — the honest answer for
2782
+ # "cannot tell" is Dynamic, never the asked class.
2783
+ #
2784
+ # `instance_of?` is exempt because it compares the exact class: a module is never an object's class,
2785
+ # so that edge's `Bot` rests on the guard's own semantics rather than on the ordering.
2786
+ def declines_bot?(rigor_class_name, target_class_name, context)
2787
+ !context.exact && class_ordering(rigor_class_name, target_class_name, context) == :unknown
2788
+ end
2789
+
2537
2790
  # Returns `true` when an instance of `rigor_class_name` satisfies
2538
2791
  # `is_a?(target_class_name)` (or `instance_of?(target_class_name)` when `exact: true`).
2539
2792
  # Falls back to the safe `false` when either name does not resolve through the analyzer
@@ -53,10 +53,6 @@ module Rigor
53
53
  # Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read (or
54
54
  # a write in value position, `if (x = MAP[k])`) resolves through, then the predicate-fold derivation
55
55
  # issue #313 added.
56
- #
57
- # @param node [Prism::Node, nil]
58
- # @param scope [Rigor::Scope, nil]
59
- # @return [Symbol, nil]
60
56
  def resolve(node, scope)
61
57
  return nil if node.nil? || scope.nil?
62
58
 
@@ -97,9 +93,7 @@ module Rigor
97
93
  # per-overload, which is what makes it precise: `Array#first` is optimistic while `Array#first(3)` is
98
94
  # not, and `String#[]` / `Enumerable#find` are honest because they already spell the miss as `?`.
99
95
  #
100
- # @param method_definition [RBS::Definition::Method]
101
- # @param method_type [RBS::MethodType] the overload {OverloadSelector.select} returned
102
- # @return [Boolean]
96
+ # @param method_type — the overload {OverloadSelector.select} returned
103
97
  def optimistic_overload?(method_definition, method_type)
104
98
  type_def = matching_type_def(method_definition, method_type)
105
99
  return false unless type_def.respond_to?(:overload_annotations)
@@ -18,9 +18,8 @@ module Rigor
18
18
  module OriginLookup
19
19
  module_function
20
20
 
21
- # @param scope [Rigor::Scope]
22
- # @param node [Prism::Node, nil] the receiver expression's node (nil for an implicit-self receiver)
23
- # @return [Symbol, nil] the effective dynamic-origin cause, or nil when none is known
21
+ # @param node — the receiver expression's node (nil for an implicit-self receiver)
22
+ # @return the effective dynamic-origin cause, or nil when none is known
24
23
  def origin_for(scope, node)
25
24
  return nil if node.nil?
26
25
 
@@ -116,11 +116,10 @@ module Rigor
116
116
  # single-level pass.
117
117
  DEFAULT_ROUNDS = 3
118
118
 
119
- # @param files [Array<String>] project `.rb` paths to scan for call sites.
120
- # @param environment [Rigor::Environment]
121
- # @param target_ruby [String, nil] Prism parse target.
122
- # @param max_rounds [Integer] the WD5 fixpoint cap (1 = single-level).
123
- # @return [Hash{[String,Symbol,Symbol] => Hash{Symbol => Rigor::Type}}] frozen.
119
+ # @param files project `.rb` paths to scan for call sites.
120
+ # @param target_ruby — Prism parse target.
121
+ # @param max_rounds the WD5 fixpoint cap (1 = single-level).
122
+ # @return => Hash{Symbol => Rigor::Type}}] frozen.
124
123
  def self.collect(files:, environment:, target_ruby: nil, max_rounds: DEFAULT_ROUNDS, workers: 0)
125
124
  new(files: files, environment: environment, target_ruby: target_ruby,
126
125
  max_rounds: max_rounds, workers: workers).collect
@@ -330,7 +329,7 @@ module Rigor
330
329
  arg.is_a?(Prism::AssocSplatNode)
331
330
  end
332
331
 
333
- # @return [[String, Symbol, Symbol, Prism::DefNode], nil]
332
+ # @return , nil]
334
333
  def resolve_callee(call_node, scope, index)
335
334
  if call_node.receiver.nil?
336
335
  class_name, kind = implicit_self_target(scope)
@@ -92,13 +92,13 @@ module Rigor
92
92
 
93
93
  # Collects and widens the constants every `pre_eval:` file declares.
94
94
  #
95
- # @param paths [Array<String>] absolute paths to the `pre_eval:` files that exist on disk.
96
- # @param scope_builder [#call] `path -> Rigor::Scope`; the caller supplies a project-seeded, environment-
95
+ # @param paths absolute paths to the `pre_eval:` files that exist on disk.
96
+ # @param scope_builder `path -> Rigor::Scope`; the caller supplies a project-seeded, environment-
97
97
  # bound scope so the rvalue typer resolves cross-file classes exactly as per-file analysis would.
98
- # @param target_ruby [String, nil] the Prism parse version (`Configuration#target_ruby`).
99
- # @param buffer [Rigor::Analysis::BufferBinding, nil] editor-mode binding; when set, a listed file that
98
+ # @param target_ruby the Prism parse version (`Configuration#target_ruby`).
99
+ # @param buffer editor-mode binding; when set, a listed file that
100
100
  # matches the in-flight buffer is read from its physical bytes.
101
- # @return [Hash{String => Rigor::Type}] frozen qualified-name -> published type table.
101
+ # @return frozen qualified-name -> published type table.
102
102
  def collect(paths:, scope_builder:, target_ruby: nil, buffer: nil)
103
103
  published = {}
104
104
  conflicted = {}
@@ -165,6 +165,7 @@ module Rigor
165
165
  when Type::Constant then widen_constant(type)
166
166
  when Type::Refined then widen(type.base)
167
167
  when Type::IntegerRange then Type::Combinator.nominal_of("Integer")
168
+ when Type::FloatRange then Type::Combinator.nominal_of("Float")
168
169
  when Type::Tuple then Type::Combinator.nominal_of("Array")
169
170
  when Type::HashShape then Type::Combinator.nominal_of("Hash")
170
171
  when Type::Nominal then type.type_args.empty? ? type : Type::Combinator.nominal_of(type.class_name)