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
@@ -20,7 +20,6 @@ module Rigor
20
20
  #
21
21
  # Centralising both here keeps the receiver-shape and constant-unwrap knowledge in one place: if
22
22
  # `Type::Singleton` or `Type::Constant` ever changes shape, there is a single edit rather than nine.
23
- # The per-tier fold bodies stay where they are — only the shared gate moves.
24
23
  module SingletonFolding
25
24
  module_function
26
25
 
@@ -20,8 +20,9 @@ module Rigor
20
20
  # The rule is exact rather than heuristic — the receiver must be a `Singleton[C]` whose `C` the project
21
21
  # pre-pass recorded as including `Singleton` — so a class that does not include the mixin is untouched,
22
22
  # and a project that defines its own `Singleton` module is the one case that would misfire. Guarded by
23
- # {project_singleton_shadow?}: a project-declared `Singleton` class or module wins and the tier declines,
24
- # because then the name means something else entirely.
23
+ # {own_singleton_include?}: when the included name resolves to a project-declared class or module (a
24
+ # top-level `Singleton`, or `M::Singleton` inside `module M`), the tier declines, because then the name
25
+ # means something else entirely.
25
26
  module SingletonMixinDispatch
26
27
  MIXIN_NAMES = Ractor.make_shareable(Set["Singleton", "::Singleton"])
27
28
  private_constant :MIXIN_NAMES
@@ -31,30 +32,62 @@ module Rigor
31
32
 
32
33
  module_function
33
34
 
34
- # @return `Nominal[C]` for `C.instance`, or nil to decline.
35
+ # @return the instance type of `C` for `C.instance`, or nil to decline.
35
36
  def try_dispatch(context)
36
- return nil unless context.method_name == SELECTOR
37
- return nil unless context.args.empty?
38
-
39
37
  receiver = context.receiver
40
- return nil unless receiver.is_a?(Type::Singleton)
41
-
42
- scope = context.scope
43
- return nil if scope.nil?
44
- return nil unless includes_singleton_mixin?(scope, receiver.class_name)
45
- return nil if project_singleton_shadow?(scope)
38
+ return nil unless answers?(context.scope, receiver, context.method_name, context.args.empty?)
46
39
 
47
40
  Type::Combinator.nominal_of(receiver.class_name)
48
41
  end
49
42
 
43
+ # Issue #2438 — the one predicate behind {try_dispatch} and `CheckRules#undefined_method_diagnostic`, so the
44
+ # typer and the `call.undefined-method` rule cannot disagree about `C.instance`: a call this tier types is
45
+ # never reported as undefined, whatever a project sidecar of `C` lists.
46
+ def answers?(scope, receiver, method_name, no_arguments)
47
+ return false unless method_name == SELECTOR && no_arguments
48
+ return false unless receiver.is_a?(Type::Singleton)
49
+ return false if scope.nil?
50
+
51
+ includes_singleton_mixin?(scope, receiver.class_name)
52
+ end
53
+
54
+ # True when `class_name`'s instance ancestry carries the stdlib `Singleton` as an `include` written in the
55
+ # class itself or in a project class up its superclass chain (Ruby's `Singleton.included` also installs an
56
+ # `inherited` hook, so `class D < C` answers `instance` with its own instance). Read off
57
+ # `Scope::ResolutionChain` (the `:constants` flavor, so an empty project `module Singleton` is a project
58
+ # entry), where the stdlib module is an external entry. Issue #2438 — what is not the mixin:
59
+ #
60
+ # - `prepend Singleton`, which runs `prepend_features` instead of the `included` hook and so defines no
61
+ # `instance`; a prepended entry sits ahead of its owner on the chain;
62
+ # - an `include Singleton` in a MODULE the class includes: `Singleton.included` raises `TypeError` for a
63
+ # module, so that code never runs;
64
+ # - a top-level project `Singleton`, which both `Singleton` and `::Singleton` name.
65
+ #
66
+ # A NESTED project `N::Singleton` the name resolved to is read as the stdlib mixin: the discovery tables
67
+ # drop the leading `::` of a mixin argument, so `include ::Singleton` and `include Singleton` inside
68
+ # `module N` record the same name, and the stdlib reading is the one that types the rooted spelling and
69
+ # never reports a correct call.
50
70
  def includes_singleton_mixin?(scope, class_name)
51
- scope.includes_of(class_name).any? { |name| MIXIN_NAMES.include?(name.to_s) }
71
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s.delete_prefix("::"), :instance, :constants)
72
+ chain.record(scope)
73
+ classes = chain.level_classes.compact
74
+ chain.entries.each_with_index.any? { |entry, index| singleton_include?(chain, classes, entry, index) }
75
+ end
76
+
77
+ def singleton_include?(chain, classes, entry, index)
78
+ if entry.external?
79
+ !entry.superclass_edge && MIXIN_NAMES.include?(entry.raw.to_s) && classes.include?(entry.owner) &&
80
+ index > chain.index_of(entry.owner)
81
+ else
82
+ entry.name.end_with?("::Singleton") && included_by_level_class?(chain, index)
83
+ end
52
84
  end
53
85
 
54
- # True when the project declares its OWN `Singleton` class / module, in which case `include Singleton`
55
- # names that one and the stdlib rule does not apply.
56
- def project_singleton_shadow?(scope)
57
- MIXIN_NAMES.any? { |name| scope.discovery.discovered_classes.key?(name.to_s.delete_prefix("::")) }
86
+ # The entry sits after its level's class, so it was included into that level rather than prepended.
87
+ def included_by_level_class?(chain, index)
88
+ level = chain.level_starts.rindex { |start| start <= index }
89
+ klass = level && chain.level_classes[level]
90
+ !klass.nil? && index > chain.index_of(klass)
58
91
  end
59
92
  end
60
93
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # Issue #2828 — how overload selection reads a parameter spelled `singleton(T)` over a method-level type
9
+ # variable `T`. RBS parses it as a class singleton whose name is the bare, relative `T`, and the
10
+ # translator reads that as a class named `T`: an unloadable class whose singleton refuses every class object
11
+ # the analyzer process has loaded (`Acceptance` rules a loaded class out of an unloadable target's
12
+ # subtypes). With `Resolv` loaded, `Resolv::DNS#getresources: [T < Resource] (dns_name, singleton(T)) ->
13
+ # Array[T] | ...` then lost to the base overload.
14
+ #
15
+ # Selection instead reads the parameter as "a class object" the variable admits: `singleton(X)` for a
16
+ # class upper bound `X`, and `Module` otherwise, which takes any class or module object and refuses an
17
+ # instance. Only names the overload declares in its own `type_params` are read this way; any other
18
+ # unresolved `singleton(X)` keeps the translator's reading.
19
+ #
20
+ # {.selecting} hands the selector the overloads rewritten where a parameter needs it and maps the selected
21
+ # rewrites back, so the return translation and the argument binding (`RbsDispatch#singleton_param_binding`)
22
+ # still read the declared `singleton(T)`.
23
+ module SingletonTypeParams
24
+ module_function
25
+
26
+ # Yields the overloads to select among and answers the block's selection, with each rewrite mapped back to
27
+ # its declaration. Nearly every method needs no rewrite, and then `declared` is yielded as is.
28
+ def selecting(declared)
29
+ return yield(declared) unless declared.any? { |method_type| rewrites?(method_type) }
30
+
31
+ originals = {}.compare_by_identity
32
+ rewritten = declared.map do |method_type|
33
+ next method_type unless rewrites?(method_type)
34
+
35
+ rewrite = rewrite(method_type)
36
+ originals[rewrite] = method_type
37
+ rewrite
38
+ end
39
+ yield(rewritten).map { |method_type| originals.fetch(method_type, method_type) }
40
+ end
41
+
42
+ def rewrites?(method_type)
43
+ params = method_type.respond_to?(:type_params) ? method_type.type_params : nil
44
+ return false if params.nil? || params.empty?
45
+
46
+ fun = method_type.type
47
+ return false unless fun.respond_to?(:required_positionals)
48
+
49
+ any_variable_singleton?(fun.required_positionals, params) ||
50
+ any_variable_singleton?(fun.optional_positionals, params) ||
51
+ any_variable_singleton?(fun.trailing_positionals, params)
52
+ end
53
+
54
+ def any_variable_singleton?(list, type_params)
55
+ list.any? { |param| variable_singleton?(param.type, type_params) }
56
+ end
57
+
58
+ def variable_singleton?(type, type_params)
59
+ return false unless type.is_a?(RBS::Types::ClassSingleton)
60
+
61
+ name = type.name
62
+ return false if name.absolute? || !name.namespace.empty?
63
+
64
+ type_params.any? { |type_param| type_param.name == name.name }
65
+ end
66
+
67
+ # One parameter of `method_type` as selection reads it: `call.argument-type-mismatch` checks a call the
68
+ # same way, and otherwise declines every overload with a `singleton(T)` parameter as naming a class nothing
69
+ # defines.
70
+ def selection_param(param, method_type)
71
+ type_params = method_type.type_params
72
+ return param unless variable_singleton?(param.type, type_params)
73
+
74
+ param.map_type { |type| class_object_type(type.name.name, type_params) }
75
+ end
76
+
77
+ def rewrite(method_type)
78
+ type_params = method_type.type_params
79
+ fun = method_type.type.map_type do |type|
80
+ variable_singleton?(type, type_params) ? class_object_type(type.name.name, type_params) : type
81
+ end
82
+ RBS::MethodType.new(type_params: type_params, type: fun, block: method_type.block,
83
+ location: method_type.location)
84
+ end
85
+
86
+ # Built per rewrite rather than held in a constant: an RBS type is not `Ractor.shareable?` (#1064), and a
87
+ # rewrite happens only for the few overloads that spell `singleton(T)`.
88
+ def module_type
89
+ RBS::Types::ClassInstance.new(
90
+ name: RBS::TypeName.new(name: :Module, namespace: RBS::Namespace.root), args: [], location: nil
91
+ )
92
+ end
93
+
94
+ def class_object_type(name, type_params)
95
+ type_param = type_params.find { |tp| tp.name == name }
96
+ bound = type_param.upper_bound
97
+ return module_type unless bound.is_a?(RBS::Types::ClassInstance)
98
+
99
+ RBS::Types::ClassSingleton.new(name: bound.name, location: nil)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -14,8 +14,8 @@ module Rigor
14
14
  #
15
15
  # A `Struct` instance is mutable (`s.x = v`, `s[:x] = v`, escape), so a member map bound to a
16
16
  # variable can be invalidated by a later write. A member read folds off a **fresh** instance (the
17
- # transient receiver of a `.new(...).x` / `.with(...).x` chain, unmutatable between materialisation
18
- # and the read — slices 1+2), off a **fold-safe stored local** the {Inference::StructFoldSafety} scan
17
+ # transient receiver of a `.new(...).x` chain, unmutatable between materialisation and the read —
18
+ # slices 1+2), off a **fold-safe stored local** the {Inference::StructFoldSafety} scan
19
19
  # proved is never mutated / aliased / escaped (slice 3), or off a **setter-mutated fold-safe local**
20
20
  # whose bindings {#apply_setter_writeback} keeps current (slice 4). Any other stored receiver degrades
21
21
  # to `Dynamic[top]` rather than fold a possibly-stale value.
@@ -255,8 +255,8 @@ module Rigor
255
255
  end
256
256
 
257
257
  # A member read is foldable when the receiver is either FRESH (the transient result of a
258
- # `.new(...)`/`.with(...)` chain, which cannot have been mutated between materialisation and this
259
- # read) or a FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
258
+ # `.new(...)` chain, which cannot have been mutated between materialisation and this read) or a
259
+ # FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
260
260
  # never mutated / aliased / escaped in its scope).
261
261
  def foldable_receiver?(context)
262
262
  fresh_receiver?(context) || fold_safe_local_receiver?(context) ||
@@ -308,7 +308,7 @@ module Rigor
308
308
  end
309
309
 
310
310
  # A fresh receiver is the result of a chained call that MATERIALISED the struct — `Point.new(1, 2).x`,
311
- # `Point[1, 2].x`, `inst.with(x: 9).y`. Not merely a chained call: slices 1-2 read "chained" as
311
+ # `Point[1, 2].x`. Not merely a chained call: slices 1-2 read "chained" as
312
312
  # "cannot have been mutated between materialisation and the read", and a method that hands back its
313
313
  # own receiver falsifies that (issue #595):
314
314
  #
@@ -31,12 +31,10 @@ module Rigor
31
31
  # nodes; the bound exists so a pathological body cannot turn a per-call-site gate into a walk.
32
32
  FACTORY_BODY_SCAN_BUDGET = 200
33
33
 
34
- # Issue #595 / #525 — the ONE materialisation test. Both places that must answer "was this receiver
35
- # expression's struct newly built?" go through it: this module's direct member-read gate above, and
36
- # `ExpressionTyper`'s caller-side `:self`-grant arm. They had forked answers once (the grant arm was
37
- # tightened for #591 while this one still trusted any chained call), so both consumers must share this
38
- # one answer. A `.with` call is never a materialisation: the composed `x.dup_self.with(indent: 9)`
39
- # shape once slipped through as one, and `Struct#with` no longer exists to fold (#1335).
34
+ # Issue #595 / #525 — the ONE materialisation test, shared by {StructFolding#fresh_receiver?} and
35
+ # `ExpressionTyper`'s `:self`-grant arm so the two gates cannot fork. A `.with` call is never a
36
+ # materialisation: the composed `x.dup_self.with(indent: 9)` shape once slipped through as one, and
37
+ # `Struct#with` no longer exists (#1335).
40
38
  #
41
39
  # @param node — the receiver EXPRESSION.
42
40
  # @param scope — the scope the factory and ancestor lookups resolve against.
@@ -55,7 +55,7 @@ module Rigor
55
55
 
56
56
  # A form long enough to suggest generated or accumulated data rather than a literal a reader wrote;
57
57
  # past it the folded `Constant[String]` stops being useful in a diagnostic and only costs memory.
58
- # Mirrors the byte ceiling `ShapeDispatch`'s own `tuple_join` fold applies for the same reason.
58
+ # Counts pairs where `ShapeDispatch`'s `tuple_join` fold caps bytes, for the same reason.
59
59
  FORM_PAIR_LIMIT = 64
60
60
  private_constant :FORM_PAIR_LIMIT
61
61
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../reflection"
4
+ require_relative "../typing_state"
4
5
  require_relative "../type"
5
6
  require_relative "../flow_contribution"
6
7
  require_relative "../flow_contribution/merger"
@@ -18,6 +19,7 @@ require_relative "method_dispatcher/shape_dispatch"
18
19
  require_relative "method_dispatcher/data_folding"
19
20
  require_relative "method_dispatcher/struct_folding"
20
21
  require_relative "method_dispatcher/rbs_dispatch"
22
+ require_relative "method_dispatcher/declared_constructor"
21
23
  require_relative "method_dispatcher/iterator_dispatch"
22
24
  require_relative "method_dispatcher/arithmetic_sequence_elements"
23
25
  require_relative "method_dispatcher/reduce_folding"
@@ -41,6 +43,7 @@ require_relative "method_dispatcher/universal_object_dispatch"
41
43
  require_relative "method_dispatcher/singleton_mixin_dispatch"
42
44
  require_relative "method_dispatcher/method_folding"
43
45
  require_relative "method_dispatcher/optional_count"
46
+ require_relative "../run_scoped_slots"
44
47
 
45
48
  module Rigor
46
49
  module Inference
@@ -194,7 +197,7 @@ module Rigor
194
197
  rbs_result = RbsDispatch.try_dispatch(context)
195
198
  if rbs_result
196
199
  record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
197
- if rbs_result.is_a?(Type::Dynamic) && rbs_result.static_facet.is_a?(Type::Top)
200
+ if Type::Combinator.untyped?(rbs_result)
198
201
  scope&.record_dynamic_origin(call_node, DynamicOrigin::EXPLICIT_UNTYPED)
199
202
  end
200
203
  return rbs_result
@@ -204,8 +207,8 @@ module Rigor
204
207
  # user-authored RBS overrides substrate synthesis) and ABOVE the dependency-source
205
208
  # inference tier so a plugin's declared emit table beats the generic gem-source fallback
206
209
  # for the same class. Slice 6a-TierB (origin_module dispatch) lands precise return types
207
- # for Tier B emissions; Tier C emissions still return `Dynamic[T]` at this tier (slice 6b
208
- # is the Tier C promotion via ADR-13's resolver chain).
210
+ # for Tier B emissions; Tier C emissions return their manifest `return_type` when the environment
211
+ # names that class (slice 6b), and `Dynamic[T]` otherwise.
209
212
  synthetic_result = try_synthetic_method(
210
213
  receiver_type, method_name, arg_types, block_type, environment
211
214
  )
@@ -825,7 +828,7 @@ module Rigor
825
828
  end
826
829
 
827
830
  def rbs_result_untyped?(rbs_result)
828
- rbs_result.is_a?(Type::Dynamic) && rbs_result.static_facet.is_a?(Type::Top)
831
+ Type::Combinator.untyped?(rbs_result)
829
832
  end
830
833
 
831
834
  def rbs_display_for(rbs_result)
@@ -915,8 +918,7 @@ module Rigor
915
918
  result || EMPTY_CONTRIBUTIONS
916
919
  end
917
920
 
918
- # Runs the precision tiers (constant fold, shape dispatch, file-path fold, block fold) in
919
- # order and returns the first non-nil answer. Each tier owns its own receiver/argument
921
+ # Runs the precision tiers in order and returns the first non-nil answer. Each tier owns its own receiver/argument
920
922
  # shape checks; a tier that does not recognise the receiver returns nil so the next tier
921
923
  # can try. The RBS tier sits below this chain and is invoked by the outer `dispatch`
922
924
  # method.
@@ -925,7 +927,7 @@ module Rigor
925
927
  # (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
926
928
  # exact-value folds win first, the stdlib singleton folders sit in the middle (each
927
929
  # gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
928
- # BlockFolding runs last because its rules only apply to block-taking calls — the cheaper
930
+ # BlockFolding runs near the end because its rules only apply to block-taking calls — the cheaper
929
931
  # arity folds above it filter the common cases first. Adding a precise tier is a one-line
930
932
  # append here rather than another link in a hand-written `||` ladder.
931
933
  PRECISE_TIERS_HEAD = Ractor.make_shareable([
@@ -1031,6 +1033,7 @@ module Rigor
1031
1033
  fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
1032
1034
  return nil if fallback_receiver.nil?
1033
1035
  return nil if declared_by_external_ancestor?(receiver_type, environment, context)
1036
+ return nil if project_singleton_definer?(receiver_type, context)
1034
1037
 
1035
1038
  # Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
1036
1039
  # self` and other `self`-returning methods route through Object's RBS while still
@@ -1069,27 +1072,114 @@ module Rigor
1069
1072
  scope = context.scope
1070
1073
  return false if scope.nil?
1071
1074
 
1075
+ class_name = receiver_type.class_name
1076
+ method_name = context.method_name
1077
+ if MemoAudit.enabled? && MemoAudit.suspended?
1078
+ return external_ancestor_declares?(class_name, method_name, scope,
1079
+ environment)
1080
+ end
1081
+
1072
1082
  memo = external_ancestor_memo(scope, environment)
1073
- key = [receiver_type.class_name, context.method_name]
1074
- return memo[key] if memo.key?(key)
1083
+ key = [class_name, method_name]
1084
+ if memo.key?(key)
1085
+ return MemoAudit.check("user class fallback external ancestor", memo[key], class_name, method_name) do
1086
+ external_ancestor_declares?(class_name, method_name, scope, environment)
1087
+ end
1088
+ end
1089
+
1090
+ memo[key] = MemoAudit.key_audit("user class fallback external ancestor") do
1091
+ external_ancestor_declares?(class_name, method_name, scope, environment)
1092
+ end
1093
+ end
1094
+
1095
+ def external_ancestor_declares?(class_name, method_name, scope, environment)
1096
+ !ExternalAncestorResolution.resolve(class_name, method_name, :instance,
1097
+ scope: scope, environment: environment, record_dependencies: false).nil?
1098
+ end
1099
+
1100
+ # Issue #2284 — a class object whose singleton chain holds a project definer of the name (its own `def
1101
+ # self.name`, one a project superclass wrote, an `extend`ed module's `def name`) answers from that definer,
1102
+ # not from `Module#name` / `Class#…` on the `Singleton[Object]` fallback: Ruby's singleton ancestry puts
1103
+ # every project entry ahead of `Class`. The fallback declines so the typer's singleton-body inference
1104
+ # answers the definer's return, or `Dynamic` where the slot is contested and the def may not be the one
1105
+ # that runs.
1106
+ #
1107
+ # The chain walk files the class edge of every entry it passes, so it runs only for a name some project file
1108
+ # defines at all ({BlockCallTiming.project_defines_anywhere?}, which files the `defines:<name>` edge either way,
1109
+ # #1459). Without it every class-body `private` / `attr_reader` call on a project class would depend on its
1110
+ # whole ancestry. The by-name answer is memoised per discovery index; the edge is filed on every call.
1111
+ def project_singleton_definer?(receiver_type, context)
1112
+ return false unless receiver_type.is_a?(Type::Singleton)
1113
+
1114
+ scope = context.scope
1115
+ return false if scope.nil?
1116
+ return false unless project_defines_name?(scope, context.method_name)
1117
+
1118
+ !scope.singleton_def_through_ancestors(receiver_type.class_name, context.method_name).last.nil?
1119
+ end
1075
1120
 
1076
- memo[key] = !ExternalAncestorResolution.resolve(receiver_type.class_name, context.method_name, :instance,
1077
- scope: scope, environment: environment,
1078
- record_dependencies: false).nil?
1121
+ def project_defines_name?(scope, method_name)
1122
+ if MemoAudit.enabled? && MemoAudit.suspended?
1123
+ return BlockCallTiming.project_defines_anywhere?(method_name,
1124
+ scope)
1125
+ end
1126
+
1127
+ memo = defines_name_memo(scope)
1128
+ if memo.key?(method_name)
1129
+ Analysis::DependencyRecorder.read_missing(:defines, method_name) if Analysis::DependencyRecorder.active?
1130
+ return MemoAudit.check("user class fallback defines name", memo[method_name], method_name) do
1131
+ BlockCallTiming.project_defines_anywhere?(method_name, scope)
1132
+ end
1133
+ end
1134
+
1135
+ memo[method_name] = MemoAudit.key_audit("user class fallback defines name") do
1136
+ BlockCallTiming.project_defines_anywhere?(method_name, scope)
1137
+ end
1138
+ end
1139
+
1140
+ # ADR-129 WD1 — a run-scoped Memo: one slot keyed on the identity of the discovery index and the environment,
1141
+ # the only inputs the name table's answers read.
1142
+ DEFINES_NAME_MEMO = TypingState.memo(
1143
+ :user_class_fallback_defines_name,
1144
+ run_scoped: true, audit: "user class fallback defines name",
1145
+ reason: "one [discovery, environment, names] slot keyed on the identities of the discovery " \
1146
+ "index and the environment; each name's answer reads only them"
1147
+ )
1148
+ private_constant :DEFINES_NAME_MEMO
1149
+
1150
+ # One slot per discovery index and environment, as {#external_ancestor_memo} keeps its own: the answer reads
1151
+ # only the frozen discovery tables and the environment's `pre_eval:` patches.
1152
+ def defines_name_memo(scope)
1153
+ discovery = scope.discovery
1154
+ environment = scope.environment
1155
+ state = TypingState.current
1156
+ slot = state[DEFINES_NAME_MEMO]
1157
+ unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1158
+ slot = [discovery, environment, {}]
1159
+ state[DEFINES_NAME_MEMO] = slot
1160
+ end
1161
+ slot[2]
1079
1162
  end
1080
1163
 
1081
- EXTERNAL_ANCESTOR_MEMO_KEY = :__rigor_user_class_fallback_external_ancestor__
1082
- private_constant :EXTERNAL_ANCESTOR_MEMO_KEY
1164
+ # ADR-129 WD1 — a run-scoped Memo, keyed as {DEFINES_NAME_MEMO} is.
1165
+ EXTERNAL_ANCESTOR_MEMO = TypingState.memo(
1166
+ :user_class_fallback_external_ancestor,
1167
+ run_scoped: true, audit: "user class fallback external ancestor",
1168
+ reason: "one [discovery, environment, answers] slot keyed on the identities of the " \
1169
+ "discovery index and the RBS environment, the only inputs the walk reads"
1170
+ )
1171
+ private_constant :EXTERNAL_ANCESTOR_MEMO
1083
1172
 
1084
1173
  # One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
1085
1174
  # reads only the frozen discovery tables and the RBS environment, and its walk is withheld from dependency
1086
1175
  # recording, so it may be served again for the same pair.
1087
1176
  def external_ancestor_memo(scope, environment)
1088
1177
  discovery = scope.discovery
1089
- slot = Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY]
1178
+ state = TypingState.current
1179
+ slot = state[EXTERNAL_ANCESTOR_MEMO]
1090
1180
  unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1091
1181
  slot = [discovery, environment, {}]
1092
- Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY] = slot
1182
+ state[EXTERNAL_ANCESTOR_MEMO] = slot
1093
1183
  end
1094
1184
  slot[2]
1095
1185
  end
@@ -1138,12 +1228,14 @@ module Rigor
1138
1228
  #
1139
1229
  # `RbsDispatch` already distributes over a `Union` (and erases a `Difference` to its base, #533),
1140
1230
  # but it does so INSIDE one tier, so a member that needs a LOWER tier — a user class with no RBS,
1141
- # a discovered `def`, an opt-in gem — makes the whole union decline. That is the corpus shape:
1142
- # rigor-actionpack types `params[:k]` as `ActionController::Parameters | nil`, the `Parameters` arm
1143
- # resolves only through the user-class fallback, and so `params[:k].present?` / `#==` / `#blank?` /
1144
- # `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the named-pair
1145
- # census drains 137 sites on redmine and 56 on mastodon, and `rigor check` reports the same
1146
- # diagnostics on both arms across eight survey targets.
1231
+ # a discovered `def`, an opt-in gem — makes the whole union decline. That was the corpus shape before
1232
+ # #2430: rigor-actionpack typed `params[:k]` as the bare `ActionController::Parameters | nil` union, the
1233
+ # `Parameters` arm resolved only through the user-class fallback, and so `params[:k].present?` / `#==`
1234
+ # / `#blank?` / `#to_s` read opaque on every Rails app. Measured by toggling this file alone: the
1235
+ # named-pair census drained 137 sites on redmine and 56 on mastodon, and `rigor check` reported the
1236
+ # same diagnostics on both arms across eight survey targets. The plugin now answers a `Dynamic`
1237
+ # carrier, which this projection does not reach, so those off-chain calls read `Dynamic[top]` until
1238
+ # the carrier is projected here too (#2448).
1147
1239
  #
1148
1240
  # **Why the bottom and not next to `RbsDispatch`.** Sitting here, the tier can only ever replace a
1149
1241
  # `Dynamic[top]` the typer was about to produce — the same ADR-5 argument {UniversalObjectDispatch}
@@ -1314,7 +1406,13 @@ module Rigor
1314
1406
  class_new_lift = class_new_lift(receiver_type.class_name, arg_types, context)
1315
1407
  return class_new_lift if class_new_lift
1316
1408
 
1317
- Type::Combinator.nominal_of(receiver_type.class_name)
1409
+ declared_or_nominal(receiver_type.class_name, context)
1410
+ end
1411
+
1412
+ # Issue #2884 — the `meta_new` tail: a declared `def self.new` ({DeclaredConstructor}), else `Nominal[X]`.
1413
+ def declared_or_nominal(class_name, context)
1414
+ declared = context && DeclaredConstructor.try_dispatch(class_name, context)
1415
+ declared || Type::Combinator.nominal_of(class_name)
1318
1416
  end
1319
1417
 
1320
1418
  # `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass* (a class object), not a
@@ -1346,8 +1444,8 @@ module Rigor
1346
1444
  # class is representable as the parent's singleton type — its singleton-method surface is
1347
1445
  # the parent's (plus whatever the block defines, which we do not statically track here),
1348
1446
  # so `Singleton[Parent]` lets downstream `klass.some_class_method` resolve. No parent →
1349
- # `singleton(Object)`. Anything else (dynamic parent, more than one positional, …) falls
1350
- # back to `Nominal[Class]` via the surrounding `meta_new` tail.
1447
+ # `singleton(Object)` (or the block's anonymous class, #319 below). Anything else (dynamic parent, more
1448
+ # than one positional, …) falls back to `Nominal[Class]` via the surrounding `meta_new` tail.
1351
1449
  # #319 — a `Class.new do ... end` with no parent is NOT `Object`: the block body is a class body, so the
1352
1450
  # class owns whatever that body defines. Answering `Singleton[Object]` handed the call site `Object`'s
1353
1451
  # zero-arity `new` and `Object`'s method surface, so an `initialize(bucket)` written right there produced