rigortype 0.4.3 → 0.4.5
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 +5 -5
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +104 -15
- data/docs/manual/02-cli-reference.md +43 -12
- data/docs/manual/03-configuration.md +20 -7
- data/docs/manual/04-diagnostics.md +56 -3
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +3 -2
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +40 -4
- data/docs/manual/18-removing-dead-code.md +23 -2
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-rspec.md +101 -1
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
- data/lib/rigor/analysis/check_rules.rb +274 -51
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +61 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +270 -28
- data/lib/rigor/analysis/path_expansion.rb +3 -1
- data/lib/rigor/analysis/reachability/graph.rb +111 -14
- data/lib/rigor/analysis/reachability/scan.rb +305 -23
- data/lib/rigor/analysis/rule_catalog.rb +22 -4
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +102 -72
- data/lib/rigor/analysis/severity_stamp.rb +45 -1
- data/lib/rigor/analysis/worker_session.rb +69 -2
- data/lib/rigor/cache/descriptor.rb +79 -12
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +39 -5
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +32 -11
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +163 -22
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +29 -3
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +67 -11
- data/lib/rigor/inference/block_call_timing.rb +12 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/captured_locals.rb +67 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/definer_resolution.rb +101 -25
- data/lib/rigor/inference/expression_typer.rb +94 -9
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher.rb +67 -3
- data/lib/rigor/inference/module_function_state.rb +213 -51
- data/lib/rigor/inference/narrowing.rb +235 -75
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +120 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer.rb +1061 -122
- data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +512 -150
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +60 -2
- data/lib/rigor/lens/document.rb +4 -3
- data/lib/rigor/lens/file_reader.rb +452 -73
- data/lib/rigor/lens/members.rb +81 -17
- data/lib/rigor/lens/run.rb +73 -4
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +63 -22
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/protection/discovery_seed.rb +9 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +27 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +56 -8
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +167 -22
- data/lib/rigor/sig_gen/layout_index.rb +40 -1
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/observation_collector.rb +137 -11
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/renderer.rb +13 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +50 -35
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/constant.rb +10 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- 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-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -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/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +4 -3
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +16 -1
|
@@ -9,24 +9,36 @@ module Rigor
|
|
|
9
9
|
# The one home for what a receiverless `module_function` call does to the methods around it
|
|
10
10
|
# ([#1507](https://github.com/rigortype/rigor/issues/1507),
|
|
11
11
|
# [#1197](https://github.com/rigortype/rigor/issues/1197)). Four readers used to compute it in four
|
|
12
|
-
# places. They disagree with each other and, in places, with
|
|
13
|
-
# one reader's answer exactly: this module gives the readings one
|
|
14
|
-
# them agree. A change to one reading lands here, beside the
|
|
15
|
-
#
|
|
12
|
+
# places, and the method-visibility table reads it too. They disagree with each other and, in places, with
|
|
13
|
+
# Ruby. Each entry point below reproduces one reader's answer exactly: this module gives the readings one
|
|
14
|
+
# place to live, and does not yet make them agree. A change to one reading lands here, beside the others.
|
|
15
|
+
#
|
|
16
|
+
# In Ruby a bare `module_function` call turns the body's module_function mode on, and a later bare
|
|
17
|
+
# `public`, `private` or `protected` ({.visibility_reset?}) turns it off again (#1968). A named call
|
|
18
|
+
# (`public :x`, `private def y`) leaves the mode alone, and each `module` body starts with it off. A reset
|
|
19
|
+
# written in the body's own statements, or in a plain `begin` or `ensure` there, runs. One nested anywhere
|
|
20
|
+
# else (control flow, a block, a `rescue` clause, an argument) may not, so the mode after it is uncertain.
|
|
21
|
+
# A reset in a nested `class`, `module`, `class <<` or `def` body, a `*_eval` / `*_exec` block or a
|
|
22
|
+
# class-creating block (`Class.new do`, `Struct.new do`, ...) belongs to another scope and does not count.
|
|
16
23
|
#
|
|
17
24
|
# - The singleton def-node table (`ScopeIndexer#walk_singleton_body`) reads
|
|
18
25
|
# {.each_singleton_sibling} and {.each_singleton_copy}. A bare call covers the later direct
|
|
19
|
-
# statements of its own body
|
|
26
|
+
# statements of its own body, up to a reset; after an uncertain one the copy is possible.
|
|
20
27
|
# - The deferred ranges (`ScopeIndexer#walk_deferred_body`) read {.prescan_deferred} and
|
|
21
|
-
# {.deferred_module_function?}. A bare call covers every later def by offset,
|
|
22
|
-
# control flow or a block counts.
|
|
28
|
+
# {.deferred_module_function?}. A bare call covers every later def by offset, up to a later reset,
|
|
29
|
+
# and a call inside control flow or a block counts. An uncertain reset changes nothing here.
|
|
23
30
|
# - The extends table (`ScopeIndexer#record_extend_call`) reads {.extends_self?}. A bare call covers
|
|
24
|
-
# every instance def of the module, in any order.
|
|
31
|
+
# every instance def of the module, in any order, and is never reset.
|
|
25
32
|
# - sig-gen (`SigGen::Generator#walk_statements`) reads {.each_sig_gen_statement} and
|
|
26
33
|
# {.sig_gen_module_function?}. A bare call covers the later statements of its own statement list and
|
|
27
|
-
# what nests in them.
|
|
34
|
+
# what nests in them, up to a reset; after an uncertain one {.sig_gen_uncertain?} says so.
|
|
35
|
+
# - The method-visibility table (`ScopeIndexer#walk_method_visibilities`) reads {.instance_visibility}. It
|
|
36
|
+
# applies a `module_function` call as it applies a `private` one, bare or named: the call makes the instance
|
|
37
|
+
# copy private (#1569), and a later bare `public`, `private` or `protected` sets the running default as it
|
|
38
|
+
# would anyway, so the reset needs no reading of its own. A call or reset that may not run leaves the slots
|
|
39
|
+
# after it contested, as any visibility call there does.
|
|
28
40
|
#
|
|
29
|
-
#
|
|
41
|
+
# Another reader does not come here yet: `rigor-activerecord`'s `ModelDiscoverer#table_name_decorators`
|
|
30
42
|
# (`plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb`) reads a module body's
|
|
31
43
|
# direct statements for its table-name readers. It also accepts `self.module_function`, and there a bare
|
|
32
44
|
# call covers the instance methods written before it as well as after it, while a named call takes only a
|
|
@@ -47,23 +59,60 @@ module Rigor
|
|
|
47
59
|
node.name == :module_function && node.receiver.nil?
|
|
48
60
|
end
|
|
49
61
|
|
|
62
|
+
# The visibility a `module_function` call gives the instance method of each name it covers, bare or named:
|
|
63
|
+
# Ruby makes the instance copy private and adds a public singleton copy, which the instance-side
|
|
64
|
+
# method-visibility table does not hold (#1569).
|
|
65
|
+
def instance_visibility = :private
|
|
66
|
+
|
|
50
67
|
# The toggle form: a `module_function` call with no arguments.
|
|
51
68
|
def bare?(node)
|
|
52
69
|
node.arguments.nil? || node.arguments.arguments.empty?
|
|
53
70
|
end
|
|
54
71
|
|
|
72
|
+
# A bare `public`, `private` or `protected` call, receiverless or on `self`: Ruby ends the module_function
|
|
73
|
+
# mode there (#1968). A call with arguments sets the named methods' visibility and leaves the mode alone.
|
|
74
|
+
def visibility_reset?(node)
|
|
75
|
+
%i[public private protected].include?(node.name) && bare?(node) &&
|
|
76
|
+
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The mode after `stmt`, a direct statement of a body, for the readers that walk the statements: `mode`
|
|
80
|
+
# is `true`, `false` or `:uncertain` before it. A {.visibility_reset?} the statement runs ends the mode,
|
|
81
|
+
# and one it may not run (`:maybe_off`, below {.prescan_deferred}) makes an on mode uncertain. A
|
|
82
|
+
# `module_function` nested in the statement is not read here, as before. `nested` says the statement
|
|
83
|
+
# itself may not run, so every reset in it may not either.
|
|
84
|
+
def mode_after(mode, stmt, nested: false)
|
|
85
|
+
return mode unless mode
|
|
86
|
+
|
|
87
|
+
events = []
|
|
88
|
+
prescan_deferred(stmt, EMPTY_PREFIX, false, events, [], nested: nested)
|
|
89
|
+
events.sort_by(&:first).each do |_offset, event|
|
|
90
|
+
case event
|
|
91
|
+
when :off then mode = false
|
|
92
|
+
when :maybe_off then mode = :uncertain if mode == true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
mode
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The owner prefix {.mode_after} hands the prescan, which records no row a reset needs.
|
|
99
|
+
EMPTY_PREFIX = [].freeze
|
|
100
|
+
|
|
55
101
|
# The singleton def-node table's reading, over one class-ish body's direct statements in source
|
|
56
102
|
# order. `ScopeIndexer#statements_of` builds that list, folding a body-level `rescue`, `else` or
|
|
57
103
|
# `ensure` clause into it. Yields `stmt, module_function_on, named_call` for every statement except a
|
|
58
104
|
# bare call:
|
|
59
105
|
#
|
|
60
|
-
# - `module_function_on`
|
|
61
|
-
#
|
|
106
|
+
# - `module_function_on` is `true` when a bare call precedes the statement with no reset between them,
|
|
107
|
+
# `:uncertain` when a reset that may not run is between them ({.mode_after}), and `false` otherwise.
|
|
62
108
|
# - `named_call` is true for a named call (`module_function :a` or `module_function def x`), which
|
|
63
109
|
# {.each_singleton_copy} resolves; the caller records nothing else for it.
|
|
64
110
|
#
|
|
65
|
-
# A call nested in control flow or a block is not seen.
|
|
66
|
-
|
|
111
|
+
# A `module_function` call nested in control flow or a block is not seen. `conditional` lists the
|
|
112
|
+
# statements of a body-level `rescue` or `else` clause ({.conditional_clause_statements}), which may not
|
|
113
|
+
# run, so a reset there makes the mode uncertain rather than ending it.
|
|
114
|
+
def each_singleton_sibling(statements, conditional = [])
|
|
115
|
+
conditional = Set.new(conditional).compare_by_identity
|
|
67
116
|
module_function_on = false
|
|
68
117
|
statements.each do |stmt|
|
|
69
118
|
if stmt.is_a?(Prism::CallNode) && call?(stmt)
|
|
@@ -75,30 +124,74 @@ module Rigor
|
|
|
75
124
|
next
|
|
76
125
|
end
|
|
77
126
|
yield stmt, module_function_on, false
|
|
127
|
+
module_function_on = mode_after(module_function_on, stmt, nested: conditional.include?(stmt))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The statements of a body-level `rescue` or `else` clause, which `ScopeIndexer#statements_of` folds into
|
|
132
|
+
# the body's direct statements although they run only on an exception, or only without one. An `ensure`
|
|
133
|
+
# clause runs either way and is not listed.
|
|
134
|
+
def conditional_clause_statements(body)
|
|
135
|
+
return [] unless body.is_a?(Prism::BeginNode)
|
|
136
|
+
|
|
137
|
+
statements = []
|
|
138
|
+
clause = body.rescue_clause
|
|
139
|
+
while clause
|
|
140
|
+
statements.concat(ScopeIndexer.statements_of(clause.statements))
|
|
141
|
+
clause = clause.subsequent
|
|
78
142
|
end
|
|
143
|
+
statements.concat(ScopeIndexer.statements_of(body.else_clause.statements)) if body.else_clause
|
|
144
|
+
statements
|
|
79
145
|
end
|
|
80
146
|
|
|
81
|
-
# The defs a named call copies onto the module's singleton, for the singleton def-node table.
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
147
|
+
# The defs a named call copies onto the module's singleton, for the singleton def-node table. Ruby
|
|
148
|
+
# copies the method in effect at the call (#1550), so a later def of the name redefines only the
|
|
149
|
+
# instance method. This reading approximates that from the body's direct `statements`: it yields
|
|
150
|
+
# `name, def_node` for each Symbol or String argument, where `def_node` is the last receiverless def
|
|
151
|
+
# of that name written as a direct statement before the call.
|
|
152
|
+
#
|
|
153
|
+
# A name declines (nothing is yielded) when no def precedes the call, where Ruby raises `NameError` or
|
|
154
|
+
# copies a def another file may load first, and when a statement after that def and before the call
|
|
155
|
+
# may rebind it in a way this reading cannot name: a def nested in control flow or a block, an `alias`,
|
|
156
|
+
# an `undef`, or a receiverless or `self.` `alias_method`, `define_method`, `attr_*`, `remove_method`
|
|
157
|
+
# or `undef_method` call. Such a call or `alias` with a non-literal name anywhere before the call, even
|
|
158
|
+
# before the def, declines every name. A `module_function def x` argument names nothing here.
|
|
159
|
+
#
|
|
160
|
+
# It does not see `send(:define_method, ...)`, a `define_method` on an explicit module receiver, a
|
|
161
|
+
# `prepend`, another file's singleton defs, or that a `rescue` clause's statements may run after an
|
|
162
|
+
# `ensure` call's (#1969).
|
|
87
163
|
def each_singleton_copy(call, statements)
|
|
88
|
-
|
|
89
|
-
|
|
164
|
+
call_start = call.location.start_offset
|
|
165
|
+
in_effect = {}
|
|
166
|
+
statements.each do |stmt|
|
|
167
|
+
next unless stmt.location.end_offset <= call_start
|
|
168
|
+
|
|
169
|
+
if stmt.is_a?(Prism::DefNode)
|
|
170
|
+
in_effect[stmt.name] = stmt if stmt.receiver.nil?
|
|
171
|
+
else
|
|
172
|
+
each_rebound_name(stmt) { |name| in_effect[name] = :unknown }
|
|
173
|
+
end
|
|
90
174
|
end
|
|
91
175
|
call.arguments&.arguments&.each do |arg|
|
|
92
176
|
name = symbol_argument_name(arg)
|
|
93
|
-
def_node = name &&
|
|
94
|
-
yield name, def_node if def_node
|
|
177
|
+
def_node = name && !in_effect.key?(ANY_NAME) && in_effect[name]
|
|
178
|
+
yield name, def_node if def_node.is_a?(Prism::DefNode)
|
|
95
179
|
end
|
|
96
180
|
end
|
|
97
181
|
|
|
98
|
-
# The
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
#
|
|
182
|
+
# The key {.each_singleton_copy} records when a statement may rebind a name it cannot read.
|
|
183
|
+
ANY_NAME = Object.new.freeze
|
|
184
|
+
|
|
185
|
+
# Calls that may define, alias or remove an instance method of the receiverless module.
|
|
186
|
+
REBINDING_CALLS = %i[
|
|
187
|
+
alias_method define_method attr attr_reader attr_writer attr_accessor remove_method undef_method
|
|
188
|
+
].freeze
|
|
189
|
+
|
|
190
|
+
# The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets` an
|
|
191
|
+
# `[offset, :on]` event for the start of every bare call, and for every {.visibility_reset?} an
|
|
192
|
+
# `[offset, :off]` event where it runs or a `[offset, :maybe_off]` one where it may not: `nested` is true
|
|
193
|
+
# once the walk has left the body's statements, a plain `begin` and an `ensure`. Appends to `ranges` one
|
|
194
|
+
# row per named call, in the table's `[start, end, name, kind, owner]` shape:
|
|
102
195
|
#
|
|
103
196
|
# - a `:name` argument gives a `:singleton` row over the call itself, because the copy happens at
|
|
104
197
|
# the call and not at the def;
|
|
@@ -108,33 +201,36 @@ module Rigor
|
|
|
108
201
|
# The prescan looks through control flow and blocks, because a call that ran there still flips the
|
|
109
202
|
# later defs. It stops at a nested class, module, `class <<` or def body, where the call targets
|
|
110
203
|
# another module. It skips an `END` body, which runs after every def, and a `*_eval` / `*_exec`
|
|
111
|
-
# block, whose self is the receiver's module
|
|
112
|
-
#
|
|
204
|
+
# block, whose self is the receiver's module, and a class-creating block (`Class.new do`, `Struct.new do`,
|
|
205
|
+
# `Data.define do`, `Module.new do`), whose self is the new class; such a block gets its own prescan as a
|
|
206
|
+
# body where the walk opens one.
|
|
113
207
|
#
|
|
114
208
|
# `qualified_prefix` names the owner, and `in_singleton_class` says whether the body is a
|
|
115
209
|
# `class <<` body; both feed `ScopeIndexer.def_singleton?`.
|
|
116
|
-
def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
210
|
+
def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges, nested: false)
|
|
117
211
|
return unless node.is_a?(Prism::Node)
|
|
118
212
|
return if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
|
|
119
213
|
node.is_a?(Prism::SingletonClassNode) || node.is_a?(Prism::DefNode) ||
|
|
120
214
|
node.is_a?(Prism::PostExecutionNode)
|
|
215
|
+
return if node.is_a?(Prism::CallNode) &&
|
|
216
|
+
prescanned_call?(node, qualified_prefix, in_singleton_class, offsets, ranges, nested)
|
|
121
217
|
|
|
122
|
-
|
|
123
|
-
return prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges) if call?(node)
|
|
124
|
-
if ScopeIndexer.receiver_eval_call?(node)
|
|
125
|
-
return prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
218
|
+
nested ||= !RUN_IN_PLACE.include?(node.class)
|
|
129
219
|
node.rigor_each_child do |child|
|
|
130
|
-
prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
220
|
+
prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges, nested: nested)
|
|
131
221
|
end
|
|
132
222
|
end
|
|
133
223
|
|
|
134
|
-
#
|
|
135
|
-
|
|
224
|
+
# The nodes whose statements run where they are written, so a reset there runs ({.prescan_deferred}).
|
|
225
|
+
RUN_IN_PLACE = [Prism::StatementsNode, Prism::BeginNode, Prism::EnsureNode].freeze
|
|
226
|
+
|
|
227
|
+
# Whether the deferred-ranges reading makes a def starting at `start` a module function: the last
|
|
228
|
+
# `:on` or `:off` event {.prescan_deferred} recorded before it, at any depth the prescan entered, is
|
|
229
|
+
# `:on`. A `:maybe_off` leaves the answer as it was: the row stays `:both`, the reading that counts the
|
|
230
|
+
# def as run on the singleton side too, since the table has no possible row for it.
|
|
136
231
|
def deferred_module_function?(offsets, start)
|
|
137
|
-
offsets.
|
|
232
|
+
last = offsets.select { |offset, event| offset < start && event != :maybe_off }.max_by(&:first)
|
|
233
|
+
last ? last[1] == :on : false
|
|
138
234
|
end
|
|
139
235
|
|
|
140
236
|
# The extends table's reading. A bare call makes the module extend itself, so every instance def the
|
|
@@ -148,10 +244,12 @@ module Rigor
|
|
|
148
244
|
end
|
|
149
245
|
|
|
150
246
|
# sig-gen's reading, over one statement list. Yields every statement except a bare call, with
|
|
151
|
-
# whether a bare call came before it in this list or `active` was already
|
|
247
|
+
# whether a bare call came before it in this list or `active` was already on at entry: `true`, `false`
|
|
248
|
+
# or `:uncertain` after a reset that may not run ({.mode_after}). The
|
|
152
249
|
# generator passes the enclosing list's answer into an `if`, a block or a `begin`, but not into a
|
|
153
250
|
# nested class, module or `class << self`. A bare call in a nested list covers only that list's
|
|
154
|
-
# later statements.
|
|
251
|
+
# later statements. A reset ends it for the statements after it, and a named call is not a directive at
|
|
252
|
+
# all.
|
|
155
253
|
def each_sig_gen_statement(statements, active)
|
|
156
254
|
statements.each do |stmt|
|
|
157
255
|
if sig_gen_directive?(stmt)
|
|
@@ -159,6 +257,7 @@ module Rigor
|
|
|
159
257
|
next
|
|
160
258
|
end
|
|
161
259
|
yield stmt, active
|
|
260
|
+
active = mode_after(active, stmt)
|
|
162
261
|
end
|
|
163
262
|
end
|
|
164
263
|
|
|
@@ -170,13 +269,73 @@ module Rigor
|
|
|
170
269
|
# Whether sig-gen renders a def as `def self?.name`: an instance-side def under an active directive,
|
|
171
270
|
# in a class or a module alike. `kind` is `:singleton` for `def self.x` and a `class << self` body.
|
|
172
271
|
def sig_gen_module_function?(active, kind)
|
|
173
|
-
active && kind == :instance
|
|
272
|
+
active == true && kind == :instance
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Whether sig-gen cannot tell a def's kind: an instance-side def after a reset that may not run, which
|
|
276
|
+
# is a module function only if the reset did not run.
|
|
277
|
+
def sig_gen_uncertain?(active, kind)
|
|
278
|
+
active == :uncertain && kind == :instance
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Yields each instance-method name `node` may rebind on the module whose body holds it, or
|
|
282
|
+
# {ANY_NAME} when one is not a literal. It does not enter a nested class, module or `class <<` body,
|
|
283
|
+
# whose methods belong to another module.
|
|
284
|
+
def each_rebound_name(node, &)
|
|
285
|
+
case node
|
|
286
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode then nil
|
|
287
|
+
when Prism::DefNode then yield node.name if node.receiver.nil?
|
|
288
|
+
else
|
|
289
|
+
each_own_rebound_name(node, &)
|
|
290
|
+
node.compact_child_nodes.each { |child| each_rebound_name(child, &) }
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# The names `node` itself may rebind, apart from its children: an `alias`, an `undef` or a
|
|
295
|
+
# {REBINDING_CALLS} call on the module.
|
|
296
|
+
def each_own_rebound_name(node, &)
|
|
297
|
+
case node
|
|
298
|
+
when Prism::AliasMethodNode then yield symbol_argument_name(node.new_name) || ANY_NAME
|
|
299
|
+
when Prism::UndefNode then node.names.each { |name| yield symbol_argument_name(name) || ANY_NAME }
|
|
300
|
+
when Prism::CallNode
|
|
301
|
+
each_rebinding_call_name(node, &) if REBINDING_CALLS.include?(node.name) &&
|
|
302
|
+
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# The names a {REBINDING_CALLS} call may rebind: the first argument of `alias_method` and
|
|
307
|
+
# `define_method`, every argument of the others (and its writer, for the `attr_*` family), or
|
|
308
|
+
# {ANY_NAME} for a non-literal one.
|
|
309
|
+
def each_rebinding_call_name(call)
|
|
310
|
+
arguments = call.arguments&.arguments || []
|
|
311
|
+
arguments = arguments.first(1) if %i[alias_method define_method].include?(call.name)
|
|
312
|
+
arguments.each do |arg|
|
|
313
|
+
name = symbol_argument_name(arg)
|
|
314
|
+
next yield(ANY_NAME) unless name
|
|
315
|
+
|
|
316
|
+
yield name
|
|
317
|
+
yield :"#{name}=" if call.name.start_with?("attr")
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# The prescan's arms for a call; true when the arm took the call and its subtree.
|
|
322
|
+
def prescanned_call?(node, qualified_prefix, in_singleton_class, offsets, ranges, nested)
|
|
323
|
+
if call?(node)
|
|
324
|
+
prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
325
|
+
elsif visibility_reset?(node)
|
|
326
|
+
offsets << [node.location.start_offset, nested ? :maybe_off : :off]
|
|
327
|
+
elsif ScopeIndexer.receiver_eval_call?(node) || (node.block && ScopeIndexer.meta_new_constant_rvalue?(node))
|
|
328
|
+
prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
329
|
+
else
|
|
330
|
+
return false
|
|
331
|
+
end
|
|
332
|
+
true
|
|
174
333
|
end
|
|
175
334
|
|
|
176
335
|
# The prescan's call arm.
|
|
177
336
|
def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
178
337
|
if bare?(node)
|
|
179
|
-
offsets << node.location.start_offset
|
|
338
|
+
offsets << [node.location.start_offset, :on]
|
|
180
339
|
else
|
|
181
340
|
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
182
341
|
node.arguments&.arguments&.each do |arg|
|
|
@@ -186,20 +345,22 @@ module Rigor
|
|
|
186
345
|
elsif (name = symbol_argument_name(arg))
|
|
187
346
|
ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
|
|
188
347
|
else
|
|
189
|
-
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
348
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
190
349
|
end
|
|
191
350
|
end
|
|
192
351
|
end
|
|
193
352
|
return unless node.block
|
|
194
353
|
|
|
195
|
-
prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
354
|
+
prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
196
355
|
end
|
|
197
356
|
|
|
198
357
|
# The prescan's eval arm: only the call's receiver and arguments keep this module's context.
|
|
199
358
|
def prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
200
|
-
|
|
359
|
+
if node.receiver
|
|
360
|
+
prescan_deferred(node.receiver, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
361
|
+
end
|
|
201
362
|
node.arguments&.arguments&.each do |arg|
|
|
202
|
-
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
363
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges, nested: true)
|
|
203
364
|
end
|
|
204
365
|
end
|
|
205
366
|
|
|
@@ -208,7 +369,8 @@ module Rigor
|
|
|
208
369
|
Source::Literals.symbol_or_string(arg)
|
|
209
370
|
end
|
|
210
371
|
|
|
211
|
-
private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name
|
|
372
|
+
private_class_method :prescan_call, :prescanned_call?, :prescan_eval_call, :symbol_argument_name,
|
|
373
|
+
:each_rebound_name, :each_own_rebound_name, :each_rebinding_call_name
|
|
212
374
|
end
|
|
213
375
|
end
|
|
214
376
|
end
|