Skip to content

Commit b98431a

Browse files
SanderMullerclaude
andcommitted
Invalidate only package-dependent files when a Composer package changes
Today any change to composer.lock or installed.json marks the whole result cache as outdated and forces a full re-analysis, even when the bumped package is a dev tool or a transitive dependency that no analysed file uses. This records, per file, the project-vendor packages whose code the file reflected during analysis. The edges reuse the reflection dependencies NodeDependencies already collects for file-to-file incremental invalidation (instead of discarding the vendor entries at NodeDependencies::getFileDependencies), so the no-under-invalidation property is inherited: if a file's analysis reflected a package's code, the file is recorded against that package. Packages are resolved through the project's installed.php install paths and scoped to the analysed project's vendor, so PHPStan's own bundled dependencies (already covered by CACHE_VERSION and the PHP version) are excluded. When the only meta difference is composerLocks/composerInstalled, the result cache now diffs the per-package versions, seeds only the files that depend on a changed package, and falls through into the existing incremental loop, which still adds content-changed files and propagates dependents on a signature change. Any other meta difference still triggers a full re-analysis, and an unreadable or unexpectedly shaped installed.php falls back to the full nuke, so the change never under-invalidates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ad1ea52 commit b98431a

21 files changed

Lines changed: 541 additions & 25 deletions

‎conf/services.neon‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ services:
5656
-
5757
class: PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor
5858

59+
-
60+
class: PHPStan\Dependency\PackageDependencyResolver
61+
arguments:
62+
composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths%
63+
5964
-
6065
class: PHPStan\Reflection\Php\PhpClassReflectionExtension
6166
arguments:

‎src/Analyser/Analyser.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public function analyse(
7474
$reachedInternalErrorsCountLimit = false;
7575
$dependencies = [];
7676
$usedTraitDependencies = [];
77+
$packageDependencies = [];
7778
$exportedNodes = [];
7879
$allProcessedFiles = [];
7980
foreach ($files as $file) {
@@ -101,6 +102,7 @@ public function analyse(
101102
$collectedData = array_merge($collectedData, $fileAnalyserResult->getCollectedData());
102103
$dependencies[$file] = $fileAnalyserResult->getDependencies();
103104
$usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies();
105+
$packageDependencies[$file] = $fileAnalyserResult->getPackageDependencies();
104106

105107
$fileExportedNodes = $fileAnalyserResult->getExportedNodes();
106108
if (count($fileExportedNodes) > 0) {
@@ -142,6 +144,7 @@ public function analyse(
142144
collectedData: $collectedData,
143145
dependencies: $internalErrorsCount === 0 ? $dependencies : null,
144146
usedTraitDependencies: $internalErrorsCount === 0 ? $usedTraitDependencies : null,
147+
packageDependencies: $internalErrorsCount === 0 ? $packageDependencies : null,
145148
exportedNodes: $exportedNodes,
146149
reachedInternalErrorsCountLimit: $reachedInternalErrorsCountLimit,
147150
peakMemoryUsageBytes: memory_get_peak_usage(true),

‎src/Analyser/AnalyserResult.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ final class AnalyserResult
2727
* @param list<InternalError> $internalErrors
2828
* @param array<string, array<string>>|null $dependencies
2929
* @param array<string, array<string>>|null $usedTraitDependencies
30+
* @param array<string, array<string>>|null $packageDependencies
3031
* @param array<string, array<RootExportedNode>> $exportedNodes
3132
* @param list<string> $processedFiles
3233
*/
@@ -41,6 +42,7 @@ public function __construct(
4142
private array $collectedData,
4243
private ?array $dependencies,
4344
private ?array $usedTraitDependencies,
45+
private ?array $packageDependencies,
4446
private array $exportedNodes,
4547
private bool $reachedInternalErrorsCountLimit,
4648
private int $peakMemoryUsageBytes,
@@ -153,6 +155,14 @@ public function getUsedTraitDependencies(): ?array
153155
return $this->usedTraitDependencies;
154156
}
155157

158+
/**
159+
* @return array<string, array<string>>|null
160+
*/
161+
public function getPackageDependencies(): ?array
162+
{
163+
return $this->packageDependencies;
164+
}
165+
156166
/**
157167
* @return array<string, array<RootExportedNode>>
158168
*/

‎src/Analyser/AnalyserResultFinalizer.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $
145145
collectedData: $analyserResult->getCollectedData(),
146146
dependencies: $analyserResult->getDependencies(),
147147
usedTraitDependencies: $analyserResult->getUsedTraitDependencies(),
148+
packageDependencies: $analyserResult->getPackageDependencies(),
148149
exportedNodes: $analyserResult->getExportedNodes(),
149150
reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(),
150151
peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(),
@@ -165,6 +166,7 @@ private function mergeFilteredPhpErrors(AnalyserResult $analyserResult): Analyse
165166
collectedData: $analyserResult->getCollectedData(),
166167
dependencies: $analyserResult->getDependencies(),
167168
usedTraitDependencies: $analyserResult->getUsedTraitDependencies(),
169+
packageDependencies: $analyserResult->getPackageDependencies(),
168170
exportedNodes: $analyserResult->getExportedNodes(),
169171
reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(),
170172
peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(),
@@ -230,6 +232,7 @@ private function addUnmatchedIgnoredErrors(
230232
collectedData: $analyserResult->getCollectedData(),
231233
dependencies: $analyserResult->getDependencies(),
232234
usedTraitDependencies: $analyserResult->getUsedTraitDependencies(),
235+
packageDependencies: $analyserResult->getPackageDependencies(),
233236
exportedNodes: $analyserResult->getExportedNodes(),
234237
reachedInternalErrorsCountLimit: $analyserResult->hasReachedInternalErrorsCountLimit(),
235238
peakMemoryUsageBytes: $analyserResult->getPeakMemoryUsageBytes(),

‎src/Analyser/FileAnalyser.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Collectors\CollectedData;
1111
use PHPStan\Collectors\Registry as CollectorRegistry;
1212
use PHPStan\Dependency\DependencyResolver;
13+
use PHPStan\Dependency\PackageDependencyResolver;
1314
use PHPStan\DependencyInjection\AutowiredParameter;
1415
use PHPStan\DependencyInjection\AutowiredService;
1516
use PHPStan\Node\FileNode;
@@ -57,6 +58,7 @@ public function __construct(
5758
#[AutowiredParameter(ref: '@defaultAnalysisParser')]
5859
private Parser $parser,
5960
private DependencyResolver $dependencyResolver,
61+
private PackageDependencyResolver $packageDependencyResolver,
6062
private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider,
6163
private RuleErrorTransformer $ruleErrorTransformer,
6264
private LocalIgnoresProcessor $localIgnoresProcessor,
@@ -92,6 +94,7 @@ public function analyseFile(
9294

9395
$fileDependencies = [];
9496
$usedTraitFileDependencies = [];
97+
$filePackageDependencies = [];
9598
$exportedNodes = [];
9699
$linesToIgnore = [];
97100
$unmatchedLineIgnores = [];
@@ -111,6 +114,7 @@ public function analyseFile(
111114
$this->ignoreErrorExtensionProvider->getExtensions(),
112115
$this->parser,
113116
$this->dependencyResolver,
117+
$this->packageDependencyResolver,
114118
$this->ruleErrorTransformer,
115119
$processedFiles,
116120
);
@@ -125,6 +129,7 @@ public function analyseFile(
125129
$fileCollectedData = $nodeCallback->getFileCollectedData();
126130
$fileDependencies = $nodeCallback->getFileDependencies();
127131
$usedTraitFileDependencies = $nodeCallback->getUsedTraitFileDependencies();
132+
$filePackageDependencies = $nodeCallback->getPackageDependencies();
128133
$exportedNodes = $nodeCallback->getExportedNodes();
129134
$linesToIgnore = $nodeCallback->getLinesToIgnore();
130135
$unmatchedLineIgnores = $nodeCallback->getUnmatchedLineIgnores();
@@ -243,6 +248,7 @@ public function analyseFile(
243248
$fileCollectedData,
244249
array_values(array_unique($fileDependencies)),
245250
array_values(array_unique($usedTraitFileDependencies)),
251+
array_values(array_unique($filePackageDependencies)),
246252
$exportedNodes,
247253
$linesToIgnore,
248254
$unmatchedLineIgnores,

‎src/Analyser/FileAnalyserCallback.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Collectors\CollectedData;
1111
use PHPStan\Collectors\Registry as CollectorRegistry;
1212
use PHPStan\Dependency\DependencyResolver;
13+
use PHPStan\Dependency\PackageDependencyResolver;
1314
use PHPStan\Dependency\RootExportedNode;
1415
use PHPStan\Node\EmitCollectedDataNode;
1516
use PHPStan\Node\InClassNode;
@@ -40,6 +41,9 @@ final class FileAnalyserCallback
4041
/** @var array<string> */
4142
private array $usedTraitFileDependencies = [];
4243

44+
/** @var array<string> */
45+
private array $filePackageDependencies = [];
46+
4347
/** @var list<RootExportedNode> */
4448
private array $exportedNodes = [];
4549

@@ -69,6 +73,7 @@ public function __construct(
6973
private array $ignoreErrorExtensions,
7074
private Parser $parser,
7175
private DependencyResolver $dependencyResolver,
76+
private PackageDependencyResolver $packageDependencyResolver,
7277
private RuleErrorTransformer $ruleErrorTransformer,
7378
private array $processedFiles,
7479
)
@@ -215,6 +220,9 @@ public function __invoke(Node $node, Scope $scope): void
215220
foreach ($dependencies->getFileDependencies($scope->getFile(), $this->analysedFiles) as $dependentFile) {
216221
$this->fileDependencies[] = $dependentFile;
217222
}
223+
foreach ($dependencies->getPackageDependencies($scope->getFile(), $this->analysedFiles, $this->packageDependencyResolver) as $package) {
224+
$this->filePackageDependencies[] = $package;
225+
}
218226
if ($dependencies->getExportedNode() !== null) {
219227
$this->exportedNodes[] = $dependencies->getExportedNode();
220228
}
@@ -234,6 +242,9 @@ public function __invoke(Node $node, Scope $scope): void
234242
foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $this->analysedFiles) as $dependentFile) {
235243
$this->usedTraitFileDependencies[] = $dependentFile;
236244
}
245+
foreach ($usedTraitDependencies->getPackageDependencies($scope->getFile(), $this->analysedFiles, $this->packageDependencyResolver) as $package) {
246+
$this->filePackageDependencies[] = $package;
247+
}
237248
}
238249

239250
/**
@@ -274,6 +285,14 @@ public function getFileDependencies(): array
274285
return $this->fileDependencies;
275286
}
276287

288+
/**
289+
* @return array<string>
290+
*/
291+
public function getPackageDependencies(): array
292+
{
293+
return $this->filePackageDependencies;
294+
}
295+
277296
/**
278297
* @return array<string>
279298
*/

‎src/Analyser/FileAnalyserResult.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final class FileAnalyserResult
2121
* @param CollectorData $collectedData
2222
* @param list<string> $dependencies
2323
* @param list<string> $usedTraitDependencies
24+
* @param list<string> $packageDependencies
2425
* @param list<RootExportedNode> $exportedNodes
2526
* @param LinesToIgnore $linesToIgnore
2627
* @param LinesToIgnore $unmatchedLineIgnores
@@ -34,6 +35,7 @@ public function __construct(
3435
private array $collectedData,
3536
private array $dependencies,
3637
private array $usedTraitDependencies,
38+
private array $packageDependencies,
3739
private array $exportedNodes,
3840
private array $linesToIgnore,
3941
private array $unmatchedLineIgnores,
@@ -98,6 +100,14 @@ public function getUsedTraitDependencies(): array
98100
return $this->usedTraitDependencies;
99101
}
100102

103+
/**
104+
* @return list<string>
105+
*/
106+
public function getPackageDependencies(): array
107+
{
108+
return $this->packageDependencies;
109+
}
110+
101111
/**
102112
* @return list<RootExportedNode>
103113
*/

‎src/Analyser/ResultCache/ResultCache.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ final class ResultCache
2424
* @param CollectorData $collectedData
2525
* @param array<string, array<string>> $dependencies
2626
* @param array<string, array<string>> $usedTraitDependencies
27+
* @param array<string, array<string>> $packageDependencies
2728
* @param array<string, array<RootExportedNode>> $exportedNodes
2829
* @param array<string, array{string, bool, string}> $projectExtensionFiles
2930
* @param array<string, string> $currentFileHashes
@@ -40,6 +41,7 @@ public function __construct(
4041
private array $collectedData,
4142
private array $dependencies,
4243
private array $usedTraitDependencies,
44+
private array $packageDependencies,
4345
private array $exportedNodes,
4446
private array $projectExtensionFiles,
4547
private array $currentFileHashes,
@@ -129,6 +131,14 @@ public function getUsedTraitDependencies(): array
129131
return $this->usedTraitDependencies;
130132
}
131133

134+
/**
135+
* @return array<string, array<string>>
136+
*/
137+
public function getPackageDependencies(): array
138+
{
139+
return $this->packageDependencies;
140+
}
141+
132142
/**
133143
* @return array<string, array<RootExportedNode>>
134144
*/

0 commit comments

Comments
 (0)