rigortype 0.3.9 → 0.4.1

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 (407) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  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/08-understanding-errors.md +3 -2
  14. data/docs/handbook/10-sorbet.md +9 -10
  15. data/docs/handbook/11-sig-gen.md +454 -13
  16. data/docs/install.md +87 -15
  17. data/docs/manual/02-cli-reference.md +65 -4
  18. data/docs/manual/03-configuration.md +12 -0
  19. data/docs/manual/04-diagnostics.md +4 -0
  20. data/docs/manual/07-plugins.md +1 -1
  21. data/docs/manual/08-skills.md +4 -3
  22. data/docs/manual/10-mcp-server.md +3 -2
  23. data/docs/manual/12-caching.md +43 -4
  24. data/docs/manual/14-rails-quickstart.md +19 -11
  25. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  26. data/docs/manual/19-effect-labels.md +10 -0
  27. data/docs/manual/plugins/README.md +7 -0
  28. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  29. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  30. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  31. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  32. data/docs/manual/plugins/rigor-activejob.md +7 -0
  33. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  34. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  35. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  36. data/docs/manual/plugins/rigor-grape.md +106 -0
  37. data/docs/manual/plugins/rigor-graphql.md +23 -2
  38. data/docs/manual/plugins/rigor-pundit.md +8 -1
  39. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  40. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  41. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  42. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  43. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  44. data/exe/rigor +19 -4
  45. data/lib/rigor/analysis/baseline.rb +1 -1
  46. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  47. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  48. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  49. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  50. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  51. data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
  52. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  53. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  54. data/lib/rigor/analysis/check_rules.rb +638 -136
  55. data/lib/rigor/analysis/dependency_recorder.rb +222 -17
  56. data/lib/rigor/analysis/fact_store.rb +9 -0
  57. data/lib/rigor/analysis/incremental.rb +26 -0
  58. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
  59. data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
  60. data/lib/rigor/analysis/incremental_session.rb +309 -53
  61. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  62. data/lib/rigor/analysis/project_scan.rb +11 -1
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
  65. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  66. data/lib/rigor/analysis/run_cache_key.rb +46 -18
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
  69. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  70. data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
  71. data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
  72. data/lib/rigor/analysis/runner.rb +603 -54
  73. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  74. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  75. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  76. data/lib/rigor/analysis/template_unit_paths.rb +93 -0
  77. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  78. data/lib/rigor/analysis/template_units.rb +409 -0
  79. data/lib/rigor/analysis/worker_session.rb +57 -13
  80. data/lib/rigor/bleeding_edge.rb +0 -2
  81. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  82. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  83. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  84. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  85. data/lib/rigor/cache/descriptor.rb +45 -1
  86. data/lib/rigor/cache/engine_source.rb +29 -1
  87. data/lib/rigor/cache/file_digest.rb +28 -0
  88. data/lib/rigor/cache/incremental_snapshot.rb +102 -8
  89. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  90. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  91. data/lib/rigor/cache/store.rb +63 -4
  92. data/lib/rigor/cache/value_digest.rb +325 -0
  93. data/lib/rigor/ci_detector.rb +1 -0
  94. data/lib/rigor/cli/check_command.rb +83 -13
  95. data/lib/rigor/cli/doc_links.rb +1 -1
  96. data/lib/rigor/cli/docs_command.rb +4 -4
  97. data/lib/rigor/cli/doctor_command.rb +1 -1
  98. data/lib/rigor/cli/effects_report.rb +2 -2
  99. data/lib/rigor/cli/plugin_command.rb +3 -3
  100. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  101. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  102. data/lib/rigor/cli/skill_command.rb +1 -1
  103. data/lib/rigor/cli/skill_describe.rb +0 -1
  104. data/lib/rigor/cli/type_of_command.rb +19 -5
  105. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  106. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  107. data/lib/rigor/cli/unused_command.rb +3 -3
  108. data/lib/rigor/cli.rb +21 -3
  109. data/lib/rigor/config_audit.rb +3 -2
  110. data/lib/rigor/config_path.rb +29 -0
  111. data/lib/rigor/configuration/severity_profile.rb +24 -3
  112. data/lib/rigor/configuration.rb +71 -7
  113. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  114. data/lib/rigor/effects/attribution.rb +11 -2
  115. data/lib/rigor/effects/callee_rule.rb +368 -0
  116. data/lib/rigor/effects/catalog.rb +7 -4
  117. data/lib/rigor/effects/collector.rb +11 -5
  118. data/lib/rigor/effects/config_envelopes.rb +9 -2
  119. data/lib/rigor/effects/definition_context.rb +179 -0
  120. data/lib/rigor/effects/effect_table.rb +11 -3
  121. data/lib/rigor/effects/envelope_check.rb +1 -1
  122. data/lib/rigor/effects/envelope_index.rb +15 -0
  123. data/lib/rigor/effects/file_collection.rb +59 -5
  124. data/lib/rigor/effects/framework_units.rb +1 -1
  125. data/lib/rigor/effects/identity.rb +16 -0
  126. data/lib/rigor/effects/local_ownership.rb +38 -12
  127. data/lib/rigor/effects/method_key.rb +21 -0
  128. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  129. data/lib/rigor/effects/plugin_facts.rb +49 -37
  130. data/lib/rigor/effects/propagator.rb +295 -16
  131. data/lib/rigor/effects/registry.rb +1 -1
  132. data/lib/rigor/effects/scanner.rb +121 -72
  133. data/lib/rigor/effects/signature_sources.rb +1 -1
  134. data/lib/rigor/effects/snapshot.rb +2 -1
  135. data/lib/rigor/effects/summary.rb +27 -4
  136. data/lib/rigor/effects/unit_scan.rb +385 -30
  137. data/lib/rigor/effects/visibility.rb +101 -0
  138. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  139. data/lib/rigor/environment/lockfile_resolver.rb +27 -1
  140. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  141. data/lib/rigor/environment/member_consistency.rb +298 -0
  142. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  143. data/lib/rigor/environment/rbs_loader.rb +400 -134
  144. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  145. data/lib/rigor/environment.rb +104 -96
  146. data/lib/rigor/hashing/xxh3.rb +264 -0
  147. data/lib/rigor/inference/acceptance.rb +139 -9
  148. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  149. data/lib/rigor/inference/block_call_timing.rb +338 -0
  150. data/lib/rigor/inference/block_parameter_binder.rb +88 -26
  151. data/lib/rigor/inference/block_repetition.rb +71 -0
  152. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  153. data/lib/rigor/inference/budget_trace.rb +2 -1
  154. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  155. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  156. data/lib/rigor/inference/captured_locals.rb +393 -15
  157. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  158. data/lib/rigor/inference/content_join.rb +200 -27
  159. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  160. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  161. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  162. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  163. data/lib/rigor/inference/declaration_walk.rb +7 -0
  164. data/lib/rigor/inference/def_node_resolver.rb +3 -2
  165. data/lib/rigor/inference/def_return_typer.rb +11 -7
  166. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  167. data/lib/rigor/inference/definer_resolution.rb +266 -0
  168. data/lib/rigor/inference/element_read_widening.rb +22 -10
  169. data/lib/rigor/inference/error_info.rb +196 -0
  170. data/lib/rigor/inference/expression_typer.rb +1665 -538
  171. data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
  172. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  173. data/lib/rigor/inference/global_write_census.rb +239 -0
  174. data/lib/rigor/inference/guard_rebinding.rb +488 -0
  175. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  176. data/lib/rigor/inference/index_write_widening.rb +16 -3
  177. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  178. data/lib/rigor/inference/jump_targets.rb +82 -0
  179. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  180. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  181. data/lib/rigor/inference/last_line.rb +340 -0
  182. data/lib/rigor/inference/last_status.rb +144 -0
  183. data/lib/rigor/inference/macro_block_self_type.rb +179 -17
  184. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  185. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  186. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  187. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  188. data/lib/rigor/inference/match_rebinding.rb +392 -0
  189. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  190. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  191. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  192. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  193. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  194. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  195. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  196. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  197. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  198. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
  199. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  200. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  201. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  203. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  204. data/lib/rigor/inference/module_function_state.rb +213 -0
  205. data/lib/rigor/inference/multi_target_binder.rb +345 -52
  206. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  207. data/lib/rigor/inference/mutation_widening.rb +70 -48
  208. data/lib/rigor/inference/narrowing.rb +674 -124
  209. data/lib/rigor/inference/operand_effects.rb +167 -0
  210. data/lib/rigor/inference/operand_walk.rb +88 -0
  211. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  212. data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
  213. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  214. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  215. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  216. data/lib/rigor/inference/receiver_alias.rb +90 -1
  217. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  218. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  219. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  220. data/lib/rigor/inference/return_barrier.rb +54 -0
  221. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  222. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  223. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  224. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  225. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  226. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  227. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
  228. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  229. data/lib/rigor/inference/scope_indexer.rb +5913 -701
  230. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  231. data/lib/rigor/inference/statement_evaluator.rb +3249 -497
  232. data/lib/rigor/inference/stored_block_call.rb +54 -0
  233. data/lib/rigor/inference/string_mutation.rb +44 -7
  234. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  235. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  236. data/lib/rigor/language_server/debouncer.rb +0 -1
  237. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  238. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  239. data/lib/rigor/language_server/project_context.rb +5 -3
  240. data/lib/rigor/mcp/server.rb +2 -1
  241. data/lib/rigor/plugin/base.rb +196 -13
  242. data/lib/rigor/plugin/box_probe.rb +91 -0
  243. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  244. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  245. data/lib/rigor/plugin/io_boundary.rb +82 -4
  246. data/lib/rigor/plugin/isolation.rb +1 -1
  247. data/lib/rigor/plugin/loader.rb +2 -1
  248. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  249. data/lib/rigor/plugin/manifest.rb +71 -10
  250. data/lib/rigor/plugin/registry.rb +35 -1
  251. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  252. data/lib/rigor/plugin/template_unit.rb +196 -0
  253. data/lib/rigor/plugin/trust_policy.rb +3 -3
  254. data/lib/rigor/plugin.rb +1 -0
  255. data/lib/rigor/protection/discovery_seed.rb +20 -9
  256. data/lib/rigor/protection/kill_signature.rb +0 -1
  257. data/lib/rigor/protection/mutation_cache.rb +4 -5
  258. data/lib/rigor/rbs_extended.rb +27 -0
  259. data/lib/rigor/reflection/constant_ancestors.rb +146 -0
  260. data/lib/rigor/reflection/constant_path.rb +41 -13
  261. data/lib/rigor/reflection.rb +73 -107
  262. data/lib/rigor/scope/discovery_index.rb +351 -2
  263. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  264. data/lib/rigor/scope/resolution_chain.rb +884 -0
  265. data/lib/rigor/scope.rb +981 -198
  266. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  267. data/lib/rigor/sig_gen/classification.rb +22 -5
  268. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  269. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  270. data/lib/rigor/sig_gen/generator.rb +580 -47
  271. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  272. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  273. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  274. data/lib/rigor/sig_gen/renderer.rb +124 -9
  275. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  276. data/lib/rigor/sig_gen/write_result.rb +19 -3
  277. data/lib/rigor/sig_gen/writer.rb +166 -35
  278. data/lib/rigor/sig_gen.rb +3 -0
  279. data/lib/rigor/signature_path_audit.rb +1 -1
  280. data/lib/rigor/source/node_walker.rb +0 -3
  281. data/lib/rigor/source/parameter_envelope.rb +72 -0
  282. data/lib/rigor/source.rb +1 -0
  283. data/lib/rigor/type/combinator.rb +112 -12
  284. data/lib/rigor/type/difference.rb +1 -0
  285. data/lib/rigor/type/hash_shape.rb +1 -1
  286. data/lib/rigor/type/refined.rb +1 -0
  287. data/lib/rigor/version.rb +1 -1
  288. data/lib/rigor.rb +1 -0
  289. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  290. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  291. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  292. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  293. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  294. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  295. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  296. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  297. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  298. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  299. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  300. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  301. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  302. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  303. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  304. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  305. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  306. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  307. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  308. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  309. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  310. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  311. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  312. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  313. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  314. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  315. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  316. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  317. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  318. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  319. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  320. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
  321. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  322. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  323. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  324. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  325. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  326. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  327. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  328. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  329. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  330. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  331. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  332. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  333. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  334. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  335. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  336. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  337. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  338. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  339. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  340. data/sig/rigor/analysis/baseline.rbs +69 -7
  341. data/sig/rigor/analysis/fact_store.rbs +1 -1
  342. data/sig/rigor/analysis/project_scan.rbs +74 -0
  343. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  344. data/sig/rigor/effects/effect_table.rbs +60 -0
  345. data/sig/rigor/effects/envelope.rbs +97 -0
  346. data/sig/rigor/effects/envelope_index.rbs +33 -0
  347. data/sig/rigor/effects/file_collection.rbs +81 -0
  348. data/sig/rigor/effects/label.rbs +26 -0
  349. data/sig/rigor/effects/label_set.rbs +46 -0
  350. data/sig/rigor/effects/method_key.rbs +24 -0
  351. data/sig/rigor/effects/origin.rbs +50 -0
  352. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  353. data/sig/rigor/effects/registry.rbs +68 -0
  354. data/sig/rigor/effects/summary.rbs +50 -0
  355. data/sig/rigor/effects/taint_cause.rbs +12 -0
  356. data/sig/rigor/environment.rbs +14 -10
  357. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  358. data/sig/rigor/inference.rbs +6 -4
  359. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  360. data/sig/rigor/plugin/base.rbs +30 -7
  361. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  362. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  363. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  364. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  365. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  366. data/sig/rigor/plugin/loader.rbs +3 -3
  367. data/sig/rigor/plugin/manifest.rbs +50 -11
  368. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  369. data/sig/rigor/plugin/registry.rbs +62 -1
  370. data/sig/rigor/plugin.rbs +1 -1
  371. data/sig/rigor/rbs_extended.rbs +1 -1
  372. data/sig/rigor/reflection.rbs +7 -6
  373. data/sig/rigor/scope.rbs +177 -20
  374. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  375. data/sig/rigor/source.rbs +4 -4
  376. data/sig/rigor/testing.rbs +10 -4
  377. data/sig/rigor/type.rbs +6 -0
  378. data/sig/rigor.rbs +38 -20
  379. data/skills/rigor-ask/SKILL.md +8 -5
  380. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  381. data/skills/rigor-ci-setup/SKILL.md +17 -21
  382. data/skills/rigor-doctor/SKILL.md +24 -22
  383. data/skills/rigor-doctor/references/01-checks.md +97 -33
  384. data/skills/rigor-editor-setup/SKILL.md +6 -4
  385. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  386. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  387. data/skills/rigor-next-steps/SKILL.md +4 -2
  388. data/skills/rigor-plugin-author/SKILL.md +19 -23
  389. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  390. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  391. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  392. data/skills/rigor-plugin-review/SKILL.md +6 -4
  393. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  394. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  395. data/skills/rigor-project-init/SKILL.md +46 -18
  396. data/skills/rigor-project-init/references/01-detect.md +13 -9
  397. data/skills/rigor-project-init/references/02-configure.md +40 -10
  398. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  399. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  400. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  401. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  402. data/skills/rigor-type-oracle/SKILL.md +4 -6
  403. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  404. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  405. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  406. data/skills/rigor-upgrade/SKILL.md +13 -8
  407. metadata +132 -1
@@ -21,12 +21,15 @@ require_relative "method_dispatcher/iterator_dispatch"
21
21
  require_relative "method_dispatcher/reduce_folding"
22
22
  require_relative "method_dispatcher/block_folding"
23
23
  require_relative "method_dispatcher/array_to_h_folding"
24
+ require_relative "method_dispatcher/hash_transform_keys_folding"
24
25
  require_relative "method_dispatcher/file_folding"
25
26
  require_relative "method_dispatcher/shellwords_folding"
26
27
  require_relative "method_dispatcher/math_folding"
27
28
  require_relative "method_dispatcher/random_folding"
28
29
  require_relative "method_dispatcher/time_folding"
29
30
  require_relative "method_dispatcher/regexp_folding"
31
+ require_relative "method_dispatcher/match_data_folding"
32
+ require_relative "method_dispatcher/process_folding"
30
33
  require_relative "method_dispatcher/cgi_folding"
31
34
  require_relative "method_dispatcher/uri_folding"
32
35
  require_relative "method_dispatcher/set_folding"
@@ -57,7 +60,6 @@ module Rigor
57
60
 
58
61
  # @param receiver_type — type of the receiver expression, or
59
62
  # `nil` for an implicit-self call.
60
- # @param arg_types — positional argument types.
61
63
  # @param block_type — inferred return type of the
62
64
  # accompanying `do ... end` / `{ ... }` block (Slice 6 phase C
63
65
  # sub-phase 2). When non-nil, the dispatcher prefers an
@@ -94,9 +96,12 @@ module Rigor
94
96
  result
95
97
  end
96
98
 
97
- def resolve(receiver_type:, method_name:, arg_types:, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
+ # `plugin_typed_sink` is internal bookkeeping for the composite-receiver tier below: when it is an
100
+ # Array, an answering plugin tier PUSHES the call node instead of recording it on the scope, and
101
+ # the tier commits the buffer only if it goes on to answer. See {#try_composite_receiver}.
102
+ def resolve(receiver_type:, method_name:, arg_types:, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
98
103
  block_type: nil, environment: nil,
99
- call_node: nil, scope: nil)
104
+ call_node: nil, scope: nil, plugin_typed_sink: nil)
100
105
  return nil if receiver_type.nil?
101
106
 
102
107
  # Build the call context once and thread it — unchanged — through every tier
@@ -126,7 +131,22 @@ module Rigor
126
131
  # RBS's, and `call.undefined-method` reads that record instead of re-deciding from the receiver's
127
132
  # (possibly partial) signature. Recorded for EVERY plugin answer, dynamic or precise: the
128
133
  # incoherence the record closes is about which subsystem typed the call, not about the type.
129
- scope&.record_plugin_typed_call(call_node)
134
+ #
135
+ # Under a composite-receiver projection the record is BUFFERED instead (#1101). It is the one
136
+ # recording in this method that is set-only — `Scope#plugin_typed_calls` has no removal, and
137
+ # `CheckRules#call_site_exempt?` / `#source_arity_envelope` read it as an exemption — so a
138
+ # member a plugin answers beside a member that declines would exempt a call this dispatcher
139
+ # never typed, silencing `call.undefined-method` and `call.wrong-arity` on a firing that stands.
140
+ # `call_site_exempt?` runs ahead of the receiver-shape branch, so the union rule is covered by
141
+ # it too and the suppression is reachable end to end — the arms are measured in
142
+ # `spec/integration/composite_receiver_plugin_typed_suppression_spec.rb`. The
143
+ # `record_dynamic_origin` writes below and in the tiers that follow need no such care: that
144
+ # table is last-wins and the caller's fail-soft widening overwrites it.
145
+ if plugin_typed_sink
146
+ plugin_typed_sink << call_node
147
+ else
148
+ scope&.record_plugin_typed_call(call_node)
149
+ end
130
150
  if plugin_result.is_a?(Type::Dynamic)
131
151
  scope&.record_dynamic_origin(call_node, DynamicOrigin::FRAMEWORK_DSL_BOUNDARY)
132
152
  end
@@ -255,7 +275,13 @@ module Rigor
255
275
  if fallback_result.is_a?(Type::Dynamic)
256
276
  scope&.record_dynamic_origin(call_node, DynamicOrigin::INFERRED_RETURN_UNTYPED)
257
277
  end
258
- fallback_result
278
+ return fallback_result if fallback_result
279
+
280
+ # Issue #1101 — composite-receiver projection, the last tier. Every tier above needs a
281
+ # receiver it can name; a `Union` or a `Difference` is not one, so a composite receiver whose
282
+ # members WOULD each resolve still lands `Dynamic[top]`. Retry per projected member through
283
+ # this same chain. See {#try_composite_receiver} for the projection rule and why it sits here.
284
+ try_composite_receiver(context)
259
285
  end
260
286
 
261
287
  # ADR-100 WD4 — records the transitive `-> void` provenance when the called method is a
@@ -451,6 +477,52 @@ module Rigor
451
477
  FlowContribution::Merger.merge(contributions).return_type
452
478
  end
453
479
 
480
+ # Issue #963 item 2 — the MEMBERSHIP half of the two plugin tiers, for #618's own-method veto.
481
+ #
482
+ # `ExpressionTyper#try_local_def_dispatch` has to decide whether the call's own `self` answers the
483
+ # name before a same-named top-level `def` may bind, and a plugin-supplied member is an answer Ruby
484
+ # really dispatches to: a model's column reader, an association, a scope, a macro-synthesised
485
+ # reader. The veto asked neither tier, so a top-level `def name` bound ahead of the member and
486
+ # typed the read as the def's `nil` — #618's false positive at a source the veto could not see.
487
+ #
488
+ # The question is asked HERE, of the same two tiers `resolve` consults and in the same order, rather
489
+ # than reconstructed in the veto: a name this reports as answered is a name the dispatch really would
490
+ # resolve, and no plugin knowledge moves into the engine's veto. The tiers are the gated
491
+ # `dynamic_return` walk and the ADR-16 synthetic-method index (Tier C `heredoc_templates` emissions
492
+ # today; the Tier B `Plugin::Macro::TraitRegistry` explosions share that table but cannot populate it
493
+ # until #476 threads a real environment into the pre-pass that builds it, so no trait member reaches
494
+ # this predicate in production yet — it will when #476 lands, with no change here).
495
+ #
496
+ # It is a predicate, not a type: the veto's job is to stop the top-level bind, after which the call
497
+ # falls through to the ordinary dispatch chain and the tier that answered here answers there too. The
498
+ # walk is not on the hot path — it runs only once `Scope#bindable_top_level_def_for` has already
499
+ # produced a candidate, which needs a project top-level `def` of the same name.
500
+ def plugin_member_answers?(call_node:, scope:, receiver_type:, method_name:)
501
+ return false if receiver_type.nil?
502
+ return true unless try_plugin_contribution(call_node, scope, receiver_type).nil?
503
+
504
+ synthetic_method_member?(receiver_type, method_name, scope&.environment)
505
+ end
506
+
507
+ # The synthetic-method half of {#plugin_member_answers?} — the same `(class_name, method_name, kind)`
508
+ # probe {#try_synthetic_method} makes, without the return-type promotion the veto has no use for.
509
+ def synthetic_method_member?(receiver_type, method_name, environment)
510
+ index = environment&.synthetic_method_index
511
+ return false if index.nil? || index.empty?
512
+
513
+ class_name = synthetic_method_class_name(receiver_type)
514
+ return false if class_name.nil?
515
+
516
+ matches = if receiver_type.is_a?(Type::Singleton)
517
+ index.lookup_singleton(class_name, method_name)
518
+ else
519
+ index.lookup_instance(class_name, method_name)
520
+ end
521
+ !matches.empty?
522
+ rescue StandardError
523
+ false
524
+ end
525
+
454
526
  # ADR-16 synthetic-method tier. Slice 2b shipped the floor — a match short-circuits at the
455
527
  # right precedence (above dep-source / discovered / user-class-fallback; below RBS) and
456
528
  # returns `Dynamic[T]`. Slice 6 (precision promotion):
@@ -766,6 +838,7 @@ module Rigor
766
838
  "Random" => RandomFolding,
767
839
  "Time" => TimeFolding,
768
840
  "Regexp" => RegexpFolding,
841
+ "Process" => ProcessFolding,
769
842
  "CGI" => CGIFolding,
770
843
  "URI" => URIFolding,
771
844
  "Set" => SetFolding,
@@ -774,7 +847,7 @@ module Rigor
774
847
  private_constant :STDLIB_SINGLETON_FOLDERS
775
848
 
776
849
  PRECISE_TIERS_TAIL = Ractor.make_shareable([
777
- MethodFolding, ReduceFolding, ArrayToHFolding, BlockFolding
850
+ MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding
778
851
  ].freeze)
779
852
  private_constant :PRECISE_TIERS_TAIL
780
853
 
@@ -802,11 +875,8 @@ module Rigor
802
875
  return result if result
803
876
  end
804
877
 
805
- receiver = context.receiver
806
- if receiver.is_a?(Type::Singleton) && (folder = STDLIB_SINGLETON_FOLDERS[receiver.class_name])
807
- result = folder.try_dispatch(context)
808
- return result if result
809
- end
878
+ folder_result = dispatch_receiver_keyed_folder(context)
879
+ return folder_result if folder_result
810
880
 
811
881
  kernel_result = dispatch_kernel_intrinsic(context)
812
882
  return kernel_result if kernel_result
@@ -818,6 +888,19 @@ module Rigor
818
888
  nil
819
889
  end
820
890
 
891
+ # The folders the receiver's class selects, at most one per call: a stdlib singleton folder, or for a
892
+ # `MatchData` receiver the slice fold of the proven `$~` (issue #1381). Every other receiver skips them without
893
+ # a trial.
894
+ def dispatch_receiver_keyed_folder(context)
895
+ receiver = context.receiver
896
+ folder =
897
+ case receiver
898
+ when Type::Singleton then STDLIB_SINGLETON_FOLDERS[receiver.class_name]
899
+ when Type::Nominal then MatchDataFolding if receiver.class_name == "MatchData"
900
+ end
901
+ folder&.try_dispatch(context)
902
+ end
903
+
821
904
  # ADR-91 WD1 — the single dispatcher-held ownership gate for the Kernel module-function surface
822
905
  # (gate-by-held-key, the same move STDLIB_SINGLETON_FOLDERS made above). The tier runs only when
823
906
  # the method name is in the compiled `INTRINSIC_NAMES` table AND the call is attributable to
@@ -895,6 +978,123 @@ module Rigor
895
978
  end
896
979
  end
897
980
 
981
+ # Issue #1101 — the composite-receiver projection tier, the LAST thing `resolve` tries.
982
+ #
983
+ # Every tier above needs a receiver it can *name*: `receiver_descriptor`, the discovered-method
984
+ # lookup, the dependency-source and synthesized-stub tiers and the user-class fallback all switch
985
+ # on `Nominal` / `Singleton` / a member carrier. A `Union` or a `Difference` is none of those, so a
986
+ # composite receiver whose members would each resolve perfectly well still fell through the whole
987
+ # chain and landed `Dynamic[top]`.
988
+ #
989
+ # `RbsDispatch` already distributes over a `Union` (and erases a `Difference` to its base, #533),
990
+ # but it does so INSIDE one tier, so a member that needs a LOWER tier — a user class with no RBS,
991
+ # a discovered `def`, an opt-in gem — makes the whole union decline. That is the corpus shape:
992
+ # rigor-actionpack types `params[:k]` as `ActionController::Parameters | nil`, the `Parameters` arm
993
+ # resolves only through the user-class fallback, and so `params[:k].present?` / `#==` / `#blank?` /
994
+ # `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the named-pair
995
+ # census drains 137 sites on redmine and 56 on mastodon, and `rigor check` reports the same
996
+ # diagnostics on both arms across eight survey targets.
997
+ #
998
+ # **Why the bottom and not next to `RbsDispatch`.** Sitting here, the tier can only ever replace a
999
+ # `Dynamic[top]` the typer was about to produce — the same ADR-5 argument {UniversalObjectDispatch}
1000
+ # makes. Hoisting the projection above `RbsDispatch` would change answers that resolve today, which
1001
+ # is a different change with a different measurement.
1002
+ #
1003
+ # **The projection rule** (`receiver_projection` below; the normative statement lives in
1004
+ # [`inference-engine.md`](docs/internal-spec/inference-engine.md) § "Composite receivers"):
1005
+ #
1006
+ # - `Union[A, B, …]` projects to its members. Each is dispatched through this same chain and the
1007
+ # results are unioned; if ANY member declines, the whole tier declines. Never a partial answer —
1008
+ # a union whose members disagree about whether the method exists is exactly the case where an
1009
+ # answer would be a guess.
1010
+ # - `Difference[base, nil]` projects to `base` with its nil-valued members removed. Removing `nil`
1011
+ # from a receiver cannot change method resolution at all: `NilClass` is nobody's ancestor, so
1012
+ # every remaining inhabitant looks a method up exactly as a plain `base` inhabitant does, and the
1013
+ # carrier is value-set-equal to the projection. This is `T.must(x)` (rigor-sorbet's
1014
+ # `strip_nil`), which builds `Difference[inferred(x), Constant[nil]]` — over a `T?` local that
1015
+ # base IS a union, which is why the two arms compose rather than being separate features.
1016
+ # - Any other `Difference` DECLINES. `A − B` for a real `B` is a claim about which inhabitants
1017
+ # survive, and the projection has nowhere to put it: dispatching on `A` re-admits exactly the
1018
+ # overrides `B` contributed, and over a union base (`(String | Integer) − Integer` is Strings
1019
+ # only) it re-admits a member the difference eliminated and unions in its answer — a WIDER type
1020
+ # than today's decline, on a shape no corpus target produces. Subtracting a class from a union
1021
+ # needs an algebra the `Difference` carrier does not have (it removes a value set, not a member),
1022
+ # so the honest answer is to keep declining until something demands it. `RbsDispatch`'s #533
1023
+ # erasure is untouched and still answers these for an RBS-known base.
1024
+ #
1025
+ # `self` binds to the PROJECTED receiver, not to the composite: the projection is the exact set of
1026
+ # inhabitants that can reach the method, so a `-> self` method (`dup`, `itself`, `tap`) answers per
1027
+ # member and the union reassembles them — `(String | Symbol)#itself` is `String | Symbol`. Nothing is
1028
+ # lost on the `Difference` arm because the projection is value-set-equal to the carrier there. This
1029
+ # falls out of re-entering `resolve` rather than needing a `self_type_override`.
1030
+ def try_composite_receiver(context)
1031
+ members = receiver_projection(context.receiver)
1032
+ return nil if members.nil? || members.size > COMPOSITE_PROJECTION_MEMBERS
1033
+
1034
+ plugin_typed = []
1035
+ results = members.map do |member|
1036
+ resolve(
1037
+ receiver_type: member, method_name: context.method_name, arg_types: context.args,
1038
+ block_type: context.block_type, environment: context.environment,
1039
+ call_node: context.call_node, scope: context.scope, plugin_typed_sink: plugin_typed
1040
+ )
1041
+ end
1042
+ return nil if results.any?(&:nil?)
1043
+
1044
+ plugin_typed.uniq.each { |node| context.scope&.record_plugin_typed_call(node) }
1045
+ Type::Combinator.union(*results)
1046
+ end
1047
+
1048
+ # An upper bound on the fan-out, because this tier re-runs the FULL dispatch chain per member.
1049
+ # Real unions are two or three members wide; a receiver wider than this is a join the engine has
1050
+ # already lost precision on, and paying N chain walks to union N answers is not worth it.
1051
+ COMPOSITE_PROJECTION_MEMBERS = 8
1052
+ private_constant :COMPOSITE_PROJECTION_MEMBERS
1053
+
1054
+ # The receivers each member of a composite carrier is dispatched as, or nil when the carrier is not
1055
+ # projectable. Every member returned MUST itself be non-composite: that is what bounds the re-entry
1056
+ # into `resolve` at one level, by construction rather than by a counter — the nested call reaches
1057
+ # this tier with a receiver it declines immediately. A normalized `Union` never holds a `Union`, so
1058
+ # the shapes the guard turns away are a `Difference` over a `Difference` (`T.must(T.must(x))`) and a
1059
+ # `Union` holding one (`c ? T.must(y) : Bar.new`, which normalizes to `(A - nil) | Bar`). Both
1060
+ # decline exactly as they did before this tier existed; neither appears in the survey corpus.
1061
+ def receiver_projection(receiver)
1062
+ members =
1063
+ case receiver
1064
+ when Type::Union then receiver.members
1065
+ when Type::Difference then difference_projection(receiver)
1066
+ end
1067
+ return nil if members.nil? || members.empty?
1068
+ return nil if members.any? { |member| member.is_a?(Type::Union) || member.is_a?(Type::Difference) }
1069
+
1070
+ members
1071
+ end
1072
+
1073
+ # `Difference[base, nil]` projects to `base` minus its nil-valued members; every other difference
1074
+ # declines. The nil filter runs over a scalar base as well as a union one, so the rule stays total:
1075
+ # `Combinator.difference` does not normalize `NilClass - nil` (or `nil - nil`) to `Bot`, and this
1076
+ # tier must not project `[NilClass]` and answer a NilClass method for a carrier with no inhabitant.
1077
+ # Nothing observable turns on it today — `RbsDispatch`'s #533 erasure sits above this tier and
1078
+ # already answers a nil-only base from the base's own descriptor — which is why the guard is here
1079
+ # rather than in `Combinator`, where it would be a normalization change with its own blast radius.
1080
+ def difference_projection(difference)
1081
+ return nil unless nil_valued_type?(difference.removed)
1082
+
1083
+ base = difference.base
1084
+ members = base.is_a?(Type::Union) ? base.members : [base]
1085
+ retained = members.reject { |member| nil_valued_type?(member) }
1086
+ return nil if retained.empty?
1087
+
1088
+ retained
1089
+ end
1090
+
1091
+ # `nil` as either carrier spells it: the value-pinned `Constant[nil]` the engine normally produces,
1092
+ # and the `Nominal[NilClass]` a widened or RBS-sourced position carries.
1093
+ def nil_valued_type?(type)
1094
+ (type.is_a?(Type::Constant) && type.value.nil?) ||
1095
+ (type.is_a?(Type::Nominal) && type.class_name == "NilClass")
1096
+ end
1097
+
898
1098
  # Slice 7 phase 8 — meta-introspection shortcuts. The default `Object#class` RBS return
899
1099
  # type is `Class`, but for a receiver of known nominal identity we can do better:
900
1100
  # `instance_of(Foo).class` is `Singleton[Foo]` (the class object itself), which downstream
@@ -1355,12 +1555,13 @@ module Rigor
1355
1555
  # method definition, or selected overload does not provide statically declared block
1356
1556
  # parameter types. Callers MUST treat the empty array as "no information"; the binder
1357
1557
  # falls back to `Dynamic[Top]` for every parameter slot in that case.
1358
- def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil)
1558
+ def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil,
1559
+ scope: nil)
1359
1560
  return [] if receiver_type.nil?
1360
1561
 
1361
1562
  context = CallContext.build(
1362
1563
  receiver: receiver_type, method_name: method_name,
1363
- args: arg_types, environment: environment
1564
+ args: arg_types, environment: environment, scope: scope
1364
1565
  )
1365
1566
  iterator_result = IteratorDispatch.block_param_types(context)
1366
1567
  return iterator_result if iterator_result
@@ -198,7 +198,14 @@ module Rigor
198
198
  # both `()` and `(int)` overloads — only the second matches a `def first(n)` redefinition)
199
199
  # are silently skipped, so the binder defaults to the most informative type the RBS
200
200
  # signature provides without having to know which overload the runtime will pick.
201
+ #
202
+ # An overload declared `(?)` (`RBS::Types::UntypedFunction`, issue #1430) has no parameter lists and
203
+ # accepts any argument in every slot, so no slot can be narrowed below `Dynamic[Top]`: binding a typed
204
+ # sibling overload's parameter type would read a value that overload legitimately passes as a
205
+ # contradiction inside the body. Every slot keeps its default; the declared return type still applies.
201
206
  def apply_rbs_overloads(types, slots, method_types)
207
+ return unless method_types.all? { |mt| mt.type.respond_to?(:required_positionals) }
208
+
202
209
  slots.each do |slot|
203
210
  next if slot.name.nil?
204
211
 
@@ -258,9 +265,7 @@ module Rigor
258
265
  return nil if contracts.empty?
259
266
 
260
267
  singleton = def_node.receiver.is_a?(Prism::SelfNode) || @singleton
261
- contracts.find do |contract|
262
- contract.method_name == def_node.name && contract.singleton == singleton
263
- end
268
+ contracts.find { |contract| contract.method_name == def_node.name && contract.singleton == singleton }
264
269
  end
265
270
 
266
271
  def positional_slot_at(slots, index)
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "../source/node_children"
5
+
6
+ module Rigor
7
+ module Inference
8
+ # The one home for what a receiverless `module_function` call does to the methods around it
9
+ # ([#1507](https://github.com/rigortype/rigor/issues/1507),
10
+ # [#1197](https://github.com/rigortype/rigor/issues/1197)). Four readers used to compute it in four
11
+ # places. They disagree with each other and, in places, with Ruby. Each entry point below reproduces
12
+ # one reader's answer exactly: this module gives the readings one place to live, and does not yet make
13
+ # them agree. A change to one reading lands here, beside the other three. Examples are #1550's
14
+ # named-form snapshot and the reset a bare `public`, `private` or `protected` makes.
15
+ #
16
+ # - The singleton def-node table (`ScopeIndexer#walk_singleton_body`) reads
17
+ # {.each_singleton_sibling} and {.each_singleton_copy}. A bare call covers the later direct
18
+ # statements of its own body and is never reset.
19
+ # - The deferred ranges (`ScopeIndexer#walk_deferred_body`) read {.prescan_deferred} and
20
+ # {.deferred_module_function?}. A bare call covers every later def by offset, and a call inside
21
+ # control flow or a block counts.
22
+ # - The extends table (`ScopeIndexer#record_extend_call`) reads {.extends_self?}. A bare call covers
23
+ # every instance def of the module, in any order.
24
+ # - sig-gen (`SigGen::Generator#walk_statements`) reads {.each_sig_gen_statement} and
25
+ # {.sig_gen_module_function?}. A bare call covers the later statements of its own statement list and
26
+ # what nests in them.
27
+ #
28
+ # A fifth reader does not come here yet: `rigor-activerecord`'s `ModelDiscoverer#table_name_decorators`
29
+ # (`plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb`) reads a module body's
30
+ # direct statements for its table-name readers. It also accepts `self.module_function`, and there a bare
31
+ # call covers the instance methods written before it as well as after it, while a named call takes only a
32
+ # def written before it. A change to what `module_function` means must decide whether that reader follows.
33
+ #
34
+ # The deferred-ranges prescan asks `ScopeIndexer.receiver_eval_call?` and `ScopeIndexer.def_singleton?`, so
35
+ # `rigor/inference/scope_indexer` must be loaded before it runs. This file does not require it:
36
+ # `scope_indexer.rb` requires this file, and a require back would be circular. The other entry points need
37
+ # only Prism.
38
+ #
39
+ # `spec/rigor/inference/module_function_state_spec.rb` pins each reader's answers, and marks the ones
40
+ # Ruby contradicts with a "flip this when" note.
41
+ module ModuleFunctionState
42
+ module_function
43
+
44
+ # A receiverless `module_function` call, bare or named.
45
+ def call?(node)
46
+ node.name == :module_function && node.receiver.nil?
47
+ end
48
+
49
+ # The toggle form: a `module_function` call with no arguments.
50
+ def bare?(node)
51
+ node.arguments.nil? || node.arguments.arguments.empty?
52
+ end
53
+
54
+ # The singleton def-node table's reading, over one class-ish body's direct statements in source
55
+ # order. `ScopeIndexer#statements_of` builds that list, folding a body-level `rescue`, `else` or
56
+ # `ensure` clause into it. Yields `stmt, module_function_on, named_call` for every statement except a
57
+ # bare call:
58
+ #
59
+ # - `module_function_on` says whether a bare call precedes the statement. The toggle never switches
60
+ # off, although a later bare `public`, `private` or `protected` resets it in Ruby.
61
+ # - `named_call` is true for a named call (`module_function :a` or `module_function def x`), which
62
+ # {.each_singleton_copy} resolves; the caller records nothing else for it.
63
+ #
64
+ # A call nested in control flow or a block is not seen.
65
+ def each_singleton_sibling(statements)
66
+ module_function_on = false
67
+ statements.each do |stmt|
68
+ if stmt.is_a?(Prism::CallNode) && call?(stmt)
69
+ if bare?(stmt)
70
+ module_function_on = true
71
+ else
72
+ yield stmt, module_function_on, true
73
+ end
74
+ next
75
+ end
76
+ yield stmt, module_function_on, false
77
+ end
78
+ end
79
+
80
+ # The defs a named call copies onto the module's singleton, for the singleton def-node table. Yields
81
+ # `name, def_node` for each Symbol or String argument that names a receiverless def among the body's
82
+ # direct `statements`. The last def of that name wins wherever it sits. A def written after the call
83
+ # is therefore taken, although Ruby copies the one in effect at the call (#1550), and a name defined
84
+ # only after the call resolves, although Ruby raises `NameError` there. A `module_function def x`
85
+ # argument names nothing here.
86
+ def each_singleton_copy(call, statements)
87
+ defs_by_name = statements.each_with_object({}) do |stmt, acc|
88
+ acc[stmt.name] = stmt if stmt.is_a?(Prism::DefNode) && stmt.receiver.nil?
89
+ end
90
+ call.arguments&.arguments&.each do |arg|
91
+ name = symbol_argument_name(arg)
92
+ def_node = name && defs_by_name[name]
93
+ yield name, def_node if def_node
94
+ end
95
+ end
96
+
97
+ # The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets` the start of
98
+ # every bare call; each def that starts after one is a module function
99
+ # ({.deferred_module_function?}). Appends to `ranges` one row per named call, in the table's
100
+ # `[start, end, name, kind, owner]` shape:
101
+ #
102
+ # - a `:name` argument gives a `:singleton` row over the call itself, because the copy happens at
103
+ # the call and not at the def;
104
+ # - a `module_function def x` argument gives a `:both` row over the def, or a `:singleton` row for
105
+ # `def self.x`.
106
+ #
107
+ # The prescan looks through control flow and blocks, because a call that ran there still flips the
108
+ # later defs. It stops at a nested class, module, `class <<` or def body, where the call targets
109
+ # another module. It skips an `END` body, which runs after every def, and a `*_eval` / `*_exec`
110
+ # block, whose self is the receiver's module; that block gets its own prescan as a body. Nothing
111
+ # resets it.
112
+ #
113
+ # `qualified_prefix` names the owner, and `in_singleton_class` says whether the body is a
114
+ # `class <<` body; both feed `ScopeIndexer.def_singleton?`.
115
+ def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges)
116
+ return unless node.is_a?(Prism::Node)
117
+ return if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
118
+ node.is_a?(Prism::SingletonClassNode) || node.is_a?(Prism::DefNode) ||
119
+ node.is_a?(Prism::PostExecutionNode)
120
+
121
+ if node.is_a?(Prism::CallNode)
122
+ return prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges) if call?(node)
123
+ if ScopeIndexer.receiver_eval_call?(node)
124
+ return prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
125
+ end
126
+ end
127
+
128
+ node.rigor_each_child do |child|
129
+ prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges)
130
+ end
131
+ end
132
+
133
+ # Whether the deferred-ranges reading makes a def starting at `start` a module function: a bare call
134
+ # starts before it, at any depth the prescan entered.
135
+ def deferred_module_function?(offsets, start)
136
+ offsets.any? { |offset| offset < start }
137
+ end
138
+
139
+ # The extends table's reading. A bare call makes the module extend itself, so every instance def the
140
+ # module has also answers on its singleton. That holds for defs before and after the call, and
141
+ # whatever a later `public` or `private` says. The over-approximation is deliberate (#526): the
142
+ # extra names only silence `call.undefined-method` on calls that raise. The walk reaches a call in
143
+ # control flow, and one in a `class_eval`-family block under that block's receiver. It does not reach
144
+ # one in an ordinary block, an `END` body or a `class << self` body.
145
+ def extends_self?(call)
146
+ call?(call) && bare?(call)
147
+ end
148
+
149
+ # sig-gen's reading, over one statement list. Yields every statement except a bare call, with
150
+ # whether a bare call came before it in this list or `active` was already true on entry. The
151
+ # generator passes the enclosing list's answer into an `if`, a block or a `begin`, but not into a
152
+ # nested class, module or `class << self`. A bare call in a nested list covers only that list's
153
+ # later statements. Nothing resets it, and a named call is not a directive at all.
154
+ def each_sig_gen_statement(statements, active)
155
+ statements.each do |stmt|
156
+ if sig_gen_directive?(stmt)
157
+ active = true
158
+ next
159
+ end
160
+ yield stmt, active
161
+ end
162
+ end
163
+
164
+ # A bare receiverless call, as sig-gen's statement-level directive.
165
+ def sig_gen_directive?(node)
166
+ node.is_a?(Prism::CallNode) && call?(node) && bare?(node)
167
+ end
168
+
169
+ # Whether sig-gen renders a def as `def self?.name`: an instance-side def under an active directive,
170
+ # in a class or a module alike. `kind` is `:singleton` for `def self.x` and a `class << self` body.
171
+ def sig_gen_module_function?(active, kind)
172
+ active && kind == :instance
173
+ end
174
+
175
+ # The prescan's call arm.
176
+ def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
177
+ if bare?(node)
178
+ offsets << node.location.start_offset
179
+ else
180
+ owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
181
+ node.arguments&.arguments&.each do |arg|
182
+ if arg.is_a?(Prism::DefNode)
183
+ kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
184
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
185
+ elsif (name = symbol_argument_name(arg))
186
+ ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
187
+ else
188
+ prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
189
+ end
190
+ end
191
+ end
192
+ return unless node.block
193
+
194
+ prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges)
195
+ end
196
+
197
+ # The prescan's eval arm: only the call's receiver and arguments keep this module's context.
198
+ def prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
199
+ prescan_deferred(node.receiver, qualified_prefix, in_singleton_class, offsets, ranges) if node.receiver
200
+ node.arguments&.arguments&.each do |arg|
201
+ prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
202
+ end
203
+ end
204
+
205
+ # The Symbol a `:name` or `"name"` literal argument names, or nil.
206
+ def symbol_argument_name(arg)
207
+ arg.unescaped.to_sym if arg.is_a?(Prism::SymbolNode) || arg.is_a?(Prism::StringNode)
208
+ end
209
+
210
+ private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name
211
+ end
212
+ end
213
+ end