Skip to content

Commit fbcde4b

Browse files
SanderMullerclaude
andcommitted
Reuse parsed PHPDocs of equal strings
FileTypeMapper, InvalidPhpDocTagValueRule and InvalidPHPStanDocTagRule parse the same PHPDoc strings, and the two rules parse each one directly after the other. PhpDocStringResolver now keeps the most recently parsed nodes in an LruCache, and the two rules use it instead of their own lexer and parser. The new cache.parsedPhpDocCountMax parameter sets how many nodes it keeps, 1024 by default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent acd0511 commit fbcde4b

8 files changed

Lines changed: 59 additions & 30 deletions

‎conf/config.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ parameters:
146146
phpStormStubsNodesCountMax: 128
147147
memberCacheKeysMax: 2048
148148
resolvedLocalTypeAliasesCountMax: 2048
149+
parsedPhpDocCountMax: 1024
149150
reportUnmatchedIgnoredErrors: true
150151
reportIgnoresWithoutComments: false
151152
typeAliases: []

‎conf/parametersSchema.neon‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ parametersSchema:
164164
nameScopeMapMemoryCacheCountMax: int(),
165165
phpStormStubsNodesCountMax: int(),
166166
memberCacheKeysMax: int(),
167-
resolvedLocalTypeAliasesCountMax: int()
167+
resolvedLocalTypeAliasesCountMax: int(),
168+
parsedPhpDocCountMax: int()
168169
])
169170
reportUnmatchedIgnoredErrors: bool()
170171
reportIgnoresWithoutComments: bool()

‎src/PhpDoc/PhpDocStringResolver.php‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace PHPStan\PhpDoc;
44

5+
use PHPStan\DependencyInjection\AutowiredParameter;
56
use PHPStan\DependencyInjection\AutowiredService;
7+
use PHPStan\Internal\LruCache;
68
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
79
use PHPStan\PhpDocParser\Lexer\Lexer;
810
use PHPStan\PhpDocParser\Parser\PhpDocParser;
@@ -12,16 +14,37 @@
1214
final class PhpDocStringResolver
1315
{
1416

15-
public function __construct(private Lexer $phpDocLexer, private PhpDocParser $phpDocParser)
17+
/**
18+
* FileTypeMapper and the rules that validate PHPDocs parse the same strings, often one right after
19+
* another. The parsed nodes are not modified afterwards, so equal strings can share one node.
20+
*
21+
* @var LruCache<PhpDocNode>
22+
*/
23+
private LruCache $parsedPhpDocs;
24+
25+
public function __construct(
26+
private Lexer $phpDocLexer,
27+
private PhpDocParser $phpDocParser,
28+
#[AutowiredParameter(ref: '%cache.parsedPhpDocCountMax%')]
29+
int $parsedPhpDocCountMax,
30+
)
1631
{
32+
$this->parsedPhpDocs = new LruCache($parsedPhpDocCountMax);
1733
}
1834

1935
public function resolve(string $phpDocString): PhpDocNode
2036
{
37+
$phpDocNode = $this->parsedPhpDocs->get($phpDocString);
38+
if ($phpDocNode !== null) {
39+
return $phpDocNode;
40+
}
41+
2142
$tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString));
2243
$phpDocNode = $this->phpDocParser->parse($tokens);
2344
$tokens->consumeTokenType(Lexer::TOKEN_END); // @phpstan-ignore missingType.checkedException
2445

46+
$this->parsedPhpDocs->set($phpDocString, $phpDocNode, 0);
47+
2548
return $phpDocNode;
2649
}
2750

‎src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
use PHPStan\DependencyInjection\RegisteredRule;
99
use PHPStan\DependencyInjection\ValidatesStubFiles;
1010
use PHPStan\Node\VirtualNode;
11-
use PHPStan\PhpDocParser\Lexer\Lexer;
12-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
13-
use PHPStan\PhpDocParser\Parser\TokenIterator;
11+
use PHPStan\PhpDoc\PhpDocStringResolver;
1412
use PHPStan\Rules\MultipleNodeTypesRule;
1513
use PHPStan\Rules\RuleErrorBuilder;
1614
use function in_array;
@@ -70,10 +68,7 @@ final class InvalidPHPStanDocTagRule implements MultipleNodeTypesRule
7068
'@phpstan-all-methods-impure',
7169
];
7270

73-
public function __construct(
74-
private Lexer $phpDocLexer,
75-
private PhpDocParser $phpDocParser,
76-
)
71+
public function __construct(private PhpDocStringResolver $phpDocStringResolver)
7772
{
7873
}
7974

@@ -111,8 +106,7 @@ public function processNode(Node $node, Scope $scope): array
111106
return [];
112107
}
113108
$phpDocString = $docComment->getText();
114-
$tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString));
115-
$phpDocNode = $this->phpDocParser->parse($tokens);
109+
$phpDocNode = $this->phpDocStringResolver->resolve($phpDocString);
116110

117111
$errors = [];
118112
foreach ($phpDocNode->getTags() as $phpDocTag) {

‎src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
use PHPStan\DependencyInjection\RegisteredRule;
99
use PHPStan\DependencyInjection\ValidatesStubFiles;
1010
use PHPStan\Node\VirtualNode;
11+
use PHPStan\PhpDoc\PhpDocStringResolver;
1112
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
1213
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
1314
use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode;
14-
use PHPStan\PhpDocParser\Lexer\Lexer;
15-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
16-
use PHPStan\PhpDocParser\Parser\TokenIterator;
1715
use PHPStan\Rules\MultipleNodeTypesRule;
1816
use PHPStan\Rules\RuleErrorBuilder;
1917
use function sprintf;
@@ -27,10 +25,7 @@
2725
final class InvalidPhpDocTagValueRule implements MultipleNodeTypesRule
2826
{
2927

30-
public function __construct(
31-
private Lexer $phpDocLexer,
32-
private PhpDocParser $phpDocParser,
33-
)
28+
public function __construct(private PhpDocStringResolver $phpDocStringResolver)
3429
{
3530
}
3631

@@ -69,8 +64,7 @@ public function processNode(Node $node, Scope $scope): array
6964
}
7065

7166
$phpDocString = $docComment->getText();
72-
$tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString));
73-
$phpDocNode = $this->phpDocParser->parse($tokens);
67+
$phpDocNode = $this->phpDocStringResolver->resolve($phpDocString);
7468

7569
$errors = [];
7670
foreach ($phpDocNode->getTags() as $phpDocTag) {

‎tests/PHPStan/PhpDoc/PhpDocStringResolverTest.php‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace PHPStan\PhpDoc;
44

5+
use PHPStan\PhpDocParser\Lexer\Lexer;
56
use PHPStan\PhpDocParser\Parser\ParserException;
7+
use PHPStan\PhpDocParser\Parser\PhpDocParser;
68
use PHPStan\Testing\PHPStanTestCase;
79

810
class PhpDocStringResolverTest extends PHPStanTestCase
@@ -17,4 +19,24 @@ public function testConsumeEnd(): void
1719
$phpDocStringResolver->resolve('/** @param int $test */ test');
1820
}
1921

22+
public function testEqualStringsShareOneNode(): void
23+
{
24+
$phpDocStringResolver = $this->createPhpDocStringResolver();
25+
26+
$node = $phpDocStringResolver->resolve('/** @param int $test */');
27+
$this->assertSame($node, $phpDocStringResolver->resolve('/** @param int $test */'));
28+
$this->assertNotSame($node, $phpDocStringResolver->resolve('/** @param string $test */'));
29+
}
30+
31+
private function createPhpDocStringResolver(): PhpDocStringResolver
32+
{
33+
$container = self::getContainer();
34+
35+
return new PhpDocStringResolver(
36+
$container->getByType(Lexer::class),
37+
$container->getByType(PhpDocParser::class),
38+
1024,
39+
);
40+
}
41+
2042
}

‎tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
namespace PHPStan\Rules\PhpDoc;
44

5-
use PHPStan\PhpDocParser\Lexer\Lexer;
6-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
5+
use PHPStan\PhpDoc\PhpDocStringResolver;
76
use PHPStan\Rules\Rule;
87
use PHPStan\Testing\RuleTestCase;
98
use PHPUnit\Framework\Attributes\RequiresPhp;
@@ -16,10 +15,8 @@ class InvalidPHPStanDocTagRuleTest extends RuleTestCase
1615

1716
protected function getRule(): Rule
1817
{
19-
$container = self::getContainer();
2018
return new InvalidPHPStanDocTagRule(
21-
$container->getByType(Lexer::class),
22-
$container->getByType(PhpDocParser::class),
19+
self::getContainer()->getByType(PhpDocStringResolver::class),
2320
);
2421
}
2522

‎tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
namespace PHPStan\Rules\PhpDoc;
44

5-
use PHPStan\PhpDocParser\Lexer\Lexer;
6-
use PHPStan\PhpDocParser\Parser\PhpDocParser;
5+
use PHPStan\PhpDoc\PhpDocStringResolver;
76
use PHPStan\Rules\Rule;
87
use PHPStan\Testing\RuleTestCase;
98
use PHPUnit\Framework\Attributes\RequiresPhp;
@@ -16,10 +15,8 @@ class InvalidPhpDocTagValueRuleTest extends RuleTestCase
1615

1716
protected function getRule(): Rule
1817
{
19-
$container = self::getContainer();
2018
return new InvalidPhpDocTagValueRule(
21-
$container->getByType(Lexer::class),
22-
$container->getByType(PhpDocParser::class),
19+
self::getContainer()->getByType(PhpDocStringResolver::class),
2320
);
2421
}
2522

0 commit comments

Comments
 (0)