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
@@ -15,11 +15,32 @@ module Rigor
15
15
  # Hash on every `mixins_for` query.
16
16
  EMPTY_MIXINS = { include: [].freeze, extend: [].freeze }.freeze
17
17
 
18
+ # Issue #2541 — the sorbet-runtime struct roots whose `prop` / `const` calls define accessors
19
+ # (`T::Props::ClassMethods#prop` → `T::Props::Decorator#add_prop_definition` defines the reader,
20
+ # and the writer for `prop` only; `const` is `prop(..., immutable: true)`). `T::ImmutableStruct`
21
+ # overrides `prop` to raise, so a writer is never answered under it.
22
+ STRUCT_ROOTS = %w[T::Struct T::InexactStruct T::ImmutableStruct].freeze
23
+ IMMUTABLE_STRUCT_ROOT = "T::ImmutableStruct"
24
+
18
25
  def initialize
19
26
  @entries = {}
20
27
  # ADR-11 slice 8 — per-class mixin declarations collected by `CatalogWalker`. Lookup-time chain
21
28
  # traversal lifts sigs declared on a mixed-in module to call sites on the host class.
22
29
  @mixins = {}
30
+ # Issue #2541 — `prop` / `const` accessors recorded per declaring class, the lexical superclass of
31
+ # every `class X < Y` the walker saw, and the instance methods each class body `def`s itself. A
32
+ # struct accessor answers only when the declaring class reaches a {STRUCT_ROOTS} root through
33
+ # `@superclasses`, and a class's own `def` of the same name shadows it (Ruby: the later `def`
34
+ # replaces the generated accessor; a subclass `def` overrides it).
35
+ @struct_props = {}
36
+ @struct_prop_types = {}
37
+ @struct_nesting = {}
38
+ @namespaces = {}
39
+ @constants = {}
40
+ @struct_includes = {}
41
+ @mixes_in = {}
42
+ @superclasses = {}
43
+ @defined_methods = {}
23
44
  @frozen_after_build = false
24
45
  end
25
46
 
@@ -42,8 +63,153 @@ module Rigor
42
63
  list << module_name unless list.include?(module_name)
43
64
  end
44
65
 
66
+ # @param signature — a {MethodSignature} for a `prop` / `const` reader or a `prop` writer.
67
+ # @param certain — false for a call nested under a conditional or a block, which never displaces
68
+ # an accessor already recorded for the name; a certain one replaces whatever is there.
69
+ # @param type_source — the source text of the macro's type argument, re-translated at lookup with
70
+ # its constants resolved against `nesting` ({#struct_prop_type}); nil when the accessor is untyped.
71
+ # @param nesting — the declaring class's lexical nesting, outermost first, its own name last.
72
+ def record_struct_prop(signature, certain: true, type_source: nil, nesting: [])
73
+ raise "Catalog already finalised" if @frozen_after_build
74
+
75
+ props = (@struct_props[signature.class_name] ||= {})
76
+ return unless certain || !props.key?(signature.method_name)
77
+
78
+ props[signature.method_name] = signature
79
+ @struct_prop_types[[signature.class_name, signature.method_name]] = type_source
80
+ @struct_nesting[signature.class_name] ||= nesting.dup.freeze
81
+ end
82
+
83
+ # Issue #2541 — every class and module name the walk declared, so a struct accessor's relative
84
+ # constant resolves to a project class the RBS environment may not know.
85
+ def record_namespace(name)
86
+ raise "Catalog already finalised" if @frozen_after_build
87
+
88
+ @namespaces[name] = true
89
+ end
90
+
91
+ def namespace?(name)
92
+ @namespaces.key?(name)
93
+ end
94
+
95
+ # A constant the walk saw assigned (`Thing = Other`) rather than declared as a class or module.
96
+ def record_constant(name)
97
+ raise "Catalog already finalised" if @frozen_after_build
98
+
99
+ @constants[name] = true
100
+ end
101
+
102
+ def constant?(name)
103
+ @constants.key?(name)
104
+ end
105
+
106
+ def record_struct_include(class_name, module_name)
107
+ raise "Catalog already finalised" if @frozen_after_build
108
+
109
+ list = (@struct_includes[class_name] ||= [])
110
+ list << module_name unless list.include?(module_name)
111
+ end
112
+
113
+ def record_mixes_in(class_name)
114
+ raise "Catalog already finalised" if @frozen_after_build
115
+
116
+ @mixes_in[class_name] = true
117
+ end
118
+
119
+ # Issue #2541 — whether any class on `class_name`'s walked superclass chain, itself included,
120
+ # `include`s or `prepend`s a module.
121
+ def mixes_in_ancestry?(class_name)
122
+ chain = []
123
+ current = class_name.to_s.delete_prefix("::")
124
+ until current.nil? || chain.include?(current) || STRUCT_ROOTS.include?(current)
125
+ return true if @mixes_in.key?(current)
126
+
127
+ chain << current
128
+ current = resolve_superclass(current)
129
+ end
130
+ false
131
+ end
132
+
133
+ # Every walked class and module name and every assigned constant name, for the plugin's check that
134
+ # no other `*::<name>` could shadow a top-level constant.
135
+ def declared_constant_names
136
+ @namespaces.keys + @constants.keys
137
+ end
138
+
139
+ # Issue #2541 — the classes whose constants a struct body's relative constant could reach through
140
+ # ancestry: the class itself, its walked superclasses below the struct root, and the modules each of
141
+ # them `include`s (as written).
142
+ def constant_ancestry(class_name)
143
+ chain = []
144
+ current = class_name.to_s.delete_prefix("::")
145
+ until current.nil? || chain.include?(current) || STRUCT_ROOTS.include?(current)
146
+ chain << current
147
+ current = resolve_superclass(current)
148
+ end
149
+ chain + chain.flat_map { |name| @struct_includes.fetch(name, []).map { |mod| mod.delete_prefix("::") } }
150
+ end
151
+
152
+ # @return the type source and the nesting for the accessor `signature` names, or nil when it has no type
153
+ # source (a conditional or type-less macro).
154
+ def struct_prop_type(signature)
155
+ source = @struct_prop_types[[signature.class_name, signature.method_name]]
156
+ return nil if source.nil?
157
+
158
+ [source, @struct_nesting.fetch(signature.class_name, [])]
159
+ end
160
+
161
+ # @param written — the superclass expression as written (`"T::Struct"`, `"Base"`, `"::Foo::Base"`).
162
+ # @param lexical_prefix — the enclosing namespace names, outermost first, that Ruby resolves a
163
+ # relative `written` against.
164
+ def record_superclass(class_name:, written:, lexical_prefix:)
165
+ raise "Catalog already finalised" if @frozen_after_build
166
+
167
+ @superclasses[class_name] ||= [written, lexical_prefix.dup.freeze].freeze
168
+ end
169
+
170
+ def record_defined_method(class_name:, method_name:)
171
+ raise "Catalog already finalised" if @frozen_after_build
172
+
173
+ list = (@defined_methods[class_name] ||= [])
174
+ list << method_name unless list.include?(method_name)
175
+ end
176
+
177
+ # Issue #2541 — the signature a `prop` / `const` accessor contributes to `class_name#method_name`,
178
+ # walking the lexical superclass chain the way Ruby's method lookup does. A class on the way that
179
+ # `def`s the name itself (or declares it with `attr_*`) stops the walk with nil, leaving the user's
180
+ # method to the sig lookup and the engine's own tiers. Returns nil unless the declaring class descends from a {STRUCT_ROOTS} root, so a
181
+ # project DSL that happens to be named `prop` is never read as Sorbet's.
182
+ def lookup_struct_prop(class_name:, method_name:)
183
+ return nil if @struct_props.empty?
184
+
185
+ current = class_name.to_s.delete_prefix("::")
186
+ visited = []
187
+ while current && !visited.include?(current)
188
+ visited << current
189
+ return nil if @defined_methods[current]&.include?(method_name)
190
+
191
+ signature = @struct_props.dig(current, method_name)
192
+ return struct_answer(signature, current) if signature
193
+
194
+ current = resolve_superclass(current)
195
+ end
196
+ nil
197
+ end
198
+
45
199
  def freeze!
46
200
  @frozen_after_build = true
201
+ @struct_props.each_value(&:freeze)
202
+ @struct_props.freeze
203
+ @struct_prop_types.freeze
204
+ @struct_nesting.freeze
205
+ @namespaces.freeze
206
+ @constants.freeze
207
+ @struct_includes.each_value(&:freeze)
208
+ @struct_includes.freeze
209
+ @mixes_in.freeze
210
+ @superclasses.freeze
211
+ @defined_methods.each_value(&:freeze)
212
+ @defined_methods.freeze
47
213
  @entries.freeze
48
214
  @mixins.each_value do |bucket|
49
215
  bucket.each_value(&:freeze)
@@ -57,14 +223,14 @@ module Rigor
57
223
  @entries[key_for(class_name, method_name, kind)]
58
224
  end
59
225
 
60
- # @return frozen mapping `{ include: [...], extend: [...] }`.
226
+ # @return frozen `:include` / `:extend` buckets of mixed-in module names.
61
227
  # Returns {EMPTY_MIXINS} when no mixins were recorded.
62
228
  def mixins_for(class_name)
63
229
  @mixins[class_name] || EMPTY_MIXINS
64
230
  end
65
231
 
66
232
  def empty?
67
- @entries.empty?
233
+ @entries.empty? && @struct_props.empty?
68
234
  end
69
235
 
70
236
  # ADR-52 slice 4 — the distinct method names the catalog carries at least one signature for,
@@ -74,7 +240,9 @@ module Rigor
74
240
  # Computed fresh per call — the plugin memoises the resolved set, and `freeze!` freezes the
75
241
  # catalog itself so a lazy memo ivar here would raise.
76
242
  def method_names
77
- @entries.keys.map { |key| key[1] }.uniq
243
+ names = @entries.keys.map { |key| key[1] }
244
+ @struct_props.each_value { |props| names.concat(props.keys) }
245
+ names.uniq
78
246
  end
79
247
 
80
248
  def size
@@ -83,6 +251,45 @@ module Rigor
83
251
 
84
252
  private
85
253
 
254
+ def struct_answer(signature, declaring_class)
255
+ root = struct_root(declaring_class)
256
+ return nil if root.nil?
257
+ return nil if root == IMMUTABLE_STRUCT_ROOT && signature.method_name.end_with?("=")
258
+
259
+ signature
260
+ end
261
+
262
+ def struct_root(class_name)
263
+ current = class_name
264
+ visited = []
265
+ while current && !visited.include?(current)
266
+ return current if STRUCT_ROOTS.include?(current)
267
+
268
+ visited << current
269
+ current = resolve_superclass(current)
270
+ end
271
+ nil
272
+ end
273
+
274
+ # Resolves the superclass expression recorded for `class_name` the way Ruby's lexical constant lookup
275
+ # would, as far as the walked sources can tell: a rooted `::Foo` is absolute; a relative `Foo` is tried
276
+ # under each enclosing namespace, innermost first, against the classes the walk saw, then as written.
277
+ def resolve_superclass(class_name)
278
+ written, prefix = @superclasses[class_name]
279
+ return nil if written.nil?
280
+ return written.delete_prefix("::") if written.start_with?("::")
281
+
282
+ prefix.size.downto(1) do |depth|
283
+ candidate = "#{prefix.first(depth).join('::')}::#{written}"
284
+ return candidate if known_class?(candidate)
285
+ end
286
+ written
287
+ end
288
+
289
+ def known_class?(name)
290
+ @superclasses.key?(name) || @struct_props.key?(name)
291
+ end
292
+
86
293
  def key_for(class_name, method_name, kind)
87
294
  [class_name.to_s, method_name.to_sym, kind]
88
295
  end
@@ -29,16 +29,20 @@ module Rigor
29
29
  # @param root — the file's program node.
30
30
  # @param catalog — mutable; signatures are recorded into it.
31
31
  # @param path — file path used for diagnostic provenance.
32
+ # @param struct_catalog — where the `T::Struct`-family accessors, superclasses, and own `def`s are
33
+ # recorded (issue #2541). Defaults to `catalog`; the plugin passes the per-run catalog here even
34
+ # when `catalog` is the discard catalog of an unenforced-sigil file, because sorbet-runtime defines
35
+ # a struct's accessors whatever the file's `# typed:` level.
32
36
  # @return errors observed during the walk; empty when the file is sig-clean.
33
- def walk(root:, catalog:, path:)
34
- state = State.new(catalog: catalog, path: path)
37
+ def walk(root:, catalog:, path:, struct_catalog: catalog)
38
+ state = State.new(catalog: catalog, path: path, struct_catalog: struct_catalog)
35
39
  walk_node(root, state, lexical_path: [], in_singleton_class: false)
36
40
  state.errors
37
41
  end
38
42
 
39
- State = Struct.new(:catalog, :path, :errors, keyword_init: true) do
40
- def initialize(catalog:, path:)
41
- super(catalog: catalog, path: path, errors: [])
43
+ State = Struct.new(:catalog, :path, :errors, :struct_catalog, keyword_init: true) do
44
+ def initialize(catalog:, path:, struct_catalog: catalog)
45
+ super(catalog: catalog, path: path, errors: [], struct_catalog: struct_catalog)
42
46
  end
43
47
 
44
48
  def record_error(kind, node)
@@ -50,7 +54,9 @@ module Rigor
50
54
  return unless node.is_a?(Prism::Node)
51
55
 
52
56
  case node
53
- when Prism::ClassNode, Prism::ModuleNode
57
+ when Prism::ClassNode
58
+ descend_class(node, state, lexical_path)
59
+ when Prism::ModuleNode
54
60
  descend_class_or_module(node, state, lexical_path)
55
61
  when Prism::SingletonClassNode
56
62
  descend_singleton_class(node, state, lexical_path)
@@ -59,6 +65,20 @@ module Rigor
59
65
  when Prism::DefNode
60
66
  # A `def` not preceded by a `sig` is fine; we just don't record anything for it. The
61
67
  # interesting case is in `walk_statements`, which pairs sig+def.
68
+ when Prism::CallNode
69
+ # Issue #2541 — any `include` / `prepend` in a class (whatever its arguments) opens the class's
70
+ # ancestry, so a struct accessor's constant is no longer answered from the top level.
71
+ if node.receiver.nil? && %i[include prepend].include?(node.name) && !lexical_path.empty?
72
+ state.struct_catalog.record_mixes_in(lexical_path.join("::"))
73
+ end
74
+ node.rigor_each_child do |child|
75
+ walk_node(child, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
76
+ end
77
+ when Prism::ConstantWriteNode
78
+ # Issue #2541 — `Thing = Other` makes `Thing` a constant that is not a class declaration, so a
79
+ # struct accessor's type naming it is not read as any class.
80
+ state.struct_catalog.record_constant((lexical_path + [node.name.to_s]).join("::"))
81
+ walk_node(node.value, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
62
82
  else
63
83
  node.rigor_each_child do |child|
64
84
  walk_node(child, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
@@ -66,8 +86,25 @@ module Rigor
66
86
  end
67
87
  end
68
88
 
89
+ # Issue #2541 — a `class` records its superclass, and only its body's own top-level statements,
90
+ # which certainly run at class definition, may declare `prop` / `const` accessors.
91
+ def descend_class(node, state, lexical_path)
92
+ name = qualified_name_for(node.constant_path)
93
+ return descend_class_or_module(node, state, lexical_path) unless name
94
+
95
+ record_superclass(node, name, state, lexical_path)
96
+ state.struct_catalog.record_namespace((lexical_path + [name]).join("::"))
97
+ if node.body.is_a?(Prism::StatementsNode)
98
+ walk_statements(node.body, state, lexical_path: lexical_path + [name], in_singleton_class: false,
99
+ class_body: true)
100
+ else
101
+ descend_class_or_module(node, state, lexical_path)
102
+ end
103
+ end
104
+
69
105
  def descend_class_or_module(node, state, lexical_path)
70
106
  name = qualified_name_for(node.constant_path)
107
+ state.struct_catalog.record_namespace((lexical_path + [name]).join("::")) if name
71
108
  if name && node.body
72
109
  child_prefix = lexical_path + [name]
73
110
  walk_node(node.body, state, lexical_path: child_prefix, in_singleton_class: false)
@@ -88,10 +125,13 @@ module Rigor
88
125
  # `sig` call, it remembers it and consumes the very next `def` / `def self.foo` as the target.
89
126
  # Anything between a sig and its def (a comment is fine — comments aren't AST nodes — but a
90
127
  # method call would be a problem) leaves the sig dangling.
91
- def walk_statements(statements, state, lexical_path:, in_singleton_class:)
128
+ def walk_statements(statements, state, lexical_path:, in_singleton_class:, class_body: false)
92
129
  pending_sig = nil
93
130
 
94
131
  statements.body.each do |child|
132
+ record_defined_method(child, state, lexical_path) unless in_singleton_class
133
+ record_struct_prop_call(child, state, lexical_path, certain: class_body) unless in_singleton_class
134
+
95
135
  if pending_sig && def_node?(child)
96
136
  record_def_with_sig(child, pending_sig, state, lexical_path, in_singleton_class)
97
137
  pending_sig = nil
@@ -153,9 +193,108 @@ module Rigor
153
193
  state.catalog.record_mixin(
154
194
  class_name: class_name, kind: kind, module_name: module_name
155
195
  )
196
+ # Issue #2541 — an included module's constants shadow a top-level one for the class body.
197
+ state.struct_catalog.record_struct_include(class_name, module_name) if kind == :include
198
+ end
199
+ end
200
+
201
+ # Issue #2541 — `class X < Y`: the superclass as written plus the enclosing namespaces, so the
202
+ # catalog can follow `X`'s chain to a `T::Struct`-family root. `lexical_path` holds the enclosing
203
+ # names (a nested `class A::B` element counts as one namespace step, as Ruby's cref does).
204
+ def record_superclass(node, name, state, lexical_path)
205
+ written = qualified_name_for(node.superclass)
206
+ return if written.nil?
207
+
208
+ state.struct_catalog.record_superclass(
209
+ class_name: (lexical_path + [name]).join("::"), written: written, lexical_prefix: lexical_path
210
+ )
211
+ end
212
+
213
+ # The instance methods a class body defines itself — `def foo`, `private def foo`, `attr_reader :foo` /
214
+ # `attr_writer` / `attr_accessor` — so a struct accessor of the same name yields to them.
215
+ def record_defined_method(node, state, lexical_path)
216
+ return if lexical_path.empty?
217
+
218
+ class_name = lexical_path.join("::")
219
+ defined_method_names(node).each do |method_name|
220
+ state.struct_catalog.record_defined_method(class_name: class_name, method_name: method_name)
221
+ end
222
+ end
223
+
224
+ def defined_method_names(node)
225
+ target = visibility_wrapped_def(node) || node
226
+ if def_node?(target)
227
+ target.receiver.nil? ? [target.name] : []
228
+ elsif attr_macro_call?(target)
229
+ attr_method_names(target)
230
+ else
231
+ []
156
232
  end
157
233
  end
158
234
 
235
+ def attr_method_names(attr_node)
236
+ attr_node.arguments.arguments.flat_map do |arg|
237
+ name = arg.unescaped
238
+ case attr_node.name
239
+ when :attr_reader then [name.to_sym]
240
+ when :attr_writer then [:"#{name}="]
241
+ else [name.to_sym, :"#{name}="]
242
+ end
243
+ end
244
+ end
245
+
246
+ STRUCT_PROP_MACROS = %i[prop const].freeze
247
+
248
+ # Issue #2541 — `prop :name, Type, **rules` / `const :name, Type, **rules` in a class body. Records
249
+ # the reader (both macros) and the writer (`prop` only); the catalog decides at lookup whether the
250
+ # class is a `T::Struct`-family class at all. At the body's top level (`certain:`) the accessor is
251
+ # typed by the translated `Type`. Nested under an `if`, a `begin`, or a block in the body, the call
252
+ # may not run, so the accessor is recorded as possible: typed `Dynamic[top]`, which keeps a call
253
+ # to it quiet without claiming its type. A name that is not a literal Symbol records nothing
254
+ # (`T::Props` takes a Symbol).
255
+ def record_struct_prop_call(node, state, lexical_path, certain:)
256
+ return if lexical_path.empty?
257
+ return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
258
+ return unless STRUCT_PROP_MACROS.include?(node.name)
259
+
260
+ args = node.arguments&.arguments
261
+ return if args.nil? || args.empty?
262
+
263
+ return unless args.first.is_a?(Prism::SymbolNode)
264
+
265
+ name = args.first.unescaped
266
+
267
+ type_node = args[1] unless args[1].is_a?(Prism::KeywordHashNode)
268
+ # The constants in the type resolve at lookup, against the classes every file declares, so only
269
+ # the source text is kept; the signature's own type is the placeholder until then.
270
+ type_source = type_node&.slice if certain
271
+ class_name = lexical_path.join("::")
272
+ record_struct_accessor(state, class_name, name.to_sym, type_source, lexical_path, certain)
273
+ return unless node.name == :prop && !immutable_rule?(args)
274
+
275
+ record_struct_accessor(state, class_name, :"#{name}=", type_source, lexical_path, certain)
276
+ end
277
+
278
+ # `prop :x, T, immutable: true` is the long form of `const`: no writer.
279
+ def immutable_rule?(args)
280
+ args.any? do |arg|
281
+ arg.is_a?(Prism::KeywordHashNode) && arg.elements.any? do |element|
282
+ element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode) &&
283
+ element.key.unescaped == "immutable" && element.value.is_a?(Prism::TrueNode)
284
+ end
285
+ end
286
+ end
287
+
288
+ def record_struct_accessor(state, class_name, method_name, type_source, nesting, certain)
289
+ state.struct_catalog.record_struct_prop(
290
+ MethodSignature.new(
291
+ class_name: class_name, method_name: method_name, kind: :instance,
292
+ params: {}, return_type: Rigor::Type::Combinator.untyped, modifiers: []
293
+ ),
294
+ certain: certain, type_source: type_source, nesting: nesting
295
+ )
296
+ end
297
+
159
298
  def sig_call?(node)
160
299
  node.is_a?(Prism::CallNode) &&
161
300
  node.name == :sig &&
@@ -22,8 +22,8 @@ module Rigor
22
22
  #
23
23
  # The parser walks the chain right-to-left, gathering whatever it recognises (`params` / `returns` /
24
24
  # `void` / `abstract` / `override` / `overridable` / `final` / `type_parameters` / `checked` /
25
- # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers and
26
- # `type_parameters` are recorded but not yet acted on.
25
+ # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers are recorded but not
26
+ # yet acted on; `type_parameters` is recognised and its payload discarded.
27
27
  #
28
28
  # The parser is intentionally tolerant — unknown chain nodes degrade to "the rest of the chain is
29
29
  # opaque" rather than raising. The plugin emits a diagnostic (`plugin.sorbet.parse-error`) only when
@@ -52,28 +52,33 @@ module Rigor
52
52
 
53
53
  module_function
54
54
 
55
+ # @param resolve — optional callable mapping an unrooted constant name as written (`"Thing"`,
56
+ # `"Foo::Bar"`) to the class it names, or to nil when that is not certain, in which case the
57
+ # constant degrades to `Dynamic[top]`. Issue #2541 passes it for `T::Struct` accessor types; without
58
+ # it (every sig) a constant becomes `Nominal[<as written>]`, as before. The `T::` vocabulary is
59
+ # never handed to it.
55
60
  # @return never `nil`; unrecognised forms degrade to `Type::Combinator.untyped`.
56
- def translate(node)
61
+ def translate(node, resolve = nil)
57
62
  return Rigor::Type::Combinator.untyped if node.nil?
58
63
 
59
64
  case node
60
- when Prism::ConstantReadNode then translate_constant_read(node)
61
- when Prism::ConstantPathNode then translate_constant_path(node)
62
- when Prism::CallNode then translate_call(node)
63
- when Prism::ArrayNode then translate_tuple(node)
64
- when Prism::HashNode then translate_shape(node)
65
+ when Prism::ConstantReadNode then translate_constant_read(node, resolve)
66
+ when Prism::ConstantPathNode then translate_constant_path(node, resolve)
67
+ when Prism::CallNode then translate_call(node, resolve)
68
+ when Prism::ArrayNode then translate_tuple(node, resolve)
69
+ when Prism::HashNode then translate_shape(node, resolve)
65
70
  else degraded
66
71
  end
67
72
  end
68
73
 
69
- def translate_constant_read(node)
70
- name = node.name.to_s
71
- return Rigor::Type::Combinator.untyped if name.empty?
74
+ def translate_constant_read(node, resolve = nil)
75
+ name = resolved_name(node.name.to_s, resolve)
76
+ return Rigor::Type::Combinator.untyped if name.nil? || name.empty?
72
77
 
73
78
  Rigor::Type::Combinator.nominal_of(name)
74
79
  end
75
80
 
76
- def translate_constant_path(node)
81
+ def translate_constant_path(node, resolve = nil)
77
82
  name = constant_path_name(node)
78
83
  return degraded if name.nil?
79
84
 
@@ -81,9 +86,20 @@ module Rigor
81
86
  # type alias.
82
87
  return boolean_type if name == "T::Boolean"
83
88
 
89
+ name = resolved_name(name, resolve)
90
+ return degraded if name.nil?
91
+
84
92
  Rigor::Type::Combinator.nominal_of(name)
85
93
  end
86
94
 
95
+ # The constant `name` as `resolve` reads it (a rooted `::Foo` included); `name` itself when there is
96
+ # no resolver.
97
+ def resolved_name(name, resolve)
98
+ return name if resolve.nil?
99
+
100
+ resolve.call(name)
101
+ end
102
+
87
103
  # `Prism::CallNode` covers three distinct surfaces:
88
104
  #
89
105
  # 1. `T.something(...)` — `untyped` / `anything` / `noreturn` / `nilable` / `any` / `all` /
@@ -96,58 +112,59 @@ module Rigor
96
112
  # instantiation intact (the unwrap-site receiver a plugin like rigor-mangrove reads `type_args`
97
113
  # from). Without this branch such forms degraded to `untyped`, silently dropping the
98
114
  # receiver's generic arguments.
99
- def translate_call(node)
100
- return translate_t_method(node) if sorbet_t_namespaced?(node.receiver)
101
- return translate_t_subscript(node) if sorbet_subscript?(node)
102
- return translate_user_subscript(node) if user_generic_subscript?(node)
115
+ def translate_call(node, resolve = nil)
116
+ return translate_t_method(node, resolve) if sorbet_t_namespaced?(node.receiver)
117
+ return translate_t_subscript(node, resolve) if sorbet_subscript?(node)
118
+ return translate_user_subscript(node, resolve) if user_generic_subscript?(node)
103
119
 
104
120
  degraded
105
121
  end
106
122
 
107
123
  # Handles the `T.foo(...)` family.
108
- def translate_t_method(node)
124
+ def translate_t_method(node, resolve = nil)
109
125
  case node.name
110
126
  when :untyped then Rigor::Type::Combinator.untyped
111
127
  when :anything then Rigor::Type::Combinator.top
112
128
  when :noreturn then Rigor::Type::Combinator.bot
113
- when :nilable then translate_nilable(node)
114
- when :any then translate_any(node)
115
- when :all then translate_all(node)
116
- when :class_of then translate_class_of(node)
129
+ when :nilable then translate_nilable(node, resolve)
130
+ when :any then translate_any(node, resolve)
131
+ when :all then translate_all(node, resolve)
132
+ when :class_of then translate_class_of(node, resolve)
117
133
  else degraded
118
134
  end
119
135
  end
120
136
 
121
- def translate_nilable(node)
137
+ def translate_nilable(node, resolve = nil)
122
138
  inner = first_argument(node)
123
139
  return degraded if inner.nil?
124
140
 
125
141
  Rigor::Type::Combinator.union(
126
- translate(inner), Rigor::Type::Combinator.constant_of(nil)
142
+ translate(inner, resolve), Rigor::Type::Combinator.constant_of(nil)
127
143
  )
128
144
  end
129
145
 
130
- def translate_any(node)
146
+ def translate_any(node, resolve = nil)
131
147
  args = call_arguments(node)
132
148
  return degraded if args.empty?
133
149
 
134
- Rigor::Type::Combinator.union(*args.map { |arg| translate(arg) })
150
+ Rigor::Type::Combinator.union(*args.map { |arg| translate(arg, resolve) })
135
151
  end
136
152
 
137
- def translate_all(node)
153
+ def translate_all(node, resolve = nil)
138
154
  args = call_arguments(node)
139
155
  return degraded if args.empty?
140
156
 
141
- Rigor::Type::Combinator.intersection(*args.map { |arg| translate(arg) })
157
+ Rigor::Type::Combinator.intersection(*args.map { |arg| translate(arg, resolve) })
142
158
  end
143
159
 
144
160
  # `T.class_of(C)` — singleton-class type for a single constant. Sorbet docs note
145
161
  # `T.class_of(MyInterface)` rarely means what users expect (it's the singleton class of
146
162
  # `MyInterface`, not "any class implementing the interface"); we honour the literal meaning here
147
163
  # and translate to `Singleton[C]`.
148
- def translate_class_of(node)
164
+ def translate_class_of(node, resolve = nil)
149
165
  target = first_argument(node)
150
166
  name = constant_path_name(target)
167
+ name = resolved_name(name, resolve) unless name.nil?
151
168
  return degraded if name.nil?
152
169
 
153
170
  Rigor::Type::Combinator.singleton_of(name)
@@ -157,9 +174,9 @@ module Rigor
157
174
  # `CallNode` whose receiver is the `T::Array` `ConstantPathNode` and whose `name` is `:[]`.
158
175
  # `T::Class[T]` lands here too; we collapse it to `Singleton[name]` (a deliberate narrowing —
159
176
  # `T::Class` is structurally generic in Sorbet, but Rigor's `Singleton` carries class identity only).
160
- def translate_t_subscript(node)
177
+ def translate_t_subscript(node, resolve = nil)
161
178
  base_name = sorbet_subscript_base(node.receiver)
162
- args = call_arguments(node).map { |arg| translate(arg) }
179
+ args = call_arguments(node).map { |arg| translate(arg, resolve) }
163
180
  mapped = T_GENERIC_CLASSES[base_name]
164
181
 
165
182
  if mapped
@@ -188,13 +205,14 @@ module Rigor
188
205
  !sorbet_t_qualified?(receiver)
189
206
  end
190
207
 
191
- def translate_user_subscript(node)
208
+ def translate_user_subscript(node, resolve = nil)
192
209
  # Parity with translate_constant_path: the constant's rendered name (carrying a leading `::` for
193
210
  # absolute paths) becomes the nominal class name.
194
211
  name = constant_path_name(node.receiver)
212
+ name = resolved_name(name, resolve) unless name.nil?
195
213
  return degraded if name.nil?
196
214
 
197
- args = call_arguments(node).map { |arg| translate(arg) }
215
+ args = call_arguments(node).map { |arg| translate(arg, resolve) }
198
216
  Rigor::Type::Combinator.nominal_of(name, type_args: args)
199
217
  end
200
218
 
@@ -214,21 +232,21 @@ module Rigor
214
232
 
215
233
  # Tuple types in `sig` position appear as bare array literals: `sig { returns([String, Integer]) }`.
216
234
  # Each element is itself a type expression we translate recursively.
217
- def translate_tuple(node)
218
- elements = node.elements.map { |element| translate(element) }
235
+ def translate_tuple(node, resolve = nil)
236
+ elements = node.elements.map { |element| translate(element, resolve) }
219
237
  Rigor::Type::Combinator.tuple_of(*elements)
220
238
  end
221
239
 
222
240
  # Shape types in `sig` position appear as bare hash literals with symbol keys:
223
241
  # `sig { returns({a: Integer, b: String}) }`. Each value is a type expression; the resulting
224
242
  # `HashShape` is closed (no extra keys allowed).
225
- def translate_shape(node)
243
+ def translate_shape(node, resolve = nil)
226
244
  pairs = []
227
245
  node.elements.each do |element|
228
246
  next unless element.is_a?(Prism::AssocNode)
229
247
  next unless element.key.is_a?(Prism::SymbolNode)
230
248
 
231
- pairs << [element.key.unescaped.to_sym, translate(element.value)]
249
+ pairs << [element.key.unescaped.to_sym, translate(element.value, resolve)]
232
250
  end
233
251
  # `.to_h`: `hash_shape_of` validates its argument is a Hash and raises `ArgumentError` on the
234
252
  # array-of-pairs form, so `sig { returns({name: String}) }` degraded to `Dynamic[top]` through