Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Parser/CachedParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ final class CachedParser implements Parser
public function __construct(
private Parser $originalParser,
private int $cachedNodesByStringCountMax,
private int $cachedSourceBytesMax = self::CACHED_SOURCE_BYTES_DEFAULT_LIMIT,
int $cachedSourceBytesMax = self::CACHED_SOURCE_BYTES_DEFAULT_LIMIT,
)
{
$this->cachedNodesByString = new LruCache(
$this->cachedNodesByStringCountMax,
$this->cachedSourceBytesMax,
$cachedSourceBytesMax,
self::SIZE_EVICTION_FLOOR_LIMIT,
);
$this->cachedSourceByFile = new LruCache(maxWeight: self::MEMOIZED_SOURCE_BYTES_LIMIT);
Expand Down
32 changes: 13 additions & 19 deletions src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Internal\LruCache;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
Expand Down Expand Up @@ -62,7 +63,6 @@
use PHPStan\Type\TypehintHelper;
use PHPStan\Type\UnionType;
use function array_key_exists;
use function array_key_first;
use function array_keys;
use function array_map;
use function array_slice;
Expand All @@ -77,8 +77,8 @@
final class PhpClassReflectionExtension
{

/** @var array<string, true> shared LRU over the member cache keys below; first entry = least recently used */
private array $memberCacheOrder = [];
/** @var LruCache<true> shared LRU over the member cache keys below */
private LruCache $memberCacheOrder;

/** @var PhpPropertyReflection[][] */
private array $propertiesIncludingAnnotations = [];
Expand Down Expand Up @@ -118,9 +118,10 @@ public function __construct(
private bool $inferPrivatePropertyTypeFromConstructor,
private PhpVersion $phpVersion,
#[AutowiredParameter(ref: '%cache.memberCacheKeysMax%')]
private int $memberCacheKeysMax,
int $memberCacheKeysMax,
)
{
$this->memberCacheOrder = new LruCache($memberCacheKeysMax);
}

/**
Expand All @@ -135,25 +136,18 @@ public function __construct(
*/
private function touchMemberCacheKey(string $cacheKey): void
{
if (isset($this->memberCacheOrder[$cacheKey])) {
unset($this->memberCacheOrder[$cacheKey]);
$this->memberCacheOrder[$cacheKey] = true;
if ($this->memberCacheOrder->get($cacheKey) !== null) {
return;
}

$this->memberCacheOrder[$cacheKey] = true;
if ($this->memberCacheKeysMax === 0 || count($this->memberCacheOrder) <= $this->memberCacheKeysMax) {
return;
foreach ($this->memberCacheOrder->set($cacheKey, true, 0) as $evictKey) {
unset(
$this->methodsIncludingAnnotations[$evictKey],
$this->nativeMethods[$evictKey],
$this->propertiesIncludingAnnotations[$evictKey],
$this->nativeProperties[$evictKey],
);
}

$evictKey = array_key_first($this->memberCacheOrder);
unset(
$this->memberCacheOrder[$evictKey],
$this->methodsIncludingAnnotations[$evictKey],
$this->nativeMethods[$evictKey],
$this->propertiesIncludingAnnotations[$evictKey],
$this->nativeProperties[$evictKey],
);
}

public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
Expand Down
37 changes: 13 additions & 24 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\File\FileContentHasher;
use PHPStan\File\FileHelper;
use PHPStan\Internal\ComposerHelper;
use PHPStan\Internal\LruCache;
use PHPStan\Parser\Parser;
use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException;
use PHPStan\PhpDoc\PhpDocNodeResolver;
Expand Down Expand Up @@ -58,10 +59,8 @@ final class FileTypeMapper
private const SKIP_NODE = 1;
private const POP_TYPE_MAP_STACK = 2;

/** @var array<string, array{array<string, IntermediaryNameScope>}> */
private array $memoryCache = [];

private int $memoryCacheCount = 0;
/** @var LruCache<array{array<string, IntermediaryNameScope>}> */
private LruCache $memoryCache;

/** @var array<string, true> */
private array $inProcess = [];
Expand All @@ -87,9 +86,13 @@ public function __construct(
#[AutowiredParameter(ref: '%cache.resolvedPhpDocBlockCacheCountMax%')]
private int $resolvedPhpDocBlockCacheCountMax,
#[AutowiredParameter(ref: '%cache.nameScopeMapMemoryCacheCountMax%')]
private int $nameScopeMapMemoryCacheCountMax,
int $nameScopeMapMemoryCacheCountMax,
)
{
// 0 kept one entry here rather than meaning "no limit" as it does for the other bounded
// caches: the eviction loop ran before the insertion, emptying the cache and then putting
// a single entry back. Preserved rather than normalised - see the PR description.
$this->memoryCache = new LruCache($nameScopeMapMemoryCacheCountMax === 0 ? 1 : $nameScopeMapMemoryCacheCountMax);
}

/** @api */
Expand Down Expand Up @@ -336,13 +339,8 @@ public function getNameScope(
*/
private function getNameScopeMap(string $fileName): array
{
if (isset($this->memoryCache[$fileName])) {
// LRU: move the freshly-accessed entry to the end so eviction drops
// genuinely cold files, not hot dependencies inserted early on.
$cachedEntry = $this->memoryCache[$fileName];
unset($this->memoryCache[$fileName]);
$this->memoryCache[$fileName] = $cachedEntry;

$cachedEntry = $this->memoryCache->get($fileName);
if ($cachedEntry !== null) {
return $cachedEntry;
}

Expand All @@ -364,19 +362,10 @@ private function getNameScopeMap(string $fileName): array
} else {
[$nameScopeMap] = $cached;
}
while ($this->memoryCacheCount >= $this->nameScopeMapMemoryCacheCountMax) {
$oldestKey = array_key_first($this->memoryCache);
if ($oldestKey === null) {
break;
}
unset($this->memoryCache[$oldestKey]);
$this->memoryCacheCount--;
}

$this->memoryCache[$fileName] = [$nameScopeMap];
$this->memoryCacheCount++;
$entry = [$nameScopeMap];
$this->memoryCache->set($fileName, $entry, 0);

return $this->memoryCache[$fileName];
return $entry;
}

/**
Expand Down
28 changes: 11 additions & 17 deletions src/Type/UsefulTypeAliasResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
use PHPStan\Analyser\NameScope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Internal\LruCache;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDoc\TypeStringResolver;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use function array_key_exists;
use function array_key_first;
use function count;
use function sprintf;

#[AutowiredService(as: TypeAliasResolver::class)]
Expand All @@ -21,8 +20,8 @@ final class UsefulTypeAliasResolver implements TypeAliasResolver
/** @var array<string, Type> */
private array $resolvedGlobalTypeAliases = [];

/** @var array<string, Type> LRU; first entry = least recently used */
private array $resolvedLocalTypeAliases = [];
/** @var LruCache<Type> */
private LruCache $resolvedLocalTypeAliases;

/** @var array<string, true> */
private array $resolvingClassTypeAliases = [];
Expand All @@ -40,9 +39,10 @@ public function __construct(
private TypeNodeResolver $typeNodeResolver,
private ReflectionProvider $reflectionProvider,
#[AutowiredParameter(ref: '%cache.resolvedLocalTypeAliasesCountMax%')]
private int $resolvedLocalTypeAliasesCountMax,
int $resolvedLocalTypeAliasesCountMax,
)
{
$this->resolvedLocalTypeAliases = new LruCache($resolvedLocalTypeAliasesCountMax);
}

public function hasTypeAlias(string $aliasName, ?string $classNameScope): bool
Expand Down Expand Up @@ -84,12 +84,9 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):

$aliasNameInClassScope = $className . '::' . $aliasName;

if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) {
// LRU: move to the most-recently-used position
$resolvedAliasType = $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
unset($this->resolvedLocalTypeAliases[$aliasNameInClassScope]);

return $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
$resolvedAliasType = $this->resolvedLocalTypeAliases->get($aliasNameInClassScope);
if ($resolvedAliasType !== null) {
return $resolvedAliasType;
}

// prevent infinite recursion
Expand Down Expand Up @@ -127,12 +124,9 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
$resolvedAliasType = new CircularTypeAliasErrorType();
}

$this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
if ($this->resolvedLocalTypeAliasesCountMax !== 0 && count($this->resolvedLocalTypeAliases) > $this->resolvedLocalTypeAliasesCountMax) {
// resolved alias types transitively pin ClassReflections and their whole
// reflection trees — evict the least recently used
unset($this->resolvedLocalTypeAliases[array_key_first($this->resolvedLocalTypeAliases)]);
}
// resolved alias types transitively pin ClassReflections and their whole
// reflection trees, so the cache is bounded and evicts the least recently used
$this->resolvedLocalTypeAliases->set($aliasNameInClassScope, $resolvedAliasType, 0);
unset($this->inProcess[$aliasNameInClassScope]);

return $resolvedAliasType;
Expand Down
Loading