rigortype 0.4.1 → 0.4.2
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 +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/18-removing-dead-code.md +32 -2
- 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 +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +289 -57
- 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/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 +3 -1
- data/lib/rigor/analysis/incremental_session.rb +14 -1
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +87 -5
- data/lib/rigor/analysis/runner/pool_coordinator.rb +50 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +34 -3
- data/lib/rigor/analysis/worker_session.rb +7 -4
- data/lib/rigor/cache/incremental_snapshot.rb +22 -0
- 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 +2 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +13 -7
- data/lib/rigor/cli.rb +10 -2
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +46 -2
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +283 -14
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment.rb +21 -3
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +215 -16
- 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 +282 -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/method_dispatcher/alias_strict_nominals.rb +36 -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/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +259 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- 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_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +687 -60
- data/lib/rigor/inference/statement_evaluator.rb +71 -20
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -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 +29 -6
- data/lib/rigor/protection/mutation_cache.rb +1 -1
- 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 +189 -10
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +55 -5
- 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-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 +27 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- metadata +66 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/constant_path"
|
|
6
|
+
require_relative "refine_census"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
# ADR-121 WD7 — the one function that answers what `self` a `refine` (or a `:refine` literal) at a program point
|
|
11
|
+
# runs on. The in-effect walk ({InEffectRefinements}) works a {Context} out with these functions for every body,
|
|
12
|
+
# method and block it enters and records it for each `refine`-shaped node; the census (`ScopeIndexer`'s module-body
|
|
13
|
+
# walk and its scan of method and refine bodies) reads that record back ({InEffectRefinements#refine_context}), so
|
|
14
|
+
# the table and the activations charge the same module.
|
|
15
|
+
module RefineSelf
|
|
16
|
+
# `here`, for a `refine` call directly at that point; `literal`, for a `:refine` literal there (which names
|
|
17
|
+
# `Module#refine` for an alias or a `send` whose method runs where a method defined there does: in a module's
|
|
18
|
+
# `class << self` body that is the module, though a `refine` call there runs on the singleton class); and
|
|
19
|
+
# `instance` / `singleton`, for a method defined there on the instance / singleton side. Each is a module name,
|
|
20
|
+
# the names a constant's spelling can denote (an Array, innermost first, for a module only another file may
|
|
21
|
+
# declare), the wildcard (a module the walk cannot name), {CLASS}, {MAIN}, {ANONYMOUS} or {REFINEMENT}.
|
|
22
|
+
# `deferred` is true inside a method body (a `def`, a `define_method` block), where a `refine` runs only once the
|
|
23
|
+
# method is called.
|
|
24
|
+
# `own_method` is true inside a singleton `def` of a module the walk names ({.def_body}), and in the blocks that
|
|
25
|
+
# keep its `self`: `here` is then that module itself, so a `refine X do … end` written there refines X for it
|
|
26
|
+
# once the method is called, and the census records its rows where it would otherwise charge a wildcard.
|
|
27
|
+
# `remote` is true in the body of an eval on a constant (`M.module_eval { … }`, and a `class << self` there),
|
|
28
|
+
# whose file need not declare the module it runs on: a singleton method defined there is not `own_method`.
|
|
29
|
+
Context = Data.define(:here, :literal, :instance, :singleton, :deferred, :own_method, :remote)
|
|
30
|
+
# A plain class, or an instance of one: `Class` undefines `refine`, so a `refine` there is the class's own
|
|
31
|
+
# method (#1689) and a `:refine` literal is data.
|
|
32
|
+
CLASS = :class
|
|
33
|
+
# The top level's `main`, which has no `refine`.
|
|
34
|
+
MAIN = :main
|
|
35
|
+
# A module a bare `Module.new { … }` creates, which the census names by its position.
|
|
36
|
+
ANONYMOUS = :anonymous
|
|
37
|
+
# The refinement a `refine` block creates, where a method defined on its singleton side (`def self.x`, a
|
|
38
|
+
# `define_singleton_method` block, a `class << self` body) runs. `using` rejects a refinement with `TypeError`,
|
|
39
|
+
# so a `refine` there reaches no `using` span and the census charges it to nothing.
|
|
40
|
+
REFINEMENT = :refinement
|
|
41
|
+
|
|
42
|
+
# Core iteration methods: the Enumerable protocol never rebinds `self` in their block.
|
|
43
|
+
CORE_ITERATORS = Set[
|
|
44
|
+
:each, :each_with_index, :each_with_object, :each_pair, :each_key, :each_value, :map, :flat_map, :times,
|
|
45
|
+
:select, :reject
|
|
46
|
+
].freeze
|
|
47
|
+
LITERALS = [Prism::ArrayNode, Prism::HashNode, Prism::RangeNode, Prism::IntegerNode, Prism::FloatNode,
|
|
48
|
+
Prism::StringNode, Prism::InterpolatedStringNode, Prism::SymbolNode].freeze
|
|
49
|
+
NAMED_RECEIVERS = [Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::LocalVariableReadNode,
|
|
50
|
+
Prism::InstanceVariableReadNode].freeze
|
|
51
|
+
METHOD_BLOCK_CALLS = Set[:define_method, :define_singleton_method].freeze
|
|
52
|
+
INSTANCE_EVAL_CALLS = Set[:instance_eval, :instance_exec].freeze
|
|
53
|
+
# Bindings of a local or an instance variable that are not a plain write: each may hold any value.
|
|
54
|
+
VARIABLE_BINDINGS = [
|
|
55
|
+
Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
|
|
56
|
+
Prism::LocalVariableTargetNode, Prism::RequiredParameterNode, Prism::OptionalParameterNode,
|
|
57
|
+
Prism::RestParameterNode, Prism::KeywordRestParameterNode, Prism::RequiredKeywordParameterNode,
|
|
58
|
+
Prism::OptionalKeywordParameterNode, Prism::BlockParameterNode, Prism::BlockLocalVariableNode,
|
|
59
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
60
|
+
Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableTargetNode
|
|
61
|
+
].freeze
|
|
62
|
+
# Bindings of a constant other than a bare `K = …`, matched by the constant's last segment.
|
|
63
|
+
CONSTANT_BINDINGS = [
|
|
64
|
+
Prism::ConstantOrWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantAndWriteNode,
|
|
65
|
+
Prism::ConstantTargetNode, Prism::ConstantPathTargetNode
|
|
66
|
+
].freeze
|
|
67
|
+
CONSTANT_PATH_BINDINGS = [
|
|
68
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
|
69
|
+
Prism::ConstantPathAndWriteNode
|
|
70
|
+
].freeze
|
|
71
|
+
|
|
72
|
+
private_constant :CORE_ITERATORS, :LITERALS, :NAMED_RECEIVERS, :METHOD_BLOCK_CALLS, :INSTANCE_EVAL_CALLS,
|
|
73
|
+
:VARIABLE_BINDINGS, :CONSTANT_BINDINGS, :CONSTANT_PATH_BINDINGS
|
|
74
|
+
|
|
75
|
+
module_function
|
|
76
|
+
|
|
77
|
+
def wildcard = RefineCensus.wildcard
|
|
78
|
+
|
|
79
|
+
# The top level: `main`; a method defined there is `Object`'s, which any module may run.
|
|
80
|
+
def top = context(MAIN, wildcard, MAIN)
|
|
81
|
+
|
|
82
|
+
# A body whose `self` the walk cannot name.
|
|
83
|
+
def unknown = context(wildcard, wildcard, wildcard)
|
|
84
|
+
|
|
85
|
+
def context(here, instance, singleton, deferred: false, literal: here, own_method: false, remote: false)
|
|
86
|
+
Context.new(here: here, literal: literal, instance: instance, singleton: singleton, deferred: deferred,
|
|
87
|
+
own_method: own_method, remote: remote)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# A `module` body (or a `Module.new` block) of the module `name` (nil when the walk cannot name it, or
|
|
91
|
+
# {ANONYMOUS}): its instance methods run on whatever includes or extends it, its singleton methods on it.
|
|
92
|
+
def module_body(name)
|
|
93
|
+
return unknown if name.nil?
|
|
94
|
+
|
|
95
|
+
context(name, wildcard, name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A `class` body (or a `Class.new` / `Struct.new` / `Data.define` block): `self` is a class; a `Module`
|
|
99
|
+
# subclass's instances are modules the walk cannot name.
|
|
100
|
+
def class_body(module_subclass) = context(CLASS, module_subclass ? wildcard : CLASS, CLASS)
|
|
101
|
+
|
|
102
|
+
# A `class << expr` body: `self` is a singleton class, so a `refine` call there is its own method; for
|
|
103
|
+
# `class << self` its methods (and a `:refine` literal an alias there names) run on the enclosing `self`, for any
|
|
104
|
+
# other expression on an object the walk cannot name.
|
|
105
|
+
def singleton_class_body(parent, of_self)
|
|
106
|
+
runs_on = of_self ? parent.singleton : wildcard
|
|
107
|
+
context(CLASS, runs_on, CLASS, literal: runs_on, remote: of_self && parent.remote)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A method body defined in `parent` on the instance or singleton side: `self` is what that side runs on, and a
|
|
111
|
+
# method defined inside it is defined on something the walk cannot name.
|
|
112
|
+
def method_body(parent, singleton)
|
|
113
|
+
context(singleton ? parent.singleton : parent.instance, wildcard, wildcard, deferred: true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A `def` body in `parent`: on no receiver an instance method, on `self` (or on the constant that is the last
|
|
117
|
+
# segment of the module `parent` names, `def M.x` inside `module M`) a singleton one, and on any other object a
|
|
118
|
+
# method defined on something the walk cannot name. A singleton method of a module the walk names is the
|
|
119
|
+
# module's own (`own_method`): `def self.x`, or a `def` written in that module's `class << self`
|
|
120
|
+
# (`parent.instance` is the module there). `self` is then the module itself, whichever way the method is called,
|
|
121
|
+
# so a `refine` in it refines for that module; for an instance `def`, whose `parent.instance` is the wildcard,
|
|
122
|
+
# Ruby runs it on whatever module extends the owner. `def M.x` is not marked, as the constant names the module by
|
|
123
|
+
# its last segment only, and neither is a method an eval on a constant defines (`remote`): its file need not
|
|
124
|
+
# declare the module, so the lookup of a refine body, which consults the files that do, would not see it.
|
|
125
|
+
def def_body(parent, receiver)
|
|
126
|
+
return mark_own_method(method_body(parent, false), parent) if receiver.nil?
|
|
127
|
+
return unknown.with(deferred: true) unless lexical_self_receiver?(receiver, parent)
|
|
128
|
+
|
|
129
|
+
context = method_body(parent, true)
|
|
130
|
+
receiver.is_a?(Prism::SelfNode) ? mark_own_method(context, parent) : context
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def mark_own_method(context, parent)
|
|
134
|
+
named_module?(context.here) && !parent.remote ? context.with(own_method: true) : context
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def lexical_self_receiver?(receiver, context)
|
|
138
|
+
return true if receiver.is_a?(Prism::SelfNode)
|
|
139
|
+
|
|
140
|
+
here = context.here
|
|
141
|
+
receiver.is_a?(Prism::ConstantReadNode) && here.is_a?(String) && here.split("::").last == receiver.name.to_s
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# A module name the walk can charge a `refine` to: not the wildcard, {CLASS}, {MAIN} or {ANONYMOUS}, and not the
|
|
145
|
+
# several names a constant's spelling can denote.
|
|
146
|
+
def named_module?(here) = here.is_a?(String) && here != wildcard
|
|
147
|
+
|
|
148
|
+
# The context inside the block of `node` (a block-carrying call, or a lambda) written in `parent`. `eval_names`
|
|
149
|
+
# are the modules a `*_eval` / `*_exec` call's constant receiver may name ({.eval_receiver_names}), or nil; the
|
|
150
|
+
# given block answers whether a constant, local or instance variable receiver holds a literal ({.keeps_self?}).
|
|
151
|
+
# A block keeps `self` only where its call is known to: a core iteration method on a receiver whose value is a
|
|
152
|
+
# literal in this file, and a `refine` call (its body is the refinement, handled by the reader). A `*_eval` /
|
|
153
|
+
# `*_exec` block runs on its receiver, a `define_method` / `define_singleton_method` block is a method body, and
|
|
154
|
+
# a `Module.new` / `Class.new` block is a module / class body. Any other block (a DSL may `module_eval` it,
|
|
155
|
+
# `tap`, `loop`, a project method, a core method's name on any other receiver), a lambda, and every block at the
|
|
156
|
+
# top level may run under any `self`.
|
|
157
|
+
def block(parent, node, eval_names, &)
|
|
158
|
+
return unknown if node.is_a?(Prism::LambdaNode)
|
|
159
|
+
return refinement_body(parent) if node.name == :refine
|
|
160
|
+
return eval_body(parent, node, eval_names) if RefineCensus::SELF_EVAL_CALLS.include?(node.name)
|
|
161
|
+
return meta_new_body(node) if ScopeIndexer.meta_new_constant_rvalue?(node)
|
|
162
|
+
return unknown if parent.here == MAIN
|
|
163
|
+
return method_body(parent, node.name == :define_singleton_method) if method_block?(node)
|
|
164
|
+
return parent if keeps_self?(node, &)
|
|
165
|
+
|
|
166
|
+
unknown
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def method_block?(node) = METHOD_BLOCK_CALLS.include?(node.name) && RefineCensus.self_call?(node)
|
|
170
|
+
|
|
171
|
+
# The block of a `refine` call: a `refine` written in it (a nested one) is charged where the call is, but `self`
|
|
172
|
+
# is the refinement, so a method defined in it is defined on something the walk cannot name: a singleton method
|
|
173
|
+
# on the refinement itself ({REFINEMENT}, whichever the method the `refine` call sits in, as long as the module it
|
|
174
|
+
# refines for is one), an instance method on an instance of the refined class (the wildcard), not on the module
|
|
175
|
+
# around.
|
|
176
|
+
def refinement_body(parent)
|
|
177
|
+
singleton = module_here?(parent.here) ? REFINEMENT : parent.singleton
|
|
178
|
+
parent.with(instance: refinement_side(parent.instance), singleton: singleton, own_method: false)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def refinement_side(side) = side.is_a?(String) || side.is_a?(Array) ? wildcard : side
|
|
182
|
+
|
|
183
|
+
# Is `here` a module a `refine` there refines for (so its block is a refinement's body), or the refinement itself?
|
|
184
|
+
def module_here?(here) = named_module?(here) || here.is_a?(Array) || here == ANONYMOUS || here == REFINEMENT
|
|
185
|
+
|
|
186
|
+
def meta_new_body(node)
|
|
187
|
+
ScopeIndexer.module_new_call?(node) ? module_body(ANONYMOUS) : class_body(false)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# A core iteration method on a receiver whose value is a literal in this file: a literal, or a constant, local
|
|
191
|
+
# or instance variable the block answers true for ({.literal_bindings}). Any other name, even on a literal, and
|
|
192
|
+
# any other receiver may be a project method that runs the block under another `self`: on Ruby 4.0.5
|
|
193
|
+
# `Config.each { refine(String) { … } }` with `def self.each(&blk) = Ext.module_eval(&blk)` refines for `Ext`,
|
|
194
|
+
# and so does `[1].configure { … }` with `class Array; def configure(&blk) = Ext.module_eval(&blk); end`.
|
|
195
|
+
def keeps_self?(node)
|
|
196
|
+
return false unless CORE_ITERATORS.include?(node.name)
|
|
197
|
+
|
|
198
|
+
receiver = node.receiver
|
|
199
|
+
return true if literal?(receiver)
|
|
200
|
+
|
|
201
|
+
NAMED_RECEIVERS.any? { |klass| receiver.is_a?(klass) } && block_given? && yield(receiver)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def literal?(node) = LITERALS.any? { |klass| node.is_a?(klass) }
|
|
205
|
+
|
|
206
|
+
# A literal, or a literal's `.freeze`.
|
|
207
|
+
def literal_value?(node)
|
|
208
|
+
return true if literal?(node)
|
|
209
|
+
|
|
210
|
+
node.is_a?(Prism::CallNode) && node.name == :freeze && node.arguments.nil? && node.block.nil? &&
|
|
211
|
+
literal?(node.receiver)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The constants (by qualified name), locals and instance variables (by name) that `root` binds, every binding a
|
|
215
|
+
# plain write of a literal ({.literal_value?}). A name any other binding gives a value (a parameter, a
|
|
216
|
+
# multiple assignment, `||=`, `+=`, a `const_set`, a path write `A::K = …`) is not one, wherever in the file
|
|
217
|
+
# it sits.
|
|
218
|
+
def literal_bindings(root)
|
|
219
|
+
literal = Set.new
|
|
220
|
+
other = Set.new
|
|
221
|
+
spoiled = ScopeIndexer.loose_constant_segments(root).dup
|
|
222
|
+
collect_bindings(root, [], literal, other, spoiled)
|
|
223
|
+
(literal - other).reject { |key| key.is_a?(String) && spoiled.include?(key.split("::").last) }.to_set.freeze
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def collect_bindings(node, prefix, literal, other, spoiled)
|
|
227
|
+
case node
|
|
228
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode
|
|
229
|
+
return collect_declaration_bindings(node, prefix, literal, other, spoiled)
|
|
230
|
+
when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
|
|
231
|
+
bind(node.value, node.name, literal, other)
|
|
232
|
+
when *VARIABLE_BINDINGS then other << node.name if node.name
|
|
233
|
+
else collect_constant_binding(node, prefix, literal, other, spoiled)
|
|
234
|
+
end
|
|
235
|
+
node.rigor_each_child { |child| collect_bindings(child, prefix, literal, other, spoiled) }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def collect_constant_binding(node, prefix, literal, other, spoiled)
|
|
239
|
+
case node
|
|
240
|
+
when Prism::ConstantWriteNode then bind(node.value, (prefix + [node.name.to_s]).join("::"), literal, other)
|
|
241
|
+
when *CONSTANT_BINDINGS then spoiled << node.name.to_s
|
|
242
|
+
when *CONSTANT_PATH_BINDINGS then spoiled << node.target.name.to_s
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# A `class` / `module` body's constants are its own; a `class << expr` body's are the singleton class's, which
|
|
247
|
+
# no read outside it names.
|
|
248
|
+
def collect_declaration_bindings(node, prefix, literal, other, spoiled)
|
|
249
|
+
if node.is_a?(Prism::SingletonClassNode)
|
|
250
|
+
collect_bindings(node.expression, prefix, literal, other, spoiled)
|
|
251
|
+
inner = prefix + ["<singleton>"]
|
|
252
|
+
else
|
|
253
|
+
inner = Source::ConstantPath.declaration_prefix(prefix, node.constant_path) || prefix
|
|
254
|
+
superclass = node.is_a?(Prism::ClassNode) && node.superclass
|
|
255
|
+
collect_bindings(superclass, prefix, literal, other, spoiled) if superclass
|
|
256
|
+
end
|
|
257
|
+
collect_bindings(node.body, inner, literal, other, spoiled) if node.body
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def bind(value, key, literal, other) = (literal_value?(value) ? literal : other) << key
|
|
261
|
+
|
|
262
|
+
# The key {.literal_bindings} holds for a receiver read in `prefix`: a constant's innermost lexical candidate (the
|
|
263
|
+
# one Ruby's lookup finds once this file's write defines it), a local's or an instance variable's name.
|
|
264
|
+
def binding_key(receiver, prefix)
|
|
265
|
+
case receiver
|
|
266
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
267
|
+
ScopeIndexer.constant_receiver_candidates(receiver, prefix).first
|
|
268
|
+
else receiver.name
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# A `*_eval` / `*_exec` block: on `self` (or no receiver) it keeps `self`, on a constant it runs on the module the
|
|
273
|
+
# constant names (any name its spelling can denote), on anything else on a module the walk cannot name. A `def` in
|
|
274
|
+
# an `instance_eval` binds on the receiver's singleton, so its instance side is the receiver itself.
|
|
275
|
+
def eval_body(parent, node, eval_names)
|
|
276
|
+
here = RefineCensus.self_call?(node) ? parent.here : eval_names
|
|
277
|
+
here = here.first if here.is_a?(Array) && here.size == 1
|
|
278
|
+
return context(CLASS, CLASS, CLASS, deferred: parent.deferred) if here == CLASS
|
|
279
|
+
return unknown unless here.is_a?(String) || here.is_a?(Array) || here == ANONYMOUS || here == REFINEMENT
|
|
280
|
+
return unknown if here == wildcard
|
|
281
|
+
|
|
282
|
+
instance = INSTANCE_EVAL_CALLS.include?(node.name) ? here : wildcard
|
|
283
|
+
remote = parent.remote || !RefineCensus.self_call?(node)
|
|
284
|
+
context(here, instance, here, deferred: parent.deferred, remote: remote)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# The modules a `*_eval` / `*_exec` call `node` written in `context` at the lexical `prefix` may run its block
|
|
288
|
+
# on, when its receiver is a constant: every name the constant's spelling can denote, innermost first, as another
|
|
289
|
+
# file may declare any of them (`M.module_eval { … }` in `module L` runs on `L::M` where some file declares it);
|
|
290
|
+
# a `self::`-anchored one as the method walk resolves it (`ScopeIndexer.eval_receiver_prefix`). Else nil.
|
|
291
|
+
def eval_receiver_names(node, prefix, context)
|
|
292
|
+
return nil unless RefineCensus::SELF_EVAL_CALLS.include?(node.name) && !RefineCensus.self_call?(node)
|
|
293
|
+
|
|
294
|
+
receiver = node.receiver
|
|
295
|
+
if Source::ConstantPath.qualified_name_or_nil(receiver)
|
|
296
|
+
return ScopeIndexer.constant_receiver_candidates(receiver, prefix)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
here = context.here
|
|
300
|
+
self_prefix = here.is_a?(String) && here != wildcard ? here.split("::") : []
|
|
301
|
+
resolved = ScopeIndexer.eval_receiver_prefix(node, self_prefix, prefix, unnameable_self: self_prefix.empty?)
|
|
302
|
+
[resolved.join("::")] unless resolved.nil? || resolved.empty?
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# The modules a context's `refine` call (or, with `literal`, its `:refine` literal) may refine for, as names a
|
|
306
|
+
# table row can hold: the module, each name a constant's spelling can denote, else the wildcard.
|
|
307
|
+
def charged_modules(context, literal: false)
|
|
308
|
+
here = literal ? context.literal : context.here
|
|
309
|
+
case here
|
|
310
|
+
when String then [here]
|
|
311
|
+
when Array then here
|
|
312
|
+
else [wildcard]
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|