rigortype 0.4.0 → 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 +4 -3
- 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/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -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/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- 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 +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- 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 +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -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 +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- 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 +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- 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 +73 -14
- 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 +305 -54
- 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 +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- 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/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- 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/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- 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 +59 -3
- 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 +13 -2
- 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/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
4
|
require_relative "incremental"
|
|
5
|
+
require_relative "incremental_run_slot"
|
|
6
|
+
require_relative "incremental_run_slot/write_guard"
|
|
5
7
|
require_relative "plugin_fact_fingerprint"
|
|
8
|
+
require_relative "source_rbs_gate"
|
|
6
9
|
require_relative "../cache/file_digest"
|
|
7
10
|
require_relative "../cache/incremental_snapshot"
|
|
8
11
|
require_relative "../effects/file_collection"
|
|
@@ -22,10 +25,11 @@ module Rigor
|
|
|
22
25
|
# only `ΔF ∪ dependents[ΔF]`, and serves every other analyzed file from the cache — the body tier.
|
|
23
26
|
#
|
|
24
27
|
# The invariant the verify harness (and the spec) assert: `#recheck`'s merged diagnostics are
|
|
25
|
-
# byte-identical
|
|
26
|
-
# `--verify-incremental` acceptance gate, here without disk
|
|
27
|
-
# in-process). It models the body tier only: an edit that adds /
|
|
28
|
-
# the analyzed set it maintains and falls to a fresh {#baseline} (the
|
|
28
|
+
# byte-identical to a full `--no-cache` re-analysis of the edited tree, in the same order (#1524; the gate
|
|
29
|
+
# itself compares sorted sets). This is the `--verify-incremental` acceptance gate, here without disk
|
|
30
|
+
# persistence or CLI wiring (the cache is in-process). It models the body tier only: an edit that adds /
|
|
31
|
+
# removes / moves a *file* is outside the analyzed set it maintains and falls to a fresh {#baseline} (the
|
|
32
|
+
# structural tier is a later slice).
|
|
29
33
|
# The class-length budget is relaxed: this is one cohesive orchestrator of the incremental state
|
|
30
34
|
# (per-file diagnostics cache, the file-level / symbol-level / negative dependency graphs, and the ADR-85
|
|
31
35
|
# seed bundles), clearer read together than split across micro-classes that would all share the same ivars.
|
|
@@ -159,6 +163,7 @@ module Rigor
|
|
|
159
163
|
# Full baseline analysis with recording. Returns the run's diagnostics; populates the in-process cache
|
|
160
164
|
# + dependency state.
|
|
161
165
|
def baseline
|
|
166
|
+
@slot_carried_from = nil # ADR-45 WD2 — see {#load_snapshot}
|
|
162
167
|
runner = build_runner(record_dependencies: true)
|
|
163
168
|
diagnostics = run_runner(runner).diagnostics
|
|
164
169
|
@last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
|
|
@@ -167,7 +172,9 @@ module Rigor
|
|
|
167
172
|
# must never read as a file that VANISHED from the project on the next recheck (which is what
|
|
168
173
|
# `previous - current` would say, `current_files` being a `.rb` expansion).
|
|
169
174
|
@analyzed = runner.analyzed_files - runner.template_unit_paths
|
|
170
|
-
|
|
175
|
+
# ADR-85 WD2 — the freshly built bundle set for the next run, each stamped with its file's source-RBS
|
|
176
|
+
# digest (issue #1536).
|
|
177
|
+
@seed_bundles = source_rbs_gate.stamp(runner.seed_bundles, whole_project: true)
|
|
171
178
|
absorb_dependency_graph(runner)
|
|
172
179
|
@return_summaries = runner.return_summaries # ADR-89 WD2 — the full-run behavioural surface.
|
|
173
180
|
# ADR-67 WD6c lift — the seed table the runner's own pre-pass computed ({} when the gate is off).
|
|
@@ -178,6 +185,7 @@ module Rigor
|
|
|
178
185
|
@effects_identity = current_effects_identity
|
|
179
186
|
@cache = per_file(runner.per_file_diagnostics)
|
|
180
187
|
@digests = @analyzed.to_h { |path| [path, pack_digest(path)] }
|
|
188
|
+
forget_unbound_digests
|
|
181
189
|
@run_level_rows = runner.run_level_rows
|
|
182
190
|
diagnostics
|
|
183
191
|
end
|
|
@@ -199,15 +207,16 @@ module Rigor
|
|
|
199
207
|
param_files, param_pairs = param_seed_invalidation(fresh_params)
|
|
200
208
|
affected = affected_closure(changed, added, removed, param_files, param_pairs)
|
|
201
209
|
analyze_set = affected & current
|
|
210
|
+
reused = (current & previous) - affected.to_a
|
|
202
211
|
# The freshly collected table is handed to the runner so the run seeds from the SAME table the diff
|
|
203
|
-
# was decided on (and the collector runs once per recheck, not twice).
|
|
212
|
+
# was decided on (and the collector runs once per recheck, not twice). The reused files' cached rows
|
|
213
|
+
# go in too, so the runner lists them in the full run's file order (#1524).
|
|
204
214
|
runner = build_runner(analyze_only: analyze_set, record_dependencies: true,
|
|
205
215
|
param_inferred_types: fresh_params,
|
|
206
|
-
restored_run_level_rows: @run_level_rows
|
|
207
|
-
|
|
216
|
+
restored_run_level_rows: @run_level_rows,
|
|
217
|
+
served_per_file_diagnostics: @cache.slice(*reused))
|
|
218
|
+
merged = run_runner(runner).diagnostics
|
|
208
219
|
@last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
|
|
209
|
-
reused = (current & previous) - affected.to_a
|
|
210
|
-
merged = fresh + reused.flat_map { |path| @cache[path] || [] }
|
|
211
220
|
absorb(runner, current, analyze_set, removed)
|
|
212
221
|
@param_table = fresh_params
|
|
213
222
|
Recheck.new(diagnostics: merged, changed: changed.to_set, added: added.to_set,
|
|
@@ -235,6 +244,7 @@ module Rigor
|
|
|
235
244
|
# stale. A full baseline is the sound answer for `--incremental`, but in editor mode it is also the
|
|
236
245
|
# latency this mode exists to avoid, so decline and let the caller drop to single-file scope.
|
|
237
246
|
break nil unless @plugin_fact_reusable.reusable_against?(restored.plugin_fact_digest)
|
|
247
|
+
break nil if source_rbs_unverified?(result)
|
|
238
248
|
|
|
239
249
|
result
|
|
240
250
|
end
|
|
@@ -253,7 +263,20 @@ module Rigor
|
|
|
253
263
|
# return exactly the way a body edit does, so it reuses the same audited dependents machinery. The
|
|
254
264
|
# pairs join AFTER the ADR-89 WD2 behavioural-stability pruning: that gate re-evaluates returns under
|
|
255
265
|
# the snapshot's OLD seeds, which is the wrong oracle for a pair whose seeds are the thing that moved.
|
|
266
|
+
#
|
|
267
|
+
# Issue #1536 (ADR-89 WD1 amendment) — every gate in {#gated_closure} reads the RUBY side of a file. What a
|
|
268
|
+
# source-RBS synthesizer derives from its comments reaches the environment as a `virtual:` buffer, and a
|
|
269
|
+
# reader of that RBS records no edge to the file it came from (an `attr_reader :name #: String` consumer
|
|
270
|
+
# records none at all), so no dependent set can bound who read it. An edit that moved it re-analyses the
|
|
271
|
+
# whole project — what a `sig/` edit already costs through the snapshot fingerprint.
|
|
256
272
|
def affected_closure(changed, added, removed, param_files = Set.new, param_pairs = Set.new)
|
|
273
|
+
return whole_project_closure(changed, added) if source_rbs_gate.moved?(@seed_bundles, changed, added, removed)
|
|
274
|
+
|
|
275
|
+
gated_closure(changed, added, removed, param_files, param_pairs)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# {#affected_closure} for an edit whose synthesized RBS is byte-identical in every file it touched.
|
|
279
|
+
def gated_closure(changed, added, removed, param_files, param_pairs)
|
|
257
280
|
scan = changed + added
|
|
258
281
|
# Parse the changed / added set ONCE for the per-symbol fingerprints, the class declarations, AND the
|
|
259
282
|
# ADR-89 WD1 declaration signatures. They were separate `discovered_def_index_for_paths` passes over
|
|
@@ -396,13 +419,11 @@ module Rigor
|
|
|
396
419
|
# dependents skippable) when its current {ScopeIndexer.declaration_signature} matches the one stored in
|
|
397
420
|
# the snapshot's seed bundle — i.e. its edit changed no method signature, visibility, ancestry, member
|
|
398
421
|
# layout, def line, or method existence, so every cross-file DECLARATION fact its dependents consume is
|
|
399
|
-
# unchanged.
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
# CODE, captured by ADR-88's plugin-fact fingerprint (WD3)
|
|
422
|
+
# unchanged. The signature reads code, not comments; the other half of stability — that no source-RBS
|
|
423
|
+
# synthesizer's output for the file moved — is settled before this runs ({SourceRbsGate#moved?}), so every
|
|
424
|
+
# file reaching here has a byte-identical synthesized contribution. Sorbet sigs / dry-types includes are
|
|
425
|
+
# CODE, captured by ADR-88's plugin-fact fingerprint (WD3).
|
|
403
426
|
def declaration_unstable(changed, declaration_signatures)
|
|
404
|
-
return changed if comment_ingesting_plugin_loaded?
|
|
405
|
-
|
|
406
427
|
changed.reject { |path| declaration_stable?(path, declaration_signatures[path]) }
|
|
407
428
|
end
|
|
408
429
|
|
|
@@ -415,14 +436,11 @@ module Rigor
|
|
|
415
436
|
bundle[:declaration_signature] == current_declaration_signature
|
|
416
437
|
end
|
|
417
438
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
@configuration.plugins.any? do |entry|
|
|
422
|
-
name = entry.is_a?(Hash) ? (entry["gem"] || entry["id"]) : entry
|
|
423
|
-
COMMENT_INGESTING_PLUGIN_IDS.include?(name.to_s)
|
|
424
|
-
end
|
|
439
|
+
# Every file this recheck can analyse: the removed ones drop out at `affected & current`.
|
|
440
|
+
def whole_project_closure(changed, added)
|
|
441
|
+
(@analyzed.to_set | changed | added).freeze
|
|
425
442
|
end
|
|
443
|
+
private :gated_closure, :whole_project_closure
|
|
426
444
|
|
|
427
445
|
# The current project file set (cheap directory expansion, no analysis), used to detect files added /
|
|
428
446
|
# removed since the last run. Also where an editor buffer's binding is re-spelled onto the set, since
|
|
@@ -441,13 +459,13 @@ module Rigor
|
|
|
441
459
|
# mutating session state. Returns the merged diagnostics.
|
|
442
460
|
def reanalyze_subset(subset)
|
|
443
461
|
affected = subset.to_set
|
|
462
|
+
reused = @analyzed - affected.to_a
|
|
444
463
|
# ADR-67 WD6c lift — seed the subset run from the baseline's own table so the verification engine
|
|
445
464
|
# exercises the exact seeds the served cache entries were computed under (and skips a re-collect).
|
|
446
465
|
runner = build_runner(analyze_only: affected, param_inferred_types: @param_table,
|
|
447
|
-
restored_run_level_rows: @run_level_rows
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
fresh + reused.flat_map { |path| @cache[path] || [] }
|
|
466
|
+
restored_run_level_rows: @run_level_rows,
|
|
467
|
+
served_per_file_diagnostics: @cache.slice(*reused))
|
|
468
|
+
run_runner(runner).diagnostics
|
|
451
469
|
end
|
|
452
470
|
|
|
453
471
|
# Cross-process incremental run (the `--incremental` flag's engine). With a disk `snapshot` whose
|
|
@@ -468,7 +486,7 @@ module Rigor
|
|
|
468
486
|
# shares one digest memo across change detection and the baseline/absorb re-pack. The inner
|
|
469
487
|
# `Runner#run` nests its own `with_run` for the analysis descriptors; nesting is safe (each restores).
|
|
470
488
|
Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
|
|
471
|
-
restored =
|
|
489
|
+
restored = load_snapshot(snapshot, fingerprint)
|
|
472
490
|
# ADR-88 WD1 — the plugin fact-surface fingerprint gates snapshot reuse the same way the global
|
|
473
491
|
# fingerprint gates the load: a plugin sig/catalog edit outside `signature_paths:` (a Sorbet `.rbi`)
|
|
474
492
|
# changes the types unchanged call sites resolve, without moving any analyzed file, so the global
|
|
@@ -489,7 +507,7 @@ module Rigor
|
|
|
489
507
|
result = recheck
|
|
490
508
|
adopt_plugin_fact_fingerprint
|
|
491
509
|
fact_reusable = @plugin_fact_reusable.reusable_against?(restored.plugin_fact_digest)
|
|
492
|
-
if fact_reusable && effects_reusable
|
|
510
|
+
if fact_reusable && effects_reusable && !source_rbs_unverified?(result)
|
|
493
511
|
diagnostics = result.diagnostics
|
|
494
512
|
warm = true
|
|
495
513
|
# ADR-87 WD3 — a warm recheck that changed nothing leaves the session state byte-equivalent to the
|
|
@@ -497,9 +515,10 @@ module Rigor
|
|
|
497
515
|
# A cold baseline always persists — there was no valid snapshot to reuse.
|
|
498
516
|
skip_save = result.no_change?
|
|
499
517
|
else
|
|
500
|
-
# The fact surface moved (a plugin sig/catalog edit), a plugin is opaque,
|
|
501
|
-
#
|
|
502
|
-
#
|
|
518
|
+
# The fact surface moved (a plugin sig/catalog edit), a plugin is opaque, the effects identity
|
|
519
|
+
# moved, or a synthesizer the source-RBS gate could not see was loaded (#1536): the cached-served
|
|
520
|
+
# files the recheck merged may be stale (or, for effects, a partial collection cannot be closed),
|
|
521
|
+
# so re-analyze the whole tree. The current fact-surface
|
|
503
522
|
# digest (from the recheck runner) is unchanged by the re-analysis, so it is kept for the save.
|
|
504
523
|
# Only a genuine fact-surface reason sets the reporting flag the CLI banner reads.
|
|
505
524
|
@fact_surface_invalidated = true unless fact_reusable
|
|
@@ -513,7 +532,7 @@ module Rigor
|
|
|
513
532
|
warm = false
|
|
514
533
|
skip_save = false
|
|
515
534
|
end
|
|
516
|
-
snapshot
|
|
535
|
+
persist_run(snapshot, fingerprint, diagnostics, skip_save: skip_save) if persist && fingerprint
|
|
517
536
|
[diagnostics, warm]
|
|
518
537
|
end
|
|
519
538
|
end
|
|
@@ -538,11 +557,41 @@ module Rigor
|
|
|
538
557
|
|
|
539
558
|
# ADR-88 WD1 — capture this invocation's fact-surface fingerprint (from the last analysis runner) onto the
|
|
540
559
|
# reporting ivars + the `@plugin_fact_reusable` decision object.
|
|
560
|
+
#
|
|
561
|
+
# Issue #1536 — the same prepared registry settles whether the synthesizers the session digested with
|
|
562
|
+
# are the ones the run's environment was built from ({SourceRbsGate#verify}). A plugin that builds its
|
|
563
|
+
# synthesizer in `#prepare` leaves the gate untrusted, and every stored digest is stamped unknown before
|
|
564
|
+
# the snapshot is saved.
|
|
541
565
|
def adopt_plugin_fact_fingerprint
|
|
542
|
-
|
|
566
|
+
registry = prepared_plugin_registry
|
|
567
|
+
fact = compute_plugin_fact_fingerprint(registry)
|
|
543
568
|
@plugin_fact_digest = fact.digest
|
|
544
569
|
@opaque_plugin_ids = fact.opaque_plugin_ids
|
|
545
570
|
@plugin_fact_reusable = fact
|
|
571
|
+
@seed_bundles = source_rbs_gate.verify(registry, @seed_bundles)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# Issue #1536 — a recheck of an edit whose closure left some file out may have under-read a synthesizer
|
|
575
|
+
# the gate could not see. {#run_incremental} re-analyses the project and {#run_buffer_recheck} declines,
|
|
576
|
+
# the way each treats a moved plugin fact surface. A run that changed nothing is exempt: under an
|
|
577
|
+
# untrusted gate every earlier edit already re-analysed the whole project, so the snapshot it serves is
|
|
578
|
+
# whole, and a null run stays one.
|
|
579
|
+
def source_rbs_unverified?(result)
|
|
580
|
+
source_rbs_gate.untrusted? && !result.no_change? && !result.reused.empty?
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Issue #1536 — a file the gate could not stamp because it was saved after the closure was decided (its
|
|
584
|
+
# readers' cached answers predate that save) must read as changed on the next run, where its unknown
|
|
585
|
+
# stamp sends the edit to the whole project. Recording its post-save content digest would hide it.
|
|
586
|
+
def forget_unbound_digests
|
|
587
|
+
source_rbs_gate.unbound.each { |path| @digests.delete(path) }
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# Issue #1536 — decides whether an edit moved synthesized RBS, and stamps each seed bundle's digest. One
|
|
591
|
+
# per session: its untrusted state must outlive every run the session makes.
|
|
592
|
+
def source_rbs_gate
|
|
593
|
+
@source_rbs_gate ||= SourceRbsGate.new(configuration: @configuration, cache_store: @cache_store,
|
|
594
|
+
plugin_requirer: @plugin_requirer)
|
|
546
595
|
end
|
|
547
596
|
|
|
548
597
|
# ADR-88 WD1 — the plugin fact-surface fingerprint for this invocation. For a SEQUENTIAL run it is read
|
|
@@ -551,17 +600,195 @@ module Rigor
|
|
|
551
600
|
# published facts — there it falls back to the always-sequential probe. Both paths compute the identical
|
|
552
601
|
# digest for a given fact surface, so the reuse decision is worker-count-independent (the parity spec
|
|
553
602
|
# asserts this).
|
|
554
|
-
def compute_plugin_fact_fingerprint
|
|
603
|
+
def compute_plugin_fact_fingerprint(registry = prepared_plugin_registry)
|
|
604
|
+
PluginFactFingerprint.from_registry(registry)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# The registry the run's environment was built from, after `#prepare`: the analysis runner's own on a
|
|
608
|
+
# sequential run, the always-sequential probe's on a pooled one (nil when the probe fails).
|
|
609
|
+
def prepared_plugin_registry
|
|
555
610
|
registry = @last_runner&.plugin_registry
|
|
556
|
-
if @workers.zero? && registry && !registry.empty?
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
611
|
+
return registry if @workers.zero? && registry && !registry.empty?
|
|
612
|
+
|
|
613
|
+
PluginFactFingerprint.prepared_registry(
|
|
614
|
+
configuration: @configuration, cache_store: @cache_store, plugin_requirer: @plugin_requirer
|
|
615
|
+
)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Loads the snapshot, noting (ADR-45 WD2) which write of the file it read: the identity is kept only when
|
|
619
|
+
# the file was the same before and after the read, since a rewrite in between leaves no telling which one
|
|
620
|
+
# the payload came from.
|
|
621
|
+
def load_snapshot(snapshot, fingerprint)
|
|
622
|
+
@slot_guard = start_slot_guard(fingerprint)
|
|
623
|
+
before = snapshot_identity(snapshot)
|
|
624
|
+
restored = fingerprint && snapshot.load(fingerprint: fingerprint)
|
|
625
|
+
@restored_snapshot_identity = restored && before == snapshot_identity(snapshot) ? before : nil
|
|
626
|
+
# The path set the previous run-result slot is keyed by, for a recheck to carry its chain from; a
|
|
627
|
+
# {#baseline} clears it, since a full run starts a chain of its own.
|
|
628
|
+
@slot_carried_from = restored&.analyzed
|
|
629
|
+
restored
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# ADR-87 WD3 — a warm recheck that changed nothing skips the snapshot rewrite: the file it restored is still
|
|
633
|
+
# the one on disk, which is what the run-result slot records as its snapshot.
|
|
634
|
+
def persist_run(snapshot, fingerprint, diagnostics, skip_save:)
|
|
635
|
+
written = if skip_save
|
|
636
|
+
@restored_snapshot_identity
|
|
637
|
+
elsif snapshot.save(fingerprint: fingerprint, payload: to_payload)
|
|
638
|
+
snapshot_identity(snapshot)
|
|
639
|
+
end
|
|
640
|
+
write_run_slot(diagnostics, written_identity: written)
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# ADR-45 WD2 (#1507) — records this run's answer as the run-result slot a later `rigor check --incremental`
|
|
644
|
+
# serves without loading the engine ({IncrementalRunSlot}). The slot's dependency descriptor must validate
|
|
645
|
+
# what the answer was computed from, and after a narrowed recheck most of the answer was computed by earlier
|
|
646
|
+
# runs, so it is assembled from three sources:
|
|
647
|
+
#
|
|
648
|
+
# - the analysed files, at the digest this session holds for each: the bytes its cached or fresh rows were
|
|
649
|
+
# computed from, never a re-digest taken after the fact;
|
|
650
|
+
# - what this run read and re-derives every run ({Runner#incremental_slot_rows}' `observed` and `derived`
|
|
651
|
+
# rows);
|
|
652
|
+
# - the CHAIN: the rows the last full run recorded for inputs a recheck does not re-derive
|
|
653
|
+
# ({Runner#baseline_dependency_rows}), and the plugin reads credited to each file's analysis. A full run
|
|
654
|
+
# starts a chain; a recheck takes the previous slot's, replaces the reads of every file it re-analysed with
|
|
655
|
+
# this run's, and drops the removed files'.
|
|
656
|
+
#
|
|
657
|
+
# A recheck writes only when the previous slot exists and was written against the very snapshot file it
|
|
658
|
+
# restored: otherwise some run in between — a pool run, `--no-cache`, a write that failed — analysed files
|
|
659
|
+
# without recording their reads, and the chain cannot vouch for them. It then writes nothing, and the next
|
|
660
|
+
# full run starts a fresh chain. Declining to write is always safe; it only leaves the next null run on the
|
|
661
|
+
# full path.
|
|
662
|
+
#
|
|
663
|
+
# Every row not recorded as a plugin read it must still describe the tree the run read
|
|
664
|
+
# ({IncrementalRunSlot::WriteGuard}): the analysed files' (whose session digest a full run takes after its
|
|
665
|
+
# analysis), the `derived` rows, and the chain's baseline, taken now or carried ({#guarded_rows}).
|
|
666
|
+
#
|
|
667
|
+
# @param written_identity — the snapshot file as this run leaves it, recorded for the next writer's check.
|
|
668
|
+
def write_run_slot(diagnostics, written_identity:)
|
|
669
|
+
return unless run_slot_writable?
|
|
670
|
+
|
|
671
|
+
roots = @paths || @configuration.paths
|
|
672
|
+
rows = @last_runner.incremental_slot_rows(roots: roots)
|
|
673
|
+
chain = rows && (@slot_carried_from ? carry_chain(rows, roots) : start_chain(rows))
|
|
674
|
+
analysed = chain && analysed_file_rows
|
|
675
|
+
return if analysed.nil?
|
|
676
|
+
|
|
677
|
+
baseline, pinned, reads = chain
|
|
678
|
+
return unless @slot_guard&.admits?(guarded_rows(analysed, rows.derived, baseline), pinned: pinned)
|
|
679
|
+
|
|
680
|
+
# A save the run read after the mark's tick must not hide behind an unmoved stat tuple.
|
|
681
|
+
reads = reads.transform_values { |descriptor| @slot_guard.mark_racy(descriptor) }
|
|
682
|
+
|
|
683
|
+
entry = IncrementalRunSlot::Entry.new(
|
|
684
|
+
diagnostics: diagnostics, roots: IncrementalRunSlot.as_written(roots),
|
|
685
|
+
baseline: baseline, pinned: pinned, reads: reads, snapshot: written_identity
|
|
686
|
+
)
|
|
687
|
+
dependencies = @slot_guard.mark_racy(run_slot_dependencies(analysed, rows, [baseline, pinned], reads.values))
|
|
688
|
+
wrote = IncrementalRunSlot.write(
|
|
689
|
+
store: @cache_store, configuration: @configuration, entry: entry, dependencies: dependencies,
|
|
690
|
+
target: IncrementalRunSlot::Target.new(files: @analyzed, roots: roots)
|
|
691
|
+
)
|
|
692
|
+
discard_previous_slot(roots) if wrote
|
|
693
|
+
rescue StandardError
|
|
694
|
+
nil
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# The previous slot is keyed by the previous path set; once this run's stands, nothing asks for it again.
|
|
698
|
+
def discard_previous_slot(roots)
|
|
699
|
+
return if @slot_carried_from.nil? || @slot_carried_from.sort == @analyzed.sort
|
|
700
|
+
|
|
701
|
+
IncrementalRunSlot.discard(
|
|
702
|
+
store: @cache_store, configuration: @configuration,
|
|
703
|
+
target: IncrementalRunSlot::Target.new(files: @slot_carried_from, roots: roots)
|
|
704
|
+
)
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# ADR-45 WD2 — the mark {IncrementalRunSlot::WriteGuard#admits?} checks the slot's rows against, taken before
|
|
708
|
+
# the run reads anything. nil (no slot this run) for a store no slot is written to, or no fingerprint.
|
|
709
|
+
def start_slot_guard(fingerprint)
|
|
710
|
+
return nil if fingerprint.nil? || @cache_store.nil? || @cache_store.read_only? || !@buffer.nil?
|
|
711
|
+
# Known before the run: none of these can be written afterwards, so the stamp and the recomputed
|
|
712
|
+
# fingerprint would be spent for nothing.
|
|
713
|
+
return nil unless @workers.zero? && !@configuration.effects_enabled?
|
|
714
|
+
|
|
715
|
+
IncrementalRunSlot::WriteGuard.start(
|
|
716
|
+
configuration: @configuration, roots: @paths || @configuration.paths, cache_root: @cache_store.root,
|
|
717
|
+
fingerprint: fingerprint
|
|
718
|
+
)
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# The runs whose answer the slot may hold: a writable store, no editor buffer (whose bytes exist only in
|
|
722
|
+
# the editor), a sequential run (a pool worker's plugin reads never reach this process), effect collection
|
|
723
|
+
# off ({IncrementalRunSlot.serve} declines it), and no opaque plugin — the next full-path run would then be
|
|
724
|
+
# cold, not the warm run the probe stands in for.
|
|
725
|
+
def run_slot_writable?
|
|
726
|
+
!@cache_store.nil? && !@cache_store.read_only? && @buffer.nil? && @workers.zero? &&
|
|
727
|
+
!@configuration.effects_enabled? && @opaque_plugin_ids.empty? && !@last_runner.nil?
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def start_chain(rows)
|
|
731
|
+
baseline = @last_runner.baseline_dependency_rows(files: @analyzed)
|
|
732
|
+
baseline && [baseline.owned, baseline.pinned, rows.by_file.slice(*@analyzed)]
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def carry_chain(rows, roots)
|
|
736
|
+
return nil if @restored_snapshot_identity.nil?
|
|
737
|
+
|
|
738
|
+
previous = IncrementalRunSlot.previous_entry(
|
|
739
|
+
store: @cache_store, configuration: @configuration,
|
|
740
|
+
target: IncrementalRunSlot::Target.new(files: @slot_carried_from, roots: roots)
|
|
741
|
+
)
|
|
742
|
+
return nil if previous.nil? || previous.snapshot != @restored_snapshot_identity
|
|
743
|
+
|
|
744
|
+
served = previous.reads.except(*@last_runner.analyzed_files)
|
|
745
|
+
[previous.baseline, previous.pinned, served.merge(rows.by_file).slice(*@analyzed)]
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# One `:stat` row per analysed file, from the entry this session recorded for it (`#pack_digest`), re-packed
|
|
749
|
+
# against the file's current stat when the bytes still hash to the recorded digest but the tuple moved (a
|
|
750
|
+
# `touch`, a checkout): the snapshot keeps the old tuple, and a row carrying it would cost every later probe
|
|
751
|
+
# a re-hash of the file. nil when any file has no usable entry (it could not be read): no row could say what
|
|
752
|
+
# its answer was computed from.
|
|
753
|
+
def analysed_file_rows
|
|
754
|
+
@analyzed.map do |path|
|
|
755
|
+
packed = Cache::FileDigest.refresh_stat(path, @digests[path])
|
|
756
|
+
return nil if packed.nil?
|
|
757
|
+
|
|
758
|
+
Cache::Descriptor::FileEntry.new(path: path, comparator: :stat, value: packed)
|
|
562
759
|
end
|
|
563
760
|
end
|
|
564
761
|
|
|
762
|
+
# @param chained — the chain's baseline descriptors, owned and pinned
|
|
763
|
+
def run_slot_dependencies(analysed, rows, chained, reads)
|
|
764
|
+
carried = chained + reads
|
|
765
|
+
Cache::Descriptor.new(
|
|
766
|
+
files: analysed + rows.derived.files + rows.observed.files + carried.flat_map(&:files),
|
|
767
|
+
globs: (rows.derived.globs + rows.observed.globs + carried.flat_map(&:globs)).uniq
|
|
768
|
+
)
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
# What the write guard asks about besides the pinned baseline: every row the key does not pin that was not
|
|
772
|
+
# recorded as a plugin read it. A recheck's carried baseline is among them. The run that took it vouched for
|
|
773
|
+
# the tree it saw, but this run reads those inputs again (a recheck with a non-empty closure rebuilds its
|
|
774
|
+
# environment from the signature tree), and a save it read that is reverted before the run ends leaves every
|
|
775
|
+
# carried row fresh while the answer came from the saved bytes.
|
|
776
|
+
def guarded_rows(analysed, derived, baseline)
|
|
777
|
+
Cache::Descriptor.new(files: analysed + derived.files + baseline.files, globs: derived.globs + baseline.globs)
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
# The snapshot file's `(size, mtime_ns, ctime_ns, inode)`: which write of it a run restored, or left behind.
|
|
781
|
+
# A rewrite renames a new file into place, so any write moves it. nil when there is no file to stat.
|
|
782
|
+
def snapshot_identity(snapshot)
|
|
783
|
+
path = snapshot.respond_to?(:path) ? snapshot.path : nil
|
|
784
|
+
return nil if path.nil?
|
|
785
|
+
|
|
786
|
+
stat = File.stat(path)
|
|
787
|
+
[stat.size, Cache::FileDigest.ns_of(stat.mtime), Cache::FileDigest.ns_of(stat.ctime), stat.ino]
|
|
788
|
+
rescue SystemCallError
|
|
789
|
+
nil
|
|
790
|
+
end
|
|
791
|
+
|
|
565
792
|
# Adopt a persisted snapshot's per-file state as this session's baseline (the warm-start path).
|
|
566
793
|
def restore(payload)
|
|
567
794
|
@analyzed = payload.analyzed
|
|
@@ -723,13 +950,16 @@ module Rigor
|
|
|
723
950
|
removed.each { |path| forget(path) }
|
|
724
951
|
@analyzed = current
|
|
725
952
|
# ADR-85 WD2 — the recheck's discovery folded the restored bundles and refreshed them (changed files
|
|
726
|
-
# re-walked, removed files dropped, added files built), so adopt the runner's current set wholesale
|
|
727
|
-
|
|
953
|
+
# re-walked, removed files dropped, added files built), so adopt the runner's current set wholesale,
|
|
954
|
+
# stamping the re-walked ones with their source-RBS digest (issue #1536).
|
|
955
|
+
whole_project = current.all? { |path| analyze_set.include?(path) }
|
|
956
|
+
@seed_bundles = source_rbs_gate.stamp(runner.seed_bundles, whole_project: whole_project)
|
|
728
957
|
fresh_by_file = per_file(runner.per_file_diagnostics)
|
|
729
958
|
analyze_set.each do |path|
|
|
730
959
|
@cache[path] = fresh_by_file[path] || []
|
|
731
960
|
@digests[path] = pack_digest(path)
|
|
732
961
|
end
|
|
962
|
+
forget_unbound_digests
|
|
733
963
|
absorb_dependency_graph(runner)
|
|
734
964
|
# Issues #796 / #794 — the recheck's own snapshots already carry the replayed rows folded together
|
|
735
965
|
# with anything its closure demanded for itself, so this is the same union the next run replays.
|
|
@@ -860,12 +1090,24 @@ module Rigor
|
|
|
860
1090
|
changed + removed, @constant_decls, new_constant_decls
|
|
861
1091
|
)
|
|
862
1092
|
keys = appeared_methods.map { |symbol| negative_key_for(symbol) }
|
|
863
|
-
keys.concat(moved_classes
|
|
1093
|
+
keys.concat(class_move_keys(moved_classes))
|
|
864
1094
|
keys.concat(moved_constants.map { |name| "constant:#{name.split('::').last}" })
|
|
865
1095
|
Incremental.negative_closure(keys, @negative_dependents) | refinement_affected(changed + removed, summary) |
|
|
866
1096
|
global_write_reporters
|
|
867
1097
|
end
|
|
868
1098
|
|
|
1099
|
+
# The negative keys a class-declaration diff fires: `class:<last segment>` for a class name, and (issue #1820)
|
|
1100
|
+
# `subclass_of:<last segment of the superclass>` for a `[class, superclass]` pair appearing or vanishing. Any
|
|
1101
|
+
# class declaration moving may move the set of classes whose superclass the tables do not know, which
|
|
1102
|
+
# `subclass_of:*` stands for.
|
|
1103
|
+
def class_move_keys(moved)
|
|
1104
|
+
names, edges = moved.partition { |entry| entry.is_a?(String) }
|
|
1105
|
+
keys = names.map { |klass| "class:#{klass.split('::').last}" }
|
|
1106
|
+
keys.concat(edges.map { |(_child, parent)| "subclass_of:#{parent.split('::').last}" })
|
|
1107
|
+
keys << "subclass_of:*" unless moved.empty?
|
|
1108
|
+
keys
|
|
1109
|
+
end
|
|
1110
|
+
|
|
869
1111
|
# Issue #1120 — the consumers whose `call.undefined-method` answer read the refinement table for a method
|
|
870
1112
|
# name whose refinements moved in this edit. The before-state is each file's seed bundle (the per-file
|
|
871
1113
|
# table the last run recorded), the after-state the scan's per-file tables. A refine body is not a class's
|
|
@@ -888,6 +1130,7 @@ module Rigor
|
|
|
888
1130
|
index[:class_sources].each do |class_name, files|
|
|
889
1131
|
files.each { |file| result[file] << class_name }
|
|
890
1132
|
end
|
|
1133
|
+
Incremental.add_superclass_edges(result, index[:class_sources], index[:superclasses])
|
|
891
1134
|
result.transform_values(&:freeze).freeze
|
|
892
1135
|
end
|
|
893
1136
|
|
|
@@ -1017,12 +1260,6 @@ module Rigor
|
|
|
1017
1260
|
TOP_LEVEL_KEY = Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
1018
1261
|
private_constant :TOP_LEVEL_KEY
|
|
1019
1262
|
|
|
1020
|
-
# B1 — plugin require-names that ingest COMMENT content as semantic input (inline-RBS reads `# @rbs` /
|
|
1021
|
-
# `#:` annotations). B1's code fingerprint ignores comments, so a project configuring one of these opts
|
|
1022
|
-
# OUT of the bundle-equality skip (a comment edit could change a cross-file type it contributes).
|
|
1023
|
-
COMMENT_INGESTING_PLUGIN_IDS = %w[rigor-rbs-inline].freeze
|
|
1024
|
-
private_constant :COMMENT_INGESTING_PLUGIN_IDS
|
|
1025
|
-
|
|
1026
1263
|
def negative_key_for(symbol)
|
|
1027
1264
|
class_name, method = symbol.split("#", 2)
|
|
1028
1265
|
class_name == TOP_LEVEL_KEY ? "toplevel:#{method}" : "method:#{symbol}"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
4
|
require_relative "runner"
|
|
5
|
+
require_relative "../cache/value_digest"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Analysis
|
|
@@ -98,9 +99,9 @@ module Rigor
|
|
|
98
99
|
def digest_registry(registry)
|
|
99
100
|
return Result.new(digest: nil, opaque_plugin_ids: [].freeze) if registry.nil? || registry.empty?
|
|
100
101
|
|
|
101
|
-
facts_by_plugin = facts_by_plugin(registry.plugins.first&.services&.fact_store)
|
|
102
|
-
parts = fact_parts(facts_by_plugin)
|
|
103
102
|
opaque = []
|
|
103
|
+
facts_by_plugin = facts_by_plugin(registry, opaque)
|
|
104
|
+
parts = fact_parts(facts_by_plugin)
|
|
104
105
|
registry.plugins.each { |plugin| collect_plugin_parts(plugin, facts_by_plugin, parts, opaque) }
|
|
105
106
|
# Sort the parts so the digest is independent of plugin registration / iteration order.
|
|
106
107
|
Result.new(
|
|
@@ -129,16 +130,25 @@ module Rigor
|
|
|
129
130
|
|
|
130
131
|
# `{ plugin_id => ["name=digest", ...] }` for every ADR-9 published fact (channel a). A plugin that
|
|
131
132
|
# published nothing does not appear.
|
|
132
|
-
|
|
133
|
+
#
|
|
134
|
+
# A fact the fingerprint cannot digest is an input it cannot see, so its publisher is opaque, whether or
|
|
135
|
+
# not the publisher contributes a type itself: other plugins read its facts. A fact store that cannot be
|
|
136
|
+
# read at all makes every plugin opaque. Both force the full run; dropping the facts instead would let a
|
|
137
|
+
# plugin with another surface reuse a snapshot its facts no longer match.
|
|
138
|
+
def facts_by_plugin(registry, opaque)
|
|
133
139
|
result = Hash.new { |hash, key| hash[key] = [] }
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
fact_store = registry.plugins.first&.services&.fact_store
|
|
141
|
+
fact_store&.each_fact do |fact|
|
|
142
|
+
plugin_id = fact.plugin_id.to_s
|
|
143
|
+
begin
|
|
144
|
+
result[plugin_id] << "#{fact.name}=#{digest_value(fact.value)}"
|
|
145
|
+
rescue StandardError
|
|
146
|
+
opaque << plugin_id
|
|
147
|
+
end
|
|
138
148
|
end
|
|
139
149
|
result
|
|
140
150
|
rescue StandardError
|
|
141
|
-
|
|
151
|
+
registry.plugins.each { |plugin| opaque << safe_id(plugin) }
|
|
142
152
|
Hash.new { |hash, key| hash[key] = [] }
|
|
143
153
|
end
|
|
144
154
|
|
|
@@ -175,12 +185,13 @@ module Rigor
|
|
|
175
185
|
true
|
|
176
186
|
end
|
|
177
187
|
|
|
178
|
-
# A
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
188
|
+
# A digest of a fact / producer / hook value that depends on the value alone (issue #1574). A producer's
|
|
189
|
+
# value is computed on one run and served from its ADR-45 cache entry on the next; the two are equal but
|
|
190
|
+
# do not share the same objects, so a digest of their `Marshal.dump` bytes differed and invalidated the
|
|
191
|
+
# snapshot on every such switch. {Cache::ValueDigest} reads no identity. A value it cannot digest raises
|
|
192
|
+
# to the caller, which marks the plugin opaque (never a silent wrong-value digest).
|
|
182
193
|
def digest_value(value)
|
|
183
|
-
|
|
194
|
+
Cache::ValueDigest.hexdigest(value)
|
|
184
195
|
end
|
|
185
196
|
|
|
186
197
|
def contributes_types?(plugin)
|