rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -604,6 +604,35 @@ class Float
604
604
  def gigabytes: () -> Float
605
605
  end
606
606
 
607
+ # ---------------------------------------------------------------
608
+ # NumericWithFormat — ActiveSupport's `core_ext/numeric/conversions`, included into Integer, Float and
609
+ # BigDecimal. Declared on the module and included, not as a `def` on each class, so a project `sig/` that
610
+ # declares `Integer#to_fs` itself overrides it instead of raising `DuplicatedMethodDefinitionError` (the
611
+ # `process.rbs` shape). Not pure: the Symbol formats read `I18n` for their locale. An Integer or String
612
+ # format is `to_s(format)`, and no format is `to_s`, so every form answers a String. `options` is any
613
+ # Hash-like value: ActiveSupport calls `symbolize_keys` on it, so String keys and a HashWithIndifferentAccess
614
+ # are both valid.
615
+ # ---------------------------------------------------------------
616
+
617
+ module ActiveSupport
618
+ module NumericWithFormat
619
+ def to_fs: (?(Symbol | Integer | String | nil) format, ?Hash[untyped, untyped]? options) -> String
620
+ def to_formatted_s: (?(Symbol | Integer | String | nil) format, ?Hash[untyped, untyped]? options) -> String
621
+ end
622
+ end
623
+
624
+ class Integer
625
+ include ActiveSupport::NumericWithFormat
626
+ end
627
+
628
+ class Float
629
+ include ActiveSupport::NumericWithFormat
630
+ end
631
+
632
+ class BigDecimal
633
+ include ActiveSupport::NumericWithFormat
634
+ end
635
+
607
636
  # ---------------------------------------------------------------
608
637
  # ActiveSupport::Duration — issue #632
609
638
  # ---------------------------------------------------------------
@@ -1565,28 +1594,35 @@ class Hash[unchecked out K, unchecked out V]
1565
1594
  # non-bang half is `%a{pure}`. `assert_valid_keys` raises rather than
1566
1595
  # mutating (raising is not an effect label).
1567
1596
  %a{pure}
1568
- def symbolize_keys: () -> Hash[Symbol, V]
1597
+ # `key.to_sym rescue key` keeps a key without `to_sym`, so no one key type is right: `Hash[untyped, V]` here,
1598
+ # and the plugin's `dynamic_return` hook (#2433) maps each key member where it can.
1599
+ def symbolize_keys: () -> Hash[untyped, V]
1600
+ # `symbolize_keys!` keeps `-> self`: `key.to_sym rescue key` leaves a key without `to_sym` as it is, so
1601
+ # `Hash[Symbol, V]` is wrong for an Integer-keyed receiver (#2424 review). The other bang forms below
1602
+ # answer the re-keyed or re-valued hash.
1569
1603
  def symbolize_keys!: () -> self
1570
1604
  %a{pure}
1571
1605
  def deep_symbolize_keys: () -> Hash[untyped, untyped]
1572
- def deep_symbolize_keys!: () -> self
1606
+ def deep_symbolize_keys!: () -> Hash[untyped, untyped]
1573
1607
  %a{pure}
1574
1608
  def stringify_keys: () -> Hash[String, V]
1575
- def stringify_keys!: () -> self
1609
+ def stringify_keys!: () -> Hash[String, V]
1576
1610
  %a{pure}
1577
1611
  def deep_stringify_keys: () -> Hash[untyped, untyped]
1578
- def deep_stringify_keys!: () -> self
1612
+ def deep_stringify_keys!: () -> Hash[untyped, untyped]
1579
1613
  %a{pure}
1580
1614
  def assert_valid_keys: (*K | Array[K]) -> self
1615
+ # The block's result is the new key at every level, so the keys are its type, not `K`: `{ a: 1 }
1616
+ # .deep_transform_keys(&:to_s)` has String keys. Nested values are re-keyed too, so they read `untyped`.
1581
1617
  %a{pure}
1582
- def deep_transform_keys: () { (K) -> K } -> Hash[K, untyped]
1583
- def deep_transform_keys!: () { (K) -> K } -> self
1618
+ def deep_transform_keys: [X] () { (K) -> X } -> Hash[X, untyped]
1619
+ def deep_transform_keys!: [X] () { (K) -> X } -> Hash[X, untyped]
1584
1620
  %a{pure}
1585
1621
  def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
1586
- def deep_transform_values!: () { (V) -> untyped } -> self
1622
+ def deep_transform_values!: () { (V) -> untyped } -> Hash[K, untyped]
1587
1623
  # `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
1588
1624
  %a{pure}
1589
- def to_options: () -> Hash[Symbol, V]
1625
+ def to_options: () -> Hash[untyped, V]
1590
1626
  def to_options!: () -> self
1591
1627
 
1592
1628
  # `core_ext/hash/deep_dup` — allocates a deep copy, never touches the
@@ -1600,11 +1636,17 @@ class Hash[unchecked out K, unchecked out V]
1600
1636
  # `core_ext/hash/deep_merge` — the block receives `(key, this_val,
1601
1637
  # other_val)` and returns the merged value; neither `deep_merge` nor its
1602
1638
  # block mutates either hash (containment covers the block itself).
1603
- %a{pure}
1604
- def deep_merge: (Hash[K, V]) -> Hash[K, V]
1605
- | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
1606
- def deep_merge!: (Hash[K, V]) -> self
1607
- | (Hash[K, V]) { (K, V, V) -> V } -> self
1639
+ # The result carries the argument's keys and values as well as the
1640
+ # receiver's, and a key both hold a Hash under merges into a third Hash
1641
+ # that is neither side's value type (`{ n: { x: 1 } }.deep_merge(n: { y:
1642
+ # "s" })[:n]` is `{ x: 1, y: "s" }`), so the values read `untyped`.
1643
+ # `(Hash[K, V]) -> Hash[K, V]` typed `h.deep_merge(b: "x")[:b]` as the
1644
+ # receiver's value type (#2424 review). The bang form answers the receiver.
1645
+ %a{pure}
1646
+ def deep_merge: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
1647
+ | [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
1648
+ def deep_merge!: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
1649
+ | [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
1608
1650
  # `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
1609
1651
  # recursing into a value. `:nodoc:`, but public.
1610
1652
  %a{pure}
@@ -2,24 +2,22 @@
2
2
 
3
3
  require "prism"
4
4
 
5
- require "rigor/inference/declaration_walk"
5
+ require "rigor/reflection"
6
6
 
7
7
  require_relative "resource_index"
8
8
 
9
9
  module Rigor
10
10
  module Plugin
11
11
  class Alba < Rigor::Plugin::Base
12
- # A {Rigor::Inference::DeclarationWalk} collector over one file: the classes it declares, whether each
13
- # `include Alba::Resource`s, and the association declarations whose resource
14
- # class alba infers from the name.
12
+ # A collector over one file, built on {Rigor::Reflection.class_declarations_in}: the classes it declares, whether
13
+ # each `include Alba::Resource`s, and the association declarations whose resource class alba infers from
14
+ # the name.
15
15
  #
16
16
  # An association is recorded only when alba would take the inference path (`Alba::Association#
17
17
  # resource_from`): a literal Symbol/String name, exactly one positional argument, no block, and no
18
18
  # `resource:` / `serializer:` keyword. A keyword splat (`**opts`) might carry either, so it is not
19
19
  # recorded either — a skipped association costs a root, never invents one.
20
20
  class ResourceCollector
21
- include Rigor::Inference::DeclarationWalk::Collector
22
-
23
21
  ASSOCIATION_METHODS = %i[association one many has_one has_many].freeze
24
22
  RESOURCE_MODULE = "Alba::Resource"
25
23
  RESOURCE_KEYWORDS = %i[resource serializer].freeze
@@ -40,22 +38,13 @@ module Rigor
40
38
  @associations = []
41
39
  end
42
40
 
43
- def on_declaration(node, context, body_context)
44
- return Rigor::Inference::DeclarationWalk::DESCEND unless node.is_a?(Prism::ClassNode)
45
-
46
- name = body_context.prefix.join("::")
47
- unless name.empty?
48
- @entries[name] = superclass_name(node.superclass)
49
- @nestings[name] = context.nesting || []
41
+ # Walks `root` (a parsed file's `Prism::Node`) and records what it finds; answers the collector itself.
42
+ def collect(root)
43
+ Rigor::Reflection.class_declarations_in(root) { |call| on_call(call.node, call.owner) }.each do |declaration|
44
+ @entries[declaration.name] = superclass_name(declaration.node.superclass)
45
+ @nestings[declaration.name] = declaration.nesting
50
46
  end
51
- Rigor::Inference::DeclarationWalk::DESCEND
52
- end
53
-
54
- def on_call(node, context)
55
- owner = context.prefix.join("::")
56
- record_call(node, owner) unless owner.empty? || !node.receiver.nil?
57
- remember_block(node)
58
- Rigor::Inference::DeclarationWalk::DESCEND
47
+ self
59
48
  end
60
49
 
61
50
  # The classes this file declares, as {ResourceIndex::ClassEntry} values.
@@ -70,6 +59,11 @@ module Rigor
70
59
 
71
60
  private
72
61
 
62
+ def on_call(node, owner)
63
+ record_call(node, owner) unless owner.nil? || !node.receiver.nil?
64
+ remember_block(node)
65
+ end
66
+
73
67
  def record_call(node, owner)
74
68
  if node.name == :include
75
69
  @includes[owner] = true if includes_resource?(node)
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "prism"
4
4
 
5
- require "rigor/inference/declaration_walk"
6
-
7
5
  require_relative "resource_collector"
8
6
  require_relative "resource_index"
9
7
 
@@ -28,10 +26,7 @@ module Rigor
28
26
  parsed = Prism.parse(contents)
29
27
  next unless parsed.success?
30
28
 
31
- collector = ResourceCollector.new
32
- Rigor::Inference::DeclarationWalk.run(
33
- parsed.value, [collector], Rigor::Inference::DeclarationWalk::Context.root(nesting: [])
34
- )
29
+ collector = ResourceCollector.new.collect(parsed.value)
35
30
  entries.concat(collector.class_entries)
36
31
  associations.concat(collector.associations)
37
32
  end
@@ -37,9 +37,9 @@ module Rigor
37
37
  @classes.empty?
38
38
  end
39
39
 
40
- # The classes alba infers for the associations that name no resource, limited to
41
- # those that exist in the project. The block receives each association name and returns alba's
42
- # `classify` of it (the caller owns the inflector; ADR-39).
40
+ # The classes alba infers for the associations that name no resource, limited to those that exist in
41
+ # the project. The block receives each association name and returns alba's `classify` of it (the
42
+ # caller owns the inflector; ADR-39).
43
43
  def inferred_roots(&classify)
44
44
  roots = @associations.filter_map do |association|
45
45
  next unless resource?(association.owner)
@@ -212,9 +212,7 @@ module Rigor
212
212
  # ... end` row's own recursive call (see {#each_block_type_info}). It caps the recursion at ONE
213
213
  # level deep: with `nested: true`, {#walk_predicate_chain} declines a FURTHER `each do ... end`
214
214
  # rather than recursing again (issue #137 deliberately does not model doubly-nested arrays of
215
- # arrays). This also keeps `collect_schema_shape` from calling itself — {#each_block_type_info}
216
- # is the only caller that ever passes `nested: true}` — so the call graph has no cycle for the
217
- # engine's return-type inference to approximate through.
215
+ # arrays). The recursion through {#each_block_type_info} is therefore bounded and terminates.
218
216
  def collect_schema_shape(block_node, type_aliases, nested:)
219
217
  required = {}
220
218
  optional = {}
@@ -34,9 +34,9 @@ module Rigor
34
34
  #
35
35
  # The synthetic reader's return type is resolved via ADR-18's `returns_from_arg:` fact lookup: the
36
36
  # call-site's second argument (`Types::String` etc.) is looked up through the `:dry_type_aliases`
37
- # fact published by `rigor-dry-types`, yielding `Nominal[String]` for common cases. When the lookup
38
- # misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
39
- # falls back to `Dynamic[Top]` silently.
37
+ # fact published by `rigor-dry-types`, yielding the aliased class (`String`) for common cases. When the
38
+ # lookup misses (e.g. inline method-chain argument whose chain-head isn't currently extracted), the row
39
+ # falls back to the untyped floor silently.
40
40
  #
41
41
  # ## Scope (slice 2c minimum)
42
42
  #
@@ -58,8 +58,8 @@ module Rigor
58
58
  "by consuming `rigor-dry-types`'s `:dry_type_aliases` fact.",
59
59
  # ADR-9 consumption — the precision-promotion path below uses `:dry_type_aliases` published by
60
60
  # `rigor-dry-types`. The fact is optional: when the `rigor-dry-types` plugin isn't loaded, the
61
- # `returns_from_arg:` lookup misses and the synthetic readers fall back to `Dynamic[Top]` (the
62
- # pre-ADR-18 floor).
61
+ # `returns_from_arg:` lookup misses and the synthetic readers fall back to the untyped floor (the
62
+ # pre-ADR-18 behaviour).
63
63
  consumes: [{ plugin_id: "dry-types", name: :dry_type_aliases, optional: true }],
64
64
  heredoc_templates: [
65
65
  Rigor::Plugin::Macro::HeredocTemplate.new(
@@ -69,7 +69,7 @@ module Rigor
69
69
  # ADR-18 — the synthetic reader's return type comes from the call site's second argument
70
70
  # (`Types::String` etc.), resolved through the `:dry_type_aliases` fact. When the lookup
71
71
  # misses (e.g. inline `attribute :tag, Types::String.constrained(...)`, whose receiver chain
72
- # head isn't currently extracted), the row falls back to Dynamic[Top].
72
+ # head isn't currently extracted), the row falls back to the untyped floor.
73
73
  emit: [{
74
74
  name: "\#{name}",
75
75
  returns_from_arg: {
@@ -33,10 +33,9 @@ module Rigor
33
33
  # `Coercible::*` (everything-to-target coercion), `Strict::*` (no coercion; raise if mismatch),
34
34
  # `Params::*` (HTTP / query-string-style coercion, used by Hanami / Roda / dry-web in request
35
35
  # handling), `JSON::*` (JSON-shape coercion). Each category exposes the same set of names as the
36
- # canonical shortcuts above, plus a few additions that are category-specific (`Params::Nil`,
37
- # `JSON::Symbol`). For Rigor's purposes the underlying class is the same regardless of category —
38
- # coercion semantics are a runtime concern. We register every `<module>::<Category>::<Name>`
39
- # mapping the upstream gem publishes so call-site references work uniformly.
36
+ # canonical shortcuts above; only those names are registered here, so a category-specific extra
37
+ # is not covered. For Rigor's purposes the underlying class is the same regardless of category —
38
+ # coercion semantics are a runtime concern.
40
39
  NESTED_CATEGORIES = %w[Coercible Strict Params JSON].freeze
41
40
  private_constant :NESTED_CATEGORIES
42
41
 
@@ -229,8 +228,8 @@ module Rigor
229
228
 
230
229
  # `include Dry.Types()` at the top of the module body is the canonical alias declaration. We
231
230
  # accept the call anywhere in the body (some projects guard it with a `if defined?` check). The
232
- # argument list must be empty (or a kwargs-only `default: :nominal` style accepted by upstream;
233
- # we treat both as "alias-installing").
231
+ # `Dry.Types(...)` argument list is not inspected: kwargs such as `default: :nominal` install the
232
+ # aliases too.
234
233
  def contains_dry_types_include?(body)
235
234
  return false if body.nil?
236
235
 
@@ -21,7 +21,7 @@ module Rigor
21
21
  # cross-plugin fact (ADR-9). Other dry-rb adapter plugins consume this fact:
22
22
  #
23
23
  # - `rigor-dry-struct` reads it so `attribute :city, Types::String` promotes `address.city` from
24
- # `Dynamic[Top]` to `Nominal[String]` via ADR-18's `returns_from_arg:` fact lookup.
24
+ # from an untyped value to `String` via ADR-18's `returns_from_arg:` fact lookup.
25
25
  # - `rigor-dry-validation` / `rigor-dry-schema` read it for per-key type recognition in `schema { … }`
26
26
  # / `params { … }` blocks (separate plugin slice).
27
27
  #
@@ -44,9 +44,8 @@ module Rigor
44
44
  end
45
45
 
46
46
  # Renders `Foo` / `Foo::Bar` / `::Foo::Bar` as the `::`-joined String {ContractScanner}'s table is
47
- # keyed by. Duplicated from rigor-dry-schema's identical helper rather than shared — five lines,
48
- # no dependency on anything ELSE in that plugin, and reusing it would need a `registered_for`
49
- # guard here too even though this leaf never touches dry-schema's own type/alias machinery.
47
+ # keyed by. Duplicated from rigor-dry-schema rather than shared: this leaf must not depend on that
48
+ # plugin, and reusing the helper would need a `registered_for` guard here too.
50
49
  def constant_name(node)
51
50
  case node
52
51
  when Prism::ConstantReadNode then node.name.to_s
@@ -58,19 +58,16 @@ module Rigor
58
58
 
59
59
  # ADR-37 dynamic return — refines `NewUserContract.new.call(input).to_h` to the per-contract
60
60
  # `HashShape` when {#result_shape_for} finds one. Gated on the method name alone (mirrors
61
- # rigor-dry-schema's own `.to_h` rule): the RBS overlay types `Contract#call`'s return as the
62
- # generic `Result`, but that alone can't select a PER-CONTRACT shape, so the block does its own
63
- # syntactic chain match via {ParamsShape.contract_name} before touching the table. When
64
- # rigor-dry-schema isn't loaded, or the chain isn't a Contract call, this returns nil and the RBS
65
- # overlay's generic `Hash[Symbol, untyped]` stands.
61
+ # rigor-dry-schema's `.to_h` rule); the chain match is in {ParamsShape.contract_name}, because the
62
+ # RBS overlay's generic `Result` cannot select a per-contract shape. Otherwise returns nil and the
63
+ # overlay's generic hash type stands.
66
64
  dynamic_return methods: [:to_h] do |call_node, _scope|
67
65
  result_shape_for(call_node)
68
66
  end
69
67
 
70
68
  # ADR-37 slice 1c two-pass — the "collect" half of `dry-validation.rule-key-mismatch`. Reads the
71
- # SAME `:dry_type_aliases` fact `#prepare` does (a plain `services.fact_store.read`, not
72
- # `#read_fact`'s memoised wrapper — `services` at collect time is the run-wide instance, so an
73
- # in-block memo isn't needed here the way it is across many per-call `dynamic_return` invocations).
69
+ # same `:dry_type_aliases` fact as `#prepare`, straight from `services.fact_store` (no `#read_fact`
70
+ # memo: `services` here is run-wide, unlike the per-call `dynamic_return` blocks).
74
71
  node_file_context do |root, _scope|
75
72
  type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
76
73
  ContractScanner.rule_key_issues(root, type_aliases)
@@ -14,7 +14,6 @@ module Rigor
14
14
  signature_paths: ["sig"]
15
15
  )
16
16
 
17
- # Option catalogue and return types for Easy methods
18
17
  EASY_RETURN_TYPES = {
19
18
  perform: -> { Rigor::Type::Combinator.nominal_of("Integer") },
20
19
  response_code: -> { Rigor::Type::Combinator.nominal_of("Integer") },
@@ -115,7 +115,7 @@ module Rigor
115
115
  # 3. Inflected from the factory name — `:user` → `"User"`, `:admin_user` → `"AdminUser"`. The
116
116
  # factory name is already singular by FactoryBot convention, so we only need camelization.
117
117
  #
118
- # Returns a String (the canonical class name).
118
+ # Returns the canonical class name.
119
119
  def factory_model_class(call_node, factory_name)
120
120
  explicit = explicit_class_option(call_node)
121
121
  return explicit if explicit
@@ -84,27 +84,20 @@ module Rigor
84
84
  @factory_search_paths = Array(config.fetch("factory_search_paths")).map(&:to_s)
85
85
  end
86
86
 
87
- # ADR-102 WD3 / #350 — the model classes the factories build, published as `:reachability_references`
88
- # rather than as `:reachability_roots`.
87
+ # ADR-102 WD3 / #350 — the factories' model classes are published as `:reachability_references`,
88
+ # not `:reachability_roots`.
89
89
  #
90
- # Both halves of that sentence are load-bearing.
91
- #
92
- # It is a CONTRIBUTION at all because a factory names its class by a mechanism the constant scan cannot
93
- # follow: `factory :user, class: "Admin::User"` is a string, and a bare `factory :user` is FactoryBot's
94
- # own constantization of the factory name. Neither leaves a constant node anywhere in the project.
95
- #
96
- # It is a REFERENCE rather than a root because factories live in the test tree. Rooting them would make
97
- # every factoried class production-reachable and erase ADR-102 WD8's "reachable only from tests"
98
- # category for exactly the classes it is most likely to be about — a model kept alive by its factory
99
- # and its spec and nothing else is the archetype of dead production code with a live test, and that is
100
- # the single most actionable row this report produces. Carrying the `:test` role keeps the finding.
90
+ # It is a contribution because a factory names its class by a mechanism the constant scan cannot
91
+ # follow (`factory :user, class: "Admin::User"` is a string; a bare `factory :user` is constantized by
92
+ # FactoryBot). It is a reference rather than a root because factories live in the test tree: rooting
93
+ # them would make every factoried class production-reachable and erase ADR-102 WD8's "reachable only
94
+ # from tests" category. The `:test` role keeps that finding.
101
95
  def prepare(services)
102
96
  index = producer_value(:factory_index)
103
97
  return if index.nil? || index.empty?
104
98
 
105
- # The producer value and the ADR-9 fact are different channels (#921) — `producer_value` is this
106
- # plugin's own cached computation, read back via `producer_value` above; `fact_store.publish` is
107
- # what makes it visible to another plugin's `read_fact`, which is how rigor-rspec binds
99
+ # The producer value and the ADR-9 fact are different channels (#921): `fact_store.publish` is what
100
+ # makes the index visible to another plugin's `read_fact`, as rigor-rspec uses it to bind
108
101
  # `create(:name)` to the factory's model class.
109
102
  services.fact_store.publish(plugin_id: manifest.id, name: :factory_index, value: index)
110
103
 
@@ -118,8 +111,7 @@ module Rigor
118
111
 
119
112
  # ADR-37 — per-call factory/attribute validation over the engine-owned walk. Each violation carries
120
113
  # its own location (the call's message_loc, or the offending attribute key), so it is positioned
121
- # via `diagnostic(node, location:)`. No file-level diagnostic remains, so there is no
122
- # `diagnostics_for_file`.
114
+ # via `diagnostic(node, location:)`.
123
115
  node_rule Prism::CallNode do |node, _scope, path|
124
116
  index = producer_value(:factory_index)
125
117
  next [] if index.nil? || index.empty?
@@ -40,7 +40,6 @@ module Rigor
40
40
  all_args = call_node.arguments&.arguments || []
41
41
  return nil if all_args.empty?
42
42
 
43
- # Strip trailing keyword options hash if present
44
43
  pos_args = all_args.grep_v(Prism::KeywordHashNode)
45
44
  return nil if pos_args.empty?
46
45
 
@@ -149,7 +149,6 @@ module Rigor
149
149
  end
150
150
  end
151
151
  when :callback
152
- # Issue 4 fix: extract callback definitions
153
152
  args = node.arguments&.arguments || []
154
153
  if args.size >= 3
155
154
  cb_name = Analyzer.extract_symbol(args[0])
@@ -193,7 +192,6 @@ module Rigor
193
192
  end
194
193
  end
195
194
  else
196
- # Issue 5 fix: pass mod_name to sub-plugin recognizers
197
195
  FFI.binding_recognizers.each do |recognizer|
198
196
  facts = recognizer.recognize(node, mod_name)
199
197
  Array(facts).each do |fact|
@@ -38,17 +38,17 @@ module Rigor
38
38
  # Dynamic return rule for attached FFI functions
39
39
  # Gated on known FFI library receivers and attached function names
40
40
  #
41
- # Issue #701 — both receiver kinds are declared because the block below already resolved
42
- # `Nominal` and `Singleton` alike: `attach_function` installs the binding on the library module
43
- # itself (`MyLib.strlen`) and `include`ing that module makes the same name an instance call, so
44
- # neither kind is an accident. The kind-aware gate makes that intent explicit rather than relying
45
- # on a `receivers:` entry answering for both.
41
+ # Issue #701 — both receiver kinds are declared because the block below already resolves `Nominal`
42
+ # and `Singleton` alike: `attach_function` installs the binding on the library module itself
43
+ # (`MyLib.strlen`), and `include`ing that module makes the same name an instance call. The
44
+ # kind-aware gate makes that intent explicit rather than relying on one `receivers:` entry for both.
46
45
  dynamic_return receivers: -> { both_receiver_kinds(producer_value(:ffi_catalog)&.libraries) },
47
46
  methods: -> { producer_value(:ffi_catalog)&.function_method_names || [] } do |call_node, scope|
48
47
  catalog = producer_value(:ffi_catalog)
49
48
  next nil if catalog.nil?
50
49
 
51
- receiver_type = call_node.receiver ? scope&.type_of(call_node.receiver) : scope&.self_type
50
+ receiver = call_node.receiver
51
+ receiver_type = receiver ? scope&.type_of(receiver) : scope&.self_type
52
52
  receiver_class = case receiver_type
53
53
  when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
54
54
  end
@@ -78,7 +78,8 @@ module Rigor
78
78
  catalog = producer_value(:ffi_catalog)
79
79
  next nil if catalog.nil?
80
80
 
81
- receiver_type = call_node.receiver ? scope&.type_of(call_node.receiver) : scope&.self_type
81
+ receiver = call_node.receiver
82
+ receiver_type = receiver ? scope&.type_of(receiver) : scope&.self_type
82
83
  receiver_class = case receiver_type
83
84
  when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
84
85
  end
@@ -85,7 +85,6 @@ module Rigor
85
85
  method_names: NAMESPACE_METHODS,
86
86
  self_type: "singleton(Grape::API::Instance)"
87
87
  ),
88
- # `params do ... end` — body `instance_eval`s on a `ParamsScope` instance.
89
88
  Rigor::Plugin::Macro::BlockAsMethod.new(
90
89
  receiver_constraint: "Grape::API",
91
90
  method_names: %i[params],
@@ -121,7 +120,6 @@ module Rigor
121
120
  method_names: %i[desc],
122
121
  self_type: "Grape::DSL::Desc::ConfigContext"
123
122
  ),
124
- # Verb bodies run inside `Grape::Endpoint` instances.
125
123
  Rigor::Plugin::Macro::BlockAsMethod.new(
126
124
  receiver_constraint: "Grape::API",
127
125
  method_names: VERB_METHODS,
@@ -23,9 +23,8 @@ module Rigor
23
23
  }.freeze
24
24
 
25
25
  # The base class name a Schema::Object subclass MUST inherit from to be recognised. Match is on
26
- # the rightmost segment of the superclass constant chain so both `GraphQL::Schema::Object` and
27
- # the locally-aliased `BaseObject = GraphQL::Schema::Object` shape work when the alias's RHS is
28
- # the canonical path.
26
+ # the last two segments of the superclass constant chain (see {#schema_subclass?}), so a locally
27
+ # aliased base such as `BaseObject = GraphQL::Schema::Object` is not followed.
29
28
  SCHEMA_OBJECT_TAIL = "Object"
30
29
  SCHEMA_ENUM_TAIL = "Enum"
31
30
  SCHEMA_INPUT_OBJECT_TAIL = "InputObject"
@@ -183,7 +182,7 @@ module Rigor
183
182
  private_class_method :statement_nodes
184
183
 
185
184
  # Walks every top-level `value "..."` call inside an enum subclass body and returns the value
186
- # names as an Array<String>. Both shapes graphql-ruby accepts work:
185
+ # names. Both shapes graphql-ruby accepts work:
187
186
  #
188
187
  # value "ACTIVE"
189
188
  # value "DISABLED", value: :off, description: "..."
@@ -299,7 +298,7 @@ module Rigor
299
298
  end
300
299
  private_class_method :parse_field_call
301
300
 
302
- # Resolves the `Type` positional argument to a `{type: "ClassName", list: bool}` tuple.
301
+ # Resolves the `Type` positional argument to a `type:` class name and a `list:` flag.
303
302
  # ArrayNode forms (`[String]` / `[Types::User]`) unwrap the single element and mark `list: true`.
304
303
  # Bare constant refs are not lists. Returns nil for unrecognised shapes (string types `"User"`,
305
304
  # Proc lazy types, etc.) so callers drop the field.
@@ -350,8 +349,8 @@ module Rigor
350
349
  end
351
350
  private_class_method :extract_nullability
352
351
 
353
- # Returns the constant chain as an Array of String segments (`["GraphQL", "Schema", "Object"]`).
354
- # Empty array for unrecognised node kinds.
352
+ # Returns the constant chain's segments, e.g. `["GraphQL", "Schema", "Object"]`. Empty for
353
+ # unrecognised node kinds.
355
354
  def constant_path_segments(node)
356
355
  case node
357
356
  when Prism::ConstantReadNode then [node.name.to_s]
@@ -77,8 +77,18 @@ module Rigor
77
77
  signature_paths: ["sig"],
78
78
  # ADR-43 WD4 — the classes the bundled sig covers completely enough that a Ruby-source subclass
79
79
  # (`class PostType < GraphQL::Schema::Object`) may bridge inherited calls to the RBS ancestor.
80
- # Listing one turns on `call.undefined-method`/arity checks for inherited DSL calls on every
81
- # subclass — justified here because the sig IS the authority (graphql-ruby ships none).
80
+ # The bridge is a signature lookup only: an inherited DSL call resolves to the declaration's
81
+ # return type and block parameters, and a subclass `def` overriding a declared method other
82
+ # than `initialize` binds its parameters from it (#2681). The subclass itself stays outside
83
+ # RBS, so no signature-reading call rule fires on a call it makes, declared or not:
84
+ # `call.undefined-method`, `call.wrong-arity` and `call.argument-type-mismatch` all stay
85
+ # silent, while the same calls on a listed class AS THE DIRECT RECEIVER still report.
86
+ # `open_receivers:` is what covers those direct receivers; it is not what silences the
87
+ # subclass — the receiver gate is (ADR-43 Context § "Amendment 2026-11-15", which withdrew
88
+ # the claim that the rules follow the resolution). So listing a class here cannot cost a
89
+ # false positive on a subclass. The risk runs the other way: a declared member that does not exist at runtime makes a genuinely
90
+ # failing call resolve silently, so the sig must be accurate — it IS the authority
91
+ # (graphql-ruby ships none).
82
92
  rbs_complete_ancestors: %w[
83
93
  GraphQL::Schema
84
94
  GraphQL::Schema::Member
@@ -20,10 +20,8 @@ module Rigor
20
20
  @contracts = contracts
21
21
  end
22
22
 
23
- # Per-`ClassNode` check over the engine-owned walk (ADR-37): called once per class node the
24
- # `node_rule` in `hanami.rb` dispatches, against every contract whose `path_glob` matches the
25
- # file. No cross-class-node state is needed, so the checker ships no `class_nodes` traversal of
26
- # its own.
23
+ # Per-`ClassNode` check over the engine-owned walk (ADR-37): called once per class node by the
24
+ # `node_rule` in `hanami.rb`, against every contract whose `path_glob` matches the file.
27
25
  def check_class(class_node, path:)
28
26
  @contracts.filter_map do |contract|
29
27
  next unless path_matches?(contract.path_glob, path)
@@ -87,11 +87,8 @@ module Rigor
87
87
  @protocol_contracts || manifest.protocol_contracts
88
88
  end
89
89
 
90
- # ADR-37 — per-class-node validation over the engine-owned walk. Each `Prism::ClassNode` is checked
91
- # against every contract independently, so the plugin no longer ships its own `class_nodes`
92
- # traversal; `ActionChecker#check_class` keeps the per-class contract logic. (A per-class contract
93
- # check is exactly what `node_rule` is for — the return type is void, so no `scope` query is
94
- # needed.)
90
+ # ADR-37 — per-class-node validation over the engine-owned walk; `ActionChecker#check_class` holds the
91
+ # per-class contract logic. The return type is void, so no `scope` query is needed.
95
92
  node_rule Prism::ClassNode do |node, _scope, path|
96
93
  contracts = protocol_contracts
97
94
  next [] if contracts.empty?
@@ -51,8 +51,8 @@ module Rigor
51
51
  #
52
52
  # - `is_a?(Result::Ok)` / `Some` / `None` exhaustive narrowing — core control-flow analysis over a sealed
53
53
  # hierarchy, not a plugin surface.
54
- # - The `variants do variant Const, Type end` Enum DSL is handled via ADR-36 `nested_class_templates:` in
55
- # this plugin's manifest (Slice A — see `nested_class_templates:` block below).
54
+ # - The `variants do variant Const, Type end` Enum DSL is not part of the unwrap contribution; it is
55
+ # synthesised through the ADR-36 `nested_class_templates:` in this plugin's manifest (Slice A).
56
56
  class Mangrove < Rigor::Plugin::Base
57
57
  manifest(
58
58
  id: "mangrove",
@@ -113,7 +113,13 @@ module Rigor
113
113
  # ADR-37 slice 2 — a pure return-type contributor. The engine gates on the receiver being a known
114
114
  # carrier class (so the block only runs on Result/Option receivers); the block then confirms the call
115
115
  # is an unwrap method on an instantiated carrier and yields the carried `type_args[0]`.
116
+ # #2717 — the rule has no `methods:` gate, so the name gate does not screen it: the engine also offers it
117
+ # the compound-write nodes (`x.attr op= v`, `x[i] ||= v`, ...) and the `&:symbol` block's
118
+ # `Prism::BlockArgumentNode`. None of those has `name`, which the rule reads after `receiver`, so decline
119
+ # anything that is not a `CallNode` rather than raise into the rescue.
116
120
  dynamic_return receivers: (RESULT_CARRIERS + OPTION_CARRIERS) do |call_node, scope|
121
+ next nil unless call_node.is_a?(Prism::CallNode)
122
+
117
123
  receiver = call_node.receiver
118
124
  next nil if receiver.nil?
119
125