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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +28 -5
- data/docs/manual/02-cli-reference.md +23 -4
- data/docs/manual/03-configuration.md +21 -8
- data/docs/manual/04-diagnostics.md +44 -2
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +32 -3
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +26 -9
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/rule_ids.rb +64 -0
- data/lib/rigor/analysis/check_rules.rb +68 -57
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/incremental.rb +12 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -3
- data/lib/rigor/analysis/incremental_session.rb +207 -40
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +139 -15
- data/lib/rigor/analysis/reachability/scan.rb +374 -173
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +6 -1
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +6 -4
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +74 -13
- data/lib/rigor/analysis/runner/pool_coordinator.rb +133 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +46 -1
- data/lib/rigor/analysis/runner.rb +62 -18
- data/lib/rigor/analysis/severity_stamp.rb +34 -1
- data/lib/rigor/analysis/worker_session.rb +58 -2
- data/lib/rigor/cache/descriptor.rb +40 -6
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +56 -12
- data/lib/rigor/cli/check_command.rb +138 -39
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +157 -24
- data/lib/rigor/cli.rb +30 -4
- data/lib/rigor/config_audit.rb +111 -15
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration.rb +15 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_loader.rb +79 -10
- data/lib/rigor/environment/required_features.rb +4 -2
- data/lib/rigor/environment.rb +12 -5
- data/lib/rigor/inference/block_call_timing.rb +5 -0
- data/lib/rigor/inference/builtins/cruby_definers.rb +22 -5
- data/lib/rigor/inference/captured_locals.rb +69 -6
- data/lib/rigor/inference/definer_resolution.rb +16 -7
- data/lib/rigor/inference/expression_typer.rb +117 -29
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +21 -17
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -16
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +52 -3
- data/lib/rigor/inference/module_function_state.rb +80 -13
- data/lib/rigor/inference/narrowing.rb +184 -68
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +38 -24
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +112 -40
- data/lib/rigor/inference/refined_dispatch.rb +62 -14
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +416 -1
- data/lib/rigor/inference/scope_indexer.rb +565 -61
- data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
- data/lib/rigor/inference/statement_evaluator.rb +307 -60
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/language_server/project_context.rb +56 -5
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +133 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +802 -0
- data/lib/rigor/lens/members.rb +203 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +322 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +41 -0
- data/lib/rigor/mcp/server.rb +61 -20
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/project_environment.rb +23 -1
- data/lib/rigor/protection/mutation_cache.rb +2 -1
- data/lib/rigor/scope.rb +33 -17
- data/lib/rigor/sig_gen/generator.rb +45 -5
- data/lib/rigor/sig_gen/layout_index.rb +36 -0
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/writer.rb +47 -34
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/sig/rigor/scope.rbs +3 -0
- data/sig/rigor.rbs +6 -3
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +21 -1
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
module RefinedDispatch
|
|
6
|
+
# Issue #1752 — the lookup of a class object, which Ruby walks like any object's: a refinement of `Object`,
|
|
7
|
+
# `Module`, `Class`, `Kernel` or `BasicObject` answers `Integer.oo`, because a class object is an `Object`.
|
|
8
|
+
#
|
|
9
|
+
# The walk is `C`'s singleton class with the modules it extends, then each superclass's singleton class with its
|
|
10
|
+
# extends, then the instance levels of `Class` (a class only), `Module`, `Object`, `Kernel` and `BasicObject`.
|
|
11
|
+
# A singleton class is a level whose own entry is a {SingletonClass}: a refinement table key names an instance
|
|
12
|
+
# side, so it never matches one (`refine C.singleton_class` records a class-unknown row), and it defines the
|
|
13
|
+
# name through its own singleton methods. An extended module is an instance-side entry, so `refine(Ext)`
|
|
14
|
+
# answers `C.z` where `C` extends `Ext` (Ruby 4.0.5).
|
|
15
|
+
#
|
|
16
|
+
# Every place the walk cannot read makes the whole walk unreadable (nil), where each reader declines: a chain
|
|
17
|
+
# cut at its limit or holding a mixin the tables cannot name, a superclass the tables do not know (`class K <
|
|
18
|
+
# <expr>`, `K = Class.new(Parent)`, an external superclass RBS does not declare), a class some constant write
|
|
19
|
+
# may have made (`Point = Struct.new(:x)`, whose superclass the tables do not record). A singleton level whose
|
|
20
|
+
# class records an `extend` the chain did not place there is read as `mixed` ({.skipped_extend?}).
|
|
21
|
+
module ClassObject
|
|
22
|
+
EMPTY = [].freeze
|
|
23
|
+
# The classes on every class object's lookup: the instance sides that hold `Class#new`, `Module#name` and
|
|
24
|
+
# `Object#tap`. A class no table places among them may still be one a class object reaches only when it is a
|
|
25
|
+
# module or a class the walk cannot place (`ClassObject.reachable?`).
|
|
26
|
+
TAIL_CLASSES = %w[Class Module Object Kernel BasicObject].freeze
|
|
27
|
+
private_constant :EMPTY
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# `[[level class, entries, mixed, every entry], …]` in {RefinedDispatch.levels}' shape for the class object
|
|
32
|
+
# `class_name`, or nil when the walk cannot be read. A singleton level's class is a {SingletonClass}.
|
|
33
|
+
def levels(scope, class_name)
|
|
34
|
+
environment = scope.environment
|
|
35
|
+
return nil if environment.nil?
|
|
36
|
+
return rbs_levels(scope, class_name) if environment.class_known?(class_name)
|
|
37
|
+
|
|
38
|
+
project_levels(scope, class_name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# RBS's singleton ancestry: each singleton class a level of its own, `mixed` where the project extends it at
|
|
42
|
+
# a position RBS does not record, and each instance side (a declared `extend`, `Class`, `Module`, …) a level
|
|
43
|
+
# `mixed` where the project mixes into it. `from` drops the leading singleton classes a project level already
|
|
44
|
+
# holds.
|
|
45
|
+
def rbs_levels(scope, class_name, from: 0)
|
|
46
|
+
loader = scope.environment&.rbs_loader
|
|
47
|
+
pairs = loader ? loader.singleton_ancestors_for(class_name) : EMPTY
|
|
48
|
+
return nil if pairs.empty?
|
|
49
|
+
|
|
50
|
+
readable(pairs.drop(from).map do |kind, name|
|
|
51
|
+
kind == :instance ? instance_level(scope, name) : singleton_level(scope, name)
|
|
52
|
+
end)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# RBS's instance ancestry of `class_name` (`Module`'s, for a module object's tail), each a level.
|
|
56
|
+
def rbs_instance_levels(scope, class_name)
|
|
57
|
+
loader = scope.environment&.rbs_loader
|
|
58
|
+
names = loader ? loader.ancestor_names_for(class_name) : EMPTY
|
|
59
|
+
return nil if names.empty?
|
|
60
|
+
|
|
61
|
+
readable(names.map { |name| instance_level(scope, name) })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def readable(levels) = levels.include?(nil) ? nil : levels
|
|
65
|
+
|
|
66
|
+
# A `mixed` level carries the modules the project mixes in beside its own entry, so a refinement of one of them
|
|
67
|
+
# makes the walk past the level {UNKNOWN} (`RefinedDispatch.targeted_after?`) and `call.undefined-method`'s
|
|
68
|
+
# reach holds it. Nil when that mixin chain cannot be read.
|
|
69
|
+
def instance_level(scope, name)
|
|
70
|
+
return [name, [name], false] unless RefinedDispatch.project_mixin?(scope, name)
|
|
71
|
+
|
|
72
|
+
mixins = mixin_entries(scope, name, :instance)
|
|
73
|
+
mixins && [name, [name, *mixins], true]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def singleton_level(scope, name)
|
|
77
|
+
return nil if instance_unreadable?(scope, name)
|
|
78
|
+
|
|
79
|
+
own = SingletonClass.new(name)
|
|
80
|
+
return [own, [own], false] unless singleton_mixin?(scope, name)
|
|
81
|
+
|
|
82
|
+
mixins = mixin_entries(scope, name, :singleton)
|
|
83
|
+
mixins && [own, [own, *mixins], true]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The names of the modules on `name`'s own level of its project chain on `side`, an unnamed entry's candidate
|
|
87
|
+
# names included, or nil when the chain is cut or holds a mixin the tables cannot name.
|
|
88
|
+
def mixin_entries(scope, name, side)
|
|
89
|
+
chain = Scope::ResolutionChain.for(scope, name, side, :methods)
|
|
90
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
91
|
+
return nil if unreadable?(chain)
|
|
92
|
+
return EMPTY if chain.level_count.zero?
|
|
93
|
+
|
|
94
|
+
names = level_names(chain, 0)
|
|
95
|
+
return nil if side == :singleton && skipped_extend?(scope, name, names)
|
|
96
|
+
|
|
97
|
+
names.reject { |entry| entry == name }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The names on level `index` of `chain` (an unnamed entry's candidate names included), the level's own
|
|
101
|
+
# singleton entry left out.
|
|
102
|
+
def level_names(chain, index)
|
|
103
|
+
chain.level_entries(index).flat_map do |entry|
|
|
104
|
+
next entry.candidates if entry.external?
|
|
105
|
+
|
|
106
|
+
entry.side == :instance ? [entry.name] : EMPTY
|
|
107
|
+
end.uniq
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Does `name` record an `extend` that the chain did not place on `name`'s own singleton level (`entries`, its
|
|
111
|
+
# names or candidate lists)? The chain skips an `extend` of a module already on the lookup, as Ruby does, but
|
|
112
|
+
# the tables record `class << self; prepend M` (and `singleton_class.prepend(M)`) as an `extend` too, and
|
|
113
|
+
# Ruby's `prepend` puts the module ahead of the class's own singleton methods again (`class Base3 < Top;
|
|
114
|
+
# class << self; prepend Ext` with `Top` extending `Ext`). Such a level cannot place that module, so it reads as
|
|
115
|
+
# `mixed`.
|
|
116
|
+
def skipped_extend?(scope, name, entries)
|
|
117
|
+
raws = scope.discovered_extends[name]
|
|
118
|
+
return false if raws.nil? || raws.empty?
|
|
119
|
+
|
|
120
|
+
placed = entries.flat_map { |entry| entry.is_a?(Array) ? entry : [entry] }
|
|
121
|
+
raws.any? { |raw| scope.ancestor_name_candidates(name, raw).none? { |candidate| placed.include?(candidate) } }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The project's singleton-side `Scope::ResolutionChain` (`C`'s singleton, its `extend`s' instance chains,
|
|
125
|
+
# then each project superclass's), spliced into RBS's singleton ancestry at an external superclass, or
|
|
126
|
+
# followed by the implicit tail after the last project class.
|
|
127
|
+
def project_levels(scope, class_name)
|
|
128
|
+
chain = Scope::ResolutionChain.for(scope, class_name, :singleton, :methods)
|
|
129
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
130
|
+
return nil if unreadable?(chain) || chain.level_count.zero?
|
|
131
|
+
|
|
132
|
+
out = []
|
|
133
|
+
chain.level_count.times do |index|
|
|
134
|
+
level_class = chain.level_classes[index]
|
|
135
|
+
return splice_external(scope, out, chain.level_entries(index)) if level_class.nil?
|
|
136
|
+
return nil if instance_unreadable?(scope, level_class)
|
|
137
|
+
|
|
138
|
+
own = SingletonClass.new(level_class)
|
|
139
|
+
every = chain.level_entries(index).map { |entry| level_entry(scope, entry, own) }
|
|
140
|
+
out << [own, every.grep_v(Array), skipped_extend?(scope, level_class, every), every]
|
|
141
|
+
end
|
|
142
|
+
tail = implicit_tail(scope, chain.level_classes[chain.level_count - 1])
|
|
143
|
+
tail && out.concat(tail)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# A chain cut at its limit, or holding a mixin the tables cannot name.
|
|
147
|
+
def unreadable?(chain) = chain.truncated? || chain.wildcard_mixin?
|
|
148
|
+
|
|
149
|
+
# Is `name`'s instance chain unreadable? A mixin the tables cannot name there includes the one an
|
|
150
|
+
# `included` hook makes (`base.extend(ClassMethods)`, `base.singleton_class.include(CM)`), which installs
|
|
151
|
+
# singleton methods on `name` that its singleton chain never records.
|
|
152
|
+
def instance_unreadable?(scope, name)
|
|
153
|
+
chain = Scope::ResolutionChain.for(scope, name, :instance, :methods)
|
|
154
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
155
|
+
unreadable?(chain)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def level_entry(scope, entry, own)
|
|
159
|
+
return own if !entry.external? && entry.side == :singleton && entry.name == own.of
|
|
160
|
+
return SingletonClass.new(entry.name) if !entry.external? && entry.side == :singleton
|
|
161
|
+
|
|
162
|
+
RefinedDispatch.entry_name(scope, entry) || RefinedDispatch.unnamed(entry)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# An external superclass stands for its whole singleton ancestry, which RBS gives when it declares the class.
|
|
166
|
+
#
|
|
167
|
+
# ADR-46 — a project superclass with no methods is not admitted by the `:methods` resolver, so it is external
|
|
168
|
+
# here and the walk is unreadable; each candidate's `class:` edge lets a new file that gives it a method (and so
|
|
169
|
+
# a project entry) re-check the consumer.
|
|
170
|
+
def splice_external(scope, out, entries)
|
|
171
|
+
external = entries.find(&:external?)
|
|
172
|
+
if Analysis::DependencyRecorder.active?
|
|
173
|
+
external&.candidates&.each { |candidate| Analysis::DependencyRecorder.read_last_segment(:class, candidate) }
|
|
174
|
+
end
|
|
175
|
+
name = external&.candidates&.find { |candidate| scope.environment&.class_known?(candidate) }
|
|
176
|
+
tail = name && rbs_levels(scope, name)
|
|
177
|
+
tail && out.concat(tail)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# What follows the last project class on the walk: `Module` onwards for a module; RBS's singleton ancestry
|
|
181
|
+
# past the class's own singleton for a class RBS declares (a reopened core class, a `sig/` class);
|
|
182
|
+
# `Object`'s for a class with no superclass written. Nil where the superclass is not known: one the tables
|
|
183
|
+
# record as unknown, and one a constant write may have given the class (a class only a constant write names
|
|
184
|
+
# carries no superclass entry, and a reopening keeps the superclass that write made).
|
|
185
|
+
def implicit_tail(scope, name)
|
|
186
|
+
return rbs_instance_levels(scope, "Module") if module?(scope, name)
|
|
187
|
+
return nil if unknown_superclass?(scope, name)
|
|
188
|
+
return rbs_levels(scope, name, from: 1) if scope.environment&.class_known?(name)
|
|
189
|
+
|
|
190
|
+
rbs_levels(scope, "Object")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def module?(scope, name)
|
|
194
|
+
scope.discovered_parameter_envelopes[name]&.key?(Scope::DiscoveryIndex::ENVELOPE_MODULE_MARK) ||
|
|
195
|
+
scope.environment&.rbs_module?(name) || false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def unknown_superclass?(scope, name)
|
|
199
|
+
superclasses = scope.discovered_superclasses
|
|
200
|
+
return true if superclasses.key?(name) && superclasses[name].nil?
|
|
201
|
+
return true if scope.discovered_parameter_envelopes[name]&.key?(
|
|
202
|
+
Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
Analysis::DependencyRecorder.read_last_segment(:constant, name) if Analysis::DependencyRecorder.active?
|
|
206
|
+
!scope.bound_constant_names(name).empty?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Does the project mix a module into `name`'s singleton class, at a position RBS does not record?
|
|
210
|
+
def singleton_mixin?(scope, name)
|
|
211
|
+
Analysis::DependencyRecorder.read_last_segment(:class, name) if Analysis::DependencyRecorder.active?
|
|
212
|
+
return true if scope.discovered_extends.key?(name)
|
|
213
|
+
|
|
214
|
+
Scope::ResolutionChain.for(scope, name, :singleton, :methods).wildcard_mixin?
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Does the singleton class of `name` define `method_name` itself: a singleton method the project writes in
|
|
218
|
+
# `name`'s own body (`def self.x`, `class << self`, an `attr_*` there), or one RBS declares on `name`'s
|
|
219
|
+
# singleton? A method the extends fold copied onto the singleton from a module `name` extends is that
|
|
220
|
+
# module's, found where the module sits.
|
|
221
|
+
def defines?(scope, name, method_name)
|
|
222
|
+
return true if own_singleton_definer?(scope, name, method_name)
|
|
223
|
+
|
|
224
|
+
definition = ExternalAncestorResolution.method_definition(name, method_name, :singleton, scope: scope)
|
|
225
|
+
ExternalAncestorResolution.declared_on_class?(definition, name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# A singleton `def` is the class's own unless it is the def a module the class extends holds, which the extends
|
|
229
|
+
# fold copied ({.fold_copy?}). A singleton name without a node of its own (`attr_reader` or `define_method` in
|
|
230
|
+
# `class << self`) counts when the existence table holds it and the class's envelope table records it, which
|
|
231
|
+
# the fold never writes. A `define_singleton_method` in the body records only the opaque envelope entry, so it
|
|
232
|
+
# is no definer here; the typed arm reads that entry as a rewritten singleton name ({.rewritten?}).
|
|
233
|
+
def own_singleton_definer?(scope, name, method_name)
|
|
234
|
+
node = scope.singleton_def_for(name, method_name)
|
|
235
|
+
return true if node && !fold_copy?(scope, name, method_name)
|
|
236
|
+
return false unless scope.discovered_method?(name, method_name, :singleton)
|
|
237
|
+
|
|
238
|
+
node.nil? || scope.parameter_envelopes_of(name).key?([:singleton, method_name.to_sym])
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Is `name`'s singleton `def` of the name the receiverless instance `def` of the module its `Module.nesting`
|
|
242
|
+
# starts with, which the extends fold copied? The node is that module's own
|
|
243
|
+
# (`SingletonHookDecline.copied_from?`), or, where the two tables came from two parses of the file, one with
|
|
244
|
+
# its location and text, and no site of a singleton `def` of the name is recorded for `name` (the fold copies
|
|
245
|
+
# nodes, not sites).
|
|
246
|
+
def fold_copy?(scope, name, method_name)
|
|
247
|
+
head = SingletonHookDecline.def_head(scope, name, method_name)
|
|
248
|
+
return false if head.nil? || head == name
|
|
249
|
+
return true if SingletonHookDecline.copied_from?(scope, head, method_name, name)
|
|
250
|
+
|
|
251
|
+
node = scope.singleton_def_for(name, method_name)
|
|
252
|
+
source = scope.user_def_for(head, method_name)
|
|
253
|
+
!node.nil? && node.receiver.nil? && !source.nil? && same_def?(node, source) &&
|
|
254
|
+
scope.discovery.discovered_singleton_def_sources.dig(name, method_name.to_sym).nil?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def same_def?(node, other)
|
|
258
|
+
node.location.start_offset == other.location.start_offset &&
|
|
259
|
+
node.location.end_offset == other.location.end_offset && node.slice == other.slice
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The typed arm's answer at a singleton level (`entries` is every entry, an unnamed one kept as its candidate
|
|
263
|
+
# names): {Winner}, {UNKNOWN}, nil (the class object's own lookup answers), `:continue`, or `:continue_unsure`
|
|
264
|
+
# when only an unnamed entry may answer, so a refinement the walk reaches later is {UNKNOWN}
|
|
265
|
+
# (`RefinedDispatch.walk`). The tables record `class << self; prepend M` (and `singleton_class.prepend(M)`) as
|
|
266
|
+
# an `extend`, so a module on the level may sit ahead of the singleton class or of another module: one entry
|
|
267
|
+
# that answers decides, several that answer decide only when none of them is refined.
|
|
268
|
+
def walk_level(scope, entries, method_name, targets, unread)
|
|
269
|
+
answers = entries.filter_map { |entry| entry_answer(scope, entry, method_name, targets, unread) }
|
|
270
|
+
return :continue if answers.empty?
|
|
271
|
+
|
|
272
|
+
known = answers.reject { |answer| answer == UNNAMED }
|
|
273
|
+
return :continue_unsure if known.empty?
|
|
274
|
+
return known.first.answer if answers.size == 1
|
|
275
|
+
|
|
276
|
+
known.all? { |answer| answer.answer.nil? } ? nil : UNKNOWN
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
Answer = Data.define(:answer)
|
|
280
|
+
# An entry no table names (an `extend` of a module from outside the analysed paths): it may define the name.
|
|
281
|
+
UNNAMED = Answer.new(:unnamed)
|
|
282
|
+
private_constant :Answer, :UNNAMED
|
|
283
|
+
|
|
284
|
+
def entry_answer(scope, entry, method_name, targets, unread)
|
|
285
|
+
if entry.is_a?(Array)
|
|
286
|
+
refined = entry.any? { |candidate| targets.key?(candidate) || unread.key?(candidate) }
|
|
287
|
+
return refined ? Answer.new(UNKNOWN) : UNNAMED
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
if entry.is_a?(SingletonClass)
|
|
291
|
+
return Answer.new(UNKNOWN) if rewritten?(scope, entry.of, method_name)
|
|
292
|
+
else
|
|
293
|
+
return Answer.new(UNKNOWN) if unread.key?(entry)
|
|
294
|
+
|
|
295
|
+
refining = targets[entry]
|
|
296
|
+
return Answer.new(Winner.new(module_name: refining, refined_class: entry)) if refining
|
|
297
|
+
end
|
|
298
|
+
Answer.new(nil) if RefinedDispatch.defines?(scope, entry, method_name)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Does the project rewrite `name`'s singleton methods in a way the walk cannot read: the class's dynamic mark
|
|
302
|
+
# (a `class_eval`, a computed definer, a `send`), or an opaque envelope entry for the singleton name (a
|
|
303
|
+
# `define_singleton_method`, a `def Const.x` written outside `name`'s body, an alias or a wrapping macro)?
|
|
304
|
+
def rewritten?(scope, name, method_name)
|
|
305
|
+
bucket = scope.parameter_envelopes_of(name)
|
|
306
|
+
bucket.key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK) ||
|
|
307
|
+
Source::ParameterEnvelope.opaque?(bucket[[:singleton, method_name.to_sym]])
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Every class and module whose refinements a call on the class object `class_name` may reach, for
|
|
311
|
+
# `call.undefined-method`'s decline: each name on the levels (an unnamed entry's candidates and a `mixed`
|
|
312
|
+
# level's project mixins included), and the RBS ancestry of a module an extend names that the project does
|
|
313
|
+
# not declare. Nil when the walk cannot be read.
|
|
314
|
+
def reach(scope, class_name)
|
|
315
|
+
levels = levels(scope, class_name)
|
|
316
|
+
return nil if levels.nil?
|
|
317
|
+
|
|
318
|
+
names = Set.new
|
|
319
|
+
levels.each do |_level_class, entries, _mixed, every|
|
|
320
|
+
(every || entries).each { |entry| add_reach(scope, names, entry) }
|
|
321
|
+
end
|
|
322
|
+
names
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def add_reach(scope, names, entry)
|
|
326
|
+
return names.merge(entry) if entry.is_a?(Array)
|
|
327
|
+
return if entry.is_a?(SingletonClass)
|
|
328
|
+
|
|
329
|
+
names << entry
|
|
330
|
+
return if scope.discovered_classes.key?(entry)
|
|
331
|
+
|
|
332
|
+
loader = scope.environment&.rbs_loader
|
|
333
|
+
names.merge(loader.ancestor_names_for(entry)) if loader
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Can a refined-class key sit on some class object's lookup the walk could not read? Every class object
|
|
337
|
+
# reaches the {TAIL_CLASSES} and whatever modules it extends, so only a class known to be none of those is
|
|
338
|
+
# out of reach: a project class that is not a module, or one RBS declares as a class. A key nothing declares
|
|
339
|
+
# may be a module.
|
|
340
|
+
def reachable?(scope, key)
|
|
341
|
+
return true if TAIL_CLASSES.include?(key)
|
|
342
|
+
return false if scope.discovered_classes.key?(key) && !module?(scope, key)
|
|
343
|
+
|
|
344
|
+
environment = scope.environment
|
|
345
|
+
!(environment&.class_known?(key) && !environment.rbs_module?(key))
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -26,6 +26,12 @@ module Rigor
|
|
|
26
26
|
#
|
|
27
27
|
# It shares the level walk with the typed arm's {RefinedDispatch.winner} and nothing else: the typer types
|
|
28
28
|
# only from rows the lexical lookup resolves, while a decline must count every row.
|
|
29
|
+
#
|
|
30
|
+
# Issue #1752 — `kind: :singleton` asks the same of a class object, over its singleton levels
|
|
31
|
+
# ({ClassObject}): `Integer.sqrt` (CRuby's own singleton method of `Integer`) answers ahead of a refinement of
|
|
32
|
+
# `Object#sqrt`, while a refinement of a module the class extends answers ahead of the class's methods
|
|
33
|
+
# further up. A singleton class's definer is proven by its own `def self.x` / `class << self` definer
|
|
34
|
+
# (`DefinerResolution` `Known` on the singleton side) or by the catalogue's singleton methods.
|
|
29
35
|
module OwnMethod # rubocop:disable Metrics/ModuleLength
|
|
30
36
|
# One candidate definer as `DefinerResolution`'s per-chain answer function reports it, read through the
|
|
31
37
|
# same four readers as the module's own hit (a call site may not name that one).
|
|
@@ -35,9 +41,10 @@ module Rigor
|
|
|
35
41
|
|
|
36
42
|
module_function
|
|
37
43
|
|
|
38
|
-
# Is `method_name` on an instance of `class_name`
|
|
39
|
-
# refinement the in-effect list `list` (no unknown
|
|
40
|
-
|
|
44
|
+
# Is `method_name` on an instance of `class_name` (on the class object itself for `kind: :singleton`)
|
|
45
|
+
# answered by the receiver's own method, ahead of every refinement the in-effect list `list` (no unknown
|
|
46
|
+
# marker, no opaque module) puts in effect?
|
|
47
|
+
def answers?(scope, class_name, method_name, list, kind: :instance)
|
|
41
48
|
InEffectRefinements.record_refinement_names(method_name)
|
|
42
49
|
raw = raw_targets(scope.discovered_refinements, method_name, list)
|
|
43
50
|
return true if raw.nil?
|
|
@@ -47,11 +54,11 @@ module Rigor
|
|
|
47
54
|
record_targets(scope, raw)
|
|
48
55
|
return false if raw.each_key.any? { |refined| InEffectRefinements.class_wildcard_key?(scope, refined) }
|
|
49
56
|
|
|
50
|
-
levels = RefinedDispatch.levels(scope, class_name)
|
|
57
|
+
levels = RefinedDispatch.levels(scope, class_name, kind: kind)
|
|
51
58
|
return false if levels.nil?
|
|
52
|
-
return false if refined_off_levels?(scope, class_name, levels, raw)
|
|
59
|
+
return false if refined_off_levels?(scope, [class_name, kind], levels, raw)
|
|
53
60
|
|
|
54
|
-
shadowing_definer?(scope, class_name, levels, method_name, raw)
|
|
61
|
+
shadowing_definer?(scope, [class_name, kind], levels, method_name, raw)
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
# `{refined-class key => [its in-effect refining modules]}` for every key with a row for `method_name` or a
|
|
@@ -119,15 +126,20 @@ module Rigor
|
|
|
119
126
|
# module (a module already on the class's levels is prepended again by `prepend`); a class RBS declares as
|
|
120
127
|
# a subclass; a module not on the levels, which `obj.extend` or a subclass's mixin can put ahead of the
|
|
121
128
|
# definer; and a module on the levels that some code prepends to an object's singleton class.
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
#
|
|
130
|
+
# Issue #1752 — for a class object (`receiver` `[class_name, :singleton]`) a value typed as the class object
|
|
131
|
+
# may be a subclass's, whose singleton level may extend a refined module; and a class is off its lookup when it
|
|
132
|
+
# is placed away from `Module`, of which every class object is an instance.
|
|
133
|
+
def refined_off_levels?(scope, (class_name, kind), levels, raw)
|
|
134
|
+
return true if unplaced_target?(scope, raw) || subclass_refined?(scope, class_name, raw, kind)
|
|
124
135
|
|
|
136
|
+
placed_from = kind == :singleton ? "Module" : class_name
|
|
125
137
|
on_levels = level_names(levels)
|
|
126
138
|
raw.each_key.any? do |refined|
|
|
127
139
|
next object_prepended?(scope, refined) if on_levels.include?(refined)
|
|
128
140
|
next true if module_name?(scope, refined)
|
|
129
141
|
|
|
130
|
-
declared_class?(scope, refined) && !placed_away?(scope, refined,
|
|
142
|
+
declared_class?(scope, refined) && !placed_away?(scope, refined, placed_from)
|
|
131
143
|
end
|
|
132
144
|
end
|
|
133
145
|
|
|
@@ -233,12 +245,16 @@ module Rigor
|
|
|
233
245
|
# refined entry on its own level, or a level the walk cannot read?
|
|
234
246
|
#
|
|
235
247
|
# A subclass level the walk cannot read (a truncated chain, or a mixin the tables cannot name) hides only
|
|
236
|
-
# modules, so it declines only when some in-effect row names a module.
|
|
237
|
-
|
|
248
|
+
# modules, so it declines only when some in-effect row names a module. `kind` `:singleton` reads each
|
|
249
|
+
# subclass's singleton level, whose own singleton class no refinement table key names, and which cannot place
|
|
250
|
+
# an `extend` the chain skipped there (`ClassObject.skipped_extend?`: it may be a `prepend` the tables record as
|
|
251
|
+
# one).
|
|
252
|
+
def subclass_refined?(scope, class_name, raw, kind = :instance)
|
|
238
253
|
each_subclass(scope, class_name) do |subclass|
|
|
239
|
-
chain = Scope::ResolutionChain.for(scope, subclass,
|
|
254
|
+
chain = Scope::ResolutionChain.for(scope, subclass, kind, :methods)
|
|
240
255
|
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
241
|
-
if chain.truncated? || chain.wildcard_mixin? || chain.level_count.zero?
|
|
256
|
+
if chain.truncated? || chain.wildcard_mixin? || chain.level_count.zero? ||
|
|
257
|
+
(kind == :singleton && singleton_level_unreadable?(scope, subclass, chain))
|
|
242
258
|
return true if raw.each_key.any? { |refined| module_name?(scope, refined) }
|
|
243
259
|
|
|
244
260
|
next
|
|
@@ -248,8 +264,16 @@ module Rigor
|
|
|
248
264
|
false
|
|
249
265
|
end
|
|
250
266
|
|
|
267
|
+
# A subclass's singleton level is unreadable where it records an `extend` the chain skipped, or where its
|
|
268
|
+
# instance chain holds a mixin the tables cannot name, which an `included` hook can use to extend it.
|
|
269
|
+
def singleton_level_unreadable?(scope, subclass, chain)
|
|
270
|
+
ClassObject.skipped_extend?(scope, subclass, ClassObject.level_names(chain, 0)) ||
|
|
271
|
+
ClassObject.instance_unreadable?(scope, subclass)
|
|
272
|
+
end
|
|
273
|
+
|
|
251
274
|
def refined_entry?(entry, raw)
|
|
252
275
|
return entry.candidates.any? { |candidate| raw.include?(candidate) } if entry.external?
|
|
276
|
+
return false if entry.side == :singleton
|
|
253
277
|
|
|
254
278
|
raw.include?(entry.name)
|
|
255
279
|
end
|
|
@@ -306,20 +330,34 @@ module Rigor
|
|
|
306
330
|
# may be prepended or refined), an unproven definer, and the end of the walk all answer false. An entry no
|
|
307
331
|
# table names is passed over otherwise: it cannot be proven a definer, and a definer the walk misses there
|
|
308
332
|
# sits ahead of every refinement the walk then reaches.
|
|
309
|
-
|
|
333
|
+
#
|
|
334
|
+
# Issue #1752 — a singleton level with a refined module answers false whatever its order: the tables record
|
|
335
|
+
# `class << self; prepend M` as an `extend`, so the module may sit ahead of the class's own singleton methods.
|
|
336
|
+
def shadowing_definer?(scope, receiver, levels, method_name, raw)
|
|
310
337
|
levels.each do |level_class, entries, mixed, every|
|
|
311
338
|
return false if mixed || (level_class && raw.include?(level_class))
|
|
312
339
|
|
|
313
|
-
|
|
340
|
+
row = every || entries
|
|
341
|
+
return false if level_class.is_a?(SingletonClass) && singleton_level_refined?(row, raw)
|
|
342
|
+
|
|
343
|
+
row.each do |entry|
|
|
314
344
|
case entry_answer(scope, entry, level_class, method_name, raw)
|
|
315
345
|
when :refined then return false
|
|
316
|
-
when :defines then return proven_definer?(scope,
|
|
346
|
+
when :defines then return proven_definer?(scope, receiver, entry, method_name)
|
|
317
347
|
end
|
|
318
348
|
end
|
|
319
349
|
end
|
|
320
350
|
false
|
|
321
351
|
end
|
|
322
352
|
|
|
353
|
+
def singleton_level_refined?(row, raw)
|
|
354
|
+
row.any? do |entry|
|
|
355
|
+
next entry.any? { |candidate| raw.include?(candidate) } if entry.is_a?(Array)
|
|
356
|
+
|
|
357
|
+
!entry.is_a?(SingletonClass) && raw.include?(entry)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
323
361
|
# `:refined`, `:defines`, or nil to move on. The level's own class was asked before its entries.
|
|
324
362
|
def entry_answer(scope, entry, level_class, method_name, raw)
|
|
325
363
|
return entry.any? { |candidate| raw.include?(candidate) } ? :refined : nil if entry.is_a?(Array)
|
|
@@ -346,57 +384,91 @@ module Rigor
|
|
|
346
384
|
#
|
|
347
385
|
# ADR-46 — the envelope bucket records the files declaring `entry`, and the class edge a new file reopening
|
|
348
386
|
# it (to `remove_method` the name, say) fires.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
387
|
+
#
|
|
388
|
+
# Issue #1752 — a {SingletonClass} entry is proven on its class's singleton side: its own singleton definer,
|
|
389
|
+
# `Known` on the class object's chain, or the catalogue's singleton methods. Any other entry of a class
|
|
390
|
+
# object's walk (an extended module; `Class`, `Module`, `Object`, `Kernel`) is proven as an instance-side
|
|
391
|
+
# entry is, with the certainty read over the class object's chain.
|
|
392
|
+
def proven_definer?(scope, receiver, entry, method_name)
|
|
393
|
+
owner, side = entry.is_a?(SingletonClass) ? [entry.of, :singleton] : [entry, :instance]
|
|
394
|
+
Analysis::DependencyRecorder.read_last_segment(:class, owner) if Analysis::DependencyRecorder.active?
|
|
395
|
+
bucket = scope.parameter_envelopes_of(owner)
|
|
352
396
|
return false if bucket.key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK)
|
|
353
397
|
return false if bucket.key?([Scope::DiscoveryIndex::ENVELOPE_REMOVED_KIND, method_name.to_sym])
|
|
354
398
|
|
|
355
|
-
opaque = Source::ParameterEnvelope.opaque?(bucket[[
|
|
356
|
-
return true if project_definer?(scope,
|
|
399
|
+
opaque = Source::ParameterEnvelope.opaque?(bucket[[side, method_name.to_sym]])
|
|
400
|
+
return true if project_definer?(scope, receiver, [owner, side], method_name, opaque)
|
|
401
|
+
return false if opaque
|
|
357
402
|
|
|
358
|
-
|
|
403
|
+
if side == :singleton
|
|
404
|
+
Builtins::CRubyDefiners.singleton_defines?(owner, method_name)
|
|
405
|
+
else
|
|
406
|
+
Builtins::CRubyDefiners.defines?(owner, method_name)
|
|
407
|
+
end
|
|
359
408
|
end
|
|
360
409
|
|
|
361
410
|
# A `def` counts while its envelope is its own (an alias or a wrapping macro makes it opaque); an `attr_*` or
|
|
362
411
|
# `define_method` always records an opaque one, so for those the existence table and the certainty read are
|
|
363
|
-
# the proof, beside the removal check above.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
412
|
+
# the proof, beside the removal check above. A singleton name the extends fold copied from an extended module
|
|
413
|
+
# is that module's (`ClassObject.own_singleton_definer?`).
|
|
414
|
+
def project_definer?(scope, receiver, (owner, side), method_name, opaque)
|
|
415
|
+
own = side == :singleton ? own_singleton_kind(scope, owner, method_name) : own_kind(scope, owner, method_name)
|
|
416
|
+
return false if own.nil? || (own == :def && opaque)
|
|
417
|
+
|
|
418
|
+
certain_owner?(scope, receiver, owner, method_name)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# `:def` for a `def` of the owner's own, `:other` for another definer the existence table records, or nil.
|
|
422
|
+
def own_kind(scope, owner, method_name)
|
|
423
|
+
return :def if scope.user_def_for(owner, method_name)
|
|
370
424
|
|
|
371
|
-
|
|
425
|
+
:other if scope.discovered_method?(owner, method_name, :instance)
|
|
372
426
|
end
|
|
373
427
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
428
|
+
def own_singleton_kind(scope, owner, method_name)
|
|
429
|
+
node = scope.singleton_def_for(owner, method_name)
|
|
430
|
+
return :def if node && !ClassObject.fold_copy?(scope, owner, method_name)
|
|
431
|
+
|
|
432
|
+
:other if ClassObject.own_singleton_definer?(scope, owner, method_name)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# ADR-119 WD2 — the candidate-set read over the receiver's chain (the class object's for `kind` `:singleton`),
|
|
436
|
+
# with every project `def`, `attr_*` and `define_method` as a candidate: true only for a `Known` answer whose
|
|
437
|
+
# owner is `owner`.
|
|
438
|
+
def certain_owner?(scope, (class_name, kind), owner, method_name)
|
|
439
|
+
case DefinerResolution.resolve(scope, class_name, method_name, kind, question: :definer,
|
|
378
440
|
&definer_answer(scope, method_name))
|
|
379
|
-
in DefinerResolution::Known(answer: _answer, owner:) then
|
|
441
|
+
in DefinerResolution::Known(answer: _answer, owner: found) then found == owner
|
|
380
442
|
in DefinerResolution::UNKNOWN then false
|
|
381
443
|
in DefinerResolution::ABSENT then false # rubocop:disable Lint/DuplicateBranch
|
|
382
444
|
end
|
|
383
445
|
end
|
|
384
446
|
|
|
385
|
-
# The first project entry at or after `from` that defines `method_name` on its
|
|
386
|
-
#
|
|
447
|
+
# The first project entry at or after `from` that defines `method_name` on its side, by a `def` or by another
|
|
448
|
+
# definer the existence table records (`attr_*`, `define_method`; an `attr_*` or `define_method` in
|
|
449
|
+
# `class << self` on a singleton entry, while a body's `define_singleton_method` records no existence entry
|
|
450
|
+
# and proves nothing). A singleton name the extends fold copied is asked past by `DefinerResolution` itself,
|
|
451
|
+
# so the module that wrote it answers at its own position.
|
|
387
452
|
def definer_answer(scope, method_name)
|
|
388
453
|
lambda do |chain, from|
|
|
389
454
|
index = from - 1
|
|
390
455
|
chain.search(scope, from) do |entry|
|
|
391
456
|
index += 1
|
|
392
|
-
next if entry.external? || entry
|
|
393
|
-
next unless scope.user_def_for(entry.name, method_name) ||
|
|
394
|
-
scope.discovered_method?(entry.name, method_name, :instance)
|
|
457
|
+
next if entry.external? || !entry_defines?(scope, entry, method_name)
|
|
395
458
|
|
|
396
459
|
DefinerHit.new(entry.name, entry.name, index, entry.side)
|
|
397
460
|
end
|
|
398
461
|
end
|
|
399
462
|
end
|
|
463
|
+
|
|
464
|
+
def entry_defines?(scope, entry, method_name)
|
|
465
|
+
name = entry.name
|
|
466
|
+
if entry.side == :singleton
|
|
467
|
+
scope.singleton_def_for(name, method_name) || scope.discovered_method?(name, method_name, :singleton)
|
|
468
|
+
else
|
|
469
|
+
scope.user_def_for(name, method_name) || scope.discovered_method?(name, method_name, :instance)
|
|
470
|
+
end
|
|
471
|
+
end
|
|
400
472
|
end
|
|
401
473
|
end
|
|
402
474
|
end
|