From 9e8dc865bda6fe70ccf1b252613da65031f6306a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Sep 2026 09:13:31 +0200 Subject: [PATCH] Reproduce the result cache keeping an error from a rule built on collected data Rules that read collected data run in AnalyserResultFinalizer, and the result cache is written before that, so their errors are never in the cached error map. That map is what restore() sweeps when a new file appears - the recovery for an error that could record no dependency, because the symbol it is about did not exist yet. A file whose only error comes from collected data is never in that sweep, so it keeps the error after the symbol arrives, while a run from a cleared cache reports nothing. src/Caller.php calls an unqualified function that falls back to a pure global one, so its only error comes from a CollectedDataNode rule. src/PlainCaller.php has an ordinary error and is there to prove the sweep ran at all. Creating src/nsfns.php defines both missing symbols at once: the ordinary error goes away, the collector-backed one does not. The test fails: the cached run reports one error and exits 1, a run with a cleared cache reports none. Amp-Thread-ID: https://ampcode.com/threads/T-01a19219-5899-7222-8fa0-6cb7e6fb7182 --- .github/workflows/e2e-tests.yml | 15 +++++++++++++++ .../appears/nsfns.php | 15 +++++++++++++++ e2e/result-cache-collector-sweep/phpstan.neon | 5 +++++ e2e/result-cache-collector-sweep/src/Caller.php | 8 ++++++++ .../src/PlainCaller.php | 13 +++++++++++++ .../src/globalfns.php | 6 ++++++ e2e/result-cache-collector-sweep/tmp/.gitignore | 2 ++ 7 files changed, 64 insertions(+) create mode 100644 e2e/result-cache-collector-sweep/appears/nsfns.php create mode 100644 e2e/result-cache-collector-sweep/phpstan.neon create mode 100644 e2e/result-cache-collector-sweep/src/Caller.php create mode 100644 e2e/result-cache-collector-sweep/src/PlainCaller.php create mode 100644 e2e/result-cache-collector-sweep/src/globalfns.php create mode 100644 e2e/result-cache-collector-sweep/tmp/.gitignore diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 84cfa939732..f3e588fdbdd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1622,6 +1622,21 @@ jobs: OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") echo "$OUTPUT" ../bashunit assert contains 'register a class in the container; re-analysing everything.' "$OUTPUT" + - script: | + cd e2e/result-cache-collector-sweep + # Two errors: one from an ordinary rule, one from a rule built on collected data. + OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") + echo "$OUTPUT" + ../bashunit assert contains 'PlainCaller.php:8:Method ResultCacheE2ECollectorSweep\PlainCaller::doPlainCaller() has invalid return type ResultCacheE2ECollectorSweep\Missing.' "$OUTPUT" + ../bashunit assert contains 'Caller.php:7:Call to function resultCacheE2ECollectorHelper() on a separate line has no effect.' "$OUTPUT" + # One new file defines both missing symbols. The files with errors are re-analysed - but + # the errors of the collector-backed rule are produced after the cache is written, so the + # file holding one is not in the cached error map and never gets swept. + cp appears/nsfns.php src/nsfns.php + OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw") + echo "$OUTPUT" + ../bashunit assert not_contains 'has invalid return type' "$OUTPUT" + ../bashunit assert not_contains 'on a separate line has no effect' "$OUTPUT" - script: | cd e2e/result-cache-extension-update composer install diff --git a/e2e/result-cache-collector-sweep/appears/nsfns.php b/e2e/result-cache-collector-sweep/appears/nsfns.php new file mode 100644 index 00000000000..1ae4ad8a1d9 --- /dev/null +++ b/e2e/result-cache-collector-sweep/appears/nsfns.php @@ -0,0 +1,15 @@ +