rigortype 0.2.8 → 0.3.0
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 +2 -2
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +4 -3
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +99 -4
- data/docs/manual/02-cli-reference.md +65 -19
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +5 -5
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +21 -0
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +207 -60
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +56 -30
- data/lib/rigor/cli/diagnostic_formats.rb +4 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_report.rb +7 -1
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +71 -43
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +379 -113
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +245 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +70 -10
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +460 -49
- data/lib/rigor/inference/statement_evaluator.rb +111 -17
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +23 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +21 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +13 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +65 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +20 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +22 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +191 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +19 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +48 -15
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +42 -12
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +49 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# Runtime CI-environment detection (ADR-51 WD7), modelled on `OndraM/ci-detector` (the library PHPStan uses). Reads
|
|
5
|
+
# the well-known environment variables a CI provider sets and returns the matching {Platform}, classifying it into a
|
|
6
|
+
# **tier** that decides how `rigor check` surfaces diagnostics there:
|
|
7
|
+
#
|
|
8
|
+
# :native_stdout — Rigor has a native format that renders purely from
|
|
9
|
+
# stdout, so it is auto-emitted on top of the human
|
|
10
|
+
# output (GitHub Actions → `github`, TeamCity →
|
|
11
|
+
# `teamcity`). These are the first-class platforms.
|
|
12
|
+
# :native_artifact — Rigor has a native format but it needs a CI-wired
|
|
13
|
+
# report artifact, not stdout (GitLab CI → `gitlab`).
|
|
14
|
+
# First-class, but Rigor only *hints* the format.
|
|
15
|
+
# :reviewdog — no native Rigor format; second-class, routed through
|
|
16
|
+
# reviewdog (`checkstyle`/`sarif`) or `junit`. Hint
|
|
17
|
+
# only.
|
|
18
|
+
#
|
|
19
|
+
# Lives outside the CLI namespace because it is a pure function of the environment hash with no CLI dependency, and
|
|
20
|
+
# non-CLI consumers exist: {Configuration#cache_validation_strict?} resolves the `auto` cache-validation default
|
|
21
|
+
# through it (a fresh CI checkout regenerates every stat tuple, so ADR-87's stat tier cannot work there).
|
|
22
|
+
# Detection is fully testable; callers pass `ENV`.
|
|
23
|
+
# `RIGOR_CI_DETECT=0` (or `false`/`no`) disables it globally — the seam the spec suite uses for determinism.
|
|
24
|
+
module CiDetector
|
|
25
|
+
Platform = Struct.new(:id, :name, :format, :tier, keyword_init: true) do
|
|
26
|
+
def native_stdout? = tier == :native_stdout
|
|
27
|
+
def native_artifact? = tier == :native_artifact
|
|
28
|
+
def reviewdog? = tier == :reviewdog
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The detection table, ordered most-specific first so the generic `CI=true` catch-all is last (a provider that
|
|
32
|
+
# also sets `CI` is still recognised by its own variable). `match` is `:truthy` (value in 1/true/yes/on),
|
|
33
|
+
# `:present` (variable set non-empty), or `:equals`.
|
|
34
|
+
PROVIDERS = [
|
|
35
|
+
{ id: "github-actions", name: "GitHub Actions", format: "github", tier: :native_stdout,
|
|
36
|
+
var: "GITHUB_ACTIONS", match: :truthy },
|
|
37
|
+
{ id: "gitlab", name: "GitLab CI", format: "gitlab", tier: :native_artifact,
|
|
38
|
+
var: "GITLAB_CI", match: :truthy },
|
|
39
|
+
{ id: "teamcity", name: "TeamCity", format: "teamcity", tier: :native_stdout,
|
|
40
|
+
var: "TEAMCITY_VERSION", match: :present },
|
|
41
|
+
{ id: "circleci", name: "CircleCI", format: nil, tier: :reviewdog,
|
|
42
|
+
var: "CIRCLECI", match: :truthy },
|
|
43
|
+
{ id: "jenkins", name: "Jenkins", format: nil, tier: :reviewdog,
|
|
44
|
+
var: "JENKINS_URL", match: :present },
|
|
45
|
+
{ id: "travis", name: "Travis CI", format: nil, tier: :reviewdog,
|
|
46
|
+
var: "TRAVIS", match: :truthy },
|
|
47
|
+
{ id: "appveyor", name: "AppVeyor", format: nil, tier: :reviewdog,
|
|
48
|
+
var: "APPVEYOR", match: :truthy },
|
|
49
|
+
{ id: "azure-pipelines", name: "Azure Pipelines", format: nil, tier: :reviewdog,
|
|
50
|
+
var: "TF_BUILD", match: :present },
|
|
51
|
+
{ id: "bitbucket", name: "Bitbucket Pipelines", format: nil, tier: :reviewdog,
|
|
52
|
+
var: "BITBUCKET_BUILD_NUMBER", match: :present },
|
|
53
|
+
{ id: "buildkite", name: "Buildkite", format: nil, tier: :reviewdog,
|
|
54
|
+
var: "BUILDKITE", match: :truthy },
|
|
55
|
+
{ id: "drone", name: "Drone CI", format: nil, tier: :reviewdog,
|
|
56
|
+
var: "DRONE", match: :truthy },
|
|
57
|
+
{ id: "semaphore", name: "Semaphore", format: nil, tier: :reviewdog,
|
|
58
|
+
var: "SEMAPHORE", match: :truthy },
|
|
59
|
+
{ id: "codeship", name: "Codeship", format: nil, tier: :reviewdog,
|
|
60
|
+
var: "CI_NAME", match: :equals, value: "codeship" },
|
|
61
|
+
{ id: "ci", name: "CI", format: nil, tier: :reviewdog,
|
|
62
|
+
var: "CI", match: :truthy }
|
|
63
|
+
].freeze
|
|
64
|
+
|
|
65
|
+
module_function
|
|
66
|
+
|
|
67
|
+
# Returns the detected {Platform}, or nil when no CI is recognised or detection is disabled via `RIGOR_CI_DETECT`.
|
|
68
|
+
def detect(env = ENV)
|
|
69
|
+
return nil if disabled?(env)
|
|
70
|
+
|
|
71
|
+
row = PROVIDERS.find { |provider| matches?(env, provider) }
|
|
72
|
+
return nil if row.nil?
|
|
73
|
+
|
|
74
|
+
Platform.new(id: row[:id], name: row[:name], format: row[:format], tier: row[:tier])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def disabled?(env)
|
|
78
|
+
%w[0 false no off].include?(env["RIGOR_CI_DETECT"].to_s.strip.downcase)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def matches?(env, provider)
|
|
82
|
+
value = env[provider[:var]].to_s.strip
|
|
83
|
+
case provider[:match]
|
|
84
|
+
when :truthy then %w[1 true yes on].include?(value.downcase)
|
|
85
|
+
when :present then !value.empty?
|
|
86
|
+
when :equals then value.downcase == provider[:value]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -9,11 +9,13 @@ require_relative "../configuration"
|
|
|
9
9
|
require_relative "options"
|
|
10
10
|
require_relative "../environment"
|
|
11
11
|
require_relative "../scope"
|
|
12
|
+
require_relative "../source/node_children"
|
|
12
13
|
require_relative "../inference/def_return_typer"
|
|
13
14
|
require_relative "../inference/scope_indexer"
|
|
14
15
|
require_relative "../inference/statement_evaluator"
|
|
15
16
|
require_relative "prism_colorizer"
|
|
16
17
|
require_relative "command"
|
|
18
|
+
require_relative "probe_environment"
|
|
17
19
|
|
|
18
20
|
module Rigor
|
|
19
21
|
class CLI
|
|
@@ -105,7 +107,7 @@ module Rigor
|
|
|
105
107
|
# bindings, so a loop-body line annotates the joined widened type (`Integer`) rather than a stale
|
|
106
108
|
# first-iterations constant (`1 | 2`).
|
|
107
109
|
scope_index = Inference::ScopeIndexer.index(
|
|
108
|
-
parse_result.value, default_scope: base_scope(configuration),
|
|
110
|
+
parse_result.value, default_scope: base_scope(configuration, file),
|
|
109
111
|
converged_loop_recording: true
|
|
110
112
|
)
|
|
111
113
|
line_types = LineTypeCollector.new(scope_index).collect(parse_result.value)
|
|
@@ -128,12 +130,12 @@ module Rigor
|
|
|
128
130
|
@out.puts(JSON.generate({ "annotations" => annotations }))
|
|
129
131
|
end
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
# The plugin-aware environment for the annotated file, so a `#=> <type>` matches what `rigor check`
|
|
134
|
+
# infers on that line — including types synthesized from the file's own inline RBS annotations (the file
|
|
135
|
+
# is threaded as the synthesizer's `source_files:`; see {ProbeEnvironment}).
|
|
136
|
+
def base_scope(configuration, file)
|
|
132
137
|
Scope.empty(
|
|
133
|
-
environment:
|
|
134
|
-
libraries: configuration.libraries,
|
|
135
|
-
signature_paths: configuration.signature_paths
|
|
136
|
-
)
|
|
138
|
+
environment: ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
137
139
|
)
|
|
138
140
|
end
|
|
139
141
|
|
|
@@ -247,14 +249,18 @@ module Rigor
|
|
|
247
249
|
block.call(stmt)
|
|
248
250
|
end
|
|
249
251
|
else
|
|
250
|
-
node.
|
|
252
|
+
node.rigor_each_child { |child| each_statement(child, &block) }
|
|
251
253
|
end
|
|
252
254
|
end
|
|
253
255
|
|
|
254
256
|
# For a line no statement closes (the `if` / block header lines), fall back to the widest expression ending there.
|
|
257
|
+
# A hash-pair node (`1 => 2,` inside a multi-line literal / keyword-argument list) is not an expression — typing
|
|
258
|
+
# it only echoes the `Dynamic[top]` fallback — so those interior lines stay bare and the literal's type appears
|
|
259
|
+
# once, on the line the enclosing statement closes.
|
|
255
260
|
def fill_uncovered_lines(program, by_line)
|
|
256
261
|
widest_per_line(program).each do |line, node|
|
|
257
262
|
next if by_line.key?(line)
|
|
263
|
+
next if node.is_a?(Prism::AssocNode) || node.is_a?(Prism::AssocSplatNode)
|
|
258
264
|
|
|
259
265
|
type = node.is_a?(Prism::BlockParametersNode) ? block_params_type(node) : type_of(node)
|
|
260
266
|
by_line[line] = type unless type.nil?
|
|
@@ -298,7 +304,7 @@ module Rigor
|
|
|
298
304
|
return if node.nil?
|
|
299
305
|
|
|
300
306
|
block.call(node)
|
|
301
|
-
node.
|
|
307
|
+
node.rigor_each_child { |child| walk(child, &block) }
|
|
302
308
|
end
|
|
303
309
|
|
|
304
310
|
# Types the node through the flow evaluator (not the bare expression typer) under its recorded entry scope, so
|
|
@@ -331,7 +337,7 @@ module Rigor
|
|
|
331
337
|
return if node.nil?
|
|
332
338
|
|
|
333
339
|
block.call(node) if node.is_a?(Prism::DefNode)
|
|
334
|
-
node.
|
|
340
|
+
node.rigor_each_child { |child| each_def_node(child, &block) }
|
|
335
341
|
end
|
|
336
342
|
end
|
|
337
343
|
end
|
|
@@ -87,8 +87,11 @@ module Rigor
|
|
|
87
87
|
"match-mode: #{options.fetch(:match_mode)})"
|
|
88
88
|
)
|
|
89
89
|
if configuration.baseline_path.nil?
|
|
90
|
+
# Name the config file that actually governs this project (`.rigor.dist.yml` when no local override
|
|
91
|
+
# exists), so the hint points at the file the user must edit.
|
|
92
|
+
config_name = options.fetch(:config) || Configuration.discover || ".rigor.yml"
|
|
90
93
|
@err.puts(
|
|
91
|
-
"rigor: note —
|
|
94
|
+
"rigor: note — `#{config_name}` does not declare `baseline:`; " \
|
|
92
95
|
"add `baseline: #{path}` to activate the suppression."
|
|
93
96
|
)
|
|
94
97
|
end
|
|
@@ -8,12 +8,17 @@ require_relative "../configuration"
|
|
|
8
8
|
require_relative "../config_audit"
|
|
9
9
|
require_relative "../analysis/result"
|
|
10
10
|
require_relative "../analysis/rule_catalog"
|
|
11
|
-
|
|
11
|
+
# The baseline filter runs on EVERY exit path — including the ADR-87 WD4 cache-HIT fast path, which skips
|
|
12
|
+
# `load_check_dependencies` — so it must be a load-time require. It pulls only YAML, never the engine.
|
|
13
|
+
require_relative "../analysis/baseline"
|
|
14
|
+
require_relative "../runtime/jit"
|
|
12
15
|
require_relative "command"
|
|
13
16
|
require_relative "options"
|
|
14
17
|
require_relative "diagnostic_formats"
|
|
15
|
-
require_relative "ci_detector"
|
|
16
|
-
|
|
18
|
+
require_relative "../ci_detector"
|
|
19
|
+
# ADR-87 WD4 — `coverage_scan` and `check_runner_factory` both pull the inference engine, so they are required
|
|
20
|
+
# lazily (in `compute_coverage` / `build_check_runner`) rather than at load time: an ordinary check whose
|
|
21
|
+
# result the run-cache probe serves must reach the hit verdict without `rigor/inference` in $LOADED_FEATURES.
|
|
17
22
|
|
|
18
23
|
module Rigor
|
|
19
24
|
class CLI
|
|
@@ -29,18 +34,34 @@ module Rigor
|
|
|
29
34
|
# concerns that are clearer read together than split across micro-classes.
|
|
30
35
|
class CheckCommand < Command # rubocop:disable Metrics/ClassLength
|
|
31
36
|
# @return [Integer] CLI exit status.
|
|
32
|
-
def run # rubocop:disable Metrics/AbcSize
|
|
33
|
-
|
|
37
|
+
def run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
38
|
+
# Arm deferred YJIT enablement before any analysis work: the deadline
|
|
39
|
+
# thread only fires once a run outlasts the amortization window, so a
|
|
40
|
+
# short check finishes before it ever pays JIT compile cost while a
|
|
41
|
+
# long run JITs its dominant tail (Runtime::Jit).
|
|
42
|
+
Runtime::Jit.enable_after(Runtime::Jit.deadline_seconds)
|
|
43
|
+
# ADR-87 WD4 — parse options + resolve config WITHOUT the inference engine, so the run-cache hit probe
|
|
44
|
+
# can run first. The heavy engine (`load_check_dependencies`) loads only on a miss / non-cacheable run.
|
|
34
45
|
options = parse_check_options
|
|
35
46
|
buffer = Options.resolve_buffer_binding(options, err: @err)
|
|
36
47
|
return CLI::EXIT_USAGE if buffer == :usage_error
|
|
37
48
|
|
|
38
49
|
configuration = load_check_configuration(options)
|
|
39
50
|
configuration = apply_bleeding_edge_override(configuration, options)
|
|
51
|
+
conflict = parameter_inference_incremental_conflict(configuration, options)
|
|
52
|
+
return conflict unless conflict.nil?
|
|
53
|
+
|
|
40
54
|
config_warnings = warn_unresolved_config(configuration)
|
|
41
55
|
cache_root = configuration.cache_path
|
|
42
56
|
handle_clear_cache(cache_root) if options.fetch(:clear_cache)
|
|
43
57
|
|
|
58
|
+
# ADR-87 WD4 — try to serve the whole run from the ADR-45 cache before booting the engine. A hit boots
|
|
59
|
+
# only CLI + config + cache + digest code (no `rigor/inference`), skipping the plugin prepass + env
|
|
60
|
+
# build entirely.
|
|
61
|
+
probed = try_run_cache_hit(configuration, options, buffer, cache_root)
|
|
62
|
+
return finalize_cache_hit(probed, configuration, options, config_warnings) unless probed.nil?
|
|
63
|
+
|
|
64
|
+
load_check_dependencies
|
|
44
65
|
special = dispatch_special_check_mode(configuration, options, cache_root)
|
|
45
66
|
return special unless special.nil?
|
|
46
67
|
|
|
@@ -66,23 +87,113 @@ module Rigor
|
|
|
66
87
|
|
|
67
88
|
private
|
|
68
89
|
|
|
90
|
+
# ADR-87 WD4 — attempt the boot-slimming run-cache hit. Returns the cached {Analysis::Result} (severity
|
|
91
|
+
# profile applied, no stats — matching a cache-served `Runner#run`) on a hit, or nil to fall through to
|
|
92
|
+
# the full engine path. Only an ordinary sequential check whose result IS cache-served the same way is
|
|
93
|
+
# eligible; every other mode (below) declines so the full path handles it unchanged.
|
|
94
|
+
def try_run_cache_hit(configuration, options, buffer, cache_root)
|
|
95
|
+
return nil unless run_cache_hit_eligible?(configuration, options, buffer)
|
|
96
|
+
|
|
97
|
+
require_relative "../analysis/run_cache_probe"
|
|
98
|
+
Analysis::RunCacheProbe.new(
|
|
99
|
+
configuration: configuration, cache_root: cache_root, explain: options.fetch(:explain)
|
|
100
|
+
).serve(@argv.empty? ? configuration.paths : @argv)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The probe is eligible only for a run the ADR-45 result cache actually serves: a writable cache (not
|
|
104
|
+
# `--no-cache`), no editor buffer, sequential (pool runs are not result-cacheable), and no mode needing
|
|
105
|
+
# the engine (`--coverage` / `--incremental` / `--verify-incremental`) or the runner's own reporting
|
|
106
|
+
# (`--cache-stats`, the `RIGOR_*_TRACE` dev probes). A wrongly-permitted run still MISSES (the entry was
|
|
107
|
+
# never written its way) and falls through — the gate is an optimisation, never a soundness boundary.
|
|
108
|
+
def run_cache_hit_eligible?(configuration, options, buffer)
|
|
109
|
+
buffer.nil? &&
|
|
110
|
+
!options.fetch(:no_cache) && !options.fetch(:coverage) && !options.fetch(:cache_stats) &&
|
|
111
|
+
!options.fetch(:incremental) && !options.fetch(:verify_incremental) &&
|
|
112
|
+
!pool_workers_configured?(options, configuration) &&
|
|
113
|
+
ENV["RIGOR_BUDGET_TRACE"].to_s.empty? && ENV["RIGOR_HEAP_TRACE"].to_s.empty?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Any signal that per-file analysis would run in a worker pool (CLI `--workers`, the env override, or the
|
|
117
|
+
# config default). Deliberately over-declines (a false positive only forgoes the fast lane); mirrors
|
|
118
|
+
# {CheckRunnerFactory.resolve_workers} without requiring it (that pulls in the engine).
|
|
119
|
+
def pool_workers_configured?(options, configuration)
|
|
120
|
+
cli = options[:workers]
|
|
121
|
+
return Integer(cli).positive? if cli
|
|
122
|
+
|
|
123
|
+
env = ENV.fetch("RIGOR_RACTOR_WORKERS", nil)
|
|
124
|
+
return Integer(env).positive? if env && !env.empty?
|
|
125
|
+
|
|
126
|
+
configuration.parallel_workers.positive?
|
|
127
|
+
rescue ArgumentError
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# ADR-87 WD4 — the engine-free tail for a cache-served hit: baseline filter + output + exit code,
|
|
132
|
+
# mirroring the full path's tail but without stats (a hit collected none), trace appendices (they read
|
|
133
|
+
# engine counters), or eviction (deferred to the next miss). `raw_result` is pre-baseline so the strict
|
|
134
|
+
# gate reads the same diagnostics the full path would.
|
|
135
|
+
def finalize_cache_hit(raw_result, configuration, options, config_warnings)
|
|
136
|
+
result = apply_baseline_filter(raw_result, configuration, options)
|
|
137
|
+
write_result(result, options.fetch(:format), config_warnings: config_warnings)
|
|
138
|
+
emit_ci_detected_output(result, options)
|
|
139
|
+
|
|
140
|
+
exit_code = result.success? ? 0 : 1
|
|
141
|
+
exit_code = 1 if baseline_strict_violation?(raw_result.diagnostics, configuration, options)
|
|
142
|
+
exit_code
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# ADR-67 WD6c — `parameter_inference:` and the ADR-46 incremental modes are mutually exclusive in slice 1.
|
|
146
|
+
# The parameter table introduces cross-file edges (a caller's argument types drive a callee's body
|
|
147
|
+
# diagnostics) that the per-file dependency recorder does not yet carry, so a cached file whose parameter
|
|
148
|
+
# seeds changed would serve a stale result. Refuse with a message naming the ADR-46 gap rather than
|
|
149
|
+
# silently producing unsound incremental diagnostics; the edge wiring is the named follow-up. Returns the
|
|
150
|
+
# usage exit code on a conflict, or nil to proceed. `--verify-incremental` (the acceptance gate that runs
|
|
151
|
+
# incremental analysis) is caught here too.
|
|
152
|
+
def parameter_inference_incremental_conflict(configuration, options)
|
|
153
|
+
return nil unless configuration.parameter_inference
|
|
154
|
+
return nil unless options.fetch(:incremental) || options.fetch(:verify_incremental)
|
|
155
|
+
|
|
156
|
+
flag = options.fetch(:incremental) ? "--incremental" : "--verify-incremental"
|
|
157
|
+
@err.puts("rigor: parameter_inference: cannot combine with #{flag} — the call-site parameter table " \
|
|
158
|
+
"introduces cross-file caller→callee edges the ADR-46 incremental dependency graph does not " \
|
|
159
|
+
"yet record, so a cached file could serve a stale diagnostic. Run a full check (drop " \
|
|
160
|
+
"#{flag}), or disable parameter_inference: for this run.")
|
|
161
|
+
CLI::EXIT_USAGE
|
|
162
|
+
end
|
|
163
|
+
|
|
69
164
|
# ADR-46 — the two incremental-analysis check modes both fully handle the run and return an exit code (so `run`
|
|
70
165
|
# short-circuits); returns nil for an ordinary check.
|
|
71
166
|
def dispatch_special_check_mode(configuration, options, cache_root)
|
|
72
|
-
return run_verify_incremental(configuration) if options.fetch(:verify_incremental)
|
|
167
|
+
return run_verify_incremental(configuration, options, cache_root) if options.fetch(:verify_incremental)
|
|
73
168
|
return run_incremental_check(configuration, options, cache_root) if options.fetch(:incremental)
|
|
74
169
|
|
|
75
170
|
nil
|
|
76
171
|
end
|
|
77
172
|
|
|
173
|
+
# ADR-85 WD1 — the persistent cache the incremental session threads into each internal Runner so plugin
|
|
174
|
+
# `#prepare` producers (the ADR-9/#74/ADR-60 WD3 record-and-validate caches) and the RBS environment serve
|
|
175
|
+
# from disk across processes / rechecks rather than recomputing every invocation. Honors `--no-cache` (nil
|
|
176
|
+
# = no store), mirroring {CheckRunnerFactory}. The whole-run ADR-45 result cache stays inert on these runs
|
|
177
|
+
# — `Runner#run_result_cacheable?` excludes `record_dependencies` / `analyze_only`.
|
|
178
|
+
def incremental_cache_store(configuration, options, cache_root)
|
|
179
|
+
return nil if options.fetch(:no_cache)
|
|
180
|
+
|
|
181
|
+
Cache::Store.new(root: cache_root, max_bytes: configuration.cache_max_bytes)
|
|
182
|
+
end
|
|
183
|
+
|
|
78
184
|
# ADR-46 — the incremental-analysis acceptance gate. Runs a baseline analysis (recording cross-file dependencies),
|
|
79
185
|
# then re-analyzes a representative subset of files and serves the rest from the per-file cache (the body tier),
|
|
80
186
|
# and asserts the merged diagnostics are byte-identical to a full `--no-cache` analysis. A mismatch means the
|
|
81
187
|
# incremental machinery would serve a stale — manufactured — diagnostic, the soundness failure this gate exists to
|
|
82
188
|
# catch. Prints a one-line PASS (exit 0) or the differing diagnostics (exit 1).
|
|
83
|
-
def run_verify_incremental(configuration)
|
|
189
|
+
def run_verify_incremental(configuration, options, cache_root)
|
|
84
190
|
paths = @argv.empty? ? nil : @argv
|
|
85
|
-
|
|
191
|
+
# ADR-85 WD1 — thread the store so the gate also asserts the cache-served producer path stays
|
|
192
|
+
# byte-identical to the uncached full run (`verify_full_diagnostics` builds a nil-store runner).
|
|
193
|
+
session = Analysis::IncrementalSession.new(
|
|
194
|
+
configuration: configuration, paths: paths,
|
|
195
|
+
cache_store: incremental_cache_store(configuration, options, cache_root)
|
|
196
|
+
)
|
|
86
197
|
session.baseline
|
|
87
198
|
analyzed = session.analyzed_files
|
|
88
199
|
|
|
@@ -101,18 +212,30 @@ module Rigor
|
|
|
101
212
|
# updated snapshot for the next invocation. Diagnostics are identical to a full run (the `--verify-incremental`
|
|
102
213
|
# gate enforces this); the win is skipping per-file inference for unchanged files.
|
|
103
214
|
def run_incremental_check(configuration, options, cache_root)
|
|
215
|
+
require_relative "check_runner_factory"
|
|
104
216
|
paths = @argv.empty? ? nil : @argv
|
|
105
|
-
probe = Analysis::Runner.new(configuration: configuration, cache_store: nil)
|
|
106
|
-
files = paths ? probe.analysis_file_set(paths) : probe.analysis_file_set
|
|
107
217
|
fingerprint = Cache::IncrementalSnapshot.fingerprint(
|
|
108
218
|
configuration: configuration, roots: paths || configuration.paths
|
|
109
219
|
)
|
|
110
220
|
snapshot = Cache::IncrementalSnapshot.new(root: cache_root)
|
|
111
|
-
|
|
221
|
+
store = incremental_cache_store(configuration, options, cache_root)
|
|
222
|
+
session = Analysis::IncrementalSession.new(
|
|
223
|
+
configuration: configuration, paths: paths,
|
|
224
|
+
cache_store: store,
|
|
225
|
+
# ADR-46 — thread the same worker-count precedence the standard `check` path uses
|
|
226
|
+
# (CLI `--workers` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > 0) so the recheck's closure
|
|
227
|
+
# re-analysis parallelises; the fork pool marshals dependency records back so the graph is sound.
|
|
228
|
+
workers: CheckRunnerFactory.resolve_workers(options, configuration)
|
|
229
|
+
)
|
|
112
230
|
|
|
113
231
|
diagnostics, warm = session.run_incremental(snapshot: snapshot, fingerprint: fingerprint)
|
|
232
|
+
# The banner's file count comes from the session's analyzed set (cold analyses all; a warm recheck's
|
|
233
|
+
# `@analyzed` advances to the current file set), so a dedicated probe Runner + `Dir.glob` is no longer
|
|
234
|
+
# built just to size the banner (recon §3 — the analysis tree was expanded three times per recheck).
|
|
114
235
|
@err.puts("rigor: --incremental #{warm ? 'warm — reused cached diagnostics' : 'cold — full analysis'} " \
|
|
115
|
-
"(#{
|
|
236
|
+
"(#{session.analyzed_files.size} files)")
|
|
237
|
+
emit_incremental_fact_surface_notes(session)
|
|
238
|
+
write_incremental_cache_stats(session, cache_root, store) if options.fetch(:cache_stats)
|
|
116
239
|
|
|
117
240
|
result = apply_baseline_filter(Analysis::Result.new(diagnostics: diagnostics, stats: nil), configuration,
|
|
118
241
|
options)
|
|
@@ -120,6 +243,40 @@ module Rigor
|
|
|
120
243
|
result.success? ? 0 : 1
|
|
121
244
|
end
|
|
122
245
|
|
|
246
|
+
# ADR-88 WD1 — a one-line stderr note when the plugin fact surface (an ADR-9 fact, an ADR-60 producer
|
|
247
|
+
# value, or an `incremental_state_fingerprint` hook) forced a full run: either it CHANGED since the
|
|
248
|
+
# snapshot (a Sorbet sig edit, a schema change) or a contributing plugin declares NO fingerprint surface
|
|
249
|
+
# (opaque — a full run every invocation). Both are the sound direction; the note explains why a recheck
|
|
250
|
+
# was not warm.
|
|
251
|
+
def emit_incremental_fact_surface_notes(session)
|
|
252
|
+
opaque = session.opaque_plugin_ids
|
|
253
|
+
unless opaque.empty?
|
|
254
|
+
@err.puts("rigor: --incremental plugin#{'s' unless opaque.size == 1} #{opaque.sort.join(', ')} " \
|
|
255
|
+
"contribute call-site types with no incremental fingerprint surface; the snapshot cannot " \
|
|
256
|
+
"be reused (full analysis every run).")
|
|
257
|
+
end
|
|
258
|
+
return unless session.fact_surface_invalidated? && opaque.empty?
|
|
259
|
+
|
|
260
|
+
@err.puts("rigor: --incremental plugin fact surface changed since the snapshot; ran a full analysis.")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# ADR-88 WD1 — `--incremental --cache-stats` surfaces the on-disk cache inventory (as a plain `check`
|
|
264
|
+
# does) plus the fact-surface status line, so an operator can see whether a full run was fact-surface
|
|
265
|
+
# driven. The plain `check` cache-stats path is bypassed by the incremental short-circuit, so it is
|
|
266
|
+
# emitted here.
|
|
267
|
+
def write_incremental_cache_stats(session, cache_root, store)
|
|
268
|
+
write_cache_stats(cache_root, store)
|
|
269
|
+
status =
|
|
270
|
+
if !session.opaque_plugin_ids.empty?
|
|
271
|
+
"opaque (#{session.opaque_plugin_ids.sort.join(', ')})"
|
|
272
|
+
elsif session.fact_surface_invalidated?
|
|
273
|
+
"changed — snapshot invalidated"
|
|
274
|
+
else
|
|
275
|
+
"unchanged"
|
|
276
|
+
end
|
|
277
|
+
@out.puts(" plugin fact surface: #{status}")
|
|
278
|
+
end
|
|
279
|
+
|
|
123
280
|
def verify_full_diagnostics(configuration, paths)
|
|
124
281
|
runner = Analysis::Runner.new(configuration: configuration, cache_store: nil)
|
|
125
282
|
(paths ? runner.run(paths) : runner.run).diagnostics
|
|
@@ -227,6 +384,7 @@ module Rigor
|
|
|
227
384
|
end
|
|
228
385
|
|
|
229
386
|
def build_check_runner(configuration:, options:, buffer:, cache_root:)
|
|
387
|
+
require_relative "check_runner_factory"
|
|
230
388
|
CheckRunnerFactory.build(
|
|
231
389
|
configuration: configuration, options: options,
|
|
232
390
|
buffer: buffer, cache_root: cache_root
|
|
@@ -391,7 +549,10 @@ module Rigor
|
|
|
391
549
|
end
|
|
392
550
|
|
|
393
551
|
def inject_treat_all_as_inline_rbs(entries)
|
|
394
|
-
|
|
552
|
+
# Single-homed with the WD2 auto-wire gate (`Configuration.rbs_inline_plugin_entry?`) so the flag and
|
|
553
|
+
# the default agree on what "already listed" means; a pre-existing entry is removed so this one's
|
|
554
|
+
# `require_magic_comment: false` wins unconditionally.
|
|
555
|
+
filtered = entries.reject { |entry| Configuration.rbs_inline_plugin_entry?(entry) }
|
|
395
556
|
filtered + [{
|
|
396
557
|
"gem" => "rigor-rbs-inline",
|
|
397
558
|
"id" => "rbs-inline",
|
|
@@ -399,18 +560,6 @@ module Rigor
|
|
|
399
560
|
}]
|
|
400
561
|
end
|
|
401
562
|
|
|
402
|
-
def rigor_rbs_inline_entry?(entry)
|
|
403
|
-
case entry
|
|
404
|
-
when String
|
|
405
|
-
entry == "rigor-rbs-inline"
|
|
406
|
-
when Hash
|
|
407
|
-
string_keyed = entry.to_h { |k, v| [k.to_s, v] }
|
|
408
|
-
string_keyed["gem"] == "rigor-rbs-inline" || string_keyed["id"] == "rbs-inline"
|
|
409
|
-
else
|
|
410
|
-
false
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
563
|
def handle_clear_cache(cache_root)
|
|
415
564
|
if File.directory?(cache_root)
|
|
416
565
|
FileUtils.rm_rf(cache_root)
|
|
@@ -451,6 +600,42 @@ module Rigor
|
|
|
451
600
|
@err.puts(" recursion-fixpoint-cap hits: #{counts[Inference::BudgetTrace::RECURSION_FIXPOINT_CAP]}")
|
|
452
601
|
@err.puts(" block-writeback-cap hits: #{counts[Inference::BudgetTrace::BLOCK_WRITEBACK_CAP]}")
|
|
453
602
|
write_budget_distributions
|
|
603
|
+
write_memo_profile(counts)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Dumps the ADR-57 return-memo profile (WD0). Entries / hit rate / body evals / the non-store split, then
|
|
607
|
+
# the top signatures by body-eval count with their distinct-memo-key counts — the evidence for choosing
|
|
608
|
+
# between a finalization-aware taint gate and memo-key normalization. `--workers 0` for exact counts.
|
|
609
|
+
def write_memo_profile(counts)
|
|
610
|
+
bt = Inference::BudgetTrace
|
|
611
|
+
hits = counts[bt::MEMO_HITS]
|
|
612
|
+
misses = counts[bt::MEMO_MISSES]
|
|
613
|
+
consults = hits + misses
|
|
614
|
+
rate = consults.positive? ? format("%.1f%%", 100.0 * hits / consults) : "n/a"
|
|
615
|
+
@err.puts("")
|
|
616
|
+
@err.puts("Return-memo profile (RIGOR_BUDGET_TRACE; --workers 0 for an exact count)")
|
|
617
|
+
@err.puts(" infer entries: #{counts[bt::MEMO_ENTRIES]}")
|
|
618
|
+
@err.puts(" memo consults: #{consults} (hits #{hits} / misses #{misses}; hit rate #{rate})")
|
|
619
|
+
@err.puts(" body evals: #{counts[bt::MEMO_BODY_EVALS]}")
|
|
620
|
+
@err.puts(" non-stored: on-stack #{counts[bt::MEMO_REFUSE_ON_STACK]} " \
|
|
621
|
+
"unroll-in-flight #{counts[bt::MEMO_REFUSE_UNROLL]} " \
|
|
622
|
+
"consult-tainted #{counts[bt::MEMO_REFUSE_CONSULT_TAINTED]} " \
|
|
623
|
+
"transient-tainted #{counts[bt::MEMO_REFUSE_TRANSIENT]}")
|
|
624
|
+
write_memo_signature_table
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Top 15 signatures by body-eval (compute) count, each with its distinct-memo-key count — a signature
|
|
628
|
+
# whose distinct-key count dwarfs its evals is arg-granularity thrash (the key-normalization candidate).
|
|
629
|
+
def write_memo_signature_table
|
|
630
|
+
bt = Inference::BudgetTrace
|
|
631
|
+
evals = bt.distribution(bt::MEMO_BODY_EVAL_BY_SIGNATURE)
|
|
632
|
+
return if evals.empty?
|
|
633
|
+
|
|
634
|
+
keys = bt.distribution(bt::MEMO_DISTINCT_KEY_BY_SIGNATURE)
|
|
635
|
+
@err.puts(" top signatures by body-eval count (evals / distinct-keys / signature):")
|
|
636
|
+
evals.sort_by { |sig, n| [-n, sig] }.first(15).each do |sig, n|
|
|
637
|
+
@err.puts(" #{n.to_s.rjust(8)} #{keys.fetch(sig, 0).to_s.rjust(6)} #{sig}")
|
|
638
|
+
end
|
|
454
639
|
end
|
|
455
640
|
|
|
456
641
|
# Dumps the read-only size distributions (ADR-41 Slice 2a). These observe how large unions actually get, with no
|
|
@@ -488,7 +673,6 @@ module Rigor
|
|
|
488
673
|
def load_check_dependencies
|
|
489
674
|
require_relative "../analysis/runner"
|
|
490
675
|
require_relative "../analysis/buffer_binding"
|
|
491
|
-
require_relative "../analysis/baseline"
|
|
492
676
|
require_relative "../cache/store"
|
|
493
677
|
start_heap_trace_if_requested
|
|
494
678
|
end
|
|
@@ -625,6 +809,7 @@ module Rigor
|
|
|
625
809
|
def compute_coverage(runner, configuration, options)
|
|
626
810
|
return nil unless options.fetch(:coverage)
|
|
627
811
|
|
|
812
|
+
require_relative "coverage_scan"
|
|
628
813
|
files = @argv.empty? ? runner.analysis_file_set : runner.analysis_file_set(@argv)
|
|
629
814
|
CoverageScan.precision_report(files: files, configuration: configuration)
|
|
630
815
|
end
|
|
@@ -682,7 +867,7 @@ module Rigor
|
|
|
682
867
|
return unless options.fetch(:ci_detect)
|
|
683
868
|
return unless options.fetch(:format) == "text"
|
|
684
869
|
|
|
685
|
-
platform =
|
|
870
|
+
platform = CiDetector.detect
|
|
686
871
|
return if platform.nil?
|
|
687
872
|
|
|
688
873
|
if platform.native_stdout?
|