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,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../reflection"
4
+ require_relative "../../type"
5
+ require_relative "argument_nodes"
6
+ require_relative "overload_selector"
7
+ require_relative "rbs_dispatch"
8
+
9
+ module Rigor
10
+ module Inference
11
+ module MethodDispatcher
12
+ # Issue #2884 — `X.new` through a `def self.new` that a project, gem or collection signature declares on `X` or
13
+ # on an ancestor of `X`. RBS models a constructor that answers another class this way (`email_address` declares
14
+ # `def self.new: (...) -> Address` on `EmailAddress`), and `MethodDispatcher#meta_new` answered `Nominal[X]` for
15
+ # every `Singleton[X].new` before any RBS tier ran. `meta_new` asks this module only after its curated lifts
16
+ # (`Array`, `Set`, `Hash`, `Struct`, `Class.new`, …) have declined, and keeps its `Nominal[X]` answer whenever
17
+ # this one is nil.
18
+ #
19
+ # The declaration counts only when it is an explicit `def self.new` (not the `new` RBS builds from `initialize`),
20
+ # every one of its definitions sits outside the `rbs` gem's core and stdlib trees
21
+ # ({Environment::RbsLoader#declared_outside_core_stdlib?}), and the class carrying it is not one core or stdlib
22
+ # declares. Core declares `Data.new: () -> bot` and `Ractor.new: … -> Ractor`, stdlib `Tempfile.new`; a project
23
+ # class inheriting one of those keeps the `Nominal` answer the curated tiers were written around.
24
+ #
25
+ # The declaration is found on `X`'s own RBS (which carries its RBS ancestors' singleton methods), or, for a
26
+ # Ruby-source class with no RBS of its own, on the first RBS-known class of its singleton
27
+ # {Scope::ResolutionChain}. A project entry ahead of it that defines `new` ends the walk with no answer: that
28
+ # method is the one Ruby runs. An inherited declaration that answers the instance
29
+ # ({Environment::RbsLoader#answers_instance_for_subclass?}) answers `Nominal[X]`; otherwise `instance`, and an
30
+ # overload returning the declaring class itself, read the receiver class, and `self` reads its class object.
31
+ #
32
+ # Nil (so `Nominal[X]`) when no overload takes the arguments (argument checking of `.new` is not this tier's
33
+ # job), and when the return translates to an untyped, `top` or `bot` answer, which would lose the precision
34
+ # `Nominal[X]` already has.
35
+ module DeclaredConstructor
36
+ # An entry of the singleton chain ahead of any declaring class defines `new` itself (in Ruby source, or as an
37
+ # `extend`ed module's RBS instance method), so that method is the one Ruby runs and the walk answers nothing.
38
+ OTHER_NEW = :other_new
39
+ private_constant :OTHER_NEW
40
+
41
+ module_function
42
+
43
+ def try_dispatch(class_name, context)
44
+ environment = context.environment
45
+ loader = environment&.rbs_loader
46
+ return nil if loader.nil? || !loader.respond_to?(:honoured_singleton_new?)
47
+
48
+ definition = declared_new(class_name, environment, loader, context.scope)
49
+ return nil if definition.nil? || !overload_matches?(definition, class_name, context)
50
+
51
+ owner = definition.defined_in.to_s.delete_prefix("::")
52
+ # The same test the walk's gate reads ({Environment::RbsLoader#singleton_new_departs_from_instance?}), so
53
+ # an inherited declaration that answers the instance answers `Nominal[receiver]` whether or not the gate let
54
+ # the walk run.
55
+ return nil if owner != class_name && loader.answers_instance_for_subclass?(definition)
56
+
57
+ answer = RbsDispatch.declared_singleton_return(context, definition, class_name)
58
+ answer = repoint_owner(answer, owner, class_name)
59
+ answer if usable?(answer)
60
+ end
61
+
62
+ class << self
63
+ private
64
+
65
+ def declared_new(class_name, environment, loader, scope)
66
+ owner = declaring_class(class_name, environment, loader, scope)
67
+ return nil if owner.nil?
68
+
69
+ definition = Rigor::Reflection.singleton_method_definition(owner, :new, environment: environment)
70
+ definition if definition && loader.honoured_singleton_new?(definition)
71
+ end
72
+
73
+ # `class_name` itself when RBS knows it; else the first RBS-known class on its singleton
74
+ # {Scope::ResolutionChain}, read in Ruby's order. Nil when a project entry ahead of it defines `new` (a
75
+ # `def self.new`, or an `extend`ed module's `def new`), when the chain reaches none, and when the chain does
76
+ # not stand (a fork or an unplaced mixin edge, where the tables cannot say which world ran).
77
+ #
78
+ # The walk runs only when some loaded declaration fails {Environment::RbsLoader#answers_instance_for_subclass?}
79
+ # ({Environment::RbsLoader#singleton_new_departs_from_instance?}); `try_dispatch` answers `Nominal[X]` for an
80
+ # inherited declaration that passes it, so without one the walk could change nothing. When it runs
81
+ # it files its reads whatever it answers: a later edit that puts such a declaration on the chain changes
82
+ # the answer, and the caller must be re-checked then.
83
+ def declaring_class(class_name, environment, loader, scope)
84
+ return class_name if Rigor::Reflection.rbs_class_known?(class_name, environment: environment)
85
+ return nil if scope.nil? || !loader.singleton_new_departs_from_instance?
86
+
87
+ chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
88
+ found = first_new_owner(chain, scope, environment)
89
+ verdict = chain.settle(scope, found, owner: nil) { |retro| first_new_owner(retro, scope, environment) }
90
+ found unless found.nil? || found == OTHER_NEW || verdict != :chain
91
+ end
92
+
93
+ # The first entry on `chain` that answers `new`: an RBS-known class's name, or {OTHER_NEW} for an entry that
94
+ # defines it otherwise. An `extend`ed module is not a class object `new` is declared on, but one whose
95
+ # instance side declares `new` (in Ruby source or in RBS) puts that method ahead of the superclass's, so it
96
+ # stops the walk.
97
+ def first_new_owner(chain, scope, environment)
98
+ chain.search(scope) do |entry|
99
+ if entry.external?
100
+ extended_rbs_new = !entry.superclass_edge && rbs_instance_new?(entry.candidates, environment)
101
+ next OTHER_NEW if extended_rbs_new
102
+ next unless entry.superclass_edge
103
+
104
+ entry.candidates.find { |name| Rigor::Reflection.rbs_class_known?(name, environment: environment) }
105
+ elsif entry.side == :singleton && Rigor::Reflection.rbs_class_known?(entry.name, environment: environment)
106
+ entry.name
107
+ elsif scope.discovered_method?(entry.name, :new, entry.side) ||
108
+ (entry.side == :instance && rbs_instance_new?([entry.name], environment))
109
+ OTHER_NEW
110
+ end
111
+ end
112
+ end
113
+
114
+ def rbs_instance_new?(names, environment)
115
+ names.any? { |name| Rigor::Reflection.instance_method_definition(name, :new, environment: environment) }
116
+ end
117
+
118
+ # A genuine match only: the selector's first-overload fallback would answer a declaration the call does
119
+ # not take.
120
+ def overload_matches?(definition, class_name, context)
121
+ args = context.args || []
122
+ !OverloadSelector.select_declared(
123
+ definition, args, Type::Combinator.singleton_of(class_name), Type::Combinator.nominal_of(class_name),
124
+ {}, !context.block_type.nil?, context.environment, true,
125
+ ArgumentNodes.keyword_hash_last?(context.call_node, args), nil, true
126
+ ).empty?
127
+ end
128
+
129
+ # A return naming the declaring class itself (`def self.new: [T] (T) -> Box[T]`) is the constructor's own
130
+ # class, so a subclass inheriting it answers the subclass, as `instance` does; generic arguments name the
131
+ # owner's parameters and are dropped with it. A foreign return (`EmailAddress` -> `Address`) stays.
132
+ def repoint_owner(answer, owner, class_name)
133
+ return answer if owner == class_name
134
+
135
+ case answer
136
+ when Type::Nominal
137
+ answer.class_name == owner ? Type::Combinator.nominal_of(class_name) : answer
138
+ when Type::Union
139
+ Type::Combinator.union(*answer.members.map { |member| repoint_owner(member, owner, class_name) })
140
+ else answer
141
+ end
142
+ end
143
+
144
+ def usable?(answer)
145
+ !(answer.nil? || answer.is_a?(Type::Dynamic) || answer.is_a?(Type::Top) || answer.is_a?(Type::Bot))
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -119,7 +119,7 @@ module Rigor
119
119
  # `Dynamic`, the untyped carrier, a facet that is only `nil`, one wider than {MEMBER_LIMIT}, or one with a
120
120
  # member that is not sealed, an untyped member included.
121
121
  def facet_members(arg)
122
- return nil unless arg.is_a?(Type::Dynamic) && !arg.static_facet.is_a?(Type::Top)
122
+ return nil unless arg.is_a?(Type::Dynamic) && !Type::Combinator.untyped?(arg)
123
123
 
124
124
  facet = arg.static_facet
125
125
  members = (facet.is_a?(Type::Union) ? facet.members : [facet]).reject { |member| nil_value?(member) }
@@ -20,12 +20,12 @@ module Rigor
20
20
  module_function
21
21
 
22
22
  # The literal `untyped` carrier, `Dynamic[top]`.
23
- def untyped?(type) = type.is_a?(Type::Dynamic) && type.static_facet.is_a?(Type::Top)
23
+ def untyped?(type) = Type::Combinator.untyped?(type)
24
24
 
25
25
  # The untyped carrier itself, or a union or `Dynamic` holding it at any depth.
26
26
  def imprecise?(type)
27
27
  case type
28
- when Type::Dynamic then type.static_facet.is_a?(Type::Top) || imprecise?(type.static_facet)
28
+ when Type::Dynamic then Type::Combinator.untyped?(type) || imprecise?(type.static_facet)
29
29
  when Type::Union then type.members.any? { |member| imprecise?(member) }
30
30
  else false
31
31
  end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../../type"
6
+ require_relative "../../typing_state"
7
+ require_relative "../../memo_audit"
8
+ require_relative "argument_nodes"
9
+
10
+ module Rigor
11
+ module Inference
12
+ module MethodDispatcher
13
+ # ADR-130 WD3 — the overload-shape rule for a call on a `Rigor::Plugin` interface value. The argument rules
14
+ # never check a call on a `Dynamic` receiver, so nothing else would notice a call no overload of the member
15
+ # takes; without this rule dispatch took whichever overload the selector's fallback picked, and that
16
+ # overload's return made the next call wrong (`xs.each.with_index` read the block overload's
17
+ # `Array[Integer]`). A call resolves only through the overloads that take its shape:
18
+ #
19
+ # - a block when the call passes one, and no required block when it does not;
20
+ # - the positional count, within required plus optional unless the overload has a rest parameter;
21
+ # - no required keyword the call omits.
22
+ #
23
+ # A shape the AST cannot state, a splat (`*xs`), a keyword splat (`**opts`) or a forwarded `...`, is taken
24
+ # by no overload, so the call reads `untyped` as it does on master.
25
+ module InterfaceShape
26
+ # ADR-130 WD5 — the dispatch-off flag {.without_dispatch} installs (`RbsDispatch.without_interface_dispatch`).
27
+ DISPATCH_OFF = TypingState.frame(
28
+ :interface_dispatch_off, reason: "a flag installed around a dispatch-off run (sig-gen, the parity harness)",
29
+ witness: "spec/fixtures/typing_state_witnesses/interface_erasure.rb"
30
+ )
31
+ private_constant :DISPATCH_OFF
32
+
33
+ module_function
34
+
35
+ # Runs the block with interface dispatch switched off.
36
+ def without_dispatch(&)
37
+ MemoAudit.check_run_level_install(:interface_dispatch_off)
38
+ TypingState.current.with(DISPATCH_OFF, true, &)
39
+ end
40
+
41
+ # False inside {.without_dispatch}.
42
+ def dispatch? = TypingState.current[DISPATCH_OFF] != true
43
+
44
+ # `definition` narrowed to the overloads that take the call's shape (the definition itself when every
45
+ # overload does), or nil when none does.
46
+ #
47
+ # Nil too when more than one overload takes the shape and one of them has a parameter that mentions `self`
48
+ # (`(self other)`, `(Array[self] others)`): which of them the call reaches depends on whether an argument is
49
+ # the receiver's kind, and the interface names no class acceptance could decide that against. Read as a
50
+ # precise overload, `x.same(1)` against `(self) -> Integer | (Integer) -> String` answered the first one's
51
+ # `Integer`; the call reads `untyped` instead.
52
+ def accepting(definition, args, block:, call_node:)
53
+ shape = call_shape(args, call_node)
54
+ return nil if shape.nil?
55
+
56
+ positional, keywords = shape
57
+ defs = definition.defs.select { |type_def| takes?(type_def.type, positional, keywords, block) }
58
+ return nil if defs.empty?
59
+ return nil if defs.size > 1 && defs.any? { |type_def| self_parameter?(type_def.type.type) }
60
+
61
+ defs.size == definition.defs.size ? definition : definition.update(defs: defs)
62
+ end
63
+
64
+ # True when a parameter type of `function` mentions RBS `self`, at any depth.
65
+ def self_parameter?(function)
66
+ return false unless function.respond_to?(:required_positionals)
67
+
68
+ params = function.required_positionals + function.optional_positionals + function.trailing_positionals +
69
+ function.required_keywords.values + function.optional_keywords.values
70
+ params << function.rest_positionals if function.rest_positionals
71
+ params << function.rest_keywords if function.rest_keywords
72
+ params.any? { |param| mentions_self?(param.type) }
73
+ end
74
+
75
+ def mentions_self?(rbs_type)
76
+ return true if rbs_type.is_a?(RBS::Types::Bases::Self)
77
+
78
+ rbs_type.each_type { |inner| return true if mentions_self?(inner) }
79
+ false
80
+ end
81
+
82
+ # True when the call's block is a block-pass whose value may be nil at runtime, so the call may run
83
+ # block-less: `&blk` unless `blk` is provably not nil ({.never_nil?}), and an anonymous `&`, which forwards
84
+ # the enclosing method's own block. `&:sym` is never nil.
85
+ def nilable_block_pass?(call_node, scope)
86
+ block = call_node.respond_to?(:block) ? call_node.block : nil
87
+ return false unless block.is_a?(Prism::BlockArgumentNode)
88
+
89
+ expression = block.expression
90
+ return true if expression.nil?
91
+ return false if expression.is_a?(Prism::SymbolNode)
92
+ return true if scope.nil?
93
+
94
+ !never_nil?(scope.type_of(expression))
95
+ end
96
+
97
+ # The classes a value of which may be `nil`: `NilClass` and its ancestors.
98
+ NIL_ANCESTORS = %w[NilClass Object BasicObject Kernel].freeze
99
+ private_constant :NIL_ANCESTORS
100
+
101
+ # True only for a type that provably excludes `nil`: a non-nil literal, an instance of a class `nil` is not
102
+ # (a `Proc`, a `Method`), a class object, or a shape. `top`, `Dynamic`, `Object` and anything this does not
103
+ # list may be `nil` (ADR-130 WD3, as amended).
104
+ def never_nil?(type)
105
+ case type
106
+ when Type::Union then type.members.all? { |member| never_nil?(member) }
107
+ when Type::Constant then !type.value.nil?
108
+ when Type::Nominal then !NIL_ANCESTORS.include?(type.class_name)
109
+ when Type::Refined, Type::Difference then never_nil?(type.base)
110
+ when Type::Singleton, Type::Tuple, Type::HashShape, Type::BoundMethod, Type::IntegerRange, Type::FloatRange,
111
+ Type::DataInstance, Type::StructInstance, Type::DataClass, Type::StructClass then true
112
+ else false
113
+ end
114
+ end
115
+
116
+ # `[positional count, keyword names or nil]`, or nil for a shape the AST cannot state, which includes a keyword
117
+ # hash whose keys are not all literal symbols. The keyword names are nil when the call passes no keyword hash.
118
+ def call_shape(args, call_node)
119
+ arguments = call_node.respond_to?(:arguments) ? call_node.arguments&.arguments : nil
120
+ return [args.size, nil] if arguments.nil? || arguments.size != args.size
121
+ return nil if arguments.any? { |node| opaque_argument?(node) }
122
+ return [args.size, nil] unless ArgumentNodes.keyword_hash_last?(call_node, args)
123
+
124
+ names = keyword_names(arguments.last)
125
+ names && [args.size - 1, names]
126
+ end
127
+
128
+ def opaque_argument?(node)
129
+ case node
130
+ when Prism::SplatNode, Prism::ForwardingArgumentsNode then true
131
+ when Prism::KeywordHashNode then node.elements.any?(Prism::AssocSplatNode)
132
+ else false
133
+ end
134
+ end
135
+
136
+ # The literal keyword names of a keyword hash, or nil when one is not a literal symbol.
137
+ def keyword_names(hash_node)
138
+ hash_node.elements.map do |element|
139
+ key = element.key
140
+ return nil unless key.is_a?(Prism::SymbolNode)
141
+
142
+ key.unescaped.to_sym
143
+ end
144
+ end
145
+
146
+ def takes?(method_type, positional, keywords, block)
147
+ function = method_type.type
148
+ # `(...) -> T`: an untyped parameter list takes every call.
149
+ return block_taken?(method_type.block, block) unless function.respond_to?(:required_positionals)
150
+
151
+ block_taken?(method_type.block, block) && arguments_taken?(function, positional, keywords)
152
+ end
153
+
154
+ def block_taken?(declared, block)
155
+ block ? !declared.nil? : (declared.nil? || !declared.required)
156
+ end
157
+
158
+ def arguments_taken?(function, positional, keywords)
159
+ return positional_taken?(function, positional) && function.required_keywords.empty? if keywords.nil?
160
+ # A keyword hash an overload declares no keywords for is its last positional argument.
161
+ return positional_taken?(function, positional + 1) unless keywords?(function)
162
+
163
+ positional_taken?(function, positional) && function.required_keywords.keys.all? do |key|
164
+ keywords.include?(key)
165
+ end
166
+ end
167
+
168
+ def positional_taken?(function, count)
169
+ required = function.required_positionals.size + function.trailing_positionals.size
170
+ return false if count < required
171
+
172
+ !function.rest_positionals.nil? || count <= required + function.optional_positionals.size
173
+ end
174
+
175
+ def keywords?(function)
176
+ !function.required_keywords.empty? || !function.optional_keywords.empty? || !function.rest_keywords.nil?
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -10,6 +10,7 @@ require_relative "imprecise_argument"
10
10
  require_relative "keyword_arguments"
11
11
  require_relative "no_match_fallback"
12
12
  require_relative "proven_overload"
13
+ require_relative "singleton_type_params"
13
14
  require_relative "receiver_affinity"
14
15
  require_relative "splat_arity"
15
16
 
@@ -53,6 +54,7 @@ module Rigor
53
54
  # The selector is intentionally agnostic about the dispatch kind (instance vs singleton). Both kinds
54
55
  # share the same arity and acceptance shape; the difference is only in which `Definition` the caller
55
56
  # fetched.
57
+ # rubocop:disable-next Metrics/ModuleLength -- the selection passes plus the #2828 overload rewrite wrapper.
56
58
  module OverloadSelector
57
59
  module_function
58
60
 
@@ -124,6 +126,16 @@ module Rigor
124
126
  declared = method_definition.method_types
125
127
  return [] if declared.empty?
126
128
 
129
+ # Issue #2828 — `singleton(T)` over a method type variable reads as a class object (`SingletonTypeParams`).
130
+ SingletonTypeParams.selecting(declared) do |overloads|
131
+ select_among(overloads, method_definition, arg_types, self_type, instance_type, type_vars, block_required,
132
+ environment, member, keywords_last, splats, block_fallback)
133
+ end
134
+ end
135
+
136
+ # rubocop:disable-next Metrics/ParameterLists -- {.select_declared}'s inputs plus the overloads it reads.
137
+ def select_among(declared, method_definition, arg_types, self_type, instance_type, type_vars, block_required,
138
+ environment, member, keywords_last, splats, block_fallback)
127
139
  # A keyword hash only some overload takes as keywords is a plain positional `Hash` otherwise (#1737).
128
140
  keywords_last &&= KeywordArguments.any_declares?(declared)
129
141