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
@@ -5,9 +5,11 @@ 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"
8
9
  require_relative "mutation_rejoin"
9
10
  require_relative "receiver_alias"
10
11
  require_relative "refinement_mutation"
12
+ require_relative "rewrite_mutation"
11
13
  require_relative "string_mutation"
12
14
 
13
15
  module Rigor
@@ -52,6 +54,8 @@ module Rigor
52
54
  #
53
55
  # - `arr.<mutator>(...)` where `arr` is a local variable.
54
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}).
55
59
  # - a receiver that *selects* among such variables rather than naming one —
56
60
  # `(kind == :required ? required : optional)[key] = info`, an `if`/`else`, `||` / `&&`. Every
57
61
  # variable the expression can evaluate to is a possible mutation target, so every one widens
@@ -89,19 +93,21 @@ module Rigor
89
93
  ].to_set.freeze
90
94
 
91
95
  # Hash mutators that invalidate a `HashShape` carrier. Same principle as `ARRAY_MUTATORS`:
92
- # 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.
93
99
  HASH_MUTATORS = %i[
94
100
  []= store
95
- delete delete_if reject! select! filter! keep_if
101
+ shift delete delete_if reject! select! filter! keep_if
96
102
  clear compact! merge! update transform_keys! transform_values!
97
103
  replace
98
104
  ].to_set.freeze
99
105
 
100
106
  # Every method name {#widen_for_mutator} responds to — the one set a body scan asks "could an
101
107
  # in-place call on this name change its binding?" against (issue #587: the block-return
102
- # threading gate). Derived from the two tables above rather than spelled out, so the scan and
103
- # the widening it predicts cannot drift apart.
104
- SHAPE_MUTATORS = (ARRAY_MUTATORS | HASH_MUTATORS | StringMutation::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
105
111
 
106
112
  # Methods that return the receiver (or a shallow copy) and cannot mutate it. They must not
107
113
  # trigger widening or any other receiver-fact invalidation. The list is intentionally
@@ -113,9 +119,7 @@ module Rigor
113
119
 
114
120
  # True when `method_name` is a pure self-returner that must
115
121
  # not invalidate the receiver's facts.
116
- def pure_self_returner?(method_name)
117
- PURE_SELF_RETURNERS.include?(method_name)
118
- end
122
+ def pure_self_returner?(method_name) = PURE_SELF_RETURNERS.include?(method_name)
119
123
 
120
124
  # Returns a scope with the call's receiver widened, for every variable the receiver expression
121
125
  # can evaluate to ({ReceiverAlias.candidates}) whose current binding is a literal-shape carrier
@@ -141,13 +145,9 @@ module Rigor
141
145
  def joinable_receiver?(receiver, scope)
142
146
  return false if receiver.nil?
143
147
 
144
- ReceiverAlias.candidates(receiver).any? do |read|
145
- current =
146
- case read
147
- when Prism::LocalVariableReadNode then scope.local(read.name)
148
- when Prism::InstanceVariableReadNode then scope.ivar(read.name)
149
- end
150
- shape_carrier?(current)
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)))
151
151
  end
152
152
  end
153
153
 
@@ -172,11 +172,12 @@ module Rigor
172
172
  end
173
173
  end
174
174
 
175
- # Widens every variable `receiver` can evaluate to, against `method_name`'s mutator table.
175
+ # Widens every variable `receiver` can evaluate to ({ReceiverAlias.mutated_reads}), against `method_name`'s
176
+ # mutator table.
176
177
  def widen_receiver_aliases(receiver, method_name, current_scope, arg_types: NO_ARG_TYPES)
177
178
  return current_scope if receiver.nil?
178
179
 
179
- ReceiverAlias.candidates(receiver).reduce(current_scope) do |acc, read|
180
+ ReceiverAlias.mutated_reads(receiver).reduce(current_scope) do |acc, read|
180
181
  widen_alias_read(method_name, read, acc, arg_types: arg_types)
181
182
  end
182
183
  end
@@ -231,9 +232,10 @@ module Rigor
231
232
  return scope if receiver.nil?
232
233
 
233
234
  ReceiverAlias.candidates(receiver).reduce(scope) do |acc, read|
234
- # A block-local read (`depth == 0`) is not a capture of the outer scope, so widening its
235
- # name against the OUTER scope would hit an unrelated same-named binding.
236
- 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)
237
239
 
238
240
  # `values: :keep` — this is the block-capture path, and the slice-C content join that runs
239
241
  # after it re-adds the appended values' types onto the widened seed, so the seed's own value
@@ -243,24 +245,30 @@ module Rigor
243
245
  end
244
246
  end
245
247
 
246
- # The local and ivar arms differ only in which pair of `Scope` accessors they use, so they are read out
247
- # of one table rather than written twice: a carrier the widening responds to must not be widened on one
248
- # kind of binding and left on the other.
249
- ALIAS_ACCESSORS = {
250
- Prism::LocalVariableReadNode => %i[local with_local],
251
- Prism::InstanceVariableReadNode => %i[ivar with_ivar]
252
- }.freeze
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
+ })
253
260
  private_constant :ALIAS_ACCESSORS
254
261
 
255
262
  def widen_alias_read(method_name, read, scope, values: :widen, arg_types: NO_ARG_TYPES)
256
263
  getter, builder = ALIAS_ACCESSORS[read.class]
257
264
  return scope if getter.nil?
258
265
 
259
- current = scope.public_send(getter, read.name)
266
+ name = ReceiverAlias.read_name(read)
267
+ current = scope.public_send(getter, name)
260
268
  widened = widen_for_mutator(current, method_name, values: values, arg_types: arg_types)
261
269
  return scope if widened.nil?
262
270
 
263
- MutationRejoin.rebind(scope, builder, read.name, widened, pre_state: current, kind: getter)
271
+ MutationRejoin.rebind(scope, builder, name, widened, pre_state: current, kind: getter)
264
272
  end
265
273
 
266
274
  # Mutators that can land a NEW value in an EXISTING slot, falsifying that slot's value pinning
@@ -277,6 +285,7 @@ module Rigor
277
285
  # nominal, whose element set is a claim this seam may not grow — see {MutationRejoin.regrowable_carrier?}).
278
286
  def widen_for_mutator(type, method_name, values: :widen, arg_types: NO_ARG_TYPES)
279
287
  values = :keep unless VALUE_REWRITING_MUTATORS.include?(method_name)
288
+ values = RewriteMutation.values_mode(type, method_name, values)
280
289
 
281
290
  return nil if type.nil?
282
291
 
@@ -287,7 +296,7 @@ module Rigor
287
296
 
288
297
  join_added_elements(widen_tuple(type, values: values), method_name, arg_types, type.elements)
289
298
  when Type::HashShape
290
- return nil unless HASH_MUTATORS.include?(method_name)
299
+ return HashLookupMutation.widen_shape(type, method_name) unless HASH_MUTATORS.include?(method_name)
291
300
 
292
301
  join_added_pairs(widen_hash_shape(type, values: values), method_name, arg_types,
293
302
  ContentJoin.hash_shape_key_values(type))
@@ -330,10 +339,12 @@ module Rigor
330
339
  end
331
340
 
332
341
  # True when the mutator's own class stands for `member` — the absorbed side of
333
- # {ContentJoin.array_residue} / {ContentJoin.hash_residue}, whichever table names the call.
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.
334
344
  def mutation_carrier?(member, method_name)
335
345
  (ARRAY_MUTATORS.include?(method_name) && ContentJoin.array_residue(member).empty?) ||
336
- (HASH_MUTATORS.include?(method_name) && ContentJoin.hash_residue(member).empty?) ||
346
+ ((HASH_MUTATORS.include?(method_name) || HashLookupMutation::MUTATORS.include?(method_name)) &&
347
+ ContentJoin.hash_residue(member).empty?) ||
337
348
  (StringMutation::MUTATORS.include?(method_name) && StringMutation.constant?(member))
338
349
  end
339
350
 
@@ -360,7 +371,11 @@ module Rigor
360
371
  # **The join records what it saw; it never CLOSES the parameter.** That is
361
372
  # {#gradual_floor}'s job, and the reason is the seam this method sits on rather than anything
362
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.
363
377
  def join_added_elements(widened, method_name, arg_types, seed_elements)
378
+ widened = RewriteMutation.arm(widened, method_name)
364
379
  return widened unless ContentJoin::ARRAY_CONTENT_ADDERS.include?(method_name)
365
380
 
366
381
  added = value_pin_widened(ContentJoin.array_added_elements(method_name, arg_types))
@@ -395,7 +410,10 @@ module Rigor
395
410
  # BLOCK path (ADR-56 slice C) on the other side: `content_writeback_block_captures` scans the
396
411
  # whole body and joins every mutator call in it before writing back, so its evidence IS
397
412
  # complete for that body and its precise join stays justified — `acc = []; xs.each { |x|
398
- # 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).
399
417
  #
400
418
  # `Array[Integer | Dynamic[top]]` still does everything issue #560 needs: a union carrying
401
419
  # `Dynamic` cannot constant-fold, so every stale always-falsey the join was written to remove
@@ -406,12 +424,17 @@ module Rigor
406
424
 
407
425
  # The Hash-side twin of {#join_added_elements}: `h[k] = v` / `h.store(k, v)` join the stored
408
426
  # key and value into the widened `Hash[K, V]` carrier, each admitted against its OWN side's
409
- # 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}.
410
429
  def join_added_pairs(widened, method_name, arg_types, seed_pairs)
430
+ widened = RewriteMutation.arm(widened, method_name)
411
431
  return widened unless ContentJoin::HASH_CONTENT_ADDERS.include?(method_name)
412
432
  return widened if arg_types.size < 2
413
433
 
414
- 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])
415
438
  return widened unless added.size == 2
416
439
 
417
440
  seed_keys, seed_values = seed_pairs
@@ -493,7 +516,7 @@ module Rigor
493
516
  # here, so a refinement joins the mutator's added content on exactly the terms the `Tuple` /
494
517
  # `HashShape` arms above do (issue #936, ADR-56 WD2.9's deferred branch).
495
518
  def widen_difference(difference, method_name, arg_types: NO_ARG_TYPES)
496
- RefinementMutation.widen(difference, method_name) do |base|
519
+ RefinementMutation.widen(difference, method_name, arg_types) do |base|
497
520
  if base.class_name == "Array"
498
521
  ARRAY_MUTATORS.include?(method_name) &&
499
522
  join_added_elements(base, method_name, arg_types, ContentJoin.collection_element_types(base))
@@ -522,16 +545,17 @@ module Rigor
522
545
  Type::Combinator.nominal_of("Array", type_args: [element_type])
523
546
  end
524
547
 
525
- # `HashShape` (closed or open) → `Nominal[Hash, [Kunion, Vunion]]`. Empty / extra-keys-only
526
- # shapes degrade to a fully-untyped Hash. Values widen their pinning the same way
527
- # {#widen_tuple}'s elements do (issue #560): `opts = {headers: false}` then
528
- # `opts[:encoding] = v` must not keep `false` as the whole value bound — redmine's
529
- # `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.
530
556
  def widen_hash_shape(shape, values: :widen)
531
- if shape.pairs.empty?
532
- return Type::Combinator.nominal_of("Hash",
533
- type_args: [Type::Combinator.untyped,
534
- 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])
535
559
  end
536
560
 
537
561
  key_type = key_union_for(shape.pairs.keys)
@@ -546,9 +570,7 @@ module Rigor
546
570
  # `key_union_for` is delegated rather than duplicated: {#widen_hash_shape} and
547
571
  # `ContentJoin.hash_shape_key_values` must map a literal key set the SAME way, or a widened
548
572
  # carrier and the join that reads it back disagree about the key parameter.
549
- def key_union_for(keys)
550
- ContentJoin.key_union_for(keys)
551
- end
573
+ def key_union_for(keys) = ContentJoin.key_union_for(keys)
552
574
  end
553
575
  end
554
576
  end