Skip to content

Commit

Permalink
Merge pull request #7876 from vognev/feature/vendor-cache
Browse files Browse the repository at this point in the history
cache statements even without persistent parser cache
  • Loading branch information
orklah committed Apr 18, 2022
2 parents 338faa9 + 6afdb0d commit 55a45a9
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 3 deletions.
19 changes: 16 additions & 3 deletions src/Psalm/Internal/Provider/StatementsProvider.php
Expand Up @@ -55,6 +55,11 @@ class StatementsProvider
*/
private $file_storage_cache_provider;

/**
* @var StatementsVolatileCache
*/
private $statements_volatile_cache;

/**
* @var array<string, array<string, bool>>
*/
Expand Down Expand Up @@ -104,6 +109,7 @@ public function __construct(
$this->parser_cache_provider = $parser_cache_provider;
$this->this_modified_time = filemtime(__FILE__);
$this->file_storage_cache_provider = $file_storage_cache_provider;
$this->statements_volatile_cache = StatementsVolatileCache::getInstance();
}

/**
Expand All @@ -126,19 +132,26 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr

$config = Config::getInstance();

$file_content_hash = md5($version . $file_contents);

if (!$this->parser_cache_provider
|| (!$config->isInProjectDirs($file_path) && strpos($file_path, 'vendor'))
) {
$cache_key = "${file_content_hash}:${php_version}";
if ($this->statements_volatile_cache->has($cache_key)) {
return $this->statements_volatile_cache->get($cache_key);
}

$progress->debug('Parsing ' . $file_path . "\n");

$has_errors = false;

$stmts = self::parseStatements($file_contents, $php_version, $has_errors, $file_path);

return $stmts ?: [];
}
$this->statements_volatile_cache->set($cache_key, $stmts);

$file_content_hash = md5($version . $file_contents);
return $stmts;
}

$stmts = $this->parser_cache_provider->loadStatementsFromCache(
$file_path,
Expand Down
108 changes: 108 additions & 0 deletions src/Psalm/Internal/Provider/StatementsVolatileCache.php
@@ -0,0 +1,108 @@
<?php

namespace Psalm\Internal\Provider;

use InvalidArgumentException;
use PhpParser\Node\Stmt;

use function array_key_exists;
use function array_search;
use function array_splice;
use function count;
use function is_null;
use function key;
use function reset;

/**
* @internal Owned by StatementsProvider
* @todo: track variables size instead
*/
final class StatementsVolatileCache
{
/**
* @var array<string, list<Stmt>>
*/
protected $cache = [];

/**
* @var array<int, string>
*/
protected $access = [];

/**
* @var int
*/
protected $max_size;

/**
* @var ?StatementsVolatileCache
*/
protected static $instance;

public function __construct(int $max_size = 4096)
{
$this->max_size = $max_size;
}

public static function getInstance(): StatementsVolatileCache
{
if (is_null(self::$instance)) {
self::$instance = new self();
}

return self::$instance;
}

public function has(string $key): bool
{
return array_key_exists($key, $this->cache);
}

/**
* @param string $key
* @return list<Stmt>
* @throws InvalidArgumentException
*/
public function get(string $key): array
{
if (! $this->has($key)) {
throw new InvalidArgumentException('Given $key does not exists');
}

$access_index = array_search($key, $this->access);
if (false !== $access_index) {
array_splice($this->access, $access_index, 1);
}
$this->access[] = $key;

return $this->cache[$key];
}

/**
* @param string $key
* @param list<Stmt> $content
*/
public function set(string $key, array $content): void
{
if (count($this->cache) > $this->max_size) {
reset($this->access);

$oldest_key_index = key($this->access);

if (! is_null($oldest_key_index)) {
$oldest_key = $this->access[$oldest_key_index];
unset($this->cache[$oldest_key]);
unset($this->access[$oldest_key_index]);
}
}

$this->cache[$key] = $content;
$this->access[] = $key;
}

public function clearCache(): void
{
$this->cache = [];
$this->access = [];
}
}
2 changes: 2 additions & 0 deletions src/Psalm/Internal/RuntimeCaches.php
Expand Up @@ -14,6 +14,7 @@
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\FileStorageProvider;
use Psalm\Internal\Provider\StatementsProvider;
use Psalm\Internal\Provider\StatementsVolatileCache;
use Psalm\Internal\Scanner\ParsedDocblock;
use Psalm\Internal\Type\TypeTokenizer;
use Psalm\IssueBuffer;
Expand All @@ -38,5 +39,6 @@ public static function clearAll(): void
StatementsProvider::clearLexer();
StatementsProvider::clearParser();
ParsedDocblock::resetNewlineBetweenAnnotations();
StatementsVolatileCache::getInstance()->clearCache();
}
}

0 comments on commit 55a45a9

Please sign in to comment.