rigortype 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/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 +409 -11
- data/docs/handbook/11-sig-gen.md +24 -14
- data/docs/manual/02-cli-reference.md +23 -7
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activerecord.md +74 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- 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 +188 -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/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +63 -16
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +14 -5
- data/lib/rigor/analysis/reachability/scan.rb +7 -3
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +41 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +233 -16
- data/lib/rigor/analysis/runner/pool_coordinator.rb +266 -47
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -2
- data/lib/rigor/analysis/runner.rb +212 -16
- data/lib/rigor/analysis/worker_session.rb +37 -3
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/imported_refinements.rb +11 -9
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/annotation_location.rb +72 -0
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +70 -8
- data/lib/rigor/cache/store.rb +17 -1
- data/lib/rigor/cli/coverage_command.rb +23 -18
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- 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 +47 -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.rb +1 -1
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/failure_slot.rb +28 -0
- 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 +743 -93
- data/lib/rigor/environment.rb +125 -25
- 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 +61 -3
- data/lib/rigor/inference/hkt_sugar_translator.rb +87 -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/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/plugin/registry.rb +25 -2
- 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 +8 -7
- data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
- data/lib/rigor/rbs_extended/reporter.rb +93 -13
- data/lib/rigor/rbs_extended.rb +6 -1
- 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 +320 -85
- data/lib/rigor/sig_gen/renderer.rb +14 -12
- 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 +9 -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
- metadata +71 -3
|
@@ -115,17 +115,79 @@ module Rigor
|
|
|
115
115
|
# sources. The env stays a LOCAL variable (not an ivar) so it goes GC-eligible when the method
|
|
116
116
|
# returns — holding it as long-lived state added memory pressure that surfaced as a Bus Error
|
|
117
117
|
# during the spec suite under Ruby 4.0 + rbs 4.0.2.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
# @param project_files [Array<String>, nil] issue #784 — the WHOLE project's analyzed file set
|
|
119
|
+
# (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing of `files`. Read only
|
|
120
|
+
# when `files` is empty, to decide whether anyone could have demanded the HKT registry at all.
|
|
121
|
+
def analyze_files(files, environment: nil, project_files: nil)
|
|
122
|
+
if files.empty?
|
|
123
|
+
# Issue #784 — an EMPTY analyze set still owes the run its HKT-scan row: the per-file cache never
|
|
124
|
+
# holds it (`IncrementalSession` caches only `Runner#per_file_diagnostics`, so every run-level row
|
|
125
|
+
# is regenerated every run), so returning here without recording flips a red project green — and
|
|
126
|
+
# the shipping `--incremental` path reaches this branch with NO environment in hand on every
|
|
127
|
+
# warm recheck that changed nothing (`CheckCommand#run_incremental_check` builds its session
|
|
128
|
+
# without one). So: an environment already in hand is consulted; otherwise one is resolved over
|
|
129
|
+
# the project's OWN file list — not `[]`, which would drop every plugin-synthesized virtual RBS
|
|
130
|
+
# (`Environment.collect_virtual_rbs` short-circuits on an empty list) and scan a different type
|
|
131
|
+
# universe from the one a full run analyses — and only when the project HAS files: with none,
|
|
132
|
+
# nobody could have demanded a registry, and an empty project keeps paying no env build. Keyed
|
|
133
|
+
# on the project's files rather than on `analyze_only`, because a recheck over an EMPTY project
|
|
134
|
+
# narrows to `Set[]`, which is non-nil.
|
|
135
|
+
env = environment || @environment_override
|
|
136
|
+
if project_files && !project_files.empty?
|
|
137
|
+
env ||= resolve_sequential_environment(source_files: project_files)
|
|
138
|
+
end
|
|
139
|
+
# #788 rounds 6 and 9 — everything the run owes from its environment that no per-file analysis
|
|
140
|
+
# produces is taken here, the way `analyze_files_sequentially` takes it: the project-signature
|
|
141
|
+
# state (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the
|
|
142
|
+
# conformance results), the effect-annotation carrier the residual pass reads, and the HKT-scan
|
|
143
|
+
# outcome. Now that run-level rows are never served from the per-file cache, this branch is the
|
|
144
|
+
# only producer on a warm recheck that changed nothing — leaving any of them out turned a red
|
|
145
|
+
# project green on its second `--incremental` run (the inline-only `effect.annotations-unchecked`
|
|
146
|
+
# went 1 → 0; a quarantined `signature_paths:` file went 1 → 0; a `conforms-to` class whose
|
|
147
|
+
# definition build fails went 1 → 0). Same ORDER as the sequential path, because the order is
|
|
148
|
+
# the contract: the conformance scan inside the signature-state snapshot demands the definition
|
|
149
|
+
# of every `rigor:v1:conforms-to` class — a user-authored, invocation-independent demand that
|
|
150
|
+
# #696 counts — so the definition-build failures are read AFTER it and BEFORE the HKT demand,
|
|
151
|
+
# exactly where `analyze_files_sequentially` reads them relative to its own. What this branch
|
|
152
|
+
# cannot regenerate is the part of that set the per-file ANALYSIS demanded (#796). This also
|
|
153
|
+
# retires the #441 "`.rbs` lane only when the run analyses nothing" boundary: its cost premise
|
|
154
|
+
# (no environment on this path) stopped holding the moment the branch above resolved one.
|
|
155
|
+
snapshot_project_signature_state(env)
|
|
156
|
+
snapshot_effect_annotation_carrier(env&.rbs_loader)
|
|
157
|
+
record_definition_build_failures(env&.rbs_loader&.definition_build_failures)
|
|
158
|
+
record_hkt_scan_failure(hkt_scan_outcome(env))
|
|
159
|
+
return []
|
|
160
|
+
end
|
|
161
|
+
# Issue #784 / #793 — `files` is what this run ANALYSES; `source_files` is what its environment
|
|
162
|
+
# is BUILT over, and the two are the same only on a full run. A narrowed run (`analyze_only`: an
|
|
163
|
+
# incremental closure, a `--verify-incremental` partition) used to build its environment over the
|
|
164
|
+
# subset, so plugin-synthesized virtual RBS from every excluded file was missing and the run
|
|
165
|
+
# scanned a different type universe than the full run it is compared against — a scan failure
|
|
166
|
+
# whose trigger lived in an excluded file's synthesized RBS fired on the full run and vanished on
|
|
167
|
+
# the subset. Every environment and worker this run builds now takes the whole project.
|
|
168
|
+
source_files = project_files || files
|
|
169
|
+
return dispatch_pool(files, source_files: source_files) if pool_mode?
|
|
170
|
+
|
|
171
|
+
analyze_files_sequentially(
|
|
172
|
+
files, environment || resolve_sequential_environment(source_files: source_files)
|
|
173
|
+
)
|
|
123
174
|
end
|
|
124
175
|
|
|
125
176
|
def analyze_files_sequentially(files, environment)
|
|
126
177
|
snapshot_project_signature_state(environment)
|
|
127
178
|
snapshot_effect_annotation_carrier(environment&.rbs_loader)
|
|
128
179
|
result = files.flat_map { |path| @analyze_file.call(path, environment) }
|
|
180
|
+
# Issue #696 — AFTER the loop, deliberately. Definition builds are lazy (ADR-54 WD1: per class, on
|
|
181
|
+
# first demand), so a snapshot taken beside the ones above — which run BEFORE `files.flat_map` —
|
|
182
|
+
# would read an empty list on every run, including the ones this diagnostic exists for.
|
|
183
|
+
record_definition_build_failures(environment&.rbs_loader&.definition_build_failures)
|
|
184
|
+
# Issue #784 — same timing contract, same reason: the HKT scan is first demanded from inside a
|
|
185
|
+
# file's analysis (the dispatcher's Singleton-receiver tier), so a snapshot taken any earlier
|
|
186
|
+
# would read nil on every run. And demanded HERE once more by the run itself, because a subset
|
|
187
|
+
# run (`--verify-incremental`'s partition, an incremental recheck's closure) may contain no file
|
|
188
|
+
# that demands it — the row must not depend on which files were analysed. See {#hkt_scan_outcome}
|
|
189
|
+
# for why a Rigor-owned demand is sound here where #696 forbids it.
|
|
190
|
+
record_hkt_scan_failure(hkt_scan_outcome(environment))
|
|
129
191
|
if @collect_stats
|
|
130
192
|
loader = environment.rbs_loader
|
|
131
193
|
@snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
|
|
@@ -152,7 +214,8 @@ module Rigor
|
|
|
152
214
|
return
|
|
153
215
|
end
|
|
154
216
|
|
|
155
|
-
|
|
217
|
+
# nil-safe: the empty-closure path reaches here with no environment when the project has no files.
|
|
218
|
+
loader = environment&.rbs_loader
|
|
156
219
|
@snapshots.synthesized_namespaces = loader&.synthesized_namespaces || []
|
|
157
220
|
@snapshots.quarantined_signatures = loader&.quarantined_signatures || []
|
|
158
221
|
@snapshots.env_build_failure = loader&.env_build_failure
|
|
@@ -216,20 +279,24 @@ module Rigor
|
|
|
216
279
|
# An effects run (ADR-103 WD13) is pinned for exactly the same reason — the Ractor messages carry
|
|
217
280
|
# no side-table channel — and degrades the same way. The degrade is sound rather than merely safe:
|
|
218
281
|
# the sequential fallback still collects, so the effect graph is complete either way.
|
|
219
|
-
|
|
282
|
+
# @param source_files [Array<String>] the file list every worker's / the fallback's environment is
|
|
283
|
+
# built over — the whole project (issue #793), defaulting to `files` for direct callers.
|
|
284
|
+
def dispatch_pool(files, source_files: files)
|
|
220
285
|
if @record_dependencies || @record_effects
|
|
221
|
-
return analyze_files_in_fork_pool(files) if Process.respond_to?(:fork)
|
|
286
|
+
return analyze_files_in_fork_pool(files, source_files: source_files) if Process.respond_to?(:fork)
|
|
222
287
|
|
|
223
288
|
return analyze_files_sequentially_fallback(
|
|
224
|
-
files, reason: "incremental parallelism requires fork; recording sequentially"
|
|
289
|
+
files, reason: "incremental parallelism requires fork; recording sequentially",
|
|
290
|
+
source_files: source_files
|
|
225
291
|
)
|
|
226
292
|
end
|
|
227
293
|
case pool_backend
|
|
228
|
-
when :ractor then analyze_files_in_pool(files)
|
|
229
|
-
when :fork then analyze_files_in_fork_pool(files)
|
|
294
|
+
when :ractor then analyze_files_in_pool(files, source_files: source_files)
|
|
295
|
+
when :fork then analyze_files_in_fork_pool(files, source_files: source_files)
|
|
230
296
|
else
|
|
231
297
|
analyze_files_sequentially_fallback(
|
|
232
|
-
files, reason: "fork-based parallelism is unavailable on this platform"
|
|
298
|
+
files, reason: "fork-based parallelism is unavailable on this platform",
|
|
299
|
+
source_files: source_files
|
|
233
300
|
)
|
|
234
301
|
end
|
|
235
302
|
end
|
|
@@ -295,7 +362,7 @@ module Rigor
|
|
|
295
362
|
# worker touches comes back as an internal analyzer error. What the code below fixes is the
|
|
296
363
|
# failure MODE, not the backend: the run used to hang forever instead of saying anything. Reviving
|
|
297
364
|
# the backend needs an upstream change, which is why `pool_backend` keeps `fork` as the default.
|
|
298
|
-
def analyze_files_in_pool(files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
365
|
+
def analyze_files_in_pool(files, source_files: files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
299
366
|
# Pre-warm class-level lazy memos on the MAIN Ractor. `Environment::ClassRegistry.default` is the
|
|
300
367
|
# default kwarg threaded through `Environment.new` inside each worker session; lazy-initialising
|
|
301
368
|
# it from a non-main Ractor would trip `Ractor::IsolationError`. Touching it here forces the
|
|
@@ -310,10 +377,28 @@ module Rigor
|
|
|
310
377
|
# constants.
|
|
311
378
|
if @cache_store.nil?
|
|
312
379
|
return analyze_files_sequentially_fallback(
|
|
313
|
-
files, reason: "pool mode requires a cache_store (--no-cache disables pool)"
|
|
380
|
+
files, reason: "pool mode requires a cache_store (--no-cache disables pool)",
|
|
381
|
+
source_files: source_files
|
|
314
382
|
)
|
|
315
383
|
end
|
|
316
|
-
|
|
384
|
+
# Issue #798 — the SAME gap the fork pool had: this coordinator never analyses a file itself, so
|
|
385
|
+
# without an explicit read the project-signature state (`synthesized-namespace`,
|
|
386
|
+
# `quarantined-signature`, `environment-build-failed`, the conformance results) has no producer.
|
|
387
|
+
# Unlike the fork pool's copy-on-write children, a Ractor worker builds its OWN Environment
|
|
388
|
+
# inside its own isolated Ractor, so nothing here inherits what the coordinator's environment
|
|
389
|
+
# finds. `#prewarm_rbs_cache_for_pool` already builds and fully loads exactly this
|
|
390
|
+
# coordinator-side environment (to warm the cache before any worker spawns); it now hands it
|
|
391
|
+
# back so its RBS state can be read the same way the fork pool reads its pre-fork session
|
|
392
|
+
# environment. The conformance scan inside the snapshot demands the definition of every
|
|
393
|
+
# `rigor:v1:conforms-to` class — a demand no Ractor worker shares memory with — so a resulting
|
|
394
|
+
# definition-build failure is recorded explicitly right after, exactly as the empty-closure
|
|
395
|
+
# branch reads its own environment's demand (#788). No matching explicit call for the HKT-scan
|
|
396
|
+
# outcome: every worker already demands it from its OWN environment in `#drain_reporters`, and
|
|
397
|
+
# the scan has one outcome whoever demands it, so a worker's report already says what the
|
|
398
|
+
# coordinator's own demand would.
|
|
399
|
+
warm_env = prewarm_rbs_cache_for_pool
|
|
400
|
+
snapshot_project_signature_state(warm_env)
|
|
401
|
+
record_definition_build_failures(warm_env&.rbs_loader&.definition_build_failures)
|
|
317
402
|
|
|
318
403
|
configuration = @configuration
|
|
319
404
|
cache_root = @cache_store&.root
|
|
@@ -321,8 +406,9 @@ module Rigor
|
|
|
321
406
|
explain = @explain
|
|
322
407
|
# ADR-32 WD4 — the full project file list travels into every Ractor worker so each worker's
|
|
323
408
|
# WorkerSession can invoke loaded plugins' source_rbs_synthesizers at env-build time. The list is
|
|
324
|
-
# a frozen Array<String>; cheaply shareable.
|
|
325
|
-
|
|
409
|
+
# a frozen Array<String>; cheaply shareable. Issue #793 — it IS the full project now
|
|
410
|
+
# (`source_files`), not the analyzed subset this comment always described.
|
|
411
|
+
shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
|
|
326
412
|
|
|
327
413
|
pool = Array.new(@workers) do
|
|
328
414
|
Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files) do |configuration, cache_root, blueprints, explain, shareable_source_files| # rubocop:disable Layout/LineLength
|
|
@@ -393,15 +479,28 @@ module Rigor
|
|
|
393
479
|
# The files no worker reported — every file of a worker that died, and any file in flight when
|
|
394
480
|
# it did. Re-analysed in process, exactly as the fork backend re-analyses a dead child's slice.
|
|
395
481
|
degraded = files.reject { |path| results_by_path.key?(path) }
|
|
396
|
-
|
|
397
|
-
environment = build_runner_environment(source_files: files)
|
|
398
|
-
degraded.each { |path| results_by_path[path] = @analyze_file.call(path, environment) }
|
|
399
|
-
end
|
|
482
|
+
reanalyze_degraded_in_process(degraded, results_by_path, source_files: source_files)
|
|
400
483
|
|
|
401
484
|
diagnostics = Array(prepare_diagnostics) + files.flat_map { |path| results_by_path.fetch(path, []) }
|
|
402
485
|
degraded.empty? ? diagnostics : diagnostics.unshift(pool_degraded_diagnostic(degraded.size, "ractor"))
|
|
403
486
|
end
|
|
404
487
|
|
|
488
|
+
# The Ractor backend's degrade: the files of a worker that died are re-analysed on a LOCAL
|
|
489
|
+
# environment built over the whole project. That worker never sent `:done`, so nothing drains its
|
|
490
|
+
# reporters — and the local environment has no session to drain either — so the run-level state
|
|
491
|
+
# this analysis produced is taken here, exactly as the sequential fallback takes it: the
|
|
492
|
+
# definition-build failures the re-analysis demanded (#696) and the HKT-scan outcome, demanded
|
|
493
|
+
# once more by the run itself (#784 — a rescued scan failure otherwise vanished with the worker).
|
|
494
|
+
# The fork backend needs none of this: it re-analyses on the parent {WorkerSession} and drains it.
|
|
495
|
+
def reanalyze_degraded_in_process(degraded, results_by_path, source_files:)
|
|
496
|
+
return if degraded.empty?
|
|
497
|
+
|
|
498
|
+
environment = build_runner_environment(source_files: source_files)
|
|
499
|
+
degraded.each { |path| results_by_path[path] = @analyze_file.call(path, environment) }
|
|
500
|
+
record_definition_build_failures(environment.rbs_loader&.definition_build_failures)
|
|
501
|
+
record_hkt_scan_failure(hkt_scan_outcome(environment))
|
|
502
|
+
end
|
|
503
|
+
|
|
405
504
|
# ADR-15 Amendment (2026-05-20) — fork-based worker pool, the active backend for `workers > 0`.
|
|
406
505
|
# Builds ONE {WorkerSession} on the parent, then `fork`s N children that copy-on-write inherit it.
|
|
407
506
|
# Each child analyses a contiguous slice of `files` and writes a Marshal'd `{results:, reporters:}`
|
|
@@ -414,7 +513,11 @@ module Rigor
|
|
|
414
513
|
#
|
|
415
514
|
# A child that exits non-zero (crash / unmarshalable payload) is degraded: the parent re-analyses
|
|
416
515
|
# that slice in-process and prepends a `pool-degraded` warning.
|
|
417
|
-
|
|
516
|
+
#
|
|
517
|
+
# Also snapshots the project-signature state and the effect-annotation carrier off the pre-fork
|
|
518
|
+
# `session.environment` before any child spawns (#798) — the only environment this backend's
|
|
519
|
+
# coordinator ever holds, and so the only place those diagnostic rows can be read from.
|
|
520
|
+
def analyze_files_in_fork_pool(files, source_files: files) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
418
521
|
Environment::ClassRegistry.default
|
|
419
522
|
|
|
420
523
|
session = WorkerSession.new(
|
|
@@ -425,12 +528,27 @@ module Rigor
|
|
|
425
528
|
synthetic_method_index: synthetic_method_index,
|
|
426
529
|
project_patched_methods: project_patched_methods,
|
|
427
530
|
project_scope_seed: project_scope_seed,
|
|
428
|
-
source_files:
|
|
531
|
+
source_files: source_files,
|
|
429
532
|
record_dependencies: @record_dependencies
|
|
430
533
|
)
|
|
431
534
|
# Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
|
|
432
535
|
# rather than each rebuilding it after the fork.
|
|
433
536
|
session.environment.rbs_loader&.prewarm
|
|
537
|
+
# Issue #798 — same set, same ORDER as the sequential path (docs/type-specification/
|
|
538
|
+
# diagnostic-policy.md § `rbs.coverage.*`): the project-signature state
|
|
539
|
+
# (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the
|
|
540
|
+
# conformance results) has no per-file producer, so this coordinator-side environment is the
|
|
541
|
+
# only place a pooled run can take it from — exactly as the empty-closure branch above is the
|
|
542
|
+
# only place an empty run can (#788). Taken BEFORE the fork below: the conformance scan this
|
|
543
|
+
# runs demands the definition of every `rigor:v1:conforms-to` class, and doing that HERE means
|
|
544
|
+
# a resulting definition-build failure is already sitting in the parent's loader at the moment
|
|
545
|
+
# each child copy-on-write-inherits it — every child's own `#drain_reporters` then reports it
|
|
546
|
+
# right alongside whatever its own slice demanded, with no separate plumbing needed. Left
|
|
547
|
+
# unconditional, unlike `#snapshot_fork_pool_stats` below: these are diagnostic rows, not
|
|
548
|
+
# `RunStats` telemetry, and the stats gate must not decide which diagnostics a run reports (a
|
|
549
|
+
# `--workers N --no-stats` run used to say strictly LESS than the sequential path over the
|
|
550
|
+
# same project).
|
|
551
|
+
snapshot_project_signature_state(session.environment)
|
|
434
552
|
snapshot_effect_annotation_carrier(session.environment.rbs_loader)
|
|
435
553
|
snapshot_fork_pool_stats(session) if @collect_stats
|
|
436
554
|
|
|
@@ -484,18 +602,16 @@ module Rigor
|
|
|
484
602
|
exit!(1)
|
|
485
603
|
end
|
|
486
604
|
|
|
487
|
-
#
|
|
488
|
-
#
|
|
605
|
+
# Issue #798 — `RunStats` telemetry ONLY, now that `#snapshot_project_signature_state` (called
|
|
606
|
+
# unconditionally above) owns every diagnostic-bearing slot this method used to ALSO write
|
|
607
|
+
# (`quarantined_signatures`, `env_build_failure`): those must not be gated on `@collect_stats`, and
|
|
608
|
+
# this method's own name says what is left — `class_decl_paths` / `signature_paths`, read off the
|
|
609
|
+
# parent session's loader so end-of-run {RunStats} can attribute the RBS class universe, which a
|
|
610
|
+
# `--no-stats` run legitimately skips.
|
|
489
611
|
def snapshot_fork_pool_stats(session)
|
|
490
612
|
loader = session.environment.rbs_loader
|
|
491
613
|
@snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
|
|
492
614
|
@snapshots.signature_paths = loader&.signature_paths || [].freeze
|
|
493
|
-
# The workers each quarantine the same broken file, but they report no diagnostics for it — the row is
|
|
494
|
-
# a whole-run one. Read it off the parent session's loader so a pooled run says exactly what a
|
|
495
|
-
# sequential one says. The same reasoning holds for a total env-build failure.
|
|
496
|
-
@snapshots.quarantined_signatures =
|
|
497
|
-
project_signature_paths? ? (loader&.quarantined_signatures || []) : []
|
|
498
|
-
@snapshots.env_build_failure = project_signature_paths? ? loader&.env_build_failure : nil
|
|
499
615
|
end
|
|
500
616
|
|
|
501
617
|
# Waits for every forked child, merges each successful payload into `results_by_path`, and returns
|
|
@@ -541,9 +657,10 @@ module Rigor
|
|
|
541
657
|
|
|
542
658
|
# ADR-15 Phase 4b.x — drives every cached RBS producer on the main Ractor so each worker can serve
|
|
543
659
|
# all reflection queries from disk (Marshal-load only). Builds a single coordinator-side
|
|
544
|
-
# {Environment} for this purpose
|
|
545
|
-
#
|
|
546
|
-
#
|
|
660
|
+
# {Environment} for this purpose and returns it fully loaded — issue #798: the caller also reads
|
|
661
|
+
# the project-signature state off it, since it is the only environment this backend's coordinator
|
|
662
|
+
# ever holds. Workers still build their OWN `Environment.for_project` inside the Ractor body, which
|
|
663
|
+
# then routes through `cached_env` instead of `RBS::EnvironmentLoader.new`.
|
|
547
664
|
def prewarm_rbs_cache_for_pool
|
|
548
665
|
warm_env = Environment.for_project(
|
|
549
666
|
libraries: @configuration.libraries,
|
|
@@ -556,17 +673,30 @@ module Rigor
|
|
|
556
673
|
rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect
|
|
557
674
|
)
|
|
558
675
|
warm_env.rbs_loader&.prewarm
|
|
676
|
+
warm_env
|
|
559
677
|
end
|
|
560
678
|
|
|
561
679
|
# ADR-15 Phase 4b.x — pool-mode safety net. When pool mode is configured but a precondition fails
|
|
562
680
|
# (currently: `--no-cache` would force workers through `EnvironmentLoader.new`), degrade to
|
|
563
681
|
# sequential analysis with a `:warning` `pool-degraded` diagnostic at run start. The actual
|
|
564
682
|
# per-file analysis runs on the coordinator, identical to the default sequential path.
|
|
565
|
-
|
|
566
|
-
|
|
683
|
+
# @param source_files [Array<String>] issue #793 — the whole project, so this path's environment
|
|
684
|
+
# carries the same plugin-synthesized RBS the pool workers' would. It used to build over `[]`,
|
|
685
|
+
# i.e. with no synthesized RBS at all, even on a full run.
|
|
686
|
+
def analyze_files_sequentially_fallback(files, reason:, source_files: files)
|
|
687
|
+
environment = build_runner_environment(source_files: source_files)
|
|
567
688
|
snapshot_effect_annotation_carrier(environment.rbs_loader)
|
|
568
689
|
diagnostics = files.flat_map { |path| @analyze_file.call(path, environment) }
|
|
569
690
|
loader = environment.rbs_loader
|
|
691
|
+
# Issue #696 — this path analyses on the coordinator, so its loader IS the one that reached the
|
|
692
|
+
# failing build, and it must snapshot the same slot the two real paths do. Reached whenever
|
|
693
|
+
# `fork` is unavailable (Windows) and on `--incremental` / effects runs without it: a run that
|
|
694
|
+
# degraded to sequential must not also report less than a sequential run would.
|
|
695
|
+
record_definition_build_failures(loader&.definition_build_failures)
|
|
696
|
+
# Issue #784 — same reasoning: this path's Environment IS the one that reached the scan, so it
|
|
697
|
+
# must snapshot the slot too, or a run that degraded to sequential would report less than a
|
|
698
|
+
# sequential run would. Demanded once by the run as well, for the reason at the sequential site.
|
|
699
|
+
record_hkt_scan_failure(hkt_scan_outcome(environment))
|
|
570
700
|
@snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
|
|
571
701
|
@snapshots.signature_paths = loader&.signature_paths || [].freeze
|
|
572
702
|
@snapshots.quarantined_signatures =
|
|
@@ -582,17 +712,7 @@ module Rigor
|
|
|
582
712
|
end
|
|
583
713
|
|
|
584
714
|
def merge_worker_reporters(drained)
|
|
585
|
-
|
|
586
|
-
rbs.fetch(:unresolved_payloads).each do |entry|
|
|
587
|
-
@rbs_extended_reporter.record_unresolved(
|
|
588
|
-
payload: entry.payload, source_location: entry.source_location
|
|
589
|
-
)
|
|
590
|
-
end
|
|
591
|
-
rbs.fetch(:lossy_projections).each do |entry|
|
|
592
|
-
@rbs_extended_reporter.record_lossy_projection(
|
|
593
|
-
head: entry.head, source_location: entry.source_location
|
|
594
|
-
)
|
|
595
|
-
end
|
|
715
|
+
merge_rbs_extended_reporter(drained.fetch(:rbs_extended))
|
|
596
716
|
drained.fetch(:boundary_cross).each do |entry|
|
|
597
717
|
@boundary_cross_reporter.record(
|
|
598
718
|
class_name: entry.class_name,
|
|
@@ -608,10 +728,109 @@ module Rigor
|
|
|
608
728
|
plugin_id: entry.plugin_id, path: entry.path, message: entry.message
|
|
609
729
|
)
|
|
610
730
|
end
|
|
731
|
+
# Issue #696. Fetched with a default so an older drain stays compatible, exactly as the line above.
|
|
732
|
+
record_definition_build_failures(drained[:definition_build_failures])
|
|
733
|
+
# Issue #784. `Hash#[]` is already a nil default, exactly as `env_build_failure` is snapshotted
|
|
734
|
+
# elsewhere — an older drain shape simply has no key and records nothing.
|
|
735
|
+
record_hkt_scan_failure(drained[:hkt_scan_failure])
|
|
611
736
|
end
|
|
612
737
|
|
|
613
738
|
private
|
|
614
739
|
|
|
740
|
+
# Replays one worker's three {RbsExtended::Reporter} streams into the run's own reporter.
|
|
741
|
+
#
|
|
742
|
+
# `hkt_directive_errors` (issue #785) is read with `Hash#[]`'s nil default rather than `fetch`,
|
|
743
|
+
# exactly as `source_rbs_synthesis` is, so an older drain shape simply records nothing.
|
|
744
|
+
#
|
|
745
|
+
# All three streams replay by their `(path, line, column)` primitives (#785 for the hkt stream, #805
|
|
746
|
+
# for its two elders). Every worker reads the same `.rbs`, so each hands over the same entries, and
|
|
747
|
+
# only a primitive-keyed dedup collapses them back to the single row a `--workers=0` run prints — an
|
|
748
|
+
# `RBS::Location` compares equal only against a location over the same `RBS::Buffer` object, so a
|
|
749
|
+
# location-keyed one would print N copies at `--workers=N`. The entries could not reach here carrying
|
|
750
|
+
# a location anyway: the drain Marshals its payload and an `RBS::Location` has no `_dump`.
|
|
751
|
+
def merge_rbs_extended_reporter(rbs)
|
|
752
|
+
rbs.fetch(:unresolved_payloads).each do |entry|
|
|
753
|
+
@rbs_extended_reporter.record_unresolved(
|
|
754
|
+
payload: entry.payload, path: entry.path, line: entry.line, column: entry.column
|
|
755
|
+
)
|
|
756
|
+
end
|
|
757
|
+
rbs.fetch(:lossy_projections).each do |entry|
|
|
758
|
+
@rbs_extended_reporter.record_lossy_projection(
|
|
759
|
+
head: entry.head, path: entry.path, line: entry.line, column: entry.column
|
|
760
|
+
)
|
|
761
|
+
end
|
|
762
|
+
Array(rbs[:hkt_directive_errors]).each do |entry|
|
|
763
|
+
@rbs_extended_reporter.record_hkt_error(
|
|
764
|
+
message: entry.message, path: entry.path, line: entry.line, column: entry.column
|
|
765
|
+
)
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
# Issue #696 — accumulate the per-class `RBS::DefinitionBuilder` failures this run observed, deduped
|
|
770
|
+
# by class name and kept in first-seen order.
|
|
771
|
+
#
|
|
772
|
+
# Accumulating rather than assigning is what makes a pooled run say what a sequential one says: each
|
|
773
|
+
# worker holds its OWN loader and its own memo (the `@state` that also gates the stderr banner, which
|
|
774
|
+
# is why the banner repeats per worker — #295), so a class that fails in two workers arrives twice
|
|
775
|
+
# and a class only one worker touched arrives once. The union over the workers is the set the RUN
|
|
776
|
+
# hit; neither worker alone is.
|
|
777
|
+
#
|
|
778
|
+
# NOT gated on `project_signature_paths?`, unlike {#snapshot_project_signature_state}. That gate
|
|
779
|
+
# exists because each condition there forces the otherwise-lazy env build, which would warm the cache
|
|
780
|
+
# on a project with no sig set of its own. Nothing is forced here — this reads what the analysis
|
|
781
|
+
# already demanded — and the failure is not a project-sig-only condition anyway: two BUNDLED sources
|
|
782
|
+
# declaring the same method collapse a class with no `signature_paths:` in sight (the
|
|
783
|
+
# `bigdecimal` / `BigMath` shape, #299). Gating it would suppress exactly the variant the user cannot
|
|
784
|
+
# fix by editing their own `sig/`.
|
|
785
|
+
def record_definition_build_failures(failures)
|
|
786
|
+
return if failures.nil? || failures.empty?
|
|
787
|
+
|
|
788
|
+
@snapshots.definition_build_failures =
|
|
789
|
+
(@snapshots.definition_build_failures + failures).uniq(&:first).freeze
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Issue #784 — first-wins, unlike {#record_definition_build_failures}'s accumulate-and-dedup. That
|
|
793
|
+
# method accumulates because each pool WORKER owns its own loader and its own per-class memo, so a
|
|
794
|
+
# collapsed class can genuinely be observed by only some workers and the run's set is the union.
|
|
795
|
+
# The HKT scan has no such per-worker variation: `Environment#hkt_registry` builds from the SAME
|
|
796
|
+
# `signature_paths:` overlay every worker was handed, so every worker that demands it either all
|
|
797
|
+
# raise identically or all succeed — there is only ever one tuple to record, and `||=` is correct
|
|
798
|
+
# (and cheap) rather than an accumulate-and-dedup this slot never needs.
|
|
799
|
+
def record_hkt_scan_failure(tuple)
|
|
800
|
+
@snapshots.hkt_scan_failure ||= tuple
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# Issue #784 — demand the registry once on behalf of the run, then read the slot. The seam in
|
|
804
|
+
# {Environment#hkt_registry} is demand-driven, and nothing guarantees any analysed file demands it:
|
|
805
|
+
# a `--verify-incremental` partition or an incremental closure can miss every `Klass.method` call,
|
|
806
|
+
# and then the slot is nil after the loop and the run says nothing — while `--incremental` has also
|
|
807
|
+
# dropped the row from its per-file cache. Demanding here makes the outcome a property of the RUN,
|
|
808
|
+
# not of which files happened to be in it.
|
|
809
|
+
#
|
|
810
|
+
# This is sound where #696's "a demand that is Rigor's own MUST NOT contribute" is not, and the
|
|
811
|
+
# difference is the shape of what is recorded. #696 reports a per-class LIST whose membership is
|
|
812
|
+
# "the classes the analysis demanded"; a Rigor-internal demand adds classes the user never asked
|
|
813
|
+
# about and makes that list vary with configuration. The HKT scan is ONE build with ONE outcome —
|
|
814
|
+
# the same tuple whoever demands it — so an extra demand cannot change what is reported, only
|
|
815
|
+
# guarantee it is observed.
|
|
816
|
+
#
|
|
817
|
+
# What it costs, stated plainly because {#record_definition_build_failures}'s comment promises
|
|
818
|
+
# "nothing is forced here": the demand DOES force the RBS env build when the environment has not
|
|
819
|
+
# built it yet (the scan reads the loader). On every path that reaches this method that build is
|
|
820
|
+
# either already done (the loop demanded it) or the same load one analysed file would have paid:
|
|
821
|
+
# a Marshal load when a cache store exists, the parse a full run pays under `--no-cache`. Never a
|
|
822
|
+
# build on a project with no files (see {#analyze_files}). Memoised, so on a reused Environment
|
|
823
|
+
# this is a hash read.
|
|
824
|
+
#
|
|
825
|
+
# @param environment [Rigor::Environment, nil]
|
|
826
|
+
# @return [Array, nil] the recorded tuple, or nil (no environment, or the scan built)
|
|
827
|
+
def hkt_scan_outcome(environment)
|
|
828
|
+
return nil if environment.nil?
|
|
829
|
+
|
|
830
|
+
environment.hkt_registry
|
|
831
|
+
environment.hkt_scan_failure
|
|
832
|
+
end
|
|
833
|
+
|
|
615
834
|
# True when the project declares its own `signature_paths:` (the only place the
|
|
616
835
|
# qualified-name-without-namespace mistake lives).
|
|
617
836
|
def project_signature_paths?
|
|
@@ -53,17 +53,11 @@ module Rigor
|
|
|
53
53
|
# consumes them and so never pays the double parse (see {#discover} + `Runner#ensure_project_discovery`).
|
|
54
54
|
# The slot names mirror the discovery half of {Result} exactly.
|
|
55
55
|
Discovery = Data.define(
|
|
56
|
-
:discovered_classes,
|
|
57
|
-
:
|
|
58
|
-
:
|
|
59
|
-
:
|
|
60
|
-
:
|
|
61
|
-
:discovered_superclasses,
|
|
62
|
-
:discovered_includes,
|
|
63
|
-
:discovered_class_sources,
|
|
64
|
-
:discovered_method_visibilities,
|
|
65
|
-
:discovered_methods,
|
|
66
|
-
:data_member_layouts,
|
|
56
|
+
:discovered_classes, :discovered_def_nodes, :discovered_def_nestings,
|
|
57
|
+
:discovered_singleton_def_nodes, :discovered_def_sources, :discovered_singleton_def_sources,
|
|
58
|
+
:discovered_superclasses, :discovered_header_nestings, :discovered_includes,
|
|
59
|
+
:discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
|
|
60
|
+
:discovered_method_visibilities, :discovered_methods, :data_member_layouts,
|
|
67
61
|
:struct_member_layouts
|
|
68
62
|
)
|
|
69
63
|
|
|
@@ -156,12 +150,22 @@ module Rigor
|
|
|
156
150
|
Discovery.new(
|
|
157
151
|
discovered_classes: index.fetch(:classes),
|
|
158
152
|
discovered_def_nodes: def_index.fetch(:def_nodes),
|
|
153
|
+
# Issue #681 — the recorded `Module.nesting` per declared `def`, so a callee's body re-walk
|
|
154
|
+
# resolves its constants the way the declaration that owns it does.
|
|
155
|
+
discovered_def_nestings: def_index.fetch(:def_nestings),
|
|
159
156
|
discovered_singleton_def_nodes: def_index.fetch(:singleton_def_nodes),
|
|
160
157
|
discovered_def_sources: def_index.fetch(:def_sources),
|
|
161
158
|
discovered_singleton_def_sources: def_index.fetch(:singleton_def_sources),
|
|
162
159
|
discovered_superclasses: def_index.fetch(:superclasses),
|
|
160
|
+
# Issue #682 — the nesting each declaration header is written in, so an ancestor name resolves in
|
|
161
|
+
# the cref Ruby resolves it in rather than by peeling the subclass's own qualified name.
|
|
162
|
+
discovered_header_nestings: def_index.fetch(:header_nestings),
|
|
163
163
|
discovered_includes: def_index.fetch(:includes),
|
|
164
164
|
discovered_class_sources: def_index.fetch(:class_sources),
|
|
165
|
+
# Issue #644 — the cross-file value-constant publication table and its write attribution.
|
|
166
|
+
constant_values: def_index.fetch(:constant_values),
|
|
167
|
+
constant_sources: def_index.fetch(:constant_sources),
|
|
168
|
+
constant_writes: def_index.fetch(:constant_writes),
|
|
165
169
|
discovered_method_visibilities: def_index.fetch(:method_visibilities),
|
|
166
170
|
discovered_methods: def_index.fetch(:methods),
|
|
167
171
|
data_member_layouts: def_index.fetch(:data_member_layouts),
|
|
@@ -17,12 +17,18 @@ module Rigor
|
|
|
17
17
|
class RunSnapshots
|
|
18
18
|
attr_accessor :class_decl_paths, :signature_paths,
|
|
19
19
|
:synthesized_namespaces, :quarantined_signatures, :conformance_results,
|
|
20
|
-
:env_build_failure, :
|
|
20
|
+
:env_build_failure, :definition_build_failures, :hkt_scan_failure,
|
|
21
|
+
:effect_annotation_carrier
|
|
21
22
|
|
|
22
23
|
# Constructor defaults match the {Runner} constructor: the pre-seed values `build_run_stats` /
|
|
23
24
|
# `pre_file_diagnostics` read before the first analysis path runs are frozen empties. The
|
|
24
25
|
# `env_build_failure` slot is nil (no failure) rather than an empty collection — it holds a single
|
|
25
|
-
# `[error_class, first_line, buffer_names]` tuple or nothing.
|
|
26
|
+
# `[error_class, first_line, buffer_names]` tuple or nothing. Its per-class sibling
|
|
27
|
+
# `definition_build_failures` (#696) holds a LIST, because a collapsed universe fails many classes,
|
|
28
|
+
# and is accumulated ACROSS pool workers rather than assigned once — see
|
|
29
|
+
# {PoolCoordinator#merge_worker_reporters}. `hkt_scan_failure` (#784) is nil-or-tuple like
|
|
30
|
+
# `env_build_failure`, not a list like `definition_build_failures`: the scan is ONE build over the
|
|
31
|
+
# whole `signature_paths:` overlay, not a per-class one, so it has exactly one outcome per run.
|
|
26
32
|
def initialize
|
|
27
33
|
@class_decl_paths = {}.freeze
|
|
28
34
|
@signature_paths = [].freeze
|
|
@@ -30,6 +36,8 @@ module Rigor
|
|
|
30
36
|
@quarantined_signatures = [].freeze
|
|
31
37
|
@conformance_results = [].freeze
|
|
32
38
|
@env_build_failure = nil
|
|
39
|
+
@definition_build_failures = [].freeze
|
|
40
|
+
@hkt_scan_failure = nil
|
|
33
41
|
@effect_annotation_carrier = [].freeze
|
|
34
42
|
end
|
|
35
43
|
|
|
@@ -42,6 +50,8 @@ module Rigor
|
|
|
42
50
|
@quarantined_signatures = []
|
|
43
51
|
@conformance_results = []
|
|
44
52
|
@env_build_failure = nil
|
|
53
|
+
@definition_build_failures = []
|
|
54
|
+
@hkt_scan_failure = nil
|
|
45
55
|
@effect_annotation_carrier = [].freeze
|
|
46
56
|
end
|
|
47
57
|
end
|