Skip to content

Commit fda77dc

Browse files
ondrejmirtesclaude
andcommitted
LRU-cap resolved local type aliases in UsefulTypeAliasResolver
Every resolved @phpstan-type local alias is cached forever, keyed by Class::alias. Resolved alias Types transitively pin ClassReflections (ObjectType::$classReflection, EnumCaseObjectType::$classReflection, ...) and through them whole BetterReflection trees, so on alias-heavy codebases this cache quietly retains a large share of the reflection universe. Retention-path instrumentation on a large project identified it as the single largest type-level pin: bounding it freed 34 MB end-of-run memory at 2,358 analysed files — more than any reflection-side cache — with byte-identical error output and no measurable time cost (interleaved A/B, 100 s vs 95 s). Cap the cache at 2048 entries with least-recently-used eviction; evicted aliases are re-resolved on demand. Global aliases are unaffected (bounded by config). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrTvR9j1mW2NNNC8RkuTsF
1 parent 6f72e96 commit fda77dc

1 file changed

Lines changed: 15 additions & 2 deletions

File tree

‎src/Type/UsefulTypeAliasResolver.php‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
use PHPStan\Reflection\ReflectionProvider;
1111
use PHPStan\ShouldNotHappenException;
1212
use function array_key_exists;
13+
use function array_key_first;
14+
use function count;
1315
use function sprintf;
1416

1517
#[AutowiredService(as: TypeAliasResolver::class)]
1618
final class UsefulTypeAliasResolver implements TypeAliasResolver
1719
{
1820

21+
private const RESOLVED_LOCAL_TYPE_ALIASES_MAX = 2048;
22+
1923
/** @var array<string, Type> */
2024
private array $resolvedGlobalTypeAliases = [];
2125

22-
/** @var array<string, Type> */
26+
/** @var array<string, Type> LRU; first entry = least recently used */
2327
private array $resolvedLocalTypeAliases = [];
2428

2529
/** @var array<string, true> */
@@ -81,7 +85,11 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
8185
$aliasNameInClassScope = $className . '::' . $aliasName;
8286

8387
if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) {
84-
return $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
88+
// LRU: move to the most-recently-used position
89+
$resolvedAliasType = $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
90+
unset($this->resolvedLocalTypeAliases[$aliasNameInClassScope]);
91+
92+
return $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
8593
}
8694

8795
// prevent infinite recursion
@@ -120,6 +128,11 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
120128
}
121129

122130
$this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
131+
if (count($this->resolvedLocalTypeAliases) > self::RESOLVED_LOCAL_TYPE_ALIASES_MAX) {
132+
// resolved alias types transitively pin ClassReflections and their whole
133+
// reflection trees — evict the least recently used
134+
unset($this->resolvedLocalTypeAliases[array_key_first($this->resolvedLocalTypeAliases)]);
135+
}
123136
unset($this->inProcess[$aliasNameInClassScope]);
124137

125138
return $resolvedAliasType;

0 commit comments

Comments
 (0)