rigortype 0.4.2 → 0.4.4

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/core_overlay/ostruct.rbs +40 -0
  4. data/docs/handbook/11-sig-gen.md +28 -5
  5. data/docs/manual/02-cli-reference.md +23 -4
  6. data/docs/manual/03-configuration.md +21 -8
  7. data/docs/manual/04-diagnostics.md +44 -2
  8. data/docs/manual/05-inspecting-types.md +50 -0
  9. data/docs/manual/07-plugins.md +1 -1
  10. data/docs/manual/10-mcp-server.md +32 -3
  11. data/docs/manual/12-caching.md +18 -1
  12. data/docs/manual/14-rails-quickstart.md +2 -2
  13. data/docs/manual/18-removing-dead-code.md +26 -9
  14. data/docs/manual/plugins/rigor-rails-routes.md +2 -2
  15. data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
  16. data/exe/rigor +12 -17
  17. data/lib/rigor/analysis/check_rules/rule_ids.rb +64 -0
  18. data/lib/rigor/analysis/check_rules.rb +68 -57
  19. data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
  20. data/lib/rigor/analysis/incremental.rb +12 -0
  21. data/lib/rigor/analysis/incremental_run_slot.rb +3 -3
  22. data/lib/rigor/analysis/incremental_session.rb +207 -40
  23. data/lib/rigor/analysis/path_expansion.rb +62 -6
  24. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
  25. data/lib/rigor/analysis/reachability/graph.rb +139 -15
  26. data/lib/rigor/analysis/reachability/scan.rb +374 -173
  27. data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
  28. data/lib/rigor/analysis/rule_catalog.rb +6 -1
  29. data/lib/rigor/analysis/run_cache_key.rb +31 -7
  30. data/lib/rigor/analysis/run_cache_probe.rb +6 -4
  31. data/lib/rigor/analysis/run_stats.rb +28 -6
  32. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +74 -13
  33. data/lib/rigor/analysis/runner/pool_coordinator.rb +133 -19
  34. data/lib/rigor/analysis/runner/project_pre_passes.rb +46 -1
  35. data/lib/rigor/analysis/runner.rb +62 -18
  36. data/lib/rigor/analysis/severity_stamp.rb +34 -1
  37. data/lib/rigor/analysis/worker_session.rb +58 -2
  38. data/lib/rigor/cache/descriptor.rb +40 -6
  39. data/lib/rigor/cache/engine_source.rb +21 -7
  40. data/lib/rigor/cache/incremental_snapshot.rb +56 -12
  41. data/lib/rigor/cli/check_command.rb +138 -39
  42. data/lib/rigor/cli/docs_command.rb +5 -4
  43. data/lib/rigor/cli/lens_command.rb +144 -0
  44. data/lib/rigor/cli/lsp_command.rb +5 -2
  45. data/lib/rigor/cli/mcp_command.rb +1 -1
  46. data/lib/rigor/cli/skill_command.rb +3 -3
  47. data/lib/rigor/cli/skill_describe.rb +1 -1
  48. data/lib/rigor/cli/type_of_command.rb +2 -1
  49. data/lib/rigor/cli/unused_command.rb +157 -24
  50. data/lib/rigor/cli.rb +30 -4
  51. data/lib/rigor/config_audit.rb +111 -15
  52. data/lib/rigor/configuration/dependencies.rb +19 -11
  53. data/lib/rigor/configuration.rb +15 -0
  54. data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
  55. data/lib/rigor/environment/installed_gem_set.rb +38 -11
  56. data/lib/rigor/environment/lockfile_resolver.rb +37 -6
  57. data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
  58. data/lib/rigor/environment/rbs_loader.rb +79 -10
  59. data/lib/rigor/environment/required_features.rb +4 -2
  60. data/lib/rigor/environment.rb +12 -5
  61. data/lib/rigor/inference/block_call_timing.rb +5 -0
  62. data/lib/rigor/inference/builtins/cruby_definers.rb +22 -5
  63. data/lib/rigor/inference/captured_locals.rb +69 -6
  64. data/lib/rigor/inference/definer_resolution.rb +16 -7
  65. data/lib/rigor/inference/expression_typer.rb +117 -29
  66. data/lib/rigor/inference/guard_rebinding.rb +7 -2
  67. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +21 -17
  68. data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
  69. data/lib/rigor/inference/match_rebinding.rb +1 -1
  70. data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
  71. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
  72. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
  73. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
  74. data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
  75. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -16
  76. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
  77. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
  78. data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
  79. data/lib/rigor/inference/method_dispatcher.rb +52 -3
  80. data/lib/rigor/inference/module_function_state.rb +80 -13
  81. data/lib/rigor/inference/narrowing.rb +184 -68
  82. data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
  83. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  84. data/lib/rigor/inference/project_patched_scanner.rb +38 -24
  85. data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
  86. data/lib/rigor/inference/refined_dispatch/own_method.rb +112 -40
  87. data/lib/rigor/inference/refined_dispatch.rb +62 -14
  88. data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
  89. data/lib/rigor/inference/repeated_or_writes.rb +416 -1
  90. data/lib/rigor/inference/scope_indexer.rb +565 -61
  91. data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
  92. data/lib/rigor/inference/statement_evaluator.rb +307 -60
  93. data/lib/rigor/inference/untyped_rejoin.rb +75 -0
  94. data/lib/rigor/language_server/project_context.rb +56 -5
  95. data/lib/rigor/lens/anchor.rb +115 -0
  96. data/lib/rigor/lens/contract_reader.rb +133 -0
  97. data/lib/rigor/lens/document.rb +113 -0
  98. data/lib/rigor/lens/file_reader.rb +802 -0
  99. data/lib/rigor/lens/members.rb +203 -0
  100. data/lib/rigor/lens/parameters.rb +91 -0
  101. data/lib/rigor/lens/plugins.rb +40 -0
  102. data/lib/rigor/lens/run.rb +322 -0
  103. data/lib/rigor/lens/slot.rb +79 -0
  104. data/lib/rigor/lens.rb +41 -0
  105. data/lib/rigor/mcp/server.rb +61 -20
  106. data/lib/rigor/plugin/box_probe.rb +154 -7
  107. data/lib/rigor/project_environment.rb +23 -1
  108. data/lib/rigor/protection/mutation_cache.rb +2 -1
  109. data/lib/rigor/scope.rb +33 -17
  110. data/lib/rigor/sig_gen/generator.rb +45 -5
  111. data/lib/rigor/sig_gen/layout_index.rb +36 -0
  112. data/lib/rigor/sig_gen/method_candidate.rb +8 -0
  113. data/lib/rigor/sig_gen/path_mapper.rb +18 -2
  114. data/lib/rigor/sig_gen/writer.rb +47 -34
  115. data/lib/rigor/version.rb +1 -1
  116. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
  117. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
  118. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
  119. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
  120. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
  121. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
  122. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
  123. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
  124. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
  125. data/sig/rigor/scope.rbs +3 -0
  126. data/sig/rigor.rbs +6 -3
  127. data/skills/rigor-project-init/references/02-configure.md +2 -2
  128. metadata +21 -1
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/node_children"
6
+ require_relative "../source/parameter_envelope"
7
+ require_relative "../type"
8
+ require_relative "guard_rebinding"
9
+ require_relative "project_method_ownership"
10
+ require_relative "refined_dispatch/own_method"
11
+
12
+ module Rigor
13
+ module Inference
14
+ # Issue #1596 — the instance variables an implicit-self or `self.` statement call may write, when the analysis
15
+ # can prove that set. The intervening-call reset (`StatementEvaluator#invalidate_ivars_for_intervening_call`)
16
+ # and the class-guard restore ({GuardRebinding}) both assume such a call may write any instance variable and
17
+ # run any code; with a proven set they widen only the instance variables in it, so `return unless
18
+ # @io.is_a?(StringIO); log("dumping"); @io.string` keeps `@io` a `StringIO` when `log` writes no instance
19
+ # variable.
20
+ #
21
+ # {.of} answers a frozen `Set` of names, or nil when it cannot prove one. It proves one only for an instance
22
+ # method's `self` (a project class, not a module, whose instance chain the resolution chain settles) and a call
23
+ # with no block argument (`&blk`, `...`), and only when every method the call can reach is proven:
24
+ #
25
+ # - A method the project defines on the class's chain must resolve to one `def` (not `define_method`, an
26
+ # `attr_*` writer or an alias), found ahead of every external ancestor, with every project subclass resolving
27
+ # the name to the same definer, and no `refine` block defining the name. Its parameters' default values and
28
+ # its body are read. A recursive or mutually recursive call adds nothing the first read did not.
29
+ # - Any other method on `self` must be one {GuardRebinding} counts as running no foreign code (`warn`, `puts`,
30
+ # `format`), and not a code-running name (`send`, `instance_variable_set`, `instance_eval`, …).
31
+ # - A call on another receiver counts only on a literal, a global, a constant naming a core or standard-library
32
+ # class, or an instance variable read as its class-ivar seed, and only when {GuardRebinding} counts its method
33
+ # as running no foreign code there. A local, a parameter or a call result is a receiver the read cannot type.
34
+ #
35
+ # Read code that writes an instance variable adds its name (an `instance_variable_set` call is a code-running
36
+ # name, so it proves nothing). Code that writes a global or a constant, yields, calls `super`, defines a method
37
+ # or opens a singleton class, an operator write on a local, and a `for` loop prove nothing. A literal block at
38
+ # the call site or in a read body is read with the code around it. The accepted gap is {GuardRebinding}'s:
39
+ # an implicit conversion a core method runs (`warn obj` runs `obj.to_s`) is read as the core method alone, and
40
+ # so is a singleton method an `extend` or `def obj.m` gives one object at run time.
41
+ module SelfCallIvarWrites
42
+ # The most `def` bodies one proof reads before it gives up.
43
+ DEF_BUDGET = 32
44
+ # Code the proof does not read past: it rebinds a global or constant, runs code the body does not show, or
45
+ # changes which method a later call dispatches to.
46
+ UNPROVABLE_NODES = Set[
47
+ Prism::GlobalVariableWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode,
48
+ Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableTargetNode,
49
+ Prism::ConstantWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode,
50
+ Prism::ConstantOperatorWriteNode, Prism::ConstantTargetNode, Prism::ConstantPathWriteNode,
51
+ Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode, Prism::ConstantPathOperatorWriteNode,
52
+ Prism::ConstantPathTargetNode,
53
+ Prism::YieldNode, Prism::SuperNode, Prism::ForwardingSuperNode, Prism::ForwardingArgumentsNode,
54
+ Prism::BlockArgumentNode, Prism::DefNode, Prism::SingletonClassNode, Prism::ForNode,
55
+ Prism::LocalVariableOperatorWriteNode, Prism::ClassVariableOperatorWriteNode, Prism::CallOperatorWriteNode,
56
+ Prism::CallOrWriteNode, Prism::CallAndWriteNode, Prism::IndexOperatorWriteNode, Prism::IndexOrWriteNode,
57
+ Prism::IndexAndWriteNode
58
+ ].freeze
59
+ # Literal receivers, by the core class their value is an instance of.
60
+ LITERAL_CLASSES = {
61
+ Prism::StringNode => "String", Prism::InterpolatedStringNode => "String", Prism::XStringNode => "String",
62
+ Prism::SymbolNode => "Symbol", Prism::InterpolatedSymbolNode => "Symbol", Prism::IntegerNode => "Integer",
63
+ Prism::FloatNode => "Float", Prism::ArrayNode => "Array", Prism::HashNode => "Hash",
64
+ Prism::RangeNode => "Range", Prism::RegularExpressionNode => "Regexp",
65
+ Prism::InterpolatedRegularExpressionNode => "Regexp", Prism::NilNode => "NilClass",
66
+ Prism::TrueNode => "TrueClass", Prism::FalseNode => "FalseClass"
67
+ }.freeze
68
+ # The methods `Kernel` or `Object` own that a read accepts on `self` when the project does not define them. Any
69
+ # other (`display`, `inspect`, `==`, `hash`, `extend`, `method`, `binding`, `instance_variables`) proves nothing.
70
+ # These may still convert a value they are given, which runs that value's methods: {PROTOCOL_NAMES} answers for
71
+ # a value that is `self`.
72
+ SELF_SAFE_NAMES = Set[
73
+ :warn, :puts, :print, :p, :pp, :format, :sprintf, :printf, :raise, :fail, :loop, :catch, :throw, :rand,
74
+ :sleep, :Integer, :Float, :String, :Array, :Hash, :Rational, :Complex, :block_given?, :frozen?, :nil?,
75
+ :is_a?, :kind_of?, :instance_of?, :object_id, :class, :__method__, :caller, :abort, :exit
76
+ ].freeze
77
+ # Issue #1596 — the methods core code calls on a value without the program spelling them: conversions,
78
+ # comparisons, hashing, iteration, `respond_to?` and its hook, `exception` for `raise`, `divmod` for `sleep`.
79
+ # A value aliasing `self` (`@me = self`, `me = self`, a constant holding the instance) handed to a core
80
+ # method runs `self`'s own definition of one of these, which the read never sees, so a class defining any
81
+ # proves nothing.
82
+ PROTOCOL_NAMES = Set[
83
+ :to_s, :inspect, :hash, :eql?, :==, :===, :<=>, :to_a, :to_ary, :to_str, :to_hash, :to_int, :to_i, :to_f,
84
+ :to_r, :to_c, :to_proc, :to_path, :to_sym, :to_io, :coerce, :exception, :divmod, :each, :respond_to?,
85
+ :respond_to_missing?, :method_missing, :initialize_copy, :marshal_dump, :pretty_print
86
+ ].freeze
87
+ private_constant :UNPROVABLE_NODES, :LITERAL_CLASSES, :SELF_SAFE_NAMES, :PROTOCOL_NAMES
88
+
89
+ # Raised inside a proof to abandon it.
90
+ class Unprovable < StandardError
91
+ end
92
+ private_constant :Unprovable
93
+
94
+ module_function
95
+
96
+ # The names of the instance variables `call_node`, an implicit-self or `self.` call, may write, as a frozen
97
+ # `Set`, or nil when the analysis cannot prove the set. `scope` is the scope the call's operands were typed in.
98
+ def of(call_node, scope)
99
+ return nil unless call_node.is_a?(Prism::CallNode)
100
+ return nil unless call_node.receiver.nil? || call_node.receiver.is_a?(Prism::SelfNode)
101
+
102
+ self_type = scope.self_type
103
+ return nil unless self_type.is_a?(Type::Nominal)
104
+
105
+ proof = Proof.new(self_type, scope)
106
+ proof.no_self_value(call_node.arguments)
107
+ proof.no_protocol_override
108
+ proof.self_call(call_node)
109
+ proof.read(call_node.block)
110
+ proof.written.freeze
111
+ rescue StandardError # an `Unprovable`, or a lookup that raised: either proves nothing
112
+ nil
113
+ end
114
+
115
+ # One proof's state: the instance variables read code writes, and the `def` bodies already read.
116
+ class Proof
117
+ attr_reader :written
118
+
119
+ def initialize(self_type, scope)
120
+ @self_type = self_type
121
+ @class_name = self_type.class_name
122
+ @scope = scope
123
+ @written = Set.new
124
+ @visited = {}.compare_by_identity
125
+ end
126
+
127
+ # Reads the method an implicit-self or `self.` call to `call_node`'s name runs.
128
+ def self_call(call_node)
129
+ name = call_node.name
130
+ unprovable! if GuardRebinding.code_running_name?(name) || refined?(name)
131
+
132
+ if ProjectMethodOwnership.defines?(@class_name, name, :instance, @scope)
133
+ read_def(proven_def(name))
134
+ elsif !SELF_SAFE_NAMES.include?(name) || GuardRebinding.type_method_foreign?(@self_type, name, @scope)
135
+ unprovable!
136
+ end
137
+ end
138
+
139
+ # Issue #1596 — gives up when `self`'s class, a project subclass, or a project ancestor or module on either's
140
+ # chain defines a {PROTOCOL_NAMES} method (a `def`, `define_method`, `attr_*` or alias), rewrites its methods
141
+ # beyond the names it spells, or is refined on one; or when either chain holds an external ancestor, whose
142
+ # own definitions the read cannot see.
143
+ def no_protocol_override
144
+ classes = [@class_name]
145
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) { |subclass| classes << subclass }
146
+ classes.each do |class_name|
147
+ chain = Scope::ResolutionChain.for(@scope, class_name.to_s, :instance, :methods)
148
+ unprovable! if chain.truncated? || chain.wildcard_mixin? || !settled?(chain)
149
+ chain.search(@scope) do |entry|
150
+ unprovable! if entry.external? || rewritten?(entry.name) || protocol_defined?(entry.name)
151
+ nil
152
+ end
153
+ end
154
+ end
155
+
156
+ # Issue #1596 — `self` handed on as a value (an argument, an interpolation, a splat, a `when` value, a literal
157
+ # element or key) lets the code it reaches run `self`'s methods by protocol (`to_s`, `hash`, `==`, `to_a`),
158
+ # which the read does not follow, so it proves nothing. Reads `node` for one, without reading anything else.
159
+ def no_self_value(node)
160
+ return unless node.is_a?(Prism::Node)
161
+
162
+ unprovable! if node.is_a?(Prism::SelfNode)
163
+ node.rigor_each_child { |child| no_self_value(child) }
164
+ end
165
+
166
+ # Reads `node`, adding each instance variable it writes.
167
+ def read(node)
168
+ return unless node.is_a?(Prism::Node)
169
+
170
+ unprovable! if UNPROVABLE_NODES.include?(node.class) || node.is_a?(Prism::SelfNode)
171
+ written = ivar_write_name(node)
172
+ @written << written if written
173
+ if node.is_a?(Prism::InstanceVariableOperatorWriteNode) && GuardRebinding.type_method_foreign?(
174
+ ivar_seed(node.name), node.binary_operator, @scope
175
+ )
176
+ unprovable!
177
+ end
178
+ read_call(node) if node.is_a?(Prism::CallNode)
179
+ # `self.m` is the one place `self` may stand: a call on it, which {#read_call} read.
180
+ receiver = call_receiver(node)
181
+ node.rigor_each_child { |child| read(child) unless child.is_a?(Prism::SelfNode) && child.equal?(receiver) }
182
+ end
183
+
184
+ private
185
+
186
+ # Whether a `refine` block anywhere in the project defines `name`, on any class: a `using` in effect where the
187
+ # read code sits may route the call to it (`refine Kernel do def warn …`), whatever the receiver.
188
+ def refined?(name)
189
+ @scope.discovered_refinements.any? { |_refined, methods| methods.key?(name) }
190
+ end
191
+
192
+ def call_receiver(node) = node.is_a?(Prism::CallNode) ? node.receiver : nil
193
+
194
+ def ivar_write_name(node)
195
+ case node
196
+ when Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
197
+ Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
198
+ node.name
199
+ end
200
+ end
201
+
202
+ def read_call(node)
203
+ receiver = node.receiver
204
+ return self_call(node) if receiver.nil? || receiver.is_a?(Prism::SelfNode)
205
+
206
+ unprovable! if GuardRebinding.code_running_name?(node.name) || refined?(node.name)
207
+ type = receiver_type(receiver)
208
+ unprovable! if type.nil? || GuardRebinding.type_method_foreign?(type, node.name, @scope)
209
+ end
210
+
211
+ def read_def(def_node)
212
+ return if @visited.key?(def_node)
213
+
214
+ @visited[def_node] = true
215
+ unprovable! if @visited.size > DEF_BUDGET
216
+ read(def_node.parameters)
217
+ read(def_node.body)
218
+ end
219
+
220
+ # The `def` the name resolves to on the class and on every project subclass, or unprovable.
221
+ def proven_def(name)
222
+ unprovable! if RefinedDispatch::OwnMethod.module_name?(@scope, @class_name)
223
+
224
+ owner = definer_ahead(@class_name, name, nil)
225
+ unprovable! if owner.nil?
226
+ def_node = @scope.user_def_for(owner, name)
227
+ unprovable! unless def_node.is_a?(Prism::DefNode) && def_node.receiver.nil?
228
+ single_definition(owner, name)
229
+
230
+ # A subclass whose superclass the tables do not know is enumerated too, and its chain ends short of the
231
+ # owner; one that reaches no definer of its own leaves the call to its superclass.
232
+ RefinedDispatch::OwnMethod.each_subclass(@scope, @class_name) do |subclass|
233
+ unprovable! unless [owner, nil].include?(definer_ahead(subclass, name, owner))
234
+ end
235
+ def_node
236
+ end
237
+
238
+ # Issue #1596 — the `def` the table holds must be the owner's only definition of `name`: the owner declared in
239
+ # one file, with one `def` of the name there (a reopening or a `class_eval` body in the same file is a second
240
+ # one), and no `define_method`, `attr_*` or alias of it, nor a rewrite no literal names (`T.define_method`,
241
+ # `T.send(:define_method, …)`, `T.class_eval` with a computed name), nor a definition the walk could not prove
242
+ # runs (a possible or contested one, as `DefinerResolution` reads them). The class edge files every declaring
243
+ # file.
244
+ def single_definition(owner, name)
245
+ Scope::ResolutionChain.record_class(@scope, owner) if Analysis::DependencyRecorder.active?
246
+ unprovable! if possible_definition?(owner, name)
247
+ bucket = @scope.discovered_parameter_envelopes[owner] || {}
248
+ unprovable! if bucket[[:instance, name]] == Source::ParameterEnvelope::OPAQUE
249
+ unprovable! if (@scope.discovered_class_sources[owner]&.size || 0) > 1
250
+ unprovable! if definition_count(owner, name) > 1
251
+ end
252
+
253
+ def possible_definition?(owner, name)
254
+ discovery = @scope.discovery
255
+ discovery.possible_method?(owner, name, :instance) ||
256
+ discovery.contested?(:discovered_def_nodes, [owner, name]) ||
257
+ discovery.contested?(:discovered_parameter_envelopes, [owner, [:instance, name]])
258
+ end
259
+
260
+ def definition_count(owner, name)
261
+ @scope.discovered_deferred_ranges.sum do |_path, rows|
262
+ rows.count { |row| row[2] == name && row[3] == :instance && row[4] == owner }
263
+ end
264
+ end
265
+
266
+ # Whether `chain`'s own order stands with no second world: no order mark and no fork. `ResolutionChain#settle`
267
+ # answers `:chain` for an answer no world can match only then; a fork sends it to the retro world, which the
268
+ # block answers otherwise, or to master's order.
269
+ def settled?(chain)
270
+ chain.settle(@scope, :chain_order) { :other_world } == :chain
271
+ end
272
+
273
+ def protocol_defined?(class_name)
274
+ refined = @scope.discovered_refinements[class_name] || {}
275
+ PROTOCOL_NAMES.any? { |name| defines_here?(class_name, name) || refined.key?(name) }
276
+ end
277
+
278
+ # Whether the project rewrites `class_name`'s method table beyond the names it spells, or refines it.
279
+ def rewritten?(class_name)
280
+ bucket = @scope.discovered_parameter_envelopes[class_name]
281
+ !bucket.nil? && Scope::DiscoveryIndex.rewritten_surface?(bucket)
282
+ end
283
+
284
+ # The first project class or module on `class_name`'s settled instance chain that defines `name`, stopping
285
+ # at `owner`; nil when the chain ends without one. An external ancestor reached first is unprovable: its
286
+ # signature, or a gem without one, may define the name.
287
+ def definer_ahead(class_name, name, owner)
288
+ chain = Scope::ResolutionChain.for(@scope, class_name.to_s, :instance, :methods)
289
+ unprovable! if chain.truncated? || chain.wildcard_mixin? || !settled?(chain)
290
+
291
+ chain.search(@scope) do |entry|
292
+ unprovable! if entry.external? || rewritten?(entry.name)
293
+
294
+ entry.name if entry.name == owner || defines_here?(entry.name, name)
295
+ end
296
+ end
297
+
298
+ # A `def`, `define_method`, `attr_*` or alias of `name` in `class_name`'s own body: the discovery table holds
299
+ # the last three project-wide, and the `def` table the first.
300
+ def defines_here?(class_name, name)
301
+ @scope.discovered_method?(class_name, name, :instance) || !@scope.def_node_slot(class_name, name).nil?
302
+ end
303
+
304
+ def receiver_type(node)
305
+ literal = LITERAL_CLASSES[node.class]
306
+ return Type::Combinator.nominal_of(literal) if literal
307
+
308
+ case node
309
+ when Prism::GlobalVariableReadNode then @scope.type_of(node)
310
+ when Prism::InstanceVariableReadNode then ivar_seed(node.name)
311
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then core_constant_type(node)
312
+ end
313
+ end
314
+
315
+ def ivar_seed(name)
316
+ @scope.class_ivars_for(@class_name)[name] || unprovable!
317
+ end
318
+
319
+ # A constant receiver read in the caller's scope, which the callee's lexical scope may resolve otherwise: only
320
+ # a core or standard-library class object or instance, under a name no project class or constant ends with.
321
+ def core_constant_type(node)
322
+ type = @scope.type_of(node)
323
+ return nil unless type.is_a?(Type::Singleton) || type.is_a?(Type::Nominal)
324
+
325
+ loader = @scope.environment.rbs_loader
326
+ return nil if loader.nil? || !loader.core_or_stdlib_class?(type.class_name)
327
+
328
+ segment = node.name.to_s
329
+ project = [@scope.discovered_classes, @scope.in_source_constants]
330
+ return nil if project.any? { |table| table.each_key.any? { |name| name.to_s.split("::").last == segment } }
331
+
332
+ type
333
+ end
334
+
335
+ def unprovable!
336
+ raise Unprovable
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end