rigortype 0.3.8 → 0.4.0

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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/node_children"
6
+ require_relative "captured_locals"
7
+ require_relative "element_read_widening"
8
+ require_relative "jump_targets"
9
+ require_relative "mutation_widening"
10
+ require_relative "receiver_alias"
11
+
12
+ module Rigor
13
+ module Inference
14
+ # Whether an expression `StatementEvaluator` would otherwise type as a pure value — a call's receiver or
15
+ # argument, a literal, an interpolation, a `rescue` modifier — holds something the scope after it must see:
16
+ # a variable write whose binding outlives the expression, or a `next` / `break` whose path a jump join
17
+ # reads. Issue #1223: `out << (n += 1)` left `n` on its pre-write binding, straight-line and through
18
+ # ADR-56's block write-back, and `puts(x && next)` never reached the block's `next` join.
19
+ #
20
+ # A local write counts only when it binds past every block and lambda it is nested in within the
21
+ # expression (its `depth` reaches the expression's own scope): `puts(xs.map { |x| y = x })` binds `y` in
22
+ # the block alone, while `puts(xs.each { t = 1 })` rebinds the outer `t`. Every instance-variable,
23
+ # class-variable and global write counts wherever it is, and so does an index `||=` / `&&=` / `op=`,
24
+ # whose store widens its receiver. A jump counts only where it targets the construct around the
25
+ # expression ({JumpTargets}). A `def`, class or module body is a scope of its own, and `defined?`
26
+ # evaluates nothing, so neither is looked into.
27
+ #
28
+ # An in-place mutation counts on the same terms as a write ({.outliving_mutation?}): a call to a name the
29
+ # straight-line widening responds to ({MutationWidening::SHAPE_MUTATORS}) on a receiver naming a variable
30
+ # that outlives the expression. The mutator's widening runs in the evaluator's post-call effects, which a
31
+ # pure-value operand never reached, so a chained `b.push(2).size`, an argument `puts(b.push(2))` or
32
+ # `d.map!.with_index { … }` left `b` / `d` on the literal their assignment wrote.
33
+ #
34
+ # Short-circuiting and allocation-free outside a mutator-named call: the evaluator asks it of every call's
35
+ # operands, and asks it again of each operand it threads. A found effect stops the recursion without a
36
+ # `return` out of the child block, which would allocate once per frame it unwinds and make a deep literal
37
+ # quadratic.
38
+ module OperandEffects
39
+ LOCAL_WRITE_NODES = CapturedLocals::LOCAL_WRITE_NODES
40
+ OUTLIVING_WRITE_NODES = (
41
+ CapturedLocals::NON_LOCAL_WRITE_NODES |
42
+ Set[Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode]
43
+ ).freeze
44
+ INSTANCE_WRITE_NODES = Set[
45
+ Prism::InstanceVariableWriteNode, Prism::InstanceVariableOperatorWriteNode,
46
+ Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableTargetNode
47
+ ].freeze
48
+ GLOBAL_WRITE_NODES = Set[
49
+ Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode,
50
+ Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableTargetNode
51
+ ].freeze
52
+ COMPOUND_LOCAL_WRITES = Set[
53
+ Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode
54
+ ].freeze
55
+ COMPOUND_INSTANCE_WRITES = Set[
56
+ Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
57
+ Prism::InstanceVariableAndWriteNode
58
+ ].freeze
59
+ JUMP_NODES = Set[Prism::NextNode, Prism::BreakNode].freeze
60
+ SCOPE_NODES = Set[Prism::BlockNode, Prism::LambdaNode].freeze
61
+ OPAQUE_NODES = Set[
62
+ Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefinedNode
63
+ ].freeze
64
+ private_constant :LOCAL_WRITE_NODES, :OUTLIVING_WRITE_NODES, :INSTANCE_WRITE_NODES, :GLOBAL_WRITE_NODES,
65
+ :COMPOUND_LOCAL_WRITES,
66
+ :COMPOUND_INSTANCE_WRITES, :JUMP_NODES, :SCOPE_NODES, :OPAQUE_NODES
67
+
68
+ module_function
69
+
70
+ def any?(node)
71
+ node.is_a?(Prism::Node) && effect?(node, 0, true)
72
+ end
73
+
74
+ # `nesting` counts the blocks and lambdas between `node` and the expression's root; `jumps` is false once
75
+ # the walk has crossed a construct that retargets a jump.
76
+ def effect?(node, nesting, jumps)
77
+ klass = node.class
78
+ return true if LOCAL_WRITE_NODES.include?(klass) && node.depth >= nesting
79
+ return true if OUTLIVING_WRITE_NODES.include?(klass)
80
+ return true if jumps && JUMP_NODES.include?(klass)
81
+ return true if klass == Prism::CallNode && outliving_mutation?(node, nesting)
82
+ return false if OPAQUE_NODES.include?(klass)
83
+
84
+ nesting += 1 if SCOPE_NODES.include?(klass)
85
+ jumps &&= !JumpTargets.boundary?(node)
86
+ found = false
87
+ node.rigor_each_child { |child| found ||= effect?(child, nesting, jumps) }
88
+ found
89
+ end
90
+ private_class_method :effect?
91
+
92
+ # True when `node` is a call the straight-line widening would answer for a variable that outlives the
93
+ # expression: a {MutationWidening::SHAPE_MUTATORS} name on a receiver whose {ReceiverAlias.mutated_reads},
94
+ # or the local an element read is rooted at ({ElementReadWidening.element_read_path}, `a[0] << e`), reach
95
+ # past the blocks between `node` and the expression's root. A block's own local — its parameter, a name it
96
+ # introduces — is that block's to widen.
97
+ def outliving_mutation?(node, nesting)
98
+ return false unless MutationWidening::SHAPE_MUTATORS.include?(node.name)
99
+
100
+ receiver = node.receiver
101
+ return false if receiver.nil? || receiver.is_a?(Prism::SelfNode)
102
+
103
+ path = ElementReadWidening.element_read_path(receiver)
104
+ return outliving_read?(path.first, nesting) if path
105
+
106
+ ReceiverAlias.mutated_reads(receiver).any? { |read| outliving_read?(read, nesting) }
107
+ end
108
+ private_class_method :outliving_mutation?
109
+
110
+ # A local read reaches the expression's own scope when its `depth` does; an `it` read is always the
111
+ # innermost block's parameter, so only one outside every block in the expression does. An instance
112
+ # variable, class variable or global always outlives it.
113
+ def outliving_read?(read, nesting)
114
+ case read
115
+ when Prism::LocalVariableReadNode then read.depth >= nesting
116
+ when Prism::ItLocalVariableReadNode then nesting.zero?
117
+ else true
118
+ end
119
+ end
120
+ private_class_method :outliving_read?
121
+
122
+ # The locals (bare names), instance variables and globals (sigil-prefixed names, as {CapturedLocals.bind}
123
+ # reads them) `node` writes on the terms {.any?} counts a write, in first-write order.
124
+ def written_variables(node)
125
+ names = []
126
+ collect_written(node, 0, names) if node.is_a?(Prism::Node)
127
+ names.uniq
128
+ end
129
+
130
+ # The locals and instance variables `node` reads, named as {.written_variables} names them. A compound write
131
+ # (`x += 1`, `x ||= v`) reads the variable it writes.
132
+ def read_variables(node)
133
+ names = []
134
+ collect_read(node, 0, names) if node.is_a?(Prism::Node)
135
+ names.uniq
136
+ end
137
+
138
+ def collect_read(node, nesting, names)
139
+ klass = node.class
140
+ if klass == Prism::LocalVariableReadNode || COMPOUND_LOCAL_WRITES.include?(klass)
141
+ names << node.name if node.depth >= nesting
142
+ elsif klass == Prism::InstanceVariableReadNode || COMPOUND_INSTANCE_WRITES.include?(klass)
143
+ names << node.name
144
+ end
145
+ return if OPAQUE_NODES.include?(klass)
146
+
147
+ nesting += 1 if SCOPE_NODES.include?(klass)
148
+ node.rigor_each_child { |child| collect_read(child, nesting, names) }
149
+ end
150
+ private_class_method :collect_read
151
+
152
+ def collect_written(node, nesting, names)
153
+ klass = node.class
154
+ if LOCAL_WRITE_NODES.include?(klass)
155
+ names << node.name if node.depth >= nesting
156
+ elsif INSTANCE_WRITE_NODES.include?(klass) || GLOBAL_WRITE_NODES.include?(klass)
157
+ names << node.name
158
+ end
159
+ return if OPAQUE_NODES.include?(klass)
160
+
161
+ nesting += 1 if SCOPE_NODES.include?(klass)
162
+ node.rigor_each_child { |child| collect_written(child, nesting, names) }
163
+ end
164
+ private_class_method :collect_written
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Rigor
6
+ module Inference
7
+ # Issue #1256 — the later operands of one expression `StatementEvaluator` threads and types as a whole (a
8
+ # call, a literal, a `rescue` modifier): the operands entered from a scope an earlier operand moved, rather
9
+ # than from the one the whole expression is typed from. `puts(b.unshift("s"), b.first.upcase)` enters
10
+ # `b.first.upcase` after the `unshift` widened `b`, and `[n += 1, n += 1]` its second element after the first
11
+ # write.
12
+ #
13
+ # Each such operand is recorded into the per-node scope index with the scope it was entered from, so the
14
+ # diagnostics read it there, and typed from that scope once, so the whole expression's value holds it
15
+ # ({#types}). The operand holding a write is still typed from its own entry: `out << (n += 1)` appends `1`.
16
+ #
17
+ # The walk takes an operand before anything below it, and compares an operand's entry against the scope its
18
+ # nearest taken ancestor is typed from, so each later operand is typed exactly once: from the bottom up, each
19
+ # holding the values of the ones below it. A threaded call below the root is still not typed as a whole
20
+ # (`StatementEvaluator#call_effects`); its later operands join the root's walk.
21
+ class OperandWalk
22
+ # Positions `ExpressionTyper` gives no value of their own: it reads their children directly, never the node,
23
+ # so a later one is recorded but not typed, and its children are compared against its ancestor's scope.
24
+ # A keyword hash is typed whole only as a plain hash: the `**h` form reads the splatted value itself
25
+ # (`ExpressionTyper#double_splat_hash_shape`), so its children are taken instead.
26
+ NON_VALUE_NODES = Set[
27
+ Prism::ArgumentsNode, Prism::AssocNode, Prism::AssocSplatNode, Prism::BlockArgumentNode, Prism::SplatNode,
28
+ Prism::KeywordHashNode
29
+ ].freeze
30
+ private_constant :NON_VALUE_NODES
31
+
32
+ # The per-node scope index's `(node, scope) ->` recorder, or nil for an unrecorded pass.
33
+ attr_reader :recorder
34
+
35
+ def initialize(recorder)
36
+ @recorder = recorder
37
+ @entries = nil
38
+ @types = nil
39
+ end
40
+
41
+ # The position the next taken operand lands at, for {#types}' `since:`.
42
+ def mark
43
+ @entries.nil? ? 0 : @entries.size
44
+ end
45
+
46
+ # Takes `node`, entered from `entry`. Answers the slot {#resolve} fills, or nil for a {NON_VALUE_NODES}
47
+ # position.
48
+ def later(node, entry)
49
+ @recorder&.call(node, entry)
50
+ return nil if NON_VALUE_NODES.include?(node.class)
51
+
52
+ (@entries ||= []) << [node, entry, nil]
53
+ @entries.size - 1
54
+ end
55
+
56
+ # The value the handler that evaluated the operand in `slot` typed it to, from its entry.
57
+ def resolve(slot, type)
58
+ @entries[slot][2] = type
59
+ end
60
+
61
+ # The identity-comparing table of every taken operand's value, for `ExpressionTyper`'s `operand_types:`, or
62
+ # nil when the walk took none at or after `since` (a {#mark}). An operand the evaluator did not type is typed
63
+ # from its entry here, after every operand below it. A threaded call below the root asks for the operands
64
+ # taken under it once they are all taken; they are typed then and only then, and the table keeps growing,
65
+ # so the root's own request types only what is left.
66
+ def types(tracer, since: 0)
67
+ return nil if @entries.nil? || @entries.size <= since
68
+
69
+ @types ||= {}.compare_by_identity
70
+ (@entries.size - 1).downto(since) do |index|
71
+ node, entry, type = @entries[index]
72
+ next if @types.key?(node)
73
+
74
+ @types[node] = type || self.class.type_of(entry, node, tracer, @types)
75
+ end
76
+ @types
77
+ end
78
+
79
+ # `node`'s type from `scope`, answering each node `operand_types` holds with its value there. `Scope#type_of`
80
+ # itself is plugin-facing and keeps its surface, so the table goes to the typer directly.
81
+ def self.type_of(scope, node, tracer, operand_types)
82
+ return scope.type_of(node, tracer: tracer) if operand_types.nil?
83
+
84
+ ExpressionTyper.new(scope: scope, tracer: tracer, operand_types: operand_types).type_of(node)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -43,20 +43,68 @@ module Rigor
43
43
  # them would widen this channel into a taint that silences genuine diagnostics.
44
44
  NIL_COLLAPSING_PREDICATES = %i[nil? !].freeze
45
45
 
46
+ # Issue #1094 — the binary comparisons that are the same statement as `nil?` when one operand is the `nil`
47
+ # literal: `x == nil`, `nil != x`, `x.equal?(nil)`, `nil === x`. Every one folds from the other operand's
48
+ # nil-freeness alone, so the derivation is exactly as sound as the unary one. A comparison against any
49
+ # other value folds from the carrier's value and stays out, for the reason the value predicates do.
50
+ NIL_COMPARISONS = %i[== != eql? equal? ===].freeze
51
+
52
+ # {.miss_answer}'s answer for an expression whose value on a miss cannot be told.
53
+ UNKNOWN_MISS = Object.new.freeze
54
+
55
+ # Issue #1302 — a local / ivar mark that also records what the bound value answers on a miss, so
56
+ # {.miss_answer} reads a predicate through the binding as it reads the inline form. `Scope` stores it in
57
+ # the mark table in place of the bare cause, so the answer is kept and dropped with the mark itself; a
58
+ # binding whose miss answer cannot be told stores the bare cause, as before.
59
+ BoundMark = Data.define(:cause, :miss)
60
+
61
+ # The marks a binding of today's single cause records, interned so that marking `v = h[k]` allocates
62
+ # nothing beyond the table it always rebuilt.
63
+ INTERNED_BOUND_MARKS = [nil, true, false].to_h do |miss|
64
+ [miss, BoundMark.new(cause: IMPLICITLY_RETURNS_NIL, miss: miss)]
65
+ end.freeze
66
+ private_constant :INTERNED_BOUND_MARKS
67
+
46
68
  module_function
47
69
 
70
+ # What `Scope` stores for a binding marked with `cause` whose value answers `miss` on a miss.
71
+ def bound_mark(cause, miss)
72
+ return cause if miss.equal?(UNKNOWN_MISS)
73
+
74
+ (cause == IMPLICITLY_RETURNS_NIL && INTERNED_BOUND_MARKS[miss]) || BoundMark.new(cause: cause, miss: miss)
75
+ end
76
+
77
+ # The cause and the recorded miss answer of an entry {.bound_mark} made (or nil for no entry).
78
+ def bound_cause(mark) = mark.is_a?(BoundMark) ? mark.cause : mark
79
+ def bound_miss(mark) = mark.is_a?(BoundMark) ? mark.miss : UNKNOWN_MISS
80
+
81
+ # The entry a control-flow join keeps for a name both arms mark: the answer survives only when the arms
82
+ # agree on it, since otherwise a miss can take either arm's.
83
+ def join_bound_marks(mine, theirs) = mine == theirs ? mine : bound_cause(mine)
84
+
85
+ # `rebound` — `scope` with the local `name` rebound to its type across a loop's iterations
86
+ # (`CapturedLocals.bind`) — re-marked with the mark `scope` holds for `name` or `optimistic`, the one an
87
+ # iteration's own rebind made. `scope`'s recorded miss answer is kept unless an iteration's mark joins it:
88
+ # that one comes without its answer, so the pair's cannot be told.
89
+ def carry_local_mark(scope, rebound, name, optimistic)
90
+ miss = optimistic ? UNKNOWN_MISS : scope.optimistic_local_miss(name)
91
+ rebound.with_optimistic_local(name, scope.optimistic_local(name) || optimistic, miss: miss)
92
+ end
93
+
94
+ # {.carry_local_mark} for an instance variable.
95
+ def carry_ivar_mark(scope, rebound, name, optimistic)
96
+ miss = optimistic ? UNKNOWN_MISS : scope.optimistic_ivar_miss(name)
97
+ rebound.with_optimistic_ivar(name, scope.optimistic_ivar(name) || optimistic, miss: miss)
98
+ end
99
+
48
100
  # The effective optimistic-nil-free cause of an expression under `scope`, or nil when its nil-freeness is
49
101
  # a property of the value rather than a bet. The single owner of the judgment: `ExpressionTyper`,
50
102
  # `StatementEvaluator` and `AlwaysTruthyConditionCollector` all route here, so the three consumers the
51
103
  # spec binds cannot drift apart.
52
104
  #
53
105
  # Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read (or
54
- # a write in value position, `if (x = MAP[k])`) resolves through, then the predicate-fold derivation
55
- # issue #313 added.
56
- #
57
- # @param node [Prism::Node, nil]
58
- # @param scope [Rigor::Scope, nil]
59
- # @return [Symbol, nil]
106
+ # a write in value position, `if (x = MAP[k])`) resolves through, then a safe-navigation call and the
107
+ # predicate-fold derivation issue #313 added.
60
108
  def resolve(node, scope)
61
109
  return nil if node.nil? || scope.nil?
62
110
 
@@ -67,20 +115,85 @@ module Rigor
67
115
  when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode then scope.optimistic_local(node.name)
68
116
  when Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode then scope.optimistic_ivar(node.name)
69
117
  when Prism::AndNode, Prism::OrNode then resolve(node.left, scope) || resolve(node.right, scope)
70
- when Prism::CallNode then resolve_through_predicate(node, scope)
118
+ when Prism::CallNode then resolve_through_safe_navigation(node, scope) || resolve_through_predicate(node, scope)
71
119
  when Prism::ParenthesesNode then resolve_through_parentheses(node, scope)
72
120
  end
73
121
  end
74
122
 
75
- # `recv.nil?` / `!recv` — the fold is a statement about `recv`, so it is exactly as optimistic as `recv`
76
- # is. A block or any argument means this is not the unary predicate it looks like (`x.!(y)` is a
77
- # user-defined operator), and the derivation declines.
123
+ # `recv&.m` is `nil` exactly when `recv` is (or when `m` answers `nil`), so it restates `recv`'s presence
124
+ # and is as optimistic as `recv`. Only the call carrying the `&.` is derived: Ruby skips that one call and
125
+ # no more, so `recv&.m.n` sends `n` to the `nil` and raises (or answers `NilClass#n`), and neither it nor
126
+ # a plain read `recv.m` produces the `nil` a miss would — their own nil-freeness rests on the method's
127
+ # answer. A nil-collapsing predicate over the call (`recv&.m.nil?`) still resolves, through
128
+ # {.resolve_through_predicate} and back here.
129
+ def resolve_through_safe_navigation(node, scope)
130
+ resolve(node.receiver, scope) if node.safe_navigation?
131
+ end
132
+
133
+ # `recv.nil?` / `!recv` / `recv == nil` — the fold is a statement about `recv`, so it is exactly as
134
+ # optimistic as `recv` is.
78
135
  def resolve_through_predicate(node, scope)
79
- return nil unless NIL_COLLAPSING_PREDICATES.include?(node.name)
136
+ operand = nil_question_operand(node)
137
+ operand && resolve(operand, scope)
138
+ end
139
+
140
+ # The operand a nil-collapsing predicate restates the nil-ness of, or nil when `node` is not one. A block
141
+ # or any argument means `nil?` / `!` is not the unary predicate it looks like (`x.!(y)` is a user-defined
142
+ # operator). A comparison needs exactly one positional argument and exactly one `nil` literal side, and
143
+ # answers the other side (`nil == nil` states nothing about a carrier).
144
+ def nil_question_operand(node)
80
145
  return nil unless node.block.nil?
81
- return nil unless node.arguments.nil? || node.arguments.arguments.empty?
82
146
 
83
- resolve(node.receiver, scope)
147
+ arguments = node.arguments&.arguments || []
148
+ if NIL_COLLAPSING_PREDICATES.include?(node.name)
149
+ node.receiver if arguments.empty?
150
+ elsif NIL_COMPARISONS.include?(node.name) && arguments.size == 1
151
+ nil_comparison_operand(node.receiver, arguments.first)
152
+ end
153
+ end
154
+
155
+ def nil_comparison_operand(receiver, argument)
156
+ if argument.is_a?(Prism::NilNode) && !receiver.nil? && !receiver.is_a?(Prism::NilNode)
157
+ receiver
158
+ elsif receiver.is_a?(Prism::NilNode) && !argument.is_a?(Prism::NilNode)
159
+ argument
160
+ end
161
+ end
162
+
163
+ # What a marked expression answers when the carrier its mark rests on misses, or {UNKNOWN_MISS}. The
164
+ # carrier itself — the read the mark is recorded on, or a `recv&.m` over a marked receiver — answers `nil`,
165
+ # and each nil-collapsing predicate over it answers what it answers for that value: `!` its negation,
166
+ # `nil?` / `== nil` whether it is `nil`, `!= nil` the reverse. A local / ivar read answers what its binding
167
+ # recorded beside the mark ({BoundMark}, issue #1302): `nil` for `x = h[k]` and for `x = recv&.m?`. An
168
+ # `&&` / `||`, a binding that recorded nothing, and anything else is unknown. `ExpressionTyper` widens a
169
+ # predicate's folded boolean only when the miss answers the other boolean (or cannot be told), so
170
+ # `!recv&.empty?` — `!false` on a hit, `!nil` on a miss — stays `true`, and so does `x = recv&.empty?; !x`.
171
+ def miss_answer(node, scope)
172
+ return nil if scope.optimistic_origins[node]
173
+
174
+ case node
175
+ when Prism::LocalVariableReadNode then scope.optimistic_local_miss(node.name)
176
+ when Prism::InstanceVariableReadNode then scope.optimistic_ivar_miss(node.name)
177
+ when Prism::CallNode then miss_answer_of_call(node, scope)
178
+ when Prism::ParenthesesNode
179
+ body = node.body
180
+ body.is_a?(Prism::StatementsNode) && body.body.size == 1 ? miss_answer(body.body.first, scope) : UNKNOWN_MISS
181
+ else UNKNOWN_MISS
182
+ end
183
+ end
184
+
185
+ def miss_answer_of_call(node, scope)
186
+ return nil if node.safe_navigation? && resolve(node.receiver, scope)
187
+
188
+ operand = nil_question_operand(node)
189
+ inner = operand ? miss_answer(operand, scope) : UNKNOWN_MISS
190
+ return UNKNOWN_MISS if inner.equal?(UNKNOWN_MISS)
191
+
192
+ case node.name
193
+ when :! then !inner
194
+ when :!= then !inner.nil?
195
+ else inner.nil?
196
+ end
84
197
  end
85
198
 
86
199
  # `(x.nil?)` — a single-statement parenthesised body is its own value, and authors do parenthesise a
@@ -93,13 +206,37 @@ module Rigor
93
206
  resolve(body.body.first, scope)
94
207
  end
95
208
 
209
+ # The mark a multiple assignment's right-hand side hands to the slots it fills, in the shape
210
+ # `MultiTargetBinder.bind_marked`'s `optimistic:` takes. `true` when the whole right-hand side resolves
211
+ # to a mark: a miss makes it `nil`, and `k, v = nil` binds `nil` to every fixed slot, so each slot's
212
+ # nil-freeness is the same bet (the rule a nested target under an optimistic slot already follows). A
213
+ # literal array right-hand side (`x, y = h[k], 1`) is judged element-wise instead — an Array of each
214
+ # element's own marks, nested for a nested literal — since each element is its slot's value; a splat
215
+ # element makes the slot positions unknowable and declines. `false` when nothing is marked.
216
+ def destructuring_marks(node, scope)
217
+ return true if resolve(node, scope)
218
+ return false unless node.is_a?(Prism::ArrayNode) && node.elements.none?(Prism::SplatNode)
219
+
220
+ marks = node.elements.map { |element| destructuring_marks(element, scope) }
221
+ marks.any? { |mark| mark != false } ? marks : false
222
+ end
223
+
224
+ # Issue #1302 — the miss answer every slot {.destructuring_marks} marks records: `nil` when each marked
225
+ # part of the right-hand side answers `nil` on a miss, since `nil` destructures to `nil` in every slot, and
226
+ # {UNKNOWN_MISS} otherwise. A marked right-hand side a miss makes a boolean (`a, b = !h[k]`) binds it to
227
+ # the first slot only, so its slots are not told apart and all of them record nothing.
228
+ def destructuring_miss(node, scope)
229
+ return miss_answer(node, scope).nil? ? nil : UNKNOWN_MISS if resolve(node, scope)
230
+ return nil unless node.is_a?(Prism::ArrayNode)
231
+
232
+ node.elements.all? { |element| destructuring_miss(element, scope).nil? } ? nil : UNKNOWN_MISS
233
+ end
234
+
96
235
  # Whether the overload the selector actually picked carries the ignored annotation. The judgment is
97
236
  # per-overload, which is what makes it precise: `Array#first` is optimistic while `Array#first(3)` is
98
237
  # not, and `String#[]` / `Enumerable#find` are honest because they already spell the miss as `?`.
99
238
  #
100
- # @param method_definition [RBS::Definition::Method]
101
- # @param method_type [RBS::MethodType] the overload {OverloadSelector.select} returned
102
- # @return [Boolean]
239
+ # @param method_type — the overload {OverloadSelector.select} returned
103
240
  def optimistic_overload?(method_definition, method_type)
104
241
  type_def = matching_type_def(method_definition, method_type)
105
242
  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
@@ -266,7 +265,7 @@ module Rigor
266
265
  method_visibilities: :discovered_method_visibilities,
267
266
  methods: :discovered_methods,
268
267
  data_member_layouts: :data_member_layouts,
269
- struct_member_layouts: :struct_member_layouts
268
+ struct_member_layouts: :struct_member_layouts, deferred_ranges: :discovered_deferred_ranges
270
269
  }.freeze
271
270
  private_constant :DISCOVERY_FIELD
272
271
 
@@ -330,7 +329,8 @@ module Rigor
330
329
  arg.is_a?(Prism::AssocSplatNode)
331
330
  end
332
331
 
333
- # @return [[String, Symbol, Symbol, Prism::DefNode], nil]
332
+ # @return the callee's `[class_name, method_name, kind, def_node]`, or nil when the receiver's class
333
+ # or the method's `def` is not in the project index
334
334
  def resolve_callee(call_node, scope, index)
335
335
  if call_node.receiver.nil?
336
336
  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)
@@ -122,13 +122,12 @@ module Rigor
122
122
  end
123
123
  end
124
124
 
125
- # @param scope [Rigor::Scope] base scope for type inference.
125
+ # @param scope — base scope for type inference.
126
126
  def initialize(scope: nil)
127
127
  @scope = scope || Scope.empty
128
128
  end
129
129
 
130
- # @param root [Prism::Node] the parsed AST
131
- # @return [FileResult]
130
+ # @param root — the parsed AST
132
131
  def scan(root)
133
132
  scope_index = ScopeIndexer.index(root, default_scope: @scope)
134
133
  tier_counts = TIERS.to_h { |t| [t, 0] }
@@ -156,7 +155,7 @@ module Rigor
156
155
  when Type::Nominal, Type::Singleton,
157
156
  Type::DataClass, Type::StructClass then :nominal
158
157
  when Type::Tuple, Type::HashShape,
159
- Type::IntegerRange, Type::App,
158
+ Type::IntegerRange, Type::FloatRange, Type::App,
160
159
  Type::DataInstance, Type::StructInstance,
161
160
  Type::BoundMethod then :shaped
162
161
  when Type::Refined then :refined