rigortype 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +286 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +34 -2
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +7 -6
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
|
@@ -48,11 +48,13 @@ module Rigor
|
|
|
48
48
|
# @param cache_path — the configuration's cache root; nil or empty is an inert
|
|
49
49
|
# cache (every fetch computes, nothing persists).
|
|
50
50
|
# @param target_ruby — parse-affecting configuration, part of the identity.
|
|
51
|
-
|
|
51
|
+
# `root` names the project and working directory a run reads roles and spellings from (#1760): a
|
|
52
|
+
# scan cached by a run from another directory carries another role, so it is never served here.
|
|
53
|
+
def self.open(cache_path, target_ruby: nil, root: nil)
|
|
52
54
|
return new(path: nil, header: nil) if cache_path.nil? || cache_path.to_s.empty?
|
|
53
55
|
|
|
54
56
|
new(path: File.join(cache_path.to_s, FILE_NAME),
|
|
55
|
-
header: "#{SCHEMA}:#{Rigor::VERSION}:#{target_ruby}:#{source_digest}")
|
|
57
|
+
header: "#{SCHEMA}:#{Rigor::VERSION}:#{target_ruby}:#{source_digest}:#{root}")
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def self.source_digest
|
|
@@ -292,6 +292,38 @@ module Rigor
|
|
|
292
292
|
since: "0.0.1"
|
|
293
293
|
),
|
|
294
294
|
|
|
295
|
+
CheckRules::RULE_DEPRECATED_RUBY2_KEYWORDS => Entry.new(
|
|
296
|
+
id: CheckRules::RULE_DEPRECATED_RUBY2_KEYWORDS,
|
|
297
|
+
summary: "A call to the ruby2_keywords family Ruby 4.1 deprecates, under an explicit target_ruby >= 4.1.",
|
|
298
|
+
fires_when: [
|
|
299
|
+
"`.rigor.yml` sets `target_ruby` explicitly to \"4.1\" or later.",
|
|
300
|
+
"The call reaches `Module#ruby2_keywords` (no receiver or `self` in a class / module body, a " \
|
|
301
|
+
"`class << self` body or a singleton method), top-level `ruby2_keywords` (at the top level, outside " \
|
|
302
|
+
"any block), or `Proc#ruby2_keywords` (a receiver typed `Proc`): removed in Ruby 4.4.",
|
|
303
|
+
"The call reaches `Hash.ruby2_keywords_hash?` or `Hash.ruby2_keywords_hash` (on `Hash` or a subclass " \
|
|
304
|
+
"RBS knows): removed in Ruby 4.5.",
|
|
305
|
+
"The same names through `send` / `__send__` with a literal Symbol."
|
|
306
|
+
],
|
|
307
|
+
does_not_fire_when: [
|
|
308
|
+
"`target_ruby` is absent, is the default, is below 4.1, or is \"latest\" (which names a parser, " \
|
|
309
|
+
"not a runtime).",
|
|
310
|
+
"A project or `pre_eval:` file defines a method by the same name anywhere.",
|
|
311
|
+
"The call sits in a block at the top level, in an `instance_eval` / `instance_exec` block, in an " \
|
|
312
|
+
"instance method of a class or module, or on an explicit receiver that is not `Proc` / `Hash`.",
|
|
313
|
+
"The call sits in a version-guard arm that cannot run on the stated Ruby, under a guard on " \
|
|
314
|
+
"`RUBY_VERSION` that cannot be decided, or after a guard that returns or raises."
|
|
315
|
+
],
|
|
316
|
+
suppression: "`# rigor:disable call.deprecated-ruby2-keywords` on the call line. To fix it instead, " \
|
|
317
|
+
"rewrite the delegation the flag serves to `(...)` or `(*args, **kwargs, &block)` first, then " \
|
|
318
|
+
"drop the `ruby2_keywords` call: removing it alone breaks keyword pass-through.",
|
|
319
|
+
severity_authored: :warning,
|
|
320
|
+
severity_by_profile: { lenient: :info, balanced: :warning, strict: :error },
|
|
321
|
+
# Each call names a known core method on a receiver whose class the syntax or the scope fixes, and a
|
|
322
|
+
# project definition of the name silences it. A file loaded only on an older Ruby is the residual.
|
|
323
|
+
evidence_tier: :high,
|
|
324
|
+
since: "0.4.2"
|
|
325
|
+
),
|
|
326
|
+
|
|
295
327
|
CheckRules::RULE_ALWAYS_RAISES => Entry.new(
|
|
296
328
|
id: CheckRules::RULE_ALWAYS_RAISES,
|
|
297
329
|
summary: "Call provably raises (today: Integer division-by-zero).",
|
|
@@ -552,6 +584,9 @@ module Rigor
|
|
|
552
584
|
],
|
|
553
585
|
does_not_fire_when: [
|
|
554
586
|
"Override raises or preserves visibility (only reductions break substitutability).",
|
|
587
|
+
"The method is one Ruby makes private wherever it is defined (`initialize`, " \
|
|
588
|
+
"`initialize_copy` / `_dup` / `_clone`, `respond_to_missing?`), so its section is not its " \
|
|
589
|
+
"runtime visibility (ADR-35 WD10).",
|
|
555
590
|
"The shadowed method lives on an RBS-known / third-party ancestor (RBS models only " \
|
|
556
591
|
"public/private; RBS-parent visibility is a deferred follow-on).",
|
|
557
592
|
"`def self.foo` singleton methods (visibility is instance-side only).",
|
|
@@ -578,6 +613,9 @@ module Rigor
|
|
|
578
613
|
does_not_fire_when: [
|
|
579
614
|
"Either side lacks an authored RBS signature (WD1 both-sides-authored gate).",
|
|
580
615
|
"The override narrows or preserves the return (covariant-safe).",
|
|
616
|
+
"`initialize` and the `initialize_copy` / `_dup` / `_clone` hooks: `Class#new` / `dup` / " \
|
|
617
|
+
"`clone` reach them on the receiver's own class and discard their return, outside " \
|
|
618
|
+
"substitutability (ADR-35 WD10).",
|
|
581
619
|
"The ancestor's return is `untyped` / `self` / an unbound generic (degrades to " \
|
|
582
620
|
"`Dynamic[Top]`, which accepts everything — FP-safe).",
|
|
583
621
|
"The subtype relationship between the two return types is not resolvable from loaded " \
|
|
@@ -609,6 +647,9 @@ module Rigor
|
|
|
609
647
|
does_not_fire_when: [
|
|
610
648
|
"Either side lacks an authored RBS signature (WD1 both-sides-authored gate).",
|
|
611
649
|
"The override widens or preserves the parameter (contravariant-safe).",
|
|
650
|
+
"`initialize` and the `initialize_copy` / `_dup` / `_clone` hooks: `Class#new` / `dup` / " \
|
|
651
|
+
"`clone` reach them only on the receiver's own class, so no parent-typed caller passes the " \
|
|
652
|
+
"parent's arguments (ADR-35 WD10).",
|
|
612
653
|
"Either side is overloaded (more than one method type — arm mapping is ambiguous).",
|
|
613
654
|
"The ancestor's parameter is `untyped` / an unbound generic / an interface (degrades " \
|
|
614
655
|
"to `Dynamic[Top]`, which is passable to anything — FP-safe).",
|
|
@@ -640,6 +681,8 @@ module Rigor
|
|
|
640
681
|
],
|
|
641
682
|
does_not_fire_when: [
|
|
642
683
|
"Later write is `nil` — the `@cache = nil` clear-idiom is allowlisted.",
|
|
684
|
+
"The class's RBS declares the ivar (`@var: T`, or the slot an `attr_*` member implies, own or " \
|
|
685
|
+
"inherited) and that type accepts both the first write and the later one.",
|
|
643
686
|
"Either side is Union / Dynamic / IntegerRange / a shape-varied carrier.",
|
|
644
687
|
"Writes live in different classes that happen to share an ivar name.",
|
|
645
688
|
"Writes are in a singleton body — `def self.foo` or a `class << self` def — those track " \
|
|
@@ -21,6 +21,9 @@ require_relative "../environment/default_libraries"
|
|
|
21
21
|
# lockfile paths without loading `rigor/environment` or the RBS machinery.
|
|
22
22
|
require_relative "../environment/lockfile_resolver"
|
|
23
23
|
require_relative "../environment/rbs_collection_discovery"
|
|
24
|
+
# A leaf file (it loads only the cache descriptor), unlike `dependency_source_inference`, whose walker loads the
|
|
25
|
+
# inference engine the boot-slimming probe exists to avoid.
|
|
26
|
+
require_relative "dependency_source_inference/gem_resolver"
|
|
24
27
|
require_relative "shadow_harness"
|
|
25
28
|
|
|
26
29
|
module Rigor
|
|
@@ -93,6 +96,7 @@ module Rigor
|
|
|
93
96
|
def descriptor(configuration:, files:, explain:, rbs_config_entries:, template_units_digest: nil)
|
|
94
97
|
Cache::Descriptor.new(
|
|
95
98
|
gems: [Cache::RbsDescriptor.rbs_gem_entry],
|
|
99
|
+
dependencies: dependency_source_entries(configuration),
|
|
96
100
|
configs: rbs_config_entries + engine_source_entries + lockfile_entries(configuration) +
|
|
97
101
|
template_unit_entries(template_units_digest) + shadow_harness_entries + [
|
|
98
102
|
config_entry("configuration", Marshal.dump(configuration.to_h)),
|
|
@@ -151,6 +155,14 @@ module Rigor
|
|
|
151
155
|
[lockfile_entry("bundler.lockfile", bundler), lockfile_entry("rbs_collection.lockfile", collection)]
|
|
152
156
|
end
|
|
153
157
|
|
|
158
|
+
# ADR-10 slice 3, issue #1814 — the `dependencies.source_inference` gems' `(name, version, mode)` rows, which the
|
|
159
|
+
# walker's output depends on and no lockfile digest covers when the project has none or the gem sits beside the
|
|
160
|
+
# bundle. Shared by this key and by {Cache::IncrementalSnapshot.fingerprint}, as the lockfiles are. A project
|
|
161
|
+
# that lists no gem adds no row, so its key is what it was.
|
|
162
|
+
def dependency_source_entries(configuration)
|
|
163
|
+
DependencySourceInference::GemResolver.dependency_entries(configuration.dependencies)
|
|
164
|
+
end
|
|
165
|
+
|
|
154
166
|
# The one resolution of the two dependency lockfiles — `[bundler, rbs_collection]`, each a Pathname or nil
|
|
155
167
|
# — shared by this key and by {Cache::IncrementalSnapshot.fingerprint} so the two caches identify the gem
|
|
156
168
|
# set by the same files (issue #1532): `bundler.lockfile:` / `rbs_collection.lockfile:` and auto-detection.
|
|
@@ -180,7 +192,10 @@ module Rigor
|
|
|
180
192
|
|
|
181
193
|
# The `rbs.libraries` config slot reconstructed from configuration alone — byte-identical to the
|
|
182
194
|
# loader's `RbsDescriptor.libraries_entry(loader.libraries)` because `Environment.for_project` merges
|
|
183
|
-
# exactly `DEFAULT_LIBRARIES + config.libraries` (uniq) into `loader.libraries
|
|
195
|
+
# exactly `DEFAULT_LIBRARIES + config.libraries` (uniq) into `loader.libraries`, plus the
|
|
196
|
+
# `Environment::RequiredFeatures` tokens the run's source activates (issue #1700). Those are left out
|
|
197
|
+
# here: reading every source file would cost the boot-slimming probe the time it exists to save, and a
|
|
198
|
+
# project that requires a gated feature then only misses this probe, never hits a wrong slot.
|
|
184
199
|
def libraries_config_entries(configuration)
|
|
185
200
|
merged = (Environment::DEFAULT_LIBRARIES + configuration.libraries.map(&:to_s)).uniq
|
|
186
201
|
[Cache::RbsDescriptor.libraries_entry(merged)]
|
|
@@ -64,7 +64,7 @@ module Rigor
|
|
|
64
64
|
# the caller loads the engine and runs the full path. Any failure declines rather than raising: the
|
|
65
65
|
# probe must never turn a servable run into a crash.
|
|
66
66
|
def serve(paths)
|
|
67
|
-
files = PathExpansion.ruby_files(paths, @configuration.exclude_patterns)
|
|
67
|
+
files = PathExpansion.ruby_files(paths, @configuration.exclude_patterns, base: @configuration.exclude_base)
|
|
68
68
|
key = RunCacheKey.descriptor(
|
|
69
69
|
configuration: @configuration, files: files, explain: @explain,
|
|
70
70
|
rbs_config_entries: RunCacheKey.libraries_config_entries(@configuration)
|
|
@@ -24,18 +24,31 @@ module Rigor
|
|
|
24
24
|
# `class_decl_paths` (a frozen `Hash<String, String>` populated once per environment by the RBS loader;
|
|
25
25
|
# ~1000-2000 entries × one `String#start_with?`).
|
|
26
26
|
class RunStats
|
|
27
|
-
attr_reader :wall_seconds,
|
|
27
|
+
attr_reader :wall_seconds,
|
|
28
28
|
:target_files,
|
|
29
29
|
:rbs_classes_total, :rbs_classes_project_sig, :rbs_classes_bundled,
|
|
30
30
|
:gem_walk_classes, :gem_walk_gems, :rbs_attribution_available
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# The live probe a run's summary uses: reads this process's RSS when first asked. The probe is a constant
|
|
33
|
+
# lambda, so the RunStats it rides on never captures the Runner (and its analysis state) through `Result`.
|
|
34
|
+
CURRENT_PROCESS_PEAK_RSS = Ractor.make_shareable(-> { RunStats.peak_rss_bytes })
|
|
35
|
+
|
|
36
|
+
# Issue #1529 — the peak RSS is read on the first {#peak_rss_bytes} call, not when the summary is built. A run
|
|
37
|
+
# that prints no summary (`--incremental` recheck, `--no-stats`) therefore never forks `ps` or reads `/proc`.
|
|
38
|
+
# The read stays in this process: the summary is built and printed by the process that ran the analysis.
|
|
39
|
+
#
|
|
40
|
+
# @param peak_rss_bytes — a value to report directly (specs, or a caller that already read it). Ignored when
|
|
41
|
+
# `peak_rss_probe:` is given.
|
|
42
|
+
# @param peak_rss_probe — a zero-argument callable returning the RSS in bytes, called at most once, lazily.
|
|
43
|
+
def initialize(wall_seconds:, # rubocop:disable Metrics/ParameterLists
|
|
33
44
|
target_files:,
|
|
34
45
|
rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:,
|
|
35
46
|
gem_walk_classes:, gem_walk_gems:,
|
|
36
|
-
rbs_attribution_available: true)
|
|
47
|
+
rbs_attribution_available: true, peak_rss_bytes: nil, peak_rss_probe: nil)
|
|
37
48
|
@wall_seconds = wall_seconds
|
|
38
|
-
@
|
|
49
|
+
@peak_rss_probe = peak_rss_probe || -> { peak_rss_bytes }
|
|
50
|
+
# A frozen RunStats cannot memoise into an ivar, so the first read lands in this (unfrozen) Hash.
|
|
51
|
+
@peak_rss_memo = {}
|
|
39
52
|
@target_files = target_files
|
|
40
53
|
@rbs_classes_total = rbs_classes_total
|
|
41
54
|
@rbs_classes_project_sig = rbs_classes_project_sig
|
|
@@ -46,6 +59,15 @@ module Rigor
|
|
|
46
59
|
freeze
|
|
47
60
|
end
|
|
48
61
|
|
|
62
|
+
# The peak RSS in bytes, read once on first call and memoised (nil when no route answers). The check command
|
|
63
|
+
# forces the first call straight after the analysis (#1529), before `--coverage` or any output, so the figure
|
|
64
|
+
# covers the analysis alone. On Linux it is the kernel's VmHWM high-water mark, so a later read is never below
|
|
65
|
+
# the run-end value. On macOS it is `ps`'s current RSS, so the reading is the process as it stood at that
|
|
66
|
+
# first call, not a high-water mark.
|
|
67
|
+
def peak_rss_bytes
|
|
68
|
+
@peak_rss_memo.fetch(:value) { @peak_rss_memo[:value] = @peak_rss_probe.call }
|
|
69
|
+
end
|
|
70
|
+
|
|
49
71
|
# Reports the process's resident set size in bytes. Source ordering: `/proc/self/status` (Linux — reads
|
|
50
72
|
# `VmHWM:`, the peak RSS the kernel records) first; otherwise `ps -o rss= -p <pid>` (macOS / BSD —
|
|
51
73
|
# reports CURRENT RSS, the closest universally-available proxy). Returns nil when neither route works
|
|
@@ -146,7 +168,7 @@ module Rigor
|
|
|
146
168
|
end
|
|
147
169
|
out.puts("#{prefix}Process")
|
|
148
170
|
out.puts("#{prefix} Wall time: #{Kernel.format('%.2fs', @wall_seconds)}")
|
|
149
|
-
out.puts("#{prefix} Memory peak: #{format_bytes(
|
|
171
|
+
out.puts("#{prefix} Memory peak: #{format_bytes(peak_rss_bytes)}")
|
|
150
172
|
end
|
|
151
173
|
|
|
152
174
|
def to_h
|
|
@@ -159,7 +181,7 @@ module Rigor
|
|
|
159
181
|
gem_walk_classes: @gem_walk_classes,
|
|
160
182
|
gem_walk_gems: @gem_walk_gems,
|
|
161
183
|
wall_seconds: @wall_seconds,
|
|
162
|
-
peak_rss_bytes:
|
|
184
|
+
peak_rss_bytes: peak_rss_bytes
|
|
163
185
|
}
|
|
164
186
|
end
|
|
165
187
|
|
|
@@ -36,6 +36,8 @@ module Rigor
|
|
|
36
36
|
# that stood down against a colliding generic arity (#610), as
|
|
37
37
|
# `[path, class_name, existing_arity, incoming_arity, existing_file]` tuples. Defaults to none so
|
|
38
38
|
# a caller that snapshots nothing of the kind need not say so.
|
|
39
|
+
# @param vendored_standdowns_snapshot — issue #1713 — reader returning what each required-feature-gated
|
|
40
|
+
# vendored directory left out (`Environment::RbsLoader#vendored_standdowns`). Defaults to none.
|
|
39
41
|
# @param env_build_failure_snapshot — reader returning the total RBS env-build failure tuple
|
|
40
42
|
# (`[error_class, first_error_line, conflicting_buffer_names]`) or nil when the env built.
|
|
41
43
|
# @param definition_build_failures_snapshot — issue #696 — reader returning the per-class
|
|
@@ -55,7 +57,7 @@ module Rigor
|
|
|
55
57
|
quarantined_signatures_snapshot:, env_build_failure_snapshot:,
|
|
56
58
|
definition_build_failures_snapshot:, hkt_scan_failure_snapshot:,
|
|
57
59
|
conformance_results_snapshot:, signature_standdowns_snapshot: -> { [] },
|
|
58
|
-
pooled_run_disclosures: -> { [] })
|
|
60
|
+
vendored_standdowns_snapshot: -> { [] }, pooled_run_disclosures: -> { [] })
|
|
59
61
|
@configuration = configuration
|
|
60
62
|
@rbs_extended_reporter = rbs_extended_reporter
|
|
61
63
|
@boundary_cross_reporter = boundary_cross_reporter
|
|
@@ -68,6 +70,7 @@ module Rigor
|
|
|
68
70
|
@synthesized_namespaces_snapshot_reader = synthesized_namespaces_snapshot
|
|
69
71
|
@quarantined_signatures_snapshot_reader = quarantined_signatures_snapshot
|
|
70
72
|
@signature_standdowns_snapshot_reader = signature_standdowns_snapshot
|
|
73
|
+
@vendored_standdowns_snapshot_reader = vendored_standdowns_snapshot
|
|
71
74
|
@env_build_failure_snapshot_reader = env_build_failure_snapshot
|
|
72
75
|
@definition_build_failures_snapshot_reader = definition_build_failures_snapshot
|
|
73
76
|
@hkt_scan_failure_snapshot_reader = hkt_scan_failure_snapshot
|
|
@@ -125,7 +128,32 @@ module Rigor
|
|
|
125
128
|
source_family: :builtin
|
|
126
129
|
)
|
|
127
130
|
end
|
|
128
|
-
|
|
131
|
+
scanned = Array(pre_eval_diagnostics_from_scanner).map { |hash| diagnostic_from_hash(hash) }
|
|
132
|
+
suppress_pre_eval(not_found + scanned)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# The ordinary suppression pipeline for the `pre-eval.*` rows: the `# rigor:disable` /
|
|
136
|
+
# `# rigor:disable-file` comments of the file a row is positioned in (a `pre_eval:` file need not be
|
|
137
|
+
# under `paths:`, so its comments are read here rather than by per-file analysis) and the project's
|
|
138
|
+
# `disable:` list. A row positioned at `.rigor.yml` has no Ruby comments (`.yml` is skipped) and answers to
|
|
139
|
+
# `disable:` only.
|
|
140
|
+
# Stream order is the output contract, so survivors are picked out of the original sequence.
|
|
141
|
+
def suppress_pre_eval(diagnostics)
|
|
142
|
+
kept = diagnostics.group_by(&:path).flat_map do |path, group|
|
|
143
|
+
CheckRules.filter_suppressed(
|
|
144
|
+
group, comments: pre_eval_comments(path), disabled_rules: @configuration.disabled_rules
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
kept_ids = kept.to_set(&:object_id)
|
|
148
|
+
diagnostics.select { |diagnostic| kept_ids.include?(diagnostic.object_id) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def pre_eval_comments(path)
|
|
152
|
+
return [].freeze unless File.file?(path) && !path.end_with?(".rbs", ".yml", ".yaml")
|
|
153
|
+
|
|
154
|
+
Prism.parse_file(path).comments
|
|
155
|
+
rescue StandardError
|
|
156
|
+
[].freeze
|
|
129
157
|
end
|
|
130
158
|
|
|
131
159
|
def diagnostic_from_hash(hash)
|
|
@@ -437,11 +465,23 @@ module Rigor
|
|
|
437
465
|
standdowns.map { |entry| build_rbs_plugin_signature_stood_down_diagnostic(entry) }
|
|
438
466
|
end
|
|
439
467
|
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
-
#
|
|
468
|
+
# Issue #1713 — the other outcome that avoided a definition-build failure: a required-feature-gated
|
|
469
|
+
# vendored directory (`data/vendored_gem_sigs/prime/`) left out what another signature source already
|
|
470
|
+
# declares — a member, a whole type, or, when nothing narrower built, the directory. One `:info` per
|
|
471
|
+
# directory, naming the declarations that stood down and the files that displaced them.
|
|
472
|
+
def rbs_vendored_signature_stood_down_diagnostics
|
|
473
|
+
standdowns = vendored_standdowns_snapshot
|
|
474
|
+
return [] if standdowns.nil? || standdowns.empty?
|
|
475
|
+
|
|
476
|
+
standdowns.map { |entry| build_rbs_vendored_signature_stood_down_diagnostic(entry) }
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# The `:info` notices that close the `rbs.coverage.*` ladder, in this order: the namespace synthesis
|
|
480
|
+
# first, then the stand-downs (#610, #1713) — the outcomes that AVOIDED a definition-build failure and so
|
|
481
|
+
# the quietest rows on it. One method so the runner's assembly reads them as one slot.
|
|
443
482
|
def rbs_coverage_notice_diagnostics
|
|
444
|
-
rbs_synthesized_namespace_diagnostics + rbs_plugin_signature_stood_down_diagnostics
|
|
483
|
+
rbs_synthesized_namespace_diagnostics + rbs_plugin_signature_stood_down_diagnostics +
|
|
484
|
+
rbs_vendored_signature_stood_down_diagnostics
|
|
445
485
|
end
|
|
446
486
|
|
|
447
487
|
# Maps the per-run `rigor:v1:conforms-to` scan results into diagnostics (spec: `rbs-extended.md` §
|
|
@@ -769,6 +809,69 @@ module Rigor
|
|
|
769
809
|
)
|
|
770
810
|
end
|
|
771
811
|
|
|
812
|
+
def build_rbs_vendored_signature_stood_down_diagnostic(entry)
|
|
813
|
+
dir, declarations, whole = entry
|
|
814
|
+
Diagnostic.new(
|
|
815
|
+
path: ".rigor.yml",
|
|
816
|
+
line: 1,
|
|
817
|
+
column: 1,
|
|
818
|
+
message: vendored_standdown_message(dir, declarations, whole),
|
|
819
|
+
severity: :info,
|
|
820
|
+
rule: "rbs.coverage.vendored-signature-stood-down",
|
|
821
|
+
source_family: :builtin
|
|
822
|
+
)
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def vendored_standdown_message(dir, declarations, whole)
|
|
826
|
+
subject = "Rigor's vendored `#{dir}` signatures (loaded because the project requires `#{dir}` or lists " \
|
|
827
|
+
"it under `libraries:`)"
|
|
828
|
+
return vendored_whole_standdown_message(subject, dir, declarations) if whole
|
|
829
|
+
|
|
830
|
+
vendored_partial_standdown_message(subject, declarations)
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# One sentence per kind of stand-down present ({Environment::GatedSignaturePlan.standdowns}' reasons).
|
|
834
|
+
VENDORED_STANDDOWN_SENTENCES = {
|
|
835
|
+
"member" => "left out %<count>d method declaration(s) another signature source also makes, since two " \
|
|
836
|
+
"declarations of one method fail the class's definition build, and uses the other " \
|
|
837
|
+
"source's: %<list>s.",
|
|
838
|
+
"type" => "stood %<count>d type(s) down whole, keeping only the types nested in them, because another " \
|
|
839
|
+
"source declares each with a different kind (class or module) or generic arity, or the type " \
|
|
840
|
+
"failed to build beside that source's declaration: %<list>s.",
|
|
841
|
+
"open" => "kept %<count>d type(s) without their superclass, because each failed to build beside the " \
|
|
842
|
+
"other signatures although no other source declares it; calls they no longer answer read " \
|
|
843
|
+
"untyped rather than undefined: %<list>s."
|
|
844
|
+
}.freeze
|
|
845
|
+
private_constant :VENDORED_STANDDOWN_SENTENCES
|
|
846
|
+
|
|
847
|
+
def vendored_partial_standdown_message(subject, declarations)
|
|
848
|
+
sentences = VENDORED_STANDDOWN_SENTENCES.filter_map do |reason, template|
|
|
849
|
+
group = declarations.select { |entry| (entry[2] || "member") == reason }
|
|
850
|
+
next if group.empty?
|
|
851
|
+
|
|
852
|
+
format(template, count: group.size, list: sampled(vendored_standdown_labels(group), 5))
|
|
853
|
+
end
|
|
854
|
+
"#{subject} loaded without some of their declarations. Rigor #{sentences.join(' Rigor ')} The rest of the " \
|
|
855
|
+
"vendored signatures still load. Nothing to fix unless the other declaration is an outdated copy; " \
|
|
856
|
+
"removing it brings the vendored one back."
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def vendored_whole_standdown_message(subject, dir, declarations)
|
|
860
|
+
labels = vendored_standdown_labels(declarations)
|
|
861
|
+
clashes = labels.empty? ? "" : " Declarations it clashed with: #{sampled(labels, 5)}."
|
|
862
|
+
"#{subject} stood down " \
|
|
863
|
+
"entirely: a type they declare failed to build beside the other signature sources even with the " \
|
|
864
|
+
"clashing declarations left out, so calls into `#{dir}`'s API resolve only against what those " \
|
|
865
|
+
"sources declare.#{clashes}"
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
def vendored_standdown_labels(declarations)
|
|
869
|
+
declarations.map do |declaration, cause_file|
|
|
870
|
+
cause = cause_file.nil? ? "" : " (#{relative_signature_path(cause_file.to_s)})"
|
|
871
|
+
"`#{declaration}`#{cause}"
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
772
875
|
def displacing_source_phrase(existing_file)
|
|
773
876
|
return "another loaded signature source" if existing_file.nil?
|
|
774
877
|
|
|
@@ -1196,6 +1299,10 @@ module Rigor
|
|
|
1196
1299
|
@signature_standdowns_snapshot_reader.call
|
|
1197
1300
|
end
|
|
1198
1301
|
|
|
1302
|
+
def vendored_standdowns_snapshot
|
|
1303
|
+
@vendored_standdowns_snapshot_reader.call
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1199
1306
|
def env_build_failure_snapshot
|
|
1200
1307
|
@env_build_failure_snapshot_reader.call
|
|
1201
1308
|
end
|
|
@@ -45,7 +45,8 @@ module Rigor
|
|
|
45
45
|
snapshots:, plugin_registry:, dependency_source_index:,
|
|
46
46
|
synthetic_method_index:, project_patched_methods:,
|
|
47
47
|
analyze_file:, project_scope_seed: -> { {} }, record_dependencies: false,
|
|
48
|
-
restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty }
|
|
48
|
+
restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty },
|
|
49
|
+
feature_sources: -> { {} }, plugin_prepare: -> { [[], []] })
|
|
49
50
|
@configuration = configuration
|
|
50
51
|
@cache_store = cache_store
|
|
51
52
|
@explain = explain
|
|
@@ -67,6 +68,7 @@ module Rigor
|
|
|
67
68
|
@collected_run_disclosures = {}
|
|
68
69
|
@collect_stats = collect_stats
|
|
69
70
|
@buffer = buffer
|
|
71
|
+
@feature_sources = feature_sources
|
|
70
72
|
@environment_override = environment_override
|
|
71
73
|
@rbs_extended_reporter = rbs_extended_reporter
|
|
72
74
|
@boundary_cross_reporter = boundary_cross_reporter
|
|
@@ -82,6 +84,10 @@ module Rigor
|
|
|
82
84
|
# inherits the same compiled bytes.
|
|
83
85
|
@template_units_reader = template_units
|
|
84
86
|
@analyze_file = analyze_file
|
|
87
|
+
# Issue #1541 — `() -> [rows, disclosure records]`, a `#prepare` run on a throwaway registry. Read
|
|
88
|
+
# only by {#serve_empty_closure}: a pooled run's `#prepare` happens inside its workers, and an
|
|
89
|
+
# empty closure dispatches none.
|
|
90
|
+
@plugin_prepare = plugin_prepare
|
|
85
91
|
# Issues #796 / #794 — the previous full run's run-level rows, handed down by
|
|
86
92
|
# {Analysis::IncrementalSession} on a NARROWED run only (a recheck's closure, a
|
|
87
93
|
# `--verify-incremental` partition). A full run leaves this nil and derives everything itself.
|
|
@@ -144,10 +150,8 @@ module Rigor
|
|
|
144
150
|
# (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing of `files`; see
|
|
145
151
|
# {#serve_empty_closure} for what an empty `files` does with it.
|
|
146
152
|
def analyze_files(files, environment: nil, project_files: nil)
|
|
147
|
-
if files.empty?
|
|
148
|
-
|
|
149
|
-
return []
|
|
150
|
-
end
|
|
153
|
+
return serve_empty_closure(environment, project_files) if files.empty?
|
|
154
|
+
|
|
151
155
|
# Issue #784 / #793 — `files` is what this run ANALYSES; `source_files` is what its environment
|
|
152
156
|
# is BUILT over, and the two are the same only on a full run. A narrowed run (`analyze_only`: an
|
|
153
157
|
# incremental closure, a `--verify-incremental` partition) used to build its environment over the
|
|
@@ -197,7 +201,16 @@ module Rigor
|
|
|
197
201
|
# whether anyone could have demanded anything at all: with no files, nobody could have, and an
|
|
198
202
|
# empty project keeps paying no env build. Keyed on the project's files rather than on
|
|
199
203
|
# `analyze_only`, because a recheck over an EMPTY project narrows to `Set[]`, which is non-nil.
|
|
204
|
+
#
|
|
205
|
+
# Issue #1541 — and, in pool mode, its `prepare` rows. A pooled run's `#prepare` happens inside the
|
|
206
|
+
# workers and its rows reach the stream through their first payload, so the coordinator-side
|
|
207
|
+
# pre-pass skips it ({ProjectPrePasses#run}) and {DiagnosticAggregator#pre_file_diagnostics} omits
|
|
208
|
+
# the cached rows; with no worker dispatched the row appeared nowhere, and a plugin whose `#prepare`
|
|
209
|
+
# raises turned a red project green on a nothing-changed `--incremental --workers N` recheck. They
|
|
210
|
+
# are returned as the run's per-file stream head, where the pool path puts them (`.rigor.yml`).
|
|
211
|
+
# Sequential, whose pre-pass already ran `#prepare`, returns no rows here.
|
|
200
212
|
def serve_empty_closure(environment, project_files)
|
|
213
|
+
prepare_rows = pool_mode? ? pooled_prepare_rows : []
|
|
201
214
|
env = environment || @environment_override
|
|
202
215
|
# Resolved over the project's OWN file list — not `[]`, which would drop every plugin-synthesized
|
|
203
216
|
# virtual RBS (`Environment.collect_virtual_rbs` short-circuits on an empty list) and scan a
|
|
@@ -208,23 +221,40 @@ module Rigor
|
|
|
208
221
|
|
|
209
222
|
env = resolve_sequential_environment(source_files: project_files)
|
|
210
223
|
end
|
|
211
|
-
snapshot_project_signature_state(signature_state_environment(resolve))
|
|
224
|
+
snapshot_project_signature_state(signature_state_environment(resolve, project_files))
|
|
212
225
|
snapshot_effect_annotation_carrier(
|
|
213
226
|
annotation_carrier_environment(resolve, in_hand: !env.nil?)&.rbs_loader
|
|
214
227
|
)
|
|
215
228
|
# `env` rather than `resolve.call`: read the demand the snapshots above just made when they built
|
|
216
229
|
# an environment, and force nothing when they did not.
|
|
217
230
|
record_definition_build_failures(env&.rbs_loader&.definition_build_failures)
|
|
218
|
-
|
|
231
|
+
record_hkt_scan_failure(hkt_scan_outcome(resolve.call)) unless replayed_restored_run_level_rows?
|
|
232
|
+
prepare_rows
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Issue #1541 — the pooled empty closure's `#prepare`: the rows go to the caller, the run-scoped
|
|
236
|
+
# registrations into the table the workers' payloads would have filled.
|
|
237
|
+
def pooled_prepare_rows
|
|
238
|
+
rows, records = @plugin_prepare.call
|
|
239
|
+
merge_run_disclosures(records)
|
|
240
|
+
Array(rows)
|
|
241
|
+
end
|
|
219
242
|
|
|
220
|
-
|
|
243
|
+
# Issue #794 — the signature-state snapshot's own gates, asked BEFORE an environment exists. Every
|
|
244
|
+
# answer is loader-free (the configuration, the plugin registry and, issue #1713, the required-feature
|
|
245
|
+
# scan), and with no gate open {#snapshot_project_signature_state} writes the same empty state a nil
|
|
246
|
+
# environment gives it.
|
|
247
|
+
def signature_state_environment(resolve, project_files)
|
|
248
|
+
gated = plugin_signature_paths? || project_signature_paths? || vendored_features_requested?(project_files)
|
|
249
|
+
gated ? resolve.call : nil
|
|
221
250
|
end
|
|
222
251
|
|
|
223
|
-
# Issue #
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
def
|
|
227
|
-
|
|
252
|
+
# Issue #1713 — whether this run activates a required-feature-gated vendored directory, whose stand-down
|
|
253
|
+
# {#snapshot_project_signature_state} reports: the scan every build of this run gates on, or the
|
|
254
|
+
# feature named under `libraries:`.
|
|
255
|
+
def vendored_features_requested?(project_files)
|
|
256
|
+
features = project_files.nil? || project_files.empty? ? [] : required_features_for(project_files)
|
|
257
|
+
!Environment::RequiredFeatures.tokens(features, @configuration.libraries).empty?
|
|
228
258
|
end
|
|
229
259
|
|
|
230
260
|
# Issue #794 — the same, for the #441 carrier. The carrier is a filter over the loader's
|
|
@@ -344,6 +374,10 @@ module Rigor
|
|
|
344
374
|
# no such plugin reads nothing off its loader here, as before.
|
|
345
375
|
@snapshots.signature_standdowns =
|
|
346
376
|
plugin_signature_paths? ? signature_standdowns_for(environment&.rbs_loader) : []
|
|
377
|
+
# Issue #1713 — NOT behind the `signature_paths:` gate either: what displaces a vendored declaration may
|
|
378
|
+
# be a gem's or a collection's copy. The loader answers without building its env when no gated
|
|
379
|
+
# directory is active.
|
|
380
|
+
@snapshots.vendored_standdowns = vendored_standdowns_for(environment&.rbs_loader)
|
|
347
381
|
unless project_signature_paths?
|
|
348
382
|
@snapshots.synthesized_namespaces = []
|
|
349
383
|
@snapshots.quarantined_signatures = []
|
|
@@ -459,7 +493,22 @@ module Rigor
|
|
|
459
493
|
**ProjectEnvironment.dependency_discovery_options(@configuration),
|
|
460
494
|
synthetic_method_index: synthetic_method_index,
|
|
461
495
|
project_patched_methods: project_patched_methods,
|
|
462
|
-
source_files: source_files
|
|
496
|
+
source_files: source_files,
|
|
497
|
+
required_features: required_features_for(source_files)
|
|
498
|
+
)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# Issue #1700 — the {Environment::RequiredFeatures} this run's environments gate on: the configured paths
|
|
502
|
+
# plus `source_files`, whichever files the run checks, with an editor buffer or an in-memory source read
|
|
503
|
+
# in place of its file. Taken once per file list and handed to every build (the sequential one, the
|
|
504
|
+
# pool's prewarm and each worker), so they agree and a pool does not re-read the project per worker.
|
|
505
|
+
def required_features_for(source_files)
|
|
506
|
+
key = source_files.map(&:to_s)
|
|
507
|
+
return @required_features if @required_features_key == key
|
|
508
|
+
|
|
509
|
+
@required_features_key = key
|
|
510
|
+
@required_features = Ractor.make_shareable(
|
|
511
|
+
Environment::RequiredFeatures.for_configuration(@configuration, key, sources: @feature_sources.call)
|
|
463
512
|
)
|
|
464
513
|
end
|
|
465
514
|
|
|
@@ -575,9 +624,10 @@ module Rigor
|
|
|
575
624
|
# a frozen Array<String>; cheaply shareable. Issue #793 — it IS the full project now
|
|
576
625
|
# (`source_files`), not the analyzed subset this comment always described.
|
|
577
626
|
shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
|
|
627
|
+
features = required_features_for(source_files)
|
|
578
628
|
|
|
579
629
|
pool = Array.new(@workers) do
|
|
580
|
-
Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems| # rubocop:disable Layout/LineLength
|
|
630
|
+
Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems, features) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems, features| # rubocop:disable Layout/LineLength
|
|
581
631
|
cache_store = cache_root ? Rigor::Cache::Store.new(root: cache_root) : nil
|
|
582
632
|
session = Rigor::Analysis::WorkerSession.new(
|
|
583
633
|
configuration: configuration,
|
|
@@ -585,7 +635,8 @@ module Rigor
|
|
|
585
635
|
plugin_blueprints: blueprints,
|
|
586
636
|
explain: explain,
|
|
587
637
|
source_files: shareable_source_files,
|
|
588
|
-
locked_gems: locked_gems
|
|
638
|
+
locked_gems: locked_gems,
|
|
639
|
+
required_features: features
|
|
589
640
|
)
|
|
590
641
|
main = Ractor.main
|
|
591
642
|
main.send([:prepare, session.prepare_diagnostics])
|
|
@@ -667,8 +718,9 @@ module Rigor
|
|
|
667
718
|
# Issue #1051, known exception: run-scoped disclosures a plugin registered from `#prepare` are NOT
|
|
668
719
|
# recovered here — nor, since #1060, a positioned `#emit_once` batch registered there, which rides
|
|
669
720
|
# the same table. There is no session to drain — this backend builds none on the coordinator — and
|
|
670
|
-
# `#prepare`
|
|
671
|
-
#
|
|
721
|
+
# `#prepare` does not run on the coordinator-side registry under pool mode (only an empty closure
|
|
722
|
+
# runs it, on a throwaway registry; see {#serve_empty_closure}), so a prepare-time disclosure lives
|
|
723
|
+
# only inside the Ractor that died with it. A disclosure registered
|
|
672
724
|
# from `#diagnostics_for_file` IS recovered, because the re-analysis below runs the coordinator-side
|
|
673
725
|
# plugin instances that {DiagnosticAggregator#plugin_run_disclosure_diagnostics} reads directly.
|
|
674
726
|
# Left as is: recovering the rest means running `#prepare` on the coordinator registry, which would
|
|
@@ -713,7 +765,8 @@ module Rigor
|
|
|
713
765
|
project_scope_seed: project_scope_seed,
|
|
714
766
|
source_files: source_files,
|
|
715
767
|
record_dependencies: @record_dependencies,
|
|
716
|
-
template_units: template_units
|
|
768
|
+
template_units: template_units,
|
|
769
|
+
required_features: required_features_for(source_files)
|
|
717
770
|
)
|
|
718
771
|
# Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
|
|
719
772
|
# rather than each rebuilding it after the fork.
|
|
@@ -890,6 +943,7 @@ module Rigor
|
|
|
890
943
|
plugin_registry: plugin_registry,
|
|
891
944
|
source_files: source_files,
|
|
892
945
|
locked_gems: locked_gems,
|
|
946
|
+
required_features: required_features_for(source_files),
|
|
893
947
|
**ProjectEnvironment.dependency_discovery_options(@configuration)
|
|
894
948
|
)
|
|
895
949
|
warm_env.rbs_loader&.prewarm
|
|
@@ -1094,6 +1148,10 @@ module Rigor
|
|
|
1094
1148
|
loader.signature_standdowns
|
|
1095
1149
|
end
|
|
1096
1150
|
|
|
1151
|
+
def vendored_standdowns_for(loader)
|
|
1152
|
+
loader.respond_to?(:vendored_standdowns) ? loader.vendored_standdowns : []
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1097
1155
|
def plugin_registry
|
|
1098
1156
|
@plugin_registry_reader.call
|
|
1099
1157
|
end
|
|
@@ -59,7 +59,7 @@ module Rigor
|
|
|
59
59
|
:unpositioned_mixins, :discovered_class_sources, :constant_values, :constant_sources, :constant_writes,
|
|
60
60
|
:discovered_method_visibilities, :discovered_methods, :discovered_parameter_envelopes, :data_member_layouts,
|
|
61
61
|
:struct_member_layouts, :discovered_deferred_ranges, :discovered_refinements, :siblings,
|
|
62
|
-
:discovered_global_write_census
|
|
62
|
+
:discovered_global_write_census, :singleton_class_sources
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
# Internal: drives every EAGER project-wide pre-pass — the ones whose products feed the RBS
|
|
@@ -175,6 +175,8 @@ module Rigor
|
|
|
175
175
|
# object's `extend`s reach `Narrowing` from a sibling file the way its `include`s already do.
|
|
176
176
|
discovered_extends: def_index.fetch(:extends), unpositioned_mixins: def_index.fetch(:unpositioned_mixins),
|
|
177
177
|
discovered_class_sources: def_index.fetch(:class_sources),
|
|
178
|
+
# Issue #1752 — the files opening `class << Const`, for the class-declaration diff alone.
|
|
179
|
+
singleton_class_sources: def_index.fetch(:singleton_class_sources, {}),
|
|
178
180
|
# Issue #644 — the cross-file value-constant publication table and its write attribution.
|
|
179
181
|
constant_values: def_index.fetch(:constant_values), constant_sources: def_index.fetch(:constant_sources),
|
|
180
182
|
constant_writes: def_index.fetch(:constant_writes),
|
|
@@ -251,6 +253,24 @@ module Rigor
|
|
|
251
253
|
registry
|
|
252
254
|
end
|
|
253
255
|
|
|
256
|
+
# Issue #1541 — what a pooled run's workers would have produced from `#prepare`, for the coordinator's
|
|
257
|
+
# empty-closure branch, which dispatches none: the `#prepare` error rows and the run-scoped
|
|
258
|
+
# registrations (`#disclose_once` / `#emit_once`) made there. It runs on a THROWAWAY registry, so
|
|
259
|
+
# nothing `#prepare` records on a plugin instance (a trust refusal, say) reaches the coordinator-side
|
|
260
|
+
# instances {DiagnosticAggregator} reads — a pooled cold run never shows those either.
|
|
261
|
+
# Returns the rows first, then the disclosure records.
|
|
262
|
+
def prepare_throwaway_registry
|
|
263
|
+
registry = load_plugins
|
|
264
|
+
rows = plugin_prepare_diagnostics(registry)
|
|
265
|
+
records = registry.plugins.flat_map do |plugin|
|
|
266
|
+
plugin_id = safe_plugin_id(plugin)
|
|
267
|
+
Array(plugin.run_disclosure_records).map { |record| record.merge(plugin_id: plugin_id) }
|
|
268
|
+
rescue StandardError
|
|
269
|
+
[]
|
|
270
|
+
end
|
|
271
|
+
[rows, records]
|
|
272
|
+
end
|
|
273
|
+
|
|
254
274
|
# Returns the per-run shared `Plugin::FactStore` instance. All loaded plugins share this store
|
|
255
275
|
# through their respective `Plugin::Services` (the same instance is threaded by
|
|
256
276
|
# `Plugin::Loader.load`). Returns `nil` when no plugins are loaded.
|