rigortype 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/data/builtins/ruby_core/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +442 -11
- data/docs/handbook/02-everyday-types.md +1 -1
- data/docs/handbook/04-tuples-and-shapes.md +1 -1
- data/docs/handbook/08-understanding-errors.md +1 -1
- data/docs/handbook/09-plugins.md +2 -2
- data/docs/handbook/10-sorbet.md +1 -1
- data/docs/handbook/README.md +2 -2
- data/docs/handbook/appendix-go.md +1 -1
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +1 -1
- data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +2 -2
- data/docs/handbook/appendix-typescript.md +4 -4
- data/docs/manual/02-cli-reference.md +29 -16
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +12 -0
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/08-skills.md +1 -1
- data/docs/manual/11-ci.md +1 -1
- data/docs/manual/12-caching.md +1 -1
- data/docs/manual/15-type-protection-coverage.md +1 -1
- data/docs/manual/18-removing-dead-code.md +13 -4
- data/docs/manual/19-effect-labels.md +19 -12
- data/docs/manual/README.md +2 -2
- data/docs/manual/plugins/rigor-actionmailer.md +4 -4
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activejob.md +3 -3
- data/docs/manual/plugins/rigor-activerecord.md +78 -5
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
- data/docs/manual/plugins/rigor-rspec.md +6 -2
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +116 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/diagnostic.rb +17 -6
- data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +44 -9
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +44 -16
- data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
- data/lib/rigor/analysis/reachability/scan.rb +27 -7
- data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +53 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +14 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
- data/lib/rigor/analysis/runner.rb +298 -38
- data/lib/rigor/analysis/worker_session.rb +10 -1
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/file_digest.rb +20 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
- data/lib/rigor/cache/store.rb +10 -1
- data/lib/rigor/cli/check_command.rb +67 -48
- data/lib/rigor/cli/coverage_command.rb +28 -24
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/doc_links.rb +100 -0
- data/lib/rigor/cli/docs_command.rb +32 -2
- data/lib/rigor/cli/effects_command.rb +27 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +82 -12
- data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +52 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +18 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli/unused_command.rb +65 -16
- data/lib/rigor/cli.rb +22 -9
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/definition_lines.rb +100 -0
- data/lib/rigor/effects/envelope_check.rb +18 -1
- data/lib/rigor/effects/liskov_check.rb +17 -8
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/effects/signature_sources.rb +13 -2
- data/lib/rigor/effects/snapshot.rb +53 -21
- data/lib/rigor/effects/snapshot_diff.rb +26 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +525 -46
- data/lib/rigor/environment.rb +38 -15
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +46 -0
- data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +254 -5
- data/lib/rigor/sig_gen/renderer.rb +1 -6
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +3 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- data/skills/rigor-ci-setup/SKILL.md +2 -2
- data/skills/rigor-editor-setup/SKILL.md +2 -2
- data/skills/rigor-mcp-setup/SKILL.md +2 -2
- data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- metadata +73 -3
|
@@ -120,23 +120,65 @@ module Rigor
|
|
|
120
120
|
Effects::FileCollection.merge_all(effect_collections)
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# The run's as-written superclass table — what `effect.liskov-widened` reads. Served straight from
|
|
124
|
+
# the summary entry on a warm hit (#482), where merging every collection to recover one of its
|
|
125
|
+
# tables would be the whole cost the split removes.
|
|
126
|
+
def effect_ancestry
|
|
127
|
+
@cached_effect_ancestry || effect_collection.superclasses
|
|
128
|
+
end
|
|
129
|
+
|
|
123
130
|
# Issue #382 — the same collections keyed by the path that produced each, which is the form the two
|
|
124
131
|
# caches persist: `{ "lib/a.rb" => FileCollection, … }`. The merged {#effect_collection} cannot be
|
|
125
132
|
# persisted per file (merging is where the path is deliberately dropped), and per file is what an
|
|
126
133
|
# ADR-46 recheck needs — it re-collects the changed closure and serves the rest from the snapshot.
|
|
127
134
|
def effect_collections_by_path
|
|
128
135
|
pooled = @pool_coordinator.collected_effects
|
|
129
|
-
|
|
136
|
+
base = forced_file_effects
|
|
137
|
+
pooled.empty? ? base.dup : base.merge(pooled)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# #482 — the per-file collections of a run served from the summary entry, loaded on the first
|
|
141
|
+
# question that genuinely needs them. A served run adopts the table, the sources and the ancestry
|
|
142
|
+
# from the small entry and never touches the collections blob; this is the escape hatch that makes
|
|
143
|
+
# that split safe, because a consumer the split did not anticipate loads the blob here instead of
|
|
144
|
+
# silently reading an empty table. Nil loader (an analyzing run) is the identity.
|
|
145
|
+
def forced_file_effects
|
|
146
|
+
loader = @cached_collections_loader
|
|
147
|
+
return @file_effects if loader.nil?
|
|
148
|
+
|
|
149
|
+
@cached_collections_loader = nil
|
|
150
|
+
loaded = loader.call
|
|
151
|
+
@file_effects = loaded if loaded.is_a?(Hash)
|
|
152
|
+
@file_effects
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Issue #382 — adopt persisted collections as this run's own. The warm-hit half of the whole-run
|
|
156
|
+
# effects slot: the analysis did not run, so `#assemble_run_diagnostics` never reached
|
|
157
|
+
# {#close_effect_graph}. The slot stores the propagated table BESIDE the collections — the whole-run
|
|
158
|
+
# entry is already invalidated by every analyzed file (the same post-run dependency descriptor as
|
|
159
|
+
# the diagnostics slot) and by every meaning input (the effects identity), which is exactly the
|
|
160
|
+
# invalidation a stored closure needs — so a warm hit adopts both and re-runs nothing. An entry
|
|
161
|
+
# from before the table rode along reads as a miss (`#peek_effect_collections`' shape guard), and
|
|
162
|
+
# the ADR-46 incremental snapshot still persists per-file collections only: a recheck re-collects a
|
|
163
|
+
# partial closure, which is the case a stored table genuinely cannot serve.
|
|
164
|
+
def adopt_effect_collections(collections, table: nil)
|
|
165
|
+
@file_effects = collections
|
|
166
|
+
if table
|
|
167
|
+
@effect_table = table
|
|
168
|
+
else
|
|
169
|
+
close_effect_graph
|
|
170
|
+
end
|
|
130
171
|
end
|
|
131
172
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@
|
|
139
|
-
|
|
173
|
+
# #482 — the warm-hit half after the split: everything a served run reads, with the collections left
|
|
174
|
+
# behind a loader ({#forced_file_effects}). `sources` and `ancestry` are the two derived tables a
|
|
175
|
+
# served run would otherwise have to merge the whole collection set to recover.
|
|
176
|
+
def adopt_effect_summary(table:, sources:, ancestry:, collections_loader:)
|
|
177
|
+
@effect_table = table
|
|
178
|
+
@cached_effect_sources = sources
|
|
179
|
+
@cached_effect_ancestry = ancestry
|
|
180
|
+
@cached_collections_loader = collections_loader
|
|
181
|
+
@file_effects = {}
|
|
140
182
|
end
|
|
141
183
|
|
|
142
184
|
# Issue #382 — whether this run's effect collections came from the whole-run effects slot rather than
|
|
@@ -154,6 +196,8 @@ module Rigor
|
|
|
154
196
|
# its entry points are named by *file* globs (`effects.snapshot.reach:`, the `unused --entry-point`
|
|
155
197
|
# syntax), so the key has to be traced back to the file it was written in.
|
|
156
198
|
def effect_sources
|
|
199
|
+
return @cached_effect_sources if @cached_effect_sources
|
|
200
|
+
|
|
157
201
|
effect_collections.each_with_object({}) do |collection, out|
|
|
158
202
|
path = collection.path
|
|
159
203
|
next if path.nil?
|
|
@@ -248,6 +292,11 @@ module Rigor
|
|
|
248
292
|
@file_effects = {}
|
|
249
293
|
@effect_table = nil
|
|
250
294
|
@effects_served_from_cache = false
|
|
295
|
+
# #482 — set only by a run served from the summary entry: the two derived tables it carries, and
|
|
296
|
+
# the loader that fetches the collections blob if anything still asks for per-file form.
|
|
297
|
+
@cached_effect_sources = nil
|
|
298
|
+
@cached_effect_ancestry = nil
|
|
299
|
+
@cached_collections_loader = nil
|
|
251
300
|
@run_dependency_descriptor = nil
|
|
252
301
|
# Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
|
|
253
302
|
# See `self_undefined_rule_active?`.
|
|
@@ -295,16 +344,31 @@ module Rigor
|
|
|
295
344
|
@cached_plugin_prepare_diagnostics = [].freeze
|
|
296
345
|
@project_discovered_classes = {}.freeze
|
|
297
346
|
@project_discovered_def_nodes = {}.freeze
|
|
347
|
+
# Issue #681 — the `Module.nesting` recorded per declared `def`, keyed by node identity.
|
|
348
|
+
@project_discovered_def_nestings = {}.compare_by_identity.freeze
|
|
298
349
|
@project_discovered_singleton_def_nodes = {}.freeze
|
|
299
350
|
@project_discovered_def_sources = {}.freeze
|
|
300
351
|
@project_discovered_singleton_def_sources = {}.freeze
|
|
301
352
|
@project_discovered_superclasses = {}.freeze
|
|
353
|
+
# Issue #682 — the `Module.nesting` each class / module declaration HEADER is written in.
|
|
354
|
+
@project_discovered_header_nestings = {}.freeze
|
|
302
355
|
@project_discovered_includes = {}.freeze
|
|
303
356
|
@project_discovered_class_sources = {}.freeze
|
|
357
|
+
# Issue #644 — the cross-file VALUE-constant publication table (`{qualified name => Type::Constant}`,
|
|
358
|
+
# literal writes only) and its per-name write attribution. The first seeds `in_source_constants` on
|
|
359
|
+
# every per-file scope so `FOO = :sym` in one file reads as `:sym` in another; the second seeds
|
|
360
|
+
# `constant_sources` only on an ADR-46 recording run, where it is the positive edge's attribution.
|
|
361
|
+
@project_constant_values = {}.freeze
|
|
362
|
+
@project_constant_sources = {}.freeze
|
|
363
|
+
# ...and the raw per-(name, path) publication census the incremental producer diffs.
|
|
364
|
+
@project_constant_writes = {}.freeze
|
|
365
|
+
@published_constant_name_set = nil
|
|
304
366
|
@project_discovered_method_visibilities = {}.freeze
|
|
305
367
|
@project_discovered_methods = {}.freeze
|
|
306
368
|
@project_data_member_layouts = {}.freeze
|
|
307
369
|
@project_struct_member_layouts = {}.freeze
|
|
370
|
+
# Issue #684 — set per run by {#project_discovery_expansion}; nil on every path that does not widen.
|
|
371
|
+
@project_discovery_expansion = nil
|
|
308
372
|
# ADR-67 WD6a — the call-site parameter-inference table, populated by the opt-in pre-pass in
|
|
309
373
|
# `assemble_run_diagnostics` when `parameter_inference:` is enabled, then seeded onto every per-file
|
|
310
374
|
# scope (sequential + fork-worker) through `project_scope_seed_tables`. Empty by default, so the gate-off
|
|
@@ -355,6 +419,7 @@ module Rigor
|
|
|
355
419
|
return Result.new(diagnostics: [target_ruby_error]) if target_ruby_error
|
|
356
420
|
|
|
357
421
|
expansion = expand_paths(paths)
|
|
422
|
+
@project_discovery_expansion = project_discovery_expansion(paths, expansion)
|
|
358
423
|
@snapshots.reset_for_run
|
|
359
424
|
# Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
|
|
360
425
|
@project_discovery_done = false
|
|
@@ -481,6 +546,22 @@ module Rigor
|
|
|
481
546
|
result.transform_values(&:freeze).freeze
|
|
482
547
|
end
|
|
483
548
|
|
|
549
|
+
# Issue #644 — per-file PUBLICATION CENSUS: `{path => {qualified constant name => descriptor}}`, the
|
|
550
|
+
# value-constant twin of {#class_declarations}. The descriptor (`[literal]` or
|
|
551
|
+
# `ScopeIndexer::CONSTANT_UNPUBLISHABLE`) is load-bearing and a bare name set is not: a name's published
|
|
552
|
+
# answer is a function of the WHOLE project's write set for it, so what a recheck has to detect is a
|
|
553
|
+
# change in the census — a value edit, a write becoming unpublishable, a second declarer appearing or
|
|
554
|
+
# going away — not merely a name appearing.
|
|
555
|
+
# {Incremental.changed_constant_publications} diffs it; the reader's `constant:` edge is recorded on
|
|
556
|
+
# every reference, resolved or not, so the diff reaches it either way.
|
|
557
|
+
def constant_declarations
|
|
558
|
+
result = Hash.new { |hash, key| hash[key] = {} }
|
|
559
|
+
@project_constant_writes.each do |name, by_path|
|
|
560
|
+
by_path.each { |path, descriptor| result[path][name] = descriptor }
|
|
561
|
+
end
|
|
562
|
+
result.transform_values(&:freeze).freeze
|
|
563
|
+
end
|
|
564
|
+
|
|
484
565
|
# ADR-67 WD6c lift — the inferred-param table this run seeded from (frozen; empty when
|
|
485
566
|
# `parameter_inference:` is off or the pre-pass failed soft). The incremental session reads it back
|
|
486
567
|
# after a baseline so the snapshot records the seeds the cached diagnostics were computed under.
|
|
@@ -588,6 +669,8 @@ module Rigor
|
|
|
588
669
|
def evaluate_return_types_setup(paths, specs)
|
|
589
670
|
expansion = expand_paths(paths || @configuration.paths)
|
|
590
671
|
@project_discovery_done = false
|
|
672
|
+
# This entry point supplies its own expansion; it never widens (see {#project_discovery_expansion}).
|
|
673
|
+
@project_discovery_expansion = nil
|
|
591
674
|
@run_generation = Object.new.freeze
|
|
592
675
|
run_project_pre_passes(expansion: expansion)
|
|
593
676
|
ensure_project_discovery(expansion)
|
|
@@ -674,9 +757,7 @@ module Rigor
|
|
|
674
757
|
return nil unless @record_effects
|
|
675
758
|
|
|
676
759
|
descriptor = effects_key_descriptor(key_descriptor)
|
|
677
|
-
|
|
678
|
-
if cached
|
|
679
|
-
adopt_effect_collections(cached)
|
|
760
|
+
if descriptor && served_from_effect_entries?(descriptor)
|
|
680
761
|
@effects_served_from_cache = true
|
|
681
762
|
return nil
|
|
682
763
|
end
|
|
@@ -686,6 +767,32 @@ module Rigor
|
|
|
686
767
|
analysis
|
|
687
768
|
end
|
|
688
769
|
|
|
770
|
+
# The two warm lanes, in cost order (#482).
|
|
771
|
+
#
|
|
772
|
+
# The summary entry is what a warm run wants: the table and the two derived tables, and no
|
|
773
|
+
# collections blob. Its absence is not a miss on its own — a run whose propagation fail-softed
|
|
774
|
+
# deliberately stores no table (see {#storable_effect_table}), and an entry written before the
|
|
775
|
+
# split has only the collections — so the second lane adopts the collections and re-runs the
|
|
776
|
+
# fixpoint over them, which is the pre-split behaviour and the retry the fail-soft posture wants.
|
|
777
|
+
# Only when neither entry answers does the run re-analyse.
|
|
778
|
+
#
|
|
779
|
+
# @return [Boolean] whether this run was served.
|
|
780
|
+
def served_from_effect_entries?(descriptor)
|
|
781
|
+
summary = peek_effect_summary(descriptor)
|
|
782
|
+
if summary
|
|
783
|
+
table, sources, ancestry = summary
|
|
784
|
+
adopt_effect_summary(table: table, sources: sources, ancestry: ancestry,
|
|
785
|
+
collections_loader: -> { peek_effect_collections(descriptor) || {} })
|
|
786
|
+
return true
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
collections = peek_effect_collections(descriptor)
|
|
790
|
+
return false if collections.nil?
|
|
791
|
+
|
|
792
|
+
adopt_effect_collections(collections)
|
|
793
|
+
true
|
|
794
|
+
end
|
|
795
|
+
|
|
689
796
|
def effects_key_descriptor(key_descriptor)
|
|
690
797
|
Effects::Identity.descriptor(base: key_descriptor, configuration: @configuration,
|
|
691
798
|
plugin_facts: effect_plugin_facts)
|
|
@@ -693,42 +800,92 @@ module Rigor
|
|
|
693
800
|
nil
|
|
694
801
|
end
|
|
695
802
|
|
|
696
|
-
# The read half. A miss, a stale dependency, a corrupt entry and a
|
|
697
|
-
# are one answer — nil, "collect it again" — because none of them
|
|
698
|
-
# and all of them have the same remedy.
|
|
803
|
+
# The read half of the serving entry (#482). A miss, a stale dependency, a corrupt entry and a
|
|
804
|
+
# stored value of the wrong shape are one answer — nil, "collect it again" — because none of them
|
|
805
|
+
# can be told apart from the outside and all of them have the same remedy. An entry from before the
|
|
806
|
+
# split simply does not exist under this producer id, so an older cache re-collects once.
|
|
807
|
+
def peek_effect_summary(descriptor)
|
|
808
|
+
cached = @cache_store.peek_validated(
|
|
809
|
+
producer_id: RunCacheKey::RUN_EFFECTS_TABLE_PRODUCER_ID, key_descriptor: descriptor
|
|
810
|
+
)
|
|
811
|
+
return nil unless cached.is_a?(Array) && cached.length == 3
|
|
812
|
+
return nil unless cached[0].is_a?(Effects::EffectTable) && cached[1].is_a?(Hash) &&
|
|
813
|
+
cached[2].is_a?(Hash)
|
|
814
|
+
|
|
815
|
+
cached
|
|
816
|
+
rescue StandardError
|
|
817
|
+
nil
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
# The read half of the collections entry — only ever asked by {#forced_file_effects}, on the paths
|
|
821
|
+
# that need per-file form. The stored shape is `[collections]`; an entry from before the split is
|
|
822
|
+
# `[collections, table]` and its first member is the same Hash, so it still reads.
|
|
699
823
|
def peek_effect_collections(descriptor)
|
|
700
824
|
cached = @cache_store.peek_validated(
|
|
701
825
|
producer_id: RunCacheKey::RUN_EFFECTS_PRODUCER_ID, key_descriptor: descriptor
|
|
702
826
|
)
|
|
703
|
-
cached.is_a?(
|
|
827
|
+
return nil unless cached.is_a?(Array) && cached.first.is_a?(Hash)
|
|
828
|
+
|
|
829
|
+
cached.first
|
|
704
830
|
rescue StandardError
|
|
705
831
|
nil
|
|
706
832
|
end
|
|
707
833
|
|
|
708
834
|
# The write half, run only after a miss, so the block never recomputes anything: it hands over the
|
|
709
|
-
# collections the analysis just produced
|
|
710
|
-
#
|
|
711
|
-
#
|
|
712
|
-
#
|
|
835
|
+
# collections the analysis just produced — and the table the analysis already closed over them, so
|
|
836
|
+
# a warm hit re-runs neither the merge nor the fixpoint — validated against the same post-run
|
|
837
|
+
# dependency descriptor the diagnostics slot records. Fail-soft — a collection that will not
|
|
838
|
+
# Marshal (which nothing in {Effects::FileCollection} should be, and the fork pool already proves
|
|
839
|
+
# per file) costs the next run its warm start and nothing else.
|
|
840
|
+
# The write half, run only after a miss, so neither block recomputes anything. Two entries under one
|
|
841
|
+
# key descriptor and one dependency descriptor (#482): the summary a warm run serves from, and the
|
|
842
|
+
# collections the incremental and fail-soft paths read. The summary is written LAST, so a run
|
|
843
|
+
# interrupted between the two leaves a collections entry that the next run's summary miss simply
|
|
844
|
+
# re-collects — never a summary whose collections are absent.
|
|
713
845
|
def store_effect_collections(descriptor, expansion, rbs_descriptor)
|
|
714
846
|
return if descriptor.nil?
|
|
715
847
|
|
|
716
848
|
collections = effect_collections_by_path
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
849
|
+
dependencies = run_dependency_descriptor(expansion, rbs_descriptor)
|
|
850
|
+
write_effect_entry(RunCacheKey::RUN_EFFECTS_PRODUCER_ID, descriptor, [collections], dependencies)
|
|
851
|
+
table = storable_effect_table(collections)
|
|
852
|
+
return nil if table.nil?
|
|
853
|
+
|
|
854
|
+
write_effect_entry(RunCacheKey::RUN_EFFECTS_TABLE_PRODUCER_ID, descriptor,
|
|
855
|
+
[table, effect_sources, effect_collection.superclasses], dependencies)
|
|
721
856
|
nil
|
|
722
857
|
rescue StandardError
|
|
723
858
|
nil
|
|
724
859
|
end
|
|
725
|
-
|
|
726
|
-
|
|
860
|
+
|
|
861
|
+
def write_effect_entry(producer_id, descriptor, payload, dependencies)
|
|
862
|
+
@cache_store.fetch_or_validate(
|
|
863
|
+
producer_id: producer_id, key_descriptor: descriptor,
|
|
864
|
+
generation_cap: RunCacheKey::EFFECTS_GENERATION_CAP
|
|
865
|
+
) { [payload, dependencies] }
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# An empty table over non-empty summaries is {#close_effect_graph}'s fail-soft answer, not a
|
|
869
|
+
# result — persisting it would freeze one run's propagation failure into every warm hit until the
|
|
870
|
+
# next invalidation. Storing nil instead makes the warm hit re-run the fixpoint, which is exactly
|
|
871
|
+
# the retry the fail-soft posture wants.
|
|
872
|
+
def storable_effect_table(collections)
|
|
873
|
+
table = @effect_table
|
|
874
|
+
return nil if table.nil?
|
|
875
|
+
return nil if table.empty? && collections.any? { |_, collection| !collection.summaries.empty? }
|
|
876
|
+
|
|
877
|
+
table
|
|
878
|
+
end
|
|
879
|
+
private :serve_effect_collections, :served_from_effect_entries?,
|
|
880
|
+
:effects_key_descriptor, :peek_effect_summary,
|
|
881
|
+
:peek_effect_collections, :store_effect_collections, :write_effect_entry,
|
|
882
|
+
:storable_effect_table, :forced_file_effects
|
|
727
883
|
|
|
728
884
|
# ADR-103 WD8 / #383 — the envelope check. Nothing at all without an `effects:` block, and nothing
|
|
729
885
|
# under `effects.check: false`; with both, one walk of the project's own RBS for `%a{pure}` /
|
|
730
886
|
# `%a{rigor:v1:effect …}`, and only if that finds an envelope does anything else run (the discovery
|
|
731
|
-
# tables the `def` positions come from are forced from inside the
|
|
887
|
+
# tables the `def` positions come from are forced from inside the judgment, on the first finding
|
|
888
|
+
# built — a judged-clean envelope forces no discovery at all).
|
|
732
889
|
#
|
|
733
890
|
# The environment is the one the cacheable path already resolved when there is one, so a warm run
|
|
734
891
|
# reads the envelopes off the loader it built anyway rather than building a second.
|
|
@@ -745,7 +902,7 @@ module Rigor
|
|
|
745
902
|
# ADR-103 WD1 / #386 — the nominal relation `effect.liskov-widened` reads, from the collector's
|
|
746
903
|
# own as-written superclass table, so the Liskov check and the closed-world proven lane resolve
|
|
747
904
|
# the same ancestry. Lazy: only a project that declared an envelope pays the merge.
|
|
748
|
-
ancestry: -> {
|
|
905
|
+
ancestry: -> { effect_ancestry },
|
|
749
906
|
apply_tolerated: !@no_tolerated_effects,
|
|
750
907
|
# #387 — the same compiled plugin tables the collection window scanned under, so an envelope may
|
|
751
908
|
# name a label a plugin opened and the unknown-label check agrees with the scan.
|
|
@@ -836,6 +993,10 @@ module Rigor
|
|
|
836
993
|
close_effect_graph
|
|
837
994
|
diagnostics += @diagnostic_aggregator.rbs_quarantined_signature_diagnostics
|
|
838
995
|
diagnostics += @diagnostic_aggregator.rbs_environment_build_failed_diagnostics
|
|
996
|
+
# Issue #696 — after its env-wide twin and before the synthesized-namespace notice: the three
|
|
997
|
+
# `rbs.coverage.*` build conditions surface widest-consequence first, and their relative order is
|
|
998
|
+
# the diagnostic output contract.
|
|
999
|
+
diagnostics += @diagnostic_aggregator.rbs_definition_build_failed_diagnostics
|
|
839
1000
|
diagnostics += @diagnostic_aggregator.rbs_synthesized_namespace_diagnostics
|
|
840
1001
|
diagnostics += @diagnostic_aggregator.conforms_to_diagnostics
|
|
841
1002
|
diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics
|
|
@@ -958,15 +1119,33 @@ module Rigor
|
|
|
958
1119
|
end
|
|
959
1120
|
|
|
960
1121
|
def build_run_dependency_descriptor(expansion, rbs_descriptor)
|
|
961
|
-
entries = analyzed_file_entries(expansion) +
|
|
1122
|
+
entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
|
|
1123
|
+
pre_eval_file_entries + rbs_descriptor.files
|
|
1124
|
+
globs = []
|
|
962
1125
|
@plugin_registry.plugins.each do |plugin|
|
|
963
1126
|
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances
|
|
964
1127
|
# are frozen after the run, and a plugin that never built a boundary read no files through it,
|
|
965
1128
|
# so it contributes no dependencies.
|
|
966
1129
|
boundary = plugin.instance_variable_get(:@io_boundary)
|
|
967
1130
|
entries.concat(boundary.cache_descriptor.files) if boundary
|
|
1131
|
+
plugin.class.producers.each_value do |prod|
|
|
1132
|
+
globs.concat(plugin.send(:watch_glob_entries, prod[:watch])) if prod[:watch]
|
|
1133
|
+
end
|
|
968
1134
|
end
|
|
969
|
-
Cache::Descriptor.new(files: entries)
|
|
1135
|
+
Cache::Descriptor.new(files: entries, globs: globs.uniq)
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
# Issue #684 — the project files this run DISCOVERED but did not analyse. A widened run's diagnostics
|
|
1139
|
+
# are a function of them (that is the entire point: the method whose absence would have been reported
|
|
1140
|
+
# is defined in one of them), so the run-result cache must invalidate when one changes. Empty on every
|
|
1141
|
+
# run whose discovery expansion is its analysis expansion, which is every whole-project run.
|
|
1142
|
+
def discovery_file_entries(expansion)
|
|
1143
|
+
return [] if @project_discovery_expansion.nil?
|
|
1144
|
+
|
|
1145
|
+
analyzed = expansion.fetch(:files).to_set
|
|
1146
|
+
analyzed_file_entries(
|
|
1147
|
+
{ files: @project_discovery_expansion.fetch(:files).reject { |path| analyzed.include?(path) } }
|
|
1148
|
+
)
|
|
970
1149
|
end
|
|
971
1150
|
|
|
972
1151
|
def analyzed_file_entries(expansion)
|
|
@@ -1062,12 +1241,18 @@ module Rigor
|
|
|
1062
1241
|
def apply_discovery_result(discovery)
|
|
1063
1242
|
@project_discovered_classes = discovery.discovered_classes
|
|
1064
1243
|
@project_discovered_def_nodes = discovery.discovered_def_nodes
|
|
1244
|
+
@project_discovered_def_nestings = discovery.discovered_def_nestings
|
|
1065
1245
|
@project_discovered_singleton_def_nodes = discovery.discovered_singleton_def_nodes
|
|
1066
1246
|
@project_discovered_def_sources = discovery.discovered_def_sources
|
|
1067
1247
|
@project_discovered_singleton_def_sources = discovery.discovered_singleton_def_sources
|
|
1068
1248
|
@project_discovered_superclasses = discovery.discovered_superclasses
|
|
1249
|
+
@project_discovered_header_nestings = discovery.discovered_header_nestings
|
|
1069
1250
|
@project_discovered_includes = discovery.discovered_includes
|
|
1070
1251
|
@project_discovered_class_sources = discovery.discovered_class_sources
|
|
1252
|
+
@project_constant_values = discovery.constant_values
|
|
1253
|
+
@published_constant_name_set = nil
|
|
1254
|
+
@project_constant_sources = discovery.constant_sources
|
|
1255
|
+
@project_constant_writes = discovery.constant_writes
|
|
1071
1256
|
@project_discovered_method_visibilities = discovery.discovered_method_visibilities
|
|
1072
1257
|
@project_discovered_methods = discovery.discovered_methods
|
|
1073
1258
|
@project_data_member_layouts = discovery.data_member_layouts
|
|
@@ -1087,6 +1272,8 @@ module Rigor
|
|
|
1087
1272
|
return if @project_discovery_done
|
|
1088
1273
|
|
|
1089
1274
|
@project_discovery_done = true
|
|
1275
|
+
# Issue #684 — the cross-file index spans the PROJECT even when the analysis targets a file list.
|
|
1276
|
+
expansion = @project_discovery_expansion || expansion
|
|
1090
1277
|
if @collect_seed_bundles
|
|
1091
1278
|
# ADR-85 WD2 — rebuild discovery from the prior run's bundles (re-walking only changed files) and
|
|
1092
1279
|
# capture the refreshed bundle set for the session to persist.
|
|
@@ -1099,6 +1286,39 @@ module Rigor
|
|
|
1099
1286
|
end
|
|
1100
1287
|
end
|
|
1101
1288
|
|
|
1289
|
+
# Issue #684 — the file set the cross-file DISCOVERY pre-pass walks, when that is wider than the set
|
|
1290
|
+
# being analysed. nil means "the same one", which is every whole-project run.
|
|
1291
|
+
#
|
|
1292
|
+
# `rigor check path/to/one_file.rb` — what an editor integration, a pre-commit hook and
|
|
1293
|
+
# `git diff --name-only | xargs rigor check` all do — discovered only the files it was handed, so a
|
|
1294
|
+
# class the project declares in `sig/` WITHOUT declaring every one of its methods drew a false
|
|
1295
|
+
# `call.undefined-method` for any method whose defining file was outside the invocation. The class was
|
|
1296
|
+
# RBS-known, source discovery could not supply the method, and the rule fired on correct code. The
|
|
1297
|
+
# same check over the containing DIRECTORY reported nothing, which is also why per-directory sums
|
|
1298
|
+
# over a tree over-reported against a whole-tree run (14 diagnostics of this shape against 2 during
|
|
1299
|
+
# the #652 work).
|
|
1300
|
+
#
|
|
1301
|
+
# Discovery is a parse pass, not an analysis: 0.55s over Rigor's own 444-file `lib` cold, and under
|
|
1302
|
+
# ADR-85 seed bundles a warm run re-walks only what changed. The alternative — standing the rule down
|
|
1303
|
+
# whenever evidence might be missing — buys the same FP safety by making `rigor check one_file.rb`
|
|
1304
|
+
# answer a different question than `rigor check .`, and the diagnostics for a file should not depend
|
|
1305
|
+
# on what else was on the command line.
|
|
1306
|
+
#
|
|
1307
|
+
# Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
|
|
1308
|
+
# seeds discovery from a snapshot), editor `buffer:` mode, `run_source`'s in-memory single file, and
|
|
1309
|
+
# ADR-46's `analyze_only` subset mode (which already passes the full expansion and filters later).
|
|
1310
|
+
def project_discovery_expansion(paths, expansion)
|
|
1311
|
+
return nil unless widen_discovery_to_project?(paths)
|
|
1312
|
+
|
|
1313
|
+
widened = expand_paths(@configuration.paths | paths)
|
|
1314
|
+
widened.fetch(:files).size > expansion.fetch(:files).size ? widened : nil
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
def widen_discovery_to_project?(paths)
|
|
1318
|
+
@prebuilt.nil? && @buffer.nil? && @in_memory_sources.nil? && @analyze_only.nil? &&
|
|
1319
|
+
paths != @configuration.paths
|
|
1320
|
+
end
|
|
1321
|
+
|
|
1102
1322
|
# ADR-46 — the dependency-recording and subset-analysis modes read the discovery tables OUTSIDE the
|
|
1103
1323
|
# analysis assembly (`Runner#symbol_fingerprints` / `#class_declarations`, consumed by
|
|
1104
1324
|
# {IncrementalSession} after the run), so they force the build eagerly — matching the pre-slice-1
|
|
@@ -1109,7 +1329,8 @@ module Rigor
|
|
|
1109
1329
|
end
|
|
1110
1330
|
|
|
1111
1331
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result,
|
|
1112
|
-
:apply_discovery_result, :ensure_project_discovery, :force_eager_discovery
|
|
1332
|
+
:apply_discovery_result, :ensure_project_discovery, :force_eager_discovery?,
|
|
1333
|
+
:project_discovery_expansion, :widen_discovery_to_project?
|
|
1113
1334
|
|
|
1114
1335
|
# Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
|
|
1115
1336
|
# `version:`. Prism exposes no version list, so the floor is found empirically — only when a
|
|
@@ -1240,6 +1461,7 @@ module Rigor
|
|
|
1240
1461
|
synthesized_namespaces_snapshot: -> { @snapshots.synthesized_namespaces },
|
|
1241
1462
|
quarantined_signatures_snapshot: -> { @snapshots.quarantined_signatures },
|
|
1242
1463
|
env_build_failure_snapshot: -> { @snapshots.env_build_failure },
|
|
1464
|
+
definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
|
|
1243
1465
|
conformance_results_snapshot: -> { @snapshots.conformance_results }
|
|
1244
1466
|
)
|
|
1245
1467
|
end
|
|
@@ -1458,6 +1680,9 @@ module Rigor
|
|
|
1458
1680
|
tables[:run_generation] = @run_generation if @run_generation
|
|
1459
1681
|
tables[:discovered_classes] = @project_discovered_classes unless @project_discovered_classes.empty?
|
|
1460
1682
|
tables[:discovered_def_nodes] = @project_discovered_def_nodes unless @project_discovered_def_nodes.empty?
|
|
1683
|
+
unless @project_discovered_def_nestings.empty?
|
|
1684
|
+
tables[:discovered_def_nestings] = @project_discovered_def_nestings
|
|
1685
|
+
end
|
|
1461
1686
|
unless @project_discovered_singleton_def_nodes.empty?
|
|
1462
1687
|
tables[:discovered_singleton_def_nodes] = @project_discovered_singleton_def_nodes
|
|
1463
1688
|
end
|
|
@@ -1465,6 +1690,9 @@ module Rigor
|
|
|
1465
1690
|
unless @project_discovered_superclasses.empty?
|
|
1466
1691
|
tables[:discovered_superclasses] = @project_discovered_superclasses
|
|
1467
1692
|
end
|
|
1693
|
+
unless @project_discovered_header_nestings.empty?
|
|
1694
|
+
tables[:discovered_header_nestings] = @project_discovered_header_nestings
|
|
1695
|
+
end
|
|
1468
1696
|
tables[:discovered_includes] = @project_discovered_includes unless @project_discovered_includes.empty?
|
|
1469
1697
|
unless @project_discovered_method_visibilities.empty?
|
|
1470
1698
|
tables[:discovered_method_visibilities] = @project_discovered_method_visibilities
|
|
@@ -1472,14 +1700,22 @@ module Rigor
|
|
|
1472
1700
|
tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
|
|
1473
1701
|
seed_opt_in_pre_pass_tables(tables)
|
|
1474
1702
|
seed_member_layout_tables(tables)
|
|
1475
|
-
|
|
1476
|
-
# dependency recording, so seed it only when recording is on; a normal run never carries it.
|
|
1477
|
-
if @record_dependencies && !@project_discovered_class_sources.empty?
|
|
1478
|
-
tables[:discovered_class_sources] = @project_discovered_class_sources
|
|
1479
|
-
end
|
|
1703
|
+
seed_dependency_attribution_tables(tables)
|
|
1480
1704
|
tables
|
|
1481
1705
|
end
|
|
1482
1706
|
|
|
1707
|
+
# ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read only by the recording accessors
|
|
1708
|
+
# (`Scope#record_class_dependency` for the class-declaration map, `Scope#record_constant_dependency`
|
|
1709
|
+
# for the constant-write map). A normal run carries neither. Extracted to keep
|
|
1710
|
+
# {#project_scope_seed_tables} under the complexity budget.
|
|
1711
|
+
def seed_dependency_attribution_tables(tables)
|
|
1712
|
+
return unless @record_dependencies
|
|
1713
|
+
|
|
1714
|
+
tables[:discovered_class_sources] = @project_discovered_class_sources unless
|
|
1715
|
+
@project_discovered_class_sources.empty?
|
|
1716
|
+
tables[:constant_sources] = @project_constant_sources unless @project_constant_sources.empty?
|
|
1717
|
+
end
|
|
1718
|
+
|
|
1483
1719
|
# Issue #260 — the mutable starting point {#project_scope_seed_tables} fills in: a copy of the opt-in
|
|
1484
1720
|
# `discovery_seed:` when one was supplied, otherwise the empty Hash every other run has always started
|
|
1485
1721
|
# from. Extracted so the seed costs {#project_scope_seed_tables} no branch of its complexity budget.
|
|
@@ -1499,9 +1735,33 @@ module Rigor
|
|
|
1499
1735
|
# Extracted to keep {#project_scope_seed_tables} under the complexity budget.
|
|
1500
1736
|
def seed_opt_in_pre_pass_tables(tables)
|
|
1501
1737
|
tables[:param_inferred_types] = @project_param_inferred_types unless @project_param_inferred_types.empty?
|
|
1502
|
-
|
|
1738
|
+
constants = merged_seed_constants
|
|
1739
|
+
return if constants.empty?
|
|
1740
|
+
|
|
1741
|
+
tables[:in_source_constants] = constants
|
|
1742
|
+
names = published_constant_name_set
|
|
1743
|
+
tables[:published_constant_names] = names unless names.empty?
|
|
1744
|
+
end
|
|
1745
|
+
|
|
1746
|
+
# Issue #644 — the seeded `in_source_constants` table, joining the two cross-file constant producers.
|
|
1747
|
+
# The whole-project literal table wins over a `pre_eval:` publication of the same name: both describe
|
|
1748
|
+
# the same assignment, the literal table pins the value the write actually carries while ADR-17's
|
|
1749
|
+
# widener erases it to the class, and listing a file under `pre_eval:` must never LOSE precision. The
|
|
1750
|
+
# per-file table still wins over both (`ScopeIndexer.index`'s merge) — same-file stays the most
|
|
1751
|
+
# specific authority.
|
|
1752
|
+
# Issue #644 — the LAST SEGMENTS of the published table, the run-wide half of
|
|
1753
|
+
# `Scope#published_constant?`. Seeded on EVERY run (unlike `constant_sources`, which only a recording
|
|
1754
|
+
# run needs) because {CheckRules::PublishedConstantGuard} asks the question on every `if` / `unless`.
|
|
1755
|
+
def published_constant_name_set
|
|
1756
|
+
@published_constant_name_set ||=
|
|
1757
|
+
@project_constant_values.keys.to_set { |name| name.split("::").last }.freeze
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
def merged_seed_constants
|
|
1761
|
+
return @project_constant_values if @project_pre_eval_constants.empty?
|
|
1762
|
+
return @project_pre_eval_constants if @project_constant_values.empty?
|
|
1503
1763
|
|
|
1504
|
-
|
|
1764
|
+
@project_pre_eval_constants.merge(@project_constant_values).freeze
|
|
1505
1765
|
end
|
|
1506
1766
|
|
|
1507
1767
|
# ADR-46 — seed the instance + singleton `"path:line"` def-source tables (each only when non-empty).
|
|
@@ -235,6 +235,14 @@ module Rigor
|
|
|
235
235
|
# Read-once snapshot of the per-worker reporters so the caller (or the eventual Phase 4b pool
|
|
236
236
|
# aggregator) can merge into a single coordinator-side reporter. Both reporters dedupe at write time,
|
|
237
237
|
# so a post-hoc concat + de-dup at the entry-key level is sound.
|
|
238
|
+
#
|
|
239
|
+
# Issue #696 — `definition_build_failures` rides this channel rather than a coordinator-side snapshot,
|
|
240
|
+
# and it has to. Under the pool the PARENT never analyses a file, so its loader never demands a
|
|
241
|
+
# definition and never enters the failing build; a diagnostic wired off the parent's loader would
|
|
242
|
+
# appear at `--workers=0` and vanish at `--workers=N`, which is the same "reports less depending on
|
|
243
|
+
# how you ran it" defect the diagnostic exists to end. Draining it out of the workers is what makes
|
|
244
|
+
# the two paths say the same thing. The payload is `[String, String, String, Array<String>]` tuples —
|
|
245
|
+
# Marshal-clean for the fork backend and shareable for the Ractor one.
|
|
238
246
|
def drain_reporters
|
|
239
247
|
{
|
|
240
248
|
rbs_extended: {
|
|
@@ -242,7 +250,8 @@ module Rigor
|
|
|
242
250
|
lossy_projections: @rbs_extended_reporter.lossy_projections
|
|
243
251
|
},
|
|
244
252
|
boundary_cross: @boundary_cross_reporter.entries,
|
|
245
|
-
source_rbs_synthesis: @source_rbs_synthesis_reporter.entries
|
|
253
|
+
source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
|
|
254
|
+
definition_build_failures: @environment&.rbs_loader&.definition_build_failures || []
|
|
246
255
|
}
|
|
247
256
|
end
|
|
248
257
|
|
data/lib/rigor/bleeding_edge.rb
CHANGED
|
@@ -107,14 +107,15 @@ module Rigor
|
|
|
107
107
|
id: "reject-unparseable-signatures",
|
|
108
108
|
kind: :severity,
|
|
109
109
|
summary: "A broken `signature_paths:` RBS set fails the run instead of degrading it silently. An " \
|
|
110
|
-
"unparseable `.rbs` is otherwise skipped with a warning
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"already is.",
|
|
110
|
+
"unparseable `.rbs` is otherwise skipped with a warning; a duplicate-declaration conflict " \
|
|
111
|
+
"(a file that parses fine but collides on resolve — typically against Rigor's own bundled " \
|
|
112
|
+
"RBS) collapses the whole env with a warning; and a duplicate METHOD definition collapses " \
|
|
113
|
+
"one class's method surface the same way. Each leaves the run quieter rather than " \
|
|
114
|
+
"cleaner. This treats all three as a build error, the way a broken source file already is.",
|
|
115
115
|
severity_overrides: {
|
|
116
116
|
"rbs.coverage.quarantined-signature" => :error,
|
|
117
|
-
"rbs.coverage.environment-build-failed" => :error
|
|
117
|
+
"rbs.coverage.environment-build-failed" => :error,
|
|
118
|
+
"rbs.coverage.definition-build-failed" => :error
|
|
118
119
|
}.freeze
|
|
119
120
|
),
|
|
120
121
|
Feature.new(
|