Skip to content

Commit 8a8b712

Browse files
committed
Merge remote-tracking branch 'origin/1.10.x' into 1.11.x
2 parents f1cf3bb + 925c0b7 commit 8a8b712

23 files changed

+296
-80
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,9 @@ jobs:
177177
cd e2e/result-cache-8
178178
composer install
179179
../../bin/phpstan
180+
echo -en '\n' >> build/CustomRule.php
180181
OUTPUT=$(../../bin/phpstan 2>&1)
181-
grep 'Warning: Result cache might not behave correctly' <<< "$OUTPUT"
182+
grep 'Result cache might not behave correctly' <<< "$OUTPUT"
182183
grep 'ResultCache8E2E\\CustomRule' <<< "$OUTPUT"
183184
184185
steps:

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ parameters:
4646
invalidPhpDocTagLine: true
4747
detectDeadTypeInMultiCatch: true
4848
zeroFiles: true
49+
projectServicesNotInAnalysedPaths: true
4950
callUserFunc: true
5051
finalByPhpDoc: true
5152
magicConstantOutOfContext: true

conf/config.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ parameters:
8181
invalidPhpDocTagLine: false
8282
detectDeadTypeInMultiCatch: false
8383
zeroFiles: false
84+
projectServicesNotInAnalysedPaths: false
8485
callUserFunc: false
8586
finalByPhpDoc: false
8687
magicConstantOutOfContext: false
@@ -986,6 +987,9 @@ services:
986987
-
987988
class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider
988989

990+
-
991+
class: PHPStan\Rules\Methods\LazyAlwaysUsedMethodExtensionProvider
992+
989993
-
990994
class: PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper
991995

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ parametersSchema:
7676
invalidPhpDocTagLine: bool()
7777
detectDeadTypeInMultiCatch: bool()
7878
zeroFiles: bool()
79+
projectServicesNotInAnalysedPaths: bool()
7980
callUserFunc: bool()
8081
finalByPhpDoc: bool()
8182
magicConstantOutOfContext: bool()

src/Analyser/ResultCache/ResultCache.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ResultCache
1717
* @param array<string, array<CollectedData>> $collectedData
1818
* @param array<string, array<string>> $dependencies
1919
* @param array<string, array<RootExportedNode>> $exportedNodes
20+
* @param array<string, array{string, bool, string}> $projectExtensionFiles
2021
*/
2122
public function __construct(
2223
private array $filesToAnalyse,
@@ -28,6 +29,7 @@ public function __construct(
2829
private array $collectedData,
2930
private array $dependencies,
3031
private array $exportedNodes,
32+
private array $projectExtensionFiles,
3133
)
3234
{
3335
}
@@ -98,4 +100,12 @@ public function getExportedNodes(): array
98100
return $this->exportedNodes;
99101
}
100102

103+
/**
104+
* @return array<string, array{string, bool, string}>
105+
*/
106+
public function getProjectExtensionFiles(): array
107+
{
108+
return $this->projectExtensionFiles;
109+
}
110+
101111
}

src/Analyser/ResultCache/ResultCacheManager.php

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\DependencyInjection\ProjectConfigHelper;
1313
use PHPStan\File\CouldNotReadFileException;
1414
use PHPStan\File\FileFinder;
15+
use PHPStan\File\FileHelper;
1516
use PHPStan\File\FileWriter;
1617
use PHPStan\Internal\ComposerHelper;
1718
use PHPStan\PhpDoc\StubFilesProvider;
@@ -34,6 +35,7 @@
3435
use function sha1_file;
3536
use function sort;
3637
use function sprintf;
38+
use function str_starts_with;
3739
use function time;
3840
use function unlink;
3941
use function var_export;
@@ -62,6 +64,7 @@ public function __construct(
6264
private FileFinder $scanFileFinder,
6365
private ReflectionProvider $reflectionProvider,
6466
private StubFilesProvider $stubFilesProvider,
67+
private FileHelper $fileHelper,
6568
private string $cacheFilePath,
6669
private array $analysedPaths,
6770
private array $composerAutoloaderProjectPaths,
@@ -85,21 +88,21 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
8588
if ($output->isDebug()) {
8689
$output->writeLineFormatted('Result cache not used because of debug mode.');
8790
}
88-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
91+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], []);
8992
}
9093
if ($onlyFiles) {
9194
if ($output->isDebug()) {
9295
$output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.');
9396
}
94-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
97+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], []);
9598
}
9699

97100
$cacheFilePath = $this->cacheFilePath;
98101
if (!is_file($cacheFilePath)) {
99102
if ($output->isDebug()) {
100103
$output->writeLineFormatted('Result cache not used because the cache file does not exist.');
101104
}
102-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
105+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], []);
103106
}
104107

105108
try {
@@ -111,7 +114,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
111114

112115
@unlink($cacheFilePath);
113116

114-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
117+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], []);
115118
}
116119

117120
if (!is_array($data)) {
@@ -120,7 +123,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
120123
$output->writeLineFormatted('Result cache not used because the cache file is corrupted.');
121124
}
122125

123-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
126+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], []);
124127
}
125128

126129
$meta = $this->getMeta($allAnalysedFiles, $projectConfigArray);
@@ -129,23 +132,30 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
129132
$diffs = $this->getMetaKeyDifferences($data['meta'], $meta);
130133
$output->writeLineFormatted('Result cache not used because the metadata do not match: ' . implode(', ', $diffs));
131134
}
132-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
135+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], []);
133136
}
134137

135138
if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) {
136139
if ($output->isDebug()) {
137140
$output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.');
138141
}
139142
// run full analysis if the result cache is older than 7 days
140-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
143+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], []);
141144
}
142145

143-
foreach ($data['projectExtensionFiles'] as $extensionFile => $fileHash) {
146+
/**
147+
* @var string $fileHash
148+
* @var bool $isAnalysed
149+
*/
150+
foreach ($data['projectExtensionFiles'] as $extensionFile => [$fileHash, $isAnalysed]) {
151+
if (!$isAnalysed) {
152+
continue;
153+
}
144154
if (!is_file($extensionFile)) {
145155
if ($output->isDebug()) {
146156
$output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile));
147157
}
148-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
158+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], []);
149159
}
150160

151161
if ($this->getFileHash($extensionFile) === $fileHash) {
@@ -156,7 +166,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
156166
$output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile));
157167
}
158168

159-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
169+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], []);
160170
}
161171

162172
$invertedDependencies = $data['dependencies'];
@@ -257,7 +267,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
257267
}
258268
}
259269

260-
return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes);
270+
return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles']);
261271
}
262272

263273
/**
@@ -362,7 +372,11 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
362372
}
363373

364374
$meta = $resultCache->getMeta();
365-
$doSave = function (array $errorsByFile, array $locallyIgnoredErrorsByFile, $collectedDataByFile, ?array $dependencies, array $exportedNodes) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool {
375+
$projectConfigArray = $meta['projectConfig'];
376+
if ($projectConfigArray !== null) {
377+
$meta['projectConfig'] = Neon::encode($projectConfigArray);
378+
}
379+
$doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $collectedDataByFile, ?array $dependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool {
366380
if ($onlyFiles) {
367381
if ($output->isDebug()) {
368382
$output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.');
@@ -397,7 +411,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
397411
}
398412
}
399413

400-
$this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $meta);
414+
$this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $meta);
401415

402416
if ($output->isDebug()) {
403417
$output->writeLineFormatted('Result cache is saved.');
@@ -408,8 +422,12 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
408422

409423
if ($resultCache->isFullAnalysis()) {
410424
$saved = false;
411-
if ($save) {
412-
$saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes());
425+
if ($save !== false) {
426+
$projectExtensionFiles = [];
427+
if ($analyserResult->getDependencies() !== null) {
428+
$projectExtensionFiles = $this->getProjectExtensionFiles($projectConfigArray, $analyserResult->getDependencies());
429+
}
430+
$saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles);
413431
} else {
414432
if ($output->isDebug()) {
415433
$output->writeLineFormatted('Result cache was not saved because it was not requested.');
@@ -426,8 +444,28 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
426444
$exportedNodes = $this->mergeExportedNodes($resultCache, $analyserResult->getExportedNodes());
427445

428446
$saved = false;
429-
if ($save) {
430-
$saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $collectedDataByFile, $dependencies, $exportedNodes);
447+
if ($save !== false) {
448+
$projectExtensionFiles = [];
449+
foreach ($resultCache->getProjectExtensionFiles() as $file => [$hash, $isAnalysed, $className]) {
450+
if ($isAnalysed) {
451+
continue;
452+
}
453+
454+
// keep the same file hashes from the old run
455+
// so that the message "When you edit them and re-run PHPStan, the result cache will get stale."
456+
// keeps being shown on subsequent runs
457+
$projectExtensionFiles[$file] = [$hash, false, $className];
458+
}
459+
if ($dependencies !== null) {
460+
foreach ($this->getProjectExtensionFiles($projectConfigArray, $dependencies) as $file => [$hash, $isAnalysed, $className]) {
461+
if (!$isAnalysed) {
462+
continue;
463+
}
464+
465+
$projectExtensionFiles[$file] = [$hash, true, $className];
466+
}
467+
}
468+
$saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles);
431469
}
432470

433471
$flatErrors = [];
@@ -583,6 +621,7 @@ private function mergeExportedNodes(ResultCache $resultCache, array $freshExport
583621
* @param array<string, array<CollectedData>> $collectedData
584622
* @param array<string, array<string>> $dependencies
585623
* @param array<string, array<RootExportedNode>> $exportedNodes
624+
* @param array<string, array{string, bool, string}> $projectExtensionFiles
586625
* @param mixed[] $meta
587626
*/
588627
private function save(
@@ -592,6 +631,7 @@ private function save(
592631
array $collectedData,
593632
array $dependencies,
594633
array $exportedNodes,
634+
array $projectExtensionFiles,
595635
array $meta,
596636
): void
597637
{
@@ -639,10 +679,6 @@ private function save(
639679
ksort($exportedNodes);
640680

641681
$file = $this->cacheFilePath;
642-
$projectConfigArray = $meta['projectConfig'];
643-
if ($projectConfigArray !== null) {
644-
$meta['projectConfig'] = Neon::encode($projectConfigArray);
645-
}
646682

647683
FileWriter::write(
648684
$file,
@@ -651,7 +687,7 @@ private function save(
651687
return [
652688
'lastFullAnalysisTime' => " . var_export($lastFullAnalysisTime, true) . ",
653689
'meta' => " . var_export($meta, true) . ",
654-
'projectExtensionFiles' => " . var_export($this->getProjectExtensionFiles($projectConfigArray, $dependencies), true) . ",
690+
'projectExtensionFiles' => " . var_export($projectExtensionFiles, true) . ",
655691
'errorsCallback' => static function (): array { return " . var_export($errors, true) . "; },
656692
'locallyIgnoredErrorsCallback' => static function (): array { return " . var_export($locallyIgnoredErrors, true) . "; },
657693
'collectedDataCallback' => static function (): array { return " . var_export($collectedData, true) . "; },
@@ -665,13 +701,23 @@ private function save(
665701
/**
666702
* @param mixed[]|null $projectConfig
667703
* @param array<string, mixed> $dependencies
668-
* @return array<string, string>
704+
* @return array<string, array{string, bool, string}>
669705
*/
670706
private function getProjectExtensionFiles(?array $projectConfig, array $dependencies): array
671707
{
672708
$this->alreadyProcessed = [];
673709
$projectExtensionFiles = [];
674710
if ($projectConfig !== null) {
711+
$vendorDirs = [];
712+
foreach ($this->composerAutoloaderProjectPaths as $autoloaderProjectPath) {
713+
$composer = ComposerHelper::getComposerConfig($autoloaderProjectPath);
714+
if ($composer === null) {
715+
continue;
716+
}
717+
$vendorDirectory = ComposerHelper::getVendorDirFromComposerConfig($autoloaderProjectPath, $composer);
718+
$vendorDirs[] = $this->fileHelper->normalizePath($vendorDirectory);
719+
}
720+
675721
$classes = ProjectConfigHelper::getServiceClassNames($projectConfig);
676722
foreach ($classes as $class) {
677723
if (!$this->reflectionProvider->hasClass($class)) {
@@ -684,13 +730,28 @@ private function getProjectExtensionFiles(?array $projectConfig, array $dependen
684730
continue;
685731
}
686732

733+
if (str_starts_with($fileName, 'phar://')) {
734+
continue;
735+
}
736+
687737
$allServiceFiles = $this->getAllDependencies($fileName, $dependencies);
738+
if (count($allServiceFiles) === 0) {
739+
$normalizedFileName = $this->fileHelper->normalizePath($fileName);
740+
foreach ($vendorDirs as $vendorDir) {
741+
if (str_starts_with($normalizedFileName, $vendorDir)) {
742+
continue 2;
743+
}
744+
}
745+
$projectExtensionFiles[$fileName] = [$this->getFileHash($fileName), false, $class];
746+
continue;
747+
}
748+
688749
foreach ($allServiceFiles as $serviceFile) {
689750
if (array_key_exists($serviceFile, $projectExtensionFiles)) {
690751
continue;
691752
}
692753

693-
$projectExtensionFiles[$serviceFile] = $this->getFileHash($serviceFile);
754+
$projectExtensionFiles[$serviceFile] = [$this->getFileHash($serviceFile), true, $class];
694755
}
695756
}
696757
}

0 commit comments

Comments
 (0)