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
|
@@ -83,10 +83,12 @@ module Rigor
|
|
|
83
83
|
# One ancestor on a chain. A project entry names a class or module (`name`) on `side` `:instance` (its
|
|
84
84
|
# instance methods) or `:singleton` (the class object's singleton). An external entry has no `name`: it
|
|
85
85
|
# keeps the spelling (`raw`) and its candidate names (`candidates`, most qualified first, empty for an
|
|
86
|
-
# ambiguous spelling), and `superclass_edge` says it was reached through a superclass edge.
|
|
86
|
+
# ambiguous spelling), and `superclass_edge` says it was reached through a superclass edge. `owner` names
|
|
87
|
+
# the class or module whose edge an external entry is (#1698: Ruby resolves an `include`'s argument with
|
|
88
|
+
# that body as the innermost cref, a namespace `candidates` leaves out); it is nil on a project entry.
|
|
87
89
|
# `last_segment` is the unqualified name a project entry files its negative class edge under, computed once
|
|
88
90
|
# when the entry is interned.
|
|
89
|
-
Entry = Data.define(:name, :side, :raw, :candidates, :superclass_edge, :last_segment) do
|
|
91
|
+
Entry = Data.define(:name, :side, :raw, :candidates, :superclass_edge, :last_segment, :owner) do
|
|
90
92
|
def external? = name.nil?
|
|
91
93
|
end
|
|
92
94
|
|
|
@@ -98,7 +100,9 @@ module Rigor
|
|
|
98
100
|
# module whose mixin edges' order is not a fact, `kind` the side read (`:include` or `:extend`), `listed`
|
|
99
101
|
# the entries `DiscoveryIndex#unpositioned_mixins` lists for it (`"*"` included), and `multi_file` whether
|
|
100
102
|
# the multi-file rule fired (two or more files declare it and it has two or more edges on that side).
|
|
101
|
-
# Interned per `[node, kind]` in the flavor's bucket, so equal marks are one object.
|
|
103
|
+
# Interned per `[node, kind]` in the flavor's bucket, so equal marks are one object. Kind `:extend_hook`
|
|
104
|
+
# (#1730, listing `"*"`) marks a node a module it extends may mix into through a hook; it is not an order mark
|
|
105
|
+
# ({#order_marks}).
|
|
102
106
|
Mark = Data.define(:node, :kind, :listed, :multi_file)
|
|
103
107
|
|
|
104
108
|
NO_MARKS = [].freeze
|
|
@@ -135,7 +139,20 @@ module Rigor
|
|
|
135
139
|
|
|
136
140
|
# True when a class on the chain has mixin edges whose order the tables cannot vouch for
|
|
137
141
|
# (`DiscoveryIndex#unpositioned_mixins`, or a class declared in several files with several edges).
|
|
138
|
-
def unsettled? =
|
|
142
|
+
def unsettled? = !order_marks.empty?
|
|
143
|
+
|
|
144
|
+
# The marks that make the chain's ORDER doubtful. An `:extend_hook` mark (#1730) says only that an extended
|
|
145
|
+
# module's hook may have mixed something in that the chain does not list: it makes a candidate-set read
|
|
146
|
+
# `:unknown` and {#wildcard_mixin?} true, and leaves every other reader on the chain's own order. Sending
|
|
147
|
+
# those readers to master's order would answer a constant lookup breadth-first, which is wrong for the
|
|
148
|
+
# #1567 shape on every class that extends such a module.
|
|
149
|
+
def order_marks
|
|
150
|
+
@memo[:order_marks] ||= @marks.reject { |mark| mark.kind == :extend_hook }.freeze
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# True when a class on the chain records a mixin the tables cannot name (`"*"`: an `include` of a
|
|
154
|
+
# non-constant, or a call the walk cannot record), so the chain may hold a module it does not list.
|
|
155
|
+
def wildcard_mixin? = @marks.any? { |mark| mark.listed.include?(Relevance::WILDCARD) }
|
|
139
156
|
|
|
140
157
|
# The ONE decision every first-definer reader makes: does `answer`, read off this chain, stand, or does
|
|
141
158
|
# the reader answer what the walk this chain replaced answered ({MasterOrder})? Returns `:chain` or
|
|
@@ -163,7 +180,7 @@ module Rigor
|
|
|
163
180
|
# goes; `search` files only the entries ahead of an answer. `owner` names the entry that answered.
|
|
164
181
|
record_beyond(scope, owner, unknown_for.nil?)
|
|
165
182
|
return settle_unknown(scope, answer, unknown_for, &) unless unknown_for.nil?
|
|
166
|
-
return :master unless
|
|
183
|
+
return :master unless order_marks.empty?
|
|
167
184
|
|
|
168
185
|
case @forks
|
|
169
186
|
when 0 then :chain
|
|
@@ -208,7 +225,10 @@ module Rigor
|
|
|
208
225
|
def search(scope, start = 0, stop = nil, side: nil)
|
|
209
226
|
recording = Analysis::DependencyRecorder.active?
|
|
210
227
|
stop ||= @entries.size
|
|
211
|
-
|
|
228
|
+
if recording
|
|
229
|
+
record_head(scope, start, side)
|
|
230
|
+
record_extend_hooks(scope)
|
|
231
|
+
end
|
|
212
232
|
index = start
|
|
213
233
|
while index < stop
|
|
214
234
|
entry = @entries[index]
|
|
@@ -228,6 +248,7 @@ module Rigor
|
|
|
228
248
|
|
|
229
249
|
ResolutionChain.record_class(scope, @root)
|
|
230
250
|
(stop || @entries.size).times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
251
|
+
record_extend_hooks(scope)
|
|
231
252
|
end
|
|
232
253
|
|
|
233
254
|
# What a {#settle} verdict adds to what the search already filed. The search files the root and every entry
|
|
@@ -241,7 +262,8 @@ module Rigor
|
|
|
241
262
|
# it `:unknown`, so the rest of the chain is filed as for any other answer;
|
|
242
263
|
# - otherwise every entry after the answer (the whole chain when `owner` names none) files its class edge
|
|
243
264
|
# and, for a project entry, the negative class edge on its unqualified name, so a NEW file declaring or
|
|
244
|
-
# reopening it re-checks the reader. An external entry files only the sites of the names it can denote
|
|
265
|
+
# reopening it re-checks the reader. An external entry files only the sites of the names it can denote,
|
|
266
|
+
# and every external on the chain files its hook edges ({#record_hooks}, #1641).
|
|
245
267
|
#
|
|
246
268
|
# What is filed is a function of this chain, the entry the filing starts from and the discovery index the
|
|
247
269
|
# sites are read from, so a consumer that settles the same chain from the same entry again (the common case:
|
|
@@ -253,6 +275,8 @@ module Rigor
|
|
|
253
275
|
|
|
254
276
|
start = owner.nil? ? nil : @entries.index { |entry| entry.name == owner }
|
|
255
277
|
Analysis::DependencyRecorder.file_chain_once(self, start, scope.discovery) { file_beyond(scope, start) }
|
|
278
|
+
record_hooks(scope)
|
|
279
|
+
record_extend_hooks(scope)
|
|
256
280
|
end
|
|
257
281
|
private :record_beyond
|
|
258
282
|
|
|
@@ -268,6 +292,113 @@ module Rigor
|
|
|
268
292
|
end
|
|
269
293
|
private :file_beyond
|
|
270
294
|
|
|
295
|
+
# #1641 — the hook edges of every EXTERNAL entry on the chain, ahead of the answer or past it. A project file
|
|
296
|
+
# that gives an external module a hook (`module Comparable; def self.included(base) = base.prepend(P); end`)
|
|
297
|
+
# can put a definer ahead of the answer wherever the module sits, and a candidate-set read declines on it
|
|
298
|
+
# (the hook's mixin lists `"*"` on the module). The entry's class edges are the sites of the names it can
|
|
299
|
+
# denote, which a NEW file is not among, and a negative class edge on its name would re-check every reader of
|
|
300
|
+
# `Kernel` or `Enumerable` on any reopening. So each candidate name files the negative method edge of every
|
|
301
|
+
# {Relevance::HOOKS} name on its singleton side (`method:Comparable.included`, the key a new `def
|
|
302
|
+
# self.included` satisfies); a file adding any other method to the module re-checks nothing, and once the
|
|
303
|
+
# module is declared its sites carry later edits and the removal. The edges are a function of the chain alone,
|
|
304
|
+
# so a consumer files them once per chain ({Analysis::DependencyRecorder.file_chain_once}, #1590). A hook
|
|
305
|
+
# written another way — an instance `def included` in a module extended onto the external, a
|
|
306
|
+
# `define_singleton_method(:included)` — satisfies no such key and is not seen.
|
|
307
|
+
def record_hooks(scope)
|
|
308
|
+
externals = (@memo[:externals] ||= @entries.select(&:external?).freeze)
|
|
309
|
+
return if externals.empty?
|
|
310
|
+
|
|
311
|
+
Analysis::DependencyRecorder.file_chain_once(self, :hooks, scope.discovery) do
|
|
312
|
+
externals.each do |entry|
|
|
313
|
+
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.hook_keys(name)) }
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
private :record_hooks
|
|
318
|
+
|
|
319
|
+
# #1730 — the edges of the {Builder#extend_hook_taint?} decision: an instance chain's project entries may carry
|
|
320
|
+
# a `"*"` mark because a module one of them `extend`s has a hook that mixes into the extender, and the
|
|
321
|
+
# module is often declared in a file the chain records nothing of. Each such module the decision read files
|
|
322
|
+
# its class edges (an edit to its hook moves its declaration signature) and the negative method edges of
|
|
323
|
+
# the hooks the decision reads ({ResolutionChain.extend_hook_keys}), which a NEW file reopening the module
|
|
324
|
+
# to add a hook satisfies; a spelling no project module answers files the singleton hook keys of each name
|
|
325
|
+
# it can denote, as {#record_hooks} does. Filed once per consumer and chain.
|
|
326
|
+
def record_extend_hooks(scope)
|
|
327
|
+
return unless @side == :instance
|
|
328
|
+
|
|
329
|
+
targets = (@memo[:extend_targets] ||= ResolutionChain.extend_targets(scope, @flavor, @entries))
|
|
330
|
+
return if targets.empty?
|
|
331
|
+
|
|
332
|
+
Analysis::DependencyRecorder.file_chain_once(self, :extend_hooks, scope.discovery) do
|
|
333
|
+
targets.each do |raw, resolved, candidates|
|
|
334
|
+
Analysis::DependencyRecorder.read_last_segment(:class, raw)
|
|
335
|
+
if resolved
|
|
336
|
+
ResolutionChain.record_class(scope, resolved)
|
|
337
|
+
Analysis::DependencyRecorder.read_keys(ResolutionChain.extend_hook_keys(resolved))
|
|
338
|
+
else
|
|
339
|
+
candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.hook_keys(name)) }
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
private :record_extend_hooks
|
|
345
|
+
|
|
346
|
+
# The hooks that let a module `extend`ed onto N reshape N's INSTANCE side (#1730): its singleton `extended` /
|
|
347
|
+
# `extend_object` run against N at the `extend` and may mix into N; its instance `included` /
|
|
348
|
+
# `append_features` / `prepended` / `prepend_features` / `inherited` become N's own singleton hooks and run
|
|
349
|
+
# against N's includers, prependers and subclasses, which the mark on N reaches through their chains.
|
|
350
|
+
SINGLETON_EXTEND_HOOKS = %i[extended extend_object].freeze
|
|
351
|
+
INSTANCE_EXTEND_HOOKS = %i[included append_features prepended prepend_features inherited].freeze
|
|
352
|
+
|
|
353
|
+
@extend_hook_keys = {}
|
|
354
|
+
|
|
355
|
+
# The frozen negative method keys of `name`'s {SINGLETON_EXTEND_HOOKS} and {INSTANCE_EXTEND_HOOKS}.
|
|
356
|
+
def self.extend_hook_keys(name)
|
|
357
|
+
@extend_hook_keys[name] ||= (
|
|
358
|
+
SINGLETON_EXTEND_HOOKS.map { |hook| "method:#{name}.#{hook}".freeze } +
|
|
359
|
+
INSTANCE_EXTEND_HOOKS.map { |hook| "method:#{name}##{hook}".freeze }
|
|
360
|
+
).freeze
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Whether extending `name` may mix modules into the extender's instance side, or into its includers' (#1730):
|
|
364
|
+
# `name` records a mixin it cannot name on its own instance side (`"*"`, which a hook's `base.include X` or
|
|
365
|
+
# `base.class_eval { … }` lists on the hook's module) and defines one of the hooks that would run it there.
|
|
366
|
+
def self.extend_reshapes_instance?(scope, name)
|
|
367
|
+
listed = scope.discovery.unpositioned_mixins[name]&.dig(:include)
|
|
368
|
+
return false unless listed&.include?(Relevance::WILDCARD)
|
|
369
|
+
|
|
370
|
+
defines_any?(scope.discovered_singleton_def_nodes[name], SINGLETON_EXTEND_HOOKS) ||
|
|
371
|
+
defines_any?(scope.discovered_def_nodes[name], INSTANCE_EXTEND_HOOKS)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Read off the def-node tables: the existence table does not carry a module's own instance hook once the
|
|
375
|
+
# extend fold has copied it onto the extender's singleton.
|
|
376
|
+
def self.defines_any?(defs, hooks)
|
|
377
|
+
!defs.nil? && hooks.any? { |hook| defs.key?(hook) }
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# `[[spelling, resolved name or nil, candidate names], …]`: every module the project entries of an instance chain
|
|
381
|
+
# `extend`, as {Builder#extend_hook_taint?} resolved them.
|
|
382
|
+
def self.extend_targets(scope, flavor, entries)
|
|
383
|
+
resolver = resolver_for(scope, flavor)
|
|
384
|
+
extends = scope.discovery.discovered_extends
|
|
385
|
+
entries.each_with_object([]) do |entry, out|
|
|
386
|
+
next if entry.external? || entry.side != :instance
|
|
387
|
+
|
|
388
|
+
(extends[entry.name] || EMPTY_NAMES).each do |raw|
|
|
389
|
+
resolved = resolver.resolve_one(entry.name, raw)
|
|
390
|
+
out << [raw, resolved, resolved ? EMPTY_NAMES : resolver.candidates(entry.name, raw)].freeze
|
|
391
|
+
end
|
|
392
|
+
end.uniq.freeze
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
@hook_keys = {}
|
|
396
|
+
|
|
397
|
+
# The frozen `method:Name.hook` negative keys of `name`'s {Relevance::HOOKS}, built once per name.
|
|
398
|
+
def self.hook_keys(name)
|
|
399
|
+
@hook_keys[name] ||= Relevance::HOOKS.map { |hook| "method:#{name}.#{hook}".freeze }.freeze
|
|
400
|
+
end
|
|
401
|
+
|
|
271
402
|
def record_head(scope, start, side)
|
|
272
403
|
ResolutionChain.record_class(scope, @root) unless start.zero? && @entries.first&.name == @root
|
|
273
404
|
start.times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
@@ -397,7 +528,8 @@ module Rigor
|
|
|
397
528
|
@discovery.discovered_superclasses.key?(name) || @discovery.discovered_includes.key?(name) ||
|
|
398
529
|
@discovery.discovered_classes.key?(name)
|
|
399
530
|
when :arity
|
|
400
|
-
@scope.known_user_class?(name) ||
|
|
531
|
+
@scope.known_user_class?(name) ||
|
|
532
|
+
DiscoveryIndex.declares_surface?(@discovery.discovered_parameter_envelopes[name])
|
|
401
533
|
else
|
|
402
534
|
@scope.known_user_class?(name)
|
|
403
535
|
end
|
|
@@ -601,7 +733,8 @@ module Rigor
|
|
|
601
733
|
# The chain for `root`, carrying its retro world when the skip rule skipped anything on the way.
|
|
602
734
|
def chain(root, side)
|
|
603
735
|
entries, starts, classes, forks, marks, retro_ok = lin_for(root, side)
|
|
604
|
-
|
|
736
|
+
order_marked = marks.any? { |mark| mark.kind != :extend_hook }
|
|
737
|
+
retro = build_retro(root, side) if forks == 1 && retro_ok && !order_marked && !@retro
|
|
605
738
|
cut_at = cut_position(entries)
|
|
606
739
|
return whole_chain(root, side, entries, starts, classes, [forks, marks], retro) if cut_at.nil?
|
|
607
740
|
|
|
@@ -701,6 +834,7 @@ module Rigor
|
|
|
701
834
|
def compute_instance(name, depth)
|
|
702
835
|
# `discovered_includes` already carries the prepended names, so it counts every instance-side edge once.
|
|
703
836
|
mark_unsettled(name, :include, (@discovery.discovered_includes[name] || EMPTY).size)
|
|
837
|
+
@frames.last.add_marks(mark_for(name, :extend_hook, WILDCARD_LIST, false)) if extend_hook_taint?(name)
|
|
704
838
|
entries = [project_entry(name, :instance)]
|
|
705
839
|
super_lin = superclass_lin(name, :instance, depth)
|
|
706
840
|
entries.concat(super_lin[0])
|
|
@@ -743,6 +877,20 @@ module Rigor
|
|
|
743
877
|
multi)].freeze
|
|
744
878
|
end
|
|
745
879
|
|
|
880
|
+
WILDCARD_LIST = ["*"].freeze # Relevance::WILDCARD, which loads after this file
|
|
881
|
+
private_constant :WILDCARD_LIST
|
|
882
|
+
|
|
883
|
+
# #1730 — whether a module `name` extends has a hook that may mix into `name`'s instance side or its
|
|
884
|
+
# includers' ({ResolutionChain.extend_reshapes_instance?}). Ruby runs it at the `extend`, so the mixin is
|
|
885
|
+
# not a fact of the tables and `name`'s instance side is unpositioned. A spelling no project module
|
|
886
|
+
# answers taints nothing: the project cannot see its hooks.
|
|
887
|
+
def extend_hook_taint?(name)
|
|
888
|
+
(@discovery.discovered_extends[name] || EMPTY).any? do |raw|
|
|
889
|
+
resolved = @resolver.resolve_one(name, raw)
|
|
890
|
+
!resolved.nil? && ResolutionChain.extend_reshapes_instance?(@scope, resolved)
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
746
894
|
def multi_file?(name)
|
|
747
895
|
sites = @discovery.discovered_class_sources[name]
|
|
748
896
|
!sites.nil? && sites.size >= 2
|
|
@@ -866,14 +1014,14 @@ module Rigor
|
|
|
866
1014
|
|
|
867
1015
|
def project_entry(name, side)
|
|
868
1016
|
by_side = (@bucket[:interned][side] ||= {})
|
|
869
|
-
by_side[name] ||= Entry.new(name, side, nil, nil, false, name.to_s.split("::").last.freeze)
|
|
1017
|
+
by_side[name] ||= Entry.new(name, side, nil, nil, false, name.to_s.split("::").last.freeze, nil)
|
|
870
1018
|
end
|
|
871
1019
|
|
|
872
1020
|
def external_entry(owner, raw, side, superclass_edge)
|
|
873
1021
|
by_edge = ((@bucket[:externals][side] ||= {})[superclass_edge] ||= {})
|
|
874
1022
|
by_owner = (by_edge[owner] ||= {})
|
|
875
1023
|
by_owner[raw] ||= Entry.new(nil, side, raw, @scope.ancestor_name_candidates(owner, raw).freeze,
|
|
876
|
-
superclass_edge, nil)
|
|
1024
|
+
superclass_edge, nil, owner)
|
|
877
1025
|
end
|
|
878
1026
|
end
|
|
879
1027
|
private_constant :Builder
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "environment"
|
|
|
5
5
|
require_relative "scope/discovery_index"
|
|
6
6
|
require_relative "scope/resolution_chain"
|
|
7
7
|
require_relative "inference/definer_resolution"
|
|
8
|
+
require_relative "inference/in_effect_refinements"
|
|
8
9
|
require_relative "analysis/fact_store"
|
|
9
10
|
require_relative "analysis/dependency_recorder"
|
|
10
11
|
require_relative "inference/expression_typer"
|
|
@@ -26,12 +27,12 @@ module Rigor
|
|
|
26
27
|
:indexed_narrowings, :method_chain_narrowings,
|
|
27
28
|
:declaration_sourced, :published_constant_sourced,
|
|
28
29
|
:source_path, :discovery, :struct_fold_safe_locals,
|
|
29
|
-
:opaque_block_self, :singleton_class_body, :lexical_nesting,
|
|
30
|
+
:opaque_block_self, :block_self_unknown, :singleton_class_body, :lexical_nesting,
|
|
30
31
|
:dynamic_origins, :local_origins, :ivar_origins,
|
|
31
32
|
:void_origins, :plugin_typed_calls,
|
|
32
33
|
:optimistic_origins, :optimistic_locals, :optimistic_ivars,
|
|
33
34
|
:repeated_or_writes, :match_frame,
|
|
34
|
-
:constant_narrowings, :guard_records, :bot_guard_classes
|
|
35
|
+
:constant_narrowings, :guard_records, :bot_guard_classes, :declared_refinements
|
|
35
36
|
|
|
36
37
|
# ADR-53 Track A — the seed-time discovery tables live on the {DiscoveryIndex} the scope carries by a single
|
|
37
38
|
# reference; the per-table readers stay on Scope so engine call sites and plugins are unaffected by the
|
|
@@ -62,6 +63,20 @@ module Rigor
|
|
|
62
63
|
# Issue #1120 — `{refined class name => {method name => [refining module names]}}`: what each
|
|
63
64
|
# `refine X do … end` block defines, visible only where a `using` of its module is in effect.
|
|
64
65
|
def discovered_refinements = @discovery.discovered_refinements
|
|
66
|
+
|
|
67
|
+
# Issue #1673 (ADR-121 WD1) — the in-effect refinements at `node`: the refining-module names whose refinements
|
|
68
|
+
# Ruby applies there, ordered so a later activation comes later (the later one wins), each once at its first
|
|
69
|
+
# position, each `using`'d module expanded through its includes and prepends in CRuby's activation order.
|
|
70
|
+
# `declared` is a block source's modules, appended and expanded on the same terms; it defaults to this scope's
|
|
71
|
+
# {#declared_refinements}, a plugin-declared refined block's (#1667). An entry may be
|
|
72
|
+
# `Inference::InEffectRefinements::UNKNOWN`, which means any refinement may be in effect. A node another file
|
|
73
|
+
# wrote (a callee body typed under this file's scope) answers `declared` alone. Reads include edges, so it records
|
|
74
|
+
# the dependencies `Inference::InEffectRefinements.activated_modules` names; a consumer that asks for a method
|
|
75
|
+
# name also records `refinement:<name>` (`Inference::InEffectRefinements.refining_modules`).
|
|
76
|
+
def in_effect_refinements(node, declared = @declared_refinements)
|
|
77
|
+
Inference::InEffectRefinements.for_node(self, node, declared)
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
# Issue #1367 — the project's `Inference::GlobalWriteCensus`, a `Set` of frozen entries.
|
|
66
81
|
def discovered_global_write_census = @discovery.discovered_global_write_census
|
|
67
82
|
def discovered_includes = @discovery.discovered_includes
|
|
@@ -182,7 +197,9 @@ module Rigor
|
|
|
182
197
|
# today.
|
|
183
198
|
# - `receiver_name` is the variable's Symbol.
|
|
184
199
|
# - `key` is the Ruby value of the literal index (Symbol / String / Integer). Non-literal keys
|
|
185
|
-
# (`params[field]`)
|
|
200
|
+
# (`params[field]`) have no stable address for a recorded value. A `key?` guard with a non-literal key
|
|
201
|
+
# (issue #1703) records an {Inference::KeyPresenceGuard::KeyExpr} here instead, with `receiver_kind`
|
|
202
|
+
# `:const` allowed; its entry marks presence only.
|
|
186
203
|
IndexedKey = Data.define(:receiver_kind, :receiver_name, :key)
|
|
187
204
|
|
|
188
205
|
# Narrowing key for a no-arg / no-block method-call chain `receiver.method_name` (a "single-hop" chain per A1
|
|
@@ -320,6 +337,7 @@ module Rigor
|
|
|
320
337
|
source_path: nil,
|
|
321
338
|
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
322
339
|
opaque_block_self: false,
|
|
340
|
+
block_self_unknown: false,
|
|
323
341
|
singleton_class_body: false,
|
|
324
342
|
lexical_nesting: nil,
|
|
325
343
|
dynamic_origins: {}.compare_by_identity,
|
|
@@ -334,7 +352,8 @@ module Rigor
|
|
|
334
352
|
match_frame: nil,
|
|
335
353
|
constant_narrowings: EMPTY_CONSTANT_NARROWINGS,
|
|
336
354
|
guard_records: EMPTY_GUARD_RECORDS,
|
|
337
|
-
bot_guard_classes: EMPTY_BOT_GUARD_CLASSES
|
|
355
|
+
bot_guard_classes: EMPTY_BOT_GUARD_CLASSES,
|
|
356
|
+
declared_refinements: Inference::InEffectRefinements::EMPTY
|
|
338
357
|
)
|
|
339
358
|
@environment = environment
|
|
340
359
|
@locals = locals
|
|
@@ -351,6 +370,7 @@ module Rigor
|
|
|
351
370
|
@source_path = source_path
|
|
352
371
|
@struct_fold_safe_locals = struct_fold_safe_locals
|
|
353
372
|
@opaque_block_self = opaque_block_self
|
|
373
|
+
@block_self_unknown = block_self_unknown
|
|
354
374
|
@singleton_class_body = singleton_class_body
|
|
355
375
|
@lexical_nesting = lexical_nesting
|
|
356
376
|
@dynamic_origins = dynamic_origins
|
|
@@ -366,6 +386,7 @@ module Rigor
|
|
|
366
386
|
@constant_narrowings = constant_narrowings
|
|
367
387
|
@guard_records = guard_records
|
|
368
388
|
@bot_guard_classes = bot_guard_classes
|
|
389
|
+
@declared_refinements = declared_refinements
|
|
369
390
|
freeze
|
|
370
391
|
end
|
|
371
392
|
|
|
@@ -378,6 +399,14 @@ module Rigor
|
|
|
378
399
|
self
|
|
379
400
|
end
|
|
380
401
|
|
|
402
|
+
# Issue #1703 — this scope with private copies of the identity-keyed side tables (dynamic, void and optimistic
|
|
403
|
+
# origins, plugin-typed calls), so a second walk from it — the `key?`-guarded re-walk — records into its own
|
|
404
|
+
# tables and never into the ones the file's analysis reads.
|
|
405
|
+
def with_isolated_side_tables
|
|
406
|
+
rebuild(dynamic_origins: @dynamic_origins.dup, void_origins: @void_origins.dup,
|
|
407
|
+
plugin_typed_calls: @plugin_typed_calls.dup, optimistic_origins: @optimistic_origins.dup)
|
|
408
|
+
end
|
|
409
|
+
|
|
381
410
|
def optimistic_local(name) = Inference::OptimisticOrigin.bound_cause(@optimistic_locals[name.to_sym])
|
|
382
411
|
def optimistic_ivar(name) = Inference::OptimisticOrigin.bound_cause(@optimistic_ivars[name.to_sym])
|
|
383
412
|
|
|
@@ -476,6 +505,39 @@ module Rigor
|
|
|
476
505
|
rebuild(self_type: type)
|
|
477
506
|
end
|
|
478
507
|
|
|
508
|
+
# Issue #1717 — installs the `self` a block body runs with when the engine KNOWS it: an ADR-16
|
|
509
|
+
# `block_as_methods:` match, `define_method`, or a `Class.new` / `refine` body (#319). Like
|
|
510
|
+
# {#with_self_type} it leaves the #316 mark alone; it also sets {#block_self_unknown?} to `keeps_unknown`,
|
|
511
|
+
# which a caller passes from {#block_self_narrowing_unknown?}. A nil `type` narrows nothing and keeps it.
|
|
512
|
+
#
|
|
513
|
+
# Issue #1695 — instance variables belong to `self`, so a narrowing onto a DIFFERENT `self` than the enclosing
|
|
514
|
+
# scope's also drops every instance-variable binding the body would otherwise inherit ({#without_ivar_bindings}):
|
|
515
|
+
# a `define_method` block in a class body runs on an instance, and the class body's `@label = nil` is the class
|
|
516
|
+
# object's ivar, not the one the block reads. The body's reads answer `Dynamic` until it writes them. A narrowing
|
|
517
|
+
# that keeps the caller's `self` (an ADR-16 `:lexical` entry) keeps them.
|
|
518
|
+
def with_block_self_type(type, keeps_unknown: false)
|
|
519
|
+
unknown = type.nil? ? @block_self_unknown : keeps_unknown
|
|
520
|
+
return rebuild(self_type: type, block_self_unknown: unknown) if type.nil? || type == @self_type
|
|
521
|
+
|
|
522
|
+
without_ivar_bindings(type, unknown)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Issue #1717 — whether a block-self narrowing for `call_node`, asked from this (the CALLER's) scope, is
|
|
526
|
+
# built on an unknown `self`. `define_method` and an implicit- or `self`-receiver `block_as_methods:` match
|
|
527
|
+
# read the lexical `self`, which inside a block of unknown `self` is the enclosing method's, not the
|
|
528
|
+
# block's; a narrowing keyed on an explicit receiver (`Grape::API.namespace do`) does not depend on it.
|
|
529
|
+
#
|
|
530
|
+
# Issue #1759 — one narrowing is not: a `define_method` written directly in a `refine X` block
|
|
531
|
+
# ({REFINEMENT_BLOCK_SELF}) defines a method of the refinement, which runs on an instance of X, and the
|
|
532
|
+
# `Nominal[X]` it reads off the block's lexical `Singleton[X]` is that `self`.
|
|
533
|
+
def block_self_narrowing_unknown?(call_node)
|
|
534
|
+
return false unless @block_self_unknown
|
|
535
|
+
return false if @block_self_unknown == REFINEMENT_BLOCK_SELF && call_node.name == :define_method
|
|
536
|
+
|
|
537
|
+
receiver = call_node.receiver
|
|
538
|
+
receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
539
|
+
end
|
|
540
|
+
|
|
479
541
|
# Issue #652 — installs the body's REAL `Module.nesting`, innermost first, as recorded at declaration
|
|
480
542
|
# time by `Inference::StatementEvaluator` (`["Admin::UsersController"]` for a compact
|
|
481
543
|
# `class Admin::UsersController`, `["Admin::UsersController", "Admin"]` for the nested spelling). A
|
|
@@ -518,15 +580,52 @@ module Rigor
|
|
|
518
580
|
# `Array#each` without knowing the callee. The flag is set at every block entry that leaves `self_type`
|
|
519
581
|
# unnarrowed and is inherited by every scope derived inside the block; it never leaks past the block,
|
|
520
582
|
# because `eval_call` returns the caller's scope unchanged.
|
|
583
|
+
#
|
|
584
|
+
# Entering also sets {#block_self_unknown?} (issue #1717), the separate mark `call.undefined-method` reads.
|
|
521
585
|
def entering_opaque_block
|
|
522
|
-
return self if @opaque_block_self
|
|
586
|
+
return self if @opaque_block_self && @block_self_unknown == true
|
|
523
587
|
|
|
524
|
-
rebuild(opaque_block_self: true)
|
|
588
|
+
rebuild(opaque_block_self: true, block_self_unknown: true)
|
|
525
589
|
end
|
|
526
590
|
|
|
527
591
|
# True when this scope sits inside a block whose `self` is unmodelled ({#entering_opaque_block}).
|
|
528
592
|
def opaque_block_self? = @opaque_block_self
|
|
529
593
|
|
|
594
|
+
# Issue #1717 — true when this scope sits in a block whose `self` the engine does not know, so an explicit
|
|
595
|
+
# `self.m` there is no evidence `m` is missing: the yielding method may `instance_exec` the block on another
|
|
596
|
+
# object. Read ONLY by `call.undefined-method`'s explicit-`self` exemption, and kept apart from
|
|
597
|
+
# {#opaque_block_self?} so that mark's reach (the top-level-def bind gate) is untouched. Set with
|
|
598
|
+
# {#entering_opaque_block} on the evaluator's block entries and by the indexer's walk of an unentered block
|
|
599
|
+
# ({#with_block_self_unknown}); cleared where a block's `self` is narrowed ({#with_block_self_type}) and, on
|
|
600
|
+
# the indexer's walk, at a `def` / `class` / `module` body, which the evaluator starts from a fresh scope.
|
|
601
|
+
def block_self_unknown? = @block_self_unknown ? true : false
|
|
602
|
+
|
|
603
|
+
# Issue #1759 — the {#block_self_unknown?} value a `refine X do … end` block body is entered with
|
|
604
|
+
# (`with_block_self_type(Singleton[X], keeps_unknown: REFINEMENT_BLOCK_SELF)`). It reads as unknown everywhere
|
|
605
|
+
# `true` does; what it adds is that the body's lexical `self` is X's singleton, which a `define_method` in it
|
|
606
|
+
# reads correctly ({#block_self_narrowing_unknown?}). A block nested in the body takes plain `true`.
|
|
607
|
+
REFINEMENT_BLOCK_SELF = :refinement
|
|
608
|
+
|
|
609
|
+
# Issue #1763 — true directly in a `refine X do … end` block body ({REFINEMENT_BLOCK_SELF}). The body's
|
|
610
|
+
# `self_type` is the `Singleton[X]` its implicit-self calls and `define_method` read, but the block runs on the
|
|
611
|
+
# refinement module, so a `self` read as a value (`x = self`) types as `Dynamic[top]` there.
|
|
612
|
+
def refinement_block_self? = @block_self_unknown == REFINEMENT_BLOCK_SELF
|
|
613
|
+
|
|
614
|
+
# The join of two arms' {#block_self_unknown?} values: `true` if either has it, else either's mark.
|
|
615
|
+
def merged_block_self_unknown(other)
|
|
616
|
+
return true if @block_self_unknown == true || other == true
|
|
617
|
+
|
|
618
|
+
@block_self_unknown || other
|
|
619
|
+
end
|
|
620
|
+
private :merged_block_self_unknown
|
|
621
|
+
|
|
622
|
+
# Issue #1717 — sets {#block_self_unknown?} to `flag`, leaving every other field (the #316 mark included).
|
|
623
|
+
def with_block_self_unknown(flag)
|
|
624
|
+
return self if @block_self_unknown == flag
|
|
625
|
+
|
|
626
|
+
rebuild(block_self_unknown: flag)
|
|
627
|
+
end
|
|
628
|
+
|
|
530
629
|
# Issue #963 — marks the body of a `class << ...` as such. Inside it `self` is the SINGLETON class, which
|
|
531
630
|
# Rigor models with the same `Singleton[X]` carrier a `class X` body gets, so the carrier alone cannot say
|
|
532
631
|
# which of the two a scope came from. The distinction decides what `define_method` does: in a `class << self`
|
|
@@ -544,6 +643,19 @@ module Rigor
|
|
|
544
643
|
rebuild(singleton_class_body: flag)
|
|
545
644
|
end
|
|
546
645
|
|
|
646
|
+
# Issue #1667 (ADR-121 WD5) — this scope with `names` appended to {#declared_refinements}, each once at its first
|
|
647
|
+
# position: the modules a plugin-declared refined block (`block_as_methods:` `refinements:`) runs under. Stamped
|
|
648
|
+
# at block entry and inherited by every scope derived inside the body, nested blocks included, and by a `def` or
|
|
649
|
+
# class body written in it (`StatementEvaluator#build_fresh_body_scope`), as CRuby's cref is.
|
|
650
|
+
def with_declared_refinements(names)
|
|
651
|
+
return self if names.empty?
|
|
652
|
+
|
|
653
|
+
merged = (@declared_refinements | names).freeze
|
|
654
|
+
return self if merged == @declared_refinements
|
|
655
|
+
|
|
656
|
+
rebuild(declared_refinements: merged)
|
|
657
|
+
end
|
|
658
|
+
|
|
547
659
|
# True when this scope IS a `class << ...` body (not merely inside one lexically — a `def` reached from it
|
|
548
660
|
# answers false).
|
|
549
661
|
def singleton_class_body? = @singleton_class_body
|
|
@@ -600,6 +712,45 @@ module Rigor
|
|
|
600
712
|
end
|
|
601
713
|
private :bind_ivar
|
|
602
714
|
|
|
715
|
+
# Issue #1695 — this scope with no instance-variable binding and none of the per-ivar state that rides on one:
|
|
716
|
+
# the narrowings keyed on or rooted at an ivar, its ADR-58 and #667 marks, ADR-82 origins, #286 optimistic marks
|
|
717
|
+
# and #1446 guard records, the state {#bind_ivar} drops for one name. It installs {#with_block_self_type}'s
|
|
718
|
+
# `self_type` and unknown-`self` mark in the same rebuild.
|
|
719
|
+
def without_ivar_bindings(self_type, block_self_unknown)
|
|
720
|
+
rebuild(
|
|
721
|
+
self_type: self_type,
|
|
722
|
+
block_self_unknown: block_self_unknown,
|
|
723
|
+
ivars: EMPTY_VAR_BINDINGS,
|
|
724
|
+
indexed_narrowings: reject_kept(@indexed_narrowings) { |k, _| ivar_indexed_key?(k) },
|
|
725
|
+
method_chain_narrowings: reject_kept(@method_chain_narrowings) { |k, _| k.receiver_kind == :ivar },
|
|
726
|
+
declaration_sourced: reject_kept(@declaration_sourced) { |ref| ref[0] == :ivar },
|
|
727
|
+
published_constant_sourced: reject_kept(@published_constant_sourced) { |ref| ref[0] == :ivar },
|
|
728
|
+
ivar_origins: EMPTY_ORIGINS,
|
|
729
|
+
optimistic_ivars: EMPTY_ORIGINS,
|
|
730
|
+
guard_records: reject_kept(@guard_records) { |key, _| key[0] == :ivar },
|
|
731
|
+
bot_guard_classes: reject_kept(@bot_guard_classes) { |key, _| key[0] == :ivar }
|
|
732
|
+
)
|
|
733
|
+
end
|
|
734
|
+
private :without_ivar_bindings
|
|
735
|
+
|
|
736
|
+
# `collection` without the entries the block selects, or `collection` itself when it selects none. It keeps the
|
|
737
|
+
# collection's class: `Set#reject` answers an Array, which a Set carrier's readers (`merge`, `|`, `==`) reject.
|
|
738
|
+
def reject_kept(collection, &)
|
|
739
|
+
return collection if collection.empty? || collection.none?(&)
|
|
740
|
+
|
|
741
|
+
collection.dup.delete_if(&).freeze
|
|
742
|
+
end
|
|
743
|
+
private :reject_kept
|
|
744
|
+
|
|
745
|
+
# An indexed narrowing of an ivar, or of a `key?` guard's key read from one ({#key_guard_rooted_at?}).
|
|
746
|
+
def ivar_indexed_key?(indexed_key)
|
|
747
|
+
return true if indexed_key.receiver_kind == :ivar
|
|
748
|
+
|
|
749
|
+
key = indexed_key.key
|
|
750
|
+
key.is_a?(Inference::KeyPresenceGuard::KeyExpr) && key.root[0] == :ivar
|
|
751
|
+
end
|
|
752
|
+
private :ivar_indexed_key?
|
|
753
|
+
|
|
603
754
|
# ADR-58 WD1 — used by the method-entry seed to mark an ivar whose only provenance is the class-ivar index.
|
|
604
755
|
# Unlike `with_ivar` this binds the type AND records the declaration-sourced mark in one transition.
|
|
605
756
|
def seed_declaration_sourced_ivar(name, type)
|
|
@@ -1954,6 +2105,7 @@ module Rigor
|
|
|
1954
2105
|
source_path: @source_path,
|
|
1955
2106
|
struct_fold_safe_locals: @struct_fold_safe_locals,
|
|
1956
2107
|
opaque_block_self: @opaque_block_self,
|
|
2108
|
+
block_self_unknown: @block_self_unknown,
|
|
1957
2109
|
singleton_class_body: @singleton_class_body,
|
|
1958
2110
|
lexical_nesting: @lexical_nesting,
|
|
1959
2111
|
dynamic_origins: @dynamic_origins,
|
|
@@ -1968,7 +2120,8 @@ module Rigor
|
|
|
1968
2120
|
match_frame: @match_frame,
|
|
1969
2121
|
constant_narrowings: @constant_narrowings,
|
|
1970
2122
|
guard_records: @guard_records,
|
|
1971
|
-
bot_guard_classes: @bot_guard_classes
|
|
2123
|
+
bot_guard_classes: @bot_guard_classes,
|
|
2124
|
+
declared_refinements: @declared_refinements
|
|
1972
2125
|
)
|
|
1973
2126
|
self.class.new(
|
|
1974
2127
|
environment: environment, locals: locals,
|
|
@@ -1982,6 +2135,7 @@ module Rigor
|
|
|
1982
2135
|
source_path: source_path,
|
|
1983
2136
|
struct_fold_safe_locals: struct_fold_safe_locals,
|
|
1984
2137
|
opaque_block_self: opaque_block_self,
|
|
2138
|
+
block_self_unknown: block_self_unknown,
|
|
1985
2139
|
singleton_class_body: singleton_class_body,
|
|
1986
2140
|
lexical_nesting: lexical_nesting,
|
|
1987
2141
|
dynamic_origins: dynamic_origins,
|
|
@@ -1996,7 +2150,8 @@ module Rigor
|
|
|
1996
2150
|
match_frame: match_frame,
|
|
1997
2151
|
constant_narrowings: constant_narrowings,
|
|
1998
2152
|
guard_records: guard_records,
|
|
1999
|
-
bot_guard_classes: bot_guard_classes
|
|
2153
|
+
bot_guard_classes: bot_guard_classes,
|
|
2154
|
+
declared_refinements: declared_refinements
|
|
2000
2155
|
)
|
|
2001
2156
|
end
|
|
2002
2157
|
|
|
@@ -2014,7 +2169,8 @@ module Rigor
|
|
|
2014
2169
|
result.freeze
|
|
2015
2170
|
end
|
|
2016
2171
|
|
|
2017
|
-
|
|
2172
|
+
# One keyword per Scope field, each with its join rule beside it; the size is the field count.
|
|
2173
|
+
def build_joined_scope(joined_locals, joined_ivars, joined_cvars, joined_globals, other) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
2018
2174
|
self.class.new(
|
|
2019
2175
|
environment: @environment,
|
|
2020
2176
|
locals: joined_locals.freeze,
|
|
@@ -2062,6 +2218,9 @@ module Rigor
|
|
|
2062
2218
|
# block entry by `entering_opaque_block` and inherited through `rebuild`), so both arms usually carry
|
|
2063
2219
|
# the same value and the `||` is that value.
|
|
2064
2220
|
opaque_block_self: @opaque_block_self || other.opaque_block_self,
|
|
2221
|
+
# Issue #1717 — the same body property, and `||` keeps the exemption it grants (the FP-safe side).
|
|
2222
|
+
# A plain `true` outranks the refine body's {REFINEMENT_BLOCK_SELF} for the same reason.
|
|
2223
|
+
block_self_unknown: merged_block_self_unknown(other.block_self_unknown),
|
|
2065
2224
|
# Issue #963 — a body property like the two above, so both arms of an in-body merge carry the identical
|
|
2066
2225
|
# value and the `||` is that value. `||` is also the safe direction on its own terms: keeping the mark
|
|
2067
2226
|
# declines the `define_method` narrowing, which is the pre-#963 answer.
|
|
@@ -2086,12 +2245,24 @@ module Rigor
|
|
|
2086
2245
|
# of a merge inside one body carry the same one; `||` keeps it should either arm lack it, since dropping
|
|
2087
2246
|
# it only loses the frame's resets.
|
|
2088
2247
|
match_frame: @match_frame || other.match_frame,
|
|
2248
|
+
# Issue #1667 — a body property stamped at block entry like the frame above, so both arms of a merge carry
|
|
2249
|
+
# the same list; the union keeps either arm's modules, the declining direction.
|
|
2250
|
+
declared_refinements: join_declared_refinements(other),
|
|
2089
2251
|
# Issue #1429 — the guard narrowings of globals and constants and their pre-guard records, and of instance
|
|
2090
2252
|
# variables (#1446).
|
|
2091
2253
|
**join_guard_narrowings(other, joined_globals, joined_ivars)
|
|
2092
2254
|
)
|
|
2093
2255
|
end
|
|
2094
2256
|
|
|
2257
|
+
def join_declared_refinements(other)
|
|
2258
|
+
mine = @declared_refinements
|
|
2259
|
+
theirs = other.declared_refinements
|
|
2260
|
+
return mine if theirs.equal?(mine) || theirs.empty?
|
|
2261
|
+
return theirs if mine.empty?
|
|
2262
|
+
|
|
2263
|
+
(mine | theirs).freeze
|
|
2264
|
+
end
|
|
2265
|
+
|
|
2095
2266
|
def join_guard_narrowings(other, joined_globals, joined_ivars)
|
|
2096
2267
|
joined_constants = join_constant_narrowings(other)
|
|
2097
2268
|
joined = { global: joined_globals, ivar: joined_ivars, constant: joined_constants }
|
|
@@ -2246,11 +2417,19 @@ module Rigor
|
|
|
2246
2417
|
sym_kind = receiver_kind.to_sym
|
|
2247
2418
|
sym_name = receiver_name.to_sym
|
|
2248
2419
|
filtered = @indexed_narrowings.reject do |k, _|
|
|
2249
|
-
k.receiver_kind == sym_kind && k.receiver_name == sym_name
|
|
2420
|
+
(k.receiver_kind == sym_kind && k.receiver_name == sym_name) || key_guard_rooted_at?(k.key, sym_kind, sym_name)
|
|
2250
2421
|
end
|
|
2251
2422
|
filtered.size == @indexed_narrowings.size ? @indexed_narrowings : filtered.freeze
|
|
2252
2423
|
end
|
|
2253
2424
|
|
|
2425
|
+
# Issue #1703 — whether `key` is a `key?` guard's key that is the variable `kind`/`name`, or a chain read from it.
|
|
2426
|
+
def key_guard_rooted_at?(key, kind, name)
|
|
2427
|
+
return false unless key.is_a?(Inference::KeyPresenceGuard::KeyExpr)
|
|
2428
|
+
|
|
2429
|
+
root = key.root
|
|
2430
|
+
root[0] == kind && root[1] == name
|
|
2431
|
+
end
|
|
2432
|
+
|
|
2254
2433
|
# ADR-58 WD1 — set/clear the declaration-sourced provenance mark.
|
|
2255
2434
|
# Issue #667 — the #667 carrier's add/drop pair. Deliberately not reusing
|
|
2256
2435
|
# {#add_declaration_sourced} / {#drop_declaration_sourced_for}: the two sets are joined by opposite
|
|
@@ -57,7 +57,10 @@ module Rigor
|
|
|
57
57
|
inline_generic_class: "sig.skipped.inline-generic-class",
|
|
58
58
|
# ADR-112 WD4 — the member is declared inline and in `sig/`, and the two disagree. A refusal, not a
|
|
59
59
|
# skip: `--write` and `--check` exit 1 until a person reconciles them or passes `--overwrite`.
|
|
60
|
-
inline_differs: "sig.skipped.inline-differs"
|
|
60
|
+
inline_differs: "sig.skipped.inline-differs",
|
|
61
|
+
# #1676 — a plain top-level `def` defines a private `Object` method and has no class to write a
|
|
62
|
+
# declaration under, so it is reported rather than silently dropped. Whether to emit it stays open.
|
|
63
|
+
top_level_def: "sig.skipped.top-level-def"
|
|
61
64
|
}.freeze
|
|
62
65
|
end
|
|
63
66
|
end
|