rigortype 0.4.1 → 0.4.3
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/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- 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 +41 -4
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- 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 +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +140 -40
- 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/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- 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_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- 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/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- 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 +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -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-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../builtins/cruby_definers"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module RefinedDispatch
|
|
8
|
+
# Issue #1740 — does the receiver's own lookup answer a call ahead of every refinement in effect, so that
|
|
9
|
+
# `call.wrong-arity` and `call.argument-type-mismatch` may check it against the receiver's own method? Ruby
|
|
10
|
+
# finds a method the receiver's class (or a nearer ancestor) defines before it consults an ancestor's
|
|
11
|
+
# refinement: under `refine Object do def center(a, b, c)`, `"x".center(1, 2, 3)` still runs `String#center`
|
|
12
|
+
# and raises.
|
|
13
|
+
#
|
|
14
|
+
# It answers yes only from positive knowledge (the caller has already declined on an in-effect list that
|
|
15
|
+
# carries {InEffectRefinements::UNKNOWN} or an opaque module), and every condition it cannot prove is a no:
|
|
16
|
+
#
|
|
17
|
+
# - it reads the RAW refinement table, every name a `refine` argument's spelling can denote and the wildcard
|
|
18
|
+
# rows included, never {RefinedDispatch.resolved}'s typing subset: a decline needs only that a row may
|
|
19
|
+
# denote a class on the walk;
|
|
20
|
+
# - no in-effect row may name a class the walk cannot name, a project subclass of the receiver's class (a
|
|
21
|
+
# value typed as the class may be one), or a module the receiver's levels do not hold (`obj.extend(M)`,
|
|
22
|
+
# or a subclass's mixin);
|
|
23
|
+
# - walking {RefinedDispatch.levels} from the most derived, the first level or entry that is refined or
|
|
24
|
+
# defines the name must define it, with no `mixed` level (a project mixin RBS does not place) before it;
|
|
25
|
+
# - that definer must be proven ({.proven_definer?}): RBS alone is not proof.
|
|
26
|
+
#
|
|
27
|
+
# It shares the level walk with the typed arm's {RefinedDispatch.winner} and nothing else: the typer types
|
|
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.
|
|
35
|
+
module OwnMethod # rubocop:disable Metrics/ModuleLength
|
|
36
|
+
# One candidate definer as `DefinerResolution`'s per-chain answer function reports it, read through the
|
|
37
|
+
# same four readers as the module's own hit (a call site may not name that one).
|
|
38
|
+
DefinerHit = Data.define(:answer, :owner, :index, :side)
|
|
39
|
+
EMPTY = [].freeze
|
|
40
|
+
private_constant :DefinerHit, :EMPTY
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
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)
|
|
48
|
+
InEffectRefinements.record_refinement_names(method_name)
|
|
49
|
+
raw = raw_targets(scope.discovered_refinements, method_name, list)
|
|
50
|
+
return true if raw.nil?
|
|
51
|
+
|
|
52
|
+
add_ancestor_spellings(scope, raw)
|
|
53
|
+
|
|
54
|
+
record_targets(scope, raw)
|
|
55
|
+
return false if raw.each_key.any? { |refined| InEffectRefinements.class_wildcard_key?(scope, refined) }
|
|
56
|
+
|
|
57
|
+
levels = RefinedDispatch.levels(scope, class_name, kind: kind)
|
|
58
|
+
return false if levels.nil?
|
|
59
|
+
return false if refined_off_levels?(scope, [class_name, kind], levels, raw)
|
|
60
|
+
|
|
61
|
+
shadowing_definer?(scope, [class_name, kind], levels, method_name, raw)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# `{refined-class key => [its in-effect refining modules]}` for every key with a row for `method_name` or a
|
|
65
|
+
# names-wildcard row that `list` puts in effect, by every spelling the table records, or nil when there is
|
|
66
|
+
# none.
|
|
67
|
+
def raw_targets(refinements, method_name, list)
|
|
68
|
+
out = nil
|
|
69
|
+
refinements.each do |refined, methods|
|
|
70
|
+
modules = in_effect_modules(methods[method_name], list) |
|
|
71
|
+
in_effect_modules(methods[RefinedDispatch.wildcard], list)
|
|
72
|
+
(out ||= {})[refined] = modules unless modules.empty?
|
|
73
|
+
end
|
|
74
|
+
out
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Ruby looks a constant up through the refining module's ancestors before the top level, so `refine(Bar)` in
|
|
78
|
+
# a module that includes `Foo` refines `Foo::Bar` when `Foo` holds one; the table records the lexical
|
|
79
|
+
# spellings only (`R::Bar`, `Bar`). Each declared `<ancestor>::<last segment>` joins the raw targets for the
|
|
80
|
+
# same refining module: over-adding only declines.
|
|
81
|
+
def add_ancestor_spellings(scope, raw)
|
|
82
|
+
raw.to_a.each do |refined, modules|
|
|
83
|
+
segment = refined.split("::").last
|
|
84
|
+
modules.each do |module_name|
|
|
85
|
+
ancestor_modules(scope, module_name).each do |ancestor|
|
|
86
|
+
spelling = "#{ancestor}::#{segment}"
|
|
87
|
+
next unless declared_class?(scope, spelling)
|
|
88
|
+
|
|
89
|
+
raw[spelling] = (raw[spelling] || EMPTY) | [module_name]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The modules `module_name` includes or prepends, transitively (`InEffectRefinements.activated_modules`, which
|
|
96
|
+
# records the chain's edges), itself left out.
|
|
97
|
+
def ancestor_modules(scope, module_name)
|
|
98
|
+
return EMPTY if module_name == RefinedDispatch.wildcard
|
|
99
|
+
|
|
100
|
+
ancestors = InEffectRefinements.activated_modules(scope, module_name)
|
|
101
|
+
ancestors.is_a?(Array) ? ancestors - [module_name] : EMPTY
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def in_effect_modules(rows, list)
|
|
105
|
+
return EMPTY if rows.nil?
|
|
106
|
+
|
|
107
|
+
rows.select { |name| RefinedDispatch.in_effect?([name], list) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# ADR-46 — a refined class appearing or vanishing (a new subclass declaration, a module turning up where a
|
|
111
|
+
# spelling named nothing), or an edit to a file declaring one (`class Kid` becoming `class Kid < Base`),
|
|
112
|
+
# moves the answer.
|
|
113
|
+
def record_targets(scope, raw)
|
|
114
|
+
return unless Analysis::DependencyRecorder.active?
|
|
115
|
+
|
|
116
|
+
raw.each_key do |refined|
|
|
117
|
+
next if refined == RefinedDispatch.wildcard
|
|
118
|
+
|
|
119
|
+
Analysis::DependencyRecorder.read_last_segment(:class, refined)
|
|
120
|
+
Scope::ResolutionChain.record_class(scope, refined)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# A refined class the receiver's levels do not show, but which a value typed as the receiver's class may
|
|
125
|
+
# still reach (critique F4): a project subclass of the class, or one whose own level mixes in a refined
|
|
126
|
+
# module (a module already on the class's levels is prepended again by `prepend`); a class RBS declares as
|
|
127
|
+
# a subclass; a module not on the levels, which `obj.extend` or a subclass's mixin can put ahead of the
|
|
128
|
+
# definer; and a module on the levels that some code prepends to an object's singleton class.
|
|
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)
|
|
135
|
+
|
|
136
|
+
placed_from = kind == :singleton ? "Module" : class_name
|
|
137
|
+
on_levels = level_names(levels)
|
|
138
|
+
raw.each_key.any? do |refined|
|
|
139
|
+
next object_prepended?(scope, refined) if on_levels.include?(refined)
|
|
140
|
+
next true if module_name?(scope, refined)
|
|
141
|
+
|
|
142
|
+
declared_class?(scope, refined) && !placed_away?(scope, refined, placed_from)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Issue #1820 — can the class `key`, off the receiver's levels, be proven never to sit at or below
|
|
147
|
+
# `class_name` in an object's ancestry? Only when RBS does not declare it a subclass and its whole instance
|
|
148
|
+
# chain is read: no cut, no mixin the tables cannot name, no superclass the tables do not know (`class K <
|
|
149
|
+
# <expr>`, `K = Class.new(Parent)`, a superclass RBS does not declare), and `class_name` nowhere on it.
|
|
150
|
+
def placed_away?(scope, key, class_name)
|
|
151
|
+
return false if rbs_subclass?(scope, key, class_name)
|
|
152
|
+
return rbs_away?(scope, key, class_name) unless project_class?(scope, key)
|
|
153
|
+
|
|
154
|
+
chain = Scope::ResolutionChain.for(scope, key, :instance, :methods)
|
|
155
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
156
|
+
return false if chain.truncated? || chain.wildcard_mixin?
|
|
157
|
+
|
|
158
|
+
chain.level_count.times.all? { |index| level_away?(scope, chain, index, class_name) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# A project level is away when it is not `class_name` and its superclass is known; an external superclass
|
|
162
|
+
# level when RBS declares it and its RBS ancestors do not hold `class_name`.
|
|
163
|
+
def level_away?(scope, chain, index, class_name)
|
|
164
|
+
level_class = chain.level_classes[index]
|
|
165
|
+
return level_class != class_name && !unknown_superclass?(scope, level_class) if level_class
|
|
166
|
+
|
|
167
|
+
external = chain.level_entries(index).find(&:external?)
|
|
168
|
+
name = external&.candidates&.find { |candidate| scope.environment&.class_known?(candidate) }
|
|
169
|
+
!name.nil? && rbs_away?(scope, name, class_name)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def rbs_away?(scope, name, class_name)
|
|
173
|
+
loader = scope.environment&.rbs_loader
|
|
174
|
+
ancestors = loader ? loader.ancestor_names_for(name) : EMPTY
|
|
175
|
+
!ancestors.empty? && !ancestors.include?(class_name)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def project_class?(scope, name)
|
|
179
|
+
scope.discovered_classes.key?(name) || !(scope.discovered_class_sources[name] || EMPTY).empty? ||
|
|
180
|
+
scope.known_user_class?(name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# A project class whose superclass the tables do not know: `class K < <expr>` (the superclass table records
|
|
184
|
+
# `nil`) or `K = Class.new(Parent)` (`ENVELOPE_META_SUPERCLASS_MARK`).
|
|
185
|
+
def unknown_superclass?(scope, name)
|
|
186
|
+
superclasses = scope.discovered_superclasses
|
|
187
|
+
return true if superclasses.key?(name) && superclasses[name].nil?
|
|
188
|
+
|
|
189
|
+
scope.discovered_parameter_envelopes[name]&.key?(Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK) ||
|
|
190
|
+
false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# A module some code `prepend`s to an object's singleton class (or `extend`s an object with in a method
|
|
194
|
+
# body), which puts it ahead of the object's class even where the class already includes it.
|
|
195
|
+
def object_prepended?(scope, name)
|
|
196
|
+
scope.discovered_parameter_envelopes[name]&.key?(Scope::DiscoveryIndex::ENVELOPE_OBJECT_EXTENDED_MARK) ||
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Is some in-effect row under a class no table declares by any spelling of it (the keys sharing its last
|
|
201
|
+
# segment that the same refining module refines)? Ruby resolved that `refine` argument to some class, through
|
|
202
|
+
# a lookup the table did not follow (the refining module's own ancestors: `module R; include Foo;
|
|
203
|
+
# refine(Bar)` refines `Foo::Bar`) or to a class outside the analysed paths (a vendored `VendorStr <
|
|
204
|
+
# String`), and Rigor cannot place it in the receiver's hierarchy.
|
|
205
|
+
def unplaced_target?(scope, raw)
|
|
206
|
+
raw.any? do |refined, modules|
|
|
207
|
+
segment = refined.split("::").last
|
|
208
|
+
modules.any? do |module_name|
|
|
209
|
+
raw.none? do |other, others|
|
|
210
|
+
others.include?(module_name) && other.split("::").last == segment && declared_class?(scope, other)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def declared_class?(scope, name)
|
|
217
|
+
scope.discovered_classes.key?(name) || !(scope.discovered_class_sources[name] || EMPTY).empty? ||
|
|
218
|
+
scope.known_user_class?(name) || scope.environment&.class_known?(name) || false
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def level_names(levels)
|
|
222
|
+
names = Set.new
|
|
223
|
+
levels.each do |level_class, entries, _mixed, every|
|
|
224
|
+
names << level_class if level_class
|
|
225
|
+
(every || entries).each { |entry| entry.is_a?(Array) ? names.merge(entry) : names << entry }
|
|
226
|
+
end
|
|
227
|
+
names
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def module_name?(scope, name)
|
|
231
|
+
bucket = scope.discovered_parameter_envelopes[name]
|
|
232
|
+
return true if bucket&.key?(Scope::DiscoveryIndex::ENVELOPE_MODULE_MARK)
|
|
233
|
+
|
|
234
|
+
scope.environment&.rbs_module?(name) || false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def rbs_subclass?(scope, refined, class_name)
|
|
238
|
+
environment = scope.environment
|
|
239
|
+
!environment.nil? && environment.class_known?(refined) &&
|
|
240
|
+
environment.class_ordering(refined, class_name) == :subclass
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Does a project subclass of `class_name` (the `discovered_superclasses` walk `SourceArity` makes, its
|
|
244
|
+
# parents resolved to any class the project or RBS knows, so a subclass of a core class counts) carry a
|
|
245
|
+
# refined entry on its own level, or a level the walk cannot read?
|
|
246
|
+
#
|
|
247
|
+
# A subclass level the walk cannot read (a truncated chain, or a mixin the tables cannot name) hides only
|
|
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)
|
|
253
|
+
each_subclass(scope, class_name) do |subclass|
|
|
254
|
+
chain = Scope::ResolutionChain.for(scope, subclass, kind, :methods)
|
|
255
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
256
|
+
if chain.truncated? || chain.wildcard_mixin? || chain.level_count.zero? ||
|
|
257
|
+
(kind == :singleton && singleton_level_unreadable?(scope, subclass, chain))
|
|
258
|
+
return true if raw.each_key.any? { |refined| module_name?(scope, refined) }
|
|
259
|
+
|
|
260
|
+
next
|
|
261
|
+
end
|
|
262
|
+
return true if chain.level_entries(0).any? { |entry| refined_entry?(entry, raw) }
|
|
263
|
+
end
|
|
264
|
+
false
|
|
265
|
+
end
|
|
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
|
+
|
|
274
|
+
def refined_entry?(entry, raw)
|
|
275
|
+
return entry.candidates.any? { |candidate| raw.include?(candidate) } if entry.external?
|
|
276
|
+
return false if entry.side == :singleton
|
|
277
|
+
|
|
278
|
+
raw.include?(entry.name)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# ADR-46 — every class whose subclasses are enumerated records `subclass_of:<last segment>`, which a class
|
|
282
|
+
# declaration with that superclass appearing or vanishing fires (`IncrementalSession#negative_affected`).
|
|
283
|
+
# A class whose superclass the tables do not know may be a subclass of any class, so it joins the walk too
|
|
284
|
+
# (`subclass_of:*`, which any class declaration appearing or vanishing fires).
|
|
285
|
+
def each_subclass(scope, class_name)
|
|
286
|
+
children = children_by_parent(scope)
|
|
287
|
+
record_subclasses_read(class_name)
|
|
288
|
+
queue = children.fetch(class_name.to_s, []) + unknown_superclass_classes(scope, class_name)
|
|
289
|
+
seen = {}
|
|
290
|
+
until queue.empty?
|
|
291
|
+
subclass = queue.shift
|
|
292
|
+
next if seen[subclass]
|
|
293
|
+
|
|
294
|
+
seen[subclass] = true
|
|
295
|
+
record_subclasses_read(subclass)
|
|
296
|
+
yield subclass
|
|
297
|
+
queue.concat(children.fetch(subclass, []))
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def unknown_superclass_classes(scope, class_name)
|
|
302
|
+
Analysis::DependencyRecorder.read_name(:subclass_of, "*") if Analysis::DependencyRecorder.active?
|
|
303
|
+
mark = Scope::DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK
|
|
304
|
+
computed = scope.discovered_superclasses.filter_map { |child, parent| child if parent.nil? }
|
|
305
|
+
meta = scope.discovered_parameter_envelopes.filter_map { |name, bucket| name if bucket.key?(mark) }
|
|
306
|
+
(computed + meta).uniq - [class_name.to_s]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def record_subclasses_read(class_name)
|
|
310
|
+
return unless Analysis::DependencyRecorder.active?
|
|
311
|
+
|
|
312
|
+
Analysis::DependencyRecorder.read_last_segment(:subclass_of, class_name.to_s)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def children_by_parent(scope)
|
|
316
|
+
scope.discovered_superclasses.each_with_object({}) do |(child, raw_parent), children|
|
|
317
|
+
parent = scope.ancestor_name_candidates(child, raw_parent).find { |name| known_class?(scope, name) }
|
|
318
|
+
(children[parent] ||= []) << child if parent
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def known_class?(scope, name)
|
|
323
|
+
scope.known_user_class?(name) || scope.discovered_parameter_envelopes.key?(name) ||
|
|
324
|
+
scope.environment&.class_known?(name) || false
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Walks the levels from the most derived: true when the first level or entry that a raw target names or
|
|
328
|
+
# that defines `method_name` defines it, and that definer is proven. A refined level or entry (an entry no
|
|
329
|
+
# table names by any of its candidate names), a `mixed` level (a project mixin RBS does not place, which
|
|
330
|
+
# may be prepended or refined), an unproven definer, and the end of the walk all answer false. An entry no
|
|
331
|
+
# table names is passed over otherwise: it cannot be proven a definer, and a definer the walk misses there
|
|
332
|
+
# sits ahead of every refinement the walk then reaches.
|
|
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)
|
|
337
|
+
levels.each do |level_class, entries, mixed, every|
|
|
338
|
+
return false if mixed || (level_class && raw.include?(level_class))
|
|
339
|
+
|
|
340
|
+
row = every || entries
|
|
341
|
+
return false if level_class.is_a?(SingletonClass) && singleton_level_refined?(row, raw)
|
|
342
|
+
|
|
343
|
+
row.each do |entry|
|
|
344
|
+
case entry_answer(scope, entry, level_class, method_name, raw)
|
|
345
|
+
when :refined then return false
|
|
346
|
+
when :defines then return proven_definer?(scope, receiver, entry, method_name)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
false
|
|
351
|
+
end
|
|
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
|
+
|
|
361
|
+
# `:refined`, `:defines`, or nil to move on. The level's own class was asked before its entries.
|
|
362
|
+
def entry_answer(scope, entry, level_class, method_name, raw)
|
|
363
|
+
return entry.any? { |candidate| raw.include?(candidate) } ? :refined : nil if entry.is_a?(Array)
|
|
364
|
+
return :refined if entry != level_class && raw.include?(entry)
|
|
365
|
+
|
|
366
|
+
:defines if RefinedDispatch.defines?(scope, entry, method_name)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Is Ruby known to find `method_name` on `entry`, the first definer the walk reached for an instance of
|
|
370
|
+
# `class_name`? Neither proof stands on a class that rewrites its method table in a way no literal names
|
|
371
|
+
# (`ENVELOPE_DYNAMIC_MARK`), nor for a name the project removes from it with a literal `undef`,
|
|
372
|
+
# `remove_method` or `undef_method` anywhere (`ENVELOPE_REMOVED_KIND`), whatever defined it.
|
|
373
|
+
#
|
|
374
|
+
# - A project `def`, `attr_*` or `define_method` (ADR-119 A7): `DefinerResolution` must answer `Known` with
|
|
375
|
+
# `entry` as the owner, on the receiver's chain, so a definer that may not have run (a `def` under `if`,
|
|
376
|
+
# which Ruby skips, leaving the ancestor's refinement to answer) or that an unplaced mixin or fork may
|
|
377
|
+
# precede is not proof. A `def` whose envelope something made opaque (`remove_method`, `undef`, an
|
|
378
|
+
# alias, a wrapping macro) is not proof either.
|
|
379
|
+
# - A core method the offline CRuby catalogue lists on `entry` itself ({Builtins::CRubyDefiners}), which the
|
|
380
|
+
# project has not removed or rewritten there (A9: no opaque envelope for the name, which `remove_method`
|
|
381
|
+
# and `undef` record). Core RBS is not proof: it redeclares some inherited methods on a subclass
|
|
382
|
+
# (`Integer#quo`, `File#to_path`, owned by `Numeric` and `IO`) and declares some CRuby no longer defines
|
|
383
|
+
# (`Process::Status#&`).
|
|
384
|
+
#
|
|
385
|
+
# ADR-46 — the envelope bucket records the files declaring `entry`, and the class edge a new file reopening
|
|
386
|
+
# it (to `remove_method` the name, say) fires.
|
|
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)
|
|
396
|
+
return false if bucket.key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK)
|
|
397
|
+
return false if bucket.key?([Scope::DiscoveryIndex::ENVELOPE_REMOVED_KIND, method_name.to_sym])
|
|
398
|
+
|
|
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
|
|
402
|
+
|
|
403
|
+
if side == :singleton
|
|
404
|
+
Builtins::CRubyDefiners.singleton_defines?(owner, method_name)
|
|
405
|
+
else
|
|
406
|
+
Builtins::CRubyDefiners.defines?(owner, method_name)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# A `def` counts while its envelope is its own (an alias or a wrapping macro makes it opaque); an `attr_*` or
|
|
411
|
+
# `define_method` always records an opaque one, so for those the existence table and the certainty read are
|
|
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)
|
|
424
|
+
|
|
425
|
+
:other if scope.discovered_method?(owner, method_name, :instance)
|
|
426
|
+
end
|
|
427
|
+
|
|
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,
|
|
440
|
+
&definer_answer(scope, method_name))
|
|
441
|
+
in DefinerResolution::Known(answer: _answer, owner: found) then found == owner
|
|
442
|
+
in DefinerResolution::UNKNOWN then false
|
|
443
|
+
in DefinerResolution::ABSENT then false # rubocop:disable Lint/DuplicateBranch
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
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.
|
|
452
|
+
def definer_answer(scope, method_name)
|
|
453
|
+
lambda do |chain, from|
|
|
454
|
+
index = from - 1
|
|
455
|
+
chain.search(scope, from) do |entry|
|
|
456
|
+
index += 1
|
|
457
|
+
next if entry.external? || !entry_defines?(scope, entry, method_name)
|
|
458
|
+
|
|
459
|
+
DefinerHit.new(entry.name, entry.name, index, entry.side)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
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
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|