rigortype 0.3.9 → 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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/10-sorbet.md +9 -10
  14. data/docs/handbook/11-sig-gen.md +454 -13
  15. data/docs/manual/02-cli-reference.md +63 -2
  16. data/docs/manual/03-configuration.md +7 -0
  17. data/docs/manual/04-diagnostics.md +4 -0
  18. data/docs/manual/07-plugins.md +1 -1
  19. data/docs/manual/10-mcp-server.md +3 -2
  20. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  21. data/docs/manual/19-effect-labels.md +10 -0
  22. data/docs/manual/plugins/README.md +7 -0
  23. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  24. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  25. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  26. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  27. data/docs/manual/plugins/rigor-activejob.md +7 -0
  28. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  29. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  30. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  31. data/docs/manual/plugins/rigor-grape.md +106 -0
  32. data/docs/manual/plugins/rigor-graphql.md +23 -2
  33. data/docs/manual/plugins/rigor-pundit.md +8 -1
  34. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  35. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  36. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  37. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  38. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  39. data/exe/rigor +19 -4
  40. data/lib/rigor/analysis/baseline.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  42. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  43. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  49. data/lib/rigor/analysis/check_rules.rb +520 -49
  50. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  51. data/lib/rigor/analysis/fact_store.rb +9 -0
  52. data/lib/rigor/analysis/incremental.rb +26 -0
  53. data/lib/rigor/analysis/incremental_session.rb +36 -4
  54. data/lib/rigor/analysis/project_scan.rb +11 -1
  55. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  56. data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
  57. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  58. data/lib/rigor/analysis/run_cache_key.rb +20 -11
  59. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
  60. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  61. data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
  62. data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
  63. data/lib/rigor/analysis/runner.rb +278 -20
  64. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  65. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  66. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  67. data/lib/rigor/analysis/template_units.rb +399 -0
  68. data/lib/rigor/analysis/worker_session.rb +53 -10
  69. data/lib/rigor/bleeding_edge.rb +0 -2
  70. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  71. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  72. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  73. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  74. data/lib/rigor/cache/descriptor.rb +6 -1
  75. data/lib/rigor/cache/engine_source.rb +29 -1
  76. data/lib/rigor/cache/incremental_snapshot.rb +33 -1
  77. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  78. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  79. data/lib/rigor/cache/store.rb +0 -1
  80. data/lib/rigor/ci_detector.rb +1 -0
  81. data/lib/rigor/cli/doc_links.rb +1 -1
  82. data/lib/rigor/cli/docs_command.rb +4 -4
  83. data/lib/rigor/cli/plugin_command.rb +3 -3
  84. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  85. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  86. data/lib/rigor/cli/skill_command.rb +1 -1
  87. data/lib/rigor/cli/skill_describe.rb +0 -1
  88. data/lib/rigor/cli/type_of_command.rb +19 -5
  89. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  90. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  91. data/lib/rigor/cli.rb +21 -3
  92. data/lib/rigor/configuration/severity_profile.rb +19 -3
  93. data/lib/rigor/configuration.rb +66 -3
  94. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  95. data/lib/rigor/effects/attribution.rb +11 -2
  96. data/lib/rigor/effects/callee_rule.rb +368 -0
  97. data/lib/rigor/effects/catalog.rb +7 -4
  98. data/lib/rigor/effects/collector.rb +11 -5
  99. data/lib/rigor/effects/config_envelopes.rb +9 -2
  100. data/lib/rigor/effects/definition_context.rb +179 -0
  101. data/lib/rigor/effects/effect_table.rb +11 -3
  102. data/lib/rigor/effects/envelope_check.rb +1 -1
  103. data/lib/rigor/effects/envelope_index.rb +15 -0
  104. data/lib/rigor/effects/file_collection.rb +59 -5
  105. data/lib/rigor/effects/framework_units.rb +1 -1
  106. data/lib/rigor/effects/identity.rb +16 -0
  107. data/lib/rigor/effects/local_ownership.rb +38 -12
  108. data/lib/rigor/effects/method_key.rb +21 -0
  109. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  110. data/lib/rigor/effects/plugin_facts.rb +43 -31
  111. data/lib/rigor/effects/propagator.rb +295 -16
  112. data/lib/rigor/effects/registry.rb +1 -1
  113. data/lib/rigor/effects/scanner.rb +121 -72
  114. data/lib/rigor/effects/signature_sources.rb +1 -1
  115. data/lib/rigor/effects/snapshot.rb +2 -1
  116. data/lib/rigor/effects/summary.rb +27 -4
  117. data/lib/rigor/effects/unit_scan.rb +385 -30
  118. data/lib/rigor/effects/visibility.rb +101 -0
  119. data/lib/rigor/environment/lockfile_resolver.rb +17 -0
  120. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  121. data/lib/rigor/environment/member_consistency.rb +298 -0
  122. data/lib/rigor/environment/rbs_loader.rb +399 -133
  123. data/lib/rigor/environment.rb +103 -38
  124. data/lib/rigor/hashing/xxh3.rb +264 -0
  125. data/lib/rigor/inference/acceptance.rb +139 -9
  126. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  127. data/lib/rigor/inference/block_call_timing.rb +338 -0
  128. data/lib/rigor/inference/block_parameter_binder.rb +73 -27
  129. data/lib/rigor/inference/block_repetition.rb +71 -0
  130. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  131. data/lib/rigor/inference/budget_trace.rb +2 -1
  132. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  133. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  134. data/lib/rigor/inference/captured_locals.rb +387 -15
  135. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  136. data/lib/rigor/inference/content_join.rb +200 -27
  137. data/lib/rigor/inference/def_return_typer.rb +11 -7
  138. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  139. data/lib/rigor/inference/element_read_widening.rb +22 -10
  140. data/lib/rigor/inference/error_info.rb +196 -0
  141. data/lib/rigor/inference/expression_typer.rb +1532 -496
  142. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  143. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  144. data/lib/rigor/inference/global_write_census.rb +239 -0
  145. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  146. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  147. data/lib/rigor/inference/index_write_widening.rb +16 -3
  148. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  149. data/lib/rigor/inference/jump_targets.rb +82 -0
  150. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  151. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  152. data/lib/rigor/inference/last_line.rb +340 -0
  153. data/lib/rigor/inference/last_status.rb +144 -0
  154. data/lib/rigor/inference/macro_block_self_type.rb +167 -17
  155. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  156. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  157. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  158. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  159. data/lib/rigor/inference/match_rebinding.rb +392 -0
  160. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  161. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  162. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  163. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  164. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  165. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  166. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  167. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  168. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  169. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
  170. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  171. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  172. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  173. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  174. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  175. data/lib/rigor/inference/multi_target_binder.rb +340 -52
  176. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  177. data/lib/rigor/inference/mutation_widening.rb +70 -48
  178. data/lib/rigor/inference/narrowing.rb +540 -120
  179. data/lib/rigor/inference/operand_effects.rb +167 -0
  180. data/lib/rigor/inference/operand_walk.rb +88 -0
  181. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  182. data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
  183. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  184. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  185. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  186. data/lib/rigor/inference/receiver_alias.rb +90 -1
  187. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  188. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  189. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  190. data/lib/rigor/inference/return_barrier.rb +54 -0
  191. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  192. data/lib/rigor/inference/scope_indexer.rb +4615 -551
  193. data/lib/rigor/inference/statement_evaluator.rb +3176 -490
  194. data/lib/rigor/inference/stored_block_call.rb +54 -0
  195. data/lib/rigor/inference/string_mutation.rb +44 -7
  196. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  197. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  198. data/lib/rigor/language_server/debouncer.rb +0 -1
  199. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  200. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  201. data/lib/rigor/language_server/project_context.rb +5 -3
  202. data/lib/rigor/mcp/server.rb +2 -1
  203. data/lib/rigor/plugin/base.rb +168 -5
  204. data/lib/rigor/plugin/box_probe.rb +91 -0
  205. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  206. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  207. data/lib/rigor/plugin/loader.rb +2 -1
  208. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  209. data/lib/rigor/plugin/manifest.rb +71 -10
  210. data/lib/rigor/plugin/registry.rb +35 -1
  211. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  212. data/lib/rigor/plugin/template_unit.rb +196 -0
  213. data/lib/rigor/plugin.rb +1 -0
  214. data/lib/rigor/protection/discovery_seed.rb +3 -1
  215. data/lib/rigor/protection/kill_signature.rb +0 -1
  216. data/lib/rigor/protection/mutation_cache.rb +1 -2
  217. data/lib/rigor/rbs_extended.rb +27 -0
  218. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  219. data/lib/rigor/reflection/constant_path.rb +19 -6
  220. data/lib/rigor/reflection.rb +72 -105
  221. data/lib/rigor/scope/discovery_index.rb +135 -2
  222. data/lib/rigor/scope.rb +859 -49
  223. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  224. data/lib/rigor/sig_gen/classification.rb +22 -5
  225. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  226. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  227. data/lib/rigor/sig_gen/generator.rb +558 -31
  228. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  229. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  230. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  231. data/lib/rigor/sig_gen/renderer.rb +124 -9
  232. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  233. data/lib/rigor/sig_gen/write_result.rb +19 -3
  234. data/lib/rigor/sig_gen/writer.rb +166 -35
  235. data/lib/rigor/sig_gen.rb +3 -0
  236. data/lib/rigor/signature_path_audit.rb +1 -1
  237. data/lib/rigor/source/node_walker.rb +0 -3
  238. data/lib/rigor/source/parameter_envelope.rb +72 -0
  239. data/lib/rigor/source.rb +1 -0
  240. data/lib/rigor/type/combinator.rb +88 -12
  241. data/lib/rigor/type/difference.rb +1 -0
  242. data/lib/rigor/type/hash_shape.rb +1 -1
  243. data/lib/rigor/type/refined.rb +1 -0
  244. data/lib/rigor/version.rb +1 -1
  245. data/lib/rigor.rb +1 -0
  246. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  247. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  248. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  249. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  250. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  251. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  252. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  253. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  254. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  255. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  256. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  257. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  258. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  259. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  260. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  261. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  262. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  263. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  264. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  265. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  266. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  267. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  268. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  269. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  270. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  271. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  272. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  273. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  274. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  275. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  276. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  277. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  278. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  279. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  280. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  281. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  282. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  283. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  284. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  285. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  286. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  287. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  288. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  289. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  290. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  291. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  292. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  293. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  294. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  295. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  296. data/sig/rigor/analysis/baseline.rbs +69 -7
  297. data/sig/rigor/analysis/fact_store.rbs +1 -1
  298. data/sig/rigor/analysis/project_scan.rbs +74 -0
  299. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  300. data/sig/rigor/effects/effect_table.rbs +60 -0
  301. data/sig/rigor/effects/envelope.rbs +97 -0
  302. data/sig/rigor/effects/envelope_index.rbs +33 -0
  303. data/sig/rigor/effects/file_collection.rbs +81 -0
  304. data/sig/rigor/effects/label.rbs +26 -0
  305. data/sig/rigor/effects/label_set.rbs +46 -0
  306. data/sig/rigor/effects/method_key.rbs +24 -0
  307. data/sig/rigor/effects/origin.rbs +50 -0
  308. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  309. data/sig/rigor/effects/registry.rbs +68 -0
  310. data/sig/rigor/effects/summary.rbs +50 -0
  311. data/sig/rigor/effects/taint_cause.rbs +12 -0
  312. data/sig/rigor/environment.rbs +12 -10
  313. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  314. data/sig/rigor/inference.rbs +6 -4
  315. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  316. data/sig/rigor/plugin/base.rbs +30 -7
  317. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  318. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  319. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  320. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  321. data/sig/rigor/plugin/io_boundary.rbs +1 -1
  322. data/sig/rigor/plugin/loader.rbs +3 -3
  323. data/sig/rigor/plugin/manifest.rbs +50 -11
  324. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  325. data/sig/rigor/plugin/registry.rbs +62 -1
  326. data/sig/rigor/plugin.rbs +1 -1
  327. data/sig/rigor/rbs_extended.rbs +1 -1
  328. data/sig/rigor/reflection.rbs +7 -6
  329. data/sig/rigor/scope.rbs +135 -20
  330. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  331. data/sig/rigor/source.rbs +4 -4
  332. data/sig/rigor/testing.rbs +10 -4
  333. data/sig/rigor/type.rbs +6 -0
  334. data/sig/rigor.rbs +38 -20
  335. data/skills/rigor-ask/SKILL.md +8 -5
  336. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  337. data/skills/rigor-ci-setup/SKILL.md +17 -21
  338. data/skills/rigor-doctor/SKILL.md +24 -22
  339. data/skills/rigor-doctor/references/01-checks.md +97 -33
  340. data/skills/rigor-editor-setup/SKILL.md +6 -4
  341. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  342. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  343. data/skills/rigor-next-steps/SKILL.md +4 -2
  344. data/skills/rigor-plugin-author/SKILL.md +19 -23
  345. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  346. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  347. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  348. data/skills/rigor-plugin-review/SKILL.md +6 -4
  349. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  350. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  351. data/skills/rigor-project-init/SKILL.md +9 -7
  352. data/skills/rigor-project-init/references/01-detect.md +13 -9
  353. data/skills/rigor-project-init/references/02-configure.md +33 -8
  354. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  355. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  356. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  357. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  358. data/skills/rigor-type-oracle/SKILL.md +4 -6
  359. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  360. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  361. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  362. data/skills/rigor-upgrade/SKILL.md +13 -8
  363. metadata +108 -1
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+ require_relative "../mutation_widening"
5
+
6
+ module Rigor
7
+ module Inference
8
+ module MethodDispatcher
9
+ # Issue #1092 — the substitute {RbsDispatch} hands the translator for `Bases::Self` on an instance
10
+ # receiver whose projection carries type arguments, so `Array[Integer]#tap {}` answers
11
+ # `Array[Integer]` rather than the raw `Array` the class name alone builds. `nil` keeps that raw
12
+ # nominal.
13
+ #
14
+ # The substitute is always a nominal built from the receiver's projected arguments, never the
15
+ # receiver's shape: the pure self-returners keep the shape through ShapeDispatch (ADR-76 WD3), and a
16
+ # block can mutate the receiver through its yielded alias, which MutationWidening does not follow.
17
+ # For the same reason every argument is widened DEEPLY — a literal to its class, a nested
18
+ # `Tuple` / `HashShape` to its projected nominal — so `[[1, 2]].tap { |a| a[0] << 3 }` cannot fold
19
+ # `.first.size == 3`.
20
+ #
21
+ # A `-> self` method that can change the element types keeps the arguments only when the call
22
+ # provably adds nothing outside them; otherwise the call value would claim `ints.map!(&:to_s)` is
23
+ # still an `Array[Integer]`.
24
+ module SelfSubstitute
25
+ # In-place methods that cannot change the receiver's element, key, or value types.
26
+ TYPE_PRESERVING = %i[
27
+ sort! sort_by! reverse! uniq! compact! shuffle! rotate!
28
+ select! filter! reject! keep_if delete_if delete subtract
29
+ clear compare_by_identity rehash reset
30
+ ].to_set.freeze
31
+
32
+ # Adders whose positional arguments are each a new element (`Array` / `Set`).
33
+ ELEMENT_ADDERS = %i[<< push append unshift prepend add add?].to_set.freeze
34
+
35
+ # Adders whose positional arguments are each a collection of the receiver's own class.
36
+ COLLECTION_ADDERS = %i[concat merge merge! update].to_set.freeze
37
+
38
+ # Every other name MutationWidening knows as a receiver mutator (`map!`, `replace`, `fill`,
39
+ # `flatten!`, `transform_values!`, ...) rewrites or may rewrite the element types.
40
+ KNOWN_MUTATORS = (MutationWidening::ARRAY_MUTATORS | MutationWidening::HASH_MUTATORS).freeze
41
+
42
+ module_function
43
+
44
+ # @param args — the call's positional argument types.
45
+ # @param block_type — the call's block return type, nil without a block.
46
+ def for(receiver, receiver_args, method_name, args, block_type)
47
+ return nil if receiver_args.empty?
48
+
49
+ case receiver
50
+ when Type::Nominal
51
+ nominal_self(receiver.class_name, receiver_args, method_name, args, block_type)
52
+ when Type::Tuple
53
+ projected_self("Array", receiver_args) unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
54
+ when Type::HashShape
55
+ projected_self("Hash", receiver_args) unless MutationWidening::HASH_MUTATORS.include?(method_name)
56
+ when Type::Refined, Type::Difference
57
+ self.for(receiver.base, receiver_args, method_name, args, block_type)
58
+ when Type::Dynamic
59
+ inner = self.for(receiver.static_facet, receiver_args, method_name, args, block_type)
60
+ inner && Type::Combinator.dynamic(inner)
61
+ end
62
+ end
63
+
64
+ def nominal_self(class_name, receiver_args, method_name, args, block_type)
65
+ return nil unless preserves_type_args?(class_name, receiver_args, method_name, args, block_type)
66
+
67
+ projected_self(class_name, receiver_args)
68
+ end
69
+
70
+ # Nil when every argument widens to the bare `Dynamic[top]`: `Hash[untyped, untyped]` says nothing
71
+ # the raw `Hash` does not.
72
+ def projected_self(class_name, receiver_args)
73
+ type_args = receiver_args.map { |arg| deep_widen(arg) }
74
+ return nil if type_args.all? { |arg| untyped?(arg) }
75
+
76
+ Type::Combinator.nominal_of(class_name, type_args: type_args)
77
+ end
78
+
79
+ def preserves_type_args?(class_name, receiver_args, method_name, args, block_type)
80
+ return true if TYPE_PRESERVING.include?(method_name)
81
+
82
+ if ELEMENT_ADDERS.include?(method_name)
83
+ receiver_args.size == 1 && all_provably_within?(receiver_args.first, args)
84
+ elsif method_name == :insert
85
+ receiver_args.size == 1 && all_provably_within?(receiver_args.first, args.drop(1))
86
+ elsif COLLECTION_ADDERS.include?(method_name)
87
+ block_type.nil? &&
88
+ all_provably_within?(Type::Combinator.nominal_of(class_name, type_args: receiver_args), args)
89
+ else
90
+ !KNOWN_MUTATORS.include?(method_name) && !method_name.end_with?("!")
91
+ end
92
+ end
93
+
94
+ def all_provably_within?(bound, args)
95
+ args.all? { |arg| !imprecise?(arg) && bound.accepts(arg, mode: :gradual).yes? }
96
+ end
97
+
98
+ def imprecise?(type)
99
+ case type
100
+ when Type::Dynamic then true
101
+ when Type::Union then type.members.any? { |member| imprecise?(member) }
102
+ else false
103
+ end
104
+ end
105
+
106
+ def untyped?(type)
107
+ type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
108
+ end
109
+
110
+ # `widen_value_pinned`, extended through nested shape carriers and applied type arguments.
111
+ def deep_widen(type)
112
+ case type
113
+ when Type::Tuple
114
+ return Type::Combinator.nominal_of("Array") if type.elements.empty?
115
+
116
+ Type::Combinator.nominal_of("Array", type_args: [deep_widen(Type::Combinator.union(*type.elements))])
117
+ when Type::HashShape
118
+ deep_widen_hash_shape(type)
119
+ when Type::Nominal
120
+ return type if type.type_args.empty?
121
+
122
+ Type::Combinator.nominal_of(type.class_name, type_args: type.type_args.map { |arg| deep_widen(arg) })
123
+ when Type::Union
124
+ Type::Combinator.union(*type.members.map { |member| deep_widen(member) })
125
+ when Type::Dynamic
126
+ untyped?(type) ? type : Type::Combinator.dynamic(deep_widen(type.static_facet))
127
+ else
128
+ Type::Combinator.widen_value_pinned(type)
129
+ end
130
+ end
131
+
132
+ def deep_widen_hash_shape(shape)
133
+ return Type::Combinator.nominal_of("Hash") if shape.pairs.empty?
134
+
135
+ keys = Type::Combinator.union(*shape.pairs.keys.map { |key| Type::Combinator.constant_of(key) })
136
+ values = Type::Combinator.union(*shape.pairs.values)
137
+ Type::Combinator.nominal_of("Hash", type_args: [deep_widen(keys), deep_widen(values)])
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -197,21 +197,11 @@ module Rigor
197
197
  Type::Difference => :dispatch_difference,
198
198
  Type::Refined => :dispatch_refined,
199
199
  Type::Intersection => :dispatch_intersection,
200
- Type::IntegerRange => :dispatch_integer_range
200
+ Type::IntegerRange => :dispatch_integer_range,
201
+ Type::FloatRange => :dispatch_float_range
201
202
  }.freeze
202
203
  private_constant :RECEIVER_HANDLERS
203
204
 
204
- # v0.1.1 Track 1 slice 5b — `Integer#to_s(base)` on a non-negative `IntegerRange` receiver. The
205
- # output of `n.to_s(b)` for `n >= 0` is digit-string-only (no leading sign), so when the base is in
206
- # this table the result lifts to the matching imported refinement. Bases not listed (2, 36, ...)
207
- # keep the v0.1.0 baseline since Rigor has no carrier for the resulting alphabet.
208
- TO_S_BASE_REFINEMENTS = {
209
- 10 => :decimal_int_string,
210
- 8 => :octal_int_string,
211
- 16 => :hex_int_string
212
- }.freeze
213
- private_constant :TO_S_BASE_REFINEMENTS
214
-
215
205
  def try_dispatch(context)
216
206
  receiver = context.receiver
217
207
  method_name = context.method_name
@@ -304,7 +294,9 @@ module Rigor
304
294
  when "String"
305
295
  dispatch_string_binary_from_arg(method_name, args.first) if args.size == 1
306
296
  when "Integer"
307
- dispatch_integer_binary_from_arg(method_name, args.first) if args.size == 1
297
+ integer_nominal_projection(method_name, args)
298
+ when "Float"
299
+ float_to_s_projection(method_name, args)
308
300
  when "Array"
309
301
  case method_name
310
302
  when :flatten then array_nominal_flatten(nominal, args)
@@ -313,6 +305,65 @@ module Rigor
313
305
  end
314
306
  end
315
307
 
308
+ # `Integer#to_s` / `Integer#inspect` (#993) on a bare `Nominal[Integer]` receiver, ahead of the
309
+ # existing `:*` binary fold. `#inspect` takes no argument and matches `#to_s()` byte for byte, so
310
+ # it routes through the same no-arg branch.
311
+ def integer_nominal_projection(method_name, args)
312
+ case method_name
313
+ when :to_s then integer_to_s_projection(args)
314
+ when :inspect then args.empty? ? Type::Combinator.decimal_int_string : nil
315
+ else dispatch_integer_binary_from_arg(method_name, args.first) if args.size == 1
316
+ end
317
+ end
318
+
319
+ # `decimal-int-string`'s predicate (`/\A-?\d+\z/`) admits the optional sign, so — unlike the
320
+ # `IntegerRange#to_s` tier above, which declines a signed range because it cannot yet prove the
321
+ # sign is absent — a bare `Integer` (sign completely unknown) still reaches it for the base-10
322
+ # case: base 10 is Ruby's default sign-and-digits grammar for every Integer. Any other base,
323
+ # known or not statically known, drops to `non-empty-string`: `to_s(base)` never returns `""`,
324
+ # but a non-decimal base's digits (`255.to_s(16)` is `"ff"`, not the `0xff` literal) match no
325
+ # registered refinement.
326
+ def integer_to_s_projection(args)
327
+ return Type::Combinator.decimal_int_string if args.empty?
328
+ return nil unless args.size == 1
329
+
330
+ decimal_base?(args.first) ? Type::Combinator.decimal_int_string : Type::Combinator.non_empty_string
331
+ end
332
+
333
+ def decimal_base?(arg)
334
+ arg.is_a?(Type::Constant) && arg.value == 10
335
+ end
336
+
337
+ # `Float#to_s` / `Float#inspect` (#993) on a bare `Nominal[Float]` receiver. No finiteness proof
338
+ # is available here — a bare `Float` admits `Infinity` / `-Infinity` / `NaN`, none of which are
339
+ # Ruby numeric literals — so the floor is `non-empty-string` (Ruby's conversion is total and
340
+ # never returns `""`). A `FloatRange` receiver with proven-finite bounds reaches `numeric-string`
341
+ # instead; see {#dispatch_float_range}.
342
+ def float_to_s_projection(method_name, args)
343
+ return nil unless %i[to_s inspect].include?(method_name)
344
+ return nil unless args.empty?
345
+
346
+ Type::Combinator.non_empty_string
347
+ end
348
+
349
+ # `Float#to_s` / `Float#inspect` (#993) on a `FloatRange` receiver. `min` / `canonical_max` both
350
+ # finite (Ruby's own `Float#finite?`, the same check {MethodDispatcher::ConstantFolding}'s
351
+ # `float_range_finite` fold uses) is exactly a finiteness proof: no bounded range contains `NaN`,
352
+ # and finite bounds rule out both infinities too. `finite-float` satisfies it, as does any
353
+ # narrower bounded sub-range and the `f.finite?` truthy-edge narrowing (ADR-109 WD5) that
354
+ # produces `finite-float` — but `Float[0.0..]` does NOT, because its endless upper bound is
355
+ # `Float::INFINITY`.
356
+ def dispatch_float_range(range, method_name, args)
357
+ return nil unless args.empty?
358
+ return nil unless %i[to_s inspect].include?(method_name)
359
+
360
+ if range.min.finite? && range.canonical_max.finite?
361
+ Type::Combinator.numeric_string
362
+ else
363
+ Type::Combinator.non_empty_string
364
+ end
365
+ end
366
+
316
367
  # `Array[T]#compact` — `compact` removes every `nil` element, so the result element type is `T`
317
368
  # with its `nil` constituent stripped (`Array[Node?]#compact` → `Array[Node]`). Mirrors the
318
369
  # `Tuple#compact` constant fold for the generic element case. Declines when the receiver carries
@@ -418,10 +469,18 @@ module Rigor
418
469
 
419
470
  # `IntegerRange#to_s` precision (v0.1.1 Track 1 slice 5b). When the range's lower bound is
420
471
  # `>= 0`, every member is a non-negative integer and `to_s(base)` returns a digit-string with no
421
- # leading sign. The result lifts to the matching imported refinement (`decimal-int-string` for
422
- # base 10, `octal-int-string` for 8, `hex-int-string` for 16). Signed ranges fall through (the
423
- # result could carry a `-` sign that no Rigor refinement currently captures), as do bases
424
- # without a digit-only refinement.
472
+ # leading sign. Base 10 is Ruby's decimal-literal grammar, so the result lifts to
473
+ # `decimal-int-string`. Signed ranges fall through entirely (the result could carry a `-` sign
474
+ # that `decimal-int-string` alone would need a sign proof for, matching the
475
+ # `Nominal[Integer]` tier's own signed-base-10 carve-out below).
476
+ #
477
+ # #1004 — every other base (a different literal, such as 8 or 16, or a value that is not
478
+ # statically known) does NOT lift to `octal-int-string` / `hex-int-string`: those refinements'
479
+ # predicates REQUIRE the `0o` / `0x` prefix Ruby's own `to_s(base)` never emits
480
+ # (`64.to_s(8)` is `"100"`, `255.to_s(16)` is `"ff"` — neither has a prefix), so a producer
481
+ # that claimed them was unsound. The floor is `non-empty-string`, same as the bare
482
+ # `Nominal[Integer]` path (#993 / #1001) reaches for the identical reason: `to_s(base)` is
483
+ # total and never returns `""`.
425
484
  def dispatch_integer_range(range, method_name, args)
426
485
  return nil unless method_name == :to_s
427
486
  return nil unless range.lower >= 0
@@ -429,10 +488,7 @@ module Rigor
429
488
  base = base_argument(args)
430
489
  return nil if base.nil?
431
490
 
432
- refinement = TO_S_BASE_REFINEMENTS[base]
433
- return nil if refinement.nil?
434
-
435
- Type::Combinator.public_send(refinement)
491
+ base == 10 ? Type::Combinator.decimal_int_string : Type::Combinator.non_empty_string
436
492
  end
437
493
 
438
494
  # `to_s` with no argument defaults to base 10. With one argument, the value MUST be a
@@ -1556,14 +1612,18 @@ module Rigor
1556
1612
  Type::Combinator.constant_of(found)
1557
1613
  end
1558
1614
 
1559
- # `shape.default` / `default_proc` — a literal `HashShape` carries no default value or proc, so
1560
- # both fold to `Constant[nil]`. `default` accepts an optional key argument (still returns the
1615
+ # `shape.default` / `default_proc` — a closed literal `HashShape` carries no default value or proc,
1616
+ # so both fold to `Constant[nil]`. `default` accepts an optional key argument (still returns the
1561
1617
  # default), `default_proc` takes none — the `args.size <= 1` guard covers both.
1562
- def hash_default(shape, _method_name, args)
1618
+ #
1619
+ # An open shape's `default` is `untyped`. The default is what an unseen key reads, which the known
1620
+ # values do not bound — the RBS answer `V?` would bound it by them, so `h = { a: 1 }; h.default = 0`
1621
+ # (which opens the shape, see `HashLookupMutation`) read `h.default` as `1?`. `default_proc` defers.
1622
+ def hash_default(shape, method_name, args)
1563
1623
  return nil unless args.size <= 1
1564
- return nil unless shape.closed?
1624
+ return Type::Combinator.constant_of(nil) if shape.closed?
1565
1625
 
1566
- Type::Combinator.constant_of(nil)
1626
+ Type::Combinator.untyped if method_name == :default
1567
1627
  end
1568
1628
 
1569
1629
  # `shape < other` / `<=` / `>` / `>=` — Hash containment comparison. Both sides must be closed
@@ -1772,17 +1832,23 @@ module Rigor
1772
1832
  #
1773
1833
  # On an `:open` shape neither applies — the key is unknown rather than missing, so both methods
1774
1834
  # answer the `untyped` step from {#hash_dig_step}.
1835
+ #
1836
+ # `[]` takes whatever step {#hash_dig_step} gives a computed key. `fetch` answers only literal keys
1837
+ # none of which can miss — a required declared key, or an undeclared one on an open shape — and
1838
+ # defers otherwise: a miss raises instead of reading nil, so the projection's nil-free value union
1839
+ # is already the sound answer.
1775
1840
  def hash_lookup(shape, method_name, args)
1776
1841
  return nil unless args.size == 1
1777
1842
 
1778
1843
  step = hash_dig_step(shape, args.first)
1779
1844
  return nil if step.nil?
1780
- return nil if method_name == :fetch && optional_key_step?(shape, args.first)
1781
- return step unless missing_key_step?(shape, args.first)
1782
-
1783
1845
  return step if method_name == :[]
1784
1846
 
1785
- nil
1847
+ keys = literal_keys(args.first)
1848
+ return nil if keys.nil?
1849
+ return nil if keys.any? { |key| shape.optional_key?(key) || (shape.closed? && !shape.pairs.key?(key)) }
1850
+
1851
+ step
1786
1852
  end
1787
1853
 
1788
1854
  # `shape.dig(:a, :b, ...)` with a chain of static keys. Same recursion semantics as Tuple#`dig`:
@@ -1799,20 +1865,45 @@ module Rigor
1799
1865
  end
1800
1866
 
1801
1867
  # Returns the per-step value type for a HashShape lookup (or `Constant[nil]` for a known-missing
1802
- # key). Returns `nil` when the argument is not a value-pinned scalar key so the caller can fall
1803
- # through to the projection answer.
1868
+ # key), or `nil` so the caller falls through to the projection answer.
1804
1869
  #
1805
1870
  # A key outside `pairs` only resolves to `Constant[nil]` on a CLOSED shape, where `pairs` is the
1806
1871
  # whole key set and the runtime read provably returns nil. On an `:open` shape the key set is by
1807
1872
  # definition not exhausted, so an undeclared key is *unknown*, not absent — it may hold a value
1808
1873
  # of any type. Typing it `Constant[nil]` there would put `call.undefined-method` on the next line
1809
1874
  # of correct code (`payload[:undeclared].upcase`), so the open case answers `untyped`.
1875
+ #
1876
+ # A union of value-pinned keys that the shape all declares answers the union of their steps, which
1877
+ # is exact. Any other key is computed — including a union with an undeclared member, whose per-member
1878
+ # answer on a stale shape is the same lone `Constant[nil]` the empty-shape rule below refuses. On a
1879
+ # closed shape a computed key may match any declared key or none, so the step is every value plus
1880
+ # `Constant[nil]`. Deferring it instead let the projection answer the value union alone — nil-free,
1881
+ # since `RbsDispatch` reads past `Hash#[]`'s `%a{implicitly-returns-nil}` — and
1882
+ # `o = { a: 1 }; o[name] == 1` folded always-truthy. An open shape still defers: its undeclared keys
1883
+ # may hold any value. So does the empty shape: a hash read by a computed key while it looks empty is
1884
+ # one filled where the analysis did not see — a loop body's earlier iteration
1885
+ # (`h = {}; xs.each { |x| h[x] = h[x] ? h[x] + 1 : 1 }`), a cache (`(@cache ||= {})[key]`), an
1886
+ # `instance_eval`ed file — and answering `Constant[nil]` there folds its own guard always-falsey.
1810
1887
  def hash_dig_step(shape, arg)
1811
- return nil unless arg.is_a?(Type::Constant)
1888
+ keys = literal_keys(arg)
1889
+ if keys && (keys.size == 1 || keys.all? { |key| shape.pairs.key?(key) })
1890
+ return Type::Combinator.union(*keys.map { |key| static_key_step(shape, key) })
1891
+ end
1892
+ return nil if shape.open? || shape.pairs.empty?
1812
1893
 
1813
- key = arg.value
1814
- return nil unless static_shape_key?(key)
1894
+ Type::Combinator.union(*shape.pairs.values, Type::Combinator.constant_of(nil))
1895
+ end
1896
+
1897
+ # The value-pinned keys `arg` can take — one for a scalar `Constant`, one per member for a union of
1898
+ # them — or `nil` when it can take any other value.
1899
+ def literal_keys(arg)
1900
+ members = arg.is_a?(Type::Union) ? arg.members : [arg]
1901
+ return nil unless members.all? { |member| member.is_a?(Type::Constant) && static_shape_key?(member.value) }
1815
1902
 
1903
+ members.map(&:value)
1904
+ end
1905
+
1906
+ def static_key_step(shape, key)
1816
1907
  if shape.pairs.key?(key)
1817
1908
  value = shape.pairs[key]
1818
1909
  return value unless shape.optional_key?(key)
@@ -1825,26 +1916,10 @@ module Rigor
1825
1916
  Type::Combinator.constant_of(nil)
1826
1917
  end
1827
1918
 
1828
- def optional_key_step?(shape, arg)
1829
- return false unless arg.is_a?(Type::Constant)
1830
-
1831
- shape.optional_key?(arg.value)
1832
- end
1833
-
1834
- # Whether the key is provably ABSENT — which only a closed shape can establish. An open shape
1835
- # permits keys outside `pairs`, so a key it does not declare is unknown rather than missing, and
1836
- # `fetch` must not be deferred on the theory that Ruby would raise `KeyError`: the key may well
1837
- # be there. {#hash_dig_step} has already answered `untyped` for that case.
1838
- def missing_key_step?(shape, arg)
1839
- return false unless arg.is_a?(Type::Constant)
1840
- return false if shape.open?
1841
-
1842
- !shape.pairs.key?(arg.value)
1843
- end
1844
-
1845
1919
  # `shape.values_at(:a, :b, ...)` with a list of static keys. Returns a `Tuple` whose per-position
1846
1920
  # values are the per-key value types (`Constant[nil]` for missing keys, mirroring Ruby's runtime
1847
- # behaviour). Falls through when any argument is not a value-pinned scalar key.
1921
+ # behaviour). A computed key takes the nil-bearing step {#hash_dig_step} gives it; the call falls
1922
+ # through when any step does (a computed key on an open shape).
1848
1923
  def hash_values_at(shape, _method_name, args)
1849
1924
  return nil if args.empty?
1850
1925
 
@@ -364,7 +364,7 @@ module Rigor
364
364
  members: widen_unowned_emptiness(current.members.merge(member => assigned_type)),
365
365
  class_name: current.class_name
366
366
  )
367
- scope.with_local(receiver.name, rebound)
367
+ scope.with_mutated_local(receiver.name, rebound)
368
368
  end
369
369
  end
370
370
  end