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
@@ -5,7 +5,12 @@ require "prism"
5
5
  require_relative "../type"
6
6
  require_relative "../source/node_children"
7
7
  require_relative "content_join"
8
+ require_relative "hash_lookup_mutation"
9
+ require_relative "mutation_rejoin"
8
10
  require_relative "receiver_alias"
11
+ require_relative "refinement_mutation"
12
+ require_relative "rewrite_mutation"
13
+ require_relative "string_mutation"
9
14
 
10
15
  module Rigor
11
16
  module Inference
@@ -49,6 +54,8 @@ module Rigor
49
54
  #
50
55
  # - `arr.<mutator>(...)` where `arr` is a local variable.
51
56
  # - `@arr.<mutator>(...)` where `@arr` is an instance variable.
57
+ # - `@@arr.<mutator>(...)` / `$arr.<mutator>(...)` where the receiver is a class variable or global, and
58
+ # `it.<mutator>(...)` on the Ruby 3.4 `it` parameter ({ReceiverAlias.mutated_reads}).
52
59
  # - a receiver that *selects* among such variables rather than naming one —
53
60
  # `(kind == :required ? required : optional)[key] = info`, an `if`/`else`, `||` / `&&`. Every
54
61
  # variable the expression can evaluate to is a possible mutation target, so every one widens
@@ -86,19 +93,21 @@ module Rigor
86
93
  ].to_set.freeze
87
94
 
88
95
  # Hash mutators that invalidate a `HashShape` carrier. Same principle as `ARRAY_MUTATORS`:
89
- # only the receiver-mutating methods are listed.
96
+ # only the receiver-mutating methods are listed. A name both classes define is listed in
97
+ # both tables — being in `ARRAY_MUTATORS` does not put it here, and `shift`'s absence let
98
+ # `k = { a: 1 }; k.shift` keep the literal shape of a hash that is empty at runtime.
90
99
  HASH_MUTATORS = %i[
91
100
  []= store
92
- delete delete_if reject! select! filter! keep_if
101
+ shift delete delete_if reject! select! filter! keep_if
93
102
  clear compact! merge! update transform_keys! transform_values!
94
103
  replace
95
104
  ].to_set.freeze
96
105
 
97
106
  # Every method name {#widen_for_mutator} responds to — the one set a body scan asks "could an
98
107
  # in-place call on this name change its binding?" against (issue #587: the block-return
99
- # threading gate). Derived from the two tables above rather than spelled out, so the scan and
100
- # the widening it predicts cannot drift apart.
101
- SHAPE_MUTATORS = (ARRAY_MUTATORS | HASH_MUTATORS).freeze
108
+ # threading gate). Derived from the tables rather than spelled out, so the scan and the
109
+ # widening it predicts cannot drift apart.
110
+ SHAPE_MUTATORS = (ARRAY_MUTATORS | HASH_MUTATORS | HashLookupMutation::MUTATORS | StringMutation::MUTATORS).freeze
102
111
 
103
112
  # Methods that return the receiver (or a shallow copy) and cannot mutate it. They must not
104
113
  # trigger widening or any other receiver-fact invalidation. The list is intentionally
@@ -110,9 +119,7 @@ module Rigor
110
119
 
111
120
  # True when `method_name` is a pure self-returner that must
112
121
  # not invalidate the receiver's facts.
113
- def pure_self_returner?(method_name)
114
- PURE_SELF_RETURNERS.include?(method_name)
115
- end
122
+ def pure_self_returner?(method_name) = PURE_SELF_RETURNERS.include?(method_name)
116
123
 
117
124
  # Returns a scope with the call's receiver widened, for every variable the receiver expression
118
125
  # can evaluate to ({ReceiverAlias.candidates}) whose current binding is a literal-shape carrier
@@ -126,10 +133,6 @@ module Rigor
126
133
  # pre-join behaviour exactly.
127
134
  NO_ARG_TYPES = [].freeze
128
135
 
129
- # @param call_node [Prism::CallNode]
130
- # @param current_scope [Rigor::Scope]
131
- # @param arg_types [Array<Rigor::Type::Base>]
132
- # @return [Rigor::Scope]
133
136
  def widen_after_call(call_node:, current_scope:, arg_types: NO_ARG_TYPES)
134
137
  return current_scope if pure_self_returner?(call_node.name)
135
138
 
@@ -138,26 +141,43 @@ module Rigor
138
141
 
139
142
  # True when `receiver` names at least one variable whose CURRENT binding is a literal-shape
140
143
  # carrier — the only pre-state {#widen_for_mutator} joins into. Callers use it to skip typing a
141
- # mutator's arguments when nothing will consume them: `buf << x` on a String and `arr << x` on an
142
- # already-nominal Array are the common cases, and `Scope#type_of` memoizes nothing.
144
+ # mutator's arguments when nothing will consume them, and `Scope#type_of` memoizes nothing.
143
145
  def joinable_receiver?(receiver, scope)
144
146
  return false if receiver.nil?
145
147
 
146
- ReceiverAlias.candidates(receiver).any? do |read|
147
- current =
148
- case read
149
- when Prism::LocalVariableReadNode then scope.local(read.name)
150
- when Prism::InstanceVariableReadNode then scope.ivar(read.name)
151
- end
152
- current.is_a?(Type::Tuple) || current.is_a?(Type::HashShape)
148
+ ReceiverAlias.mutated_reads(receiver).any? do |read|
149
+ getter, = ALIAS_ACCESSORS[read.class]
150
+ getter && shape_carrier?(scope.public_send(getter, ReceiverAlias.read_name(read)))
153
151
  end
154
152
  end
155
153
 
156
- # Widens every variable `receiver` can evaluate to, against `method_name`'s mutator table.
154
+ # A union carrying a shape member is joinable too (issue #645): {#widen_union} widens that
155
+ # member through the same join, so the arguments the caller would skip are the evidence it
156
+ # needs.
157
+ # A `Constant[String]` counts too since issue #617 residue (4): the gate exists to skip typing arguments
158
+ # nothing will consume, and this carrier IS consumed now — `buf << x` on a `+"ab"` rewrites the binding.
159
+ # An empty-witness `Difference` counts too (issue #936): the `non-empty-array[T]` arm joins the
160
+ # mutator's added element now, so its arguments are evidence this seam consumes rather than
161
+ # arguments nothing will read.
162
+ def shape_carrier?(type)
163
+ case type
164
+ when Type::Tuple, Type::HashShape then true
165
+ when Type::Constant then StringMutation.constant?(type)
166
+ # A re-openable nominal counts since issue #580: skipping it made the one-way door
167
+ # self-fulfilling, since the arm never saw the arg types it grows from.
168
+ when Type::Nominal then MutationRejoin.regrowable_carrier?(type)
169
+ when Type::Difference then type.removes_empty_witness?
170
+ when Type::Union then type.members.any? { |m| shape_carrier?(m) }
171
+ else false
172
+ end
173
+ end
174
+
175
+ # Widens every variable `receiver` can evaluate to ({ReceiverAlias.mutated_reads}), against `method_name`'s
176
+ # mutator table.
157
177
  def widen_receiver_aliases(receiver, method_name, current_scope, arg_types: NO_ARG_TYPES)
158
178
  return current_scope if receiver.nil?
159
179
 
160
- ReceiverAlias.candidates(receiver).reduce(current_scope) do |acc, read|
180
+ ReceiverAlias.mutated_reads(receiver).reduce(current_scope) do |acc, read|
161
181
  widen_alias_read(method_name, read, acc, arg_types: arg_types)
162
182
  end
163
183
  end
@@ -212,9 +232,10 @@ module Rigor
212
232
  return scope if receiver.nil?
213
233
 
214
234
  ReceiverAlias.candidates(receiver).reduce(scope) do |acc, read|
215
- # A block-local read (`depth == 0`) is not a capture of the outer scope, so widening its
216
- # name against the OUTER scope would hit an unrelated same-named binding.
217
- next acc if read.is_a?(Prism::LocalVariableReadNode) && read.depth.zero?
235
+ # A block-local read ({ReceiverAlias.block_local?}: a `depth == 0` local, or `it`) is not a
236
+ # capture of the outer scope, so widening its name against the OUTER scope would hit an
237
+ # unrelated same-named binding.
238
+ next acc if ReceiverAlias.block_local?(read)
218
239
 
219
240
  # `values: :keep` — this is the block-capture path, and the slice-C content join that runs
220
241
  # after it re-adds the appended values' types onto the widened seed, so the seed's own value
@@ -224,30 +245,30 @@ module Rigor
224
245
  end
225
246
  end
226
247
 
227
- def widen_alias_read(method_name, read, scope, values: :widen, arg_types: NO_ARG_TYPES)
228
- case read
229
- when Prism::LocalVariableReadNode
230
- widen_local(method_name, read.name, scope, values: values, arg_types: arg_types)
231
- when Prism::InstanceVariableReadNode
232
- widen_ivar(method_name, read.name, scope, values: values, arg_types: arg_types)
233
- else scope
234
- end
235
- end
236
-
237
- def widen_local(method_name, var_name, current_scope, values: :widen, arg_types: NO_ARG_TYPES)
238
- current = current_scope.local(var_name)
239
- widened = widen_for_mutator(current, method_name, values: values, arg_types: arg_types)
240
- return current_scope if widened.nil?
248
+ # The arms per kind of binding differ only in which pair of `Scope` accessors they use, so they are read out
249
+ # of one table rather than written once per kind: a carrier the widening responds to must not be widened on
250
+ # one kind of binding and left on another. `$g << x; $g == "k"` folded always-truthy on a `$g` Ruby holds
251
+ # as `"kx"` while the table had only the local and ivar rows. A local rebinds through `with_mutated_local`: the
252
+ # mutated binding names the same object, so it keeps the marks a write drops (issue #1287).
253
+ ALIAS_ACCESSORS = Ractor.make_shareable({
254
+ Prism::LocalVariableReadNode => %i[local with_mutated_local],
255
+ Prism::ItLocalVariableReadNode => %i[local with_mutated_local],
256
+ Prism::InstanceVariableReadNode => %i[ivar with_ivar],
257
+ Prism::ClassVariableReadNode => %i[cvar with_cvar],
258
+ Prism::GlobalVariableReadNode => %i[global with_global]
259
+ })
260
+ private_constant :ALIAS_ACCESSORS
241
261
 
242
- current_scope.with_local(var_name, widened)
243
- end
262
+ def widen_alias_read(method_name, read, scope, values: :widen, arg_types: NO_ARG_TYPES)
263
+ getter, builder = ALIAS_ACCESSORS[read.class]
264
+ return scope if getter.nil?
244
265
 
245
- def widen_ivar(method_name, var_name, current_scope, values: :widen, arg_types: NO_ARG_TYPES)
246
- current = current_scope.ivar(var_name)
266
+ name = ReceiverAlias.read_name(read)
267
+ current = scope.public_send(getter, name)
247
268
  widened = widen_for_mutator(current, method_name, values: values, arg_types: arg_types)
248
- return current_scope if widened.nil?
269
+ return scope if widened.nil?
249
270
 
250
- current_scope.with_ivar(var_name, widened)
271
+ MutationRejoin.rebind(scope, builder, name, widened, pre_state: current, kind: getter)
251
272
  end
252
273
 
253
274
  # Mutators that can land a NEW value in an EXISTING slot, falsifying that slot's value pinning
@@ -259,29 +280,74 @@ module Rigor
259
280
  private_constant :VALUE_REWRITING_MUTATORS
260
281
 
261
282
  # Returns the widened type for a binding whose receiver is about to be mutated by
262
- # `method_name`, or `nil` when no widening applies (binding is not a literal-shape
263
- # carrier, OR the method is not a mutator for that shape, OR the binding is already a
264
- # nominal — no precision to lose).
283
+ # `method_name`, or `nil` when no widening applies (binding is not a carrier the mutation
284
+ # stands for, OR the method is not a mutator for that shape, OR the carrier is a PRECISE
285
+ # nominal, whose element set is a claim this seam may not grow — see {MutationRejoin.regrowable_carrier?}).
265
286
  def widen_for_mutator(type, method_name, values: :widen, arg_types: NO_ARG_TYPES)
266
287
  values = :keep unless VALUE_REWRITING_MUTATORS.include?(method_name)
288
+ values = RewriteMutation.values_mode(type, method_name, values)
267
289
 
268
290
  return nil if type.nil?
269
291
 
270
292
  case type
293
+ when Type::Nominal then MutationRejoin.widen_nominal(type, method_name, values:, arg_types:)
271
294
  when Type::Tuple
272
295
  return nil unless ARRAY_MUTATORS.include?(method_name)
273
296
 
274
297
  join_added_elements(widen_tuple(type, values: values), method_name, arg_types, type.elements)
275
298
  when Type::HashShape
276
- return nil unless HASH_MUTATORS.include?(method_name)
299
+ return HashLookupMutation.widen_shape(type, method_name) unless HASH_MUTATORS.include?(method_name)
277
300
 
278
301
  join_added_pairs(widen_hash_shape(type, values: values), method_name, arg_types,
279
302
  ContentJoin.hash_shape_key_values(type))
303
+ when Type::Constant then StringMutation.widen_constant(type, method_name)
280
304
  when Type::Difference
281
- widen_difference(type, method_name)
305
+ widen_difference(type, method_name, arg_types: arg_types)
306
+ when Type::Union
307
+ widen_union(type, method_name, values: values, arg_types: arg_types)
282
308
  end
283
309
  end
284
310
 
311
+ # A `Union` seed is widened MEMBERWISE (issue #645). Declining it outright left a straight-line
312
+ # mutation on a union binding recording nothing at all — `out = flag ? 5 : [2]; out << 2` kept
313
+ # the `[2]` Tuple's arity after the mutation falsified it, so a later `out.size == 1` folds on a
314
+ # value the program never holds.
315
+ #
316
+ # Which members the mutation stands for is {ContentJoin}'s residue partition read from this
317
+ # side, so the straight-line seam and the block seam (#631) agree about a union: a member the
318
+ # mutator's class carries widens through the ordinary non-union path above, every other member
319
+ # survives whole. The two tables are consulted per member rather than once for the union, which
320
+ # is what a name in BOTH of them (`clear`, `delete`, `replace`) needs on a mixed
321
+ # `Array | Hash` seed.
322
+ #
323
+ # `nil` when no member widened — a union with no carrier member, or one whose carriers are
324
+ # already nominal, is untouched exactly as the scalar path leaves them.
325
+ def widen_union(union, method_name, values: :widen, arg_types: NO_ARG_TYPES)
326
+ widened_any = false
327
+ members = union.members.map do |member|
328
+ next member unless mutation_carrier?(member, method_name)
329
+
330
+ widened = widen_for_mutator(member, method_name, values: values, arg_types: arg_types)
331
+ next member if widened.nil?
332
+
333
+ widened_any = true
334
+ widened
335
+ end
336
+ return nil unless widened_any
337
+
338
+ Type::Combinator.union(*members)
339
+ end
340
+
341
+ # True when the mutator's own class stands for `member` — the absorbed side of
342
+ # {ContentJoin.array_residue} / {ContentJoin.hash_residue}, whichever table names the call. A
343
+ # {HashLookupMutation} name stands for a Hash member as a Hash mutator does.
344
+ def mutation_carrier?(member, method_name)
345
+ (ARRAY_MUTATORS.include?(method_name) && ContentJoin.array_residue(member).empty?) ||
346
+ ((HASH_MUTATORS.include?(method_name) || HashLookupMutation::MUTATORS.include?(method_name)) &&
347
+ ContentJoin.hash_residue(member).empty?) ||
348
+ (StringMutation::MUTATORS.include?(method_name) && StringMutation.constant?(member))
349
+ end
350
+
285
351
  # Joins the element evidence the mutator's own ARGUMENTS introduce into the already-widened
286
352
  # Array carrier (issue #560). Without it the widening keeps only the SEED's elements, which
287
353
  # under-covers precisely the value the mutation added: `u = [1, 2]; u.push(6)` left
@@ -305,7 +371,11 @@ module Rigor
305
371
  # **The join records what it saw; it never CLOSES the parameter.** That is
306
372
  # {#gradual_floor}'s job, and the reason is the seam this method sits on rather than anything
307
373
  # about Array or Hash.
374
+ #
375
+ # A mutator whose arguments do not describe what it stores ({RewriteMutation}) takes that module's gradual
376
+ # arm first, on every path that reaches this join: the literal arms, the refinement arm, and the re-join.
308
377
  def join_added_elements(widened, method_name, arg_types, seed_elements)
378
+ widened = RewriteMutation.arm(widened, method_name)
309
379
  return widened unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name)
310
380
 
311
381
  added = value_pin_widened(ContentJoin.array_added_elements(method_name, arg_types))
@@ -340,7 +410,10 @@ module Rigor
340
410
  # BLOCK path (ADR-56 slice C) on the other side: `content_writeback_block_captures` scans the
341
411
  # whole body and joins every mutator call in it before writing back, so its evidence IS
342
412
  # complete for that body and its precise join stays justified — `acc = []; xs.each { |x|
343
- # acc.push(x) }` keeps reading `Array[Integer]`.
413
+ # acc.push(x) }` keeps reading `Array[Integer]`. Seeing every store is only half of that
414
+ # claim: a store computed from the collection's own contents (`h[k] = h[k] + 1`) is
415
+ # complete evidence only once it holds past the first iteration, which is why that seam
416
+ # iterates such a store to a fixpoint instead of typing it once (ADR-56 WD2.13).
344
417
  #
345
418
  # `Array[Integer | Dynamic[top]]` still does everything issue #560 needs: a union carrying
346
419
  # `Dynamic` cannot constant-fold, so every stale always-falsey the join was written to remove
@@ -351,12 +424,17 @@ module Rigor
351
424
 
352
425
  # The Hash-side twin of {#join_added_elements}: `h[k] = v` / `h.store(k, v)` join the stored
353
426
  # key and value into the widened `Hash[K, V]` carrier, each admitted against its OWN side's
354
- # seed evidence (a foreign key does not make the value gradual, or the reverse).
427
+ # seed evidence (a foreign key does not make the value gradual, or the reverse). {RewriteMutation}'s arm lands
428
+ # first, as it does in {#join_added_elements}.
355
429
  def join_added_pairs(widened, method_name, arg_types, seed_pairs)
430
+ widened = RewriteMutation.arm(widened, method_name)
356
431
  return widened unless ContentJoin::HASH_CONTENT_ADDERS.include?(method_name)
357
432
  return widened if arg_types.size < 2
358
433
 
359
- added = value_pin_widened([arg_types.first, arg_types.last])
434
+ # A `nil` index marks a splat for the Array splice classifier; the pair seam reads
435
+ # `arg_types.first` as the KEY, where the splat's expansion is an unknown key rather
436
+ # than absent arity — it degrades to untyped, not `nil` (issue #1140).
437
+ added = value_pin_widened([arg_types.first || Type::Combinator.untyped, arg_types.last])
360
438
  return widened unless added.size == 2
361
439
 
362
440
  seed_keys, seed_values = seed_pairs
@@ -433,25 +511,19 @@ module Rigor
433
511
  end
434
512
  end
435
513
 
436
- # `non-empty-array[T]` / `non-empty-hash[K, V]` → the bare base nominal. These refinement
437
- # carriers are what `empty?` / `any?` narrowing writes (ADR-47 §4-4), and they are just as
438
- # invalidated by an in-place mutator as a `Tuple` is: `arr.clear` makes `arr` empty, so a
439
- # surviving `Difference[Array[T], Tuple[]]` would project `arr.size` to `positive-int` and
440
- # fold `arr.size == 0` to `Constant[false]` — a false `flow.always-falsey-condition` on
441
- # correct code.
442
- #
443
- # Only the EMPTY-witness differences over Array / Hash are widened, and only for that
444
- # base's mutator table. The other catalogued refinements are unreachable from these tables
445
- # by construction: `non-empty-string` and `non-zero-int` bind String / Integer receivers,
446
- # whose mutators (`String#<<` and friends) appear in neither `ARRAY_MUTATORS` nor
447
- # `HASH_MUTATORS` — and none of them can empty a non-empty string anyway.
448
- def widen_difference(difference, method_name)
449
- return nil unless difference.removes_empty_witness?
450
-
451
- base = difference.base
452
- case base.class_name
453
- when "Array" then ARRAY_MUTATORS.include?(method_name) ? base : nil
454
- when "Hash" then HASH_MUTATORS.include?(method_name) ? base : nil
514
+ # The empty-witness refinement arm. {RefinementMutation} owns the witness half — whether
515
+ # `xs` is still provably non-empty after `method_name` — and takes the content half from
516
+ # here, so a refinement joins the mutator's added content on exactly the terms the `Tuple` /
517
+ # `HashShape` arms above do (issue #936, ADR-56 WD2.9's deferred branch).
518
+ def widen_difference(difference, method_name, arg_types: NO_ARG_TYPES)
519
+ RefinementMutation.widen(difference, method_name, arg_types) do |base|
520
+ if base.class_name == "Array"
521
+ ARRAY_MUTATORS.include?(method_name) &&
522
+ join_added_elements(base, method_name, arg_types, ContentJoin.collection_element_types(base))
523
+ else
524
+ HASH_MUTATORS.include?(method_name) &&
525
+ join_added_pairs(base, method_name, arg_types, ContentJoin.hash_shape_key_values(base))
526
+ end
455
527
  end
456
528
  end
457
529
 
@@ -473,16 +545,17 @@ module Rigor
473
545
  Type::Combinator.nominal_of("Array", type_args: [element_type])
474
546
  end
475
547
 
476
- # `HashShape` (closed or open) → `Nominal[Hash, [Kunion, Vunion]]`. Empty / extra-keys-only
477
- # shapes degrade to a fully-untyped Hash. Values widen their pinning the same way
478
- # {#widen_tuple}'s elements do (issue #560): `opts = {headers: false}` then
479
- # `opts[:encoding] = v` must not keep `false` as the whole value bound — redmine's
480
- # `import.rb:274` read the stored key back through it and drew a false always-falsey.
548
+ # Closed `HashShape` → `Nominal[Hash, [Kunion, Vunion]]`. An empty or an open shape degrades to
549
+ # a fully-untyped Hash: an open shape's unseen keys may hold any value, and one {HashLookupMutation}
550
+ # opened reads its DEFAULT for a missing key, so a bound built from the known values alone would
551
+ # pin that read to one of them (`{ a: 1 }` under `default = 0` then `delete(:a)` read `h[:b]` as
552
+ # `1`). Values widen their pinning the same way {#widen_tuple}'s elements do (issue #560):
553
+ # `opts = {headers: false}` then `opts[:encoding] = v` must not keep `false` as the whole value
554
+ # bound — redmine's `import.rb:274` read the stored key back through it and drew a false
555
+ # always-falsey.
481
556
  def widen_hash_shape(shape, values: :widen)
482
- if shape.pairs.empty?
483
- return Type::Combinator.nominal_of("Hash",
484
- type_args: [Type::Combinator.untyped,
485
- Type::Combinator.untyped])
557
+ if shape.pairs.empty? || shape.open?
558
+ return Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.untyped, Type::Combinator.untyped])
486
559
  end
487
560
 
488
561
  key_type = key_union_for(shape.pairs.keys)
@@ -497,9 +570,7 @@ module Rigor
497
570
  # `key_union_for` is delegated rather than duplicated: {#widen_hash_shape} and
498
571
  # `ContentJoin.hash_shape_key_values` must map a literal key set the SAME way, or a widened
499
572
  # carrier and the join that reads it back disagree about the key parameter.
500
- def key_union_for(keys)
501
- ContentJoin.key_union_for(keys)
502
- end
573
+ def key_union_for(keys) = ContentJoin.key_union_for(keys)
503
574
  end
504
575
  end
505
576
  end