rigortype 0.4.7 → 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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  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 +29 -0
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/docs/handbook/01-getting-started.md +1 -1
  8. data/docs/handbook/11-sig-gen.md +35 -0
  9. data/docs/install.md +20 -0
  10. data/docs/manual/01-installation.md +67 -26
  11. data/docs/manual/02-cli-reference.md +307 -91
  12. data/docs/manual/03-configuration.md +41 -3
  13. data/docs/manual/04-diagnostics.md +5 -4
  14. data/docs/manual/08-skills.md +11 -0
  15. data/docs/manual/09-editor-integration.md +8 -0
  16. data/docs/manual/14-rails-quickstart.md +1 -1
  17. data/docs/manual/19-effect-labels.md +3 -4
  18. data/docs/manual/plugins/rigor-actionpack.md +15 -6
  19. data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
  20. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  21. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  22. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  23. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
  24. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
  25. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
  26. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  27. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
  28. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  29. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  30. data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
  31. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  32. data/lib/rigor/analysis/check_rules.rb +598 -99
  33. data/lib/rigor/analysis/crash_signature.rb +43 -1
  34. data/lib/rigor/analysis/dependency_recorder.rb +233 -76
  35. data/lib/rigor/analysis/incremental.rb +11 -1
  36. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
  37. data/lib/rigor/analysis/incremental_session.rb +279 -37
  38. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  39. data/lib/rigor/analysis/rule_catalog.rb +37 -1
  40. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  41. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  42. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
  43. data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
  44. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
  45. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  48. data/lib/rigor/analysis/runner.rb +222 -39
  49. data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
  50. data/lib/rigor/analysis/worker_session.rb +42 -41
  51. data/lib/rigor/bleeding_edge.rb +19 -4
  52. data/lib/rigor/cache/file_digest.rb +39 -31
  53. data/lib/rigor/cache/incremental_snapshot.rb +66 -7
  54. data/lib/rigor/cache/rbs_descriptor.rb +37 -2
  55. data/lib/rigor/cache/rbs_environment.rb +5 -1
  56. data/lib/rigor/cli/baseline_command.rb +15 -2
  57. data/lib/rigor/cli/check_command.rb +4 -2
  58. data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
  59. data/lib/rigor/cli/lens_command.rb +17 -2
  60. data/lib/rigor/cli/sig_gen_command.rb +8 -0
  61. data/lib/rigor/cli/skill_describe.rb +1 -0
  62. data/lib/rigor/cli.rb +5 -2
  63. data/lib/rigor/config_audit.rb +32 -18
  64. data/lib/rigor/configuration/severity_profile.rb +9 -0
  65. data/lib/rigor/configuration.rb +32 -5
  66. data/lib/rigor/effects/collector.rb +53 -21
  67. data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
  68. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  69. data/lib/rigor/environment/default_libraries.rb +17 -0
  70. data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
  71. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  72. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  73. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  74. data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
  75. data/lib/rigor/environment/rbs_loader.rb +312 -63
  76. data/lib/rigor/environment.rb +131 -35
  77. data/lib/rigor/inference/acceptance.rb +50 -15
  78. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  79. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  80. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  81. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  82. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  83. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  84. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  85. data/lib/rigor/inference/block_run_count.rb +9 -2
  86. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  87. data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
  88. data/lib/rigor/inference/captured_locals.rb +2 -0
  89. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  90. data/lib/rigor/inference/clause_widening.rb +1 -1
  91. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  92. data/lib/rigor/inference/construction_escape.rb +56 -15
  93. data/lib/rigor/inference/declaration_walk.rb +4 -0
  94. data/lib/rigor/inference/def_node_resolver.rb +32 -14
  95. data/lib/rigor/inference/def_return_typer.rb +1 -1
  96. data/lib/rigor/inference/definer_resolution.rb +15 -7
  97. data/lib/rigor/inference/definers_index.rb +72 -16
  98. data/lib/rigor/inference/error_info.rb +16 -0
  99. data/lib/rigor/inference/expression_typer.rb +1459 -287
  100. data/lib/rigor/inference/flow_tracer.rb +31 -14
  101. data/lib/rigor/inference/fork_map.rb +3 -0
  102. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  103. data/lib/rigor/inference/global_write_census.rb +8 -6
  104. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  105. data/lib/rigor/inference/indexed_narrowing.rb +57 -8
  106. data/lib/rigor/inference/ivar_writer_census.rb +106 -11
  107. data/lib/rigor/inference/key_presence_guard.rb +51 -25
  108. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  109. data/lib/rigor/inference/last_line.rb +5 -2
  110. data/lib/rigor/inference/loop_value_record.rb +63 -15
  111. data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
  112. data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
  113. data/lib/rigor/inference/match_rebinding.rb +21 -8
  114. data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
  115. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  116. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  117. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  118. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  119. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  120. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  121. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
  122. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
  123. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
  124. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
  125. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  126. data/lib/rigor/inference/method_dispatcher.rb +66 -18
  127. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  128. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  129. data/lib/rigor/inference/module_function_state.rb +13 -2
  130. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  131. data/lib/rigor/inference/mutation_widening.rb +44 -4
  132. data/lib/rigor/inference/narrowing.rb +201 -27
  133. data/lib/rigor/inference/operand_effects.rb +43 -2
  134. data/lib/rigor/inference/optimistic_origin.rb +133 -8
  135. data/lib/rigor/inference/origin_lookup.rb +1 -1
  136. data/lib/rigor/inference/precision_scanner.rb +1 -1
  137. data/lib/rigor/inference/project_method_ownership.rb +28 -10
  138. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  139. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  140. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  141. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  142. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  143. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  144. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
  145. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
  146. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  147. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  148. data/lib/rigor/inference/remover_holder_scan.rb +76 -22
  149. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  150. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
  151. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
  152. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  153. data/lib/rigor/inference/scope_indexer.rb +2079 -183
  154. data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
  155. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  156. data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
  157. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  158. data/lib/rigor/inference/statement_evaluator.rb +970 -250
  159. data/lib/rigor/inference/string_mutation.rb +14 -0
  160. data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
  161. data/lib/rigor/inference/toplevel_def_census.rb +14 -6
  162. data/lib/rigor/inference/unknown_store_widening.rb +1 -1
  163. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  164. data/lib/rigor/inference/void_tail_summary.rb +29 -10
  165. data/lib/rigor/lens/claims/comparison.rb +78 -0
  166. data/lib/rigor/lens/claims/request.rb +131 -0
  167. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  168. data/lib/rigor/lens/claims.rb +208 -0
  169. data/lib/rigor/lens/contract_reader.rb +5 -0
  170. data/lib/rigor/lens/document.rb +1 -1
  171. data/lib/rigor/lens/file_reader.rb +21 -8
  172. data/lib/rigor/lens/run.rb +8 -0
  173. data/lib/rigor/lens.rb +1 -1
  174. data/lib/rigor/library_probe.rb +68 -0
  175. data/lib/rigor/memo_audit.rb +282 -0
  176. data/lib/rigor/plugin/base.rb +15 -2
  177. data/lib/rigor/plugin/io_boundary.rb +25 -3
  178. data/lib/rigor/plugin/manifest.rb +7 -3
  179. data/lib/rigor/plugin/registry.rb +13 -2
  180. data/lib/rigor/plugin/trust_policy.rb +46 -0
  181. data/lib/rigor/protection/mutation_cache.rb +4 -1
  182. data/lib/rigor/reflection/constant_ancestors.rb +79 -22
  183. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  184. data/lib/rigor/reflection/declarations.rb +58 -0
  185. data/lib/rigor/reflection/project_method.rb +177 -0
  186. data/lib/rigor/reflection.rb +2 -0
  187. data/lib/rigor/rooted_glob.rb +18 -1
  188. data/lib/rigor/run_scoped_slots.rb +20 -20
  189. data/lib/rigor/scope/discovery_index.rb +149 -2
  190. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  191. data/lib/rigor/scope/resolution_chain.rb +900 -55
  192. data/lib/rigor/scope.rb +307 -34
  193. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  194. data/lib/rigor/sig_gen/classification.rb +14 -1
  195. data/lib/rigor/sig_gen/generator.rb +222 -13
  196. data/lib/rigor/sig_gen/method_candidate.rb +14 -0
  197. data/lib/rigor/sig_gen/observation_collector.rb +11 -2
  198. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  199. data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
  200. data/lib/rigor/sig_gen/writer.rb +21 -0
  201. data/lib/rigor/sig_gen.rb +1 -0
  202. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  203. data/lib/rigor/source/node_children.rb +13 -0
  204. data/lib/rigor/type/combinator.rb +164 -8
  205. data/lib/rigor/type/interface.rb +60 -0
  206. data/lib/rigor/type.rb +1 -0
  207. data/lib/rigor/type_of_memo/serving.rb +99 -0
  208. data/lib/rigor/type_of_memo/stats.rb +43 -0
  209. data/lib/rigor/type_of_memo.rb +337 -0
  210. data/lib/rigor/typing_state.rb +1094 -0
  211. data/lib/rigor/version.rb +1 -1
  212. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
  213. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
  214. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  215. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  216. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  217. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  218. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  219. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
  220. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
  221. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  222. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
  223. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  224. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
  225. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
  226. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
  227. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  228. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  229. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
  230. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  231. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
  232. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
  233. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
  234. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
  235. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
  236. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  237. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  238. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
  239. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  240. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
  241. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  242. data/sig/rigor/analysis/fact_store.rbs +2 -3
  243. data/sig/rigor/cache/descriptor.rbs +14 -0
  244. data/sig/rigor/environment.rbs +4 -1
  245. data/sig/rigor/plugin/base.rbs +53 -19
  246. data/sig/rigor/plugin/interfaces.rbs +51 -0
  247. data/sig/rigor/plugin/io_boundary.rbs +2 -3
  248. data/sig/rigor/plugin/node_context.rbs +21 -0
  249. data/sig/rigor/reflection.rbs +37 -0
  250. data/sig/rigor/scope.rbs +22 -3
  251. data/sig/rigor/trinary.rbs +1 -0
  252. data/sig/rigor/type.rbs +9 -3
  253. data/sig/rigor.rbs +3 -2
  254. data/skills/rigor-ask/SKILL.md +1 -0
  255. data/skills/rigor-project-init/references/01-detect.md +1 -0
  256. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
  257. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  258. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  259. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  260. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  261. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  262. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  263. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  264. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  265. data/skills/rigor-type-oracle/SKILL.md +26 -1
  266. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  267. metadata +48 -2
  268. data/lib/rigor/inference/memo_audit.rb +0 -55
@@ -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
@@ -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"
@@ -195,7 +197,7 @@ module Rigor
195
197
  rbs_result = RbsDispatch.try_dispatch(context)
196
198
  if rbs_result
197
199
  record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
198
- if rbs_result.is_a?(Type::Dynamic) && rbs_result.static_facet.is_a?(Type::Top)
200
+ if Type::Combinator.untyped?(rbs_result)
199
201
  scope&.record_dynamic_origin(call_node, DynamicOrigin::EXPLICIT_UNTYPED)
200
202
  end
201
203
  return rbs_result
@@ -826,7 +828,7 @@ module Rigor
826
828
  end
827
829
 
828
830
  def rbs_result_untyped?(rbs_result)
829
- rbs_result.is_a?(Type::Dynamic) && rbs_result.static_facet.is_a?(Type::Top)
831
+ Type::Combinator.untyped?(rbs_result)
830
832
  end
831
833
 
832
834
  def rbs_display_for(rbs_result)
@@ -1070,13 +1072,29 @@ module Rigor
1070
1072
  scope = context.scope
1071
1073
  return false if scope.nil?
1072
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
+
1073
1082
  memo = external_ancestor_memo(scope, environment)
1074
- key = [receiver_type.class_name, context.method_name]
1075
- 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
1076
1094
 
1077
- memo[key] = !ExternalAncestorResolution.resolve(receiver_type.class_name, context.method_name, :instance,
1078
- scope: scope, environment: environment,
1079
- record_dependencies: false).nil?
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?
1080
1098
  end
1081
1099
 
1082
1100
  # Issue #2284 — a class object whose singleton chain holds a project definer of the name (its own `def
@@ -1101,43 +1119,67 @@ module Rigor
1101
1119
  end
1102
1120
 
1103
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
+
1104
1127
  memo = defines_name_memo(scope)
1105
1128
  if memo.key?(method_name)
1106
1129
  Analysis::DependencyRecorder.read_missing(:defines, method_name) if Analysis::DependencyRecorder.active?
1107
- return memo[method_name]
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
1108
1133
  end
1109
1134
 
1110
- memo[method_name] = BlockCallTiming.project_defines_anywhere?(method_name, scope)
1135
+ memo[method_name] = MemoAudit.key_audit("user class fallback defines name") do
1136
+ BlockCallTiming.project_defines_anywhere?(method_name, scope)
1137
+ end
1111
1138
  end
1112
1139
 
1113
- DEFINES_NAME_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_defines_name__)
1114
- private_constant :DEFINES_NAME_MEMO_KEY
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
1115
1149
 
1116
1150
  # One slot per discovery index and environment, as {#external_ancestor_memo} keeps its own: the answer reads
1117
1151
  # only the frozen discovery tables and the environment's `pre_eval:` patches.
1118
1152
  def defines_name_memo(scope)
1119
1153
  discovery = scope.discovery
1120
1154
  environment = scope.environment
1121
- slot = Thread.current[DEFINES_NAME_MEMO_KEY]
1155
+ state = TypingState.current
1156
+ slot = state[DEFINES_NAME_MEMO]
1122
1157
  unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1123
1158
  slot = [discovery, environment, {}]
1124
- Thread.current[DEFINES_NAME_MEMO_KEY] = slot
1159
+ state[DEFINES_NAME_MEMO] = slot
1125
1160
  end
1126
1161
  slot[2]
1127
1162
  end
1128
1163
 
1129
- EXTERNAL_ANCESTOR_MEMO_KEY = RunScopedSlots.register(:__rigor_user_class_fallback_external_ancestor__)
1130
- 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
1131
1172
 
1132
1173
  # One slot per discovery index and environment, as `RbsDispatch#ancestor_memo_slot` keeps its own: the answer
1133
1174
  # reads only the frozen discovery tables and the RBS environment, and its walk is withheld from dependency
1134
1175
  # recording, so it may be served again for the same pair.
1135
1176
  def external_ancestor_memo(scope, environment)
1136
1177
  discovery = scope.discovery
1137
- slot = Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY]
1178
+ state = TypingState.current
1179
+ slot = state[EXTERNAL_ANCESTOR_MEMO]
1138
1180
  unless slot && slot[0].equal?(discovery) && slot[1].equal?(environment)
1139
1181
  slot = [discovery, environment, {}]
1140
- Thread.current[EXTERNAL_ANCESTOR_MEMO_KEY] = slot
1182
+ state[EXTERNAL_ANCESTOR_MEMO] = slot
1141
1183
  end
1142
1184
  slot[2]
1143
1185
  end
@@ -1364,7 +1406,13 @@ module Rigor
1364
1406
  class_new_lift = class_new_lift(receiver_type.class_name, arg_types, context)
1365
1407
  return class_new_lift if class_new_lift
1366
1408
 
1367
- 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)
1368
1416
  end
1369
1417
 
1370
1418
  # `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass* (a class object), not a
@@ -54,6 +54,114 @@ module Rigor
54
54
  end
55
55
  end
56
56
 
57
+ # Issue #2681 — which declared type, if any, one RBS overload hands a `def`'s parameter slot when the def's shape
58
+ # need not match the declaration's (an override bound from its ancestor, or a source `def` whose RBS was written
59
+ # for another shape). A slot takes a type only where the two shapes line up:
60
+ #
61
+ # - a leading positional (required or optional) takes the declared leading positional at the same ABSOLUTE
62
+ # position, and a post positional the declared trailing one, provided both sides have as many post as
63
+ # trailing positionals: Ruby fills the leading parameters left to right on both sides, so the argument a call
64
+ # puts at a leading position reaches the slot at the same one;
65
+ # - `*rest` takes the declared rest only when the def has exactly the declared leading positionals;
66
+ # - a keyword takes the declared keyword of its name, and `**kw` the declared keyword rest only when the def
67
+ # names every declared keyword.
68
+ #
69
+ # Any other overlap is {MISALIGNED}, and leaves the slot `Dynamic[Top]`. {NOT_PASSED} is an overload that passes
70
+ # the slot nothing (`def first(n)` under `Array#first`'s `()`), which the binder skips.
71
+ module SlotAlignment
72
+ module_function
73
+
74
+ MISALIGNED = :misaligned
75
+ NOT_PASSED = :not_passed
76
+
77
+ # `accepts_keywords` is false for a def with no keyword parameter and no `**`, `**nil` or `...`: Ruby passes a
78
+ # call's keywords to such a def as a Hash in one more positional argument.
79
+ Shape = Data.define(:requireds, :leading, :posts, :keywords, :accepts_keywords)
80
+
81
+ def shape_of(params_node)
82
+ return Shape.new(0, 0, 0, [].freeze, false) if params_node.nil?
83
+
84
+ requireds = params_node.requireds.size
85
+ keywords = params_node.keywords.map(&:name).freeze
86
+ accepts_keywords = !keywords.empty? || !params_node.keyword_rest.nil?
87
+ Shape.new(requireds, requireds + params_node.optionals.size, params_node.posts.size, keywords,
88
+ accepts_keywords)
89
+ end
90
+
91
+ # The RBS type the overload `function` declares for `slot`, {MISALIGNED} or {NOT_PASSED}.
92
+ def declared(function, slot, shape)
93
+ return MISALIGNED if keyword_hash_reaches?(function, slot, shape)
94
+
95
+ case slot.kind
96
+ when :required_positional then leading(function, shape, slot.index)
97
+ when :optional_positional then leading(function, shape, shape.requireds + slot.index)
98
+ when :trailing_positional then trailing(function, shape, slot.index)
99
+ when :rest_positional then rest(function, shape)
100
+ when :required_keyword, :optional_keyword then keyword(function, slot.name)
101
+ when :rest_keyword then rest_keyword(function, shape)
102
+ else NOT_PASSED
103
+ end
104
+ end
105
+
106
+ # Whether the overload's keywords may reach `slot` as a positional Hash: the def accepts no keywords, so a
107
+ # call's keywords arrive as one more positional argument, which lands past the declared required leading
108
+ # positionals, in the `*rest`, or (shifting every post positional) in a post positional.
109
+ def keyword_hash_reaches?(function, slot, shape)
110
+ return false if shape.accepts_keywords
111
+ return false if function.required_keywords.empty? && function.optional_keywords.empty? &&
112
+ function.rest_keywords.nil?
113
+
114
+ case slot.kind
115
+ when :required_positional then slot.index >= function.required_positionals.size
116
+ when :optional_positional then shape.requireds + slot.index >= function.required_positionals.size
117
+ when :rest_positional, :trailing_positional then true
118
+ else false
119
+ end
120
+ end
121
+
122
+ def leading(function, shape, position)
123
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
124
+
125
+ required = function.required_positionals
126
+ return required[position].type if position < required.size
127
+
128
+ optional = function.optional_positionals[position - required.size]
129
+ return optional.type if optional
130
+
131
+ function.rest_positionals ? MISALIGNED : NOT_PASSED
132
+ end
133
+
134
+ def trailing(function, shape, index)
135
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
136
+
137
+ function.trailing_positionals[index].type
138
+ end
139
+
140
+ def rest(function, shape)
141
+ return MISALIGNED unless function.trailing_positionals.size == shape.posts
142
+
143
+ declared_leading = function.required_positionals.size + function.optional_positionals.size
144
+ return MISALIGNED if shape.leading < declared_leading
145
+ return NOT_PASSED if function.rest_positionals.nil?
146
+
147
+ shape.leading == declared_leading ? function.rest_positionals.type : MISALIGNED
148
+ end
149
+
150
+ def keyword(function, name)
151
+ declared = function.required_keywords[name] || function.optional_keywords[name]
152
+ return declared.type if declared
153
+
154
+ function.rest_keywords ? MISALIGNED : NOT_PASSED
155
+ end
156
+
157
+ def rest_keyword(function, shape)
158
+ declared = function.required_keywords.keys + function.optional_keywords.keys
159
+ return MISALIGNED unless declared.all? { |name| shape.keywords.include?(name) }
160
+
161
+ function.rest_keywords ? function.rest_keywords.type : NOT_PASSED
162
+ end
163
+ end
164
+
57
165
  class MethodParameterBinder
58
166
  # @param class_path — the qualified name of the class the method is defined in
59
167
  # (e.g., `"Foo::Bar"`), or `nil` for a top-level `def` outside any class. When `nil` (or
@@ -64,11 +172,15 @@ module Rigor
64
172
  # @param source_path — the project-relative path of the file the method is
65
173
  # defined in. Used to match ADR-28 path-scoped protocol contracts; `nil` (the default
66
174
  # for synthetic / probe scopes) disables the contract tier.
67
- def initialize(environment:, class_path:, singleton:, source_path: nil)
175
+ # @param scope — the scope the `def` is written in. When supplied, a `def` on a Ruby-source class with no RBS
176
+ # of its own binds the parameters of the declaration it overrides on an ADR-43 allow-listed ancestor (#2681);
177
+ # `nil` keeps that class's parameters `Dynamic[Top]`.
178
+ def initialize(environment:, class_path:, singleton:, source_path: nil, scope: nil)
68
179
  @environment = environment
69
180
  @class_path = class_path
70
181
  @singleton = singleton
71
182
  @source_path = source_path
183
+ @scope = scope
72
184
  end
73
185
 
74
186
  # @return ordered map from parameter name to bound type.
@@ -79,7 +191,9 @@ module Rigor
79
191
 
80
192
  rbs_method = lookup_rbs_method(def_node)
81
193
  if rbs_method
82
- apply_rbs_overloads(types, slots, rbs_method.method_types) unless rbs_method.method_types.empty?
194
+ unless rbs_method.method_types.empty?
195
+ apply_rbs_overloads(types, slots, rbs_method.method_types, SlotAlignment.shape_of(def_node.parameters))
196
+ end
83
197
  # `rigor:v1:param: <name> <refinement>` annotations tighten the bound type for matching
84
198
  # slots. Applied after the RBS-overload pass so the override is the authoritative
85
199
  # answer regardless of what the RBS signature declared.
@@ -186,15 +300,17 @@ module Rigor
186
300
  # `def self.foo` always means a singleton method on the immediate enclosing class. `def
187
301
  # foo` inside `class << self` is also a singleton method (the StatementEvaluator threads
188
302
  # the `singleton:` flag through this case).
189
- if def_node.receiver.is_a?(Prism::SelfNode) || @singleton
190
- Rigor::Reflection.singleton_method_definition(@class_path, method_name, environment: @environment)
191
- else
192
- Rigor::Reflection.instance_method_definition(@class_path, method_name, environment: @environment)
193
- end
303
+ # ADR-43 (#2681) — a source-only subclass of an allow-listed RBS-complete ancestor (`Rigor::Plugin::Base`)
304
+ # overriding one of its hooks binds the hook's declared parameters, as an empty RBS declaration of the
305
+ # subclass would through RBS's own inheritance.
306
+ kind = def_node.receiver.is_a?(Prism::SelfNode) || @singleton ? :singleton : :instance
307
+ MethodDispatcher::RbsDispatch.def_declaration(@environment, @class_path, kind, method_name, @scope)
194
308
  end
195
309
 
196
310
  # Bind each parameter slot to the union of the matching parameter types across every
197
- # overload that *has* that slot. Overloads that omit the slot (e.g., `Array#first` has
311
+ # overload that *has* that slot, where the def's parameter shape lines up with the overload's
312
+ # ({SlotAlignment}); one overload that does not line up leaves the slot `Dynamic[Top]`.
313
+ # Overloads that pass the slot no argument (e.g., `Array#first` has
198
314
  # both `()` and `(int)` overloads — only the second matches a `def first(n)` redefinition)
199
315
  # are silently skipped, so the binder defaults to the most informative type the RBS
200
316
  # signature provides without having to know which overload the runtime will pick.
@@ -203,14 +319,14 @@ module Rigor
203
319
  # accepts any argument in every slot, so no slot can be narrowed below `Dynamic[Top]`: binding a typed
204
320
  # sibling overload's parameter type would read a value that overload legitimately passes as a
205
321
  # contradiction inside the body. Every slot keeps its default; the declared return type still applies.
206
- def apply_rbs_overloads(types, slots, method_types)
322
+ def apply_rbs_overloads(types, slots, method_types, shape)
207
323
  return unless method_types.all? { |mt| mt.type.respond_to?(:required_positionals) }
208
324
 
209
325
  slots.each do |slot|
210
326
  next if slot.name.nil?
211
327
 
212
- translated = collect_translated_types(method_types, slot)
213
- next if translated.empty?
328
+ translated = collect_translated_types(method_types, slot, shape)
329
+ next if translated.nil? || translated.empty?
214
330
 
215
331
  types[slot.name] = build_slot_type(translated, slot.kind)
216
332
  end
@@ -272,10 +388,15 @@ module Rigor
272
388
  slots.find { |slot| POSITIONAL_KINDS.include?(slot.kind) && slot.index == index }
273
389
  end
274
390
 
275
- def collect_translated_types(method_types, slot)
276
- rbs_types = method_types.flat_map do |mt|
277
- t = rbs_type_for_slot(mt.type, slot)
278
- t ? [t] : []
391
+ # The translated types the overloads declare for `slot`, or nil when one of them does not line up with the
392
+ # def's parameter shape ({SlotAlignment}): that overload may hand the slot an argument of another type.
393
+ def collect_translated_types(method_types, slot, shape)
394
+ rbs_types = []
395
+ method_types.each do |mt|
396
+ declared = SlotAlignment.declared(mt.type, slot, shape)
397
+ return nil if declared.equal?(SlotAlignment::MISALIGNED)
398
+
399
+ rbs_types << declared unless declared.equal?(SlotAlignment::NOT_PASSED)
279
400
  end
280
401
  rbs_types.map { |t| translate_with_self(t) }.uniq
281
402
  end
@@ -285,47 +406,6 @@ module Rigor
285
406
  wrap_for_kind(bound, kind)
286
407
  end
287
408
 
288
- # Dispatch table from slot kind to a small lambda that pulls the matching RBS parameter
289
- # type out of an `RBS::Types::Function`. The hash keeps `rbs_type_for_slot` linear (one
290
- # lookup, one call) so the cyclomatic-complexity budget does not balloon as future slices
291
- # add more parameter kinds (e.g., `**Symbol kw` is a candidate for a stricter route in
292
- # Slice 5+).
293
- # Match keyword parameters by name across both required and optional keyword maps. RBS may
294
- # declare a keyword as optional (`?by:`) while the Ruby `def` lists it as required (or
295
- # vice versa); the binding is by-name regardless of which side defines it.
296
- KEYWORD_PROVIDER = Ractor.make_shareable(lambda do |fn, slot|
297
- fn.required_keywords[slot.name]&.type || fn.optional_keywords[slot.name]&.type
298
- end)
299
- private_constant :KEYWORD_PROVIDER
300
-
301
- RBS_TYPE_PROVIDERS = Ractor.make_shareable({
302
- required_positional: Ractor.make_shareable(lambda { |fn, slot|
303
- fn.required_positionals[slot.index]&.type
304
- }),
305
- optional_positional: Ractor.make_shareable(lambda { |fn, slot|
306
- fn.optional_positionals[slot.index]&.type
307
- }),
308
- rest_positional: Ractor.make_shareable(lambda { |fn, _slot|
309
- fn.rest_positionals&.type
310
- }),
311
- trailing_positional: Ractor.make_shareable(lambda { |fn, slot|
312
- fn.trailing_positionals[slot.index]&.type
313
- }),
314
- required_keyword: KEYWORD_PROVIDER,
315
- optional_keyword: KEYWORD_PROVIDER,
316
- rest_keyword: Ractor.make_shareable(lambda { |fn, _slot|
317
- fn.rest_keywords&.type
318
- })
319
- })
320
- private_constant :RBS_TYPE_PROVIDERS
321
-
322
- def rbs_type_for_slot(function, slot)
323
- provider = RBS_TYPE_PROVIDERS[slot.kind]
324
- return nil unless provider
325
-
326
- provider.call(function, slot)
327
- end
328
-
329
409
  # The variable bound to a `*rest` parameter is the *Array* of rest-positional arguments,
330
410
  # not a single element. Likewise `**kw` is bound to a `Hash[Symbol, V]`. Wrap the
331
411
  # translated element/value type accordingly so `rest` reads as `Array[Integer]` rather
@@ -41,8 +41,8 @@ module Rigor
41
41
  any = arguments.empty?
42
42
  arguments.each do |arg|
43
43
  if arg.is_a?(Prism::DefNode)
44
- kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
45
- ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
44
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
45
+ *ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
46
46
  elsif (name = Source::Literals.symbol_or_string(arg))
47
47
  ranges << [*call_span, name, :singleton, owner]
48
48
  else
@@ -54,6 +54,17 @@ module Rigor
54
54
  module ModuleFunctionState
55
55
  module_function
56
56
 
57
+ # The `[kind, owner]` of a deferred-range row for a `def` passed to a `module_function` spelling: `:singleton`
58
+ # for a singleton def, `:both` for an instance one, and `[nil, nil]` for a def on another object's singleton
59
+ # (#3155), which claims no owner or side.
60
+ def def_row_side(def_node, qualified_prefix, in_singleton_class, owner)
61
+ case ScopeIndexer.def_side(def_node, qualified_prefix, in_singleton_class)
62
+ when :singleton then [:singleton, owner]
63
+ when :instance then [:both, owner]
64
+ else [nil, nil]
65
+ end
66
+ end
67
+
57
68
  # A receiverless `module_function` call, bare or named.
58
69
  def call?(node)
59
70
  node.name == :module_function && node.receiver.nil?
@@ -360,8 +371,8 @@ module Rigor
360
371
  owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
361
372
  node.arguments&.arguments&.each do |arg|
362
373
  if arg.is_a?(Prism::DefNode)
363
- kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
364
- ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
374
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name,
375
+ *ModuleFunctionState.def_row_side(arg, qualified_prefix, in_singleton_class, owner)]
365
376
  elsif (name = symbol_argument_name(arg))
366
377
  ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
367
378
  else
@@ -302,7 +302,7 @@ module Rigor
302
302
  # and a per-slot `T | nil` fires `call.possible-nil-receiver` on the guarded read.
303
303
  def join_member_bindings(types, soften_slots)
304
304
  untyped = Type::Combinator.untyped
305
- return [untyped, false] if types.any? { |type| type == untyped }
305
+ return [untyped, false] if types.any? { |type| Type::Combinator.untyped?(type) }
306
306
 
307
307
  firm = types.reject { |type| nil_literal?(type) }
308
308
  return [Type::Combinator.union(*types), false] if !soften_slots || firm.empty? || firm.size == types.size